Backend Architecture

Goldy supports three GPU backends, each implemented natively against the platform graphics API — no translation layers (like MoltenVK) are involved.

BackendAPI LevelPlatformsRust Crate
Vulkan1.4+Windows, Linuxash
DX12Direct3D 12Windowswindows + gpu-allocator
MetalTier 2+macOS, iOSmetal

Native Implementations

Each backend maps Goldy concepts directly to the most natural primitives of its target API:

┌─────────────────────────────────────────────────────────────┐
│                    Goldy Core API                           │
│                                                             │
│   Device, Buffer, Texture, Pipeline, CommandEncoder, ...    │
└─────────────────────────────────────────────────────────────┘
        │                    │                    │
        ▼                    ▼                    ▼
┌───────────────┐    ┌───────────────┐    ┌───────────────┐
│ Vulkan 1.4+   │    │ Metal 2+      │    │ DX12          │
│               │    │               │    │               │
│ • ash crate   │    │ • metal-rs    │    │ • windows-rs  │
│ • Dynamic     │    │ • Argument    │    │ • Root        │
│   rendering   │    │   buffers     │    │   signatures  │
│ • Descriptor  │    │ • Native      │    │ • Descriptor  │
│   indexing    │    │   hazard      │    │   heaps       │
│ • Buffer      │    │   tracking    │    │               │
│   device addr │    │               │    │               │
└───────────────┘    └───────────────┘    └───────────────┘

Translation layers introduce overhead from API mismatches, incompatible synchronization models, and extra validation. Native backends can leverage each API's strengths directly — for example, Metal's built-in hazard tracking, or Vulkan's descriptor indexing for bindless rendering.

Backend Selection

Default Selection

Goldy selects the platform-preferred backend automatically:

PlatformDefault Backend
macOSMetal
WindowsDX12
LinuxVulkan

Runtime Override — GOLDY_BACKEND

Override the backend at runtime with the GOLDY_BACKEND environment variable:

GOLDY_BACKEND=vulkan cargo run --example triangle
GOLDY_BACKEND=dx12   cargo run --example triangle

Accepted values (case-insensitive):

ValueBackend
vulkan, vkVulkan
dx12, d3d12, directxDX12
metal, mtlMetal

An unrecognized value produces a clear error listing the valid options.

Programmatic Selection

Query the active backend at runtime:

#![allow(unused)]
fn main() {
let instance = Instance::new()?;
println!("Backend: {:?}", instance.backend_type());
// Prints: Backend: Dx12   (on Windows)
// Prints: Backend: Vulkan (on Linux)
// Prints: Backend: Metal  (on macOS)
}

Compile-Time Selection (Feature Flags)

You can also restrict which backends are compiled in via Cargo features. This excludes both the code and the dependencies of unselected backends:

cargo build --no-default-features --features vulkan

See Conditional Compilation for details on feature flags, dependency exclusion, and CI setup.

Adapter Enumeration

After creating an Instance, enumerate available GPU adapters to inspect what hardware is present:

#![allow(unused)]
fn main() {
let instance = Instance::new()?;
let adapters = instance.enumerate_adapters();

for adapter in &adapters {
    println!("{}: {} ({})", adapter.id(), adapter.name(), adapter.vendor());
    println!("  Type: {:?}", adapter.device_type());
}
}

DeviceType

Each adapter reports a DeviceType:

VariantMeaning
DiscreteGpuDedicated graphics card with its own VRAM
IntegratedGpuGPU integrated into the CPU (shared memory)
CpuSoftware renderer (e.g. WARP on DX12, lavapipe on Vulkan)
OtherUnknown or unrecognized device class

Creating a Device

Request a device with a preferred DeviceType. If no adapter matches, Goldy falls back to the first available adapter:

#![allow(unused)]
fn main() {
let device = instance.create_device(DeviceType::DiscreteGpu)?;

// Or target a specific adapter by ID:
let device = instance.create_device_for_adapter(adapter.id())?;
}

Backend Capabilities

Device Capabilities

Query format preferences and backend-specific capabilities after creating a device:

#![allow(unused)]
fn main() {
let caps = device.capabilities();

println!("Surface format:     {:?}", caps.preferred_surface_format);
println!("Render target fmt:  {:?}", caps.preferred_render_target_format);
println!("Zero-copy readback: {}", caps.has_zero_copy_storage_readback);
}
CapabilityVulkanDX12Metal
Zero-copy CPU storage readbackYesNo (requires GPU copy to readback heap)Yes
Preferred surface formatBgra8UnormSrgbBgra8UnormSrgbBgra8UnormSrgb

Vulkan Backend

The Vulkan backend requires Vulkan 1.4+ and uses:

  • Dynamic rendering (VK_KHR_dynamic_rendering) — no VkRenderPass or VkFramebuffer objects
  • Descriptor indexing — bindless resource access by index in shaders
  • Buffer device address — 64-bit GPU pointers for direct memory access in shaders

DX12 Backend

The DX12 backend uses the windows crate and provides:

  • Root signatures for resource binding
  • Descriptor heaps for efficient bindless resource management
  • Shader compilation via Slang to DXIL
  • WARP software rasterizer for headless/CI use (GOLDY_DX12_FORCE_WARP=1)
  • GPU-Based Validation for deep debugging (GOLDY_DX12_GBV=1)

Metal Backend

The Metal backend uses the metal crate (native Metal, not MoltenVK):

  • Argument buffers for bindless resource binding
  • Native hazard tracking — Metal tracks resource hazards automatically
  • Shader compilation via Slang to Metal Shading Language

The GpuBackend Trait

All backends implement the GpuBackend trait, which defines the full interface for device management, resource creation, shader compilation, pipeline management, rendering, and compute dispatch:

#![allow(unused)]
fn main() {
pub trait GpuBackend: Send + Sync {
    fn backend_type(&self) -> BackendType;
    fn enumerate_adapters(&self) -> Vec<AdapterInfo>;
    fn create_device(&mut self, adapter_id: u32) -> Result<DeviceHandle>;
    fn create_buffer(&mut self, device: DeviceHandle, ...) -> Result<BufferHandle>;
    fn create_shader_with_paths(&mut self, device: DeviceHandle, ...) -> Result<ShaderHandle>;
    fn create_pipeline(&mut self, device: DeviceHandle, ...) -> Result<PipelineHandle>;
    // ... rendering, compute, surface, texture, sampler, timeline ...
}
}

Resources are identified by opaque u64 handles (DeviceHandle, BufferHandle, ShaderHandle, etc.) that each backend maps to native API objects internally.