CNAEXT Extensions

CNA snapshot b0e97bb1

⚠

Opt-in and small. The CNA::Graphics extensions are compiled only when you configure CNA with -DCNA_CNAEXT=ON; the option is OFF by default, and no CNA workflow builds it, so nothing on this page is continuously integrated. The evidence is source, headers, CMake registrations and CNA’s own recorded test runs — we built and ran none of it. Everything below describes CNA snapshot b0e97bb1 (branch next).

CNA reimplements the XNA 4.0 API. What it adds on top is labelled as an extension: physically based materials and their shadow and lighting inputs, morph targets, renderer capability queries, the device-services layer, and one small opt-in graphics module with retro post-processing effects and a debug-drawing helper. This page is the reference for what “CNAEXT” means, what that module contains, which renderers can run it, how it is built and tested, and where its limits are. For a guided tour with code, start with Tutorial 115 and Tutorial 116.

What CNAEXT is

Four unrelated things share the name, and confusing them is the most common mistake. Only two of them are build switches.

PropertyCNAEXTCNA_STRICT_XNA_APICNA_CNAEXTCNA_DEVICES
What it isMarker macro (plus an EXT naming convention)Preprocessor defineCMake optionCMake option
DefaultExpands to nothingNever definedOFFOFF
EffectTags a declaration as “not XNA 4.0”; documentation and lint onlyTurns the marker into [[deprecated]] in the translation unit that defines itCompiles the CNA::Graphics extensions (modules/graphics-ext, 11 public headers: retro effects, debug drawing, shader payloads) and a few ShaderEffect overloadsCompiles CNA::Devices (modules/devices-ext, 17 public headers)
GranularityPer declarationPer translation unitPer buildPer build

The marker is exactly this (modules/core/include/CNA/CNAHelper.hpp):

#ifdef CNA_STRICT_XNA_API
#define CNAEXT [[deprecated("CNAEXT: not part of the XNA 4.0 API surface")]]
#else
#define CNAEXT
#endif

It is never a compile guard: everything it marks is compiled in every build. CNA_STRICT_XNA_API is not a CMake option — nothing in CMake declares it — so you set it on the one target you want checked, together with -Werror=deprecated-declarations. Its verification harness (cna_strict_xna_api_check, and a WILL_FAIL leak check) covers only the Microsoft::Devices and Sensors surface, not the graphics, content or input API, so a clean strict build is a hint about your own code, not a proof that you stayed within XNA 4.0. Tutorial 115 walks through the mechanism.

ℹ

The EXT suffix is a convention, not an enforced rule. New members on XNA types usually carry it (SetDisplayColorSpaceEXT, GetRendererCapabilityProfileEXT, SupportsShaderLanguageEXT), but marked members without it exist too (BasicEffect::SetOwnedTexture, Effect::Apply(), the class names ShaderEffect and ColorMatrixEffect). Use the marker, not the suffix, to decide what is an extension. There is no repository-wide validator that every non-XNA symbol is marked.

Where extensions live

NamespaceCompiled whenContents
CNA::Graphics (module graphics-ext)CNA_CNAEXT=ONThe extensions described on this page. Verbs are lowerCamelCase and accessors are getX()/setX()/isX(), not the XNA-style getXProperty() spelling.
CNA::Devices (module devices-ext)CNA_DEVICES=ONClipboard, file dialogs, message boxes, power, system info, locale, display info, tray, camera, URL launcher — see Tutorial 88 and Tutorial 117.
Microsoft::Xna::Framework::GraphicsalwaysCNAEXT-marked members and types that extend an XNA type: PbrEffect, SkinnedPbrEffect, ShaderEffect, ColorMatrixEffect, the shadow and IBL state types, morph targets, skinned-model data.
CNA:: (core value types)alwaysRendererCapabilityProfile, ShaderLanguageEXT, ShaderDiagnosticEXT, DisplayColorSpace.

