Tutorial 126: Build Several Renderers and Select One at Runtime
What you’ll learn: when to keep the smaller single-renderer default, how CNA_GRAPHICS_RENDERERS changes the build, and how selection and fallback behave before the first graphics device.
Start with the architectural choice
A normal alpha.1 build compiles exactly one renderer selected by CNA_GRAPHICS_RENDERER. That remains the recommended path for a fixed deployment: fewer dependencies, a smaller binary and no runtime decision.
Multi-renderer mode is opt-in. It compiles a compatible list of identities, keeps one member as the default, and publishes the complete list to CNA::GraphicsRendererSelection.
Configure a multi-renderer build
cmake -S ../cna -B build-multi \
-DCNA_GRAPHICS_RENDERER=OPENGLES3 \
-DCNA_GRAPHICS_RENDERERS="OPENGLES3;VULKAN;HEADLESS" \
-DCNA_BUILD_TESTS=ON
cmake --build build-multi --parallel 3
CNA_GRAPHICS_RENDERER names the default and must be a member of the plural list. The default is normalized to the first registry entry; legacy CNA_RENDERER_* booleans still describe the default identity, not every compiled family.
Every selected renderer brings its build dependencies and code size. CMake rejects demonstrated conflicts: PortableGL with a real-GL family, GDI with Software, Glide with anything else, and combinations that span mutually exclusive OS partitions.
Select before the device exists
#include "CNA/GraphicsRendererSelection.hpp"
#include "CNA/GraphicsRendererType.hpp"
using CNA::GraphicsRendererSelection;
using CNA::GraphicsRendererType;
int main()
{
for (auto renderer : GraphicsRendererSelection::GetAvailable())
{
// Populate a settings menu or diagnostic here.
(void)renderer;
}
GraphicsRendererSelection::SetPreferred(GraphicsRendererType::Vulkan);
MyGame game;
game.Run(); // first GraphicsDevice construction latches the choice
}
The resolution order is explicit SetPreferred(), then the CNA_GRAPHICS_RENDERER environment variable, then the compiled default. Names passed to the string overload or environment variable use the public CMake spelling case-insensitively, such as VULKAN.
GetAvailable()reports what was compiled into this binary.GetSelected()reports what CNA will try first and does not latch the decision.GetActive()reports what was actually created and throws before creation.IsLatched()becomes true when the first graphics device begins construction.
An unknown public name is an argument error. A known but uncompiled renderer is a hard error while fallback remains disabled. Calling SetPreferred() after latching is also an error.
Make fallback an explicit policy
const GraphicsRendererType chain[] = {
GraphicsRendererType::OpenGLES3,
GraphicsRendererType::Software
};
GraphicsRendererSelection::SetPreferred(GraphicsRendererType::Vulkan);
GraphicsRendererSelection::SetFallbackChain(chain);
Fallback is off by default so a request for Vulkan cannot silently become CPU rendering. A custom chain may include identities absent from a particular build; they are skipped and recorded. EnableAutomaticFallback(true) instead derives an order from CNA's maturity/category data. After creation, inspect GetFallbackHistory() and GetActive() for the honest result.
Know the window boundary
Selection happens before device creation. An externally owned window cannot necessarily be recreated across incompatible GL/Vulkan window kinds; CNA records that as a WindowKindConflict fallback failure. Build-time compatibility therefore does not promise that every renderer can replace every other renderer inside a pre-existing native window.