CNAEXT graphics extensions internals
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. Checked by reading modules/graphics-ext (nine translation units, eleven public headers, six test files with 76 test-macro definitions, six example programs), the two shader-package generators and their CMake registration, the core ShaderEffect and GraphicsDevice declarations the module touches, and the C header graphics_ext.h; none of the tests was executed. The validation figures quoted from CNA's module scope document are CNA's record, not ours. Whether the CRT and colour-depth effects render correctly on each renderer, in particular through the HLSL variants on the Direct3D renderers, was not established.
modules/graphics-ext/ holds a small set of CNA-only graphics utilities that sit on top of XNA's GraphicsDevice: three retro effects (AsciiPostProcessEffect, CRTEffect and DepthEffect), the DebugDraw wireframe helper, and the portable shader-package types ShaderCodeEXT and ShaderPackageEXT that the effects and ShaderEffect's package constructors use. Everything in it compiles only with CNA_CNAEXT=ON. It is not a renderer: it selects no native graphics API and owns no operating-system surface. It is not a frame pipeline either: each effect is used directly, through SpriteBatch or its own Draw call, in the ordinary render-target pattern, and the game decides where in its frame each one runs. This page traces how each piece reaches the screen, explains how the module's one shader package is authored, selected and checked, and maps the sources, C routes and tests. The user-level reference is the CNAEXT extensions guide; this page is for maintainers changing an effect, DebugDraw or the shader package.
The physical and compile-time boundary
modules/graphics-ext/CMakeLists.txt globs src/*.cpp (nine translation units at this snapshot) into cna_add_module(cna_graphics_ext GraphicsExt ...), the helper in modules/CMakeLists.txt that makes a STATIC library with the alias CNA::GraphicsExt and a public link to cna_build_config. The module links cna_graphics_core and the Sharp Runtime component Core.Base publicly and owns its own examples/ directory. Its public surface is eleven headers under include/CNA/Graphics/, all in namespace CNA::Graphics: the master include CNAEXT.hpp and the ten it includes (AsciiPostProcessEffect, AsciiQuantizeMode, CRTEffect, CRTMaskType, DepthEffect, DepthEffectMode, DitherMode, DebugDraw, ShaderCodeEXT and ShaderPackageEXT). Two internal ASCII helper headers, AsciiFontAtlas.hpp and AsciiQuantizer.hpp, live under include/CNA/Internal/Graphics/Ascii/.
CNA_CNAEXT is an OFF-by-default option declared in CMakeLists.txt ("Enable standalone ASCII, CRT, colour-depth and DebugDraw graphics extensions"). modules/CMakeLists.txt turns it into a compile definition on the shared cna_build_config interface target, which every module and every consumer inherits, so a header and the code it guards always see the same macro. Every header and source of the module wraps its content in #ifdef CNA_CNAEXT and a closing #endif // CNA_CNAEXT, and the module's tests carry the same guard. scripts/check_cnaext_guards.sh checks that rule, but no CMake file at this snapshot registers it as a ctest, so the discipline is upheld by review. The practical result is the classic trap: #include "CNA/Graphics/CRTEffect.hpp" succeeds in a default build and declares nothing. CnaExtMasterIncludeTests is the test that asserts CNAEXT.hpp declares every retained type when the option is on. No CI workflow configures CNA_CNAEXT=ON; the local route is the cnaext configure and build preset in CMakePresets.json, which sets OPENGLES3, CNA_CNAEXT=ON and CNA_BUILD_TESTS=ON and builds CnaTests.
CNA::CnaExt is a separate INTERFACE umbrella (cna_cnaext) over cna_graphics_ext and cna_devices_ext; the device extensions in that second module are gated by their own macro, CNA_DEVICES, not by CNA_CNAEXT (device extension internals). The CNA aggregate lists cna_graphics_ext and cna_cnaext among its runtime parts, so the targets are always present; only the macro decides whether the guarded functionality exists. One dependency runs the other way: core ShaderEffect.hpp declares, under #ifdef CNA_CNAEXT, two public constructors taking ShaderCodeEXT or ShaderPackageEXT and one protected package-with-fallback constructor, but their definitions live in this module's ShaderPackageEXT.cpp, so code that uses them links cna_graphics_ext (the CNA umbrella does). To change one extension, check four things independently: target presence, the compile define, what the effect needs from the device (CustomEffects and a usable shader variant for CRT and colour depth, texture readback for ASCII), and which renderer is selected underneath. The ASCII effect is a CPU glyph quantiser drawn with SpriteBatch; it is not a selectable renderer identity.
Game owns GraphicsDevice [neutral; the selected renderer sits underneath]
-> game renders its scene into a RenderTarget2D it owns
-> AsciiPostProcessEffect::Draw(source[, destination])
GetData readback -> AsciiQuantizer cell grid -> two SpriteBatch quads per cell from an owned glyph atlas
-> SpriteBatch::Begin(..., CRTEffect or DepthEffect) / Draw(source) / End
ShaderEffect compiled from the selected post_process variant (or the GLSL ES fallback)
-> DebugDraw begin(view, projection) / add... / end
depth-tested list, then overlay list -> BasicEffect + DrawUserPrimitives(LineList)
Where the effects keep their settings
Each extension holds its own parameters; there is no shared settings object, and no accessor on GraphicsDevice returns one. CRTEffect stores scanline, curvature, vignette and mask intensity (each setter clamps to 0..1) and a CRTMaskType (None, ApertureGrille or ShadowMask), and writes them as the uniforms uCrtParams and uMaskType in OnApply (CRTEffect.cpp). DepthEffect stores a DepthEffectMode and a DitherMode and writes uDepthParams; for the two palette modes it builds two small palette textures on first use (the 216-entry web-safe palette for Palette256 and the 16-entry EGA palette for Palette16) and binds one as uPalette at texture slot 1 (DepthEffect.cpp). AsciiPostProcessEffect stores a cell size (8 by 8 by default) and an AsciiQuantizeMode whose initial value comes from the CNA_ASCII_MODE environment variable (Color unless it reads blackwhite, in any case). DebugDraw keeps only its open batch and the depth-test mode. That placement is a deliberate application of the compile-time boundary described above. GraphicsDevice is an XNA type compiled in every build, while these types exist only when CNA_CNAEXT is on. A device member of one of them would give an XNA type a member whose type exists only under a compile option.
The C routes mirror the same ownership: the CRT and colour-depth routes in graphics_ext.h get and set the parameters of an ordinary CNA_EffectHandle, and the ASCII and DebugDraw routes use handles of their own. Checked by reading at c1c316b9.
How each extension reaches the screen
There is no frame object in this module and nothing subscribes to the game loop. Each extension is an ordinary client of the GraphicsDevice it was constructed with, and its draw happens where the game calls it. The usual pattern for the three effects is to render the scene into a RenderTarget2D, unbind it, and then draw that target to the back buffer through the effect.
AsciiPostProcessEffect: a CPU pass drawn with SpriteBatch
The constructor of AsciiPostProcessEffect builds an owned glyph-atlas Texture2D from the internal font atlas (uploaded with SetDataRGBA) and an owned SpriteBatch. Draw(source[, destinationRectangle]) reads the whole source texture back with GetData, repacks it into tightly packed RGBA8 bytes (the code packs explicitly because Color is not a 4-byte type, so its array cannot be reinterpreted as raw pixels), and hands it to QuantizeFrameToGrid in AsciiQuantizer.cpp, which returns a grid of cells, each with a glyph index, a foreground colour and, in Color mode, a background colour. It then draws two quads per cell from the atlas (a solid background, black in BlackWhite mode, and the glyph) inside one SpriteBatch with PointClamp sampling and AlphaBlend, computing each cell's edges from its row and column so that neighbouring cells share an edge exactly. The default destination is the viewport, and GetLastGridDimensions reports the grid of the last call. Two consequences follow: the source must be a texture the renderer can read back, normally a render target the game drew into, and every call costs a full GPU-to-CPU readback. setCellSize rejects a non-positive size with std::invalid_argument.
CRTEffect and DepthEffect: ShaderEffects for SpriteBatch
Both classes derive from core ShaderEffect and are passed to SpriteBatch::Begin like any other effect. Their constructors call the protected package-with-fallback ShaderEffect constructor with a package built in PostProcessShaderPackages.cpp (CreateCrtShaderPackage, CreateDepthEffectShaderPackage) and the GLSL ES sources as the fallback. When the live renderer can use a variant of the package, that exact language is selected, recorded on the effect and compiled. When no variant is usable, the constructor takes the legacy source path instead, which keeps the legacy constructor's non-throwing compile and invalid-effect behaviour; IsEffectValid() tells the two outcomes apart. OnApply first runs ShaderEffect::OnApply and then sets the uniforms described above. DepthEffect is colour-depth and palette reduction with optional ordered dithering: DepthEffectMode selects 16-bit or 8-bit colour, 4-, 2- or 1-bit greyscale, or one of the two palettes, and DitherMode adds a 4×4 or 8×8 Bayer pattern. It does not visualise a depth buffer. Clone() on either class returns a new effect of the same type with the same parameters.
DebugDraw: batched lines through BasicEffect
DebugDraw owns a core BasicEffect with vertex colours enabled. begin(view, projection) empties both line lists, resets the mode to depth-tested and opens a batch. addLine, addBox, addSphere (two overloads; the segment count is clamped rather than trusted), addFrustum and addCross append VertexPositionColor pairs to the depth-tested list or the overlay list, whichever setDepthTested selected when the shape was added; clear() empties both and leaves the batch open. end() does nothing without an open batch. Otherwise it records the current DepthStencilState, draws the depth-tested list with DepthStencilState::Default and then the overlay list with DepthStencilState::None (so an overlay line stays visible through geometry in front of it), each through Apply, SetVertexBuffer(nullptr) and DrawUserPrimitives(LineList), then restores the depth-stencil state and empties the lists. Only the depth-stencil state is restored: the vertex-buffer binding and the applied effect are left as the last draw set them.
Resource lifetime, reset and exception edges
The module's objects own few GPU resources. AsciiPostProcessEffect owns its atlas texture and its SpriteBatch; DepthEffect owns two palette textures created on first use; CRTEffect and DepthEffect own their compiled program through ShaderEffect; DebugDraw owns its BasicEffect. Each holds a reference or pointer to the GraphicsDevice it was built with, so each must be destroyed while that device still exists. No file in the module subscribes to DeviceReset or DeviceLost (no match at this snapshot), so after a device reset these resources are only as valid as the selected renderer keeps them; that was not established here. None of this is a thread-safety guarantee: the objects are ordinary graphics clients and follow the device's thread rules (thread and callback map).
The exception edges are visible in the source. DebugDraw::end() lowers its open flag before it draws, and has no scope guard: if a draw throws, the lists are not emptied and the saved depth-stencil state is not restored (the next begin empties the lists again). AsciiPostProcessEffect::Draw begins its SpriteBatch only after the readback and quantisation, so a failing GetData leaves no batch open, but an exception between Begin and End leaves the effect's batch begun. These are source-observed risks to validate before changing that code, not reported bugs; a change needs an exception test as well as a successful-frame image.
Shader packages: from manifest to the selected variant
The module carries one shader package, src/shaders/post_process. Its package.json (schema: 1, namespace CNA::Graphics::detail::PostProcessGenerated, preprocessor guard CNA_CNAEXT) declares a fullscreen vertex shader and the CRT and colour-depth fragment shaders in four groups: glsl-es, desktop glsl, spirv (compiled from Vulkan GLSL) and wgsl (derived from that SPIR-V); the checked-in PostProcessShaderPackage.generated.hpp embeds them. A second checked-in header, PostProcessHlsl.generated.hpp, carries HLSL translations of the fullscreen vertex shader and the two fragment shaders, and PostProcessShaderPackages.cpp adds them as Hlsl variants when it finds an HLSL fragment for the package's SPIR-V label. The only other package in the tree is the portable tint fixture under modules/graphics/examples.
The authoring chain
generate_shader_package.py (described in its README) reads a manifest with schema: 1, a C++ namespace, a preprocessor guard and a list of payloads, each naming a symbol, a source file, a language, a stage, a format and an entry point. Text variants are embedded verbatim. Vulkan GLSL sources are compiled to SPIR-V through the system libshaderc.so.1 (--shaderc-library or CNA_SHADERC_LIBRARY), and WGSL payloads are produced from that SPIR-V with naga (--naga or CNA_NAGA) after a mechanical rewrite of declarations. The generated header records the SHA-256 of the manifest, every source and the compiler, together with the fixed options, so the same inputs and toolchain reproduce it byte for byte; a different toolchain hash is an explicit toolchain change to regenerate, inspect and commit together with the payload diff. The HLSL header comes from a separate script, generate_post_process_hlsl.py, which translates the Vulkan GLSL sources through glslang and SPIRV-Cross (and compiles the result with fxc when one is given) and records each fragment's source SHA-256. The tools are offline authoring tools: no CNA runtime target loads or links them, and an ordinary build needs none of them.
Never edit either header by hand. To change a shader, edit the source and its sibling variants, then regenerate with generate_shader_package.py <package.json> --output <header> and rerun the HLSL script. The write-free form, --check, exits 77 when the toolchain is absent. cmake/Tests/ModuleProbes.cmake registers PostProcessShaderPackageReproducibility, which runs that check with SKIP_RETURN_CODE 77; it exists only with CNA_BUILD_TESTS on a native (non-Emscripten, non-Android) build with Python found. The HLSL script has no --check mode and no ctest, and no CI workflow runs either generator, so reproducibility is a developer-machine gate.
How a package is selected
ShaderPackageEXT::selectFor walks languages in a fixed preference order — SpirV, Dxil, GlslDesktop, GlslEs, GlslVulkan, Hlsl, Msl, Wgsl — and takes the first language for which every required stage has exactly one payload that the live renderer accepts through GraphicsDevice::SupportsShaderLanguageEXT(language, stage). Declaration order never matters, and two payloads for the same language and stage make that language ambiguous, so it is never chosen. Graphics stages also require the CustomEffects capability, a compute stage requires ComputeShaders, and the package's declared binding requirements are checked (3D sampling, texture arrays, storage textures, constant buffers and storage buffers have conditions). A rejected language is listed in the diagnostic with its reasons, so selectFor(device).isUsable() is the question a caller asks before relying on a package. Renderer answers live in each renderer: EasyGL accepts exactly one of desktop GLSL (its desktop core profile) or GLSL ES (the ES and WebGL profiles); Vulkan and SDL_GPU accept SPIR-V; WebGPU accepts WGSL; DirectX11 accepts HLSL; the SDL_GPU, WebGPU and Direct3D answers also require a live device. For the post_process package that means GlslDesktop on OPENGL33, GlslEs on OPENGLES3 and WEBGL2, SpirV on VULKAN and SDL_GPU, Wgsl on WEBGPU and Hlsl on DIRECTX11; on every other renderer the CRT and colour-depth effects take the GLSL ES fallback described above. The guide's renderer table is which renderers can run it, and the renderer pages describe the implementations (EasyGL, Vulkan, SDL_gpu).
A second predicate matters to host code: CNA::UsesDescriptorBindingContractEXT(language) in ShaderLanguageEXT.hpp is true for SpirV and Wgsl, whose payloads share one binding layout generated from the same Vulkan GLSL. Code choosing between that route and the GLSL route must ask this predicate, not name one language.
The post_process package at this snapshot
Each of the two packages that PostProcessShaderPackages.cpp builds requires exactly the vertex and fragment stages and declares the sampled 2D texture texture1 at binding 0 for the fragment stage; the colour-depth package adds uPalette at binding 1. Every package carries a vertex and a fragment payload in GLSL ES, desktop GLSL, SPIR-V and WGSL, plus the HLSL pair when the HLSL header has the fragment (it has both the CRT and the colour-depth fragment at this snapshot). The SPIR-V and WGSL fragments are generated from the same *.vulkan.frag.glsl source, and the HLSL fragment is translated from it too, so a change to that one source touches three variants. DepthEffectTest.SelectsTheBestShaderPackageVariant pins selection for the colour-depth effect, and ShaderPackageOverloadTest.DerivedEffectCanRetainLegacyFallbackWhenNoVariantExists pins the fallback path. Which variant a pixel test exercises depends on the renderer the test binary is built for; several CRT and colour-depth pixel tests are named after EasyGL's screen or row orientation. A passing focused suite on one renderer says nothing about the other variants.
Tests and examples
The module's test tree has six test files with 76 line-anchored test macros: ShaderInfrastructureTests (24: ShaderCodeEXT ownership and payload forms, package validation, selection preference, ambiguity and binding checks, and the ShaderEffect overloads including a native HLSL package and the legacy fallback), CRTEffectTests, DepthEffectTests and DebugDrawTests (15 each), AsciiPostProcessEffectTests (6) and CnaExtMasterIncludeTests (1). They build into the focused CnaGraphicsExtTests target (cmake/UnitTests.cmake). RetroGraphicsTestSupport.hpp provides HiDefDevice, a standalone device at the HiDef profile for the renderer the build was configured with, and the CNA_SKIP_WITHOUT_RENDER_TARGETS and CNA_SKIP_WITHOUT_RENDER_TARGET_READBACK gates, which turn an unsupported renderer into a skip, not a failure. The DebugDraw geometry tests read getVertices() and do not draw. examples/CMakeLists.txt adds two manual screenshot demos, cna_depth_effect_demo and cna_crt_effect_demo (with CNA_BUILD_EXAMPLES and CNA_CNAEXT, for OPENGLES3, OPENGL33 or VULKAN, not on Emscripten or Android), and four ASCII programs registered as renderer tests with CNA_BUILD_TESTS and CNA_CNAEXT: Ascii_Quantizer (a pure function, on every renderer) and Ascii_FontAtlas, AsciiPostProcessEffect_Pixel and AsciiPostProcessEffect_3DSource (not on Emscripten or Windows; the last exits 77 on a renderer without ThreeD). CNA's module scope document records 78/78 retained-effect and graphics-profile tests, 4/4 ASCII examples and 17/17 focused C API tests with CNA_CNAEXT=ON on OPENGLES3 (2026-09-27); those figures are CNA's record, and nothing was run for this page.
Source, C route and test map
The grouping below is by file name and is this page's own. Test counts are line-anchored TEST, TEST_F, TEST_P and TYPED_TEST macros over the module's six test files (76 in all), located and not executed. The C routes are declared in graphics_ext.h (44 declarations) and implemented in CnaCApiGraphicsExt.cpp and CnaCApiDebugDraw.cpp.
| Family | Main sources under src/ | C routes | Tests (files, macros) |
|---|---|---|---|
| ASCII | AsciiPostProcessEffect, AsciiFontAtlas, AsciiQuantizer | 8 cna_ascii_post_process_effect_* | 1 file, 6; plus the four ASCII example programs |
| CRT | CRTEffect | 11 cna_crt_effect_* | 1 file, 15 |
| Colour depth | DepthEffect | 5 cna_depth_effect_* | 1 file, 15 |
| Debug drawing | DebugDraw | 15 cna_debug_draw_* | 1 file, 15 |
Shader authoring and the ShaderEffect overloads | ShaderCodeEXT, ShaderPackageEXT, PostProcessShaderPackages | none | 1 file, 24 |
| Master include and availability | CNAEXT.hpp | cna_graphics_ext_is_available | 1 file, 1 |
With CNA_CNAEXT off the declarations stay in the C ABI: cna_graphics_ext_is_available reports false and the effect routes return CNA_RESULT_NOT_SUPPORTED (docs/c-api/GRAPHICS_EXT.md). The same header also declares four routes for core value types, cna_image_based_light_ext_init, cna_image_based_light_ext_is_valid, cna_indirect_draw_arguments_init and cna_indirect_draw_indexed_arguments_init; they belong to core PBR and renderer capabilities, not to this module.
Several CNA-only graphics types that are sometimes associated with these extensions are not in this module and are compiled in every build: PbrEffect and SkinnedPbrEffect, the receiver types IShadowReceiverEXT, ShadowCascadeStateEXT, PunctualLightEXT and ImageBasedLightEXT, morph targets (MorphTargetEXT) and glTF runtime loading all live in core graphics and content. CNA generates no shadow maps or image-based-lighting textures: an application renders or supplies its own (for example a depth pass into a RenderTarget2D) and hands them to those receivers. GraphicsDevice also declares DrawPrimitivesIndirectEXT and DrawIndexedPrimitivesIndirectEXT, but they take a renderer-internal argument-buffer type that no public class produces (GraphicsDevice.hpp), so they are not usable from game code at this snapshot. The guide's contents, test evidence and limits sections give the user-level view, and Effects: beyond XNA and Shader effects: typed shader code and packages cover the effect and package APIs.
Human route for one extension change
To change the CRT or colour-depth effect, start with the effect's OnApply and the uniform it writes, then the shader source. Edit the source for every variant the package carries (GLSL ES, desktop GLSL, and the Vulkan GLSL behind SPIR-V, WGSL and HLSL), run generate_shader_package.py with --output and then generate_post_process_hlsl.py, inspect the payload diff, and commit sources, manifest and both headers together; PostProcessShaderPackageReproducibility is the check that the main header still derives from its sources. A changed or added parameter also needs its C route in CnaCApiGraphicsExt.cpp and graphics_ext.h, which is an ABI change (see update the C API). Then run the effect's own suite on each renderer family that takes a different variant, because selection means one renderer exercises one language. For the ASCII effect, start with AsciiQuantizer, a pure function with its own oracle (Ascii_Quantizer), before the drawing code. For DebugDraw, the geometry tests do not draw, so a change to end() needs a draw-level test, including one for the exception edge above. For a build-flag change, remember the guard discipline: an unguarded file builds in both configurations and only surfaces in an XNA-only consumer, and the guard script is not a registered test.
Source-observed gaps (not measured). No object in the module reacts to a device reset; DebugDraw::end() restores the depth-stencil state only when every draw succeeds; the HLSL header has no reproducibility check; the guard script is not registered as a ctest; and no CI workflow builds the module with CNA_CNAEXT=ON. None is a reported defect; each is a place to add a test before changing the code around it.
Curated source route and open coverage
graphics-ext/CMakeLists.txtandmodules/CMakeLists.txt: establish the macro, the physical target, theCNA::CnaExtumbrella and theCNAaggregate.CNAEXT.hppand the ten headers it includes: the whole public surface.CRTEffect.cpp,DepthEffect.cppandPostProcessShaderPackages.cpp, thenShaderPackageEXT.cpp: the package factory, the fallback,selectForand theShaderEffectpackage constructors.AsciiPostProcessEffect.cppandAsciiQuantizer.cpp: readback, quantisation and the per-cell quads; thenDebugDraw.cpp.- The post_process
package.json,the package generatorandthe HLSL generator: the package mechanism end to end. ShaderInfrastructureTests.cpp,CRTEffectTests.cppandDepthEffectTests.cpp: the selection, fallback and pixel oracles to extend before changing an effect.docs/cnaext-engine-layer.md(CNA's one-table summary of the module's public classes) anddocs/c-api/GRAPHICS_EXT.md(the C routes). Thecnaextpreset's own display name and description inCMakePresets.jsonlag the code, so verify every statement in source.
Open coverage: the pixel output of each effect on each renderer, the HLSL route on the Direct3D renderers, the ASCII effect on renderers that cannot read a texture back, and the behaviour of the C routes have their own validation routes that this page does not reconstruct. Related pages: GraphicsDevice internals, textures and render targets, renderer selection, graphics backends; the change routes are in the Maintainer Handbook and I need to fix a renderer bug, ownership across the tree is in the ownership map, and the module list is in the module index. Evidence level: everything above was checked by reading the TARGET sources at c1c316b9; nothing was built or executed, and the CNA-reported test figures are attributed as such.
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 c1c316b9, namespace by namespace, with member names, reasons and boundaries, how much the CNAEXT marker covers, and what the strict check proves.
- Four shader routes: stock semantics, D3D9 stock sources, compiled effects and ShaderEffect — How CNA answers XNA's .fx: renderer-owned stock effects, DIRECTX9's recompiled Microsoft sources, compiled Effect Framework bytecode on qualified renderers, and the renderer-specific ShaderEffect contract.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- 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) 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 regeneratio
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- User guide
- CNAEXT extensions: building it and the gate · CNAEXT extensions: which renderers can run it · CNAEXT extensions: test evidence · Effects: beyond XNA · Shader effects: typed shader code and packages · Tutorial 116: post-process effects
- Architecture
- Graphics architecture · Architecture: module layout
- Internals
- Renderer selection internals · GraphicsDevice internals · Textures and render targets · EasyGL renderer internals · Vulkan renderer internals · SDL_gpu renderer internals
- Maintainer workflow
- I need to fix a renderer bug · Update the C API · Ownership and lifetime master map · Thread and callback map · Maintainer Handbook
- Tests and validation
- Test architecture and change recipes · What to test after changing X
- Reference
- Module index · Test target index · CMake option index