CNA::Graphics is not a synonym for “gated”: CNA::Graphics::VertexTypeRegistryEXT lives in the always-compiled graphics module. And CNAEXT itself can never be a namespace, because it is a macro.

The always-compiled extension surface

A large part of what people call “CNAEXT” needs no build option. The headers say so deliberately: an effect's public surface must not change with a build flag.

Always compiledNotes
PbrEffect, SkinnedPbrEffectMetallic-roughness PBR; Tutorial 114. Implement IShadowReceiverEXT; take an ImageBasedLightEXT.
IShadowReceiverEXT, ShadowCascadeStateEXT, PunctualLightEXT, ImageBasedLightEXTShadow and image-based-lighting inputs. BasicEffect, SkinnedEffect, PbrEffect and SkinnedPbrEffect are receivers. CNA does not generate shadow maps or environment maps for you: render your own depth or environment pass into a render target (Tutorial 59) and hand the result to the receiver.
ShaderEffect(device, vertexSource, fragmentSource), its diagnostics and array settersGetCompileErrorEXT(), GetShaderDiagnosticsEXT(), GetSelectedShaderLanguageEXT(), SetUniformVec3Array/SetUniformMat4Array. The portable-payload constructors (ShaderCodeEXT, ShaderPackageEXT) are #ifdef CNA_CNAEXT.
ColorMatrixEffectA colour transform that only the CPU SpriteBatch path of SOFTWARE executes; Tutorial 116.
GraphicsDevice EXT queries and RendererCapabilityProfileSupportsRendererFeatureEXT, GetRendererCapabilityProfileEXT (32 features, 22 limits, per-format usage), GetRendererCapabilityReportEXT, SupportsShaderLanguageEXT, ExecutesShaderEffectSourceEXT, SupportsShadowSamplingEXT, SupportsImageBasedLightingEXT, SupportsSurfaceFormatAsRenderTargetEXT, base-instance draws, compute work-group limits. The capability queries report what a renderer can do internally; there is no public compute-shader, storage-buffer or GPU-timer class, so they are answers for diagnostics, not an API to dispatch work.
MorphTargetEXT, SkinnedModelEXT, AnimationPlayer, ModelAnimationsEXT, glTF importUnchanged since alpha.1 at the header level; Tutorial 112, Tutorial 113.
GamerServices avatar extensionsThe standard avatar renderer draws CNA’s own avatar art through XNA’s AvatarRenderer::Draw; see Gamer Services & Avatars. Independent of this module.

Building it, and how the gate works

OptionDefaultGates
CNA_CNAEXTOFFThe CNA::Graphics extensions, a few ShaderEffect overloads, and the implementation behind the C API’s graphics_ext.h routes.
CNA_DEVICESOFFCNA::Devices (devices-ext).
CNA_STRICT_XNA_APInot a CMake optionMakes CNAEXT deprecated in the consuming translation unit only.

Both options are attached as public compile definitions to the shared CNA::BuildConfig interface target, so every consumer of the CNA target sees the same macro the headers test. Both extension modules are always added and always compiled; with the option off, every source file and every header of graphics-ext is wrapped in #ifdef CNA_CNAEXT ... #endif (a text-level check enforces this) and compiles to nothing. The consequence is the classic trap: #include "CNA/Graphics/CRTEffect.hpp" succeeds in a default build and declares nothing, so the error you get is “no type named CRTEffect”, not “missing header”. The fix is the CMake flag, not an include path.

# The repository's own preset: OPENGLES3, Debug, CNA_CNAEXT=ON, tests on, examples off.
# (Its display name in CMakePresets.json still says "engine layer"; it builds this module.)
cmake --preset cnaext

