The open source OpenXR runtime
1// Copyright 2019-2024, Collabora, Ltd.
2// Copyright 2025, NVIDIA CORPORATION.
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief D3D12 client side glue to compositor implementation.
7 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
8 * @author Jakob Bornecrantz <jakob@collabora.com>
9 * @author Fernando Velazquez Innella <finnella@magicleap.com>
10 * @author Korcan Hussein <korcan.hussein@collabora.com>
11 * @ingroup comp_client
12 */
13
14#include "comp_d3d12_client.h"
15
16#include "comp_d3d_common.hpp"
17#include "xrt/xrt_compositor.h"
18#include "xrt/xrt_config_os.h"
19#include "xrt/xrt_handles.h"
20#include "xrt/xrt_deleters.hpp"
21#include "xrt/xrt_results.h"
22#include "xrt/xrt_vulkan_includes.h"
23#include "d3d/d3d_dxgi_formats.h"
24#include "d3d/d3d_d3d12_helpers.hpp"
25#include "d3d/d3d_d3d12_fence.hpp"
26#include "d3d/d3d_d3d12_bits.h"
27#include "d3d/d3d_d3d12_allocator.hpp"
28#include "util/u_misc.h"
29#include "util/u_pretty_print.h"
30#include "util/u_time.h"
31#include "util/u_logging.h"
32#include "util/u_debug.h"
33#include "util/u_handles.h"
34#include "util/u_win32_com_guard.hpp"
35
36#include <d3d12.h>
37#include <wil/resource.h>
38#include <wil/com.h>
39#include <wil/result_macros.h>
40
41#include <assert.h>
42#include <memory>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <chrono>
47#include <array>
48
49using namespace std::chrono_literals;
50using namespace std::chrono;
51
52DEBUG_GET_ONCE_LOG_OPTION(log, "D3D_COMPOSITOR_LOG", U_LOGGING_INFO)
53
54DEBUG_GET_ONCE_BOOL_OPTION(barriers, "D3D12_COMPOSITOR_BARRIERS", false);
55DEBUG_GET_ONCE_BOOL_OPTION(compositor_copy, "D3D12_COMPOSITOR_COPY", true);
56
57/*!
58 * Spew level logging.
59 *
60 * @relates client_d3d12_compositor
61 */
62#define D3D_SPEW(c, ...) U_LOG_IFL_T(c->log_level, __VA_ARGS__);
63
64/*!
65 * Debug level logging.
66 *
67 * @relates client_d3d12_compositor
68 */
69#define D3D_DEBUG(c, ...) U_LOG_IFL_D(c->log_level, __VA_ARGS__);
70
71/*!
72 * Info level logging.
73 *
74 * @relates client_d3d12_compositor
75 */
76#define D3D_INFO(c, ...) U_LOG_IFL_I(c->log_level, __VA_ARGS__);
77
78/*!
79 * Warn level logging.
80 *
81 * @relates client_d3d12_compositor
82 */
83#define D3D_WARN(c, ...) U_LOG_IFL_W(c->log_level, __VA_ARGS__);
84
85/*!
86 * Error level logging.
87 *
88 * @relates client_d3d12_compositor
89 */
90#define D3D_ERROR(c, ...) U_LOG_IFL_E(c->log_level, __VA_ARGS__);
91
92using unique_compositor_semaphore_ref = std::unique_ptr<
93 struct xrt_compositor_semaphore,
94 xrt::deleters::reference_deleter<struct xrt_compositor_semaphore, xrt_compositor_semaphore_reference>>;
95
96using unique_swapchain_ref =
97 std::unique_ptr<struct xrt_swapchain,
98 xrt::deleters::reference_deleter<struct xrt_swapchain, xrt_swapchain_reference>>;
99
100// Timeout to wait for completion
101static constexpr auto kFenceTimeout = 500ms;
102
103/*!
104 * @class client_d3d12_compositor
105 *
106 * Wraps the real compositor providing a D3D12 based interface.
107 *
108 * @ingroup comp_client
109 * @implements xrt_compositor_d3d12
110 */
111struct client_d3d12_compositor
112{
113 struct xrt_compositor_d3d12 base = {};
114
115 //! Owning reference to the backing native compositor
116 struct xrt_compositor_native *xcn{nullptr};
117
118 //! Just keeps COM alive while we keep references to COM things.
119 xrt::auxiliary::util::ComGuard com_guard;
120
121 //! Logging level.
122 enum u_logging_level log_level;
123
124 //! Device we got from the app
125 wil::com_ptr<ID3D12Device> device;
126
127 //! Command queue for @ref device
128 wil::com_ptr<ID3D12CommandQueue> app_queue;
129
130 //! Command list allocator for the compositor
131 wil::com_ptr<ID3D12CommandAllocator> command_allocator;
132
133 /*!
134 * A timeline semaphore made by the native compositor and imported by us.
135 *
136 * When this is valid, we should use @ref xrt_compositor::layer_commit_with_semaphone:
137 * it means the native compositor knows about timeline semaphores, and we can import its semaphores, so we can
138 * pass @ref timeline_semaphore instead of blocking locally.
139 */
140 unique_compositor_semaphore_ref timeline_semaphore;
141
142 /*!
143 * A fence (timeline semaphore) object.
144 *
145 * Signal using @ref app_queue if this is not null.
146 *
147 * Wait on it in `layer_commit` if @ref timeline_semaphore *is* null/invalid.
148 */
149 wil::com_ptr<ID3D12Fence> fence;
150
151 /*!
152 * Event used for blocking in `layer_commit` if required (if @ref client_d3d12_compositor::timeline_semaphore
153 * *is* null/invalid)
154 */
155 wil::unique_event_nothrow local_wait_event;
156
157 /*!
158 * The value most recently signaled on the timeline semaphore
159 */
160 uint64_t timeline_semaphore_value = 0;
161};
162
163static_assert(std::is_standard_layout<client_d3d12_compositor>::value);
164
165struct client_d3d12_swapchain;
166
167static inline DWORD
168convertTimeoutToWindowsMilliseconds(int64_t timeout_ns)
169{
170 return (timeout_ns == XRT_INFINITE_DURATION) ? INFINITE : (DWORD)(timeout_ns / (int64_t)U_TIME_1MS_IN_NS);
171}
172
173static inline bool
174isPowerOfTwo(uint32_t n)
175{
176 return (n & (n - 1)) == 0;
177}
178
179static inline uint32_t
180nextPowerOfTwo(uint32_t n)
181{
182 uint32_t res;
183 for (res = 1; res < n; res *= 2)
184 ;
185 return res;
186}
187
188
189/*!
190 * Split out from @ref client_d3d12_swapchain to ensure that it is standard
191 * layout, std::vector for instance is not standard layout.
192 */
193struct client_d3d12_swapchain_data
194{
195 explicit client_d3d12_swapchain_data(enum u_logging_level log_level) {}
196
197 //! The shared handles for all our images
198 std::vector<wil::unique_handle> handles;
199
200 //! Images
201 std::vector<wil::com_ptr<ID3D12Resource>> images;
202
203 //! Images used by the application
204 std::vector<wil::com_ptr<ID3D12Resource>> app_images;
205
206 //! Command list per-image to put the resource in a state for acquire (@ref appResourceState) from @ref
207 //! compositorResourceState
208 std::vector<wil::com_ptr<ID3D12CommandList>> commandsToApp;
209
210 //! Command list per-image to put the resource in a state for composition (@ref compositorResourceState) from
211 //! @ref appResourceState
212 std::vector<wil::com_ptr<ID3D12CommandList>> commandsToCompositor;
213
214 //! State we hand over the image in, and expect it back in.
215 D3D12_RESOURCE_STATES appResourceState = D3D12_RESOURCE_STATE_RENDER_TARGET;
216
217 //! State the compositor wants the image in before use.
218 D3D12_RESOURCE_STATES compositorResourceState = D3D12_RESOURCE_STATE_COMMON;
219
220 std::vector<D3D12_RESOURCE_STATES> state;
221
222 /*!
223 * Optional app to compositor copy mechanism, used as a workaround for d3d12 -> Vulkan interop issues
224 */
225
226 //! Shared handles for compositor images
227 std::vector<wil::unique_handle> comp_handles;
228
229 //! Images used by the compositor
230 std::vector<wil::com_ptr<ID3D12Resource>> comp_images;
231
232 //! Command list per-image to copy from app image to compositor image
233 std::vector<wil::com_ptr<ID3D12CommandList>> comp_copy_commands;
234};
235
236/*!
237 * Wraps the real compositor swapchain providing a D3D12 based interface.
238 *
239 * @ingroup comp_client
240 * @implements xrt_swapchain_d3d12
241 */
242struct client_d3d12_swapchain
243{
244 struct xrt_swapchain_d3d12 base;
245
246 //! Owning reference to the imported swapchain.
247 unique_swapchain_ref xsc;
248
249 //! Non-owning reference to our parent compositor.
250 struct client_d3d12_compositor *c{nullptr};
251
252 //! UV coordinates scaling when translating from app to compositor image
253 xrt_vec2 comp_uv_scale = {1.0f, 1.0f};
254
255 //! implementation struct with things that aren't standard_layout
256 std::unique_ptr<client_d3d12_swapchain_data> data;
257};
258
259static_assert(std::is_standard_layout<client_d3d12_swapchain>::value);
260
261/*!
262 * Down-cast helper.
263 * @private @memberof client_d3d12_swapchain
264 */
265static inline struct client_d3d12_swapchain *
266as_client_d3d12_swapchain(struct xrt_swapchain *xsc)
267{
268 return reinterpret_cast<client_d3d12_swapchain *>(xsc);
269}
270
271/*!
272 * Down-cast helper.
273 * @private @memberof client_d3d12_compositor
274 */
275static inline struct client_d3d12_compositor *
276as_client_d3d12_compositor(struct xrt_compositor *xc)
277{
278 return (struct client_d3d12_compositor *)xc;
279}
280
281
282/*
283 *
284 * Logging helper.
285 *
286 */
287static constexpr size_t kErrorBufSize = 256;
288
289template <size_t N>
290static inline bool
291formatMessage(DWORD err, char (&buf)[N])
292{
293 if (0 != FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err,
294 LANG_SYSTEM_DEFAULT, buf, N - 1, NULL)) {
295 return true;
296 }
297 memset(buf, 0, N);
298 return false;
299}
300
301
302/*
303 *
304 * Helpers for Swapchain
305 *
306 */
307
308static xrt_result_t
309client_d3d12_swapchain_barrier_to_app(client_d3d12_swapchain *sc, uint32_t index)
310{
311 auto *data = sc->data.get();
312 if (data->commandsToApp.empty()) {
313 // We have decided not to use barriers here
314 return XRT_SUCCESS;
315 }
316 if (data->state[index] == data->appResourceState) {
317 D3D_INFO(sc->c, "Image %" PRId32 " is already in the right state", index);
318 return XRT_SUCCESS;
319 }
320 if (data->state[index] == data->compositorResourceState) {
321 D3D_INFO(sc->c, "Acquiring image %" PRId32, index);
322 std::array<ID3D12CommandList *, 1> commandLists{{data->commandsToApp[index].get()}};
323 sc->c->app_queue->ExecuteCommandLists(1, commandLists.data());
324 data->state[index] = data->appResourceState;
325 return XRT_SUCCESS;
326 }
327 D3D_WARN(sc->c, "Image %" PRId32 " is in an unknown state", index);
328 return XRT_ERROR_D3D12;
329}
330
331static xrt_result_t
332client_d3d12_swapchain_barrier_to_compositor(client_d3d12_swapchain *sc, uint32_t index)
333{
334 auto *data = sc->data.get();
335
336 if (data->commandsToCompositor.empty()) {
337 // We have decided not to use barriers here
338 return XRT_SUCCESS;
339 }
340
341 std::array<ID3D12CommandList *, 1> commandLists{{data->commandsToCompositor[index].get()}};
342 sc->c->app_queue->ExecuteCommandLists(1, commandLists.data());
343 data->state[index] = data->compositorResourceState;
344 return XRT_SUCCESS;
345}
346
347static void
348client_d3d12_swapchain_scale_rect(struct xrt_swapchain *xsc, xrt_normalized_rect *inOutRect)
349{
350 xrt_vec2 &uvScale = as_client_d3d12_swapchain(xsc)->comp_uv_scale;
351
352 inOutRect->x *= uvScale.x;
353 inOutRect->y *= uvScale.y;
354 inOutRect->w *= uvScale.x;
355 inOutRect->h *= uvScale.y;
356}
357
358
359/*
360 *
361 * Swapchain functions.
362 *
363 */
364
365static xrt_result_t
366client_d3d12_swapchain_acquire_image(struct xrt_swapchain *xsc, uint32_t *out_index)
367{
368 struct client_d3d12_swapchain *sc = as_client_d3d12_swapchain(xsc);
369
370 uint32_t index = 0;
371 // Pipe down call into imported swapchain in native compositor.
372 xrt_result_t xret = xrt_swapchain_acquire_image(sc->xsc.get(), &index);
373
374 if (xret == XRT_SUCCESS) {
375 // Set output variable
376 *out_index = index;
377 }
378 return xret;
379}
380
381static xrt_result_t
382client_d3d12_swapchain_wait_image(struct xrt_swapchain *xsc, int64_t timeout_ns, uint32_t index)
383{
384 struct client_d3d12_swapchain *sc = as_client_d3d12_swapchain(xsc);
385
386 // Pipe down call into imported swapchain in native compositor.
387 xrt_result_t xret = xrt_swapchain_wait_image(sc->xsc.get(), timeout_ns, index);
388
389 //! @todo discard old contents?
390 return xret;
391}
392
393static xrt_result_t
394client_d3d12_swapchain_barrier_image(struct xrt_swapchain *xsc, enum xrt_barrier_direction direction, uint32_t index)
395{
396 struct client_d3d12_swapchain *sc = as_client_d3d12_swapchain(xsc);
397 xrt_result_t xret;
398
399 switch (direction) {
400 case XRT_BARRIER_TO_APP: xret = client_d3d12_swapchain_barrier_to_app(sc, index); break;
401 case XRT_BARRIER_TO_COMP: xret = client_d3d12_swapchain_barrier_to_compositor(sc, index); break;
402 default: assert(false);
403 }
404
405 return xret;
406}
407
408static xrt_result_t
409client_d3d12_swapchain_release_image(struct xrt_swapchain *xsc, uint32_t index)
410{
411 struct client_d3d12_swapchain *sc = as_client_d3d12_swapchain(xsc);
412
413 // Pipe down call into imported swapchain in native compositor.
414 xrt_result_t xret = xrt_swapchain_release_image(sc->xsc.get(), index);
415
416 return xret;
417}
418
419static xrt_result_t
420client_d3d12_swapchain_release_image_copy(struct xrt_swapchain *xsc, uint32_t index)
421{
422 struct client_d3d12_swapchain *sc = as_client_d3d12_swapchain(xsc);
423
424 // Queue copy from app to compositor image
425 std::array<ID3D12CommandList *, 1> commandLists{sc->data->comp_copy_commands[index].get()};
426 sc->c->app_queue->ExecuteCommandLists((UINT)commandLists.size(), commandLists.data());
427
428 // Pipe down call into imported swapchain in native compositor.
429 xrt_result_t xret = xrt_swapchain_release_image(sc->xsc.get(), index);
430
431 return xret;
432}
433
434static void
435client_d3d12_swapchain_destroy(struct xrt_swapchain *xsc)
436{
437 /*
438 * Letting automatic destruction do it all, happens at the end of
439 * this function once the sc variable goes out of scope.
440 */
441 std::unique_ptr<client_d3d12_swapchain> sc(as_client_d3d12_swapchain(xsc));
442
443 // this swapchain resources may be in flight, wait till compositor finishes using them
444 struct client_d3d12_compositor *c = sc->c;
445 if (c && c->fence) {
446 c->timeline_semaphore_value++;
447 HRESULT hr = c->app_queue->Signal(c->fence.get(), c->timeline_semaphore_value);
448
449 xrt::auxiliary::d3d::d3d12::waitOnFenceWithTimeout( //
450 c->fence, //
451 c->local_wait_event, //
452 c->timeline_semaphore_value, //
453 kFenceTimeout); //
454 }
455}
456
457
458xrt_result_t
459client_d3d12_create_swapchain(struct xrt_compositor *xc,
460 const struct xrt_swapchain_create_info *info,
461 struct xrt_swapchain **out_xsc)
462try {
463 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
464 xrt_result_t xret = XRT_SUCCESS;
465 xrt_swapchain_create_properties xsccp{};
466 xret = xrt_comp_get_swapchain_create_properties(xc, info, &xsccp);
467
468 if (xret != XRT_SUCCESS) {
469 D3D_ERROR(c, "Could not get properties for creating swapchain");
470 return xret;
471 }
472 uint32_t image_count = xsccp.image_count;
473
474
475 if ((info->create & XRT_SWAPCHAIN_CREATE_PROTECTED_CONTENT) != 0) {
476 D3D_WARN(c,
477 "Swapchain info is valid but this compositor doesn't support creating protected content "
478 "swapchains!");
479 return XRT_ERROR_SWAPCHAIN_FLAG_VALID_BUT_UNSUPPORTED;
480 }
481
482 int64_t vk_format = d3d_dxgi_format_to_vk((DXGI_FORMAT)info->format);
483 if (vk_format == 0) {
484 D3D_ERROR(c, "Invalid format!");
485 return XRT_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED;
486 }
487
488 struct xrt_swapchain_create_info xinfo = *info;
489 struct xrt_swapchain_create_info vkinfo = *info;
490
491 // Update the create info.
492 xinfo.bits = (enum xrt_swapchain_usage_bits)(xsccp.extra_bits | xinfo.bits);
493 vkinfo.format = vk_format;
494 vkinfo.bits = (enum xrt_swapchain_usage_bits)(xsccp.extra_bits | vkinfo.bits);
495
496 std::unique_ptr<struct client_d3d12_swapchain> sc = std::make_unique<struct client_d3d12_swapchain>();
497 sc->data = std::make_unique<client_d3d12_swapchain_data>(c->log_level);
498 auto &data = sc->data;
499 std::uint64_t image_mem_size = 0;
500
501 // Allocate images
502 xret = xrt::auxiliary::d3d::d3d12::allocateSharedImages( //
503 *(c->device), //
504 xinfo, //
505 image_count, //
506 data->images, //
507 data->handles, //
508 image_mem_size); //
509 if (xret != XRT_SUCCESS) {
510 return xret;
511 }
512
513 data->app_images.reserve(image_count);
514
515 // Import from the handles for the app.
516 for (uint32_t i = 0; i < image_count; ++i) {
517 wil::com_ptr<ID3D12Resource> image =
518 xrt::auxiliary::d3d::d3d12::importImage(*(c->device), data->handles[i].get());
519
520 // Put the image where the OpenXR state tracker can get it
521 sc->base.images[i] = image.get();
522
523 // Store the owning pointer for lifetime management
524 data->app_images.emplace_back(std::move(image));
525 }
526
527 D3D12_RESOURCE_STATES appResourceState = d3d_convert_usage_bits_to_d3d12_app_resource_state(xinfo.bits);
528 /// @todo No idea if this is right, might depend on whether it's the compute or graphics compositor!
529 D3D12_RESOURCE_STATES compositorResourceState = D3D12_RESOURCE_STATE_COMMON;
530
531 data->appResourceState = appResourceState;
532 data->compositorResourceState = compositorResourceState;
533
534 data->state.resize(image_count, appResourceState);
535
536 if (debug_get_bool_option_barriers()) {
537 D3D_INFO(c, "Will use barriers at runtime");
538 data->commandsToApp.reserve(image_count);
539 data->commandsToCompositor.reserve(image_count);
540
541 // Make the command lists to transition images
542 for (uint32_t i = 0; i < image_count; ++i) {
543 wil::com_ptr<ID3D12CommandList> commandsToApp;
544 wil::com_ptr<ID3D12CommandList> commandsToCompositor;
545
546 D3D_INFO(c, "Creating command lists for image %" PRId32, i);
547 HRESULT hr = xrt::auxiliary::d3d::d3d12::createCommandLists( //
548 *(c->device), // device
549 *(c->command_allocator), // command_allocator
550 *(data->images[i]), // resource
551 xinfo.bits, // bits
552 commandsToApp, // out_acquire_command_list
553 commandsToCompositor); // out_release_command_list
554 if (!SUCCEEDED(hr)) {
555 char buf[kErrorBufSize];
556 formatMessage(hr, buf);
557 D3D_ERROR(c, "Error creating command list: %s", buf);
558 return XRT_ERROR_D3D12;
559 }
560
561 data->commandsToApp.emplace_back(std::move(commandsToApp));
562 data->commandsToCompositor.emplace_back(std::move(commandsToCompositor));
563 }
564 }
565
566
567 /*
568 * There is a bug in nvidia systems where D3D12 and Vulkan disagree on the memory layout
569 * of smaller images, this causes the native compositor to not display these swapchains
570 * correctly.
571 *
572 * The workaround for this is to create a second set of images for use in the native
573 * compositor and copy the contents from the app image into the compositor image every
574 * time the swapchain is released by the app.
575 *
576 * @todo: check if AMD and Intel platforms have this issue as well.
577 */
578 bool fixWidth = info->width < 256 && !isPowerOfTwo(info->width);
579 bool fixHeight = info->height < 256 && !isPowerOfTwo(info->height);
580 bool compositorNeedsCopy = debug_get_bool_option_compositor_copy() && (fixWidth || fixHeight);
581
582 if (compositorNeedsCopy) {
583 // These bits doesn't matter for D3D12, just set it to something.
584 xinfo.bits = XRT_SWAPCHAIN_USAGE_SAMPLED;
585
586 if (fixWidth) {
587 vkinfo.width = xinfo.width = nextPowerOfTwo(info->width);
588 }
589 if (fixHeight) {
590 vkinfo.height = xinfo.height = nextPowerOfTwo(info->height);
591 }
592
593 sc->comp_uv_scale = xrt_vec2{
594 (float)info->width / xinfo.width,
595 (float)info->height / xinfo.height,
596 };
597
598 // Allocate compositor images
599 xret = xrt::auxiliary::d3d::d3d12::allocateSharedImages( //
600 *(c->device), // device
601 xinfo, // xsci
602 image_count, // image_count
603 data->comp_images, // out_images
604 data->comp_handles, // out_handles
605 image_mem_size); // out_image_mem_size (in bytes)
606 if (xret != XRT_SUCCESS) {
607 return xret;
608 }
609
610 // Create copy command lists
611 for (uint32_t i = 0; i < image_count; ++i) {
612 wil::com_ptr<ID3D12CommandList> copyCommandList;
613
614 D3D_INFO(c, "Creating copy-to-compositor command list for image %" PRId32, i);
615 HRESULT hr = xrt::auxiliary::d3d::d3d12::createCommandListImageCopy( //
616 *(c->device), // device
617 *(c->command_allocator), // command_allocator
618 *(data->images[i]), // resource_src
619 *(data->comp_images[i]), // resource_dst
620 appResourceState, // src_resource_state
621 compositorResourceState, // dst_resource_state
622 copyCommandList); // out_copy_command_list
623 if (!SUCCEEDED(hr)) {
624 char buf[kErrorBufSize];
625 formatMessage(hr, buf);
626 D3D_ERROR(c, "Error creating command list: %s", buf);
627 return XRT_ERROR_D3D12;
628 }
629 data->comp_copy_commands.emplace_back(std::move(copyCommandList));
630 }
631 }
632
633 std::vector<wil::unique_handle> &handles = compositorNeedsCopy ? data->comp_handles : data->handles;
634
635 // Import into the native compositor, to create the corresponding swapchain which we wrap.
636 xret = xrt::compositor::client::importFromHandleDuplicates(*(c->xcn), handles, vkinfo, image_mem_size, true,
637 sc->xsc);
638 if (xret != XRT_SUCCESS) {
639 D3D_ERROR(c, "Error importing D3D swapchain into native compositor");
640 return xret;
641 }
642
643 // app_images do not inherit the initial state of images, so
644 // transition all app images from _COMMON to the correct state
645 {
646 D3D12_RESOURCE_BARRIER barrier{};
647 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
648 barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
649 barrier.Transition.StateAfter = appResourceState;
650 barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
651
652 data->state.resize(image_count, barrier.Transition.StateAfter);
653
654 std::vector<D3D12_RESOURCE_BARRIER> barriers;
655 for (const auto &image : data->app_images) {
656 barrier.Transition.pResource = image.get();
657 barriers.emplace_back(barrier);
658 }
659 wil::com_ptr<ID3D12GraphicsCommandList> commandList;
660 THROW_IF_FAILED(c->device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
661 c->command_allocator.get(), nullptr,
662 IID_PPV_ARGS(commandList.put())));
663 commandList->ResourceBarrier((UINT)barriers.size(), barriers.data());
664 commandList->Close();
665 std::array<ID3D12CommandList *, 1> commandLists{commandList.get()};
666
667 c->app_queue->ExecuteCommandLists((UINT)commandLists.size(), commandLists.data());
668 }
669
670 auto release_image_fn = compositorNeedsCopy //
671 ? client_d3d12_swapchain_release_image_copy
672 : client_d3d12_swapchain_release_image;
673
674 sc->base.base.destroy = client_d3d12_swapchain_destroy;
675 sc->base.base.acquire_image = client_d3d12_swapchain_acquire_image;
676 sc->base.base.wait_image = client_d3d12_swapchain_wait_image;
677 sc->base.base.barrier_image = client_d3d12_swapchain_barrier_image;
678 sc->base.base.release_image = release_image_fn;
679 sc->c = c;
680 sc->base.base.image_count = image_count;
681
682 xrt_swapchain_reference(out_xsc, &sc->base.base);
683 (void)sc.release();
684
685 return XRT_SUCCESS;
686
687} catch (wil::ResultException const &e) {
688 U_LOG_E("Error creating D3D12 swapchain: %s", e.what());
689 return XRT_ERROR_ALLOCATION;
690} catch (std::exception const &e) {
691 U_LOG_E("Error creating D3D12 swapchain: %s", e.what());
692 return XRT_ERROR_ALLOCATION;
693} catch (...) {
694 U_LOG_E("Error creating D3D12 swapchain");
695 return XRT_ERROR_ALLOCATION;
696}
697
698static xrt_result_t
699client_d3d12_compositor_passthrough_create(struct xrt_compositor *xc, const struct xrt_passthrough_create_info *info)
700{
701 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
702
703 // Pipe down call into native compositor.
704 return xrt_comp_create_passthrough(&c->xcn->base, info);
705}
706
707static xrt_result_t
708client_d3d12_compositor_passthrough_layer_create(struct xrt_compositor *xc,
709 const struct xrt_passthrough_layer_create_info *info)
710{
711 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
712
713 // Pipe down call into native compositor.
714 return xrt_comp_create_passthrough_layer(&c->xcn->base, info);
715}
716
717static xrt_result_t
718client_d3d12_compositor_passthrough_destroy(struct xrt_compositor *xc)
719{
720 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
721
722 // Pipe down call into native compositor.
723 return xrt_comp_destroy_passthrough(&c->xcn->base);
724}
725
726/*
727 *
728 * Compositor functions.
729 *
730 */
731
732static xrt_result_t
733client_d3d12_compositor_begin_session(struct xrt_compositor *xc, const struct xrt_begin_session_info *info)
734{
735 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
736
737 // Pipe down call into native compositor.
738 return xrt_comp_begin_session(&c->xcn->base, info);
739}
740
741static xrt_result_t
742client_d3d12_compositor_end_session(struct xrt_compositor *xc)
743{
744 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
745
746 // Pipe down call into native compositor.
747 return xrt_comp_end_session(&c->xcn->base);
748}
749
750static xrt_result_t
751client_d3d12_compositor_wait_frame(struct xrt_compositor *xc,
752 int64_t *out_frame_id,
753 int64_t *predicted_display_time,
754 int64_t *predicted_display_period)
755{
756 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
757
758 // Pipe down call into native compositor.
759 return xrt_comp_wait_frame(&c->xcn->base, out_frame_id, predicted_display_time, predicted_display_period);
760}
761
762static xrt_result_t
763client_d3d12_compositor_begin_frame(struct xrt_compositor *xc, int64_t frame_id)
764{
765 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
766
767 // Pipe down call into native compositor.
768 return xrt_comp_begin_frame(&c->xcn->base, frame_id);
769}
770
771static xrt_result_t
772client_d3d12_compositor_discard_frame(struct xrt_compositor *xc, int64_t frame_id)
773{
774 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
775
776 // Pipe down call into native compositor.
777 return xrt_comp_discard_frame(&c->xcn->base, frame_id);
778}
779
780static xrt_result_t
781client_d3d12_compositor_layer_begin(struct xrt_compositor *xc, const struct xrt_layer_frame_data *data)
782{
783 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
784
785 // Pipe down call into native compositor.
786 return xrt_comp_layer_begin(&c->xcn->base, data);
787}
788
789static xrt_result_t
790client_d3d12_compositor_layer_projection(struct xrt_compositor *xc,
791 struct xrt_device *xdev,
792 struct xrt_swapchain *xsc[XRT_MAX_VIEWS],
793 const struct xrt_layer_data *data)
794{
795 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
796
797 assert(data->type == XRT_LAYER_PROJECTION);
798
799 struct xrt_swapchain *xscn[XRT_MAX_VIEWS];
800 for (uint32_t i = 0; i < data->view_count; ++i) {
801 xscn[i] = as_client_d3d12_swapchain(xsc[i])->xsc.get();
802 }
803 struct xrt_layer_data d = *data;
804
805 // Scale to compensate for power-of-two texture sizes.
806 for (uint32_t i = 0; i < data->view_count; ++i) {
807 client_d3d12_swapchain_scale_rect(xsc[i], &d.proj.v[i].sub.norm_rect);
808 }
809 // No flip required: D3D12 swapchain image convention matches Vulkan.
810 return xrt_comp_layer_projection(&c->xcn->base, xdev, xscn, &d);
811}
812
813static xrt_result_t
814client_d3d12_compositor_layer_projection_depth(struct xrt_compositor *xc,
815 struct xrt_device *xdev,
816 struct xrt_swapchain *xsc[XRT_MAX_VIEWS],
817 struct xrt_swapchain *d_xsc[XRT_MAX_VIEWS],
818 const struct xrt_layer_data *data)
819{
820 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
821
822 assert(data->type == XRT_LAYER_PROJECTION_DEPTH);
823
824 struct xrt_swapchain *xscn[XRT_MAX_VIEWS];
825 struct xrt_swapchain *d_xscn[XRT_MAX_VIEWS];
826 for (uint32_t i = 0; i < data->view_count; ++i) {
827 xscn[i] = as_client_d3d12_swapchain(xsc[i])->xsc.get();
828 d_xscn[i] = as_client_d3d12_swapchain(d_xsc[i])->xsc.get();
829 }
830
831 struct xrt_layer_data d = *data;
832 for (uint32_t i = 0; i < data->view_count; ++i) {
833 client_d3d12_swapchain_scale_rect(xsc[i], &d.depth.v[i].sub.norm_rect);
834 client_d3d12_swapchain_scale_rect(d_xsc[i], &d.depth.d[i].sub.norm_rect);
835 }
836 // No flip required: D3D12 swapchain image convention matches Vulkan.
837 return xrt_comp_layer_projection_depth(&c->xcn->base, xdev, xscn, d_xscn, &d);
838}
839
840static xrt_result_t
841client_d3d12_compositor_layer_quad(struct xrt_compositor *xc,
842 struct xrt_device *xdev,
843 struct xrt_swapchain *xsc,
844 const struct xrt_layer_data *data)
845{
846 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
847
848 assert(data->type == XRT_LAYER_QUAD);
849
850 struct xrt_swapchain *xscfb = as_client_d3d12_swapchain(xsc)->xsc.get();
851
852 struct xrt_layer_data d = *data;
853 client_d3d12_swapchain_scale_rect(xsc, &d.quad.sub.norm_rect);
854
855 // No flip required: D3D12 swapchain image convention matches Vulkan.
856 return xrt_comp_layer_quad(&c->xcn->base, xdev, xscfb, &d);
857}
858
859static xrt_result_t
860client_d3d12_compositor_layer_cube(struct xrt_compositor *xc,
861 struct xrt_device *xdev,
862 struct xrt_swapchain *xsc,
863 const struct xrt_layer_data *data)
864{
865 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
866
867 assert(data->type == XRT_LAYER_CUBE);
868
869 struct xrt_swapchain *xscfb = as_client_d3d12_swapchain(xsc)->xsc.get();
870
871 struct xrt_layer_data d = *data;
872 client_d3d12_swapchain_scale_rect(xsc, &d.cube.sub.norm_rect);
873
874 // No flip required: D3D12 swapchain image convention matches Vulkan.
875 return xrt_comp_layer_cube(&c->xcn->base, xdev, xscfb, &d);
876}
877
878static xrt_result_t
879client_d3d12_compositor_layer_cylinder(struct xrt_compositor *xc,
880 struct xrt_device *xdev,
881 struct xrt_swapchain *xsc,
882 const struct xrt_layer_data *data)
883{
884 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
885
886 assert(data->type == XRT_LAYER_CYLINDER);
887
888 struct xrt_swapchain *xscfb = as_client_d3d12_swapchain(xsc)->xsc.get();
889
890 struct xrt_layer_data d = *data;
891 client_d3d12_swapchain_scale_rect(xsc, &d.cylinder.sub.norm_rect);
892
893 // No flip required: D3D12 swapchain image convention matches Vulkan.
894 return xrt_comp_layer_cylinder(&c->xcn->base, xdev, xscfb, &d);
895}
896
897static xrt_result_t
898client_d3d12_compositor_layer_equirect1(struct xrt_compositor *xc,
899 struct xrt_device *xdev,
900 struct xrt_swapchain *xsc,
901 const struct xrt_layer_data *data)
902{
903 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
904
905 assert(data->type == XRT_LAYER_EQUIRECT1);
906
907 struct xrt_swapchain *xscfb = as_client_d3d12_swapchain(xsc)->xsc.get();
908
909 struct xrt_layer_data d = *data;
910 client_d3d12_swapchain_scale_rect(xsc, &d.equirect1.sub.norm_rect);
911
912 // No flip required: D3D12 swapchain image convention matches Vulkan.
913 return xrt_comp_layer_equirect1(&c->xcn->base, xdev, xscfb, &d);
914}
915
916static xrt_result_t
917client_d3d12_compositor_layer_equirect2(struct xrt_compositor *xc,
918 struct xrt_device *xdev,
919 struct xrt_swapchain *xsc,
920 const struct xrt_layer_data *data)
921{
922 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
923
924 assert(data->type == XRT_LAYER_EQUIRECT2);
925
926 struct xrt_swapchain *xscfb = as_client_d3d12_swapchain(xsc)->xsc.get();
927
928 struct xrt_layer_data d = *data;
929 client_d3d12_swapchain_scale_rect(xsc, &d.equirect2.sub.norm_rect);
930
931 // No flip required: D3D12 swapchain image convention matches Vulkan.
932 return xrt_comp_layer_equirect2(&c->xcn->base, xdev, xscfb, &d);
933}
934
935static xrt_result_t
936client_d3d12_compositor_layer_passthrough(struct xrt_compositor *xc,
937 struct xrt_device *xdev,
938 const struct xrt_layer_data *data)
939{
940 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
941
942 assert(data->type == XRT_LAYER_PASSTHROUGH);
943
944 // No flip required: D3D12 swapchain image convention matches Vulkan.
945 return xrt_comp_layer_passthrough(&c->xcn->base, xdev, data);
946}
947
948static xrt_result_t
949client_d3d12_compositor_layer_commit(struct xrt_compositor *xc, xrt_graphics_sync_handle_t sync_handle)
950{
951 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
952
953 // We make the sync object, not st/oxr which is our user.
954 assert(!xrt_graphics_sync_handle_is_valid(sync_handle));
955
956 xrt_result_t xret = XRT_SUCCESS;
957 if (c->fence) {
958 c->timeline_semaphore_value++;
959 HRESULT hr = c->app_queue->Signal(c->fence.get(), c->timeline_semaphore_value);
960 if (!SUCCEEDED(hr)) {
961 char buf[kErrorBufSize];
962 formatMessage(hr, buf);
963 D3D_ERROR(c, "Error signaling fence: %s", buf);
964 return xrt_comp_layer_commit(&c->xcn->base, XRT_GRAPHICS_SYNC_HANDLE_INVALID);
965 }
966 }
967 if (c->timeline_semaphore) {
968 // We got this from the native compositor, so we can pass it back
969 return xrt_comp_layer_commit_with_semaphore( //
970 &c->xcn->base, //
971 c->timeline_semaphore.get(), //
972 c->timeline_semaphore_value); //
973 }
974
975 if (c->fence) {
976 // Wait on it ourselves, if we have it and didn't tell the native compositor to wait on it.
977 xret = xrt::auxiliary::d3d::d3d12::waitOnFenceWithTimeout( //
978 c->fence, //
979 c->local_wait_event, //
980 c->timeline_semaphore_value, //
981 kFenceTimeout); //
982 if (xret != XRT_SUCCESS) {
983 struct u_pp_sink_stack_only sink; // Not inited, very large.
984 u_pp_delegate_t dg = u_pp_sink_stack_only_init(&sink);
985 u_pp(dg, "Problem waiting on fence: ");
986 u_pp_xrt_result(dg, xret);
987 D3D_ERROR(c, "%s", sink.buffer);
988
989 return xret;
990 }
991 }
992
993 return xrt_comp_layer_commit(&c->xcn->base, XRT_GRAPHICS_SYNC_HANDLE_INVALID);
994}
995
996
997static xrt_result_t
998client_d3d12_compositor_get_swapchain_create_properties(struct xrt_compositor *xc,
999 const struct xrt_swapchain_create_info *info,
1000 struct xrt_swapchain_create_properties *xsccp)
1001{
1002 struct client_d3d12_compositor *c = as_client_d3d12_compositor(xc);
1003
1004 int64_t vk_format = d3d_dxgi_format_to_vk((DXGI_FORMAT)info->format);
1005 if (vk_format == 0) {
1006 D3D_ERROR(c, "Invalid format!");
1007 return XRT_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED;
1008 }
1009
1010 struct xrt_swapchain_create_info xinfo = *info;
1011 xinfo.format = vk_format;
1012
1013 return xrt_comp_get_swapchain_create_properties(&c->xcn->base, &xinfo, xsccp);
1014}
1015
1016static void
1017client_d3d12_compositor_destroy(struct xrt_compositor *xc)
1018{
1019 std::unique_ptr<struct client_d3d12_compositor> c{as_client_d3d12_compositor(xc)};
1020}
1021
1022static void
1023client_d3d12_compositor_init_try_timeline_semaphores(struct client_d3d12_compositor *c)
1024{
1025 struct xrt_compositor_semaphore *xcsem{nullptr};
1026 HANDLE timeline_semaphore_handle_raw{};
1027 xrt_result_t xret;
1028
1029 // Set the value to something non-zero.
1030 c->timeline_semaphore_value = 1;
1031
1032 // See if we can make a "timeline semaphore", also known as ID3D12Fence
1033 if (!c->xcn->base.create_semaphore || !c->xcn->base.layer_commit_with_semaphore) {
1034 return;
1035 }
1036
1037 /*
1038 * This call returns a HANDLE in the out_handle argument, it is owned by
1039 * the returned xrt_compositor_semaphore object we should not track it.
1040 */
1041 xret = xrt_comp_create_semaphore( //
1042 &(c->xcn->base), // xc
1043 &timeline_semaphore_handle_raw, // out_handle
1044 &xcsem); // out_xcsem
1045 if (xret != XRT_SUCCESS) {
1046 D3D_WARN(c, "Native compositor tried but failed to created a timeline semaphore for us.");
1047 return;
1048 }
1049 D3D_INFO(c, "Native compositor created a timeline semaphore for us.");
1050
1051 // Because importFence throws on failure we use this ref.
1052 unique_compositor_semaphore_ref timeline_semaphore{xcsem};
1053
1054 // Try to import, importFence throws on failure.
1055 wil::com_ptr<ID3D12Fence1> fence = xrt::auxiliary::d3d::d3d12::importFence( //
1056 *(c->device), //
1057 timeline_semaphore_handle_raw); //
1058
1059 // Check flags.
1060 D3D12_FENCE_FLAGS flags = fence->GetCreationFlags();
1061 if (flags & D3D12_FENCE_FLAG_NON_MONITORED) {
1062 D3D_WARN(c,
1063 "Your graphics driver creates the native compositor's semaphores as 'non-monitored' making "
1064 "them unusable in D3D12, falling back to local blocking.");
1065 return;
1066 }
1067
1068 // Check if we can signal it.
1069 HRESULT hr = fence->Signal(c->timeline_semaphore_value);
1070 if (!SUCCEEDED(hr)) {
1071 D3D_WARN(c,
1072 "Your graphics driver does not support importing the native compositor's "
1073 "semaphores into D3D12, falling back to local blocking.");
1074 return;
1075 }
1076
1077 D3D_INFO(c, "We imported a timeline semaphore and can signal it.");
1078
1079 // OK, keep these resources around.
1080 c->fence = std::move(fence);
1081 c->timeline_semaphore = std::move(timeline_semaphore);
1082}
1083
1084static void
1085client_d3d12_compositor_init_try_internal_blocking(struct client_d3d12_compositor *c)
1086{
1087 wil::com_ptr<ID3D12Fence> fence;
1088 HRESULT hr = c->device->CreateFence( //
1089 0, // InitialValue
1090 D3D12_FENCE_FLAG_NONE, // Flags
1091 __uuidof(ID3D12Fence), // ReturnedInterface
1092 fence.put_void()); // ppFence
1093
1094 if (!SUCCEEDED(hr)) {
1095 char buf[kErrorBufSize];
1096 formatMessage(hr, buf);
1097 D3D_WARN(c, "Cannot even create an ID3D12Fence for internal use: %s", buf);
1098 return;
1099 }
1100
1101 hr = c->local_wait_event.create();
1102 if (!SUCCEEDED(hr)) {
1103 char buf[kErrorBufSize];
1104 formatMessage(hr, buf);
1105 D3D_ERROR(c, "Error creating event for synchronization usage: %s", buf);
1106 return;
1107 }
1108
1109 D3D_INFO(c, "We created our own ID3D12Fence and will wait on it ourselves.");
1110 c->fence = std::move(fence);
1111}
1112
1113struct xrt_compositor_d3d12 *
1114client_d3d12_compositor_create(struct xrt_compositor_native *xcn, ID3D12Device *device, ID3D12CommandQueue *queue)
1115try {
1116 std::unique_ptr<struct client_d3d12_compositor> c = std::make_unique<struct client_d3d12_compositor>();
1117 c->log_level = debug_get_log_option_log();
1118 c->xcn = xcn;
1119
1120 c->device = device;
1121 c->app_queue = queue;
1122
1123 HRESULT hr =
1124 c->device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(c->command_allocator.put()));
1125 if (!SUCCEEDED(hr)) {
1126 char buf[kErrorBufSize];
1127 formatMessage(hr, buf);
1128 D3D_ERROR(c, "Error creating command allocator: %s", buf);
1129 return nullptr;
1130 }
1131
1132
1133 // See if we can make a "timeline semaphore", also known as ID3D12Fence
1134 client_d3d12_compositor_init_try_timeline_semaphores(c.get());
1135 if (!c->timeline_semaphore) {
1136 // OK native compositor doesn't know how to handle timeline semaphores, or we can't import them, but we
1137 // can still use them entirely internally.
1138 client_d3d12_compositor_init_try_internal_blocking(c.get());
1139 }
1140 if (!c->fence) {
1141 D3D_WARN(c, "No sync mechanism for D3D12 was successful!");
1142 }
1143 c->base.base.get_swapchain_create_properties = client_d3d12_compositor_get_swapchain_create_properties;
1144 c->base.base.create_swapchain = client_d3d12_create_swapchain;
1145 c->base.base.create_passthrough = client_d3d12_compositor_passthrough_create;
1146 c->base.base.create_passthrough_layer = client_d3d12_compositor_passthrough_layer_create;
1147 c->base.base.destroy_passthrough = client_d3d12_compositor_passthrough_destroy;
1148 c->base.base.begin_session = client_d3d12_compositor_begin_session;
1149 c->base.base.end_session = client_d3d12_compositor_end_session;
1150 c->base.base.wait_frame = client_d3d12_compositor_wait_frame;
1151 c->base.base.begin_frame = client_d3d12_compositor_begin_frame;
1152 c->base.base.discard_frame = client_d3d12_compositor_discard_frame;
1153 c->base.base.layer_begin = client_d3d12_compositor_layer_begin;
1154 c->base.base.layer_projection = client_d3d12_compositor_layer_projection;
1155 c->base.base.layer_projection_depth = client_d3d12_compositor_layer_projection_depth;
1156 c->base.base.layer_quad = client_d3d12_compositor_layer_quad;
1157 c->base.base.layer_cube = client_d3d12_compositor_layer_cube;
1158 c->base.base.layer_cylinder = client_d3d12_compositor_layer_cylinder;
1159 c->base.base.layer_equirect1 = client_d3d12_compositor_layer_equirect1;
1160 c->base.base.layer_equirect2 = client_d3d12_compositor_layer_equirect2;
1161 c->base.base.layer_passthrough = client_d3d12_compositor_layer_passthrough;
1162 c->base.base.layer_commit = client_d3d12_compositor_layer_commit;
1163 c->base.base.destroy = client_d3d12_compositor_destroy;
1164
1165
1166 // Passthrough our formats from the native compositor to the client.
1167 uint32_t count = 0;
1168 for (uint32_t i = 0; i < xcn->base.info.format_count; i++) {
1169 // Can we turn this format into DXGI?
1170 DXGI_FORMAT f = d3d_vk_format_to_dxgi(xcn->base.info.formats[i]);
1171 if (f == 0) {
1172 continue;
1173 }
1174 // And back to Vulkan?
1175 auto v = d3d_dxgi_format_to_vk(f);
1176 if (v == 0) {
1177 continue;
1178 }
1179 // Do we have a typeless version of it?
1180 DXGI_FORMAT typeless = d3d_dxgi_format_to_typeless_dxgi(f);
1181 if (typeless == f) {
1182 continue;
1183 }
1184 c->base.base.info.formats[count++] = f;
1185 }
1186 c->base.base.info.format_count = count;
1187
1188 return &(c.release()->base);
1189} catch (wil::ResultException const &e) {
1190 U_LOG_E("Error creating D3D12 client compositor: %s", e.what());
1191 return nullptr;
1192} catch (std::exception const &e) {
1193 U_LOG_E("Error creating D3D12 client compositor: %s", e.what());
1194 return nullptr;
1195} catch (...) {
1196 U_LOG_E("Error creating D3D12 client compositor");
1197 return nullptr;
1198}