Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WEBGL_get_buffer_sub_data_async extension #2079

Merged
merged 29 commits into from Dec 15, 2016
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5dadf4e
WebGL 2: test getBufferSubDataAsync
kainino0x Sep 30, 2016
cd7d8bd
stress test getBufferSubDataAsync
kainino0x Oct 5, 2016
a21d8ca
getBufferSubDataAsync-lose-context.html
kainino0x Oct 5, 2016
05f5648
add getBufferSubDataAsync to WebGL 2 spec
kainino0x Oct 5, 2016
183846a
more tests for getBufferSubDataAsync
kainino0x Oct 5, 2016
4be0799
add corner case
kainino0x Oct 6, 2016
4e129ff
update spec language to match new getBufferSubData
kainino0x Oct 7, 2016
b00c412
Correct stress test and add more tests
kainino0x Oct 7, 2016
df573f4
Merge branch 'master' into async
kainino0x Oct 7, 2016
27b3624
remove ordering requirement, and fix/clean up some catches
kainino0x Oct 7, 2016
f357342
Merge branch 'master' into async
kainino0x Oct 7, 2016
8b7df39
update spec language to match getBufferSubData
kainino0x Oct 7, 2016
81121c7
hopefully improve robustness of getBufferSubDataAsync-lose-context.html
kainino0x Oct 10, 2016
a695837
add test for getBufferSubDataAsync when transform feedback is active
kainino0x Oct 12, 2016
19f92b5
Merge branch 'master' into async
kainino0x Oct 12, 2016
b060aac
Revert "add test for getBufferSubDataAsync when transform feedback is…
kainino0x Oct 12, 2016
4af531e
small fix
kainino0x Oct 12, 2016
1e89eb2
Merge branch 'master' into async
kainino0x Oct 12, 2016
a8a2d58
test transform feedback error case
kainino0x Oct 12, 2016
4c12d19
Merge branch 'master' into async
kainino0x Oct 20, 2016
80df4a0
formalize getBufferSubDataAsync spec text + modify stress test
kainino0x Oct 22, 2016
2ba44a8
Merge branch 'master' into async
kainino0x Oct 24, 2016
90b809d
Merge branch 'master' into async
kainino0x Nov 4, 2016
cceab14
Merge branch 'master' into async
kainino0x Dec 13, 2016
fa9821c
move to extension
kainino0x Dec 13, 2016
100232b
update tests
kainino0x Dec 14, 2016
3c9a7c6
write extension draft
kainino0x Dec 14, 2016
4748934
use 'let' in for-loops
kainino0x Dec 15, 2016
5cabc1a
remove manual tests which were not updated anyway
kainino0x Dec 15, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
136 changes: 136 additions & 0 deletions extensions/WEBGL_get_buffer_sub_data_async/extension.xml
@@ -0,0 +1,136 @@
<?xml version="1.0"?>

<draft href="WEBGL_get_buffer_sub_data_async/">
<name>WEBGL_get_buffer_sub_data_async</name>
<contact>
<a href="https://www.khronos.org/webgl/public-mailing-list/">WebGL working group</a> (public_webgl 'at' khronos.org)
</contact>
<contributors>
<contributor>Kai Ninomiya, Google Inc.</contributor>
<contributor>Members of the WebGL working group</contributor>
</contributors>
<number>34</number>
<depends>
<api version="2.0"/>
</depends>
<overview>
<p>
This extension allows asynchronous buffer readback in WebGL 2.0.
</p>
<features>
<feature>
This extension exposes an asynchronous buffer readback entry point for
non-blocking readbacks from WebGL buffers. It is equivalent to
<code>getBufferSubData</code> but returns a <code>Promise</code>
instead of an immediate readback result.
</feature>
</features>
</overview>

<idl xml:space="preserve">
[NoInterfaceObject]
interface WEBGL_get_buffer_sub_data_async {
// Asynchronous version of getBufferSubData which fulfills the returned promise when the data becomes available.
Promise&lt;ArrayBuffer&gt; getBufferSubDataAsync(GLenum target, GLintptr srcByteOffset, ArrayBufferView dstBuffer,
optional GLuint dstOffset = 0, optional GLuint length = 0); // May throw DOMException
};
</idl>

