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
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 cd7d8bd
stress test getBufferSubDataAsync
kainino0x a21d8ca
getBufferSubDataAsync-lose-context.html
kainino0x 05f5648
add getBufferSubDataAsync to WebGL 2 spec
kainino0x 183846a
more tests for getBufferSubDataAsync
kainino0x 4be0799
add corner case
kainino0x 4e129ff
update spec language to match new getBufferSubData
kainino0x b00c412
Correct stress test and add more tests
kainino0x df573f4
Merge branch 'master' into async
kainino0x 27b3624
remove ordering requirement, and fix/clean up some catches
kainino0x f357342
Merge branch 'master' into async
kainino0x 8b7df39
update spec language to match getBufferSubData
kainino0x 81121c7
hopefully improve robustness of getBufferSubDataAsync-lose-context.html
kainino0x a695837
add test for getBufferSubDataAsync when transform feedback is active
kainino0x 19f92b5
Merge branch 'master' into async
kainino0x b060aac
Revert "add test for getBufferSubDataAsync when transform feedback is…
kainino0x 4af531e
small fix
kainino0x 1e89eb2
Merge branch 'master' into async
kainino0x a8a2d58
test transform feedback error case
kainino0x 4c12d19
Merge branch 'master' into async
kainino0x 80df4a0
formalize getBufferSubDataAsync spec text + modify stress test
kainino0x 2ba44a8
Merge branch 'master' into async
kainino0x 90b809d
Merge branch 'master' into async
kainino0x cceab14
Merge branch 'master' into async
kainino0x fa9821c
move to extension
kainino0x 100232b
update tests
kainino0x 3c9a7c6
write extension draft
kainino0x 4748934
use 'let' in for-loops
kainino0x 5cabc1a
remove manual tests which were not updated anyway
kainino0x File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
extensions/WEBGL_get_buffer_sub_data_async/extension.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ArrayBuffer> 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<ArrayBuffer>"> | ||
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
sdk/tests/conformance2/extensions/webgl-get-buffer-sub-data-async-lose-context.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
111 changes: 111 additions & 0 deletions
111
sdk/tests/conformance2/extensions/webgl-get-buffer-sub-data-async-stress.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() { | ||
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> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 seei == 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 usinglet
.