Before you start#
Review the userspace graphics path, OpenGL and Vulkan, graphics synchronization, and Wine windowing. This chapter uses Wine 10.0 commit b073859675060c9211fcbccfd90e4e87520dc2c2. All frame and lifetime examples are synthetic; no GPU workload ran.
Learning goals#
Trace Direct3D objects into wined3d device and command-stream state, explain asynchronous presentation and reference ownership, and distinguish a selected OpenGL backend from Wine's other backend possibilities.
Source walkthrough#
- COM interface
Device, context and resources expose API state.
- wined3d command stream
Queue selected operations and retain needed state.
- Backend
Translate to OpenGL or supported Vulkan operations.
- Driver and GPU
Execute asynchronously; completion has a separate lifetime.
Direct3D is a family of graphics APIs with version-specific interfaces. Applications create COM objects such as devices, resources, views, shaders and contexts. COM interfaces expose methods and reference counting; a pointer identifies an interface, not a guarantee that GPU work has completed. Direct3D 9, 10 and 11 differ in their object and context models, so this chapter's simplified path should not be read as an identical implementation of each version. Wine's wined3d is one implementation layer that can translate supported operations to a graphics backend.
In Wine 10.0 dlls/wined3d/device.c, wined3d_device_init starts with the following actual excerpt, licensed LGPL-2.1-or-later; later initialization is omitted:
device->ref = 1;
device->wined3d = wined3d;
wined3d_incref(device->wined3d);
device->adapter = adapter;
The initial device reference establishes ownership of the newly initialized object. The second statement stores its parent wined3d pointer, and the third retains that parent. The fourth associates the selected adapter. The source subsequently initializes resource and shader lists, records creation parameters, builds state tables and creates a command stream. If state-table compilation fails, it destroys the initialized trees, decrements the wined3d reference and returns the failure. If command-stream creation fails, it takes a separate cleanup branch. A partially initialized device must unwind what was actually acquired; a returned failure is not a usable device just because initialization began.
The following excerpt is from Wine 10.0 dlls/wined3d/cs.c, wined3d_cs_emit_present, also LGPL-2.1-or-later. Earlier operation setup and later submission are omitted:
wined3d_resource_reference(&swapchain->front_buffer->resource);
for (i = 0; i < swapchain->state.desc.backbuffer_count; ++i)
{
wined3d_resource_reference(&swapchain->back_buffers[i]->resource);
}
The first statement retains the front-buffer resource. The loop retains every back-buffer resource described by this swapchain. These references are taken before the command is submitted in the surrounding function, so queued work has resource lifetime beyond the immediate caller's scope. They are not a general promise that every GPU operation has finished, nor proof that the window system has displayed the frame. The execution path invokes the swapchain's present operation, handles applicable discard state and decrements pending_presents; the producer can wait when the configured latency limit is reached. Read both the emit and execution paths before deciding which thread owns a state transition.
Objects and state from API to backend#
A device represents the application's relationship to an adapter and owns or coordinates resources and state. A resource can be a buffer or texture; its usage, dimensions, format and bind flags constrain what operations are valid. A view describes a particular interpretation or range of a resource for a role such as a shader input or render target. The same resource can have multiple views, but view compatibility is constrained by format and API rules. A shader is executable graphics code in an API-defined representation, and pipeline state controls how inputs become output pixels or compute results.
A draw call uses the current pipeline state: vertex inputs and buffers, shader stages, rasterization settings, render targets, blend/depth state and other bindings. A draw is not a single immutable picture stored in the call unless the implementation captures or retains the required state. If the application changes a binding immediately after submitting work, the command machinery must preserve the semantics required for the earlier draw. Implementations can cache, translate or defer state, but they must maintain observable ordering. An invalid resource description, unsupported format, insufficient capability or allocation failure can return an error before a valid object is produced.
Wine selects implementation machinery based on adapter capabilities and configuration. The v10 source contains an OpenGL adapter and a Vulkan adapter. Their existence does not mean every system supports both, that every Direct3D feature works on either, or that a particular runtime selected one. OpenGL driver and extension availability, Vulkan device capabilities, build options and the application feature request affect the result. Mesa is one possible host driver implementation; it is not synonymous with OpenGL or guaranteed to be installed.
On an OpenGL-backed path, translating Direct3D state may involve GL objects, shader representations and context operations. A context is associated with a thread according to the graphics API and window-system binding rules; backend work must make an appropriate context current and synchronize state when work moves between threads or contexts. Wine's own locks and command-stream conventions govern its implementation, but they do not make an arbitrary application call safe on every thread. A GL context, Windows device and Wayland or X11 surface are related resources with different owners and destruction rules.
Shaders also cross representation boundaries. HLSL is a source language; applications commonly provide compiled Direct3D shader bytecode, including DXBC for applicable versions. Wine's shader machinery can translate supported instructions and state into a representation appropriate to the selected backend, potentially producing GLSL for an OpenGL route. This is not a claim that every bytecode version is supported or that every shader always passes through the same translator. Precision, resource formats, shader-stage limits and driver bugs can produce differences even after a device is created. A shader compiler failure must preserve its error evidence; silently assuming a successful translation hides the actual boundary.
A worked presentation lifetime#
Use this synthetic example. A swapchain has one front buffer and two back buffers. Before present submission, the application and swapchain machinery hold the relevant references. The producer constructs a present operation, increments the pending-present counter and takes one resource reference on the front buffer and one on each of the two back buffers as shown above. It submits the command. If the application releases one of its own interface references immediately afterward, that release alone does not erase the command stream's retained resource references.
The worker later executes the operation, calls the swapchain's selected present function and handles applicable discard behavior. It decrements the pending-present counter and can wake a producer waiting for latency control. In this pinned cs.c path, the emit/exec excerpt does not visibly decrement the resource references it took; do not infer their release timing from this excerpt. The exact release path and timing for each retained resource must be checked in the matching implementation; this simplified count is a teaching ledger, not a measured reference count. Crucially, completion of command-stream processing is not automatically identical to physical scanout or the point at which the GPU has finished every use. GPU fences and host presentation protocols have separate completion semantics.
Now suppose initialization fails while compiling the device state table. The source destroys several trees that were already initialized, decrements the parent wined3d reference and returns the failure. It does not return a fully usable device merely because device->ref was set earlier. If command-stream creation instead fails later, follow the distinct err cleanup path. An investigation should list the resources acquired before the failure and the branch that releases each one. Treating every error as though it followed the successful present path leads to leaks or invalid releases.
The difference between CPU ownership and GPU completion is central. A reference can keep a CPU-side object or backend resource alive while commands remain queued; GPU synchronization establishes whether particular device work has completed. A raw pointer captured by an asynchronous operation needs an appropriate retained object, and a fence alone does not rescue a pointer freed before submission. Conversely, holding a COM interface forever avoids neither missing synchronization nor a resource-state error. The correct proof has both an ownership timeline and an ordering timeline.
Threads, queues, and state hazards#
The wined3d command stream decouples some API-side work from backend execution. A producer constructs an operation with an opcode and parameters, and the consumer executes it according to the stream implementation. This can reduce work on the application thread, but introduces a period in which queued state must remain valid. The present path copies rectangles and scalar flags into the operation and retains buffer resources. A pointer to application stack memory would be unsafe if the worker consumed it after the stack frame ended; the implementation must instead copy data or retain an owner where appropriate.
- Emit operation
wined3d_cs_emit_present queues an opcode and copies rectangles and flags.
- Count and retain
pending_presents is incremented and front and back buffer resources are referenced.
- Submit
The operation is handed to the command stream through the device context.
- Latency gate
At max_frame_latency the producer waits on present_event after releasing the wined3d mutex.
- Worker executes
wined3d_cs_exec_present runs the present path and decrements pending_presents.
- Wake producer
If waiting_for_present was set, the worker signals present_event.
The pending-present count is a limit on producer progress ahead of the command-stream worker, not a universal count of frames physically visible on screen. The source increments it atomically and can wait on an event when the configured maximum is reached, temporarily releasing the wined3d mutex around the wait. The worker decrements it and signals a waiting producer. This avoids holding that mutex continuously while waiting for the work that may need progress. A wake must be interpreted with the associated predicate; scheduler delay, another present and errors can alter timing. No frame rate can be derived from the source excerpt alone.
In Wine 10.0 dlls/wined3d/cs.c, wined3d_cs_emit_present, LGPL-2.1-or-later (the operation setup and submission before this point are omitted):
/* Limit input latency by limiting the number of presents that we can get
* ahead of the worker thread. */
while (pending >= swapchain->max_frame_latency)
{
InterlockedExchange(&cs->waiting_for_present, TRUE);
pending = InterlockedCompareExchange(&cs->pending_presents, 0, 0);
if (pending >= swapchain->max_frame_latency || !InterlockedCompareExchange(&cs->waiting_for_present, FALSE, TRUE))
{
TRACE_(d3d_perf)("Reached latency limit (%u frames), blocking to wait.\n", swapchain->max_frame_latency);
wined3d_mutex_unlock();
WaitForSingleObject(cs->present_event, INFINITE);
wined3d_mutex_lock();
TRACE_(d3d_perf)("Woken up from the wait.\n");
}
}
The loop compares the producer's pending count with max_frame_latency, marks waiting_for_present, and releases the wined3d mutex while it waits on present_event. It reads the count again after waking. The excerpt does not show the worker's decrement, so it bounds producer progress only, not how many frames are visible.
Backend formats require careful matching. A Direct3D texture format implies channel types, precision and sometimes sampling or render-target capabilities. A host API may support the format for sampling but not for a requested attachment or usage. A view can select a range or interpretation permitted by the Direct3D API, but cannot conjure an unsupported host format. The implementation can translate or emulate some cases; others fail device or resource creation. Compare the requested usage, backend feature query and actual returned HRESULT before blaming a particular graphics driver.
A Windows-facing device-lost or removed result is an API-level condition with version-specific semantics. D3D9's lost-device and reset rules are not identical to D3D11's removed-device reporting, and neither is simply a synonym for a window resize. An application must follow the appropriate API contract, release or recreate resources where required and preserve diagnostic status. A backend failure, surface loss, unsupported feature or GPU reset can have different underlying causes. The chapter does not claim a specific mapping for every Wine or host driver version.
Tracing an initialization failure#
Suppose an application asks for a device with a particular feature level and the adapter is present. Presence alone is insufficient: initialization also prepares state translation, chooses shader and pipeline operations, and creates a command stream. If state-table compilation returns an error, the source destroys its initialized state trees and releases the parent reference before returning that HRESULT. The application must discard any output that the API does not promise valid on failure. A second attempt with a different feature request is a separate operation; it should not reuse a pointer from the failed attempt as though initialization had completed.
In the same dlls/wined3d/device.c, wined3d_device_init, LGPL-2.1-or-later (the successful initialization steps are omitted):
if (vertex_pipeline->vp_states && fragment_pipeline->states
&& FAILED(hr = compile_state_table(device->state_table, device->multistate_funcs,
&adapter->d3d_info, supported_extensions, vertex_pipeline,
fragment_pipeline, adapter->misc_state_template)))
{
ERR("Failed to compile state table, hr %#lx.\n", hr);
wine_rb_destroy(&device->samplers, NULL, NULL);
wine_rb_destroy(&device->rasterizer_states, NULL, NULL);
wine_rb_destroy(&device->blend_states, NULL, NULL);
wine_rb_destroy(&device->depth_stencil_states, NULL, NULL);
wine_rb_destroy(&device->so_descs, NULL, NULL);
wine_rb_destroy(&device->ffp_vertex_shaders, NULL, NULL);
wine_rb_destroy(&device->ffp_pixel_shaders, NULL, NULL);
wined3d_decref(device->wined3d);
return hr;
The branch runs only when both pipeline state arrays exist and the state-table compile fails. Each tree is destroyed and wined3d_decref releases the parent before return hr. The later err label is a separate cleanup path, as the paragraph above says, and this excerpt does not show what it releases.
If command-stream creation fails after the state-table stage, the source sets a failure value and goes to its error label. That label is a separate path to inspect when determining which resources exist at that point. The first error path cannot be substituted as a complete proof for the second. An evidence record should retain the requested adapter index, feature levels, returned result, selected backend and exact Wine build. Those facts let a reviewer distinguish unsupported capability from resource exhaustion or missing graphics infrastructure, although a result alone might still be ambiguous.
- Applicationwined3d_device_init
Request a device for an adapter and feature level
- wined3d_device_initcompile_state_table
Compile the state table
- compile_state_tablewined3d_device_init
Return. Fails with a failure HRESULT
- wined3d_device_initParent wined3d object
Release the parent reference (wined3d_decref)
- wined3d_device_initApplication
Return. Return failure; no usable device
Time runs downward. Dashed arrows show returns.
Read the text version
- Application → wined3d_device_init: Request a device for an adapter and feature level
- wined3d_device_init → compile_state_table: Compile the state table
- compile_state_table → wined3d_device_init (return): Fails with a failure HRESULT
- wined3d_device_init → Parent wined3d object: Release the parent reference (wined3d_decref)
- wined3d_device_init → Application (return): Return failure; no usable device
The application can use a negative control by submitting an intentionally invalid resource description in an isolated, authorized test environment and checking that it gets an error rather than a usable resource. A source-only reader can instead trace the supplied state-table failure without constructing a device. Neither approach licenses treating all failures as harmless: a failed creation may reveal a leak or reference imbalance, so repeatability and cleanup must be examined if a real test is later performed. No such GPU test has run here.
Practice#
The following owned arithmetic model tracks three references taken for a hypothetical front buffer and two back buffers. It does not create graphics objects or call Wine:
python3 - <<'PY'
refs = {'front': 1, 'back0': 1, 'back1': 1}
for name in refs:
refs[name] += 1
refs['back0'] -= 1 # synthetic application-side release
print('after enqueue and one release:', refs)
for name in refs:
refs[name] -= 1 # synthetic completion of retained references
print('after retained-reference release:', refs)
PY
The first result is front two, first back buffer one and second back buffer two. The second result is one, zero and one. This is an explicitly simplified ledger: it does not model swapchain ownership, actual resource counts or GPU completion. As a counterexample, remove the retained-reference increment and observe that the application's release would leave the synthetic back buffer at zero before queued work completes. That is why the source's reference operations matter; the program is not a Wine or GPU test.
For a source-only alternative, use the device initialization and present excerpts. Draw the acquired parent reference, three buffer references, producer and worker in sequence. Mark the state-table failure cleanup separately from successful submission and label GPU completion as an additional observation. If Python is unavailable, calculate the same table by hand; no display server, Wine prefix or GPU access is required.
Knowledge check#
- Why does device initialization increment the parent wined3d reference, and what happens on the state-table error branch?
- Why are the swapchain buffers referenced before the present command is submitted?
- Does a decremented pending-present count prove that scanout or all GPU work has completed?
- Why is Wine 10.0's OpenGL backend not evidence that every wined3d configuration uses OpenGL?
- Why is a valid Direct3D format insufficient to establish that every requested host usage is supported?
Answers#
- The device retains its parent as part of the initialized object's ownership. On the cited state-table failure path, the code destroys previously initialized trees, decrements that parent reference and returns the error instead of a usable device.
- The command can outlive the immediate call. Retained resources allow queued work to refer to them even if the application's own reference changes; exact release and GPU synchronization still need their corresponding paths.
- No. It reports progress in the command-stream bookkeeping. Backend submission, GPU completion and display presentation have separate meanings and need separate evidence.
- The same pinned source tree also has a Vulkan adapter. Actual backend choice and supported capabilities depend on configuration, build and host environment.
- Backend format support depends on usage and features. A host may allow sampling but not rendering for a particular format; translation or failure depends on the implementation and request.
Checkpoint#
Submit the parent and buffer reference ledger, state-table failure cleanup, and a timeline separating command processing from GPU completion. Pass when the selected backend is conditional and failed initialization is not described as a valid device. Revisit device.c and cs.c and retry if a COM release is treated as a GPU fence. This is not certification.