CNA-BUG-113: An enabled zero-area ScissorRectangle draws unclipped on SDL_GPU, while every other rasterizing renderer clips everything
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 3b43bd86; 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 ApplyScissorForRef treats a zero width or height like a disabled scissor and uses the full target; XNA's contract (rasterise only inside the rectangle) means nothing is drawn, as the other renderers in CNA's shared scissor fixture do.
- Identifier
CNA-BUG-113- Category
- Bug
- Subsystem
- Graphics & renderers
- Status
- Open
- Verified against
- CNA
3b43bd86(3b43bd8687ed46523d873f9bec4b32bd8481dcb5) - Severity
- Medium (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
- GraphicsDevice.ScissorRectangle with zero width or height and RasterizerState.ScissorTestEnable = true
Expected behaviour
With the scissor test enabled, only pixels inside the rectangle are written; an empty rectangle writes nothing (Direct3D 9, which XNA runs on, behaves this way, and so do WEBGPU, EasyGL, SOFTWARE and DIRECTX9 in CNA).
Actual behaviour at TARGET
SdlGpuRenderer.cpp's ApplyScissorForRef takes its full-target branch unless scissorEnabled && scissorW > 0 && scissorH > 0, so an enabled rectangle of zero width or height draws over the whole target. WebGPURenderer.cpp's ApplyDrawScissor clips an enabled empty rectangle to nothing, and the other rasterizing renderers do the same. So a UI panel whose clip rectangle collapses to zero height draws its whole content over the screen on SDL_GPU (the default renderer on Windows, macOS and iOS) and disappears elsewhere. CNA already records this: the shared fixture deferred_scissor_capture_test.cpp declares emptyScissorDrawsNothing false for SDL_GPU (‘measured here’), and its check E1 (3D draws into a render target) asserts the unclipped output there, so the wrong result is pinned as expected output.
Source locations
modules/renderers/sdl-gpu/src/SdlGpuRenderer.cpp— SdlGpuRenderer::ApplyScissorForRefmodules/renderers/webgpu/src/WebGPURenderer.cpp— WebGPURenderer::ApplyDrawScissormodules/graphics/examples/deferred_scissor_capture_test.cpp— the per-renderer contract table: emptyScissorDrawsNothing false for SDL_GPU only
Evidence
Checked by reading the named sources at 3b43bd86; nothing was built or executed for this entry.
Independent re-verification: Checked by reading the named sources at 3b43bd86; nothing was built or executed for this entry. CNA's own records agree: deferred_scissor_capture_test.cpp, registered as SdlGpu_Deferred_Scissor, sets zero-width, zero-height and 0x0 scissors in check E1 (3D draws into a render target) and declares through emptyScissorDrawsNothing=false that SDL_GPU does not clip them, while the other families in that fixture declare that a degenerate rectangle rasterizes nothing. The SpriteBatch path with an empty rectangle is not exercised.
Re-verified at c6dec91f: the fixture's contract table now covers WEBGPU, EASYGL, SDL_GPU, SOFTWARE, HEADLESS and DIRECTX9; only SDL_GPU declares emptyScissorDrawsNothing false (EasyGL's own early return was fixed by SOFTWARE-310), and ApplyScissorForRef is unchanged. Not executed.
Focused reproduction
// Illustrative; not compiled or run for this entry.
device.setScissorRectangleProperty(Rectangle(10, 10, 0, 50)); // collapsed panel
batch.Begin(SpriteSortMode::Deferred, nullptr, nullptr, nullptr, scissorOn);
batch.Draw(panelContent, Rectangle(0, 0, 800, 600), Color::White);
batch.End();
// Expected: nothing drawn. SDL_GPU: full-screen panel content.
Current tests
SdlGpu_Deferred_Scissor (deferred_scissor_capture_test.cpp, check E1) draws 3D geometry into a render target with zero-width, zero-height and 0×0 scissors and pins the unclipped output for SDL_GPU (emptyScissorDrawsNothing=false); the other families in that fixture declare that a degenerate rectangle rasterizes nothing. The SpriteBatch path with an empty rectangle is not exercised.
Regression test
Flip the emptyScissorDrawsNothing declaration for SDL_GPU once the renderer clips a degenerate rectangle: the fixture then fails until it does.
Blast radius
Clipped UI (scroll views, collapsing panels, animated reveals) that can reach zero size on SDL_GPU.
Workaround
Skip drawing when the clip rectangle is empty.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Deep dives
- Viewport and scissor · State objects: scissor
- Known issues
- Bug index