CNA-BUG-006: SpriteEffect caches a MatrixTransform parameter that never exists, so OnApply() never sets the sprite projection

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

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.

SpriteEffect looks up a MatrixTransform parameter its base Effect never creates, so Parameters["MatrixTransform"] is null and OnApply() returns before computing the orthographic projection and half-pixel offset.

Identifier
CNA-BUG-006
Category
Bug
Subsystem
Graphics & renderers
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Low (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
Microsoft::Xna::Framework::Graphics::SpriteEffect: its MatrixTransform parameter and OnApply()

Expected behaviour

CNA's own SpriteEffect header documents the class as applying "a single world-view-projection matrix transform to a textured quad" and its OnApply() as applying "the sprite transform matrix parameter to the graphics device". OnApply() in SpriteEffect.cpp computes exactly that (Matrix::CreateOrthographicOffCenter over the viewport, multiplied after Matrix::CreateTranslation(-0.5f, -0.5f, 0.0f)). Note on XNA: XNA 4.0 has no public SpriteEffect type; its SpriteBatch owns a private Effect built from an embedded SpriteEffectCode blob and sets that effect's MatrixTransform parameter itself in PrepRenderState. A public SpriteEffect with a MatrixTransform parameter that Apply() fills is therefore a CNA class, not an XNA 4.0 contract.

Actual behaviour at TARGET

Both constructors call SpriteEffect::CacheEffectParameters(), which stores getParametersProperty()["MatrixTransform"]. Nothing ever adds a parameter of that name: Effect::Effect(GraphicsDevice&) adds only a technique named Default, and SpriteEffect adds no parameter. EffectParameterCollection::operator[](const std::string&) returns nullptr for an absent name, so matrixParam_ is always null and OnApply() returns at its first statement. Code that reads effect.getParametersProperty()["MatrixTransform"] on a SpriteEffect receives a null pointer, and Apply() never computes the projection its own documentation describes.

Source locations

Evidence

Checked by reading at 009d40f5; not executed. No renderer consumes the SpriteEffect class: the renderers that run XNA's sprite shader (for example D3D9SpriteBatch.cpp, D3D12SpriteBatch.cpp and SoftwareSpriteBatch.cpp) load their own embedded compiled SpriteEffect.fxb and look up MatrixTransform in that blob, and SpriteBatch.cpp never instantiates the class. Its only in-tree creator outside tests is the C API's cna_sprite_effect_create. The defect is therefore latent for ordinary SpriteBatch drawing and visible to code that instantiates SpriteEffect directly.

Independently observed as a separate finding (merged): CacheEffectParameters looks the name up on an effect that has no parameters, matrixParam_ stays null and OnApply returns before building XNA's half-pixel orthographic projection; renderers supply sprite projection themselves, so ordinary drawing is unaffected.

Independent re-verification: Checked by reading at 009d40f5; not executed. No renderer consumes the SpriteEffect class: the renderers that run XNA's sprite shader (for example D3D9SpriteBatch.cpp, D3D12SpriteBatch.cpp and SoftwareSpriteBatch.cpp) load their own embedded compiled SpriteEffect.fxb and look up MatrixTransform in that blob, and SpriteBatch.cpp never instantiates the class. Its only in-tree creator outside tests is the C API's cna_sprite_effect_create. The defect is therefore latent for ordinary SpriteBatch drawing and visible only to code that instantiates SpriteEffect directly; because XNA 4.0 has no public SpriteEffect, that code is CNA-specific (C++ or the C API), not ported XNA code. XNA's own SpriteBatch keeps the MatrixTransform parameter on a private Effect and sets it itself.

Focused reproduction

// Illustrative; not compiled or run for this entry.
using namespace Microsoft::Xna::Framework::Graphics;
GraphicsDevice device;
SpriteEffect effect(device);
EffectParameter* transform = effect.getParametersProperty()["MatrixTransform"];
// XNA 4.0: a Matrix parameter that Apply() fills with halfPixelOffset * projection.
// CNA at 009d40f5: transform == nullptr, and effect.Apply() computes nothing.
effect.Apply();

Current tests

SpriteEffectTests.cpp has CloneReturnsIndependentSpriteEffect and CloneAndOriginalApplyIndependently (Apply() does not throw). Neither asserts that the parameter exists or that Apply() writes a value.

Regression test

A SpriteEffectTests.cpp case asserting that getParametersProperty()["MatrixTransform"] is non-null and, after Apply() on a device with a known viewport, holds CreateTranslation(-0.5, -0.5, 0) * CreateOrthographicOffCenter(0, width, height, 0, 0, 1).

Blast radius

Code that constructs SpriteEffect directly (C++ or cna_sprite_effect_create) and reads, sets or relies on MatrixTransform, for example XNA code ported unchanged. Ordinary SpriteBatch drawing is unaffected because each renderer supplies the sprite transform itself; the C API's cna_effect_is_exact_stock_sprite_effect identity query is unaffected.

Workaround

Do not rely on SpriteEffect's MatrixTransform; ordinary SpriteBatch drawing needs no explicit SpriteEffect.

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

Maintainer workflow
Change public XNA behaviour
Known issues
Bug index