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
ID3D12Devicemaps directly to a VulkanVkDeviceandVkPhysicalDevice. - An
ID3D12CommandQueuemaps to a VulkanVkQueue. - An
ID3D12CommandList(and its allocator) records operations into aVkCommandBufferallocated from aVkCommandPool.
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:
- Parsing: It parses incoming DXBC or DXIL binary tokens into an internal abstract syntax tree (AST) or intermediate representation.
- Transformations: It normalizes differences between HLSL and GLSL/Vulkan semantics, such as resource binding models, control flow differences, and intrinsic functions.
- 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:
ID3D12Heapmaps toVkDeviceMemoryallocated usingvkAllocateMemory.- Placed or committed buffers and textures map to
VkBufferorVkImageobjects bound to device memory withvkBindBufferMemoryorvkBindImageMemory.
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
vkCmdPipelineBarriercalls, mapping D3D12 resource states to VulkanVkAccessFlags,VkPipelineStageFlags, andVkImageLayoutparameters 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.