Source code

Revision control

Copy as Markdown

Other Tools

<!--
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 Uniform Buffers Conformance Tests</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>
<script id='vshader' type='x-shader/x-vertex'>#version 300 es
layout(location=0) in vec3 p;
void main()
{
gl_Position = vec4(p.xyz, 1.0);
}
</script>
<script id='fbadshader' type='x-shader/x-fragment'>#version 300 es
precision mediump float;
layout(location=0) out vec4 oColor;
uniform UBOData {
float UBORed;
float UBOGreen;
float UBOBlue;
};
uniform Color {
float Red;
float UBOGreen;
float Blue;
};
void main()
{
oColor = vec4(UBORed * Red, UBOGreen * UBOGreen, UBOBlue * Blue, 1.0);
}
</script>
<script id='fshader' type='x-shader/x-fragment'>#version 300 es
precision mediump float;
layout(location=0) out vec4 oColor;
uniform UBOData {
float UBORed;
float UBOGreen;
float UBOBlue;
};
uniform UBOD {
float UBOR;
float UBOG;
float UBOB;
};
void main()
{
oColor = vec4(UBORed * UBOR, UBOGreen * UBOG, UBOBlue * UBOB, 1.0);
}
</script>
<script id='fshadernamed' type='x-shader/x-fragment'>#version 300 es
precision mediump float;
layout(location=0) out vec4 oColor;
uniform UBOData {
float Red;
float Green;
float Blue;
} UBOA;
void main()
{
oColor = vec4(UBOA.Red, UBOA.Green, UBOA.Blue, 1.0);
}
</script>
<script id='fshadernamedarray' type='x-shader/x-fragment'>#version 300 es
precision mediump float;
layout(location=0) out vec4 oColor;
uniform UBOData {
float Red;
float Green;
float Blue;
} UBOA[2];
void main()
{
oColor = vec4((UBOA[0].Red + UBOA[1].Red) / 2.0,
(UBOA[0].Green + UBOA[1].Green) / 2.0,
(UBOA[0].Blue + UBOA[1].Blue) / 2.0, 1.0);
}
</script>
<script id='fshadernestedstruct' type='x-shader/x-fragment'>#version 300 es
precision mediump float;
layout(location=0) out vec4 oColor;
struct color_t {
float red;
float green;
float blue;
};
struct wrapper_t {
color_t color;
};
uniform UBOData {
wrapper_t UBOStruct;
};
// This is intended to reproduce a specific ANGLE bug that triggers when the wrapper struct is passed to a function.
void processColor(wrapper_t wrapper) {
oColor = vec4(wrapper.color.red, wrapper.color.green, wrapper.color.blue, 1.0);
}
void main()
{
processColor(UBOStruct);
}
</script>
<script id='fshaderarrayofstructs' type='x-shader/x-fragment'>#version 300 es
precision mediump float;
layout(location=0) out vec4 oColor;
struct color_t {
float red;
float green;
float blue;
};
uniform UBOData {
color_t UBOColors[2];
};
// This is intended to reproduce a specific ANGLE bug that triggers when a struct from an array of structs in an interface block is passed to a function.
vec3 processColor(color_t color) {
return vec3(color.red, color.green, color.blue);
}
void main()
{
oColor = vec4(processColor(UBOColors[0]) + processColor(UBOColors[1]), 1.0);
}
</script>
</head>
<body>
<div id="description"></div>
<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
<div id="console"></div>
<script>
"use strict";
description("This test verifies the functionality of the Uniform Buffer objects");
debug("");
var wtu = WebGLTestUtils;
var canvas = document.getElementById("canvas");
var gl = wtu.create3DContext(canvas, null, 2);
var b1 = null;
var b2 = null;
if (!gl) {
testFailed("WebGL context does not exist");
} else {
testPassed("WebGL context exists");
wtu.setupUnitQuad(gl);
runBindingTest();
runBadShaderTest();
runUniformBufferOffsetAlignmentTest();
runDrawTest();
runNamedDrawTest();
runNamedArrayDrawTest();
runNestedStructsDrawTest();
runArrayOfStructsDrawTest();
}
function runBindingTest() {
debug("");
debug("Testing uniform buffer binding behavior");
shouldBeNull("gl.getParameter(gl.UNIFORM_BUFFER_BINDING)");
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "UNIFORM_BUFFER_BINDING query should succeed");
debug("Testing basic uniform buffer binding and unbinding");
b1 = gl.createBuffer();
b2 = gl.createBuffer();
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "createBuffer should not set an error");
shouldBeNonNull("b1");
shouldBeNonNull("b2");
gl.bindBuffer(gl.UNIFORM_BUFFER, b1);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to bind uniform buffer");
shouldBe("gl.getParameter(gl.UNIFORM_BUFFER_BINDING)", "b1");
gl.bindBuffer(gl.UNIFORM_BUFFER, b2);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to update uniform buffer binding");
shouldBe("gl.getParameter(gl.UNIFORM_BUFFER_BINDING)", "b2");
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to unbind uniform buffer");
debug("Testing deleting uniform buffers");
gl.deleteBuffer(b1);
gl.deleteBuffer(b2);
shouldBeNull("gl.getParameter(gl.UNIFORM_BUFFER_BINDING)");
// Shouldn't be able to bind a deleted buffer.
gl.bindBuffer(gl.UNIFORM_BUFFER, b2);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "binding a deleted buffer should generate INVALID_OPERATION");
shouldBeNull("gl.getParameter(gl.UNIFORM_BUFFER_BINDING)");
}
function runBadShaderTest() {
debug("");
var testProgram = wtu.setupProgram(gl, ['vshader', 'fbadshader']);
if (testProgram) {
testFailed("To define the same uniform in two uniform blocks should fail");
} else {
testPassed("To define the same uniform in two uniform blocks should fail");
}
}
function runUniformBufferOffsetAlignmentTest() {
debug("");
var offsetAlignment = gl.getParameter(gl.UNIFORM_BUFFER_OFFSET_ALIGNMENT);
if (offsetAlignment % 4 != 0) {
testFailed("Unexpected UNIFORM_BUFFER_OFFSET_ALIGNMENT - should be aligned on a 4-byte boundary");
} else {
testPassed("UNIFORM_BUFFER_OFFSET_ALIGNMENT is divisible by four");
}
}
function setRGBValuesToFloat32Array(floatView, red, green, blue) {
floatView[0] = red;
floatView[1] = green;
floatView[2] = blue;
}
function checkFloat32UniformOffsetsInStd140Layout(uniformOffsets, expectedInitialOffset) {
if (expectedInitialOffset === undefined)
{
expectedInitialOffset = 0;
}
// Verify that the uniform offsets are set according to the std140 layout, which WebGL enforces.
// This function checks this for 32-bit float values, which are expected to be tightly packed.
for (var i = 0; i < uniformOffsets.length; ++i)
{
if (uniformOffsets[i] != expectedInitialOffset + i * Float32Array.BYTES_PER_ELEMENT)
{
testFailed("Uniform offsets are not according to std140 layout");
return false;
}
}
return true;
}
function runDrawTest() {
debug("");
debug("Testing drawing with uniform buffers");
var program = wtu.setupProgram(gl, ['vshader', 'fshader']);
if (!program) {
testFailed("Could not compile shader with uniform blocks without error");
return;
}
var blockIndex_1 = gl.getUniformBlockIndex(program, "UBOData");
var blockSize_1 = gl.getActiveUniformBlockParameter(program, blockIndex_1, gl.UNIFORM_BLOCK_DATA_SIZE);
var uniformIndices_1 = gl.getUniformIndices(program, ["UBORed", "UBOGreen", "UBOBlue"]);
var uniformOffsets_1 = gl.getActiveUniforms(program, uniformIndices_1, gl.UNIFORM_OFFSET);
var blockIndex_2 = gl.getUniformBlockIndex(program, "UBOD");
var blockSize_2 = gl.getActiveUniformBlockParameter(program, blockIndex_2, gl.UNIFORM_BLOCK_DATA_SIZE);
var uniformIndices_2 = gl.getUniformIndices(program, ["UBOR", "UBOG", "UBOB"]);
var uniformOffsets_2 = gl.getActiveUniforms(program, uniformIndices_2, gl.UNIFORM_OFFSET);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to query uniform block information without error");
if (uniformOffsets_1.length < 3 || uniformOffsets_2.length < 3) {
testFailed("Could not query uniform offsets");
return;
}
if (!checkFloat32UniformOffsetsInStd140Layout(uniformOffsets_1) || !checkFloat32UniformOffsetsInStd140Layout(uniformOffsets_2))
{
return;
}
var uboArray_1 = new ArrayBuffer(blockSize_1);
var uboFloatView_1 = new Float32Array(uboArray_1);
setRGBValuesToFloat32Array(uboFloatView_1, 1.0, 0.0, 0.0); // UBORed, UBOGreen, UBOBlue
var uboArray_2 = new ArrayBuffer(blockSize_2);
var uboFloatView_2 = new Float32Array(uboArray_2);
setRGBValuesToFloat32Array(uboFloatView_2, 1.0, 1.0, 1.0); // UBOR, UBOG, UBOB
var b_1 = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, b_1);
gl.bufferData(gl.UNIFORM_BUFFER, uboFloatView_1, gl.DYNAMIC_DRAW);
var b_2 = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, b_2);
gl.bufferData(gl.UNIFORM_BUFFER, uboFloatView_2, gl.DYNAMIC_DRAW);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to set UBO data with no errors");
var bindings = [1, 2];
gl.uniformBlockBinding(program, blockIndex_1, bindings[0]);
gl.bindBufferBase(gl.UNIFORM_BUFFER, bindings[0], b_1);
gl.uniformBlockBinding(program, blockIndex_2, bindings[1]);
gl.bindBufferBase(gl.UNIFORM_BUFFER, bindings[1], b_2);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call bindBufferBase without errors");
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [255, 0, 0, 255], "draw call should set canvas to red", 2);
debug("Changing the data in the uniform buffer should automatically update the uniforms exposed to the draw call");
setRGBValuesToFloat32Array(uboFloatView_1, 0.0, 0.0, 1.0); // UBORed, UBOGreen, UBOBlue
gl.bindBuffer(gl.UNIFORM_BUFFER, b_1);
gl.bufferData(gl.UNIFORM_BUFFER, uboFloatView_1, gl.DYNAMIC_DRAW);
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [0, 0, 255, 255], "draw call should set canvas to blue", 2);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
}
function runNamedDrawTest() {
debug("");
debug("Testing drawing with named uniform buffers");
var program = wtu.setupProgram(gl, ['vshader', 'fshadernamed']);
if (!program) {
testFailed("Could not compile shader with named uniform blocks without error");
return;
}
var blockIndex = gl.getUniformBlockIndex(program, "UBOData");
var blockSize = gl.getActiveUniformBlockParameter(program, blockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
var uniformIndices = gl.getUniformIndices(program, ["UBOData.Red", "UBOData.Green", "UBOData.Blue"]);
var uniformOffsets = gl.getActiveUniforms(program, uniformIndices, gl.UNIFORM_OFFSET);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to query uniform block information without error");
if (uniformOffsets.length < 3) {
testFailed("Could not query uniform offsets");
return;
}
if (!checkFloat32UniformOffsetsInStd140Layout(uniformOffsets))
{
return;
}
var uboArray = new ArrayBuffer(blockSize);
var uboFloatView = new Float32Array(uboArray);
setRGBValuesToFloat32Array(uboFloatView, 1.0, 0.0, 0.0);
b1 = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, b1);
gl.bufferData(gl.UNIFORM_BUFFER, uboArray, gl.DYNAMIC_DRAW);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to set UBO data with no errors");
var binding = 3;
gl.uniformBlockBinding(program, blockIndex, binding);
gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, b1);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call bindBufferBase without errors");
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [255, 0, 0, 255], "draw call should set canvas to red", 2);
debug("Changing the data in the uniform buffer should automatically update the uniforms exposed to the draw call");
setRGBValuesToFloat32Array(uboFloatView, 0.0, 0.0, 1.0);
gl.bufferData(gl.UNIFORM_BUFFER, uboArray, gl.DYNAMIC_DRAW);
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [0, 0, 255, 255], "draw call should set canvas to blue", 2);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
}
function runNamedArrayDrawTest() {
debug("");
debug("Testing drawing with named uniform buffer arrays");
var program = wtu.setupProgram(gl, ['vshader', 'fshadernamedarray']);
if (!program) {
testFailed("could not compile shader with named uniform block arrays without error");
return;
}
var blockIndex = [gl.getUniformBlockIndex(program, "UBOData[0]"),
gl.getUniformBlockIndex(program, "UBOData[1]")];
if (blockIndex[0] == gl.INVALID_INDEX ||
blockIndex[1] == gl.INVALID_INDEX) {
testFailed("Could not query uniform block index");
return;
}
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to query uniform block indices without error");
var blockSize = [gl.getActiveUniformBlockParameter(program, blockIndex[0], gl.UNIFORM_BLOCK_DATA_SIZE),
gl.getActiveUniformBlockParameter(program, blockIndex[1], gl.UNIFORM_BLOCK_DATA_SIZE)];
if (blockSize[0] != blockSize[1]) {
testFailed("uniform block instance array with different block sizes");
}
var uniformIndices = gl.getUniformIndices(program, ["UBOData.Red", "UBOData.Green", "UBOData.Blue"]);
if (uniformIndices < 3 ||
uniformIndices[0] == gl.INVALID_INDEX ||
uniformIndices[1] == gl.INVALID_INDEX ||
uniformIndices[2] == gl.INVALID_INDEX) {
testFailed("Could not query uniform indices");
return;
}
var uniformOffsets = gl.getActiveUniforms(program, uniformIndices, gl.UNIFORM_OFFSET);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to query uniform block information without error");
if (uniformOffsets.length < 3) {
testFailed("Could not query uniform offsets");
return;
}
if (!checkFloat32UniformOffsetsInStd140Layout(uniformOffsets))
{
return;
}
var offsetAlignment = gl.getParameter(gl.UNIFORM_BUFFER_OFFSET_ALIGNMENT);
var offset = Math.ceil(blockSize[0] / offsetAlignment) * offsetAlignment;
var bufferSize = offset + blockSize[1];
var uboArray = new ArrayBuffer(bufferSize);
var uboFloatView = new Float32Array(uboArray);
setRGBValuesToFloat32Array(uboFloatView, 1.0, 0.0, 0.0);
var uboFloatView2 = new Float32Array(uboArray, offset);
setRGBValuesToFloat32Array(uboFloatView2, 0.0, 0.0, 1.0);
b1 = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, b1);
gl.bufferData(gl.UNIFORM_BUFFER, uboArray, gl.DYNAMIC_DRAW);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to set UBO data with no errors");
var bindings = [4, 5];
gl.uniformBlockBinding(program, blockIndex[0], bindings[0]);
gl.bindBufferRange(gl.UNIFORM_BUFFER, bindings[0], b1, 0, blockSize[0]);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call bindBufferRange without errors");
gl.uniformBlockBinding(program, blockIndex[1], bindings[1]);
gl.bindBufferRange(gl.UNIFORM_BUFFER, bindings[1], b1, offset, blockSize[1]);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call bindBufferRange without errors");
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [127, 0, 127, 255], "draw call should set canvas to (0.5, 0, 0.5)", 2);
debug("Changing the data in the uniform buffer should automatically update the uniforms exposed to the draw call");
setRGBValuesToFloat32Array(uboFloatView, 0.0, 1.0, 1.0);
setRGBValuesToFloat32Array(uboFloatView2, 0.0, 0.0, 1.0);
gl.bufferData(gl.UNIFORM_BUFFER, uboArray, gl.DYNAMIC_DRAW);
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [0, 127, 255, 255], "draw call should set canvas to (0, 0.5, 1)", 2);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
}
function runNestedStructsDrawTest() {
debug("");
debug("Testing drawing with nested struct inside uniform block. The wrapper struct is passed to a function.");
var program = wtu.setupProgram(gl, ['vshader', 'fshadernestedstruct'], undefined, undefined, true);
if (!program) {
testFailed("Could not compile shader with nested structs without error");
return;
}
var blockIndex = gl.getUniformBlockIndex(program, "UBOData");
var blockSize = gl.getActiveUniformBlockParameter(program, blockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
var uniformIndices = gl.getUniformIndices(program, ["UBOStruct.color.red", "UBOStruct.color.green", "UBOStruct.color.blue"]);
var uniformOffsets = gl.getActiveUniforms(program, uniformIndices, gl.UNIFORM_OFFSET);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to query uniform block information without error");
if (uniformOffsets.length < 3) {
testFailed("Could not query uniform offsets");
return;
}
if (!checkFloat32UniformOffsetsInStd140Layout(uniformOffsets))
{
return;
}
var uboArray = new ArrayBuffer(blockSize);
var uboFloatView = new Float32Array(uboArray);
setRGBValuesToFloat32Array(uboFloatView, 0.0, 1.0, 0.0);
b1 = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, b1);
gl.bufferData(gl.UNIFORM_BUFFER, uboArray, gl.DYNAMIC_DRAW);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to set UBO data with no errors");
var binding = 3;
gl.uniformBlockBinding(program, blockIndex, binding);
gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, b1);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call bindBufferBase without errors");
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [0, 255, 0, 255], "draw call should set canvas to green", 2);
debug("Changing the data in the uniform buffer should automatically update the uniforms exposed to the draw call");
setRGBValuesToFloat32Array(uboFloatView, 0.0, 0.0, 1.0);
gl.bufferData(gl.UNIFORM_BUFFER, uboArray, gl.DYNAMIC_DRAW);
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [0, 0, 255, 255], "draw call should set canvas to blue", 2);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
}
function runArrayOfStructsDrawTest() {
debug("");
debug("Testing drawing with array of structs inside uniform block. A struct in the block is passed to a function.");
var program = wtu.setupProgram(gl, ['vshader', 'fshaderarrayofstructs'], undefined, undefined, true);
if (!program) {
testFailed("Could not compile shader with an array of structs in an interface block without error");
return;
}
var blockIndex = gl.getUniformBlockIndex(program, "UBOData");
var blockSize = gl.getActiveUniformBlockParameter(program, blockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
var uniformIndices = gl.getUniformIndices(program, ["UBOColors[0].red", "UBOColors[0].green", "UBOColors[0].blue"]);
var uniformOffsets = gl.getActiveUniforms(program, uniformIndices, gl.UNIFORM_OFFSET);
var uniformIndices_2 = gl.getUniformIndices(program, ["UBOColors[1].red", "UBOColors[1].green", "UBOColors[1].blue"]);
var uniformOffsets_2 = gl.getActiveUniforms(program, uniformIndices_2, gl.UNIFORM_OFFSET);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to query uniform block information without error");
if (uniformOffsets.length < 3) {
testFailed("Could not query uniform offsets");
return;
}
if (!checkFloat32UniformOffsetsInStd140Layout(uniformOffsets))
{
return;
}
if (!checkFloat32UniformOffsetsInStd140Layout(uniformOffsets_2, uniformOffsets_2[0]))
{
return;
}
var uboArray = new ArrayBuffer(blockSize);
var uboFloatView = new Float32Array(uboArray);
setRGBValuesToFloat32Array(uboFloatView, 0.0, 0.5, 0.0);
var uboFloatView2 = new Float32Array(uboArray, uniformOffsets_2[0]);
setRGBValuesToFloat32Array(uboFloatView2, 0.0, 0.5, 0.0);
b1 = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, b1);
gl.bufferData(gl.UNIFORM_BUFFER, uboArray, gl.DYNAMIC_DRAW);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to set UBO data with no errors");
var binding = 3;
gl.uniformBlockBinding(program, blockIndex, binding);
gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, b1);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call bindBufferBase without errors");
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [0, 255, 0, 255], "draw call should set canvas to green", 2);
debug("Changing the data in the uniform buffer should automatically update the uniforms exposed to the draw call");
setRGBValuesToFloat32Array(uboFloatView, 1.0, 0.0, 0.0);
setRGBValuesToFloat32Array(uboFloatView2, 0.0, 0.0, 1.0);
gl.bufferData(gl.UNIFORM_BUFFER, uboArray, gl.DYNAMIC_DRAW);
wtu.clearAndDrawUnitQuad(gl);
wtu.checkCanvas(gl, [255, 0, 255, 255], "draw call should set canvas to purple", 2);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
}
debug("");
var successfullyParsed = true;
</script>
<script src="../../js/js-test-post.js"></script>
</body>
</html>