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

Conversation

kainino0x
Copy link
Contributor

@kainino0x kainino0x commented Oct 6, 2016

I took @kenrussell's spec text from #1937 but significantly rewrote it text to exactly match the new text for getBufferSubData where appropriate. I also changed the IDL to match getBufferSubData.

This entry point solves the problem of synchronous, blocking GPU-to-CPU memory readback on the main thread.

Some performance results in real-world usage (screen picking):
https://github.com/kainino0x/getBufferSubDataAsync-Demo

This PR fixes #1938.

Copy link
Member

@kenrussell kenrussell left a comment

Choose a reason for hiding this comment

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

This is excellent work @kainino0x . The tests are superb and you clearly thought through all of the edge cases when writing them.

Working group members, please review this; in particular, @jdashg @Kangz @grorg @RafaelCintron @zhenyao @junov . All of you reviewed the earlier pull request #1937 which has been closed in favor of this one.

Kai has implemented this in Chrome and it's showing excellent results. It eliminates the synchronous cost of getting data back from the GPU, and in Chrome, the time between when getBufferSubDataAsync is called and when the promise is resolved is less than when making the synchronous getBufferSubData call! This benefit will be realized for any multi-process or multi-threaded WebGL implementation. Kai will send more details and a test case to the mailing list tomorrow.

A couple of minor comments about the tests. They look basically perfect overall. The chaining of promises together in the main test is an excellent design and is comprehensible even to newcomers.

for (var i = 0; i < TEST_COUNT; i++) {
(function() {
var counter = i;
var color = [counter & 0xff, (counter << 8) & 0xff, (counter << 16) & 0xff, 255];
Copy link
Member

Choose a reason for hiding this comment

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

Did you mean to right shift instead of left shift these? They'll always shift in zero bits as written.

Copy link
Member

Choose a reason for hiding this comment

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

Still looking for an update in this area.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops, missed this. Fixed.

debug("");
finishTest();
}).catch(function(e) {
testFailed(e.toString());
Copy link
Member

Choose a reason for hiding this comment

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

Should this also call finishTest so the test harness (webgl-conformance-test.html) doesn't report this as a timeout if an exception is thrown?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, fixed

Copy link
Member

@kenrussell kenrussell left a comment

Choose a reason for hiding this comment

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

Looking great. One unresolved comment from earlier.

for (var i = 0; i < TEST_COUNT; i++) {
(function() {
var counter = i;
var color = [counter & 0xff, (counter << 8) & 0xff, (counter << 16) & 0xff, 255];
Copy link
Member

Choose a reason for hiding this comment

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

Still looking for an update in this area.

Copy link
Contributor

@Kangz Kangz left a comment

Choose a reason for hiding this comment

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

LGTM overall, a couple comments.

buf + " but expected " + color);
}
if (counter <= last_counter) {
testFailed("getBufferSubDataAsync calls #" + last_counter + " and #" + counter + " out of order");
Copy link
Contributor

Choose a reason for hiding this comment

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

The getBufferSubDataAsync promises being called in order doesn't appear in the spec (or I didn't find it) and seems to be an implementation detail. How about having a vector of bools and in Promise.all(promises).then() check that it is all true?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a good point. The command "executes serially with respect to others in the command stream" but the promises may not need to return that way. However, I'm not totally certain whether it should be changed in the spec or the test. @kenrussell, what do you think?

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure how the guarantees about what data is seen in the ArrayBufferView would be maintained if the promises resolved out of order. The same ArrayBufferView might be used as the target to getBufferSubData multiple times, and the promises' "then" callbacks would assume the data would be observed sequentially.

Should we say something in the spec about sequential calls A and B to getBufferSubDataAsync, and that the promise for call A must be resolved before the promise to call B, assuming both are not rejected early?

It'd be fine by me to change the test. Is there a simple way to express success? I would assume that Promise.all's resolve callback would only be called upon success of all of the individual promises and their resolve callbacks -- so throwing an exception out of one of them would cause Promise.all to fail. Could you investigate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the ArrayBufferView isn't written until promise resolution, I don't see a problem there. We don't make any guarantees about the contents of the ArrayBufferView after the promise callback is executed; only during its execution.

Yes, failing one would cause Promise.all to fail. But there can be testFailed messages inside the individual callbacks as well so I think that would be okay.

Copy link
Contributor

Choose a reason for hiding this comment

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

Like Kai, I don't think we need to add ordering to the spec, the fact that all the promises are resolved on the borwsing context event loop gives the guarantee that inside the promise.then, the ArrayBufferView content is correct.

Copy link
Member

Choose a reason for hiding this comment

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

OK. Given your analyses I think it's fine to leave the spec as is.

});

allPromises = allPromises.then(function() {
var code = "promise = gl.getBufferSubDataAsync(gl.ARRAY_BUFFER, 0, new ArrayBuffer(4))";
Copy link
Contributor

Choose a reason for hiding this comment

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

Why should these be rejected? Is it because no buffer is bound to gl.ARRAY_BUFFER (I think some might be)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Those are type checks, which I didn't make clear at all. I added debug prints to explain.

PASS correctly rejected with TypeError: Failed to execute 'getBufferSubDataAsync' on 'WebGL2RenderingContext': parameter 3 is not of type 'ArrayBufferView'.: promise = gl.getBufferSubDataAsync(gl.ARRAY_BUFFER, 0, new ArrayBuffer(4))
PASS correctly rejected with TypeError: Failed to execute 'getBufferSubDataAsync' on 'WebGL2RenderingContext': parameter 3 is not of type 'ArrayBufferView'.: promise = gl.getBufferSubDataAsync(gl.ARRAY_BUFFER, 0, null)

wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, code);
return expectRejected(promise, code);
});

Copy link
Contributor

Choose a reason for hiding this comment

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

A couple more tests that might be useful:

  • Check that getBufferSubDataAsync on an uninitialized (gl.bufferData with null) buffer returns zeros.
  • Check that getBufferSubDataAsync immediately followed by a buffer resize to be too small doesn't crash.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I will add those.

<code>dstData</code>. If any of the above errors occur, the Promise is already rejected upon
return. If the context is lost before the Promise is fulfilled, or if <code>dstData</code>
is neutered at the time the Promise is fulfilled, then the Promise will be rejected later.
In this case, no WebGL error is generated. If the promise rejects, no data is written to
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kenrussell Just want to make sure that this is the semantic we want. Right now no WebGL error will ever be generated upon resolution of the promise.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, that sounds like the best semantic to me.

@kainino0x
Copy link
Contributor Author

I merged master into this branch, updated the spec language to match getBufferSubData, etc. I think I've addressed all of the outstanding comments and issues.

@Kangz
Copy link
Contributor

Kangz commented Oct 11, 2016

Still LGTM

@kainino0x
Copy link
Contributor Author

Added a test for the transform feedback error case (finally).

Also added in getBufferSubData tests in #2097

@kainino0x
Copy link
Contributor Author

kainino0x commented Oct 12, 2016

Added a test for the transform feedback error case (finally).

Removed, will add it back to this PR once that one has landed and this PR's fate has stabilized.

EDIT: it's added back now

@kenrussell
Copy link
Member

Excellent work. For a little more background for other reviewers: the new form of the spec text came from a suggestion from Elliott Sprehn from the Blink team, pointing us at other web specs that return promises.

LGTM.

@kainino0x
Copy link
Contributor Author

I've finally moved this entire spec into an extension. The extension is implemented and tested on my local Chromium build. Aside from the new specification text, everything is as it was before. Ready for review.

Copy link
Contributor

@zhenyao zhenyao left a comment

Choose a reason for hiding this comment

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

LGTM with a question
Feel free to merge yourself

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.

});

