| <!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> |
| |
| <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 = sessionObjects.gl.canvas; |
| |
| 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> |