Graphics / QUBICENGINE HANDBOOK

DirectX 12 ↔ Vulkan

Compare responsibilities precisely: shared engine data, separate native objects, and the places where an analogy stops being exact.

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.

C++ / DIRECTX 12
// 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);

Map the useful analogies#

ResponsibilityDirectX 12VulkanBoundary to remember
DeviceID3D12DeviceVkDeviceSelection and feature enablement differ
SubmissionID3D12CommandQueueVkQueueFamily ownership is explicit in Vulkan
Recording storageCommand allocatorCommand poolReuse needs completion; threading rules differ
Recorded workCommand listCommand bufferLifecycle and reset semantics differ
ResourceID3D12ResourceVkBuffer / VkImage + memoryCreation/allocation are separate in Vulkan
Shader-visible viewsSRV/UAV/CBV descriptorsDescriptor sets and image/buffer viewsBinding models are not identical
Binding contractRoot signaturePipeline layout + set layoutsRoot parameters need a chosen Vulkan representation
Usage orderingResource states/barriersStage/access masks, layouts/barriersVulkan layouts alone do not express all dependencies
Host completionID3D12Fence valuesFences or timeline semaphore valuesBinary WSI semaphores have different lifecycle rules
PresentationDXGI swap chainVkSwapchainKHR + WSIAcquire/present synchronization differs
Shader targetDXILSPIR-VShared 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.

01DecodeCPU pixels02StageUpload storage03CopyGPU texture04BindDescriptor · material
Upload storage remains alive until its completion fence passes.

A deliberate shader variant#

hlsl · REFERENCE EXCERPT
[[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.

Search titles and full article text.