diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index c624a3a1..d63a33c1 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -574,9 +574,61 @@ void CIndirectDeviceContext::SetResolution(int width, int height) mode.refresh = g_settings.GetDefaultRefresh(); mode.preferred = true; + // A window resize streams many WINDOWSIZE requests in quick succession. + // Record the latest request and (re)start a timer; the actual mode change and + // replug only happen once the requested resolution has settled (i.e. no new + // request has arrived) for m_resChangeDelayMs. Each new request restarts the + // countdown, coalescing a burst of resizes into a single replug. AcquireSRWLockExclusive(&m_stateLock); - m_setMode = mode; - m_doSetMode = true; + m_pendingMode = mode; + m_resChangePending = true; + ReleaseSRWLockExclusive(&m_stateLock); + + // Create the debounce timer once; WdfTimerStart on an already-queued + // non-periodic timer simply updates its due time, giving us the restart. + if (!m_resChangeTimer) + { + WDF_TIMER_CONFIG config; + WDF_TIMER_CONFIG_INIT(&config, + [](WDFTIMER timer) -> void + { + WDFOBJECT parent = WdfTimerGetParentObject(timer); + auto wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(parent); + wrapper->context->ApplyResolution(); + }); + config.AutomaticSerialization = FALSE; + + WDF_OBJECT_ATTRIBUTES attribs; + WDF_OBJECT_ATTRIBUTES_INIT(&attribs); + attribs.ParentObject = m_wdfDevice; + attribs.ExecutionLevel = WdfExecutionLevelDispatch; + + NTSTATUS status = WdfTimerCreate(&config, &attribs, &m_resChangeTimer); + if (!NT_SUCCESS(status)) + { + DEBUG_ERROR_HR(status, "Resolution change timer creation failed"); + m_resChangeTimer = nullptr; + // Fall back to applying immediately so a resize is not lost. + ApplyResolution(); + return; + } + } + + WdfTimerStart(m_resChangeTimer, WDF_REL_TIMEOUT_IN_MS(m_resChangeDelayMs)); +} + +void CIndirectDeviceContext::ApplyResolution() +{ + AcquireSRWLockExclusive(&m_stateLock); + if (!m_resChangePending) + { + ReleaseSRWLockExclusive(&m_stateLock); + return; + } + CSettings::DisplayMode mode = m_pendingMode; + m_resChangePending = false; + m_setMode = mode; + m_doSetMode = true; ReleaseSRWLockExclusive(&m_stateLock); g_settings.SetExtraMode(mode); @@ -779,6 +831,14 @@ void CIndirectDeviceContext::DeInitLGMP() m_initTimer = nullptr; } + // The resolution debounce timer callback dereferences this context too; stop + // and drain it before tearing anything down. + if (m_resChangeTimer) + { + WdfTimerStop(m_resChangeTimer, TRUE); + m_resChangeTimer = nullptr; + } + if (m_lgmp == nullptr) return; diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index e4581a4e..a07b974c 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -153,6 +153,15 @@ private: CSettings::DisplayMode m_setMode = {}; bool m_doSetMode = false; + // Debounce for resolution changes. A window resize streams many WINDOWSIZE + // requests in quick succession; rather than replug the monitor for each, we + // (re)start a timer and only apply the last requested resolution once it has + // remained unchanged for m_resChangeDelayMs. Guarded by m_stateLock. + static const ULONG m_resChangeDelayMs = 1000; + WDFTIMER m_resChangeTimer = nullptr; + CSettings::DisplayMode m_pendingMode = {}; + bool m_resChangePending = false; + // Set by ReplugMonitor after a departure to rebuild the monitor from the LGMP // timer, off the IddCx callback thread. volatile LONG m_finishInitQueued = 0; @@ -188,6 +197,7 @@ public: #endif void SetResolution(int width, int height); + void ApplyResolution(); size_t GetAlignSize () const { return m_alignSize ; } size_t GetMaxFrameSize() const { return m_maxFrameSize ; }