diff --git a/extensions/proposals/EXT_blend_func_extended/extension.xml b/extensions/EXT_blend_func_extended/extension.xml similarity index 97% rename from extensions/proposals/EXT_blend_func_extended/extension.xml rename to extensions/EXT_blend_func_extended/extension.xml index bb0d8444d..e6c6f8e21 100644 --- a/extensions/proposals/EXT_blend_func_extended/extension.xml +++ b/extensions/EXT_blend_func_extended/extension.xml @@ -1,5 +1,5 @@ - + EXT_blend_func_extended WebGL @@ -9,7 +9,7 @@ Members of the WebGL working group - NN + 58 @@ -27,7 +27,7 @@

INVALID_OPERATION is generated by attempting to draw - when any of the new blend factors are used for an active draw buffer, + when any of the SRC1 blend factors are used for an active draw buffer, which has blending enabled, and the fragment shader does not

    @@ -145,4 +145,4 @@ interface EXT_blend_func_extended { Initial Draft. - + diff --git a/sdk/tests/conformance/extensions/00_test_list.txt b/sdk/tests/conformance/extensions/00_test_list.txt index e79aa2fb5..24450b554 100644 --- a/sdk/tests/conformance/extensions/00_test_list.txt +++ b/sdk/tests/conformance/extensions/00_test_list.txt @@ -1,5 +1,6 @@ --min-version 1.0.3 --max-version 1.9.9 angle-instanced-arrays.html --min-version 1.0.3 --max-version 1.9.9 angle-instanced-arrays-out-of-bounds.html +--min-version 1.0.4 --max-version 1.9.9 ext-blend-func-extended.html --min-version 1.0.3 --max-version 1.9.9 ext-blend-minmax.html --min-version 1.0.4 ext-clip-control.html --min-version 1.0.4 ext-color-buffer-half-float.html diff --git a/sdk/tests/conformance/extensions/ext-blend-func-extended.html b/sdk/tests/conformance/extensions/ext-blend-func-extended.html new file mode 100644 index 000000000..8c84b822d --- /dev/null +++ b/sdk/tests/conformance/extensions/ext-blend-func-extended.html @@ -0,0 +1,26 @@ + + + + + + +WebGL 1.0 EXT_blend_func_extended Conformance Tests + + + + + + +
    +
    + + + + + diff --git a/sdk/tests/conformance2/extensions/00_test_list.txt b/sdk/tests/conformance2/extensions/00_test_list.txt index 0c74048c0..10e51247d 100644 --- a/sdk/tests/conformance2/extensions/00_test_list.txt +++ b/sdk/tests/conformance2/extensions/00_test_list.txt @@ -1,3 +1,4 @@ +--min-version 2.0.1 ext-blend-func-extended.html ext-color-buffer-float.html --min-version 2.0.1 ext-color-buffer-half-float.html --min-version 2.0.1 ext-conservative-depth.html diff --git a/sdk/tests/conformance2/extensions/ext-blend-func-extended.html b/sdk/tests/conformance2/extensions/ext-blend-func-extended.html new file mode 100644 index 000000000..3943e00e5 --- /dev/null +++ b/sdk/tests/conformance2/extensions/ext-blend-func-extended.html @@ -0,0 +1,26 @@ + + + + + + +WebGL 2.0 EXT_blend_func_extended Conformance Tests + + + + + + +
    +
    + + + + + diff --git a/sdk/tests/js/tests/ext-blend-func-extended.js b/sdk/tests/js/tests/ext-blend-func-extended.js new file mode 100644 index 000000000..fc1db8692 --- /dev/null +++ b/sdk/tests/js/tests/ext-blend-func-extended.js @@ -0,0 +1,548 @@ +"use strict"; +description("This test verifies the functionality of the EXT_blend_func_extended extension, if it is available."); + +debug(""); + +var wtu = WebGLTestUtils; +var gl = wtu.create3DContext("c", undefined, contextVersion); +var ext; + +function runTestNoExtension() { + debug(""); + debug("Testing getParameter without the extension"); + shouldBeNull("gl.getParameter(0x88FC /* MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT */)"); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown"); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors"); + + if (contextVersion == 1) { + debug(""); + debug("Testing SRC_ALPHA_SATURATE without the extension"); + + gl.blendFunc(gl.ONE, gl.SRC_ALPHA_SATURATE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "SRC_ALPHA_SATURATE not accepted as blendFunc dfactor"); + gl.blendFuncSeparate(gl.ONE, gl.SRC_ALPHA_SATURATE, gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "SRC_ALPHA_SATURATE not accepted as blendFuncSeparate dstRGB"); + gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.SRC_ALPHA_SATURATE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "SRC_ALPHA_SATURATE not accepted as blendFuncSeparate dstAlpha"); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors"); + } + + debug(""); + debug("Testing SRC1 blend funcs without the extension"); + + const extFuncs = { + SRC1_COLOR_EXT: 0x88F9, + SRC1_ALPHA_EXT: 0x8589, + ONE_MINUS_SRC1_COLOR_EXT: 0x88FA, + ONE_MINUS_SRC1_ALPHA_EXT: 0x88FB + }; + + for (const func in extFuncs) { + gl.blendFunc(extFuncs[func], gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFunc sfactor`); + gl.blendFunc(gl.ONE, extFuncs[func]); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFunc dfactor`); + gl.blendFuncSeparate(extFuncs[func], gl.ONE, gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFuncSeparate srcRGB`); + gl.blendFuncSeparate(gl.ONE, extFuncs[func], gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFuncSeparate dstRGB`); + gl.blendFuncSeparate(gl.ONE, gl.ONE, extFuncs[func], gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFuncSeparate srcAlpha`); + gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, extFuncs[func]); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFuncSeparate dstAlpha`); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors"); + } + + const dbi = gl.getExtension("OES_draw_buffers_indexed"); + if (!dbi) return; + + debug(""); + debug("Testing indexed SRC1 blend funcs without the extension"); + for (const func in extFuncs) { + dbi.blendFunciOES(0, extFuncs[func], gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFunciOES src`); + dbi.blendFunciOES(0, gl.ONE, extFuncs[func]); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFunciOES dst`); + dbi.blendFuncSeparateiOES(0, extFuncs[func], gl.ONE, gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFuncSeparateiOES srcRGB`); + dbi.blendFuncSeparateiOES(0, gl.ONE, extFuncs[func], gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFuncSeparateiOES dstRGB`); + dbi.blendFuncSeparateiOES(0, gl.ONE, gl.ONE, extFuncs[func], gl.ONE); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFuncSeparateiOES srcAlpha`); + dbi.blendFuncSeparateiOES(0, gl.ONE, gl.ONE, gl.ONE, extFuncs[func]); + wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, `${func} not accepted as blendFuncSeparateiOES dstAlpha`); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors"); + } +} + +function runEnumTests() { + debug(""); + debug("Testing enums"); + shouldBe("ext.SRC1_COLOR_EXT", "0x88F9"); + shouldBe("ext.SRC1_ALPHA_EXT", "0x8589"); + shouldBe("ext.ONE_MINUS_SRC1_COLOR_EXT", "0x88FA"); + shouldBe("ext.ONE_MINUS_SRC1_ALPHA_EXT", "0x88FB"); + shouldBe("ext.MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT", "0x88FC"); +} + +function runQueryTests() { + debug(""); + debug("Testing getParameter"); + shouldBeGreaterThanOrEqual("gl.getParameter(ext.MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT)", "1"); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors"); + + if (contextVersion == 1) { + debug(""); + debug("Testing SRC_ALPHA_SATURATE with the extension"); + + gl.blendFunc(gl.ONE, gl.SRC_ALPHA_SATURATE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "SRC_ALPHA_SATURATE accepted as blendFunc dfactor"); + shouldBe("gl.getParameter(gl.BLEND_DST_RGB)", "gl.SRC_ALPHA_SATURATE"); + shouldBe("gl.getParameter(gl.BLEND_DST_ALPHA)", "gl.SRC_ALPHA_SATURATE"); + gl.blendFuncSeparate(gl.ONE, gl.SRC_ALPHA_SATURATE, gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "SRC_ALPHA_SATURATE accepted as blendFuncSeparate dstRGB"); + shouldBe("gl.getParameter(gl.BLEND_DST_RGB)", "gl.SRC_ALPHA_SATURATE"); + gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.SRC_ALPHA_SATURATE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "SRC_ALPHA_SATURATE accepted as blendFuncSeparate dstAlpha"); + shouldBe("gl.getParameter(gl.BLEND_DST_ALPHA)", "gl.SRC_ALPHA_SATURATE"); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors"); + } + + const extFuncs = [ + "SRC1_COLOR_EXT", + "SRC1_ALPHA_EXT", + "ONE_MINUS_SRC1_COLOR_EXT", + "ONE_MINUS_SRC1_ALPHA_EXT" + ]; + + debug(""); + debug("Testing blend state updates with SRC1 blend funcs"); + for (const func of extFuncs) { + gl.blendFunc(ext[func], gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} accepted as blendFunc sfactor`); + shouldBe("gl.getParameter(gl.BLEND_SRC_RGB)", `ext.${func}`); + shouldBe("gl.getParameter(gl.BLEND_SRC_ALPHA)", `ext.${func}`); + gl.blendFunc(gl.ONE, ext[func]); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} accepted as blendFunc dfactor`); + shouldBe("gl.getParameter(gl.BLEND_DST_RGB)", `ext.${func}`); + shouldBe("gl.getParameter(gl.BLEND_DST_ALPHA)", `ext.${func}`); + gl.blendFuncSeparate(ext[func], gl.ONE, gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} accepted as blendFuncSeparate srcRGB`); + shouldBe("gl.getParameter(gl.BLEND_SRC_RGB)", `ext.${func}`); + gl.blendFuncSeparate(gl.ONE, ext[func], gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} accepted as blendFuncSeparate dstRGB`); + shouldBe("gl.getParameter(gl.BLEND_DST_RGB)", `ext.${func}`); + gl.blendFuncSeparate(gl.ONE, gl.ONE, ext[func], gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} accepted as blendFuncSeparate srcAlpha`); + shouldBe("gl.getParameter(gl.BLEND_SRC_ALPHA)", `ext.${func}`); + gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, ext[func]); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} accepted as blendFuncSeparate dstAlpha`); + shouldBe("gl.getParameter(gl.BLEND_DST_ALPHA)", `ext.${func}`); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors"); + } + + const dbi = gl.getExtension("OES_draw_buffers_indexed"); + if (!dbi) return; + + debug(""); + debug("Testing indexed blend state updates with SRC1 blend funcs"); + for (const func of extFuncs) { + dbi.blendFunciOES(0, ext[func], gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} accepted as blendFunciOES src`); + shouldBe("gl.getIndexedParameter(gl.BLEND_SRC_RGB, 0)", `ext.${func}`); + shouldBe("gl.getIndexedParameter(gl.BLEND_SRC_ALPHA, 0)", `ext.${func}`); + dbi.blendFunciOES(0, gl.ONE, ext[func]); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} accepted as blendFunciOES dst`); + shouldBe("gl.getIndexedParameter(gl.BLEND_DST_RGB, 0)", `ext.${func}`); + shouldBe("gl.getIndexedParameter(gl.BLEND_DST_ALPHA, 0)", `ext.${func}`); + dbi.blendFuncSeparateiOES(0, ext[func], gl.ONE, gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} not accepted as blendFuncSeparateiOES srcRGB`); + shouldBe("gl.getIndexedParameter(gl.BLEND_SRC_RGB, 0)", `ext.${func}`); + dbi.blendFuncSeparateiOES(0, gl.ONE, ext[func], gl.ONE, gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} not accepted as blendFuncSeparateiOES dstRGB`); + shouldBe("gl.getIndexedParameter(gl.BLEND_DST_RGB, 0)", `ext.${func}`); + dbi.blendFuncSeparateiOES(0, gl.ONE, gl.ONE, ext[func], gl.ONE); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} not accepted as blendFuncSeparateiOES srcAlpha`); + shouldBe("gl.getIndexedParameter(gl.BLEND_SRC_ALPHA, 0)", `ext.${func}`); + dbi.blendFuncSeparateiOES(0, gl.ONE, gl.ONE, gl.ONE, ext[func]); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, `${func} not accepted as blendFuncSeparateiOES dstAlpha`); + shouldBe("gl.getIndexedParameter(gl.BLEND_DST_ALPHA, 0)", `ext.${func}`); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors"); + } +} + +function runShaderTests(extensionEnabled) { + debug(""); + debug("Testing various shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled")); + + const shaderSets = []; + + const macro100 = `precision mediump float; + void main() { + #ifdef GL_EXT_blend_func_extended + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + #else + #error no GL_EXT_blend_func_extended; + #endif + }`; + const macro300 = `#version 300 es + out mediump vec4 my_FragColor; + void main() { + #ifdef GL_EXT_blend_func_extended + my_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + #else + #error no GL_EXT_blend_func_extended; + #endif + }`; + shaderSets.push([wtu.simpleVertexShader, macro100]); + if (contextVersion == 2) { + shaderSets.push([wtu.simpleVertexShaderESSL300, macro300]); + } + + for (const shaders of shaderSets) { + // Expect the macro shader to succeed ONLY if enabled + if (wtu.setupProgram(gl, shaders)) { + if (extensionEnabled) { + testPassed("Macro defined in shaders when extension is enabled"); + } else { + testFailed("Macro defined in shaders when extension is disabled"); + } + } else { + if (extensionEnabled) { + testFailed("Macro not defined in shaders when extension is enabled"); + } else { + testPassed("Macro not defined in shaders when extension is disabled"); + } + } + } + + shaderSets.length = 0; + + const missing100 = ` + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + gl_SecondaryFragColorEXT = vec4(0.0, 1.0, 0.0, 1.0); + }`; + shaderSets.push([wtu.simpleVertexShader, missing100]); + + const missing300 = `#version 300 es + layout(location = 0) out mediump vec4 oColor0; + layout(location = 0, index = 1) out mediump vec4 oColor1; + void main() { + oColor0 = vec4(1.0, 0.0, 0.0, 1.0); + oColor1 = vec4(0.0, 1.0, 0.0, 1.0); + }`; + if (contextVersion == 2) { + shaderSets.push([wtu.simpleVertexShaderESSL300, missing300]); + } + + // Always expect the shader missing the #extension pragma to fail (whether enabled or not) + for (const shaders of shaderSets) { + if (wtu.setupProgram(gl, shaders)) { + testFailed("Secondary fragment output allowed without #extension pragma"); + } else { + testPassed("Secondary fragment output disallowed without #extension pragma"); + } + } + + shaderSets.length = 0; + + const valid100 = `#extension GL_EXT_blend_func_extended : enable + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + gl_SecondaryFragColorEXT = vec4(0.0, 1.0, 0.0, 1.0); + }`; + shaderSets.push([wtu.simpleVertexShader, valid100]); + + const valid300 = `#version 300 es + #extension GL_EXT_blend_func_extended : enable + layout(location = 0) out mediump vec4 oColor0; + layout(location = 0, index = 1) out mediump vec4 oColor1; + void main() { + oColor0 = vec4(1.0, 0.0, 0.0, 1.0); + oColor1 = vec4(0.0, 1.0, 0.0, 1.0); + }`; + if (contextVersion == 2) { + shaderSets.push([wtu.simpleVertexShaderESSL300, valid300]); + } + + // Try to compile a shader using a secondary fragment output that should only succeed if enabled + for (const shaders of shaderSets) { + if (wtu.setupProgram(gl, shaders)) { + if (extensionEnabled) { + testPassed("Secondary fragment output compiled successfully when extension enabled"); + } else { + testFailed("Secondary fragment output compiled successfully when extension disabled"); + } + } else { + if (extensionEnabled) { + testFailed("Secondary fragment output failed to compile when extension enabled"); + } else { + testPassed("Secondary fragment output failed to compile when extension disabled"); + } + } + } + + // ESSL 3.00: Testing that multiple outputs require explicit locations + if (contextVersion == 2) { + const locations300 = `#version 300 es + #extension GL_EXT_blend_func_extended : enable + out mediump vec4 color0; + out mediump vec4 color1; + void main() { + color0 = vec4(1.0, 0.0, 0.0, 1.0); + color1 = vec4(0.0, 1.0, 0.0, 1.0); + }`; + if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, locations300])) { + testFailed("Multiple fragment outputs compiled successfully without explicit locations"); + } else { + testPassed("Multiple fragment outputs failed to compile without explicit locations"); + } + } +} + +function runMissingOutputsTests() { + debug(""); + debug("Test draw calls with missing fragment outputs"); + + wtu.setupUnitQuad(gl); + gl.blendFunc(gl.ONE, ext.SRC1_COLOR_EXT); + + for (const enabled of [false, true]) { + if (enabled) { + gl.enable(gl.BLEND); + } else { + gl.disable(gl.BLEND); + } + + for (const maskedOut of [false, true]) { + gl.colorMask(!maskedOut, false, false, false); + + const label = `Dual-source blending ${enabled ? "ENABLED" : "DISABLED"}, ` + + `missing fragment outputs, and ` + + `${maskedOut ? "" : "NOT "}all color channels masked out`; + debug(`ESSL 1.00: ${label}`); + + { + const none = "void main() {}"; + wtu.setupProgram(gl, [wtu.simpleVertexShader, none]); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, maskedOut ? gl.NO_ERROR : gl.INVALID_OPERATION, + "no fragment outputs"); + } + + { + const fragColor = ` + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + }`; + wtu.setupProgram(gl, [wtu.simpleVertexShader, fragColor]); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, (!enabled || maskedOut) ? gl.NO_ERROR : gl.INVALID_OPERATION, + "only gl_FragColor"); + } + + { + const secondaryFragColor = `#extension GL_EXT_blend_func_extended : enable + void main() { + gl_SecondaryFragColorEXT = vec4(0.0, 1.0, 0.0, 1.0); + }`; + wtu.setupProgram(gl, [wtu.simpleVertexShader, secondaryFragColor]); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, maskedOut ? gl.NO_ERROR : gl.INVALID_OPERATION, + "only gl_SecondaryFragColorEXT"); + } + + if (contextVersion == 1) continue; + + debug(`ESSL 3.00: ${label}`); + + { + const none = `#version 300 es + void main() {}`; + wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, none]); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, maskedOut ? gl.NO_ERROR : gl.INVALID_OPERATION, + "no fragment outputs"); + } + + { + const color0 = `#version 300 es + out mediump vec4 color0; + void main() { + color0 = vec4(1.0, 0.0, 0.0, 1.0); + }`; + wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, color0]); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, (!enabled || maskedOut) ? gl.NO_ERROR : gl.INVALID_OPERATION, + "only index 0 output"); + } + + { + const color1 = `#version 300 es + #extension GL_EXT_blend_func_extended : enable + layout(location = 0, index = 1) out mediump vec4 color1; + void main() { + color1 = vec4(0.0, 1.0, 0.0, 1.0); + }`; + wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, color1]); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, maskedOut ? gl.NO_ERROR : gl.INVALID_OPERATION, + "only index 1 output"); + } + } + } + gl.colorMask(true, true, true, true); +} + +function runDrawBuffersLimitTests() { + const dbi = gl.getExtension("OES_draw_buffers_indexed"); + if (!dbi) return; + + debug(""); + debug("Testing that dual-source blending limits the number of active draw buffers"); + + const rb0 = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, rb0); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, 1, 1); + + const rb1 = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, rb1); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, 1, 1); + + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + + const fbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb0); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.RENDERBUFFER, rb1); + wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE); + + const fs = `#version 300 es + #extension GL_EXT_blend_func_extended : enable + layout(location = 0, index = 0) out mediump vec4 color0; + layout(location = 0, index = 1) out mediump vec4 color1; + void main() { + color0 = vec4(1.0, 0.0, 0.0, 1.0); + color1 = vec4(0.0, 1.0, 0.0, 1.0); + }`; + wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, fs]); + + wtu.setupUnitQuad(gl); + + // Enable both draw buffers + gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]); + + // Mask out draw buffer 1 to pass missing fragment outputs check + dbi.colorMaskiOES(1, false, false, false, false); + + const extFuncs = [ + "SRC1_COLOR_EXT", + "SRC1_ALPHA_EXT", + "ONE_MINUS_SRC1_COLOR_EXT", + "ONE_MINUS_SRC1_ALPHA_EXT" + ]; + + for (const func of extFuncs) { + for (let slot = 0; slot < 4; slot++) { + let param; + switch (slot) { + case 0: + param = "srcRGB"; + gl.blendFuncSeparate(ext[func], gl.ONE, gl.ONE, gl.ONE); + break; + case 1: + param = "dstRGB"; + gl.blendFuncSeparate(gl.ONE, ext[func], gl.ONE, gl.ONE); + break; + case 2: + param = "srcAlpha"; + gl.blendFuncSeparate(gl.ONE, gl.ONE, ext[func], gl.ONE); + break; + case 3: + param = "dstAlpha"; + gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, ext[func]); + break; + } + debug(`Testing ${func} with ${param}`); + + // Limit must be applied even with blending disabled + gl.disable(gl.BLEND); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blending disabled"); + + gl.enable(gl.BLEND); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blending enabled"); + + // Limit is not applied when non-SRC1 funcs are used + gl.blendFunc(gl.ONE, gl.ONE); + wtu.drawUnitQuad(gl); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "dual-source blending disabled"); + } + } + gl.bindFramebuffer(gl.FRAMEBUFFER, null); +} + +function runBlendingTests() { + debug(""); + debug("Testing rendering with two most common dual-source blending configurations"); + + const fs = `#extension GL_EXT_blend_func_extended : enable + uniform mediump vec4 u_src0; + uniform mediump vec4 u_src1; + void main() { + gl_FragColor = u_src0; + gl_SecondaryFragColorEXT = u_src1; + }`; + const program = wtu.setupProgram(gl, [wtu.simpleVertexShader, fs]); + const uSrc0 = gl.getUniformLocation(program, "u_src0"); + const uSrc1 = gl.getUniformLocation(program, "u_src1"); + + gl.enable(gl.BLEND); + wtu.setupUnitQuad(gl); + gl.clearColor(1.0, 1.0, 1.0, 1.0); + + gl.clear(gl.COLOR_BUFFER_BIT); + gl.blendFunc(gl.ONE, ext.SRC1_COLOR_EXT); + gl.uniform4f(uSrc0, 0.250, 0.375, 0.500, 0.625); + gl.uniform4f(uSrc1, 0.125, 0.125, 0.125, 0.125); + wtu.drawUnitQuad(gl); + wtu.checkCanvas(gl, [96, 128, 159, 191], "Multiply destination by SRC1 and add SRC0", 2); + + gl.clear(gl.COLOR_BUFFER_BIT); + gl.blendFunc(ext.SRC1_COLOR_EXT, ext.ONE_MINUS_SRC1_COLOR_EXT); + gl.uniform4f(uSrc0, 0.125, 0.125, 0.125, 0.125); + gl.uniform4f(uSrc1, 0.500, 0.375, 0.250, 0.125); + wtu.drawUnitQuad(gl); + wtu.checkCanvas(gl, [143, 171, 199, 227], "Per-channel color interpolation using SRC1", 2); +} + +function runTest() { + if (!gl) { + testFailed("context does not exist"); + return; + } + testPassed("context exists"); + + runTestNoExtension(); + runShaderTests(false); + + ext = gl.getExtension("EXT_blend_func_extended"); + wtu.runExtensionSupportedTest(gl, "EXT_blend_func_extended", ext !== null); + + if (ext !== null) { + runEnumTests(); + runQueryTests(); + runShaderTests(true); + runMissingOutputsTests(); + runDrawBuffersLimitTests(); + runBlendingTests(); + } else { + testPassed("No EXT_blend_func_extended support -- this is legal"); + } +} + +runTest(); + +var successfullyParsed = true;