CNA-GAP-009: Sub-pixel SpriteBatch destinations reach only EasyGL, SOFTWARE and METAL; every other renderer truncates them to whole pixels

CNA snapshot c1c316b9  ·  Known Issues › Functional gaps  ·  source links pinned to c1c316b9

✓

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 c1c316b9; 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, SOFTWARE and METAL sprite renderers override it.

Identifier
CNA-GAP-009
Category
Functional gap
Subsystem
Graphics & renderers
Status
Narrowed (partially fixed; describes only what survives)
Verified against
CNA c1c316b9 (c1c316b9c7a846ce8002809c151fcd1af14942c9)
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, the SOFTWARE sprite renderer and, since AM4-136, the METAL sprite renderer override it; VULKAN, SDL_GPU, WEBGPU, DIRECTX9, DIRECTX11, SDL_RENDERER, FNA3D and the rest draw on whole pixels. Smooth sub-pixel scrolling and fractional text scaling therefore snap on those renderers.

Source locations

Evidence

Checked by reading the named sources at c1c316b9; 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 c1c316b9; 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, SOFTWARE and (since AM4-136) METAL; no other renderer registers it, so the truncating renderers have no sub-pixel pixel check.

Narrowed at c1c316b9: 198adf18f (AM4-136) gave the METAL sprite renderer its own float-destination Draw override in MetalRenderer.mm and registered the shared spritebatch_subpixel_contract_test for METAL in the Metal examples; read at c1c316b9, not executed. The other renderers named above still inherit the truncating default.

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, SOFTWARE or METAL.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.