# Or by hand, in your own build directory:
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3 -DCNA_CNAEXT=ON
# In a game project that adds CNA as a subdirectory (Tutorial 03), set the option first:
set(CNA_GRAPHICS_RENDERER "OPENGLES3" CACHE STRING "CNA graphics renderer")
set(CNA_CNAEXT ON CACHE BOOL "Build the CNA::Graphics extensions")
add_subdirectory(${CNA_DIR} ${CMAKE_BINARY_DIR}/cna)

target_link_libraries(MyGame PRIVATE CNA)   # CNA already includes graphics-ext; CNA_CNAEXT reaches your code

Then include the master header and use CNA::Graphics:

#include "CNA/Graphics/CNAEXT.hpp"   // every CNA::Graphics extension type; empty without CNA_CNAEXT

Where the tests and examples come from: the module’s tests need CNA_BUILD_TESTS and CNA_CNAEXT; the CRT and colour-depth demos additionally need CNA_BUILD_EXAMPLES and a renderer of OPENGLES3, OPENGL33 or VULKAN. The cnaext preset builds tests but not examples. Two more facts worth knowing before you plan a project around it:

  • Shared library and C API. A shared libcna names both extension modules when they are present. The C API keeps one exported ABI regardless of the option: graphics_ext.h declares its 44 routes (CRT, colour-depth and ASCII effects, debug drawing and two value initializers) in every build, and they return CNA_RESULT_NOT_SUPPORTED when the module is absent. See the C API page.
  • No extra dependencies. The effects’ shaders are embedded as one portable shader package (GLSL ES, desktop GLSL, SPIR-V and WGSL payloads, regenerated by tools/shader_package/generate_shader_package.py), with HLSL variants of the CRT and colour-depth shaders compiled in; at run time nothing links a shader compiler.

What the CNAEXT module contains

modules/graphics-ext has 11 public headers in CNA::Graphics, all included by CNA/Graphics/CNAEXT.hpp:

TypeWhat it does
CRTEffect (+ CRTMaskType)A full-screen ShaderEffect that emulates a cathode-ray display: scanlines, screen curvature, vignette and an aperture-grille, slot or shadow mask (setScanlineIntensity, setCurvature, setVignetteIntensity, setMaskIntensity, setMaskType).
DepthEffect (+ DepthEffectMode, DitherMode)Colour-depth reduction, not depth visualisation: quantizes the frame to 16-bit or 8-bit colour, to 4-, 2- or 1-bit greyscale, or to a real 216-colour or 16-colour palette, optionally with ordered (Bayer) dithering.
AsciiPostProcessEffect (+ AsciiQuantizeMode)Turns a finished frame into an ASCII-art look: reads the source texture back, averages cells (8×8 by default) and draws glyphs from a built-in atlas through SpriteBatch. It works on every renderer, including 2D-only and GPU-free ones, at the cost of a readback per frame. It is a drawing helper, not an Effect.
DebugDrawImmediate-mode lines for debugging 3D scenes: begin(view, projection), addLine, addBox, addSphere, addFrustum, addCross, setDepthTested, end; it draws through the core BasicEffect.
ShaderCodeEXT, ShaderPackageEXTPortable shader payloads: one package carries several shading-language variants and ShaderEffect selects the one the active renderer executes (ShaderPackageSelectionEXT).

CRT and colour-depth reduction are used the way any custom post-process is: draw the scene into a RenderTarget2D, then redraw it through SpriteBatch::Begin(…, &effect). Anything beyond these effects — bloom, tone mapping, shadow-map generation, sky rendering — is application code built on the XNA API, as Tutorial 66 and Tutorial 59 show.

Which renderers can run it

FeatureNeedsRenderers
CRTEffect, DepthEffectA renderer that executes custom ShaderEffect source in one of the package’s languagesThe EasyGL identities (GLSL ES and desktop GLSL), VULKAN (SPIR-V) and WEBGPU (WGSL) by the package; DIRECTX11 and DIRECTX12 report HLSL support and the effects carry HLSL variants, but no recorded run proves them on Direct3D. CNA’s demos are registered for OPENGLES3, OPENGL33 and VULKAN.
AsciiPostProcessEffectTexture2D::GetData and SpriteBatchEvery renderer that supports readback, 2D-only and GPU-free ones included.
DebugDrawBasicEffect line listsEvery 3D-capable renderer.

