<!--
Copyright (c) 2019 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL stencil mask/func front-state-back-state equality test</title>
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/webgl-test-utils.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>

<script>
"use strict";
var wtu = WebGLTestUtils;
description("Tests that stencil mask/func are validated correctly when the front state and back state differ.");

var gl;

function checkDrawError(errIfMismatch) {
    wtu.shouldGenerateGLError(gl, errIfMismatch, "wtu.dummySetProgramAndDrawNothing(gl)");
}

function setStencilMask(mask) {
    wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilMaskSeparate(gl.FRONT, " + mask[0] + ")");
    wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilMaskSeparate(gl.BACK,  " + mask[1] + ")");
}

function testStencilMaskCase(mask, error) {
    setStencilMask(mask);
    // If an error is generated, it should be at draw time.
    checkDrawError(error);
}

function testStencilMask(errIfMismatch) {
    testStencilMaskCase([0, 256], gl.NO_ERROR);
    testStencilMaskCase([1, 256], errIfMismatch);
    testStencilMaskCase([1, 257], gl.NO_ERROR);
    testStencilMaskCase([1, 258], errIfMismatch);
    wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilMask(1023)", "resetting stencilMask");
}

function setStencilFunc(ref, mask) {
    wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFuncSeparate(gl.FRONT, gl.ALWAYS, " + ref[0] + ", " + mask[0] + ")");
    wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFuncSeparate(gl.BACK,  gl.ALWAYS, " + ref[1] + ", " + mask[1] + ")");
}

function testStencilFuncCase(ref, mask, error) {
    setStencilFunc(ref, mask);
    // If an error is generated, it should be at draw time.
    checkDrawError(error);
}

function testStencilFunc(errIfMismatch) {
    testStencilFuncCase([ 256,  257], [1023, 1023], gl.NO_ERROR);
    testStencilFuncCase([ 256,  254], [1023, 1023], errIfMismatch);

    testStencilFuncCase([  -1,    0], [1023, 1023], gl.NO_ERROR);
    testStencilFuncCase([  -1,  254], [1023, 1023], errIfMismatch);

    testStencilFuncCase([   0,    0], [   1,  257], gl.NO_ERROR);
    testStencilFuncCase([   0,    0], [   1,  258], errIfMismatch);

    testStencilFuncCase([   1,    1], [1024, 2048], gl.NO_ERROR);
    testStencilFuncCase([   1,    1], [2048, 1024], gl.NO_ERROR);

    testStencilFuncCase([  -1,   -1], [1023, 1023], gl.NO_ERROR);
    testStencilFuncCase([  -1,    0], [1023, 1023], gl.NO_ERROR);
    testStencilFuncCase([   0,   -1], [1023, 1023], gl.NO_ERROR);
    testStencilFuncCase([   0,    0], [1023, 1023], gl.NO_ERROR);

    testStencilFuncCase([  -1,  255], [1023, 1023], errIfMismatch);
    testStencilFuncCase([   0,  256], [1023, 1023], errIfMismatch);
    testStencilFuncCase([   0, 1024], [1023, 1023], errIfMismatch);
    testStencilFuncCase([   1,  257], [1023, 1023], errIfMismatch);
    testStencilFuncCase([ 255,   -1], [1023, 1023], errIfMismatch);
    testStencilFuncCase([ 256,    0], [1023, 1023], errIfMismatch);
    testStencilFuncCase([1024,    0], [1023, 1023], errIfMismatch);
    testStencilFuncCase([ 257,    1], [1023, 1023], errIfMismatch);

    wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.stencilFunc(gl.ALWAYS, 0, 1023)", "resetting stencilFunc");
}

//
// Tests of the default framebuffer
//

debug("");

debug("Testing default framebuffer with { stencil: true }");
gl = wtu.create3DContext(undefined, { stencil: true });
{
    gl.enable(gl.STENCIL_TEST);
    testStencilMaskCase([1, 256], gl.INVALID_OPERATION);
    testStencilFuncCase([256, 0], [1023, 1023], gl.INVALID_OPERATION);
}

debug("Testing default framebuffer with { stencil: false }");
gl = wtu.create3DContext(undefined, { stencil: false });
{
    // with { stencil: false }
    gl.enable(gl.STENCIL_TEST);
    testStencilMaskCase([1, 256], gl.NO_ERROR);
    testStencilFuncCase([256, 0], [1023, 1023], gl.NO_ERROR);
}
// (continue using this GL context for the other tests)

//
// Tests with a framebuffer object
//

const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
const colorRB = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, colorRB);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 1, 1);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorRB);

wtu.glErrorShouldBe(gl, gl.NO_ERROR, "initial framebuffer setup")

