The open source OpenXR runtime
at mr/scanout-values 1185 lines 41 kB view raw
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}