The open source OpenXR runtime
at main 1198 lines 41 kB view raw
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}