Repository navigation
[GLEmulation] Re-read client-side vertex arrays at draw time - #27974
TheTomBritton wants to merge 3 commits into
Conversation
Real GL reads client-side vertex arrays when the draw call executes, so an application may keep the same arrays bound via gl*Pointer and refill them in place between draws. GLImmediate cached the restrided copy of planar client arrays and skipped the re-copy unless a gl*Pointer or client-state call had set modifiedClientAttributes since the last draw, so every such draw re-drew the first draw's vertex data. Two changes, both needed: - Drop the modifiedClientAttributes early-out (and the now-unread flag) from prepareClientAttributes, so the client arrays are examined on every draw, per the discussion in emscripten-core#27945. - Stop overwriting attr.pointer with the restrided staging location in the planar slow path. With the early-out gone this overwrite made the next prepareClientAttributes re-read the staging buffer's interleaved output using the original planar strides (reading its own output, with the wrong layout) instead of the application's arrays. Nothing needs the overwritten value: rendering works from attr.offset / stride / vertexPointer, the interleaved fast path needs the real pointer to compute offsets, and glGetPointerv now answers with the pointer the application actually set. Planar client arrays are re-copied on every draw now, which is slower, but they were already the documented very-slow path and correctness wins; interleaved arrays remain the fast path. Adds a browser test that draws planar client arrays, refills them in place without touching any GL state, draws again, and asserts via readPixels that each draw showed its own data. The test fails before this change and passes after it, and the fix was also verified on the real-world app the bug was found in (see emscripten-core#27945 for captures). Fixes emscripten-core#27945 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| // Leave attr.pointer at the client's own array: client-side vertex | ||
| // arrays are read at draw time, so the next draw call must re-read | ||
| // (and re-restride) the array's contents from the application's | ||
| // memory, not from this staging copy of the previous draw's data. |
There was a problem hiding this comment.
| // Leave attr.pointer at the client's own array: client-side vertex | |
| // arrays are read at draw time, so the next draw call must re-read | |
| // (and re-restride) the array's contents from the application's | |
| // memory, not from this staging copy of the previous draw's data. | |
| // n.b. The above restrided data cannot be cached, since the user might change the | |
| // vertex data between draws, and there is no way to detect that. So later draws must | |
| // re-do the above work. |
| - `LEGACY_GL_EMULATION` now re-reads client-side vertex arrays at every draw | ||
| call, as real GL does. Previously, planar (non-interleaved) client arrays | ||
| that an application refilled in place between draws kept being drawn with | ||
| the first draw's data. Planar client arrays are consequently re-copied on | ||
| every draw, which can be slower; they were already the documented slow path, | ||
| and interleaved arrays remain fast. (#27945) |
There was a problem hiding this comment.
| - `LEGACY_GL_EMULATION` now re-reads client-side vertex arrays at every draw | |
| call, as real GL does. Previously, planar (non-interleaved) client arrays | |
| that an application refilled in place between draws kept being drawn with | |
| the first draw's data. Planar client arrays are consequently re-copied on | |
| every draw, which can be slower; they were already the documented slow path, | |
| and interleaved arrays remain fast. (#27945) | |
| - Removed broken caching of client-side vertex arrays with `LEGACY_GL_EMULATION`, | |
| since there is no sound way to detect if the cached data might have gotten | |
| modified by the user between draws. (#27945) |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juj
left a comment
There was a problem hiding this comment.
LGTM, a couple of comment suggestions to avoid LLMism creep.
This change can have a performance impact to FFP GL code, but given that the caching logic was not sound, we cannot unfortunately keep that.
|
Applied both suggestions verbatim in 2b0589f — thanks for the review! |
There was a problem hiding this comment.
Is this comment not out of date (since it refers to defining attr.pointer)?
There was a problem hiding this comment.
Good catch, that clause was out of date now that the loop no longer assigns attr.pointer. Dropped it in ced9b58.
| #include <assert.h> | ||
| #include <stdio.h> | ||
|
|
||
| static void read_pixel(int x, int y, GLubyte rgba[4]) { |
There was a problem hiding this comment.
minor nit: Drop the static here. See #27873. I'm trying to keep the tests consistent in this regard.
The restride loop no longer defines attr.pointer, so its comment no longer says it does. The test helper drops `static`, matching emscripten-core#27873. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UR3bCLyLBQiGtevGJx2ARt
|
lgtm.. will land once the tests are passing. |
|
I think a rebase (or merge) will fix of the current failures. |
Fixes #27945.
Real GL reads client-side vertex arrays when the draw call executes, so an application may keep the same arrays bound via
gl*Pointerand refill them in place between draws. GLImmediate cached the restrided copy of planar client arrays and skipped the re-copy unless agl*Pointer/client-state call had setmodifiedClientAttributessince the last draw — so every such draw re-drew the first draw's vertex data.Two changes, both needed:
modifiedClientAttributesearly-out fromprepareClientAttributes(and the now-unread flag everywhere), as proposed by @juj in the issue, so client arrays are examined at every draw.attr.pointerwith the restrided staging location in the planar slow path. With the early-out gone, this overwrite made the nextprepareClientAttributesre-read the staging buffer's interleaved output using the original planar strides — reading its own previous output with the wrong layout — instead of the application's arrays. Nothing needs the overwritten value: rendering works fromattr.offset/stride/vertexPointer, the interleaved fast path needs the real pointer to compute offsets, andglGetPointervnow answers with the pointer the application actually set.Verification that (1) alone is not sufficient and (1)+(2) fixes the real-world app this was found in, with captures: #27945 (comment)
Test:
browser.test_gl_client_arrays_refilldraws planar client arrays, refills them in place without touching any GL state, draws again, and asserts viaglReadPixelsthat each draw showed its own data. It fails before this change (second draw re-draws the first draw's triangle; the asserted pixel stays at the clear color) and passes after it.Perf note: planar client arrays are now re-copied on every draw. That is slower, but they were already the documented very-slow path (
GL_ASSERTIONSwarns about it), correctness wins per the issue discussion, and interleaved arrays remain the fast path.🤖 Generated with Claude Code