<!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>
