CNA-GAP-008: SDL_GPU ShaderEffect draws bind only the first vertex stream, and the limitation report misstates ShaderEffect instancing
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; 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's custom-effect draw takes its attributes from the first vertex buffer and binds slot 0 only, so other streams are never consumed, while its limitation text calls ShaderEffect instancing unimplemented although instance-indexed draws work.
- Identifier
CNA-GAP-008- Category
- Functional gap
- Subsystem
- Graphics & renderers
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- GraphicsDevice::DrawInstancedPrimitives / DrawIndexedPrimitives with a CNAEXT ShaderEffect applied, on SDL_GPU; GraphicsDevice::GetRendererCapabilityReportEXT()
Expected behaviour
XNA-style hardware instancing binds a per-vertex stream and a per-instance stream (a VertexBufferBinding with an instance frequency) and draws with an effect that reads both. SDL_GPU reports MultiStreamVertexInput true, and its stock routes build one vertex-buffer layout per bound stream. The renderer's English limitation text (GetAdditionalLimitationsTextEXT, part of GetRendererCapabilityReportEXT()) is meant to state what an application cannot branch on.
Actual behaviour at TARGET
With a ShaderEffect applied, DispatchStockDrawEXT and DrawInstancedPrimitivesEx send the draw to QueueCustomEffect3DDrawEXT, which derives every attribute from the first buffer's VertexDeclaration (location = element index, buffer_slot = 0); IssueCustomEffect3DDrawEXT binds exactly one vertex buffer at slot 0. Per-instance streams and additional per-vertex streams in GpuDrawParams::vertexStreams are ignored, and a shader that consumes a location beyond the first declaration is refused with System::NotSupportedException. Instanced ShaderEffect draws do work when the shader reads per-instance data from a storage buffer by gl_InstanceIndex (SMG-0022, the engine layer's particle route). The limitation text nevertheless says "CNAEXT ShaderEffect instancing is not implemented", which is wrong for that route and silent about the stream restriction.
Source locations
modules/renderers/sdl-gpu/src/SdlGpuRenderer.cpp— SdlGpuRenderer::QueueCustomEffect3DDrawEXT (attributes from the first declaration, buffer_slot 0)modules/renderers/sdl-gpu/src/SdlGpuRenderer.cpp— SdlGpuRenderer::IssueCustomEffect3DDrawEXT binds one vertex buffer; DrawInstancedPrimitivesEx SMG-0022 branchmodules/renderers/sdl-gpu/src/SdlGpuRenderer.cpp— SdlGpuRenderer::GetAdditionalLimitationsTextEXTmodules/renderers/webgpu/src/WebGPURenderer.cpp— WebGPURenderer::BuildCustomEffectVertexLayoutsEXT, the WMG-0021 comparison that binds per-instance streams
Evidence
Checked by reading at 009d40f5; not executed. The SMG-0019 to SMG-0022 custom-effect path was added shortly before TARGET. For comparison, WebGPU's BuildCustomEffectVertexLayoutsEXT (WMG-0021) gives a custom-effect draw one layout for the per-vertex stream at slot 0 and one for each per-instance stream. On SDL_GPU the location check prevents silently wrong output when the shader reads a missing location; a stream the shader never reads is simply unused.
Independent re-verification: GraphicsCapability.hpp says a renderer that would silently render from a subset of the bound streams must report MultiStreamVertexInput false. SDL_GPU reports it true for its stock routes, and on the ShaderEffect route the location check refuses a shader that reads a missing location while a stream the shader never reads is unused, so the output is not silently wrong; that is why this entry stays a gap and not a bug.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
The storage-buffer instancing route is exercised through the particle system in CnaGraphicsExtTests (CNA records SDL_GPU at 931/0/31 of 962, SMG-0042). No SDL_GPU test draws a ShaderEffect with a per-instance VertexBufferBinding or with two per-vertex streams, and no test reads the instancing sentence of the limitation text (sdlgpu_smoke_test.cpp and sdlgpu_occlusionquery_limitation_test.cpp check other sentences).
Regression test
An SDL_GPU example that draws a ShaderEffect with a per-vertex quad stream plus a per-instance offset stream and reads back two distinct instance positions (it fails today with the location refusal), and an assertion on the limitation text once it states the stream restriction precisely.
Blast radius
SDL_GPU only, and only draws shaded by a CNAEXT ShaderEffect. Stock-effect draws and storage-buffer instancing are unaffected; compiled XNA effects use a separate route not assessed here. Code ported from XNA that feeds instance data through a second vertex stream to a custom shader is refused on this renderer.
Workaround
On SDL_GPU, give a ShaderEffect its per-instance data through a storage buffer indexed by gl_InstanceIndex, or interleave all per-vertex attributes into one stream.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Shader effects: vertex inputs
- Known issues
- Functional gap index