The engine shares intent#
A scene contains entities and components regardless of backend. An imported mesh contains geometry. An animation pose contains joint transforms. A material contains parameters and asset handles. A render graph describes dependencies and usages. Those are engine responsibilities. The backend implements that intent using its native allocation, binding, command, and presentation APIs. Switching backends recreates native resources and selects a supported capability profile; it does not convert a pointer or substitute a directory path.
Reference excerpt: existing texture and recording context, after a copy command has been recorded. Lifetime completion is tracked separately.
// Destination texture: copied on this direct queue.
D3D12_RESOURCE_BARRIER b{};
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
b.Transition.pResource = texture;
b.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
b.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
list->ResourceBarrier(1, &b);Reference excerpt: existing texture and recording context, after a copy command has been recorded. Lifetime completion is tracked separately.
// Same-family copy → fragment sampling; one mip, one layer.
VkImageMemoryBarrier2 b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
b.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
b.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
b.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
b.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
b.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
b.image = image;
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkDependencyInfo d{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
d.imageMemoryBarrierCount = 1;
d.pImageMemoryBarriers = &b;
vkCmdPipelineBarrier2(commandBuffer, &d);Map the useful analogies#
| Responsibility | DirectX 12 | Vulkan | Boundary to remember |
|---|---|---|---|
| Device | ID3D12Device | VkDevice | Selection and feature enablement differ |
| Submission | ID3D12CommandQueue | VkQueue | Family ownership is explicit in Vulkan |
| Recording storage | Command allocator | Command pool | Reuse needs completion; threading rules differ |
| Recorded work | Command list | Command buffer | Lifecycle and reset semantics differ |
| Resource | ID3D12Resource | VkBuffer / VkImage + memory | Creation/allocation are separate in Vulkan |
| Shader-visible views | SRV/UAV/CBV descriptors | Descriptor sets and image/buffer views | Binding models are not identical |
| Binding contract | Root signature | Pipeline layout + set layouts | Root parameters need a chosen Vulkan representation |
| Usage ordering | Resource states/barriers | Stage/access masks, layouts/barriers | Vulkan layouts alone do not express all dependencies |
| Host completion | ID3D12Fence values | Fences or timeline semaphore values | Binary WSI semaphores have different lifecycle rules |
| Presentation | DXGI swap chain | VkSwapchainKHR + WSI | Acquire/present synchronization differs |
| Shader target | DXIL | SPIR-V | Shared HLSL needs deliberate target/layout variants |
Follow one texture through both#
Both paths decode a file into CPU pixels, reserve staging storage, copy into a sampled texture, establish shader-read usage, publish a descriptor, and retire staging after completion. DX12 uses GetCopyableFootprints and byte RowPitch. Vulkan uses buffer-image copy regions with format/block constraints and explicit image layouts. DX12’s direct-queue sample transitions COPY_DEST to PIXEL_SHADER_RESOURCE. Vulkan’s same-queue transfer barrier names transfer writes as the source and sampled reads at the consuming stage as the destination. A separate upload queue introduces additional queue dependencies in either API, plus queue-family ownership when Vulkan families differ.
A deliberate shader variant#
[[vk::binding(0, 0)]] Texture2D<float4> baseColor;
[[vk::binding(1, 0)]] SamplerState linearSampler;
[[vk::binding(2, 0)]] cbuffer FrameConstants {
row_major float4x4 worldViewProjection;
};This Vulkan excerpt uses set 0 bindings 0, 1, and 2. It does not conflict the image and sampler by assigning both binding zero. The DX12 sample instead uses t0, s0, and b0 through a root signature. Vertex locations and matrix packing must also be declared and validated for the complete shader.
Feature checks determine behavior#
The baseline DX12 teaching path requires Shader Model 6.0. The Vulkan reference profile requires Vulkan 1.3 plus the enabled dynamic-rendering, synchronization2, and timeline features. Ray tracing and other advanced paths have additional device requirements. A backend chooses a supported technique or explains the missing capability. Portability includes shader formats, memory limits, attachment formats, presentation integration, and coordinate conventions. Test several drivers rather than interpreting one successful run as proof of every platform. The engine’s shared abstractions should expose required functionality without claiming these APIs are the same.
Choose a path#
Build the primary DX12 samples or study Vulkan setup. Khronos HLSL guidance explains the secondary shader target. Neither native backend was run in the website authoring environment.