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; }