for (var i = 0; i < types.length; 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.

Same question here.

debug("kicking off " + TEST_COUNT + " getBufferSubDataAsync calls");
for (var i = 0; i < TEST_COUNT; i++) {
insertTimeoutAssertion();
(function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I actually forgot about these tests and haven't updated them. I need to do this before I merge.

Copy link
Contributor

Choose a reason for hiding this comment

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

What about leaving these manual tests out of this pull request?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, good call. Done.

@kainino0x kainino0x changed the title add+test getBufferSubDataAsync for WebGL 2 WEBGL_get_buffer_sub_data_async extension Dec 15, 2016
@kainino0x kainino0x merged commit 8415ca3 into KhronosGroup:master Dec 15, 2016
@kainino0x kainino0x deleted the async branch December 15, 2016 19:41
@zhenyao
Copy link
Contributor

zhenyao commented Dec 15, 2016

lgtm

@kenrussell
Copy link
Member

Very nice @kainino0x and thanks @zhenyao for carrying forward the code review. LGTM after the fact.

xingri pushed a commit to xingri/WebGL that referenced this pull request Jan 6, 2017
Add an extension for asynchronous buffer readback. Name subject to bikeshedding.

Fixes KhronosGroup#1938.
@kainino0x kainino0x restored the async branch February 24, 2017 18:42
@kainino0x kainino0x deleted the async branch February 24, 2017 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add conformance tests for getBufferSubDataAsync
4 participants