From 791da3387a7ca18f4eda1881783176ed80f4e4ee Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sat, 18 Jul 2026 04:30:21 +1000 Subject: [PATCH] [idd] serialize monitor replugs with swap chain teardown Keep the replug lifecycle gated until the old swap chain has drained and released its pending frame, and the replacement swap chain is fully initialized. Coalesce additional requests into a follow-up replug to prevent overlapping topology changes and IddCx release-frame timeout bugchecks. --- idd/LGIdd/CIndirectDeviceContext.cpp | 99 +++++++++++++++++++++++---- idd/LGIdd/CIndirectDeviceContext.h | 13 +++- idd/LGIdd/CIndirectMonitorContext.cpp | 7 +- idd/LGIdd/CSwapChainProcessor.cpp | 4 ++ idd/LGIdd/Device.cpp | 1 - 5 files changed, 108 insertions(+), 16 deletions(-) diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index 08be3ace..80aa7bb3 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -348,8 +348,11 @@ void CIndirectDeviceContext::ReplugMonitor() { AcquireSRWLockExclusive(&m_stateLock); - if (m_replugMonitor) + if (m_replugMonitor || (m_swapChainAssigned && !m_swapChainReady)) { + // Coalesce changes received while a swap chain is being initialized, the + // old one is draining, or its replacement is being initialized. + m_replugPending = true; ReleaseSRWLockExclusive(&m_stateLock); return; } @@ -357,6 +360,8 @@ void CIndirectDeviceContext::ReplugMonitor() IDDCX_MONITOR monitor = m_monitor; if (monitor == WDF_NO_HANDLE) { + m_replugMonitor = true; + m_monitorDeparted = true; 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. @@ -367,8 +372,10 @@ void CIndirectDeviceContext::ReplugMonitor() // 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; + m_replugMonitor = true; + m_monitorDeparted = false; + m_waitForSwapChainRelease = m_swapChainAssigned; + m_monitor = nullptr; ReleaseSRWLockExclusive(&m_stateLock); DEBUG_TRACE("ReplugMonitor"); @@ -376,17 +383,25 @@ void CIndirectDeviceContext::ReplugMonitor() if (!NT_SUCCESS(status)) { AcquireSRWLockExclusive(&m_stateLock); - m_replugMonitor = false; - m_monitor = monitor; + m_replugMonitor = false; + m_replugPending = false; + m_monitorDeparted = false; + m_waitForSwapChainRelease = 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); + AcquireSRWLockExclusive(&m_stateLock); + m_monitorDeparted = true; + const bool rebuild = !m_waitForSwapChainRelease; + ReleaseSRWLockExclusive(&m_stateLock); + + // If there was no swap chain there will be no unassign callback to queue the + // rebuild. Otherwise OnSwapChainReleased does so after teardown has drained. + if (rebuild) + InterlockedExchange(&m_finishInitQueued, 1); } void CIndirectDeviceContext::OnMonitorDestroyed(IDDCX_MONITOR monitor) @@ -409,6 +424,63 @@ void CIndirectDeviceContext::OnAssignSwapChain() g_pipe.SetDisplayMode(mode.width, mode.height, mode.refresh); } +void CIndirectDeviceContext::OnSwapChainAssigned() +{ + AcquireSRWLockExclusive(&m_stateLock); + m_swapChainAssigned = true; + m_swapChainReady = false; + ReleaseSRWLockExclusive(&m_stateLock); +} + +void CIndirectDeviceContext::OnSwapChainReleased() +{ + bool rebuild = false; + + AcquireSRWLockExclusive(&m_stateLock); + m_swapChainAssigned = false; + m_swapChainReady = false; + if (m_replugMonitor && m_waitForSwapChainRelease) + { + m_waitForSwapChainRelease = false; + rebuild = m_monitorDeparted; + } + ReleaseSRWLockExclusive(&m_stateLock); + + if (rebuild) + InterlockedExchange(&m_finishInitQueued, 1); +} + +void CIndirectDeviceContext::OnSwapChainReady() +{ + bool replug = false; + + AcquireSRWLockExclusive(&m_stateLock); + m_swapChainReady = true; + if (m_replugMonitor) + { + m_replugMonitor = false; + m_monitorDeparted = false; + if (m_replugPending) + { + m_replugPending = false; + replug = true; + } + } + else if (m_replugPending) + { + m_replugPending = false; + replug = true; + } + ReleaseSRWLockExclusive(&m_stateLock); + + // Do not expose the context to pipe reload requests until the initial swap + // chain has reached the same ready state used by the replug gate. + g_pipe.SetDeviceContext(this); + + if (replug) + InterlockedExchange(&m_replugQueued, 1); +} + static inline void FillSignalInfo(DISPLAYCONFIG_VIDEO_SIGNAL_INFO & mode, DWORD width, DWORD height, DWORD vsync, bool monitorMode) { mode.totalSize.cx = mode.activeSize.cx = width; @@ -866,9 +938,12 @@ void CIndirectDeviceContext::LGMPTimer() if (InterlockedExchange(&m_finishInitQueued, 0)) { FinishInit(0); - AcquireSRWLockExclusive(&m_stateLock); - m_replugMonitor = false; - ReleaseSRWLockExclusive(&m_stateLock); + return; + } + + if (InterlockedExchange(&m_replugQueued, 0)) + { + ReplugMonitor(); return; } diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index c348ed33..d8b0918b 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -64,10 +64,15 @@ private: IDDCX_ADAPTER m_adapter = nullptr; IDDCX_MONITOR m_monitor = nullptr; bool m_replugMonitor = false; + bool m_replugPending = false; + bool m_monitorDeparted = false; + bool m_swapChainAssigned = false; + bool m_swapChainReady = false; + bool m_waitForSwapChainRelease = false; // Guards the adapter/monitor init handshake and the replug state machine - // (m_monitor, m_replugMonitor, m_doSetMode, m_setMode). These are touched - // from the IddCx callback threads and the LGMP timer thread. + // (monitor/replug/swap-chain state, m_doSetMode, m_setMode). These are + // touched from the IddCx callback threads, swap-chain thread and LGMP timer. SRWLOCK m_stateLock = SRWLOCK_INIT; // Retry state for InitAdapter. At boot the IVSHMEM device may not have @@ -166,6 +171,7 @@ private: // Set by ReplugMonitor after a departure to rebuild the monitor from the LGMP // timer, off the IddCx callback thread. volatile LONG m_finishInitQueued = 0; + volatile LONG m_replugQueued = 0; public: CIndirectDeviceContext(_In_ WDFDEVICE wdfDevice) : @@ -182,6 +188,9 @@ public: void OnMonitorDestroyed(IDDCX_MONITOR monitor); void OnAssignSwapChain(); + void OnSwapChainAssigned(); + void OnSwapChainReleased(); + void OnSwapChainReady(); NTSTATUS ParseMonitorDescription( const IDARG_IN_PARSEMONITORDESCRIPTION* inArgs, IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs); diff --git a/idd/LGIdd/CIndirectMonitorContext.cpp b/idd/LGIdd/CIndirectMonitorContext.cpp index 7906a359..4566aa4b 100644 --- a/idd/LGIdd/CIndirectMonitorContext.cpp +++ b/idd/LGIdd/CIndirectMonitorContext.cpp @@ -78,6 +78,7 @@ void CIndirectMonitorContext::AssignSwapChain(IDDCX_SWAPCHAIN swapChain, LUID re break; } + m_devContext->OnSwapChainAssigned(); std::unique_ptr processor(new CSwapChainProcessor( m_monitor, m_devContext, swapChain, dx11Device, dx12Device, newFrameEvent)); @@ -103,7 +104,11 @@ void CIndirectMonitorContext::UnassignSwapChain() dx12Device = std::move(m_dx12Device); ReleaseSRWLockExclusive(&m_lock); + const bool hadSwapChain = !!processor; processor.reset(); dx11Device.reset(); dx12Device.reset(); -} \ No newline at end of file + + if (hadSwapChain) + m_devContext->OnSwapChainReleased(); +} diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 068d0f30..36a1f3c1 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -147,6 +147,10 @@ bool CSwapChainProcessor::SwapChainThreadCore() m_lastShapeId = 0; m_thread[1].Attach(CreateThread(nullptr, 0, _CursorThread, this, 0, nullptr)); + // The replacement swap chain is fully initialized and no frame has been + // acquired yet, so a coalesced follow-up replug may now proceed safely. + m_devContext->OnSwapChainReady(); + // postpone sending this to ensure we dont spam messages if we end up in a // restart loop while waiting for a valid configuration g_pipe.SetGPUStatus(m_dx11Device->IsSoftware()); diff --git a/idd/LGIdd/Device.cpp b/idd/LGIdd/Device.cpp index e4142763..e996a009 100644 --- a/idd/LGIdd/Device.cpp +++ b/idd/LGIdd/Device.cpp @@ -75,7 +75,6 @@ NTSTATUS LGIddAdapterInitFinished(IDDCX_ADAPTER adapter, const IDARG_IN_ADAPTER_ return STATUS_SUCCESS; wrapper->context->FinishInit(0); - g_pipe.SetDeviceContext(wrapper->context); return STATUS_SUCCESS; }