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