Skip to content

Commit

Permalink
Initial conformance tests for WEBGL_shader_pixel_local_storage
Browse files Browse the repository at this point in the history
Creates a set of tests that verify the non-normative WebGL behavior, as
well as providing some scaffolding to verify the ANGLE extension is
properly hooked up.

If these all look good, we can start porting tests from the ANGLE
extension next.
  • Loading branch information
csmartdalton committed Mar 17, 2023
1 parent 539037f commit 13531ea
Show file tree
Hide file tree
Showing 3 changed files with 344 additions and 9 deletions.
18 changes: 9 additions & 9 deletions extensions/WEBGL_shader_pixel_local_storage/extension.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ interface WEBGL_shader_pixel_local_storage {
<function name="framebufferPixelLocalClearValuefvWEBGL" type="undefined">
<param name="plane" type="GLint"/>
<param name="value" type="Float32List"/>
<param name="src_offset" type="GLuint" default="0"/>
<param name="srcOffset" type="GLuint" default="0"/>
Sets the floating point clear value for the given plane.
<div class="note">
If <code>value</code> has less than <code>src_offset + 4</code> elements, generates an
<code>INVALID_OPERATION</code> error.
If <code>value</code> has less than <code>srcOffset + 4</code> elements, generates an
<code>INVALID_VALUE</code> error.
</div>
</function>
</newfun>
Expand All @@ -143,11 +143,11 @@ interface WEBGL_shader_pixel_local_storage {
<function name="framebufferPixelLocalClearValueivWEBGL" type="undefined">
<param name="plane" type="GLint"/>
<param name="value" type="Int32List"/>
<param name="src_offset" type="GLuint" default="0"/>
<param name="srcOffset" type="GLuint" default="0"/>
Sets the integer clear value for the given plane.
<div class="note">
If <code>value</code> has less than <code>src_offset + 4</code> elements, generates an
<code>INVALID_OPERATION</code> error.
If <code>value</code> has less than <code>srcOffset + 4</code> elements, generates an
<code>INVALID_VALUE</code> error.
</div>
</function>
</newfun>
Expand All @@ -156,11 +156,11 @@ interface WEBGL_shader_pixel_local_storage {
<function name="framebufferPixelLocalClearValueuivWEBGL" type="undefined">
<param name="plane" type="GLint"/>
<param name="value" type="Uint32List"/>
<param name="src_offset" type="GLuint" default="0"/>
<param name="srcOffset" type="GLuint" default="0"/>
Sets the unsigned integer clear value for the given plane.
<div class="note">
If <code>value</code> has less than <code>src_offset + 4</code> elements, generates an
<code>INVALID_OPERATION</code> error.
If <code>value</code> has less than <code>srcOffset + 4</code> elements, generates an
<code>INVALID_VALUE</code> error.
</div>
</function>
</newfun>
Expand Down
1 change: 1 addition & 0 deletions sdk/tests/conformance2/extensions/00_test_list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ promoted-extensions-in-shaders.html
--min-version 2.0.1 webgl-clip-cull-distance.html
--min-version 2.0.1 webgl-multi-draw-instanced-base-vertex-base-instance.html
--min-version 2.0.1 webgl-provoking-vertex.html
--min-version 2.0.1 webgl-shader-pixel-local-storage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL WEBGL_shader_pixel_local_storage Conformance Tests</title>
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/desktop-gl-constants.js"></script>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/webgl-test-utils.js"></script>
<script src="../../js/tests/compositing-test.js"></script>
<script src="../../js/tests/invalid-vertex-attrib-test.js"></script>
</head>
<body>
<div id="description"></div>
<canvas id="canvas" width="128" height="128"> </canvas>
<div id="console"></div>
<script>
"use strict";
description("This test verifies the functionality of the WEBGL_shader_pixel_local_storage " +
"extension, if it is available.");

const wtu = WebGLTestUtils;
const canvas = document.getElementById("canvas");
const gl = wtu.create3DContext(canvas, null, 2);
let pls = null;
let MAX_PIXEL_LOCAL_STORAGE_PLANES = null;
let MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE = null;
let MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES = null;
let MAX_COLOR_ATTACHMENTS = null;
let MAX_DRAW_BUFFERS = null;

function arraysEqual(a, b) {
if (typeof a !== typeof b)
return false;
if (a.length != b.length)
return false;
for (let i = 0; i < a.length; ++i) {
if (a[i] !== b[i])
return false;
}
return true;
}

function runTest() {
if (!gl) {
return function() {
testFailed("WebGL2 context does not exist");
}
}

// Check the behavior surrounding WEBGL_shader_pixel_local_storage being enabled.
checkExtensionNotSupportedWhenDisabled();
checkDependencyExtensionsEnabled(false);
pls = gl.getExtension("WEBGL_shader_pixel_local_storage");
wtu.runExtensionSupportedTest(gl, "WEBGL_shader_pixel_local_storage", pls != null);
if (!pls)
return;
checkDependencyExtensionsEnabled(true);

// Query and verify PLS implementation dependent limits.
MAX_PIXEL_LOCAL_STORAGE_PLANES =
gl.getParameter(pls.MAX_PIXEL_LOCAL_STORAGE_PLANES_WEBGL);
MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE =
gl.getParameter(pls.MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE_WEBGL);
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES =
gl.getParameter(pls.MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_WEBGL);
wtu.glErrorShouldBe(gl, gl.NONE, "Pixel local storage queries should be supported.");
MAX_COLOR_ATTACHMENTS = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
MAX_DRAW_BUFFERS = gl.getParameter(gl.MAX_DRAW_BUFFERS);
checkImplementationDependentLimits();

checkInitialValues();
checkWebGLNonNormativeBehavior();

gl.disable(gl.DITHER);
checkRendering();
}

// Check that a context does not support WEBGL_shader_pixel_local_storage before it is enabled.
function checkExtensionNotSupportedWhenDisabled() {
shouldBeNull("gl.getParameter(0x96E0 /*MAX_PIXEL_LOCAL_STORAGE_PLANES_WEBGL*/)");
wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
shouldBeNull(
"gl.getParameter(0x96E1 /*MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE_WEBGL*/)");
wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
shouldBeNull(
"gl.getParameter(0x96E2 /*MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_WEBGL*/)");
wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
shouldBeNull(
"gl.getParameter(0x96E3 /*PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL*/)");
wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
wtu.glErrorShouldBe(gl, gl.NONE);
}

// Check that the dependency extensions of WEBGL_shader_pixel_local_storage are properly enabled or
// disabled.
function checkDependencyExtensionsEnabled(enabled) {
if (wtu.getSupportedExtensionWithKnownPrefixes(gl, "OES_draw_buffers_indexed") !== undefined) {
gl.getIndexedParameter(gl.BLEND_EQUATION_RGB, 1);
wtu.glErrorShouldBe(gl, enabled ? gl.NONE : gl.INVALID_ENUM,
"OES_draw_buffers_indexed not enabled or disabled as expected");
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}
if (wtu.getSupportedExtensionWithKnownPrefixes(gl, "EXT_color_buffer_float") !== undefined) {
gl.bindRenderbuffer(gl.RENDERBUFFER, gl.createRenderbuffer());
gl.renderbufferStorage(gl.RENDERBUFFER, gl.R32F, 1, 1);
wtu.glErrorShouldBe(gl, enabled ? gl.NONE : gl.INVALID_ENUM,
"EXT_color_buffer_float not enabled or disabled as expected");
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}
if (wtu.getSupportedExtensionWithKnownPrefixes(gl, "EXT_color_buffer_half_float") !== undefined) {
gl.bindRenderbuffer(gl.RENDERBUFFER, gl.createRenderbuffer());
gl.renderbufferStorage(gl.RENDERBUFFER, gl.RG16F, 1, 1);
wtu.glErrorShouldBe(gl, enabled ? gl.NONE : gl.INVALID_ENUM,
"EXT_color_buffer_half_float not enabled or disabled as expected");
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
}
}

// Verify conformant implementation-dependent PLS limits.
function checkImplementationDependentLimits() {
// Table 6.X: Impementation Dependent Pixel Local Storage Limits.
shouldBeTrue("MAX_PIXEL_LOCAL_STORAGE_PLANES >= 4");
shouldBeTrue("MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE >= 0");
shouldBeTrue("MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >= 4");

// Logical deductions based on 6.X.
shouldBeTrue(`MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >=
MAX_PIXEL_LOCAL_STORAGE_PLANES`);
shouldBeTrue(`MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >=
MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE`);
shouldBeTrue(`MAX_COLOR_ATTACHMENTS + MAX_PIXEL_LOCAL_STORAGE_PLANES >=
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES`);
shouldBeTrue(`MAX_DRAW_BUFFERS + MAX_PIXEL_LOCAL_STORAGE_PLANES >=
MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES`);
}

// Check that PLS state has the specified initialized values.
function checkInitialValues() {
shouldBeTrue("gl.getParameter(pls.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 0");
wtu.glErrorShouldBe(
gl, gl.NONE,
"It's valid to query GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL even when fbo 0 is bound.");

// Table 6.Y: Pixel Local Storage State
gl.bindFramebuffer(gl.FRAMEBUFFER, gl.createFramebuffer());
shouldBeTrue("gl.getParameter(pls.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 0");
for (let i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
{
expectTrue(pls.getFramebufferPixelLocalStorageParameterWEBGL(
i, pls.PIXEL_LOCAL_FORMAT_WEBGL) == gl.NONE);
expectTrue(pls.getFramebufferPixelLocalStorageParameterWEBGL(
i, pls.PIXEL_LOCAL_TEXTURE_NAME_WEBGL) == null);
expectTrue(pls.getFramebufferPixelLocalStorageParameterWEBGL(
i, pls.PIXEL_LOCAL_TEXTURE_LEVEL_WEBGL) == 0);
expectTrue(pls.getFramebufferPixelLocalStorageParameterWEBGL(
i, pls.PIXEL_LOCAL_TEXTURE_LAYER_WEBGL) == 0);
expectTrue(arraysEqual(
pls.getFramebufferPixelLocalStorageParameterWEBGL(
i, pls.PIXEL_LOCAL_CLEAR_VALUE_FLOAT_WEBGL),
new Float32Array([0, 0, 0, 0])));
expectTrue(arraysEqual(
pls.getFramebufferPixelLocalStorageParameterWEBGL(
i, pls.PIXEL_LOCAL_CLEAR_VALUE_INT_WEBGL),
new Int32Array([0, 0, 0, 0])));
expectTrue(arraysEqual(
pls.getFramebufferPixelLocalStorageParameterWEBGL(
i, pls.PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_WEBGL),
new Uint32Array([0, 0, 0, 0])));
}
wtu.glErrorShouldBe(gl, gl.NONE);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

// Check the non-normative behavior not found in the ANGLE_shader_pixel_local_storage specification.
function checkWebGLNonNormativeBehavior() {
gl.bindFramebuffer(gl.FRAMEBUFFER, gl.createFramebuffer());

// If 'texture' has been deleted, generates an INVALID_OPERATION error.
wtu.glErrorShouldBe(gl, gl.NONE);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1);
wtu.glErrorShouldBe(gl, gl.NONE);
gl.deleteTexture(tex);
pls.framebufferTexturePixelLocalStorageWEBGL(0, tex, 0, 0);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);

// If 'texture' was generated by a different WebGL2RenderingContext than this one, generates an
// INVALID_OPERATION error.
const gl2 = wtu.create3DContext(null, null, 2);
const tex2 = gl2.createTexture();
gl2.bindTexture(gl2.TEXTURE_2D, tex2);
gl2.texStorage2D(gl2.TEXTURE_2D, 1, gl2.RGBA8, 1, 1);
pls.framebufferTexturePixelLocalStorageWEBGL(0, tex2, 0, 0);
wtu.glErrorShouldBe(gl2, gl2.NONE);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);

// If value has less than srcOffset + 4 elements, generates an INVALID_VALUE error.
wtu.glErrorShouldBe(gl, gl.NONE);
pls.framebufferPixelLocalClearValuefvWEBGL(0, new Float32Array(3));
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValuefvWEBGL(1, [0, 0, 0]);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValueivWEBGL(2, new Int32Array(3));
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValueivWEBGL(3, [0, 0, 0]);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValueuivWEBGL(4, new Uint32Array(3));
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValueuivWEBGL(3, [0, 0, 0]);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValuefvWEBGL(2, new Float32Array(5), 2);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValuefvWEBGL(1, [0, 0, 0, 0, 0], 2);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValueivWEBGL(0, new Int32Array(5), 2);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValueivWEBGL(1, [0, 0, 0, 0, 0], 2);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValueuivWEBGL(2, new Uint32Array(5), 2);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
pls.framebufferPixelLocalClearValueuivWEBGL(3, [0, 0, 0, 0, 0], 2);
wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);

// Check that srcOffset works properly.
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
pls.framebufferPixelLocalClearValuefvWEBGL(0, new Float32Array(arr), 1);
wtu.glErrorShouldBe(gl, gl.NONE);
shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
0, pls.PIXEL_LOCAL_CLEAR_VALUE_FLOAT_WEBGL),
new Float32Array([1, 2, 3, 4]))`);
pls.framebufferPixelLocalClearValuefvWEBGL(1, new Float32Array(arr), 2);
wtu.glErrorShouldBe(gl, gl.NONE);
shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
1, pls.PIXEL_LOCAL_CLEAR_VALUE_FLOAT_WEBGL),
[2, 3, 4, 5])`);
pls.framebufferPixelLocalClearValueivWEBGL(2, new Int32Array(arr), 3);
wtu.glErrorShouldBe(gl, gl.NONE);
shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
2, pls.PIXEL_LOCAL_CLEAR_VALUE_INT_WEBGL),
new Float32Array([3, 4, 5, 6]))`);
pls.framebufferPixelLocalClearValueivWEBGL(3, new Float32Array(arr), 4);
wtu.glErrorShouldBe(gl, gl.NONE);
shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
3, pls.PIXEL_LOCAL_CLEAR_VALUE_INT_WEBGL),
[4, 5, 6, 7])`);
pls.framebufferPixelLocalClearValueuivWEBGL(2, new Int32Array(arr), 5);
wtu.glErrorShouldBe(gl, gl.NONE);
shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
2, pls.PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_WEBGL),
new Uint32Array([5, 6, 7, 8]))`);
pls.framebufferPixelLocalClearValueuivWEBGL(1, new Float32Array(arr), 6);
wtu.glErrorShouldBe(gl, gl.NONE);
shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
1, pls.PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_WEBGL),
[6, 7, 8, 9])`);

