Tutorial 85: The Vulkan Renderer: Setup and Quirks

CNA — C++ XNA 4.0 reimplementation

What you’ll learn

  • Enabling the VULKAN renderer in CMake and what the Vulkan SDK is needed for.
  • Turning on validation layers while developing.
  • Where Vulkan differs from the EasyGL GL profiles: SPIR-V shaders and explicit synchronisation.
  • Its real limits, and its state on macOS and Android.

Before you startTutorial 72: Choosing a Renderer — read the group comparison before committing to one renderer. Tutorial 52: Writing Custom Shaders (ShaderEffect) explains the shader source this renderer consumes differently from the GL family.

Why Vulkan?

VULKAN is one of CNA's 50 renderer identities and one of the 4 modern-GPU-API renderers. It gives you explicit GPU control, lower driver overhead and more multi-threading headroom than the GL family, and it is one of the renderers on which a custom ShaderEffect genuinely executes.

CNA publishes no maturity ranking across its renderers, and there is no machine-readable registry to derive one from — so choose Vulkan because you want its execution model, not because of a league position. The one place a renderer choice is objectively settled is XNA-authentic output, and there the answer is DIRECTX9, not Vulkan.

Required: Vulkan SDK

# Ubuntu/Debian
sudo apt install libvulkan-dev vulkan-validationlayers spirv-tools glslc

# Arch Linux
sudo pacman -S vulkan-headers vulkan-validation-layers shaderc

# Windows: Download LunarG Vulkan SDK from https://vulkan.lunarg.com
# and set VULKAN_SDK environment variable

A Linux CNA build also needs the ../sharp-runtime sibling checkout and the FFmpeg development packages (libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev) — FFmpeg is a hard requirement on Linux and macOS, and configure fails without it, whichever renderer you pick.

Enabling the Vulkan renderer in CMake

cmake -S . -B build-vulkan \
  -DCNA_GRAPHICS_RENDERER=VULKAN \
  -DCMAKE_BUILD_TYPE=Release
cmake --build build-vulkan

# The per-renderer option form is equivalent — use one or the other, never both:
#   -DCNA_RENDERER_VULKAN=ON

Do not pass --target CNA: CNA is an interface library with no sources and is not a buildable target. Build CnaTests, a demo target, or just the whole build directory.

Validation layers for debugging

Always enable validation layers during development. They catch API misuse, resource leaks, and synchronization errors.

# Enable validation layers at runtime via environment variable:
export VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation
./build-vulkan/MyGame

Differences from the EasyGL profiles (SPIR-V shaders, explicit sync)

The five GL profile identities (OPENGLES2, OPENGLES3, OPENGL33, WEBGL1, WEBGL2) share the EasyGL implementation and consume GLSL source directly. Vulkan consumes SPIR-V. Pipeline state — blend, rasterizer, depth — is baked into pipeline objects rather than set dynamically, which makes state changes comparatively more expensive and draw submission comparatively cheaper. CNA's Effect API hides the difference for the stock effects: the same Apply() call works on every renderer that implements them.

Where it does not hide the difference is ShaderEffect, CNA's hand-written-shader extension. The source you hand it must be in the language the selected renderer expects, so a Vulkan build wants SPIR-V where an OPENGLES3 build wants GLSL. See Tutorial 52.

Limits worth knowing

  • Compute shaders are not exposed through the XNA API surface. XNA 4.0 has no compute concept, and CNA does not add one here.
  • Compiled effects are opt-in-DCNA_VULKAN_COMPILED_EFFECTS=ON enables XNA/FNA D3D9 Effect Framework bytecode through Effect and EffectReader; the option defaults to OFF. HLSL .fx source, DXBC and MGFX are not accepted. Renderer-native ShaderEffect remains available separately.
  • macOS is served by METAL, not by Vulkan. METAL is the macOS-gated renderer and the only one with an automatically triggered macOS CI job. CNA does not verify a MoltenVK path.
  • No dedicated Vulkan renderer lane. Alpha.1 has Vulkan-focused source and test coverage, but no workflow that gates its complete renderer pixel suite on every push.

Vulkan on Android

Android supports Vulkan from API level 24 (Android 7.0). Add uses-feature android:name="android.hardware.vulkan.level" android:version="0" to AndroidManifest.xml. CNA's Android support is code paths and NDK backends with no automatic CI at all, and the Vulkan renderer is not validated there — OPENGLES3 is the better-trodden Android path today. See Tutorial 82.

Consuming CNA from your own CMake project

The C++ framework has no general install/export package, so C++ consumers add the source tree as a subdirectory. The experimental C layer's declared CNACApi package is separate and is not consumable at alpha.1 because its final implementation is compile-blocked.

# CMakeLists.txt: build your game against the Vulkan renderer
cmake_minimum_required(VERSION 3.20)
project(MyGame CXX)
set(CMAKE_CXX_STANDARD 23)

set(CNA_GRAPHICS_RENDERER "VULKAN" CACHE STRING "CNA renderer")
add_subdirectory(../cna ${CMAKE_BINARY_DIR}/cna)

add_executable(MyGame main.cpp)
target_link_libraries(MyGame PRIVATE CNA)

# You do not define the renderer macro yourself — CNA's own configure emits
# CNA_RENDERER_VULKAN for the whole build when VULKAN is selected.

Compiling GLSL to SPIR-V with glslc is an ordinary offline step:

glslc shaders/basic.vert -o shaders/basic.vert.spv
glslc shaders/basic.frag -o shaders/basic.frag.spv

Looking for a native Windows GPU API instead of cross-platform Vulkan? CNA ships native DIRECTX9, DIRECTX11 and DIRECTX12 renderers. The tag includes manual Wine/DXVK and vkd3d-proton paths, while its native-Windows workflows are manual-dispatch; see Tutorial 72. For GPU-free CI runs alongside Vulkan development, that same tutorial covers HEADLESS (logic only) and SOFTWARE (a real CPU rasteriser you read back rather than present).