CNA-BUG-280: SpriteSortMode::Immediate is batched on SDL_GPU and WEBGPU: a texture changed between two Draw calls of one Immediate batch shows its new contents in both sprites
Evidence basis: source-verified at the pinned commit; recorded by CNA's own run (not repeated here); tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit c6dec91f; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
SDL_GPU and WEBGPU accept Immediate but defer its stock sprites to a later flush, so a texture rewritten between two Draw calls is rasterised with its final contents; EasyGL, SOFTWARE, FNA3D and, since a fix awaiting Windows validation, DIRECTX9 submit per Draw.
- Identifier
CNA-BUG-280- Category
- Bug
- Subsystem
- Graphics & renderers
- Status
- Narrowed (partially fixed; describes only what survives)
- Verified against
- CNA
c6dec91f(c6dec91fbf3ef5440f60e5329e4a4758b2d80c22) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Recorded by CNA: CNA's own recorded run, not repeated here
- Tests touching this area
- Yes: see Current tests
- Affected contract
- Microsoft::Xna::Framework::Graphics::SpriteBatch with SpriteSortMode::Immediate on SDL_GPU and WEBGPU
Expected behaviour
XNA defines Immediate as drawing each sprite at its own draw call rather than at End(), so a texture mutated between two Draw calls leaves the first sprite showing the old contents.
Actual behaviour at TARGET
CNA passes the mode to the renderer, and what the renderer does is per family. EasyGL and FNA3D flush the sprite before Draw returns while immediate mode is set (EasyGL ends its sprite Draw with if (immediateMode_) FlushBatch(), commit 515958a7a, SOFTWARE-252), SOFTWARE rasterises at once, and DIRECTX9's sprite renderer flushes per Draw since 3b6a2eaf3 (CNA-DEMOS-ISSUE-003, whose Windows runtime validation CNA still lists as pending). SDL_GPU stores the mode and reads it only on its compiled-effect sprite route; stock sprites are recorded for replay at the next flush. WEBGPU's per-Draw FlushPendingSpritesEXT acts only in a CNA_WEBGPU_COMPILED_EFFECTS build and only on compiled-effect sprites; stock sprites stay queued. On both, texture uploads take effect immediately, so the earlier sprite of the batch samples the later contents.
CNA records this as its one current defect (handoff.md section 5.1, low priority, reproduced 2026-10-17): with EasyGL_SpriteBatch_SortModeSemantics, run on SDL_GPU as SdlGpu_CrossFamily_SpriteBatch_SortModeSemantics and elsewhere with CNA_GRAPHICS_RENDERER, leg B prints blue/blue where XNA gives red/blue. The test passes, because spritebatch_sort_mode_semantics_test.cpp reports the leg without asserting XNA's answer.
Source locations
modules/graphics/examples/spritebatch_sort_mode_semantics_test.cpp— leg B prints the mid-batch mutation result without asserting itmodules/renderers/sdl-gpu/src/SdlGpuRenderer.cpp— the sprite renderer reads immediateMode_ only on the compiled-effect route; stock sprites are queued for the flushmodules/renderers/webgpu/src/WebGPURenderer.cpp— WebGPUSpriteBatchRenderer::Draw: FlushPendingSpritesEXT acts only with CNA_WEBGPU_COMPILED_EFFECTSmodules/renderers/easygl/src/EasyGLRenderer.cpp— EasyGLSpriteBatchRenderer::Draw ends with if (immediateMode_) FlushBatch() (SOFTWARE-252)modules/renderers/directx9/src/D3D9SpriteBatch.cpp— flushes per Draw in immediate mode since 3b6a2eaf3handoff.md— section 5.1: the one recorded defect, reproduced 2026-10-17
Evidence
Checked by reading the named sources at c6dec91f; nothing was built or executed for this entry.
Narrowed by the independent adversarial audit: the entry originally named EasyGL as a renderer that batches below the seam. Commit 515958a7a (2026-09-09) made EasyGL honour immediate submission (FNA3D already did); nothing was executed.
Re-verified at c6dec91f: SetImmediateMode is overridden by every sprite renderer; SdlGpuRenderer.cpp reads immediateMode_ only on the compiled-effect sprite route, WebGPURenderer.cpp's per-Draw flush is compiled only with CNA_WEBGPU_COMPILED_EFFECTS, and D3D9SpriteBatch.cpp calls FlushBatch() per Draw in immediate mode (3b6a2eaf3). The behaviour on SDL_GPU and WEBGPU is CNA's own record in handoff.md section 5.1 (2026-10-17), not re-run here. CNA classes it as a defect; the deliberate-divergence record this entry used to cite is no longer in the tree.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
EasyGL_SpriteBatch_SortModeSemantics and SdlGpu_CrossFamily_SpriteBatch_SortModeSemantics (spritebatch_sort_mode_semantics_test.cpp) check order and placement and print the mid-batch mutation result (leg B) without asserting it. spritebatch_immediate_submission_contract_test.cpp (SOFTWARE-252) asserts immediate submission across a render-target switch and is registered for EasyGL and SOFTWARE only.
Regression test
Make leg B of the sort-mode fixture assert the first sprite's old contents (red/blue) on every family, and register the immediate-submission contract for SDL_GPU and WEBGPU.
Blast radius
Games that update a texture (SetData, render-target reuse) between Draw calls of one Immediate batch on SDL_GPU (the default renderer on Windows, macOS and iOS) or WEBGPU. Deferred and sorted modes, and the other renderers, are unaffected.
Workaround
End the batch before mutating the texture.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Known issues
- Bug index