From 1ce446f97a3cd023743557fa53fd5938082d5dec Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 16 Jul 2026 16:31:53 +1000 Subject: [PATCH] [idd] stop using IddCxMonitorUpdateModes(2) While it would make much more sense to use these then a full replug to change modes, Microsoft have not properly implemented the API to clear the cached monitor mode states internally, making these calls useless. Revert to just replugging the device on mode change --- idd/LGIdd/CD3D12CommandQueue.h | 3 +- idd/LGIdd/CD3D12Device.cpp | 16 ++ idd/LGIdd/CD3D12Device.h | 5 + idd/LGIdd/CIndirectDeviceContext.cpp | 224 ++++++-------------------- idd/LGIdd/CIndirectDeviceContext.h | 14 +- idd/LGIdd/CIndirectMonitorContext.cpp | 86 ++++++---- idd/LGIdd/CIndirectMonitorContext.h | 4 + idd/LGIdd/CSwapChainProcessor.cpp | 43 +++-- idd/LGIdd/CSwapChainProcessor.h | 2 +- idd/LGIdd/Device.cpp | 1 - 10 files changed, 165 insertions(+), 233 deletions(-) diff --git a/idd/LGIdd/CD3D12CommandQueue.h b/idd/LGIdd/CD3D12CommandQueue.h index fb8b6064..e9236e4f 100644 --- a/idd/LGIdd/CD3D12CommandQueue.h +++ b/idd/LGIdd/CD3D12CommandQueue.h @@ -24,6 +24,7 @@ #include #include #include +#include using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; @@ -40,7 +41,7 @@ class CD3D12CommandQueue ComPtr m_cmdList; ComPtr m_fence; - bool m_pending = false; + std::atomic m_pending = false; HandleT m_event; HANDLE m_waitHandle = INVALID_HANDLE_VALUE; UINT64 m_fenceValue = 0; diff --git a/idd/LGIdd/CD3D12Device.cpp b/idd/LGIdd/CD3D12Device.cpp index 813c881a..45b8868a 100644 --- a/idd/LGIdd/CD3D12Device.cpp +++ b/idd/LGIdd/CD3D12Device.cpp @@ -147,6 +147,22 @@ void CD3D12Device::DeInit() m_infoQueue.Reset(); } +void CD3D12Device::WaitForIdle() +{ + // A queue is ready once its GPU work has signalled and its completion + // callback has run (clearing the pending flag). Bound the wait so a + // removed/hung device cannot stall teardown indefinitely. + auto drain = [](CD3D12CommandQueue& queue) + { + for (int i = 0; i < 1000 && !queue.IsReady(); ++i) + Sleep(1); + }; + + for (int i = 0; i < ARRAYSIZE(m_copyQueue); ++i) + drain(m_copyQueue[i]); + drain(m_computeQueue); +} + bool CD3D12Device::HeapTest() { D3D12_RESOURCE_DESC desc = {}; diff --git a/idd/LGIdd/CD3D12Device.h b/idd/LGIdd/CD3D12Device.h index 3e1d0382..3828c07d 100644 --- a/idd/LGIdd/CD3D12Device.h +++ b/idd/LGIdd/CD3D12Device.h @@ -69,6 +69,11 @@ struct CD3D12Device InitResult Init(CIVSHMEM &ivshmem, UINT64 &alignSize); void DeInit(); + // Wait for all command queues to finish in-flight GPU work and run their + // completion callbacks. Used at swap-chain teardown so no callback touches + // resources we are about to release. + void WaitForIdle(); + ComPtr GetDevice() { return m_device; } ComPtr GetHeap() { return m_ivshmemHeap; } bool IsIndirectCopy() { return m_indirectCopy; } diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index 366284c4..c624a3a1 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -278,6 +278,17 @@ void CIndirectDeviceContext::InitAdapter() void CIndirectDeviceContext::FinishInit(UINT connectorIndex) { + // We support a single monitor; never create a second one if one already + // exists (a replug must clear m_monitor via departure first). + AcquireSRWLockExclusive(&m_stateLock); + bool haveMonitor = m_monitor != WDF_NO_HANDLE; + ReleaseSRWLockExclusive(&m_stateLock); + if (haveMonitor) + { + DEBUG_WARN("FinishInit skipped: a monitor already exists"); + return; + } + WDF_OBJECT_ATTRIBUTES attr; WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, CIndirectMonitorContextWrapper); @@ -332,18 +343,7 @@ void CIndirectDeviceContext::FinishInit(UINT connectorIndex) void CIndirectDeviceContext::ReplugMonitor() { - // Sample and mutate the replug state atomically. The monitor handle is - // published by FinishInit on an IddCx thread while this can run from the - // timer thread, so a plain check-then-act would race. AcquireSRWLockExclusive(&m_stateLock); - IDDCX_MONITOR monitor = m_monitor; - - if (monitor == WDF_NO_HANDLE) - { - ReleaseSRWLockExclusive(&m_stateLock); - FinishInit(0); - return; - } if (m_replugMonitor) { @@ -351,7 +351,21 @@ void CIndirectDeviceContext::ReplugMonitor() return; } + IDDCX_MONITOR monitor = m_monitor; + if (monitor == WDF_NO_HANDLE) + { + ReleaseSRWLockExclusive(&m_stateLock); + // Either no monitor yet, or one is already pending; build it now and + // cancel any queued rebuild so we do not create two. + InterlockedExchange(&m_finishInitQueued, 0); + FinishInit(0); + return; + } + + // Clear the handle before departing so nothing calls an IddCx monitor API on + // a departing/destroyed handle. FinishInit publishes the new one. m_replugMonitor = true; + m_monitor = nullptr; ReleaseSRWLockExclusive(&m_stateLock); DEBUG_TRACE("ReplugMonitor"); @@ -360,16 +374,28 @@ void CIndirectDeviceContext::ReplugMonitor() { AcquireSRWLockExclusive(&m_stateLock); m_replugMonitor = false; + m_monitor = monitor; ReleaseSRWLockExclusive(&m_stateLock); DEBUG_ERROR("IddCxMonitorDeparture Failed (0x%08x)", status); return; } + + // Always queue the rebuild here rather than relying on the unassign callback: + // if the monitor had no swap chain (e.g. recovering from a lost path) no + // unassign fires. The timer runs FinishInit off this thread. + InterlockedExchange(&m_finishInitQueued, 1); +} + +void CIndirectDeviceContext::OnMonitorDestroyed(IDDCX_MONITOR monitor) +{ + AcquireSRWLockExclusive(&m_stateLock); + if (m_monitor == monitor) + m_monitor = nullptr; + ReleaseSRWLockExclusive(&m_stateLock); } void CIndirectDeviceContext::OnAssignSwapChain() { - InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); - AcquireSRWLockExclusive(&m_stateLock); bool doSetMode = m_doSetMode; CSettings::DisplayMode mode = m_setMode; @@ -380,40 +406,6 @@ void CIndirectDeviceContext::OnAssignSwapChain() g_pipe.SetDisplayMode(mode.width, mode.height, mode.refresh); } -void CIndirectDeviceContext::OnUnassignedSwapChain() -{ - InterlockedExchange(&m_replugMonitorQueued, 0); - InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); - - AcquireSRWLockExclusive(&m_stateLock); - bool replug = m_replugMonitor; - m_replugMonitor = false; - ReleaseSRWLockExclusive(&m_stateLock); - - // Do NOT rebuild the monitor from inside this callback. Creating and arriving - // a new monitor here re-enters IddCx while the old monitor's swap-chain - // teardown is still unwinding on this thread, which leaves the new swap - // chain's surfaces lost/abandoned (DXGI_ERROR_ACCESS_LOST). Defer to the LGMP - // timer, matching how the departure is scheduled. - if (replug) - InterlockedExchange(&m_finishInitQueued, 1); -} - -void CIndirectDeviceContext::OnSwapChainLost() -{ - // A mode update normally keeps the swap chain alive. If Windows instead - // reports the existing path disappeared before we see a frame at the new - // size, recover by scheduling the old replug path from the LGMP timer so we - // do not tear down the swap chain from one of its worker threads. - if (!InterlockedCompareExchange(&m_recoverModeUpdateSwapChain, 0, 0)) - return; - - if (InterlockedExchange(&m_replugMonitorQueued, 1)) - return; - - DEBUG_WARN("Swap chain was lost after a mode update, falling back to monitor replug"); -} - static inline void FillSignalInfo(DISPLAYCONFIG_VIDEO_SIGNAL_INFO & mode, DWORD width, DWORD height, DWORD vsync, bool monitorMode) { mode.totalSize.cx = mode.activeSize.cx = width; @@ -574,96 +566,6 @@ NTSTATUS CIndirectDeviceContext::MonitorQueryTargetModes2( } #endif -bool CIndirectDeviceContext::UpdateMonitorModes() -{ - AcquireSRWLockExclusive(&m_stateLock); - IDDCX_MONITOR monitor = m_monitor; - ReleaseSRWLockExclusive(&m_stateLock); - - if (!monitor) - return false; - - // Snapshot the mode list so we do not hold m_modeLock across the IddCx call - // below - IddCxMonitorUpdateModes can synchronously re-enter our mode - // enumeration callbacks (which also take m_modeLock). - CSettings::DisplayModes displayModes; - AcquireSRWLockShared(&m_modeLock); - displayModes = m_displayModes; - ReleaseSRWLockShared(&m_modeLock); - -#ifdef HAS_IDDCX_110 - if (CanUseIddCx110DDIs()) - { - IDDCX_TARGET_MODE2* modes = (IDDCX_TARGET_MODE2*)_malloca( - displayModes.size() * sizeof(IDDCX_TARGET_MODE2)); - if (!modes) - { - DEBUG_WARN("Failed to allocate memory for the mode list"); - return false; - } - - ZeroMemory(modes, displayModes.size() * sizeof(IDDCX_TARGET_MODE2)); - - auto* mode = modes; - for (auto it = displayModes.cbegin(); it != displayModes.cend(); ++it, ++mode) - { - mode->Size = sizeof(IDDCX_TARGET_MODE2); - mode->RequiredBandwidth = (UINT64)it->width * it->height * it->refresh * 32; - mode->BitsPerComponent = GetWireBitsPerComponent(CanUseIddCx110DDIs()); - FillSignalInfo(mode->TargetVideoSignalInfo.targetVideoSignalInfo, it->width, it->height, it->refresh, false); - } - - IDARG_IN_UPDATEMODES2 updateModes = {}; - updateModes.Reason = IDDCX_UPDATE_REASON_OTHER; - updateModes.TargetModeCount = (UINT)displayModes.size(); - updateModes.pTargetModes = modes; - - NTSTATUS status = IddCxMonitorUpdateModes2(monitor, &updateModes); - _freea(modes); - if (!NT_SUCCESS(status)) - { - DEBUG_WARN("IddCxMonitorUpdateModes2 Failed (0x%08x)", status); - return false; - } - - return true; - } -#endif - - IDDCX_TARGET_MODE* modes = (IDDCX_TARGET_MODE*)_malloca( - displayModes.size() * sizeof(IDDCX_TARGET_MODE)); - if (!modes) - { - DEBUG_WARN("Failed to allocate memory for the mode list"); - return false; - } - - ZeroMemory(modes, displayModes.size() * sizeof(IDDCX_TARGET_MODE)); - - auto* mode = modes; - for (auto it = displayModes.cbegin(); it != displayModes.cend(); ++it, ++mode) - { - mode->Size = sizeof(IDDCX_TARGET_MODE); - mode->RequiredBandwidth = (UINT64)it->width * it->height * it->refresh * 32; - FillSignalInfo(mode->TargetVideoSignalInfo.targetVideoSignalInfo, it->width, it->height, it->refresh, false); - } - - IDARG_IN_UPDATEMODES updateModes = {}; - updateModes.Reason = IDDCX_UPDATE_REASON_OTHER; - updateModes.TargetModeCount = (UINT)displayModes.size(); - updateModes.pTargetModes = modes; - - NTSTATUS status = IddCxMonitorUpdateModes(monitor, &updateModes); - _freea(modes); - if (!NT_SUCCESS(status)) - { - DEBUG_WARN("IddCxMonitorUpdateModes Failed (0x%08x)", status); - return false; - } - - return true; -} - void CIndirectDeviceContext::SetResolution(int width, int height) { CSettings::DisplayMode mode = {}; @@ -673,29 +575,17 @@ void CIndirectDeviceContext::SetResolution(int width, int height) mode.preferred = true; AcquireSRWLockExclusive(&m_stateLock); - m_setMode = mode; + m_setMode = mode; + m_doSetMode = true; ReleaseSRWLockExclusive(&m_stateLock); g_settings.SetExtraMode(mode); PopulateDefaultModes(); - if (UpdateMonitorModes()) - { - DEBUG_TRACE("Updated monitor modes without replugging"); - AcquireSRWLockExclusive(&m_stateLock); - m_doSetMode = false; - ReleaseSRWLockExclusive(&m_stateLock); - InterlockedExchange(&m_recoverModeUpdateSwapChain, 1); - g_pipe.SetDisplayMode(mode.width, mode.height, mode.refresh); - return; - } - - DEBUG_TRACE("Falling back to monitor replug for mode update"); - AcquireSRWLockExclusive(&m_stateLock); - m_doSetMode = true; - ReleaseSRWLockExclusive(&m_stateLock); - InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); + // IddCxMonitorUpdateModes[2] does not invalidate Windows' cached mode list, + // so the only reliable way to apply a new mode is to depart and re-arrive the + // monitor, forcing Windows to rebuild the topology from the new mode list. ReplugMonitor(); } @@ -909,20 +799,13 @@ void CIndirectDeviceContext::DeInitLGMP() void CIndirectDeviceContext::LGMPTimer() { - if (InterlockedExchange(&m_replugMonitorQueued, 0)) - { - AcquireSRWLockExclusive(&m_stateLock); - m_doSetMode = true; - ReleaseSRWLockExclusive(&m_stateLock); - ReplugMonitor(); - return; - } - - // Rebuild the monitor deferred from the IddCx unassign callback, off that - // callback's thread and after its swap-chain teardown has fully unwound. + // Rebuild the monitor queued by ReplugMonitor, off the IddCx callback thread. if (InterlockedExchange(&m_finishInitQueued, 0)) { FinishInit(0); + AcquireSRWLockExclusive(&m_stateLock); + m_replugMonitor = false; + ReleaseSRWLockExclusive(&m_stateLock); return; } @@ -984,17 +867,6 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame if (!m_lgmp || !m_frameQueue) return result; - if (InterlockedCompareExchange(&m_recoverModeUpdateSwapChain, 0, 0)) - { - AcquireSRWLockShared(&m_stateLock); - const bool sizeMatches = - srcFormat.width == m_setMode.width && srcFormat.height == m_setMode.height; - ReleaseSRWLockShared(&m_stateLock); - - if (sizeMatches) - InterlockedExchange(&m_recoverModeUpdateSwapChain, 0); - } - if (m_width != dstFormat.desc.Width || m_height != dstFormat.desc.Height || m_pitch != pitch || diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index 7ca8079d..e4581a4e 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -140,7 +140,6 @@ private: void DeInitLGMP(); void LGMPTimer(); void ResendCursor() const; - bool UpdateMonitorModes(); // Guards m_displayModes and m_edid. The mode list is rebuilt on the LGMP // timer thread (SetResolution) while IddCx concurrently enumerates it on its @@ -153,13 +152,9 @@ private: CSettings::DisplayMode m_setMode = {}; bool m_doSetMode = false; - volatile LONG m_replugMonitorQueued = 0; - volatile LONG m_recoverModeUpdateSwapChain = 0; - // Set from the IddCx unassign callback to defer the monitor rebuild - // (FinishInit) onto the LGMP timer. Creating/arriving a new monitor from - // inside the old monitor's unassign callback re-enters IddCx while its - // swap-chain teardown is still unwinding, which leaves the new swap-chain - // surfaces in a lost/abandoned state. + + // Set by ReplugMonitor after a departure to rebuild the monitor from the LGMP + // timer, off the IddCx callback thread. volatile LONG m_finishInitQueued = 0; public: @@ -175,9 +170,8 @@ public: void FinishInit(UINT connectorIndex); void ReplugMonitor(); + void OnMonitorDestroyed(IDDCX_MONITOR monitor); void OnAssignSwapChain(); - void OnUnassignedSwapChain(); - void OnSwapChainLost(); NTSTATUS ParseMonitorDescription( const IDARG_IN_PARSEMONITORDESCRIPTION* inArgs, IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs); diff --git a/idd/LGIdd/CIndirectMonitorContext.cpp b/idd/LGIdd/CIndirectMonitorContext.cpp index 0b384702..7906a359 100644 --- a/idd/LGIdd/CIndirectMonitorContext.cpp +++ b/idd/LGIdd/CIndirectMonitorContext.cpp @@ -32,50 +32,78 @@ CIndirectMonitorContext::CIndirectMonitorContext(_In_ IDDCX_MONITOR monitor, CIn CIndirectMonitorContext::~CIndirectMonitorContext() { UnassignSwapChain(); + m_devContext->OnMonitorDestroyed(m_monitor); } void CIndirectMonitorContext::AssignSwapChain(IDDCX_SWAPCHAIN swapChain, LUID renderAdapter, HANDLE newFrameEvent) { -reInit: UnassignSwapChain(); - m_dx11Device = std::make_shared(renderAdapter); - if (FAILED(m_dx11Device->Init())) - { - WdfObjectDelete(swapChain); - return; - } + // Build everything into locals so the members are never observed + // half-constructed by a concurrent unassign, and so the processor (which + // spawns a worker thread) is created outside the lock. + std::shared_ptr dx11Device; + std::shared_ptr dx12Device; - UINT64 alignSize = CPlatformInfo::GetPageSize(); - m_dx12Device = std::make_shared(renderAdapter); - switch (m_dx12Device->Init(m_devContext->GetIVSHMEM(), alignSize)) + for (;;) { - case CD3D12Device::SUCCESS: - break; - - case CD3D12Device::FAILURE: + dx11Device = std::make_shared(renderAdapter); + if (FAILED(dx11Device->Init())) + { WdfObjectDelete(swapChain); return; + } - case CD3D12Device::RETRY: - m_dx12Device.reset(); - m_dx11Device.reset(); - goto reInit; - } - - if (!m_devContext->SetupLGMP(alignSize)) - { - WdfObjectDelete(swapChain); - DEBUG_ERROR("SetupLGMP failed"); - return; + UINT64 alignSize = CPlatformInfo::GetPageSize(); + dx12Device = std::make_shared(renderAdapter); + CD3D12Device::InitResult r = dx12Device->Init(m_devContext->GetIVSHMEM(), alignSize); + if (r == CD3D12Device::RETRY) + { + dx12Device.reset(); + dx11Device.reset(); + continue; + } + if (r == CD3D12Device::FAILURE) + { + WdfObjectDelete(swapChain); + return; + } + + if (!m_devContext->SetupLGMP(alignSize)) + { + WdfObjectDelete(swapChain); + DEBUG_ERROR("SetupLGMP failed"); + return; + } + break; } - m_swapChain.reset(new CSwapChainProcessor(m_monitor, m_devContext, swapChain, m_dx11Device, m_dx12Device, newFrameEvent)); + std::unique_ptr processor(new CSwapChainProcessor( + m_monitor, m_devContext, swapChain, dx11Device, dx12Device, newFrameEvent)); + + AcquireSRWLockExclusive(&m_lock); + m_dx11Device = std::move(dx11Device); + m_dx12Device = std::move(dx12Device); + m_swapChain = std::move(processor); + ReleaseSRWLockExclusive(&m_lock); } void CIndirectMonitorContext::UnassignSwapChain() { - m_swapChain.reset(); - m_dx11Device.reset(); - m_dx12Device.reset(); + // Detach under the lock, then destroy outside it. Destroying the processor + // joins its worker thread, whose teardown (WdfObjectDelete) re-enters this + // method on another thread - holding the lock across that would deadlock. + std::unique_ptr processor; + std::shared_ptr dx11Device; + std::shared_ptr dx12Device; + + AcquireSRWLockExclusive(&m_lock); + processor = std::move(m_swapChain); + dx11Device = std::move(m_dx11Device); + dx12Device = std::move(m_dx12Device); + ReleaseSRWLockExclusive(&m_lock); + + processor.reset(); + dx11Device.reset(); + dx12Device.reset(); } \ No newline at end of file diff --git a/idd/LGIdd/CIndirectMonitorContext.h b/idd/LGIdd/CIndirectMonitorContext.h index 7a9fe5f4..9ac35a8d 100644 --- a/idd/LGIdd/CIndirectMonitorContext.h +++ b/idd/LGIdd/CIndirectMonitorContext.h @@ -35,6 +35,10 @@ class CIndirectMonitorContext private: IDDCX_MONITOR m_monitor; + // Guards the swap chain and device pointers. Assign and unassign can run + // concurrently (an unassign triggered by the worker's WdfObjectDelete can + // race the next assign), and shared_ptr copy/reset is not thread safe. + SRWLOCK m_lock = SRWLOCK_INIT; std::shared_ptr m_dx11Device; std::shared_ptr m_dx12Device; diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index bc1e4ae0..437cb362 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -38,7 +38,9 @@ CSwapChainProcessor::CSwapChainProcessor(IDDCX_MONITOR monitor, CIndirectDeviceC if (!m_postProcessor.Init(dx12Device)) DEBUG_ERROR("Failed to initialize post processor"); - m_terminateEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr)); + // Manual-reset: both worker threads wait on this, so it must stay signalled + // once set or only one thread would ever observe termination. + m_terminateEvent.Attach(CreateEvent(nullptr, TRUE, FALSE, nullptr)); m_thread[0].Attach(CreateThread(nullptr, 0, _SwapChainThread, this, 0, nullptr)); m_cursorDataEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr)); @@ -53,6 +55,11 @@ CSwapChainProcessor::~CSwapChainProcessor() if (m_thread[1].Get()) WaitForSingleObject(m_thread[1].Get(), INFINITE); + // Drain in-flight GPU work / completion callbacks before releasing the + // resources they reference. The swap chain was already released in the + // worker epilogue, so this does not hold an IddCx frame. + m_dx12Device->WaitForIdle(); + m_postProcessor.Reset(); m_resPool.Reset(); m_fbPool.Reset(); @@ -71,22 +78,26 @@ void CSwapChainProcessor::SwapChainThread() HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask); DEBUG_INFO("Start Thread"); - SwapChainThreadCore(); - WdfObjectDelete((WDFOBJECT)m_hSwapChain); + // Only delete the swap chain if we took ownership of it (SetDevice + // succeeded). If SetDevice failed IddCx still owns and tears it down, so + // deleting it here would double-free the WDF object. Releasing it when we do + // own it hands the acquired frame back to IddCx promptly. + if (SwapChainThreadCore()) + WdfObjectDelete((WDFOBJECT)m_hSwapChain); m_hSwapChain = nullptr; AvRevertMmThreadCharacteristics(avTaskHandle); } -void CSwapChainProcessor::SwapChainThreadCore() +bool CSwapChainProcessor::SwapChainThreadCore() { ComPtr dxgiDevice; HRESULT hr = m_dx11Device->GetDevice().As(&dxgiDevice); if (FAILED(hr)) { DEBUG_ERROR_HR(hr, "Failed to get the dxgiDevice"); - return; + return false; } if (IDD_IS_FUNCTION_AVAILABLE(IddCxSetRealtimeGPUPriority)) @@ -107,12 +118,17 @@ void CSwapChainProcessor::SwapChainThreadCore() IDARG_IN_SWAPCHAINSETDEVICE setDevice = {}; setDevice.pDevice = dxgiDevice.Get(); + // A failure here (commonly DXGI_ERROR_ACCESS_LOST on the first assignment) + // is not recoverable on this handle - IddCx reassigns a fresh swap chain, + // which is what actually succeeds. Bail cleanly and let that happen. hr = IddCxSwapChainSetDevice(m_hSwapChain, &setDevice); if (FAILED(hr)) { DEBUG_ERROR_HR(hr, "IddCxSwapChainSetDevice Failed"); - return; + return false; } + // Past this point SetDevice succeeded: we own the swap chain and are + // responsible for deleting it. IDARG_IN_SETUP_HWCURSOR c = {}; c.CursorInfo.Size = sizeof(c.CursorInfo); @@ -125,7 +141,7 @@ void CSwapChainProcessor::SwapChainThreadCore() if (!NT_SUCCESS(status)) { DEBUG_ERROR("IddCxMonitorSetupHardwareCursor Failed (0x%08x)", status); - return; + return true; } m_lastShapeId = 0; @@ -206,9 +222,9 @@ void CSwapChainProcessor::SwapChainThreadCore() hr = IddCxSwapChainFinishedProcessingFrame(m_hSwapChain); if (FAILED(hr)) { - if (hr == STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY) - m_devContext->OnSwapChainLost(); - else + // A lost path is normal (mode change/topology rebuild); Windows + // reassigns a fresh swap chain. Just exit and let it. + if (hr != STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY) DEBUG_ERROR_HR(hr, "IddCxSwapChainFinishedProcessingFrame Failed"); break; } @@ -216,12 +232,10 @@ void CSwapChainProcessor::SwapChainThreadCore() } } else - { - if (hr == STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY) - m_devContext->OnSwapChainLost(); break; - } } + + return true; } void CSwapChainProcessor::CompletionFunction( @@ -584,7 +598,6 @@ bool CSwapChainProcessor::QueryHWCursor() // this occurs if the display went away (ie, screen blanking or disabled) if (status == STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY) { - m_devContext->OnSwapChainLost(); SetEvent(m_terminateEvent.Get()); return false; } diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/CSwapChainProcessor.h index 50f9faa4..bd380b26 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/CSwapChainProcessor.h @@ -62,7 +62,7 @@ private: static DWORD CALLBACK _SwapChainThread(LPVOID arg); void SwapChainThread(); - void SwapChainThreadCore(); + bool SwapChainThreadCore(); static DWORD CALLBACK _CursorThread(LPVOID arg); bool QueryHWCursor(); diff --git a/idd/LGIdd/Device.cpp b/idd/LGIdd/Device.cpp index a03bdabd..8335ba46 100644 --- a/idd/LGIdd/Device.cpp +++ b/idd/LGIdd/Device.cpp @@ -199,7 +199,6 @@ NTSTATUS LGIddMonitorUnassignSwapChain(IDDCX_MONITOR monitor) { auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); wrapper->context->UnassignSwapChain(); - wrapper->context->GetDeviceContext()->OnUnassignedSwapChain(); return STATUS_SUCCESS; }