CNA-GAP-009: Sub-pixel SpriteBatch destinations reach only EasyGL and SOFTWARE; every other renderer truncates them to whole pixels
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 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
SpriteBatch forwards float destinations through ISpriteBatchRenderer's sub-pixel Draw overload, whose default truncates toward zero into an integer Rectangle; only the EasyGL and SOFTWARE sprite renderers override it.
- Identifier
CNA-GAP-009- Category
- Functional gap
- Subsystem
- Graphics & renderers
- Status
- Open
- Verified against
- CNA
3b43bd86(3b43bd8687ed46523d873f9bec4b32bd8481dcb5) - Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- SpriteBatch::Draw / DrawString with fractional positions or scales; ISpriteBatchRenderer::Draw(texture, float x, float y, float w, float h, ...)
Expected behaviour
XNA 4.0 carries sprite destinations as floats to the GPU, so a sprite at x = 10.5 lands between pixels and is filtered by the sampler (the interface comment in IGraphicsRenderer.hpp says the same).
Actual behaviour at TARGET
SpriteBatch calls the float overload for every queued sprite (SpriteBatch.cpp). The default implementation quantises each value with static_cast<int> (truncation toward zero, so -0.5 becomes 0) and forwards to the integer-Rectangle overload. Only EasyGLSpriteBatchRenderer and the SOFTWARE sprite renderer override it; SDL_GPU, WEBGPU, DIRECTX9, FNA3D and the rest draw on whole pixels. Smooth sub-pixel scrolling and fractional text scaling therefore snap on those renderers.
Source locations
modules/graphics/include/CNA/Internal/Renderers/Common/IGraphicsRenderer.hpp— ISpriteBatchRenderer float Draw default (quantise and forward)modules/graphics/src/Xna/SpriteBatch.cpp— renderer Draw call with destX/destY/destWidth/destHeightmodules/renderers/easygl/src/EasyGLRenderer.cpp— EasyGLSpriteBatchRenderer::Draw float overridemodules/renderers/software/src/SoftwareSpriteBatch.cpp— SoftwareSpriteBatchRenderer::Draw float override
Evidence
Checked by reading the named sources at 3b43bd86; nothing was built or executed for this entry. The set of overriding renderers was established by grepping renderer headers and sources for the float overload.
Independent re-verification: Checked by reading the named sources at 3b43bd86; nothing was built or executed for this entry. The set of overriding renderers was established by enumerating every ISpriteBatchRenderer implementation and grepping for the float overload (EasyGL, SOFTWARE). Test coverage: SpriteBatchSubPixelDestinationTest in SpriteBatchTests.cpp checks that SpriteBatch records fractional positions. spritebatch_subpixel_contract_test.cpp (SOFTWARE-137) is a renderer-neutral, byte-exact pixel fixture (a direct float draw must equal an integer draw under a transform matrix, which a truncating renderer fails) and is registered only for EasyGL and SOFTWARE, so the truncating renderers have no sub-pixel pixel check.
Re-verified at c6dec91f: a grep of every renderer for the float-destination overload finds only the EasyGL and SOFTWARE sprite renderers, and only their example trees register spritebatch_subpixel_contract_test.cpp. Not executed.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
SpriteBatchSubPixelDestinationTest in SpriteBatchTests.cpp checks that SpriteBatch records fractional positions; it does not check what each renderer draws.
Regression test
A renderer-neutral pixel fixture drawing a sprite at x + 0.5 with LinearClamp and asserting a blended edge column, registered for each family.
Blast radius
2D games with slow scrolling, sub-pixel camera motion or fractional text scale on the non-overriding renderers.
Workaround
Round positions yourself to get stable (if stepped) motion, or use an EasyGL identity or SOFTWARE.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Deep dives
- SpriteBatch: sub-pixel destinations
- Known issues
- Functional gap index