function runWithStencilSettings(haveDepthBuffer, haveStencilBuffer, enableStencilTest, fn) {
    let rbo = null;
    let attachment;
    if (haveDepthBuffer || haveStencilBuffer) {
        rbo = gl.createRenderbuffer();
        gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);

        let internalformat;
        if (haveDepthBuffer && haveStencilBuffer) {
            internalformat = gl.DEPTH_STENCIL;
            attachment = gl.DEPTH_STENCIL_ATTACHMENT;
        } else if (haveDepthBuffer) {
            internalformat = gl.DEPTH_COMPONENT16;
            attachment = gl.DEPTH_ATTACHMENT;
        } else if (haveStencilBuffer) {
            internalformat = gl.STENCIL_INDEX8;
            attachment = gl.STENCIL_ATTACHMENT;
        }
        gl.renderbufferStorage(gl.RENDERBUFFER, internalformat, 1, 1);
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, rbo);
    }
    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "depth/stencil renderbuffer setup")

    if (enableStencilTest) {
        gl.enable(gl.STENCIL_TEST);
    } else {
        gl.disable(gl.STENCIL_TEST);
    }

    fn();

    if (rbo) {
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, null);
        gl.bindRenderbuffer(gl.RENDERBUFFER, null);
        gl.deleteRenderbuffer(rbo);
    }

    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "depth/stencil renderbuffer cleanup")
}

function testStencilSettings(haveDepthBuffer, haveStencilBuffer, enableStencilTest, errIfMismatch) {
    debug("");
    debug("With depthbuffer=" + haveDepthBuffer +
            ", stencilbuffer=" + haveStencilBuffer +
            ", stencilTest=" + enableStencilTest +
            ", expecting error=" + wtu.glEnumToString(gl, errIfMismatch) +
            " for mismatching mask or func settings.");

    runWithStencilSettings(haveDepthBuffer, haveStencilBuffer, enableStencilTest, () => {
        // Errors should be the same for both mask and func, because stencil test
        // and stencil write are always enabled/disabled in tandem.
        testStencilMask(errIfMismatch);
        testStencilFunc(errIfMismatch);
    });
}

debug("");
debug("Base case checks:");
testStencilMaskCase([0, 0], gl.NO_ERROR);
testStencilFuncCase([0, 0], [1023, 1023], gl.NO_ERROR);

//                  haveDepthBuffer
//                  |      haveStencilBuffer
//                  |      |      enableStencilTest
//                  |      |      |      errIfMismatch
testStencilSettings(false, false, false, gl.NO_ERROR);
testStencilSettings( true, false, false, gl.NO_ERROR);
testStencilSettings(false,  true, false, gl.NO_ERROR);
testStencilSettings( true,  true, false, gl.NO_ERROR);

testStencilSettings(false, false,  true, gl.NO_ERROR);
testStencilSettings( true, false,  true, gl.NO_ERROR);
testStencilSettings(false,  true,  true, gl.INVALID_OPERATION);
testStencilSettings( true,  true,  true, gl.INVALID_OPERATION);

//
// Tests to make sure the stencil validation check, if cached, is invalidated correctly.
//

debug("");

debug("Setup for stencil validation cache invalidation tests");
setStencilMask([1, 258]);
setStencilFunc([0, 256], [1023, 1023]);

debug("Test with enabling/disabling stencil test");
runWithStencilSettings(false, true, false, () => {
    checkDrawError(gl.NO_ERROR);
    gl.enable(gl.STENCIL_TEST);
    checkDrawError(gl.INVALID_OPERATION);
    gl.disable(gl.STENCIL_TEST);
    checkDrawError(gl.NO_ERROR);
});

debug("Test with swapping in a new FBO");
runWithStencilSettings(false, false, true, () => {
    // no error with no stencil buffer
    checkDrawError(gl.NO_ERROR);

    // swap in a new FBO with a stencil buffer
    const fb2 = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
    gl.bindRenderbuffer(gl.RENDERBUFFER, colorRB);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorRB);
    const rbo = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, 1, 1);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, rbo);
    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);

    // this draw sholud detect the new fbo state
    checkDrawError(gl.INVALID_OPERATION);

    gl.deleteFramebuffer(fb2);
    gl.deleteRenderbuffer(rbo)
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);
});

debug("Test with adding a stencil attachment");
runWithStencilSettings(false, false, true, () => {
    // no error with no stencil buffer
    checkDrawError(gl.NO_ERROR);

    // add a stencil attachment
    const rbo = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, 1, 1);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, rbo);
    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);

    // this draw sholud detect the new fbo state
    checkDrawError(gl.INVALID_OPERATION);

    gl.deleteRenderbuffer(rbo)
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);
});

debug("Test with reallocating the DEPTH_STENCIL attachment from depth to depth+stencil");
runWithStencilSettings(false, false, true, () => {
    // attach a depth buffer to the DEPTH_STENCIL attachment
    const rbo = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, rbo);
    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);

    // this draw is invalid, but it still might trigger caching of the stencil validation
    checkDrawError(gl.INVALID_FRAMEBUFFER_OPERATION);

    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, 1, 1);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);
    // this draw sholud detect the new fbo state
    checkDrawError(gl.INVALID_OPERATION);

    gl.deleteRenderbuffer(rbo)
    wtu.glErrorShouldBe(gl, gl.NO_ERROR);
});

gl.deleteFramebuffer(fb);
gl.deleteRenderbuffer(colorRB);

var successfullyParsed = true;
</script>

<script src="../../js/js-test-post.js"></script>
</body>
</html>
