Render backends
B3D's renderer talks to the GPU through a pluggable backend. Three backends ship with the framework: Vulkan (primary desktop backend), Metal (Apple platforms), and Null (a headless stub for tests and tooling). Every backend implements the same @b3d::render::GpuDevice interface, so application code is identical across platforms — the choice is made once at build time.
Selecting a backend
The active backend is picked at CMake configuration time through the B3D_RENDER_BACKEND cache variable. The default depends on the host platform.
# Explicit selection
cmake -DB3D_RENDER_BACKEND=Vulkan ..
cmake -DB3D_RENDER_BACKEND=Metal ..
cmake -DB3D_RENDER_BACKEND=Null ..
B3D_BUILD_ALL_PLUGINS=ON builds every backend that the current platform supports, even when only one is the runtime default — useful for developers who want to A/B test against the non-primary path.
Platform support matrix
| Backend | Windows | Linux | macOS | iOS | Default on |
|---|---|---|---|---|---|
| Vulkan | Yes | Yes | via MoltenVK | via MoltenVK | Windows, Linux |
| Metal | — | — | Yes | Yes | macOS, iOS |
| Null | Yes | Yes | Yes | Yes | Headless tests |
MoltenVK layers Vulkan on top of Metal; it is kept enabled on Apple platforms so the Vulkan backend remains usable for debugging against MoltenVK-specific code paths even when Metal is the default.
Feature parity
| Feature | Vulkan | Metal | Null |
|---|---|---|---|
| Compute programs | Yes | Yes | — |
| Tessellation programs | Yes | Emulated (compute) | — |
| Geometry programs | Yes | — | — |
| Argument-buffer parameter sets | Descriptor sets | Tier-2 argument buffers | — |
| Occlusion queries | Yes | Yes | — |
| Timer queries | Yes | Apple Silicon only | — |
| Pipeline-statistics queries | Yes | — | — |
| MSAA | Yes | Yes | — |
| Texture compression (BC) | Yes (discrete) | Discrete Mac only | — |
| Texture compression (ETC2 / ASTC) | Mobile | Apple GPU 4+ | — |
| Ray tracing | Driver-dependent | Follow-up | — |
Debug labels (BeginLabel / InsertLabel) |
Yes | Yes | No-op |
Metal backend — known limitations
The Metal backend currently requires Metal Tier-2 argument buffers. That means Apple GPU family 4 or newer (iPhone 8 / A11 Bionic and later, all M-series Macs) or Mac 2 family (most discrete Mac GPUs from 2018 onwards). Older Intel integrated GPUs on Intel Macs will fail @b3d::render::GpuDevice::Initialize with a capability error — run Vulkan via MoltenVK instead.
Other phase-2 limitations:
- Metal textures always use
MTLStorageModePrivate.@b3d::render::Texture::Mapreturns an invalid scope; CPU traffic for texture data goes throughTextureUtility::Write/TextureUtility::Read, which stage into aGpuBufferand blit viaCopyBufferToTexture/CopyTextureToBuffer. GpuBufferstorage mode isSharedwhen the buffer is created withGpuBufferFlag::StoreOnCPUWithGPUAccessor as aStagingRead/StagingWritetype; otherwisePrivateandGetMappedMemory()returns null (the engine-levelGpuBufferUtilityhandles staging). Managed storage for discrete Mac GPUs is a follow-up.- Timer queries need
supportsCounterSampling:MTLCounterSamplingPointAtStageBoundary, which is only advertised on Apple Silicon. Intel Macs transparently disableRSC_TIMER_QUERIESandWriteTimestampbecomes a no-op. MetalGpuCommandBuffer::WriteTimestamprequires an active encoder (render, compute, or blit) and samples at stage boundaries. Each encoder variant additionally needs the matchingMTLCounterSamplingPoint(AtDrawBoundary/AtDispatchBoundary/AtBlitBoundary) to be advertised by the device — Apple Silicon typically advertises only the stage boundary, so timestamps inside an encoder whose per-encoder sampling point is unsupported are dropped with a warn-once log. Timestamps outside of any active encoder are likewise dropped with a warn-once log.- Occlusion query pools must be staged on the command buffer via
MetalGpuCommandBuffer::SetPendingVisibilityPool(pool)beforeBeginRenderPass; Metal requiresvisibilityResultBufferto be attached to the render-pass descriptor at encoder creation time.
Clip-space conventions
Metal's clip space Y axis points up (positive Y = top of the viewport), matching D3D11 / D3D12 and the HLSL convention that BSL shaders author in. The Metal backend therefore reports NdcYAxis = Up and does not pass flip_vert_y = true to SPIRV-Cross — cross-compiled MSL forwards the HLSL-authored clip positions verbatim. This differs from the Vulkan backend, which uses NdcYAxis = Down (Vulkan's native NDC) and flip_vert_y = true to reconcile the same HLSL source with Vulkan's y-down clip space.
Engine-side code that branches on GpuBackendConventions::NdcYAxis (projection matrices, screen-to-NDC helpers, DrawScreenQuad, shadow-map sampling, gizmos) automatically picks the correct path — see RendererUtility::DrawScreenQuad and RendererView::GetNDCToUV for the representative patterns.
Vulkan backend — known limitations
Descriptor-set update frequency is bounded by the driver's maxBoundDescriptorSets limit (typically 4 on desktop, 8 on mobile). The framework's parameter-set layout is designed to stay within those limits.
Null backend
The Null backend accepts and drops every command. It exists so that unit tests and the asset importer can instantiate a GpuDevice on a headless build agent that has no GPU or display server.