Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 4 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /webxr/xrWebGLLayer_opaque_framebuffer_stencil.https.html - WPT Dashboard Interop Dashboard
<!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.RGBA, 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.RGBA, 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>