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>Switching transform feedback objects</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>
<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
<div id="console"></div>
<script id="vshader" type="x-shader/x-vertex">#version 300 es
in float in_value;
out float out_value1;
out float out_value2;
void main() {
out_value1 = in_value * 2.;
out_value2 = in_value * 4.;
}
</script>
<script id="fshader" type="x-shader/x-fragment">#version 300 es
precision mediump float;
out vec4 dummy;
void main() {
dummy = vec4(0.);
}
</script>
<script>
"use strict";
description("Tests switching transform feedback objects.");
debug("<h3>Setup</h3>")
var wtu = WebGLTestUtils;
var canvas = document.getElementById("canvas");
var gl = wtu.create3DContext(canvas, null, 2);
if (!gl) {
testFailed("WebGL context does not exist");
}
// Setup
const prog_interleaved = wtu.setupTransformFeedbackProgram(gl, ["vshader", "fshader"],
["out_value1", "out_value2"], gl.INTERLEAVED_ATTRIBS,
["in_value"]);
const prog_no_varyings = wtu.setupTransformFeedbackProgram(gl, ["vshader", "fshader"],
[], gl.INTERLEAVED_ATTRIBS,
["in_value"]);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "shader compilation");
const vertexBuffer = createBuffer(gl, new Float32Array([1, 2, 3, 4]));
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 0, 0);
gl.useProgram(prog_interleaved);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "vertex buffer and program setup");
const tf1 = gl.createTransformFeedback();
const tf2 = gl.createTransformFeedback();
const tfBuffer1 = createBuffer(gl, new Float32Array([0, 0]));
const tfBuffer2 = createBuffer(gl, new Float32Array([0, 0]));
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf1);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, tfBuffer1);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf2);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, tfBuffer2);
const expected_tf_output = [2, 4];
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "TF object setup");
debug("<h3>Baseline transform feedback success case</h3>");
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf1);
gl.beginTransformFeedback(gl.POINTS);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "begin TF");
gl.drawArrays(gl.POINTS, 0, 1);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "draw");
gl.endTransformFeedback();
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "end TF");
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuffer1);
wtu.checkFloatBuffer(gl, gl.TRANSFORM_FEEDBACK_BUFFER, expected_tf_output);
debug("<h3>Generic binding is not changed when switching TF object</h3>");
// According to the GL ES spec historically, TRANSFORM_FEEDBACK_BUFFER_BINDING is listed as part
// of the transform feedback object state. However, many drivers treat it as global context state
// and not part of the tranform feedback object, which means that it does not change when
// bindTransformFeedback is called. Khronos has resolved to change the spec to specify the latter
// for the new behavior.
// Set each buffer to contain its buffer number. We use this to check which
// buffer is *really* bound at the driver level by reading the buffer contents.
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuffer1);
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, new Float32Array([1]), gl.STREAM_READ);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuffer2);
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, new Float32Array([2]), gl.STREAM_READ);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "bufferData");
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf1);
checkParameter(gl.TRANSFORM_FEEDBACK_BUFFER_BINDING, tfBuffer2);
checkIndexedParameter(gl.TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, tfBuffer1);
wtu.checkFloatBuffer(gl, gl.TRANSFORM_FEEDBACK_BUFFER, [2]);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readback");
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, null);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf2);
checkParameter(gl.TRANSFORM_FEEDBACK_BUFFER_BINDING, null);
checkIndexedParameter(gl.TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, tfBuffer2);
debug("<h3>Error switching TF object while TF is enabled</h3>");
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf1);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuffer1);
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, new Float32Array([0, 0]), gl.STREAM_READ);
gl.beginTransformFeedback(gl.POINTS);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "begin");
checkParameter(gl.TRANSFORM_FEEDBACK_BINDING, tf1);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf2);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "bind while unpaused");
// Check that nothing actually changed and rendering still works
checkParameter(gl.TRANSFORM_FEEDBACK_BINDING, tf1);
gl.drawArrays(gl.POINTS, 0, 1);
gl.endTransformFeedback();
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "transform feedback should complete successfully");
wtu.checkFloatBuffer(gl, gl.TRANSFORM_FEEDBACK_BUFFER, expected_tf_output);
debug("<h3>Successfully switching TF object while TF is paused</h3>");
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuffer1);
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, new Float32Array([0, 0]), gl.STREAM_READ);
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuffer2);
gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, new Float32Array([0, 0]), gl.STREAM_READ);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf2);
gl.beginTransformFeedback(gl.POINTS);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "begin on tf2");
checkParameter(gl.TRANSFORM_FEEDBACK_BINDING, tf2);
gl.pauseTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf1);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "bind while paused");
gl.beginTransformFeedback(gl.POINTS);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "begin on tf1");
checkParameter(gl.TRANSFORM_FEEDBACK_BINDING, tf1);
gl.drawArrays(gl.POINTS, 0, 1);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "draw should succeed");
gl.endTransformFeedback();
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "end on tf1");
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuffer1);
wtu.checkFloatBuffer(gl, gl.TRANSFORM_FEEDBACK_BUFFER, expected_tf_output);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf2);
gl.endTransformFeedback();
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "end on tf2");
debug("<h3>Misc. invalid operations</h3>")
gl.endTransformFeedback();
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "endTransformFeedback before begin");
gl.pauseTransformFeedback();
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "pauseTransformFeedback when not active");
gl.resumeTransformFeedback();
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "pauseTransformFeedback when not active");
gl.beginTransformFeedback(gl.POINTS);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "transform feedback should begin successfully");
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 1);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "wrong primitive mode");
gl.useProgram(prog_no_varyings);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "switch program while active");
gl.resumeTransformFeedback();
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "resumeTransformFeedback when not paused");
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf2);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "bindTransformFeedback when active");
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, tfBuffer2);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "bindBuffer(TRANSFORM_FEEDBACK_BUFFER) when active");
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, tfBuffer2);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "bindBufferBase(TRANSFORM_FEEDBACK_BUFFER) when active");
gl.pauseTransformFeedback();
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "pause");
gl.pauseTransformFeedback();
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "already paused");
gl.endTransformFeedback();
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "end while paused");
finishTest();
// Helper functions
function createBuffer(gl, dataOrSize) {
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, dataOrSize, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return buf;
}
function checkParameter(param, expected) {
const value = gl.getParameter(param);
if (value != expected) {
testFailed(wtu.glEnumToString(gl, param) + " was " + value + ", but expected " + expected);
} else {
testPassed(wtu.glEnumToString(gl, param) + " was " + value + ", matching expected " + expected);
}
}
function checkIndexedParameter(param, index, expected) {
const value = gl.getIndexedParameter(param, index);
if (value != expected) {
testFailed(wtu.glEnumToString(gl, param) + "[" + index + "] was " + value + ", but expected " + expected);
} else {
testPassed(wtu.glEnumToString(gl, param) + "[" + index + "] was " + value + ", matching expected " + expected);
}
}
</script>
</body>
</html>