Add OS snapshotting to the gfx sanity test and report whether or not it matches the compositing test results. (bug 1173117 part 4, r=mattwoodrow,vladan)
authorDavid Anderson <danderson@mozilla.com>
Thu, 18 Jun 2015 13:44:04 -0700
changeset 249639 b0a391c7c14aa92ec14f9a56e46f5f4e2d65fc65
parent 249638 9266896a26d4009d46ea04dd5d3a184f596af89f
child 249640 ea281f1bffcba07c69045890e9e5cbdc726b7980
push id61300
push userdanderson@mozilla.com
push dateThu, 18 Jun 2015 20:45:06 +0000
treeherdermozilla-inbound@b0a391c7c14a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, vladan
bugs1173117
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Add OS snapshotting to the gfx sanity test and report whether or not it matches the compositing test results. (bug 1173117 part 4, r=mattwoodrow,vladan)
toolkit/components/gfx/SanityTest.js
toolkit/components/telemetry/Histograms.json
--- a/toolkit/components/gfx/SanityTest.js
+++ b/toolkit/components/gfx/SanityTest.js
@@ -12,23 +12,30 @@ Cu.import("resource://gre/modules/XPCOMU
 
 const PAGE_WIDTH=72;
 const PAGE_HEIGHT=136;
 const DRIVER_PREF="sanity-test.driver-version";
 const DEVICE_PREF="sanity-test.device-id";
 const VERSION_PREF="sanity-test.version";
 const DISABLE_VIDEO_PREF="media.hardware-video-decoding.failed";
 const RUNNING_PREF="sanity-test.running";
+const OS_SNAPSHOT_TIMEOUT_SEC=3;
 
 // GRAPHICS_SANITY_TEST histogram enumeration values
 const TEST_PASSED=0;
 const TEST_FAILED_RENDER=1;
 const TEST_FAILED_VIDEO=2;
 const TEST_CRASHED=3;
 
+// GRAPHICS_SANITY_TEST_OS_SNAPSHOT histogram enumeration values
+const SNAPSHOT_OK=0;
+const SNAPSHOT_INCORRECT=1;
+const SNAPSHOT_ERROR=2;
+const SNAPSHOT_TIMEOUT=3;
+
 function testPixel(ctx, x, y, r, g, b, a, fuzz) {
   var data = ctx.getImageData(x, y, 1, 1);
 
   if (Math.abs(data.data[0] - r) <= fuzz &&
       Math.abs(data.data[1] - g) <= fuzz &&
       Math.abs(data.data[2] - b) <= fuzz &&
       Math.abs(data.data[3] - a) <= fuzz) {
     return true;
@@ -41,26 +48,53 @@ function reportResult(val) {
     let histogram = Services.telemetry.getHistogramById("GRAPHICS_SANITY_TEST");
     histogram.add(val);
   } catch (e) {}
 
   Preferences.set(RUNNING_PREF, false);
   Services.prefs.savePrefFile(null);
 }
 
+function reportSnapshotResult(val) {
+  let histogram = Services.telemetry.getHistogramById("GRAPHICS_SANITY_TEST_OS_SNAPSHOT");
+  histogram.add(val);
+}
+
 function takeWindowSnapshot(win, ctx) {
-  // Take a snapshot of the window contents, and then close the window
-  //
   // TODO: drawWindow reads back from the gpu's backbuffer, which won't catch issues with presenting
   // the front buffer via the window manager. Ideally we'd use an OS level API for reading back
   // from the desktop itself to get a more accurate test.
   var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
   ctx.drawWindow(win.ownerGlobal, 0, 0, PAGE_WIDTH, PAGE_HEIGHT, "rgb(255,255,255)", flags);
 }
 
+function takeWidgetSnapshot(win, canvas, ctx) {
+  ctx.clearRect(0, 0, canvas.width, canvas.height);
+  ctx.drawWidgetAsOnScreen(win.ownerGlobal);
+}
+
+function testWidgetSnapshot(win, canvas, ctx) {
+  try {
+    takeWidgetSnapshot(win, canvas, ctx);
+    if (verifyVideoRendering(ctx)) {
+      reportSnapshotResult(SNAPSHOT_OK);
+    } else {
+      reportSnapshotResult(SNAPSHOT_INCORRECT);
+    }
+  } catch (e) {
+    reportSnapshotResult(SNAPSHOT_ERROR);
+  }
+}
+
+function setTimeout(aMs, aCallback) {
+  var timer = Components.classes["@mozilla.org/timer;1"]
+              .createInstance(Components.interfaces.nsITimer);
+  timer.initWithCallback(aCallback, aMs, Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
 // Verify that all the 4 coloured squares of the video
 // render as expected (with a tolerance of 64 to allow for
 // yuv->rgb differences between platforms).
 //
 // The video is 64x64, and is split into quadrants of
 // different colours. The top left of the video is 8,72
 // and we test a pixel 10,10 into each quadrant to avoid
 // blending differences at the edges.
@@ -87,33 +121,122 @@ function testCompositor(win, ctx) {
   reportResult(TEST_PASSED);
   return true;
 }
 
 let listener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   win: null,
+  utils: null,
   canvas: null,
 
+  // State flow:
+  //   onload -> WM_PAINT -> MozAfterPaint -> ready
+  //   WM_PAINT -> onload -> MozAfterPaint -> ready
+  //
+  // We always wait for the low-level paint message because this is what tells
+  // us whether or not the OS has actually started drawing.
+  windowLoaded: false,   // onload event has fired.
+  windowReady: false,    // widget has received an OS-level paint request.
+
   scheduleTest: function(win) {
     this.win = win;
     this.win.onload = this.onWindowLoaded.bind(this);
+    this.utils = this.win.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils);
+
+    let observerService = Cc["@mozilla.org/observer-service;1"].
+                          getService(Components.interfaces.nsIObserverService);
+    observerService.addObserver(this, "widget-first-paint", false);
   },
 
   onWindowLoaded: function() {
     this.canvas = this.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     this.canvas.setAttribute("width", PAGE_WIDTH);
     this.canvas.setAttribute("height", PAGE_HEIGHT);
     this.ctx = this.canvas.getContext("2d");
 
-    testCompositor(this.win, this.ctx);
+    // Perform the compositor backbuffer test, which currently we use for
+    // actually deciding whether to enable hardware media decoding.
+    if (!testCompositor(this.win, this.ctx)) {
+      this.endTest();
+      return;
+    }
+
+    // Wait to perform the OS snapshot test. Since this waits for a series of
+    // events to occur, we set a timeout in case nothing happens.
+    setTimeout(OS_SNAPSHOT_TIMEOUT_SEC * 1000, (() => {
+      if (this.win) {
+        reportSnapshotResult(SNAPSHOT_TIMEOUT);
+        this.endTest();
+      }
+    }));
+
+    this.windowLoaded = true;
+    if (this.windowReady) {
+      this.waitForPaintsFlushed();
+    }
+  },
+
+  // Watch for the first OS-level paint message to the window.
+  observe: function(aSubject, aTopic, aData) {
+    if (aSubject != this.win || aTopic != "widget-first-paint") {
+      return;
+    }
+
+    this.windowReady = true;
+    if (this.windowLoaded) {
+      this.waitForPaintsFlushed();
+    }
+  },
+
+  // Wait for all layout-induced paints to flush.
+  waitForPaintsFlushed: function() {
+    // If the test ended prematurely due to a timeout, just ignore the event.
+    if (!this.win) {
+      return;
+    }
+
+    if (this.utils.isMozAfterPaintPending) {
+      let paintListener = (() => {
+        if (this.utils && this.utils.isMozAfterPaintPending) {
+          return;
+        }
+
+        this.win.removeEventListener("MozAfterPaint", paintListener);
+
+        if (this.utils) {
+          // Painting is finished, we will fail the above
+          // isMozAfterPaintPending test now.
+          this.waitForPaintsFlushed();
+        }
+      });
+      this.win.addEventListener("MozAfterPaint", paintListener);
+      return;
+    }
+
+    testWidgetSnapshot(this.win, this.canvas, this.ctx);
+    this.endTest();
+  },
+
+  endTest: function() {
+    if (!this.win) {
+      return;
+    }
 
     this.win.ownerGlobal.close();
-  },
+    this.win = null;
+    this.utils = null;
+    this.canvas = null;
+
+    let observerService = Cc["@mozilla.org/observer-service;1"].
+                          getService(Components.interfaces.nsIObserverService);
+    observerService.removeObserver(this, "widget-first-paint");
+  }
 };
 
 function SanityTest() {}
 SanityTest.prototype = {
   classID: Components.ID("{f3a8ca4d-4c83-456b-aee2-6a2cbf11e9bd}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8227,10 +8227,18 @@
   },
   "GRAPHICS_DRIVER_STARTUP_TEST": {
     "alert_emails": ["danderson@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 20,
     "releaseChannelCollection": "opt-out",
     "description": "Reports whether or not graphics drivers crashed during startup."
+  },
+  "GRAPHICS_SANITY_TEST_OS_SNAPSHOT": {
+    "alert_emails": ["danderson@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 10,
+    "releaseChannelCollection": "opt-out",
+    "description": "Reports whether the graphics sanity test passed an OS snapshot test. 0=Pass, 1=Fail, 2=Error, 3=Timed out."
   }
 }