CNAEXT Extensions
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.
| Property | CNAEXT | CNA_STRICT_XNA_API | CNA_CNAEXT | CNA_DEVICES |
|---|---|---|---|---|
| What it is | Marker macro (plus an EXT naming convention) | Preprocessor define | CMake option | CMake option |
| Default | Expands to nothing | Never defined | OFF | OFF |
| Effect | Tags a declaration as “not XNA 4.0”; documentation and lint only | Turns the marker into [[deprecated]] in the translation unit that defines it | Compiles the CNA::Graphics extensions (modules/graphics-ext, 11 public headers: retro effects, debug drawing, shader payloads) and a few ShaderEffect overloads | Compiles CNA::Devices (modules/devices-ext, 17 public headers) |
| Granularity | Per declaration | Per translation unit | Per build | Per 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
| Namespace | Compiled when | Contents |
|---|---|---|
CNA::Graphics (module graphics-ext) | CNA_CNAEXT=ON | The 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=ON | Clipboard, file dialogs, message boxes, power, system info, locale, display info, tray, camera, URL launcher — see Tutorial 88 and Tutorial 117. |
Microsoft::Xna::Framework::Graphics | always | CNAEXT-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) | always | RendererCapabilityProfile, 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 compiled | Notes |
|---|---|
PbrEffect, SkinnedPbrEffect | Metallic-roughness PBR; Tutorial 114. Implement IShadowReceiverEXT; take an ImageBasedLightEXT. |
IShadowReceiverEXT, ShadowCascadeStateEXT, PunctualLightEXT, ImageBasedLightEXT | Shadow 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 setters | GetCompileErrorEXT(), GetShaderDiagnosticsEXT(), GetSelectedShaderLanguageEXT(), SetUniformVec3Array/SetUniformMat4Array. The portable-payload constructors (ShaderCodeEXT, ShaderPackageEXT) are #ifdef CNA_CNAEXT. |
ColorMatrixEffect | A colour transform that only the CPU SpriteBatch path of SOFTWARE executes; Tutorial 116. |
GraphicsDevice EXT queries and RendererCapabilityProfile | SupportsRendererFeatureEXT, 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 import | Unchanged since alpha.1 at the header level; Tutorial 112, Tutorial 113. |
GamerServices avatar extensions | The 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
| Option | Default | Gates |
|---|---|---|
CNA_CNAEXT | OFF | The CNA::Graphics extensions, a few ShaderEffect overloads, and the implementation behind the C API’s graphics_ext.h routes. |
CNA_DEVICES | OFF | CNA::Devices (devices-ext). |
CNA_STRICT_XNA_API | not a CMake option | Makes 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
libcnanames both extension modules when they are present. The C API keeps one exported ABI regardless of the option:graphics_ext.hdeclares its 44 routes (CRT, colour-depth and ASCII effects, debug drawing and two value initializers) in every build, and they returnCNA_RESULT_NOT_SUPPORTEDwhen 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:
| Type | What 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. |
DebugDraw | Immediate-mode lines for debugging 3D scenes: begin(view, projection), addLine, addBox, addSphere, addFrustum, addCross, setDepthTested, end; it draws through the core BasicEffect. |
ShaderCodeEXT, ShaderPackageEXT | Portable 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
| Feature | Needs | Renderers |
|---|---|---|
CRTEffect, DepthEffect | A renderer that executes custom ShaderEffect source in one of the package’s languages | The 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. |
AsciiPostProcessEffect | Texture2D::GetData and SpriteBatch | Every renderer that supports readback, 2D-only and GPU-free ones included. |
DebugDraw | BasicEffect line lists | Every 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=ONonOPENGLES3, and a Vulkan compile, on 27 September 2026. That is CNA’s record, not a result we reproduced. - CI: no workflow configures
CNA_CNAEXT=ONor thecnaextpreset.
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
DIRECTX11orDIRECTX12. DrawPrimitivesIndirectEXTandDrawIndexedPrimitivesIndirectEXTare declared onGraphicsDevice, 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
- Tutorial 115: CNAEXT overview — the marker, the options and the strict-XNA check.
- Tutorial 116: post-process effects — CRT, colour-depth and ASCII effects on a scene target.
- Tutorial 114: PBR materials —
PbrEffect, with image-based lighting and shadow inputs. - Tutorial 117: the devices layer —
CNA::Devices. - Effects System, Shader Effects and the C API.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- CNAEXT catalogue: extension surfaces by namespace — A catalogue of CNA's non-XNA surface at snapshot b0e97bb1, namespace by namespace, with member names, reasons and boundaries, how much the CNAEXT marker covers, and what the strict check proves.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-181: Sdl3Platform reports the nativeFileDialog capability on iOS and web builds, where SDL has no usable file-dialog backend — The SDL3 platform sets nativeFileDialog unconditionally, so on iOS and the web the platform says a dialog can be shown while FileDialog::getIsSupportedProperty says it cannot, and FileDialog::Show* reach SDL, which fails
- CNA-BUG-274: README.md calls the CNAEXT purity check "a dedicated CMake build option", but CNA_STRICT_XNA_API is a compile definition on two harness targets and no CMake option exists — The README describes the compile-time CNAEXT purity check as a dedicated CMake build option; cmake/Harnesses.cmake only sets the CNA_STRICT_XNA_API compile definition on two check targets, and passing -DCNA_STRICT_XNA_AP
- CNA-VGAP-056: The checked-in shader bytecode headers of VULKAN, SDL_GPU and the Direct3D renderers come from generators that no CTest or workflow runs and that have no --check mode — spirv_shaders.hpp (VULKAN, SDL_GPU), hlsl_shaders.hpp (DIRECTX11/12) and the d3d9_*shaders.hpp headers come from hand-run generators with no --check mode and no CTest or workflow, so a shader source edit without regenera