<newfun>
<function name="getBufferSubDataAsync" type="Promise&lt;ArrayBuffer&gt;">
<param name="target" type="GLenum"/>
<param name="srcByteOffset" type="GLintptr"/>
<param name="dstBuffer" type="ArrayBufferView"/>
<param name="dstOffset" type="optional GLuint"/>
<param name="length" type="optional GLuint"/>
Reads back data asynchronously from the bound WebGLBuffer into <code>dstBuffer</code>.
<br/><br/>
Let <code>buf</code> be the buffer bound to <code>target</code> at the time
<code>getBufferSubDataAsync</code> is called.
If <code>length</code> is 0, let <code>copyLength</code> be
<code>dstBuffer.length - dstOffset</code>; otherwise, let
<code>copyLength</code> be <code>length</code>.
<br/><br/>
If <code>copyLength</code> is greater than zero,
copy <code>copyLength</code> typed elements (each of size <code>dstBuffer.BYTES_PER_ELEMENT</code>)
from <code>buf</code> into <code>dstBuffer</code>,
reading <code>buf</code> starting at byte index <code>srcByteOffset</code> and
writing into <code>dstBuffer</code> starting at element index <code>dstOffset</code>.
If <code>copyLength</code> is 0, no data is written to <code>dstBuffer</code>, but
this does not cause a GL error to be generated.
<ul>
<li>If no WebGLBuffer is bound to <code>target</code>,
an <code>INVALID_OPERATION</code> error is generated.
</li>
<li>If <code>target</code> is <code>TRANSFORM_FEEDBACK_BUFFER</code>,
and any transform feedback object is currently active,
an <code>INVALID_OPERATION</code> error is generated.
</li>
<li>If <code>dstOffset</code> is greater than <code>dstBuffer.length</code>,
an <code>INVALID_VALUE</code> error is generated.
</li>
<li>If <code>dstOffset + copyLength</code> is greater than <code>dstBuffer.length</code>,
an <code>INVALID_VALUE</code> error is generated.
</li>
<li>If <code>srcByteOffset</code> is less than zero,
an <code>INVALID_VALUE</code> error is generated.
</li>
<li>If <code>srcByteOffset + copyLength*dstBuffer.BYTES_PER_ELEMENT</code>
is larger than the length of <code>buf</code>,
an <code>INVALID_OPERATION</code> is generated.
</li>
</ul>
When invoked, <code>getBufferSubDataAsync</code> must run these steps:
<ul>
<li>Let <code>promise</code> be a Promise to be returned.
</li>
<li>Check for the errors defined above. If there are any errors, generate the GL error
synchronously and
<a href="https://www.w3.org/2001/tag/doc/promises-guide/#reject-promise">reject</a>
<code>promise</code> with an <code>InvalidStateError</code>.
</li>
<li>Insert a readback of <code>buf</code> into the GL command stream, using the range
defined above.
</li>
<li>Return <code>promise</code>, but continue running these steps in parallel.
</li>
<li>Upon completion of the readback, queue a task performing the following steps:
<ul>
<li>If the context has been lost, or if <code>dstBuffer</code> has been neutered,
<a href="https://www.w3.org/2001/tag/doc/promises-guide/#reject-promise">reject</a>
<code>promise</code> with an <code>InvalidStateError</code>. In this case, no GL
error is generated.
</li>
<li>Write the readback result into <code>dstBuffer</code>, using the range defined
above.
</li>
<li><a href="https://www.w3.org/2001/tag/doc/promises-guide/#resolve-promise">Resolve</a>
<code>promise</code> with <code>dstBuffer</code>.
</li>
</ul>
The task source for this task is the <a href="#WEBGLCONTEXTEVENT">WebGL task source</a>.
</li>
</ul>
If the returned Promise is rejected, no data is written to <code>dstBuffer</code>.

<div class="note">
Even if <code>getBufferSubDataAsync</code> is called multiple times in a row with the same
<code>dstBuffer</code>, <code>then</code> callbacks added synchronously will never see
results of subsequent <code>getBufferSubDataAsync</code> calls.
</div>

<div class="note rationale">
Compared to the synchronous version of <code>getBufferSubData</code>, this version may
impose less overhead on applications. Intended use cases include reading pixels into a
pixel buffer object and examining that data on the CPU. It does not force the graphics
pipeline to be stalled as <code>getBufferSubData</code> does.
</div>
</function>
</newfun>

<history>
<revision date="2016/12/13">
<change>Initial revision.</change>
</revision>
</history>
</draft>
3 changes: 3 additions & 0 deletions sdk/tests/conformance2/extensions/00_test_list.txt
Expand Up @@ -2,3 +2,6 @@ ext-color-buffer-float.html
ext-disjoint-timer-query-webgl2.html
promoted-extensions.html
promoted-extensions-in-shaders.html
webgl-get-buffer-sub-data-async.html
webgl-get-buffer-sub-data-async-lose-context.html
webgl-get-buffer-sub-data-async-stress.html
@@ -0,0 +1,117 @@
<!--