wtu.glErrorShouldBe(gl, gl.NONE);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

// Check very simple rendering.
function checkRendering() {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 10, 10);
wtu.glErrorShouldBe(gl, gl.NONE);

const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
pls.framebufferTexturePixelLocalStorageWEBGL(0, tex, 0, 0);
wtu.glErrorShouldBe(gl, gl.NONE);

gl.viewport(0, 0, 10, 10);

const vs = `#version 300 es
void main() {
gl_Position.x = (gl_VertexID & 1) == 0 ? -1. : 1.;
gl_Position.y = (gl_VertexID & 2) == 0 ? -1. : 1.;
gl_Position.zw = vec2(0, 1);
}`;

const fs = `#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
precision lowp float;
uniform vec4 color;
layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
void main() {
vec4 newColor = color + pixelLocalLoadANGLE(pls);
pixelLocalStoreANGLE(pls, newColor);
}`;

const program = wtu.setupProgram(gl, [vs, fs]);
if (!program) {
testFailed("Failed to compile program.");
return;
}

gl.useProgram(program);
const colorUniLocation = gl.getUniformLocation(program, "color");
wtu.glErrorShouldBe(gl, gl.NONE);

pls.beginPixelLocalStorageWEBGL([pls.LOAD_OP_ZERO_WEBGL]);
wtu.glErrorShouldBe(gl, gl.NONE);
shouldBeTrue("gl.getParameter(pls.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 1");

gl.uniform4f(colorUniLocation, 1, 0, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

pls.pixelLocalStorageBarrierWEBGL();

gl.uniform4f(colorUniLocation, 0, 0, 1, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

pls.endPixelLocalStorageWEBGL([pls.STORE_OP_STORE_WEBGL]);
wtu.glErrorShouldBe(gl, gl.NONE);
shouldBeTrue("gl.getParameter(pls.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 0");

const readFBO = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, readFBO);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
wtu.checkCanvasRect(gl, 0, 0, 10, 10, [255, 0, 255, 0]);

wtu.glErrorShouldBe(gl, gl.NONE);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

runTest();
var successfullyParsed = true;
</script>
<script src="../../js/js-test-post.js"></script>
</body>
</html>

0 comments on commit 13531ea

Please sign in to comment.