Ask at run time rather than assuming: GraphicsDevice::SupportsShaderLanguageEXT and ExecutesShaderEffectSourceEXT answer whether a ShaderEffect variant can run, and a ShaderEffect whose package has no usable variant fails at construction with a diagnostic.

A minimal example

#include "CNA/Graphics/CNAEXT.hpp"
#include "Microsoft/Xna/Framework/Graphics/RenderTarget2D.hpp"
#include "Microsoft/Xna/Framework/Graphics/SpriteBatch.hpp"

using namespace Microsoft::Xna::Framework::Graphics;

// LoadContent: one scene target and the effect.
sceneTarget_ = std::make_unique<RenderTarget2D>(device, width, height);
crt_ = std::make_unique<CNA::Graphics::CRTEffect>(device);
crt_->setScanlineIntensity(0.35f);
crt_->setCurvature(0.08f);

// Draw: render the scene off-screen, then present it through the effect.
device.SetRenderTarget(sceneTarget_.get());
DrawScene();
device.SetRenderTarget(static_cast<RenderTarget2D*>(nullptr));
SamplerState pointClamp = SamplerState::PointClamp;
spriteBatch_->Begin(SpriteSortMode::Deferred, BlendState::Opaque, &pointClamp, nullptr, nullptr, crt_.get());
spriteBatch_->Draw(*sceneTarget_, Rectangle(0, 0, width, height), Color::White);
spriteBatch_->End();

The calls follow CNA’s own crt_effect_demo (modules/graphics-ext/examples); the snippet was not compiled for this page. Tutorial 116 builds the same pipeline step by step, with DepthEffect and AsciiPostProcessEffect as well.

API style and ownership

Extension types follow C++ conventions rather than XNA’s: lowerCamelCase methods, getX()/setX() accessors and RAII ownership. CRTEffect and DepthEffect derive from ShaderEffect, so they are owned and disposed like any effect; AsciiPostProcessEffect and DebugDraw own their internal resources and must be destroyed before the GraphicsDevice they were created with.

Stability

The module is small and recent. Its API is not covered by any compatibility promise beyond CNA’s general pre-1.0 rule, and the C ABI routes in graphics_ext.h follow the C ABI’s own versioning (0.44.0 at this snapshot).

Test evidence and what is CI'd

  • Tests in the tree: 6 test files with 76 GoogleTest-family definitions for the module, 6 example programs (ASCII atlas, quantizer and pixel tests, CRT and colour-depth demos) and one shader-package reproducibility test (PostProcessShaderPackageReproducibility).
  • Recorded by CNA: CNA’s scope note records 78 of 78 retained-effect and graphics-profile tests, 4 of 4 ASCII examples and 17 of 17 focused C API tests passing with CNA_CNAEXT=ON on OPENGLES3, and a Vulkan compile, on 27 September 2026. That is CNA’s record, not a result we reproduced.
  • CI: no workflow configures CNA_CNAEXT=ON or the cnaext preset.

Limits and open questions

  • The ASCII effect pays a GPU-to-CPU readback every frame; a shader-only path is deliberately deferred so the effect keeps working on 2D-only renderers.
  • The HLSL variants of the CRT and colour-depth shaders have not been shown to render on DIRECTX11 or DIRECTX12.
  • DrawPrimitivesIndirectEXT and DrawIndexedPrimitivesIndirectEXT are declared on GraphicsDevice, but they take a renderer-internal argument buffer that no public class creates, so game code cannot use them.
  • CNA’s own preset description and parts of its older CNAEXT notes still describe a larger module; the headers are authoritative.

Where to go next