CNA-GAP-066: A runtime .cnj Texture2D colorKey leaves keyed texels as straight-alpha (key RGB, alpha 0), so they are invisible only under a non-premultiplied blend state
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.
The loose-tier .cnj Texture2D reader writes Color(keyR, keyG, keyB, 0) into a straight-alpha texture, deliberately: plans/plan_cnj.md CNB-9 defines the result as transparent with alpha 0, and CnjSourceFileTests pins it. Under SpriteBatch's default premultiplied AlphaBlend such a texel adds the key colour instead of vanishing; the compiled route premultiplies after keying and clears it.
- Identifier
CNA-GAP-066- Category
- Functional gap
- Subsystem
- Content & XNB/CNB/CNJ
- 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
- ContentManager::Load<Texture2D>(name) for a .cnj Texture2D document with colorKey; SpriteBatch::Begin() default BlendState::AlphaBlend
Expected behaviour
A colour-keyed texel is not visible when the texture is drawn with SpriteBatch::Begin()'s default blend state, as it is when the same document is compiled to .cnb.
Actual behaviour at TARGET
ApplyColorKey in ContentManager.cpp replaces each matching texel with Color(keyR, keyG, keyB, 0) and does not premultiply; the texture is straight alpha like every loose-loaded image. BlendState::AlphaBlend (One, InverseSourceAlpha) then adds the key colour to the destination inside the sprite's rectangle. BlendState::NonPremultiplied hides the texel. The pipeline comment in Texture2DContentPipeline.cpp records that an authored key keeps its colour on purpose.
Source locations
modules/content/src/Xna/ContentManager.cpp— ApplyColorKey and the Texture2D .cnj reader (ReadCnj)modules/content/src/Pipeline/Texture2DContentPipeline.cpp— compiled route: colour key, then premultiply; the build-versus-authored key commentmodules/content/src/Internal/CnjCanonicalRead.cpp— ReadCnjColorKey
Evidence
Checked by reading at 009d40f5; not executed; the draw result follows from the blend equation. The pipeline comment records a deliberate difference (a build-requested key clears the colour, a .cnj-authored key keeps it, XNAP-96); on the compiled route premultiplication then clears it, so the runtime route is the only place where the kept colour reaches a draw. CnjCacheIsolationTests.cpp asserts cache isolation and alpha only.
Independent re-verification: Read at 009d40f5; not executed. The original entry omitted CnjSourceFileTests.cpp (ColorKeyMakesMatchingPixelsTransparentOthersUnchanged), which asserts Color(255, 0, 255, 0) for keyed pixels, and plan_cnj.md CNB-9, which specifies 'magenta pixels become transparent (alpha 0)'. Writing Color(0,0,0,0) in ApplyColorKey would need that test changed. No CNA document promises invisibility under the default blend state for runtime-loaded straight-alpha textures.
Focused reproduction
// Illustrative; not compiled or run. sprite.cnj: {"sourceFile": "sprite.png", "colorKey": [255, 0, 255]}
auto texture = content.Load<Texture2D>("sprite");
spriteBatch.Begin(); // BlendState::AlphaBlend
spriteBatch.Draw(&texture, Vector2(0, 0), Color::White);
spriteBatch.End();
// Expected: keyed texels invisible. Actual by reading: background + (255, 0, 255).
Current tests
CnjCacheIsolationTests.cpp (keyed alpha and cache isolation); no draw test of a keyed .cnj texture.
Regression test
Load a keyed .cnj texture, draw it with the default SpriteBatch::Begin() over a known background, and read back: the keyed area must equal the background. The fix is writing Color(0, 0, 0, 0) in ApplyColorKey (or premultiplying on this route).
Blast radius
Runtime-loaded .cnj Texture2D documents with colorKey drawn with a premultiplied blend state, typically ports of colour-keyed 2D games. Build-requested keys and compiled .cnb textures are unaffected.
Workaround
Draw such textures with BlendState::NonPremultiplied, or compile the document to .cnb.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- ContentManager: JSON descriptor formats
- Known issues
- Functional gap index