From 83df6ce27c8337cb1d85d8550dc03c17c188cbb2 Mon Sep 17 00:00:00 2001 From: Alvin Wong Date: Wed, 31 Jul 2024 03:27:08 +0800 Subject: [PATCH] D3D12: Use waitable swap chain --- .../d3d12/rendering_device_driver_d3d12.cpp | 51 +++++++++++++++++++ drivers/d3d12/rendering_device_driver_d3d12.h | 3 ++ platform/windows/display_server_windows.cpp | 6 +++ servers/rendering/rendering_device.cpp | 9 ++++ servers/rendering/rendering_device.h | 1 + servers/rendering/rendering_device_driver.h | 3 ++ 6 files changed, 73 insertions(+) diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index 122585e5953..efe0066c6d3 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/io/marshalls.h" +#include "main/main.h" #include "servers/rendering/rendering_device.h" #include "thirdparty/zlib/zlib.h" @@ -2294,11 +2295,32 @@ Error RenderingDeviceDriverD3D12::command_queue_execute_and_present(CommandQueue bool any_present_failed = false; for (uint32_t i = 0; i < p_swap_chains.size(); i++) { SwapChain *swap_chain = (SwapChain *)(p_swap_chains[i].id); + bool skip_present = false; + if (swap_chain->needs_wait) { + DWORD wait = WaitForSingleObject(swap_chain->frame_latency_waitable_obj, 0); + if (wait != WAIT_OBJECT_0) { + skip_present = true; + if (wait == WAIT_TIMEOUT) { + // Force a redraw so that we can present the latest frame + // for this swap chain on the next redraw. + Main::force_redraw(); + } else if (wait == WAIT_FAILED) { + DWORD error = GetLastError(); + ERR_PRINT(vformat("Wait for frame latency waitable failed with error: 0x%08X", (unsigned)error)); + } else { + ERR_PRINT(vformat("Wait for frame latency waitable failed, WaitForSingleObject returned 0x%08X", (unsigned)wait)); + } + } + } + if (skip_present) { + continue; + } res = swap_chain->d3d_swap_chain->Present(swap_chain->sync_interval, swap_chain->present_flags); if (!SUCCEEDED(res)) { print_verbose(vformat("D3D12: Presenting swapchain failed with error 0x%08ux.", (uint64_t)res)); any_present_failed = true; } + swap_chain->needs_wait = true; } return any_present_failed ? FAILED : OK; @@ -2412,6 +2434,10 @@ void RenderingDeviceDriverD3D12::command_buffer_execute_secondary(CommandBufferI void RenderingDeviceDriverD3D12::_swap_chain_release(SwapChain *p_swap_chain) { _swap_chain_release_buffers(p_swap_chain); + CloseHandle(p_swap_chain->frame_latency_waitable_obj); + p_swap_chain->frame_latency_waitable_obj = nullptr; + p_swap_chain->needs_wait = false; + p_swap_chain->d3d_swap_chain.Reset(); } @@ -2493,6 +2519,8 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue, break; } + creation_flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + print_verbose("Using swap chain flags: " + itos(creation_flags) + ", sync interval: " + itos(sync_interval) + ", present flags: " + itos(present_flags)); if (swap_chain->d3d_swap_chain != nullptr && creation_flags != swap_chain->creation_flags) { @@ -2535,6 +2563,9 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue, res = context_driver->dxgi_factory_get()->MakeWindowAssociation(surface->hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES); ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE); + + swap_chain->frame_latency_waitable_obj = swap_chain->d3d_swap_chain->GetFrameLatencyWaitableObject(); + swap_chain->needs_wait = true; } res = swap_chain->d3d_swap_chain->GetDesc1(&swap_chain_desc); @@ -2555,6 +2586,8 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue, swap_chain->render_targets_info.reserve(swap_chain_desc.BufferCount); swap_chain->framebuffers.reserve(swap_chain_desc.BufferCount); + swap_chain->d3d_swap_chain->SetMaximumFrameLatency(swap_chain_desc.BufferCount - 1); + for (uint32_t i = 0; i < swap_chain_desc.BufferCount; i++) { // Retrieve the resource corresponding to the swap chain's buffer. ID3D12Resource *render_target = nullptr; @@ -2623,6 +2656,24 @@ void RenderingDeviceDriverD3D12::swap_chain_free(SwapChainID p_swap_chain) { memdelete(swap_chain); } +void RenderingDeviceDriverD3D12::wait_for_present(SwapChainID p_swap_chain) { + SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id); + + if (swap_chain->needs_wait) { + DWORD wait = WaitForSingleObject(swap_chain->frame_latency_waitable_obj, 1000); + if (wait != WAIT_OBJECT_0) { + if (wait == WAIT_FAILED) { + DWORD error = GetLastError(); + ERR_PRINT(vformat("Wait for frame latency waitable failed with error: 0x%08X", (unsigned)error)); + } else { + ERR_PRINT(vformat("Wait for frame latency waitable failed, WaitForSingleObject returned 0x%08X", (unsigned)wait)); + } + } else { + swap_chain->needs_wait = false; + } + } +} + /*********************/ /**** FRAMEBUFFER ****/ /*********************/ diff --git a/drivers/d3d12/rendering_device_driver_d3d12.h b/drivers/d3d12/rendering_device_driver_d3d12.h index 61b1498755f..44966667dcd 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.h +++ b/drivers/d3d12/rendering_device_driver_d3d12.h @@ -512,6 +512,8 @@ private: TightLocalVector render_targets_info; TightLocalVector framebuffers; RDD::DataFormat data_format = DATA_FORMAT_MAX; + HANDLE frame_latency_waitable_obj = nullptr; + bool needs_wait = false; }; void _swap_chain_release(SwapChain *p_swap_chain); @@ -524,6 +526,7 @@ public: virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override; virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override; virtual void swap_chain_free(SwapChainID p_swap_chain) override; + virtual void wait_for_present(SwapChainID p_swap_chain) override; /*********************/ /**** FRAMEBUFFER ****/ diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index fa6ed81793f..6b3b5974b55 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3003,6 +3003,12 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const { void DisplayServerWindows::process_events() { ERR_FAIL_COND(!Thread::is_main_thread()); +#ifdef RD_ENABLED + if (rendering_device) { + rendering_device->screen_wait_for_present(get_focused_window() == INVALID_WINDOW_ID ? MAIN_WINDOW_ID : get_focused_window()); + } +#endif + if (!drop_events) { joypad->process_joypads(); } diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 22044a8c2d1..c275fcdc0e1 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -3544,6 +3544,15 @@ Error RenderingDevice::screen_free(DisplayServer::WindowID p_screen) { return OK; } +void RenderingDevice::screen_wait_for_present(DisplayServer::WindowID p_screen) { + _THREAD_SAFE_METHOD_ + + HashMap::ConstIterator it = screen_swap_chains.find(p_screen); + ERR_FAIL_COND_MSG(it == screen_swap_chains.end(), "A swap chain was not created for the screen."); + + driver->wait_for_present(it->value); +} + /*******************/ /**** DRAW LIST ****/ /*******************/ diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index 38ffd5d23de..a9603d1e1ad 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -1054,6 +1054,7 @@ public: int screen_get_height(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const; FramebufferFormatID screen_get_framebuffer_format(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const; Error screen_free(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID); + void screen_wait_for_present(DisplayServer::WindowID p_screen); /*************************/ /**** DRAW LISTS (II) ****/ diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h index 0b5fc51a1d8..81471828ba9 100644 --- a/servers/rendering/rendering_device_driver.h +++ b/servers/rendering/rendering_device_driver.h @@ -459,6 +459,9 @@ public: // Wait until all rendering associated to the swap chain is finished before deleting it. virtual void swap_chain_free(SwapChainID p_swap_chain) = 0; + // Wait for the swap chain to be presented on the screen. + virtual void wait_for_present(SwapChainID p_swap_chain){}; + /*********************/ /**** FRAMEBUFFER ****/ /*********************/