Bug 1620751 [wpt PR 22129] - Adding a test for bug 1057571 fix, a=testonly
authorArtem Bolgar <artyom@fb.com>
Thu, 12 Mar 2020 12:10:08 +0000
changeset 518592 bca11a6bdc3b185c4d05f66e9d0c1e325ae13d0c
parent 518591 00cd7d7046a1aaaee5b039a682392f4520f7988c
child 518593 8fbffd01fd04d736fe31bead70fa9b437f8b564e
push id37213
push usershindli@mozilla.com
push dateFri, 13 Mar 2020 21:46:16 +0000
treeherdermozilla-central@8ef0a54d7715 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1620751, 22129, 1057571, 2084067, 2093391, 748462
milestone76.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
Bug 1620751 [wpt PR 22129] - Adding a test for bug 1057571 fix, a=testonly Automatic update from web-platform-tests Adding a test for bug 1057571 fix Bug 1057571 (broken stencil buffer functionality in WebXR) was fixed in https://chromium-review.googlesource.com/c/chromium/src/+/2084067. This commit adds a test for the issue. Bug: 1057571 Change-Id: I9390aa47a3eabc1d8cf2d0717fb020b70e455ccb Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2093391 Commit-Queue: Artem Bolgar <artyom@fb.com> Reviewed-by: Brandon Jones <bajones@chromium.org> Reviewed-by: Alexander Cooper <alcooper@chromium.org> Cr-Commit-Position: refs/heads/master@{#748462} -- wpt-commits: 799a9ee8d4319e0f53af37b00e8ce6362c514f82 wpt-pr: 22129
testing/web-platform/tests/webxr/resources/webxr_test_constants.js
testing/web-platform/tests/webxr/resources/webxr_util.js
testing/web-platform/tests/webxr/xrWebGLLayer_opaque_framebuffer_stencil.https.html
--- a/testing/web-platform/tests/webxr/resources/webxr_test_constants.js
+++ b/testing/web-platform/tests/webxr/resources/webxr_test_constants.js
@@ -80,18 +80,18 @@ const VALID_BOUNDS = [
     { x: 3.5, z: 0.0 },
     { x: 3.0, z: 2.0 },
     { x: -3.0, z: 2.0 },
     { x: -3.5, z: 0.0 },
     { x: -3.0, z: -2.0 }
 ];
 
 const VALID_RESOLUTION = {
-    width: 20,
-    height: 20
+    width: 200,
+    height: 200
 };
 
 const LEFT_OFFSET = {
     position: [-0.1, 0, 0],
     orientation: [0, 0, 0, 1]
 };
 
 const RIGHT_OFFSET = {
--- a/testing/web-platform/tests/webxr/resources/webxr_util.js
+++ b/testing/web-platform/tests/webxr/resources/webxr_util.js
@@ -29,30 +29,32 @@ function xr_promise_test(name, func, pro
   }, name, properties);
 }
 
 // A test function which runs through the common steps of requesting a session.
 // Calls the passed in test function with the session, the controller for the
 // device, and the test object.
 // Requires a webglCanvas on the page.
 function xr_session_promise_test(
-    name, func, fakeDeviceInit, sessionMode, sessionInit, properties) {
+    name, func, fakeDeviceInit, sessionMode, sessionInit, properties, glcontextPropertiesParam, gllayerPropertiesParam) {
   let testDeviceController;
   let testSession;
   let sessionObjects = {};
+  const glcontextProperties = (glcontextPropertiesParam) ? glcontextPropertiesParam : {};
+  const gllayerProperties = (gllayerPropertiesParam) ? gllayerPropertiesParam : {};
 
   const webglCanvas = document.getElementsByTagName('canvas')[0];
   // We can't use assert_true here because it causes the wpt testharness to treat
   // this as a test page and not as a test.
   if (!webglCanvas) {
     promise_test(async (t) => {
       Promise.reject('xr_session_promise_test requires a canvas on the page!');
     }, name, properties);
   }
-  let gl = webglCanvas.getContext('webgl', {alpha: false, antialias: false});
+  let gl = webglCanvas.getContext('webgl', {alpha: false, antialias: false, ...glcontextProperties});
   sessionObjects.gl = gl;
 
   xr_promise_test(
       name,
       (t) => {
           // Ensure that any pending sessions are ended when done. This needs to
           // use a cleanup function to ensure proper sequencing. If this were
           // done in a .then() for the success case, a test that expected
@@ -72,17 +74,17 @@ function xr_session_promise_test(
               })
               .then(() => new Promise((resolve, reject) => {
                       // Perform the session request in a user gesture.
                       navigator.xr.test.simulateUserActivation(() => {
                         navigator.xr.requestSession(sessionMode, sessionInit || {})
                             .then((session) => {
                               testSession = session;
                               session.mode = sessionMode;
-                              let glLayer = new XRWebGLLayer(session, gl);
+                              let glLayer = new XRWebGLLayer(session, gl, gllayerProperties);
                               glLayer.context = gl;
                               // Session must have a baseLayer or frame requests
                               // will be ignored.
                               session.updateRenderState({
                                   baseLayer: glLayer
                               });
                               sessionObjects.glLayer = glLayer;
                               resolve(func(session, testDeviceController, t, sessionObjects));
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/webxr/xrWebGLLayer_opaque_framebuffer_stencil.https.html
@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webxr_util.js"></script>
+<script src="resources/webxr_test_constants.js"></script>
+<canvas />
+
+<script>
+let immersiveTestName = "Ensure that the framebuffer given by the WebGL layer" +
+  " works with stencil for immersive";
+let nonImmersiveTestName = "Ensure that the framebuffer given by the WebGL layer" +
+  " works with stencil for non-immersive";
+
+let fakeDeviceInitParams = TRACKED_IMMERSIVE_DEVICE;
+
+function createShader(gl, type, source) {
+  var shader = gl.createShader(type);
+  gl.shaderSource(shader, source);
+  gl.compileShader(shader);
+  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+  if (success) {
+    return shader;
+  }
+
+  gl.deleteShader(shader);
+}
+
+function createProgram(gl, vertexShader, fragmentShader) {
+  var program = gl.createProgram();
+  gl.attachShader(program, vertexShader);
+  gl.attachShader(program, fragmentShader);
+  gl.linkProgram(program);
+  var success = gl.getProgramParameter(program, gl.LINK_STATUS);
+  if (success) {
+    return program;
+  }
+
+  gl.deleteProgram(program);
+}
+
+let testFunction =
+  (session, fakeDeviceController, t, sessionObjects) => new Promise((resolve, reject) => {
+  const gl = sessionObjects.gl;
+  const webglLayer = sessionObjects.glLayer;
+  const xrFramebuffer = webglLayer.framebuffer;
+  const webglCanvas = document.getElementsByTagName('canvas')[0];
+
+  session.requestAnimationFrame((time, xrFrame) => {
+    t.step(() => {
+      // Make sure we're starting with a clean error slate.
+      gl.getError();
+      assert_equals(gl.getError(), gl.NO_ERROR, "Should not initially have any errors");
+
+      if (session.mode === 'inline') {
+        // Creating a layer with an inline session should return a framebuffer of
+        // null, and as such most of these tests won't apply.
+        assert_equals(xrFramebuffer, null, 'inline, fbo = null');
+        // We need to set canvas size here for inline session testing, since
+        // xrFramebuffer is null.
+        webglCanvas.width = webglCanvas.height = 300;
+        gl.bindFramebuffer(gl.FRAMEBUFFER, xrFramebuffer);
+        assert_equals(gl.getError(), gl.NO_ERROR, "Binding default framebuffer for inline session");
+      } else {
+        assert_not_equals(xrFramebuffer, null, 'immersive, fbo != null');
+        gl.bindFramebuffer(gl.FRAMEBUFFER, xrFramebuffer);
+        assert_equals(gl.getError(), gl.NO_ERROR, "Binding WebGLLayer framebuffer");
+      }
+
+      // Framebuffer status must be complete inside of a XR frame callback.
+      assert_equals(gl.checkFramebufferStatus(gl.FRAMEBUFFER), gl.FRAMEBUFFER_COMPLETE, "FBO complete");
+    });
+    gl.clearColor(1, 1, 1, 1);
+
+    const vs = `
+    attribute vec4 position;
+    uniform mat4 matrix;
+    void main() {
+      gl_Position = matrix * position;
+    }
+    `;
+
+    const fs = `
+    precision mediump float;
+    uniform vec4 color;
+    void main() {
+      gl_FragColor = color;
+    }
+    `;
+    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vs);
+    const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fs);
+    const program = createProgram(gl, vertexShader, fragmentShader);
+
+    const posLoc = gl.getAttribLocation(program, 'position');
+    const matLoc = gl.getUniformLocation(program, 'matrix');
+    const colorLoc = gl.getUniformLocation(program, 'color');
+
+    const buf = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+       0, -1,
+       1,  1,
+      -1,  1,
+    ]), gl.STATIC_DRAW);
+
+    gl.enableVertexAttribArray(posLoc);
+    gl.vertexAttribPointer(
+        posLoc,    // attribute location
+        2,         // 2 value per vertex
+        gl.FLOAT,  // 32bit floating point values
+        false,     // don't normalize
+        0,         // stride (0 = base on type and size)
+        0,         // offset into buffer
+    );
+
+    let xrViewport;
+    if (session.mode == 'inline') {
+      xrViewport = { x: 0, y: 0, width: webglCanvas.width, height: webglCanvas.height };
+    } else {
+      xrViewport = { x: 0, y: 0, width: webglLayer.framebufferWidth, height: webglLayer.framebufferHeight };
+    }
+
+    gl.viewport(xrViewport.x, xrViewport.y, xrViewport.width, xrViewport.height);
+    gl.scissor(xrViewport.x, xrViewport.y, xrViewport.width, xrViewport.height);
+
+    // clear the stencil to 0 (the default)
+    gl.stencilMask(0xFF);
+    gl.clearStencil(0x0);
+    gl.disable( gl.SCISSOR_TEST );
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
+
+    gl.useProgram(program);
+    let m4 = [1, 0, 0, 0,
+              0, 1, 0, 0,
+              0, 0, 1, 0,
+              0, 0, 0, 1];
+
+    // turn on the stencil
+    gl.enable(gl.STENCIL_TEST);
+
+    // Drawing into a stencil, always passes, ref val 1, mask 0xFF
+    gl.stencilFunc(
+       gl.ALWAYS,
+       1,
+       0xFF,
+    );
+    // Set it to replace with the ref val (which is 1)
+    gl.stencilOp(
+       gl.KEEP,     // stencil test fails
+       gl.KEEP,     // depth test fails
+       gl.REPLACE,  // both tests pass
+    );
+
+    m4[0] = 0.2; m4[5] = 0.2; // scale x and y
+    // draw a white triangle
+    gl.uniform4fv(colorLoc, [1, 1, 1, 1]); // white
+    gl.uniformMatrix4fv(matLoc, false, m4);
+    gl.colorMask(false, false, false, false);
+    gl.drawArrays(gl.TRIANGLES, 0, 3);
+
+    gl.colorMask(true, true, true, true);
+
+    // Stencil must be 0
+    gl.stencilFunc(
+       gl.EQUAL,
+       0,
+       0xFF,
+    );
+    // keep stencil unmodified during the draw pass
+    gl.stencilOp(
+       gl.KEEP,     // stencil test fails
+       gl.KEEP,     // depth test fails
+       gl.KEEP,     // both tests pass
+    );
+
+    m4[0] = 0.9; m4[5] = -0.9; // scale x and y
+    // draw a large green triangle
+    gl.uniform4fv(colorLoc, [0, 1, 0, 1]); // green
+    gl.uniformMatrix4fv(matLoc, false, m4);
+    gl.drawArrays(gl.TRIANGLES, 0, 3);
+
+    gl.flush();
+    gl.finish();
+    let pixels = new Uint8Array(4);
+
+    // check that the main color is used correctly (green)
+    pixels[0] = pixels[1] = pixels[2] = pixels[3] = 30;
+    gl.readPixels(xrViewport.x + xrViewport.width / 2, xrViewport.y + xrViewport.height/4, 1, 1, gl.RGB, gl.UNSIGNED_BYTE, pixels);
+    if (pixels[0] == 0x0 && pixels[1] == 0xFF && pixels[2] == 0x0) { // green?
+      // PASSED.
+    } else if (pixels[0] == 0xFF && pixels[1] == 0xFF && pixels[2] == 0xFF) { // white?
+      reject("Failed, white detected, must be green");
+    } else {
+      reject("Failed, readPixels (1) didn't work, gl error = " + gl.getError() + ", pixel = " +pixels[0] + " " +pixels[1] + " " +pixels[2]);
+    }
+
+    // check if stencil worked, i.e. white pixels in the center
+    pixels[0] = pixels[1] = pixels[2] = pixels[3] = 20;
+    gl.readPixels(xrViewport.x + xrViewport.width / 2, xrViewport.y + xrViewport.height/2, 1, 1, gl.RGB, gl.UNSIGNED_BYTE, pixels);
+    if (pixels[0] == 0xFF && pixels[1] == 0xFF && pixels[2] == 0xFF) { // white?
+      // PASSED.
+    } else if (pixels[0] == 0x0 && pixels[1] == 0xFF && pixels[2] == 0x0) { // green?
+      reject("Failed, green detected, must be white");
+    } else {
+      reject("Failed, readPixels (2) didn't work, gl error = " + gl.getError() + ", pixel = " +pixels[0] + " " +pixels[1] + " " +pixels[2]);
+    }
+
+    // Finished.
+    resolve();
+  });
+});
+
+const gl_props = { antialias: false, alpha: false, stencil: true, depth: true };
+
+// mac has issues with readPixels from the default fbo.
+// skipping this test for Mac.
+if (navigator.platform.indexOf("Mac") == -1) {
+  xr_session_promise_test(
+    nonImmersiveTestName, testFunction, fakeDeviceInitParams, 'inline', {}, {}, gl_props, gl_props);
+}
+
+xr_session_promise_test(
+  immersiveTestName, testFunction, fakeDeviceInitParams, 'immersive-vr', {}, {}, gl_props, gl_props);
+
+</script>