From ce0c4e0a8740fcfd401ed43e73227e5092d9694e Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 24 Oct 2026 10:13:24 +0800 Subject: [PATCH 1/2] D3D12: fix IFence::Wait() returning before the fence reaches the value Co-Authored-By: Codex Co-Authored-By: Claude Opus 5.5 (1M context) --- .../include/FenceD3D12Impl.hpp | 2 - .../src/CommandQueueD3D12Impl.cpp | 2 +- .../src/FenceD3D12Impl.cpp | 27 ++++-------- Tests/DiligentCoreAPITest/src/FenceTest.cpp | 41 +++++++++++++++++++ 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/Graphics/GraphicsEngineD3D12/include/FenceD3D12Impl.hpp b/Graphics/GraphicsEngineD3D12/include/FenceD3D12Impl.hpp index dbe4293e48..d1ddc2a032 100644 --- a/Graphics/GraphicsEngineD3D12/include/FenceD3D12Impl.hpp +++ b/Graphics/GraphicsEngineD3D12/include/FenceD3D12Impl.hpp @@ -64,8 +64,6 @@ class FenceD3D12Impl final : public FenceBase private: /// Access to the fence internal data is thread safe. CComPtr m_pd3d12Fence; ///< D3D12 Fence object - - const HANDLE m_FenceCompleteEvent; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineD3D12/src/CommandQueueD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/CommandQueueD3D12Impl.cpp index b877e9d5e5..3efc42812a 100644 --- a/Graphics/GraphicsEngineD3D12/src/CommandQueueD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/CommandQueueD3D12Impl.cpp @@ -49,7 +49,7 @@ CommandQueueD3D12Impl::CommandQueueD3D12Impl(IReferenceCounters* pRefCounters, m_WaitForGPUEventHandle{CreateEvent(nullptr, false, false, nullptr)} // clang-format on { - VERIFY_EXPR(m_WaitForGPUEventHandle != INVALID_HANDLE_VALUE); + VERIFY_EXPR(m_WaitForGPUEventHandle != nullptr); m_d3d12Fence->Signal(0); } diff --git a/Graphics/GraphicsEngineD3D12/src/FenceD3D12Impl.cpp b/Graphics/GraphicsEngineD3D12/src/FenceD3D12Impl.cpp index a6c3e09df4..dfcfc97e97 100644 --- a/Graphics/GraphicsEngineD3D12/src/FenceD3D12Impl.cpp +++ b/Graphics/GraphicsEngineD3D12/src/FenceD3D12Impl.cpp @@ -44,17 +44,8 @@ namespace Diligent FenceD3D12Impl::FenceD3D12Impl(IReferenceCounters* pRefCounters, RenderDeviceD3D12Impl* pDevice, const FenceDesc& Desc) : - TFenceBase{pRefCounters, pDevice, Desc}, - m_FenceCompleteEvent // - { - CreateEvent(NULL, // default security attributes - TRUE, // manual-reset event - FALSE, // initial state is nonsignaled - NULL) // object name - } + TFenceBase{pRefCounters, pDevice, Desc} { - VERIFY(m_FenceCompleteEvent != NULL, "Failed to create fence complete event"); - const D3D12_FENCE_FLAGS Flags = (m_Desc.Type == FENCE_TYPE_GENERAL && pDevice->GetNumImmediateContexts() > 1) ? D3D12_FENCE_FLAG_SHARED : D3D12_FENCE_FLAG_NONE; ID3D12Device* const pd3d12Device = pDevice->GetD3D12Device(); HRESULT hr = pd3d12Device->CreateFence(0, Flags, __uuidof(m_pd3d12Fence), reinterpret_cast(static_cast(&m_pd3d12Fence))); @@ -67,10 +58,6 @@ FenceD3D12Impl::~FenceD3D12Impl() { // D3D12 object can only be destroyed when it is no longer used by the GPU GetDevice()->SafeReleaseDeviceObject(std::move(m_pd3d12Fence), ~0ull); - if (m_FenceCompleteEvent != NULL) - { - CloseHandle(m_FenceCompleteEvent); - } } Uint64 FenceD3D12Impl::GetCompletedValue() @@ -94,13 +81,13 @@ void FenceD3D12Impl::Wait(Uint64 Value) if (GetCompletedValue() >= Value) return; - if (m_FenceCompleteEvent != NULL) - { - m_pd3d12Fence->SetEventOnCompletion(Value, m_FenceCompleteEvent); - WaitForSingleObject(m_FenceCompleteEvent, INFINITE); - } - else + // A null event blocks this caller until Value completes and supports concurrent waits. + // Reusing a manual-reset event would allow later waits to return prematurely. + const HRESULT hr = m_pd3d12Fence->SetEventOnCompletion(Value, nullptr); + if (FAILED(hr)) { + // Wait() must not return before the fence reaches the value, so fall back to polling. + LOG_D3D_ERROR(hr, FormatString("Failed to wait for fence '", m_Desc.Name, "' to reach value ", Value, ". Falling back to polling.")); while (GetCompletedValue() < Value) std::this_thread::sleep_for(std::chrono::microseconds{1}); } diff --git a/Tests/DiligentCoreAPITest/src/FenceTest.cpp b/Tests/DiligentCoreAPITest/src/FenceTest.cpp index f71f45134f..cabdf3d1bd 100644 --- a/Tests/DiligentCoreAPITest/src/FenceTest.cpp +++ b/Tests/DiligentCoreAPITest/src/FenceTest.cpp @@ -24,6 +24,9 @@ * of the possibility of such damages. */ +#include +#include + #include "GPUTestingEnvironment.hpp" #include "TestingSwapChainBase.hpp" #include "BasicMath.hpp" @@ -327,4 +330,42 @@ TEST_F(FenceTest, ContextWaitForAnotherContext) pSwapChain->Present(); } + +TEST_F(FenceTest, RepeatedCPUWait) +{ + auto* pEnv = GPUTestingEnvironment::GetInstance(); + auto* pDevice = pEnv->GetDevice(); + if (!pDevice->GetDeviceInfo().Features.NativeFence) + { + GTEST_SKIP() << "NativeFence feature is not supported"; + } + + FenceDesc FenceCI; + FenceCI.Name = "Repeated CPU wait"; + FenceCI.Type = FENCE_TYPE_GENERAL; + + RefCntAutoPtr pFence; + pDevice->CreateFence(FenceCI, &pFence); + ASSERT_NE(pFence, nullptr); + + // Gives Wait() time to start blocking before the fence is signaled + static constexpr std::chrono::milliseconds SignalDelay{20}; + // Every wait after the first blocking one must also block until its own value is reached + static constexpr Uint64 NumWaits = 4; + + for (Uint64 Value = 1; Value <= NumWaits; ++Value) + { + std::thread SignalThread{ + [&pFence, Value]() { + std::this_thread::sleep_for(SignalDelay); + pFence->Signal(Value); + }}; + + pFence->Wait(Value); + EXPECT_GE(pFence->GetCompletedValue(), Value) << "Wait() returned before the fence reached the value"; + + SignalThread.join(); + } +} + } // namespace From 530c74251c721d29596d0908c392f5a5b841a354 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 24 Oct 2026 15:16:37 +0800 Subject: [PATCH 2/2] D3D11/D3D12: close DXGI frame-latency waitable object handles Co-Authored-By: Codex Co-Authored-By: Claude Opus 5.5 (1M context) --- .../include/SwapChainD3DBase.hpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Graphics/GraphicsEngineD3DBase/include/SwapChainD3DBase.hpp b/Graphics/GraphicsEngineD3DBase/include/SwapChainD3DBase.hpp index e28d469876..c67dcbbdc5 100644 --- a/Graphics/GraphicsEngineD3DBase/include/SwapChainD3DBase.hpp +++ b/Graphics/GraphicsEngineD3DBase/include/SwapChainD3DBase.hpp @@ -73,6 +73,7 @@ class SwapChainD3DBase : public SwapChainBase ~SwapChainD3DBase() { + ReleaseFrameLatencyWaitableObject(); if (m_pSwapChain) { // Swap chain must be in windowed mode when it is destroyed @@ -85,6 +86,15 @@ class SwapChainD3DBase : public SwapChainBase } protected: + void ReleaseFrameLatencyWaitableObject() + { + if (m_FrameLatencyWaitableObject != NULL) + { + CloseHandle(m_FrameLatencyWaitableObject); + m_FrameLatencyWaitableObject = NULL; + } + } + virtual void UpdateSwapChain(bool CreateNew) = 0; bool Resize(Uint32 NewWidth, Uint32 NewHeight, SURFACE_TRANSFORM NewPreTransform, Int32 Dummy = 0 /*To be different from virtual function*/) @@ -103,6 +113,9 @@ class SwapChainD3DBase : public SwapChainBase void CreateDXGISwapChain(IUnknown* pD3D11DeviceOrD3D12CmdQueue) { + // The previous swap chain, if any, has already been released. Close its waitable object. + ReleaseFrameLatencyWaitableObject(); + #if PLATFORM_WIN32 HWND hWnd = reinterpret_cast(m_Window.hWnd); if (m_SwapChainDesc.Width == 0 || m_SwapChainDesc.Height == 0) @@ -282,7 +295,6 @@ class SwapChainD3DBase : public SwapChainBase } else { - m_FrameLatencyWaitableObject = NULL; SetDXGIDeviceMaximumFrameLatency(); } }