This guide covers SnapRHI's built-in validation system, debug build configuration, sanitizers, graphics API debugging, and frame capture tools.
Related: Build Guide | Profiling Guide | Resource Management | API Reference
- Quick Start: Enable All Validation
- SnapRHI Validation System
- Debug Build Configuration
- Debug Labels & Markers
- Sanitizers
- Graphics API Validation
- Frame Capture Tools
- Creating Minimal Reproductions
The fastest way to enable comprehensive debugging is the master validation switch. This turns on every validation tag, Vulkan layers, slow safety checks, full logging, and report-level all in a single flag:
# Via build.sh
./build.sh --vulkan --with-demo --validation
# Via CMake presets (recommended)
cmake --preset macos-metal-demo-validation
cmake --build build/macos-metal-demo-validation
# Via raw CMake
cmake -B build -DSNAP_RHI_ENABLE_METAL=ON -DSNAP_RHI_ENABLE_ALL_VALIDATION=ONWhen SNAP_RHI_ENABLE_ALL_VALIDATION=ON is set, the following options are force-enabled:
| Category | What gets enabled |
|---|---|
| Validation layers | SNAP_RHI_VULKAN_ENABLE_LAYERS=ON |
| Slow safety checks | SNAP_RHI_ENABLE_SLOW_SAFETY_CHECKS=ON |
| Debug labels | SNAP_RHI_ENABLE_DEBUG_LABELS=ON |
| Logging | SNAP_RHI_ENABLE_LOGS=ON |
| Report level | SNAP_RHI_REPORT_LEVEL=all |
| All 30 validation tags | Every SNAP_RHI_VALIDATION_* option set to ON |
⚠️ Performance: Full validation has significant CPU overhead. Use it for development and debugging, not profiling or benchmarking.
SnapRHI has a two-layer validation system:
- Compile-time validation tags — CMake options that compile validation checks into the library
- Runtime validation config —
DeviceCreateInfofields that control what gets reported at runtime
These options control what debug code is compiled into the library. Defined in cmake/SnapRHIOptions.cmake:
| CMake Option | Default | Description |
|---|---|---|
SNAP_RHI_ENABLE_ALL_VALIDATION |
OFF |
Master switch — enables everything below |
SNAP_RHI_ENABLE_DEBUG_LABELS |
ON |
Debug labels for GPU resources (setDebugLabel(), encoder debug groups) |
SNAP_RHI_ENABLE_LOGS |
ON |
Logging output |
SNAP_RHI_REPORT_LEVEL |
warning |
Minimum severity: all, debug, info, warning, performance_warning, error, critical_error |
SNAP_RHI_ENABLE_SLOW_SAFETY_CHECKS |
OFF |
Additional runtime safety checks (impacts performance) |
SNAP_RHI_ENABLE_API_DUMP |
OFF |
Dumps the exact sequence of API calls for reproduction |
SNAP_RHI_ENABLE_SOURCE_LOCATION |
OFF |
Source location tracking in API calls |
SNAP_RHI_THROW_ON_LIFETIME_VIOLATION |
OFF |
Throw exceptions on object lifetime violations (instead of logging) |
SNAP_RHI_ENABLE_CUSTOM_PROFILING_LABELS |
OFF |
Custom profiling callbacks (ProfilingCreateInfo) |
| CMake Option | Default | Description |
|---|---|---|
SNAP_RHI_VULKAN_ENABLE_LAYERS |
OFF |
Enable Vulkan validation layers (VK_LAYER_KHRONOS_validation) |
Validation tags enable checks for specific resource types or operations. Each tag compiles to a SNAP_RHI_ENABLE_VALIDATION_TAG_* preprocessor definition.
Resource creation & destruction:
| Tag | Validates |
|---|---|
SNAP_RHI_VALIDATION_CREATE_OP |
All resource creation parameters |
SNAP_RHI_VALIDATION_DESTROY_OP |
Resource destruction ordering and cleanup |
SNAP_RHI_VALIDATION_DEVICE_OP |
Device operations |
Command recording:
| Tag | Validates |
|---|---|
SNAP_RHI_VALIDATION_COMMAND_BUFFER_OP |
Command buffer state transitions and lifecycle |
SNAP_RHI_VALIDATION_COMMAND_QUEUE_OP |
Queue submission parameters |
SNAP_RHI_VALIDATION_RENDER_COMMAND_ENCODER_OP |
Render encoder calls (pipeline bound, valid state, etc.) |
SNAP_RHI_VALIDATION_COMPUTE_COMMAND_ENCODER_OP |
Compute encoder calls |
SNAP_RHI_VALIDATION_BLIT_COMMAND_ENCODER_OP |
Blit encoder calls |
Pipeline & shader:
| Tag | Validates |
|---|---|
SNAP_RHI_VALIDATION_RENDER_PIPELINE_OP |
Render pipeline creation parameters |
SNAP_RHI_VALIDATION_COMPUTE_PIPELINE_OP |
Compute pipeline creation parameters |
SNAP_RHI_VALIDATION_SHADER_MODULE_OP |
Shader module creation |
SNAP_RHI_VALIDATION_SHADER_LIBRARY_OP |
Shader library compilation |
SNAP_RHI_VALIDATION_PIPELINE_CACHE_OP |
Pipeline cache operations |
Resources:
| Tag | Validates |
|---|---|
SNAP_RHI_VALIDATION_BUFFER_OP |
Buffer creation, mapping, alignment |
SNAP_RHI_VALIDATION_TEXTURE_OP |
Texture creation, format support |
SNAP_RHI_VALIDATION_SAMPLER_OP |
Sampler parameters |
SNAP_RHI_VALIDATION_RENDER_PASS_OP |
Render pass attachment compatibility |
SNAP_RHI_VALIDATION_FRAMEBUFFER_OP |
Framebuffer attachment validation |
Descriptors:
| Tag | Validates |
|---|---|
SNAP_RHI_VALIDATION_DESCRIPTOR_SET_LAYOUT_OP |
Layout binding configuration |
SNAP_RHI_VALIDATION_PIPELINE_LAYOUT_OP |
Pipeline layout set slot completeness |
SNAP_RHI_VALIDATION_DESCRIPTOR_POOL_OP |
Pool allocation tracking |
SNAP_RHI_VALIDATION_DESCRIPTOR_SET_OP |
Descriptor set bindings |
Synchronization:
| Tag | Validates |
|---|---|
SNAP_RHI_VALIDATION_FENCE_OP |
Fence lifecycle (reset-before-reuse, etc.) |
SNAP_RHI_VALIDATION_SEMAPHORE_OP |
Semaphore signal/wait pairing |
SNAP_RHI_VALIDATION_DEVICE_CONTEXT_OP |
Context binding (OpenGL) |
SNAP_RHI_VALIDATION_QUERY_POOL_OP |
Query pool operations |
OpenGL-specific:
| Tag | Validates |
|---|---|
SNAP_RHI_VALIDATION_GL_COMMAND_QUEUE_EXTERNAL_ERROR |
glGetError() after each command |
SNAP_RHI_VALIDATION_GL_COMMAND_QUEUE_INTERNAL_ERROR |
Internal GL command queue errors |
SNAP_RHI_VALIDATION_GL_STATE_CACHE_OP |
GL state cache consistency |
SNAP_RHI_VALIDATION_GL_PROGRAM_VALIDATION_OP |
GL program linking/validation |
SNAP_RHI_VALIDATION_GL_PROFILE_OP |
GL profile capabilities |
For targeted debugging without full validation overhead:
# Only validate render pipeline creation and command encoder usage
cmake -B build \
-DSNAP_RHI_ENABLE_METAL=ON \
-DSNAP_RHI_VALIDATION_RENDER_PIPELINE_OP=ON \
-DSNAP_RHI_VALIDATION_RENDER_COMMAND_ENCODER_OP=ON \
-DSNAP_RHI_REPORT_LEVEL=debugAt device creation time, you control validation behavior via DeviceCreateInfo:
snap::rhi::DeviceCreateInfo info{};
// Enable debug callbacks (required for DebugMessenger)
info.deviceCreateFlags = snap::rhi::DeviceCreateFlags::EnableDebugCallback;
// Select which validation categories are active at runtime
info.enabledTags = snap::rhi::ValidationTag::CreateOp
| snap::rhi::ValidationTag::RenderPipelineOp
| snap::rhi::ValidationTag::BufferOp;
// Set minimum report severity
info.enabledReportLevel = snap::rhi::ReportLevel::Warning;Note: Runtime tags are ANDed with compile-time tags. A validation check runs only if both the CMake option and the
DeviceCreateInfotag are enabled.
Exception:
ReportLevel::CriticalErrorreports bypass all filtering — they are compiled into every configuration and always throw, regardless of the enabled tags and report level. Backends use this level for unrecoverable failures such as the driver refusing to create an object.
Register a callback to receive validation messages at runtime:
auto messenger = device->createDebugMessenger({
.debugMessengerCallback = [](const snap::rhi::DebugCallbackInfo& info) {
std::cerr << "[SnapRHI] " << info.message << std::endl;
}
});Requirements:
- Device must be created with
DeviceCreateFlags::EnableDebugCallback - Callbacks may be invoked from any thread — the implementation must be thread-safe
- OpenGL: single global callback is fanned out to all
DebugMessengerinstances
| Type | Use Case |
|---|---|
Debug |
Full debug symbols, no optimization (-O0 -g) |
RelWithDebInfo |
Optimized with debug symbols — recommended for GPU debugging |
Release |
Full optimization, no debug symbols |
SnapRHI provides pre-configured presets in CMakePresets.json for each platform and backend:
Standard presets:
| Preset | Platform | Backend | Notes |
|---|---|---|---|
macos-metal |
macOS | Metal | Debug build |
macos-metal-release |
macOS | Metal | Optimized, no debug labels/logs |
macos-opengl |
macOS | OpenGL | Debug build |
macos-vulkan |
macOS | Vulkan/MoltenVK | Debug build |
linux-vulkan |
Linux | Vulkan | Debug build |
linux-opengl |
Linux | OpenGL | Debug build |
windows-vulkan |
Windows | Vulkan | Debug build |
windows-opengl |
Windows | OpenGL | Debug build |
Demo app presets (append -demo): Includes the triangle demo application.
Full validation presets (append -demo-validation): Demo app + SNAP_RHI_ENABLE_ALL_VALIDATION=ON:
# macOS Metal with full validation
cmake --preset macos-metal-demo-validation
cmake --build build/macos-metal-demo-validation
# Linux Vulkan with full validation
cmake --preset linux-vulkan-demo-validation
cmake --build build/linux-vulkan-demo-validationThe build.sh script wraps CMake for macOS and Linux:
./build.sh --help
# Backend selection
./build.sh --metal # Metal (macOS default)
./build.sh --vulkan # Vulkan
./build.sh --opengl # OpenGL
# Build options
./build.sh --with-demo # Include demo application
./build.sh --release # Release build
./build.sh --clean # Clean rebuild
./build.sh --xcode --with-demo # Generate Xcode project
# Validation
./build.sh --validation # Enable ALL validation features
./build.sh --validation-layers # Enable Vulkan validation layers only
# Combined
./build.sh --vulkan --with-demo --validation # Full validation Vulkan demo| Platform | Method |
|---|---|
| macOS/iOS | -g flag (preserved by Xcode in Debug/RelWithDebInfo) |
| Windows | /Zi flag for PDB generation |
| Linux | -g flag with GCC/Clang |
When SNAP_RHI_ENABLE_DEBUG_LABELS=ON (default), all DeviceChild objects support human-readable labels:
auto buffer = device->createBuffer(info);
buffer->setDebugLabel("SceneUBO");
auto texture = device->createTexture(info);
texture->setDebugLabel("ShadowMap_Cascade0");
auto pipeline = device->createRenderPipeline(info);
pipeline->setDebugLabel("PBR_Forward_Pass");Labels are forwarded to native APIs for GPU debugger visibility:
- Metal: Sets
MTLResource.label - OpenGL: Uses
GL_KHR_debugobject labels - Vulkan: Uses
VK_EXT_debug_utilsobject naming
When SNAP_RHI_ENABLE_DEBUG_LABELS=OFF, setDebugLabel() compiles to a no-op with zero overhead.
Command encoders support hierarchical debug groups for GPU profiler nesting:
auto encoder = commandBuffer->getRenderCommandEncoder();
encoder->beginEncoding(renderPassInfo);
encoder->beginDebugGroup("Shadow Pass");
// ... shadow render commands ...
encoder->endDebugGroup();
encoder->beginDebugGroup("Lighting Pass");
encoder->beginDebugGroup("Directional Lights");
// ... render commands ...
encoder->endDebugGroup();
encoder->beginDebugGroup("Point Lights");
// ... render commands ...
encoder->endDebugGroup();
encoder->endDebugGroup();
encoder->endEncoding();Backend mapping:
- Metal:
-[MTLCommandEncoder pushDebugGroup:]/popDebugGroup - OpenGL: Recorded into command stream via KHR_debug
- Vulkan:
vkCmdBeginDebugUtilsLabelEXT/vkCmdEndDebugUtilsLabelEXTviaVK_EXT_debug_utils
Rule: Every
beginDebugGroup()must have a matchingendDebugGroup().
Sanitizers detect memory and threading bugs at runtime. Use with debug or minimally-optimized builds.
# AddressSanitizer + UBSan
target_compile_options(<target> PRIVATE
-fsanitize=address,undefined
-fno-omit-frame-pointer)
target_link_options(<target> PRIVATE
-fsanitize=address,undefined)Tip: Use
-O1or-O0for best diagnostic quality.
| Platform | Configuration |
|---|---|
| macOS/iOS (Xcode) | Scheme → Diagnostics → Enable Address/Thread/UB Sanitizer |
| Android (NDK r18+) | Add -fsanitize=address to CMake flags; requires ASan runtime on device |
| Windows | Use clang-cl with -fsanitize= flags, or VS AddressSanitizer for Clang toolchains |
| Sanitizer | Detects | Compatibility |
|---|---|---|
| AddressSanitizer (ASan) | Buffer overflows, use-after-free, memory leaks | ❌ Cannot combine with TSan |
| ThreadSanitizer (TSan) | Data races, deadlocks | ❌ Cannot combine with ASan |
| UndefinedBehaviorSanitizer (UBSan) | Undefined behavior (null deref, integer overflow) | ✅ Combinable with ASan |
Enable in Xcode: Scheme → Diagnostics → Metal API Validation
Additional options:
- Metal Shader Validation (Xcode 15+)
- Metal GPU-Side Validation
- GPU Frame Capture
Capabilities:
- API usage validation
- Shader validation (buffer bounds, null resources)
- Resource tracking and leak detection
Enable via CMake:
cmake -B build -DSNAP_RHI_ENABLE_VULKAN=ON -DSNAP_RHI_VULKAN_ENABLE_LAYERS=ONOr via build.sh:
./build.sh --vulkan --validation-layersWhen SNAP_RHI_VULKAN_ENABLE_LAYERS=ON, the backend requests VK_LAYER_KHRONOS_validation at device creation.
Vulkan Configurator (vkconfig) can be used to fine-tune layer settings without recompiling.
SnapRHI integration: Set DeviceCreateFlags::EnableDebugCallback on DeviceCreateInfo to activate KHR_debug callbacks. Then register a DebugMessenger to receive messages.
OpenGL-specific validation tags for deep driver debugging:
cmake -B build \
-DSNAP_RHI_ENABLE_OPENGL=ON \
-DSNAP_RHI_VALIDATION_GL_COMMAND_QUEUE_EXTERNAL_ERROR=ON \
-DSNAP_RHI_VALIDATION_GL_STATE_CACHE_OP=ON \
-DSNAP_RHI_VALIDATION_GL_PROGRAM_VALIDATION_OP=ONAPI dump:
cmake -B build -DSNAP_RHI_ENABLE_OPENGL=ON -DSNAP_RHI_ENABLE_API_DUMP=ONThis captures the exact sequence of SnapRHI API calls for reproduction.
| Platform | Recommended Tool | API Support |
|---|---|---|
| macOS/iOS | Xcode GPU Frame Capture | Metal |
| All | RenderDoc | Vulkan, OpenGL |
| Android | Android GPU Inspector | Vulkan, OpenGL ES |
| Windows | PIX, RenderDoc | D3D12, Vulkan, OpenGL |
| NVIDIA | Nsight Graphics | Vulkan, OpenGL |
- Isolate the issue — Reduce to minimal scene/frame
- Capture a frame — Use tool's capture trigger
- Inspect draw calls — Review state, resources, timings
- Check pixel history — For rendering artifacts
- Run app from Xcode
- Click camera icon in Debug navigator
- Inspect command buffers, encoders, resources
- Use Metal System Trace for timing
Tip:
SNAP_RHI_ENABLE_DEBUG_LABELS=ONensures all resources appear with readable names in the Xcode GPU debugger.
- Launch application through RenderDoc
- Press capture hotkey (F12 default)
- Open capture for analysis
- Inspect API calls, pipeline state, textures
Tip: Encoder debug groups (
beginDebugGroup/endDebugGroup) appear as collapsible sections in RenderDoc.
- Exact git commit/tag
- Platform, OS version, GPU/driver versions
- CMake preset or build flags used
- Backend (Metal / Vulkan / OpenGL)
- Minimal reproduction steps
- SnapRHI validation output (build with
--validation) - Sanitizer output (if applicable)
- Frame capture or API dump
1. Enable SNAP_RHI_ENABLE_ALL_VALIDATION
2. Enable SNAP_RHI_ENABLE_API_DUMP for call trace
3. Reduce scene complexity → single object
4. Reduce to single frame → disable animation
5. Isolate API calls → use validation output
6. Create standalone test case from triangle-demo-app
Enable comprehensive API logging for exact call sequence capture:
cmake -B build -DSNAP_RHI_ENABLE_OPENGL=ON -DSNAP_RHI_ENABLE_API_DUMP=ONFor tracking resource lifetime issues:
cmake -B build \
-DSNAP_RHI_THROW_ON_LIFETIME_VIOLATION=ON \
-DSNAP_RHI_VALIDATION_DESTROY_OP=ONThis combination will:
- Throw an exception when a resource lifetime contract is violated
- Validate destruction order (e.g., DescriptorPool outlives DescriptorSets)
- SnapRHI Resource Management Guide — Lifetime rules, synchronization contracts
- SnapRHI API Reference — Complete public API documentation
- Clang Sanitizers
- Vulkan Validation Layers
- RenderDoc
- Xcode Metal Debugging
Last updated: 2026-02-20