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::Map returns an invalid scope; CPU traffic for texture data goes through TextureUtility::Write / TextureUtility::Read, which stage into a GpuBuffer and blit via CopyBufferToTexture / CopyTextureToBuffer.
  • GpuBuffer storage mode is Shared when the buffer is created with GpuBufferFlag::StoreOnCPUWithGPUAccess or as a StagingRead / StagingWrite type; otherwise Private and GetMappedMemory() returns null (the engine-level GpuBufferUtility handles 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 disable RSC_TIMER_QUERIES and WriteTimestamp becomes a no-op.
  • MetalGpuCommandBuffer::WriteTimestamp requires an active encoder (render, compute, or blit) and samples at stage boundaries. Each encoder variant additionally needs the matching MTLCounterSamplingPoint (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) before BeginRenderPass; Metal requires visibilityResultBuffer to 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.