Before you start#
Review Vulkan and presentation, graphics synchronization, Wine loading, and wined3d. This chapter independently pins DXVK v2.5.3 to commit c707d9026f33b6ab89639f154b6ac5f6326fa037 and vkd3d-proton v2.14 to commit c965c1351fd6915a65bb7f647319536252a24a93. Wine 10.0 remains a separate historical baseline; these versions were not tested together.
Learning goals#
Compare Direct3D translation projects, trace a buffer creation failure and a command-list barrier, and account for CPU resources, Vulkan submission, presentation and completion as separate lifetimes.
Source walkthrough#
- DXVK
- Translates supported D3D9, D3D10 and D3D11 calls to Vulkan.
- vkd3d-proton
- Translates supported D3D12 operations to Vulkan.
- Vulkan device and WSI
- Driver capabilities, synchronization and presentation constrain both.
DXVK implements supported Direct3D 9, 10 and 11 interfaces over Vulkan. vkd3d-proton is a distinct project focused on Direct3D 12 over Vulkan; it is not the same project as upstream vkd3d, even though they share history. Wine's wined3d is another implementation with its own backend choices. Proton is a distribution and integration project with its own versions and patches, not a synonym for Wine, DXVK or vkd3d-proton. Which DLL a Windows program loads depends on its environment and loader configuration. This source comparison neither changes DLL overrides nor claims that any selected combination works on a particular machine.
The following exact excerpt is from DXVK src/d3d11/d3d11_device.cpp, D3D11Device::CreateBuffer, tag v2.5.3. DXVK's repository license is the zlib/libpng license. Surrounding validation and allocation are omitted:
if (!pDesc)
return E_INVALIDARG;
D3D11_BUFFER_DESC desc = *pDesc;
HRESULT hr = D3D11Buffer::NormalizeBufferProperties(&desc);
if (FAILED(hr))
return hr;
The null description is rejected before it is dereferenced. A local copy is then normalized, and failure from that validation is returned before construction of the buffer. Later checks reject unsupported tiled-resource requests; a null output pointer after successful validation produces S_FALSE in this path. When creation proceeds, a scoped COM wrapper holds the object, initialization is scheduled where appropriate, and an interface reference is assigned to the caller on success. The catch path reports a DXVK error and returns E_INVALIDARG. The code's output and lifetime behavior must be understood along with its initial output-pointer handling; failure does not hand the caller a valid object merely because memory for an output variable exists.
The second exact excerpt is from vkd3d-proton libs/vkd3d/command.c, d3d12_command_list_ResourceBarrier, tag v2.14. Its file notice grants LGPL-2.1-or-later; surrounding setup and barrier handling are omitted:
d3d12_command_list_end_current_render_pass(list, false);
d3d12_command_list_end_transfer_batch(list);
d3d12_command_list_barrier_batch_init(&batch);
The function ends the current render-pass context, ends a transfer batch and initializes a barrier batch before iterating the supplied barriers. These calls establish an ordering point in the implementation's command recording; they do not themselves prove that a submitted GPU queue has completed. The later loop examines barrier type and split-barrier flags; a begin-only barrier takes a distinct branch. The resource, declared before/after states and subresource range determine the later translation. An application that declares an incorrect old state has not repaired a hazard merely by calling a barrier method.
Devices, interfaces, and shaders#
A Direct3D application obtains interfaces through COM-style methods. A D3D11 device creates resources and state, while a device context records or issues operations using those resources. A resource descriptor describes size, usage and binding, and initialization data may supply initial contents. The implementation converts these requirements into Vulkan objects subject to the Vulkan device's enabled features and limits. A Windows interface reference is not itself a Vulkan queue completion token, and the application must still follow Direct3D's usage rules.
DXVK's D3D11 context implementation manages bindings and operations that feed its lower-level Vulkan context. The exact route depends on the operation and version. A buffer creation request, binding, draw and resource destruction are separate events. Implementations may defer execution, batch work or retain resources internally so that releasing an application interface does not destroy an object still needed by submitted work. To establish a specific guarantee, inspect the matching retention and completion path; one reference-count observation is not enough.
Shader translation converts a Direct3D-facing representation into code usable by Vulkan implementations. DXBC is a bytecode format used by many D3D programs; SPIR-V is an intermediate representation used by Vulkan shader modules. Translation includes semantics for resource access, stages, formats and other API behavior, not merely renaming opcodes. vkd3d-proton's D3D12 ecosystem can involve different shader formats and translator paths depending on application and project version. The presence of a translator does not prove that every instruction, shader model or extension is supported. Pipeline creation can fail due to unsupported features, invalid input or host driver behavior even after an earlier resource was created successfully.
A pipeline packages shader stages and relevant fixed-function state for execution. Its creation can be expensive, so implementations and drivers may cache compiled information. A cache is an optimization with version, device, driver and compatibility constraints; it is not portable proof that a different machine supports the same features. A missing or stale cache may increase preparation time without meaning that a shader is invalid. Conversely, a cache hit does not establish correctness of resource synchronization or pixel output. Do not copy unknown binary caches into a personal prefix to reproduce this source-only lesson.
D3D11 and D3D12 synchronization models#
D3D11 presents an API where much resource hazard management is implicit from the application's perspective. The implementation still must translate operations into Vulkan's explicit requirements, tracking relevant resource uses and arranging suitable execution and memory dependencies. Implicit API semantics do not mean the GPU sees no barriers. They mean the application expresses a different contract, leaving more of the mapping to the implementation. Simultaneous access from multiple threads also depends on the API's device/context rules and application synchronization; a graphics call is not automatically thread-safe because a resource is reference counted.
D3D12 exposes more explicit application control over command lists, heaps, resource states and fences. A heap is backing storage from which resources may be placed according to alignment and compatibility constraints; the resource is a distinct object with state and lifetime. A command list records commands but is not itself proof of GPU execution. A queue submission orders work subject to queue and synchronization rules, and a fence allows a producer or consumer to observe or wait for an appropriate completion value. A fence value is associated with a particular fence object and timeline of signals; comparing unrelated numerical values does not establish ordering.
A resource transition barrier describes an intended state change for specified subresources. A before-state mismatch, omitted dependency, early reuse or incorrectly split barrier can produce invalid behavior even if the API call returns normally. The vkd3d-proton excerpt shows the implementation ending active batches and preparing barrier handling; the remainder of the function handles types and flags. It is not enough to see ResourceBarrier in a source log and infer the exact Vulkan stage masks, access masks or image layout used in every case. Inspect the selected branch, tracked state and backend commands.
In Vulkan, command buffers, queues, semaphores and fences serve distinct purposes. A queue submission can wait on semaphores and signal others; a fence can signal to the host that a submission completed. Binary semaphores represent discrete signal/wait relationships and have reuse constraints; timeline semaphores associate monotonic values with dependencies where supported. Neither automatically owns an application's resource forever. Proper cleanup ties object release to the completion of every submission that can use it, including cross-queue dependencies.
Worked upload, draw, and present#
The following is synthetic source-based reasoning, not captured Vulkan commands or a measured frame. Assume an application creates a vertex buffer and uploads 256 bytes. The implementation validates the descriptor, retains objects needed for the upload and records transfer work. A later draw reads the vertex data. The necessary dependency must make the completed transfer writes available to the draw's reads in the relevant resource and queue context. If the implementation uses a staging allocation, it cannot recycle that allocation while the GPU may still read it.
For a D3D12-style state example, assume a texture starts in a copy-destination state, receives an upload and must next be sampled by a pixel shader. The application records an appropriate transition for that texture and subresource before the draw. The translator processes the barrier and constructs backend synchronization according to the actual branch and capabilities. A counterexample declares the texture already in the shader-read state while it was actually still being written as a copy destination. That incorrect before-state or missing dependency can leave a read-after-write hazard; adding a fence after presentation does not retroactively make an incorrect earlier transition valid. This example does not prescribe universal Vulkan stage masks.
The draw writes a presentation image, then a swapchain operation makes it available for presentation. A Vulkan window-system integration path generally acquires an available swapchain image, arranges rendering after the acquire dependency, submits work that signals a render-finished dependency, and requests presentation with the appropriate wait. The compositor or presentation engine can retain the image after the CPU's submission returns. A rendering fence and image reacquisition or presentation-related completion convey different facts; do not reuse an image or destroy its backing solely because the API call returned. Actual WSI behavior depends on the platform and implementation.
Suppose the application releases its buffer interface after recording the draw. The implementation must maintain references required by recorded or submitted work until safe retirement. The buffer's CPU-side interface reference, Vulkan buffer and memory allocation, command buffer, queue submission and fence each have a lifetime. On a device-loss result, waiting indefinitely for a normal completion is not a valid recovery strategy; the program must handle the API's reported device-removed condition and tear down or recreate resources according to the implementation and application contract. VK_ERROR_DEVICE_LOST is a Vulkan result; its precise translation to an HRESULT must be established from the specific DXVK or vkd3d-proton branch, not guessed as one universal mapping.
One observed source path for device loss#
The pinned vkd3d-proton source supplies a concrete example rather than a universal error translation rule. In command.c, one timeline-semaphore wait checks the returned Vulkan result; on failure it can report a fault for VK_ERROR_DEVICE_LOST or timeout, releases the associated pending submissions and returns. In the separately inspected device implementation, d3d12_device_report_fault calls d3d12_device_mark_as_removed with DXGI_ERROR_DEVICE_REMOVED and a device-loss message. That helper stores the reason with release ordering; GetDeviceRemovedReason reads it with acquire ordering. The fault handler can also return early when device-fault reporting is unavailable. This explains one checked route from a detected failure to an application-visible removal reason without claiming that every Vulkan error in either project takes the same route or that fault details are always available. The source paths and atomic operations are verified; no device-loss event was induced.
In vkd3d-proton libs/vkd3d/command.c, vkd3d_wait_for_gpu_timeline_semaphore, LGPL-2.1-or-later (the timeout selection above the wait is omitted):
if ((vr = VK_CALL(vkWaitSemaphores(device->vk_device, &wait_info, timeout))))
{
ERR("Failed to wait for Vulkan timeline semaphore, vr %d.\n", vr);
VKD3D_DEVICE_REPORT_FAULT_AND_BREADCRUMB_IF(device, vr == VK_ERROR_DEVICE_LOST || vr == VK_TIMEOUT);
vkd3d_waiting_fence_release_submissions(device, worker, fence);
return;
}
On a failed wait the code logs the Vulkan result, reports a fault for VK_ERROR_DEVICE_LOST or VK_TIMEOUT, releases the waiting submissions and returns. The five-second timeout above this call applies only when the breadcrumb or fault configuration flags are set, which this excerpt does not show. Its fault report is the next excerpt.
The fault report is d3d12_device_report_fault, LGPL-2.1-or-later (the fault-information query after the early return is omitted):
void d3d12_device_report_fault(struct d3d12_device *device)
{
const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
static pthread_mutex_t report_lock = PTHREAD_MUTEX_INITIALIZER;
VkDeviceFaultCountsEXT fault_counts;
VkDeviceFaultInfoEXT fault_info;
static bool reported = false;
VkResult vr;
uint32_t i;
d3d12_device_mark_as_removed(device, DXGI_ERROR_DEVICE_REMOVED, "VK_ERROR_DEVICE_LOST");
if (!device->device_info.fault_features.deviceFault)
return;
The helper stores DXGI_ERROR_DEVICE_REMOVED before it checks fault-reporting support, so a removal reason exists even when fault details are unavailable. The message passed to d3d12_device_mark_as_removed is fixed text, VK_ERROR_DEVICE_LOST, so the same message is recorded when the caller was reacting to a timeout. The reason is stored before that early return, so the early return does not undo it.
- Timeline wait (command.c)Vulkan driver
vkWaitSemaphores on a timeline value
- Vulkan driverTimeline wait (command.c)
Return. VK_ERROR_DEVICE_LOST or VK_TIMEOUT
- Timeline wait (command.c)d3d12_device_report_fault
Report fault when the condition holds
- d3d12_device_report_faultStored removed reason
Store DXGI_ERROR_DEVICE_REMOVED with release ordering
- ApplicationStored removed reason
GetDeviceRemovedReason loads the reason with acquire ordering
- Stored removed reasonApplication
Return. Return the stored HRESULT
Time runs downward. Dashed arrows show returns.
Read the text version
- Timeline wait (command.c) → Vulkan driver: vkWaitSemaphores on a timeline value
- Vulkan driver → Timeline wait (command.c) (return): VK_ERROR_DEVICE_LOST or VK_TIMEOUT
- Timeline wait (command.c) → d3d12_device_report_fault: Report fault when the condition holds
- d3d12_device_report_fault → Stored removed reason: Store DXGI_ERROR_DEVICE_REMOVED with release ordering
- Application → Stored removed reason: GetDeviceRemovedReason loads the reason with acquire ordering
- Stored removed reason → Application (return): Return the stored HRESULT
Capability and failure boundaries#
A physical GPU and its Vulkan driver expose a set of supported extensions, features, queue families, memory types and format capabilities. The Vulkan loader, installed driver, window system and application environment also matter. A particular Wine version does not guarantee a Vulkan extension; a Vulkan-capable device does not guarantee every Direct3D feature. Different versions of DXVK and vkd3d-proton can change minimum requirements. The pinned source versions establish what can be read here, not an endorsement of a current release or a compatibility matrix.
Failure should be preserved at its layer. A null D3D11 buffer descriptor triggers the quoted E_INVALIDARG branch before buffer construction. An unsupported resource feature can be rejected by later validation. A Vulkan allocation or pipeline operation can fail for resource or capability reasons. A swapchain can become unusable when a surface changes, and a device can be lost independently of a window resize. A caller should inspect the API return, associated device state and relevant configuration rather than reduce every error to “bad GPU driver.” Do not disable security controls or change system graphics configuration to force an experiment to pass.
Read the text version
- VK_ERROR_DEVICE_LOST: Checked by the timeline-wait and queue paths in command.c. → DXGI_ERROR_DEVICE_REMOVED: Stored by d3d12_device_mark_as_removed when the fault report runs. (report_fault stores the removed reason)
- VK_TIMEOUT: Checked on the timeline-wait path; the five-second bound applies only under fault or breadcrumb flags. → DXGI_ERROR_DEVICE_REMOVED: Stored by d3d12_device_mark_as_removed when the fault report runs. (same report call; the recorded message still names VK_ERROR_DEVICE_LOST)
- Other Vulkan results: Allocation, pipeline or capability failures follow their own branches. → Caller-specific result: Other error branches return or log their own results; this excerpt does not establish them. (branch-specific, not a universal mapping)
DXVK, vkd3d-proton and Wine DLL selection are separate deployment decisions. Installing or overriding a translation DLL in an existing personal prefix can change applications' behavior and is outside this exercise. A separate project or distribution can carry patches with behavior different from the pinned upstream repositories. Project source, tag, commit and exact binary build are necessary evidence before attributing an observed call path to one of them. No games, proprietary DLLs, anti-cheat or DRM bypass are involved in this lesson.
Practice#
The following pure Python ledger is an authored model of the upload-to-draw dependency. It creates no GPU object and does not load Vulkan, Wine, or a Windows executable:
python3 - <<'PY'
state = 'COPY_DEST'
upload_complete = True
if upload_complete and state == 'COPY_DEST':
state = 'SHADER_READ'
print('state before draw:', state)
print('draw dependency represented:', upload_complete and state == 'SHADER_READ')
PY
The program reports SHADER_READ and true. Its Boolean is not a Vulkan barrier, memory dependency or proof of GPU completion. As a counterexample, set upload_complete to false: the model does not transition and reports false. A real implementation needs the queue, resource, stage and access dependencies appropriate to the actual commands. If Python is absent, write the state table by hand.
For the source-only route, use the supplied 256-byte upload, draw and presentation sequence. Produce a table with one row each for staging storage, vertex or texture resource, command buffer, swapchain image and completion signal. Record first use, last possible use and evidence needed before reuse. Add the wrong-before-state counterexample, then explain why the quoted barrier setup does not prove device completion. This exercise requires no graphics session, physical device, Wine installation or prefix.
Knowledge check#
- What does DXVK's buffer creation path return for a null description, and why is that result earlier than object construction?
- What do the three quoted vkd3d-proton statements establish, and what do they not establish?
- Why can a D3D11 application rely on implicit hazard semantics while the Vulkan implementation still needs explicit dependencies?
- Why is a fence signal not interchangeable with a swapchain image becoming available for reuse?
- Does a Vulkan device-loss result establish one universal HRESULT mapping across these projects?
Answers#
- It returns
E_INVALIDARGbefore dereferencing the description or constructing a buffer. The output variable is not evidence of a valid resource on this failure path. - They end an active render-pass context and transfer batch and initialize barrier batching before processing the supplied barriers. They do not prove queue submission or GPU completion, and the later barrier branch determines the actual translation.
- The API contract places more hazard tracking responsibility on the implementation. Vulkan still requires the implementation to express suitable execution and memory ordering for the actual resource uses.
- A fence can describe completion of a submission, whereas presentation and image reacquisition involve additional ownership and window-system state. The meaning of each signal depends on the associated operation and protocol.
- No. The precise mapping depends on the implementation and path. Preserve the Vulkan result and the Windows-facing error separately until the relevant versioned source or runtime evidence establishes their relationship.
Checkpoint#
Submit the upload/draw/present ownership table, invalid transition counterexample and the two source-branch explanations. Pass when recording, submission, GPU completion and presentation are separate and the two translator projects have distinct pinned versions. Revisit the barrier and buffer creation paths if a recorded command is treated as a completed operation. This is not certification.
Sources#
-
Khronos Vulkan Specification: synchronization and cache control — distinguishes fences (host notification of device completion), semaphores (queue synchronization) and pipeline barriers (within-queue dependencies).
-
Khronos Vulkan Specification: WSI swapchains — acquire/present image ownership and presentation synchronization requirements.
-
Microsoft Direct3D 12 resource barriers — application-declared state transitions and synchronization model.
-
Microsoft Direct3D 11 multithreading — device/context concurrency constraints.