How vkd3d Implements Direct3D 12 to Vulkan Translation

vkd3d is an open-source translation layer that implements the Microsoft Direct3D 12 graphics API on top of the Khronos Vulkan API. Primarily developed for use with compatibility layers like Wine and Valve's Proton, it allows Windows games designed for DirectX 12 to run on Linux and other non-Windows platforms with near-native performance. This article explains how vkd3d translates core Direct3D 12 concepts—including API constructs, shader bytecode, memory management, and synchronization primitives—directly into Vulkan equivalents.

Architectural Alignment

Direct3D 12 and Vulkan share a fundamental modern graphics design philosophy: both are explicit, low-overhead APIs designed to give developers direct control over GPU hardware, memory allocation, and multithreading. Because of this common design lineage, vkd3d does not need to emulate high-level abstractions like older translation layers (such as Direct3D 9 or 11 to OpenGL). Instead, vkd3d acts largely as a structural translator, mapping direct counterparts between the two APIs.

At the device level:

  • An ID3D12Device maps directly to a Vulkan VkDevice and VkPhysicalDevice.
  • An ID3D12CommandQueue maps to a Vulkan VkQueue.
  • An ID3D12CommandList (and its allocator) records operations into a VkCommandBuffer allocated from a VkCommandPool.

Shader Translation via vkd3d-shader

Direct3D 12 shaders are authored in HLSL and compiled into either DirectX Bytecode (DXBC) or the newer LLVM-based DirectX Intermediate Language (DXIL). Vulkan GPUs do not consume DXBC or DXIL; they consume SPIR-V (Standard Portable Intermediate Representation).

To bridge this gap, vkd3d includes a dedicated sub-library named vkd3d-shader. It operates in stages:

  1. Parsing: It parses incoming DXBC or DXIL binary tokens into an internal abstract syntax tree (AST) or intermediate representation.
  2. Transformations: It normalizes differences between HLSL and GLSL/Vulkan semantics, such as resource binding models, control flow differences, and intrinsic functions.
  3. Emission: It generates valid SPIR-V binary code compatible with the host Vulkan driver.

vkd3d-shader also handles differences in coordinate spaces. While Direct3D 12 uses a clip-space depth range of \([0, 1]\), Vulkan historically used \([-1, 1]\) before adopting \([0, 1]\) as standard. However, Direct3D 12 and Vulkan differ in vertical viewport orientation (the Y-axis direction), which vkd3d normalizes within the generated SPIR-V or via modern Vulkan viewport extensions (VK_KHR_maintenance1).

Resource Binding and Root Signatures

Direct3D 12 organizes shader resources using "Root Signatures," which define constants, root descriptors, and descriptor tables pointing into descriptor heaps. Vulkan structures resources via VkPipelineLayout and VkDescriptorSetLayout.

vkd3d translates a D3D12 root signature into a corresponding Vulkan pipeline layout:

  • Root constants are mapped directly to Vulkan push constants.
  • Descriptor tables (CBVs, SRVs, UAVs, and Samplers) are mapped to Vulkan descriptor sets.
  • D3D12 allows unbounded and dynamically indexed descriptor arrays. vkd3d relies heavily on Vulkan’s Descriptor Indexing (VK_EXT_descriptor_indexing) features to map D3D12 descriptor heaps directly onto large, partially bound, and update-after-bind descriptor sets in Vulkan.

Memory and Resource Allocation

Direct3D 12 applications explicitly manage video memory through resources (ID3D12Resource) placed on heaps (ID3D12Heap). Vulkan exposes an identical model:

  • ID3D12Heap maps to VkDeviceMemory allocated using vkAllocateMemory.
  • Placed or committed buffers and textures map to VkBuffer or VkImage objects bound to device memory with vkBindBufferMemory or vkBindImageMemory.

vkd3d queries host memory properties via vkGetPhysicalDeviceMemoryProperties to match Direct3D 12 memory flags (such as D3D12_HEAP_TYPE_DEFAULT for GPU-local memory and D3D12_HEAP_TYPE_UPLOAD for CPU-accessible host memory) to the appropriate Vulkan memory type indices.

Synchronization and State Tracking

Synchronization in Direct3D 12 relies heavily on monotonic fences (ID3D12Fence) and resource barriers (D3D12_RESOURCE_BARRIER):

  • Fences: D3D12 fences allow the CPU and GPU to synchronize on a monotonically increasing 64-bit integer. vkd3d maps these directly to Vulkan Timeline Semaphores (VK_KHR_timeline_semaphore), providing a 1:1 behavioral match without performance penalties.
  • Barriers: D3D12 resource barriers dictate subresource state transitions (such as transitioning a texture from a render target to a shader resource view). vkd3d translates these barriers into vkCmdPipelineBarrier calls, mapping D3D12 resource states to Vulkan VkAccessFlags, VkPipelineStageFlags, and VkImageLayout parameters to ensure proper execution dependencies and memory caches are flushed.

Pipeline State Management

D3D12 compiles graphics and compute state into monolithic Pipeline State Objects (ID3D12PipelineState). Vulkan behaves similarly using VkPipeline objects. Because both APIs discourage runtime pipeline compilation on the main rendering thread due to stutter, vkd3d creates VkPipeline objects concurrently during D3D12 PSO creation. It translates vertex input layouts, rasterizer configurations, blend states, and depth-stencil states directly into their corresponding Vulkan pipeline creation structures.