/*
** Copyright (c) 2016 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/

-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WEBGL_get_buffer_sub_data_async context loss regression test.</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" width="1" height="1"></canvas>
<div id="console"></div>
<script>
"use strict";
description("This test makes sure that getBufferSubDataAsync does not cause crashes upon context loss.");

var wtu = WebGLTestUtils;
var canvas = document.getElementById("canvas");

var gl = wtu.create3DContext(canvas, undefined, 2);
var WEBGL_get_buffer_sub_data_async = gl.getExtension("WEBGL_get_buffer_sub_data_async");
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
if (!WEBGL_get_buffer_sub_data_async) {
testPassed("No WEBGL_get_buffer_sub_data_async support -- this is legal");
finishTest();
} else {
testPassed("Successfully enabled WEBGL_get_buffer_sub_data_async extension");
runTests();
}

var extension;

function runTests() {
var extensionName = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_lose_context");
if (!extensionName) {
debug("Could not find WEBGL_lose_context extension");
return;
}
extension = gl.getExtension(extensionName);

var iter = 0;
var ITERS = 20;
canvas.addEventListener("webglcontextrestored", test);
canvas.addEventListener("webglcontextlost", function(e) {
e.preventDefault();
webglHarnessCollectGarbage();
setTimeout(function() {
wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "extension.restoreContext()");
}, 100);
});

test();

function test() {
shouldBeFalse("gl.isContextLost()");
if (iter >= ITERS) {
finishTest();
return;
}
iter++;

var pbo = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
gl.bufferData(gl.PIXEL_PACK_BUFFER, 4, gl.DYNAMIC_READ);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
var readbackBuffer = new Uint8Array(4);

gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, 0);
var promise = WEBGL_get_buffer_sub_data_async.getBufferSubDataAsync(gl.PIXEL_PACK_BUFFER, 0, readbackBuffer);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
var completed = false;
promise.then(function(buf) {
testFailed("should not resolve");
}, function(e) {
testPassed("correctly rejected with " + e);
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no new errors from late rejection");
}).then(function() {
completed = true;
});

wtu.shouldGenerateGLError(gl, gl.CONTEXT_LOST_WEBGL, "extension.loseContext()");
}
}

var successfullyParsed = true;
</script>
</body>
</html>
@@ -0,0 +1,111 @@
<!--

/*
** Copyright (c) 2016 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/

-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WEBGL_get_buffer_sub_data_async stress test.</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" width="1" height="1"></canvas>
<div id="console"></div>
<script>
"use strict";
description("This test makes sure that getBufferSubDataAsync acts under stress as expected governed by WebGL 2.");

var wtu = WebGLTestUtils;
var canvas = document.getElementById("canvas");

var gl = wtu.create3DContext(canvas, undefined, 2);
var WEBGL_get_buffer_sub_data_async = gl.getExtension("WEBGL_get_buffer_sub_data_async");
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors from setup.");
if (!WEBGL_get_buffer_sub_data_async) {
testPassed("No WEBGL_get_buffer_sub_data_async support -- this is legal");
finishTest();
} else {
testPassed("Successfully enabled WEBGL_get_buffer_sub_data_async extension");
runTests();
}

function runTests() {
var pbo = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
gl.bufferData(gl.PIXEL_PACK_BUFFER, 4, gl.DYNAMIC_READ);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
var readbackBuffer = new Uint8Array(4);

var TEST_COUNT = 300;
var lastCounter = -1;
var promises = [];
debug("kicking off " + TEST_COUNT + " getBufferSubDataAsync calls");
for (var i = 0; i < TEST_COUNT; i++) {
(function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious: why make the loop body a function? why regular code will not do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a javascript programming pattern that guards against a weirdness of closures, which is that they always capture variables, not values. So if you capture i, then all of the closures (promise callbacks) will see i == TEST_COUNT instead of the loop value.

After writing this, though, I learned about let, which actually will solve this. I'm going to go ahead and clean this up using let.

var counter = i;
var color = [counter & 0xff, (counter >> 8) & 0xff, (counter >> 16) & 0xff, 255];
gl.clearColor(color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, 0);
var promise = WEBGL_get_buffer_sub_data_async.getBufferSubDataAsync(gl.PIXEL_PACK_BUFFER, 0, readbackBuffer);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
promise = promise.then(function(buf) {
if (buf[0] == color[0] &&
buf[1] == color[1] &&
buf[2] == color[2] &&
buf[3] == color[3]) {
testPassed("color readback #" + counter + " was correct");
} else {
testFailed("color readback #" + counter + " was incorrect: was " +
buf + " but expected " + color);
}
if (counter == lastCounter + 1) {
testPassed(" and its Promise resolved in order after " + lastCounter);
} else {
testFailed(" and its Promise resolved out of order order after " + lastCounter);
}
lastCounter = counter;
}, function(e) {
testFailed(e.toString());
});
promises.push(promise);
})();
}

Promise.all(promises).catch(function(e) {
testFailed(e.toString());
}).then(finishTest);
}

var successfullyParsed = true;
</script>
</body>
</html>