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;