[idd] recovery: unplug monitor without helper

When LGIddHelper is unavailable, recovery requests could remain
pending until timeout while the virtual monitor stayed connected.
This could leave a logged-out guest without a usable display.

Fall back to departing the IDD monitor for active recovery, then
re-arrive it before normal recovery completes. Serialize monitor
lifecycle changes and preserve recovery ordering across Helper
reconnects, timeouts, and stale responses.
This commit is contained in:
Geoffrey McRae
2026-08-20 16:03:58 +10:00
parent 7b059ad67e
commit 60a51fccdf
6 changed files with 777 additions and 47 deletions

View File

@@ -385,16 +385,42 @@ void CDeviceContext::FinishAdapterInit(UINT connectorIndex)
DEBUG_INFO("Preferred render adapter set");
}
FinishInit(connectorIndex);
bool monitorDisabled;
bool arrivalPending;
{
CSRWExclusiveLock lock(m_localRecoveryLock);
m_adapterReady = true;
monitorDisabled = m_recoveryMonitorDisabled;
arrivalPending = m_localRecoveryArrival;
}
if (!monitorDisabled)
{
FinishInit(connectorIndex);
return;
}
if (arrivalPending)
m_monitorManager.Enable();
else
{
// Recovery can intentionally disable the monitor before the adapter's
// first arrival. This is still a valid synchronization boundary: allow a
// later NORMAL request to re-enable the monitor instead of waiting for an
// arrival that ACTIVE deliberately suppressed.
m_transport->SyncRecovery();
}
}
void CDeviceContext::FinishInit(UINT connectorIndex)
{
CDisplayConfiguration::Description description =
m_displayConfiguration.GetDescription();
if (m_monitorManager.Create(
connectorIndex, m_adapter, std::move(description.edid), this))
const bool arrived = m_monitorManager.Create(
connectorIndex, m_adapter, std::move(description.edid), this);
if (arrived)
m_transport->SyncRecovery();
CompleteRecoveryArrival(arrived);
}
void CDeviceContext::ReplugMonitor()
@@ -489,28 +515,80 @@ bool CDeviceContext::InitializeTransport()
g_pipe.SetRecoveryHandler(
[](void * opaque, uint64_t route, uint64_t session,
uint32_t serial, bool active, LGPipeMsg::Type result)
uint32_t serial, bool active, CPipeServer::RecoveryResult result)
{
CDeviceContext * context =
static_cast<CDeviceContext *>(opaque);
RecoveryAction action;
action.route = route;
action.session = session;
action.serial = serial;
action.active = active;
if (result == CPipeServer::RecoveryResult::HELPER_UNAVAILABLE)
{
// Pipe recovery messages do not carry the local operation deadline.
// Recover it from the canonical action so a late monitor transition
// cannot complete an operation after the recovery hub timed it out.
{
CSRWSharedLock lock(context->m_localRecoveryLock);
if (context->m_latestRecoveryValid &&
context->SameRecoveryAction(action,
context->m_latestRecoveryAction))
action = context->m_latestRecoveryAction;
}
if (!context->QueueLocalRecovery(action))
context->m_transport->RecoveryStatus(
route, session, serial, active,
ITransport::Recovery::FAILED, RPC_S_SERVER_UNAVAILABLE);
return;
}
std::lock_guard<std::mutex> transitionLock(
context->m_recoveryTransitionMutex);
context->m_helperRecoveryAction = action;
context->m_helperRecoveryComplete = true;
context->RemoveLocalRecovery(action);
ITransport::Recovery state = ITransport::Recovery::FAILED;
uint32_t error = ERROR_SUCCESS;
switch (result)
{
case LGPipeMsg::RECOVERY_OFF:
case CPipeServer::RecoveryResult::NORMAL:
state = ITransport::Recovery::NORMAL;
{
CSRWExclusiveLock lock(context->m_localRecoveryLock);
if (context->m_latestRecoveryValid &&
context->SameRecoveryAction(action,
context->m_latestRecoveryAction))
{
context->m_recoveryActive = false;
context->m_latestRecoveryAction.deadline = 0;
}
}
break;
case LGPipeMsg::RECOVERY_ON:
case CPipeServer::RecoveryResult::ACTIVE:
state = ITransport::Recovery::ACTIVE;
{
CSRWExclusiveLock lock(context->m_localRecoveryLock);
if (context->m_latestRecoveryValid &&
context->SameRecoveryAction(action,
context->m_latestRecoveryAction))
{
context->m_recoveryActive = true;
context->m_latestRecoveryAction.deadline = 0;
}
}
break;
case LGPipeMsg::RECOVERY_FAILED:
case CPipeServer::RecoveryResult::FAILED:
error = ERROR_GEN_FAILURE;
break;
case LGPipeMsg::RECOVERY_NO_DISPLAY:
case CPipeServer::RecoveryResult::NO_DISPLAY:
error = ERROR_NOT_FOUND;
break;
@@ -588,6 +666,8 @@ bool CDeviceContext::SetupTransport(size_t alignSize)
void CDeviceContext::TransportTimer()
{
ProcessLocalRecovery();
// Monitor work is deferred off IddCx callback threads.
switch (m_monitorManager.TakeDeferredAction())
{
@@ -623,6 +703,360 @@ InteractionResult CDeviceContext::OnSetResolution(const SourceKey& source,
bool CDeviceContext::OnRecoveryAction(const RecoveryAction& action)
{
return g_pipe.SetRecovery(this, action.route, action.session,
action.serial, action.active);
bool recoveryActive;
bool monitorDisabled;
{
std::lock_guard<std::mutex> transitionLock(m_recoveryTransitionMutex);
CSRWExclusiveLock lock(m_localRecoveryLock);
m_latestRecoveryAction = action;
m_latestRecoveryValid = true;
recoveryActive = m_recoveryActive;
monitorDisabled = m_recoveryMonitorDisabled;
}
// The monitor must exist before Helper can restore the LG display path.
// Re-arrive it first when recovery previously used the IDD-only fallback.
if (!action.active && monitorDisabled)
return QueueLocalRecovery(action);
const CPipeServer::RecoveryDispatch dispatch = g_pipe.SetRecovery(
this, action.route, action.session, action.serial, action.active,
action.active || !recoveryActive);
std::lock_guard<std::mutex> transitionLock(m_recoveryTransitionMutex);
if (m_helperRecoveryComplete &&
SameRecoveryAction(m_helperRecoveryAction, action))
return true;
switch (dispatch)
{
case CPipeServer::RecoveryDispatch::SENT:
return true;
case CPipeServer::RecoveryDispatch::UNAVAILABLE:
case CPipeServer::RecoveryDispatch::QUEUED:
return QueueLocalRecovery(action);
case CPipeServer::RecoveryDispatch::REJECTED:
return false;
}
return false;
}
bool CDeviceContext::SameRecoveryAction(
const RecoveryAction& left, const RecoveryAction& right)
{
return left.route == right.route &&
left.session == right.session &&
left.serial == right.serial &&
left.active == right.active;
}
bool CDeviceContext::QueueLocalRecovery(const RecoveryAction& action)
{
if (!action.route || !action.session || !action.serial)
return false;
CSRWExclusiveLock lock(m_localRecoveryLock);
if (m_latestRecoveryValid &&
!SameRecoveryAction(action, m_latestRecoveryAction))
return true;
if (m_localRecoveryArrival)
{
const RecoveryAction& pending = m_localRecoveryArrivalAction;
if (SameRecoveryAction(pending, action))
return true;
}
for (const RecoveryAction& pending : m_localRecoveryQueue)
if (SameRecoveryAction(pending, action))
return true;
m_localRecoveryQueue.push_back(action);
return true;
}
void CDeviceContext::RemoveLocalRecovery(const RecoveryAction& action)
{
CSRWExclusiveLock lock(m_localRecoveryLock);
for (auto it = m_localRecoveryQueue.begin();
it != m_localRecoveryQueue.end();)
{
if (SameRecoveryAction(*it, action))
it = m_localRecoveryQueue.erase(it);
else
++it;
}
}
void CDeviceContext::ProcessLocalRecovery()
{
std::lock_guard<std::mutex> transitionLock(m_recoveryTransitionMutex);
RecoveryAction action;
bool haveAction = false;
bool reconcileActive = false;
bool reconcileAdapterReady = false;
{
CSRWExclusiveLock lock(m_localRecoveryLock);
for (;;)
{
if (m_localRecoveryQueue.empty())
break;
action = m_localRecoveryQueue.front();
m_localRecoveryQueue.pop_front();
const bool current = !m_latestRecoveryValid ||
SameRecoveryAction(action, m_latestRecoveryAction);
const bool expired = action.deadline &&
GetTickCount64() >= action.deadline;
if (current && !expired)
{
haveAction = true;
break;
}
// The request can expire after Helper disappears but before this timer
// consumes the handoff. Its result is stale, but an already-established
// ACTIVE state still needs the IDD monitor physically disabled.
if (current && expired && m_recoveryActive &&
!m_recoveryMonitorDisabled)
{
m_recoveryMonitorDisabled = true;
reconcileActive = true;
reconcileAdapterReady = m_adapterReady;
break;
}
}
}
if (reconcileActive)
{
DEBUG_WARN(
"IDD Helper is unavailable; reconciling the active recovery topology");
if (!m_monitorManager.Disable())
{
CSRWExclusiveLock lock(m_localRecoveryLock);
m_recoveryMonitorDisabled = false;
}
else if (reconcileAdapterReady)
m_transport->SyncRecovery();
return;
}
if (!haveAction)
return;
if (action.active)
{
DEBUG_WARN(
"IDD Helper is unavailable; disabling the virtual monitor for recovery");
bool oldRecoveryActive;
bool oldMonitorDisabled;
bool adapterReady;
{
CSRWExclusiveLock lock(m_localRecoveryLock);
oldRecoveryActive = m_recoveryActive;
oldMonitorDisabled = m_recoveryMonitorDisabled;
m_recoveryActive = true;
m_recoveryMonitorDisabled = true;
adapterReady = m_adapterReady;
}
const bool disabled = m_monitorManager.Disable();
if (!disabled)
{
CSRWExclusiveLock lock(m_localRecoveryLock);
m_recoveryActive = oldRecoveryActive;
m_recoveryMonitorDisabled = oldMonitorDisabled;
}
if (disabled && adapterReady)
m_transport->SyncRecovery();
m_transport->RecoveryStatus(action.route, action.session, action.serial,
true, disabled ? ITransport::Recovery::ACTIVE :
ITransport::Recovery::FAILED,
disabled ? ERROR_SUCCESS : ERROR_GEN_FAILURE);
return;
}
bool disable = false;
{
CSRWExclusiveLock lock(m_localRecoveryLock);
disable = m_recoveryActive && !m_recoveryMonitorDisabled;
if (disable)
m_recoveryMonitorDisabled = true;
}
if (disable)
{
DEBUG_WARN(
"IDD Helper disconnected during recovery; cycling the virtual monitor");
if (!m_monitorManager.Disable())
{
{
CSRWExclusiveLock lock(m_localRecoveryLock);
m_recoveryMonitorDisabled = false;
}
m_transport->RecoveryStatus(action.route, action.session, action.serial,
false, ITransport::Recovery::FAILED, ERROR_GEN_FAILURE);
return;
}
}
bool enable = false;
bool waitArrival = false;
{
CSRWExclusiveLock lock(m_localRecoveryLock);
if (m_recoveryMonitorDisabled)
{
m_localRecoveryArrivalAction = action;
m_localRecoveryArrival = true;
enable = m_adapterReady;
waitArrival = true;
}
else
m_recoveryActive = false;
}
if (enable)
m_monitorManager.Enable();
if (waitArrival)
return;
// The normal monitor is already present. With no Helper there is no
// user-session topology work to perform, so monitor presence is the local
// completion boundary.
m_transport->RecoveryStatus(action.route, action.session, action.serial,
false, ITransport::Recovery::NORMAL, ERROR_SUCCESS);
}
void CDeviceContext::CompleteRecoveryArrival(bool arrived)
{
RecoveryAction action;
RecoveryAction latest;
bool stale = false;
bool latestValid = false;
bool latestHandled = false;
bool latestCurrent = false;
std::unique_lock<std::mutex> transitionLock(m_recoveryTransitionMutex);
{
CSRWExclusiveLock lock(m_localRecoveryLock);
if (!m_localRecoveryArrival)
return;
action = m_localRecoveryArrivalAction;
const uint64_t now = GetTickCount64();
const bool expired = action.deadline && now >= action.deadline;
const bool superseded = m_latestRecoveryValid &&
!SameRecoveryAction(action, m_latestRecoveryAction);
stale = expired || superseded;
if (stale)
{
latestValid = m_latestRecoveryValid;
latest = m_latestRecoveryAction;
latestHandled = m_helperRecoveryComplete &&
SameRecoveryAction(m_helperRecoveryAction, latest);
latestCurrent = latestValid &&
(!latest.deadline || now < latest.deadline);
if (superseded && arrived && latestCurrent && !latest.active)
{
action = latest;
m_localRecoveryArrivalAction = latest;
stale = false;
for (auto it = m_localRecoveryQueue.begin();
it != m_localRecoveryQueue.end();)
{
if (SameRecoveryAction(*it, latest))
it = m_localRecoveryQueue.erase(it);
else
++it;
}
}
else
{
m_localRecoveryArrivalAction = {};
m_localRecoveryArrival = false;
}
}
if (arrived && !stale)
{
// Arrival is the IDD-only NORMAL completion boundary. Keep the monitor
// present if Helper disappears while applying its user-session topology.
m_localRecoveryArrivalAction = {};
m_localRecoveryArrival = false;
m_recoveryActive = false;
m_recoveryMonitorDisabled = false;
}
else if (!stale)
{
m_localRecoveryArrivalAction = {};
m_localRecoveryArrival = false;
}
}
if (stale)
{
// The expired/superseded NORMAL operation must not leave the IDD monitor
// arrived while CPipe still retains the preceding ACTIVE topology. Return
// to that stable local state before allowing newer work to proceed.
const bool disabled = m_monitorManager.Disable();
{
CSRWExclusiveLock lock(m_localRecoveryLock);
m_recoveryActive = true;
m_recoveryMonitorDisabled = disabled;
}
const bool queueLatest = latestValid && latest.active &&
latestCurrent && !latestHandled;
transitionLock.unlock();
if (queueLatest)
QueueLocalRecovery(latest);
return;
}
if (!arrived)
{
const bool disabled = m_monitorManager.Disable();
{
CSRWExclusiveLock lock(m_localRecoveryLock);
if (disabled)
{
m_recoveryActive = true;
m_recoveryMonitorDisabled = true;
}
}
transitionLock.unlock();
m_transport->RecoveryStatus(action.route, action.session, action.serial,
false, ITransport::Recovery::FAILED, ERROR_GEN_FAILURE);
return;
}
transitionLock.unlock();
// If Helper appeared while the monitor was disabled, let it restore the
// user-session topology now that the LG display path exists again.
const CPipeServer::RecoveryDispatch dispatch = g_pipe.SetRecovery(
this, action.route, action.session, action.serial, false, true);
{
std::lock_guard<std::mutex> completionLock(m_recoveryTransitionMutex);
CSRWExclusiveLock lock(m_localRecoveryLock);
if (m_helperRecoveryComplete &&
SameRecoveryAction(m_helperRecoveryAction, action))
return;
}
if (dispatch == CPipeServer::RecoveryDispatch::SENT)
return;
m_transport->RecoveryStatus(action.route, action.session, action.serial,
false,
(dispatch == CPipeServer::RecoveryDispatch::QUEUED ||
dispatch == CPipeServer::RecoveryDispatch::UNAVAILABLE) ?
ITransport::Recovery::NORMAL : ITransport::Recovery::FAILED,
(dispatch == CPipeServer::RecoveryDispatch::QUEUED ||
dispatch == CPipeServer::RecoveryDispatch::UNAVAILABLE) ?
ERROR_SUCCESS : RPC_S_SERVER_UNAVAILABLE);
}

View File

@@ -26,7 +26,9 @@
#include <wdf.h>
#include <IddCx.h>
#include <deque>
#include <memory>
#include <mutex>
#include <stddef.h>
#include <stdint.h>
@@ -55,6 +57,19 @@ private:
WDFTIMER m_transportTimer = nullptr;
bool m_recoveryHandlerSet = false;
std::mutex m_recoveryTransitionMutex;
CSRWLock m_localRecoveryLock;
std::deque<RecoveryAction> m_localRecoveryQueue;
RecoveryAction m_localRecoveryArrivalAction = {};
RecoveryAction m_latestRecoveryAction = {};
RecoveryAction m_helperRecoveryAction = {};
bool m_localRecoveryArrival = false;
bool m_latestRecoveryValid = false;
bool m_helperRecoveryComplete = false;
bool m_recoveryActive = false;
bool m_recoveryMonitorDisabled = false;
bool m_adapterReady = false;
UINT m_iddCxVersion = 0;
bool m_hasIddCx110DDIs = false;
bool m_canProcessFP16 = false;
@@ -67,6 +82,12 @@ private:
bool InitializeTransport();
void TransportTimer();
static bool SameRecoveryAction(
const RecoveryAction& left, const RecoveryAction& right);
bool QueueLocalRecovery(const RecoveryAction& action);
void RemoveLocalRecovery(const RecoveryAction& action);
void ProcessLocalRecovery();
void CompleteRecoveryArrival(bool arrived);
InteractionResult OnSetCursorPos(
const SourceKey& source, int32_t x, int32_t y) override;
InteractionResult OnSetResolution(const SourceKey& source,

View File

@@ -27,6 +27,7 @@
bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
std::vector<BYTE> edid, CDeviceContext * owner)
{
std::lock_guard<std::mutex> lifecycleLock(m_lifecycleMutex);
DEBUG_INFO("Creating monitor on connector %u", connectorIndex);
// We support a single monitor; never create a second one if one already
@@ -34,6 +35,8 @@ bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
bool haveMonitor;
{
CSRWExclusiveLock lock(m_lock);
if (!m_enabled)
return false;
haveMonitor = m_monitor != WDF_NO_HANDLE;
}
if (haveMonitor)
@@ -79,18 +82,27 @@ bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
DEBUG_INFO("Monitor object created (%p)", createOut.MonitorObject);
const IDDCX_MONITOR monitor = createOut.MonitorObject;
auto * wrapper = WdfObjectGet_CMonitorContextWrapper(monitor);
wrapper->context = new CMonitorContext(monitor, owner);
{
CSRWExclusiveLock lock(m_lock);
m_monitor = createOut.MonitorObject;
m_monitor = monitor;
m_monitorDeparted = false;
}
auto * wrapper = WdfObjectGet_CMonitorContextWrapper(m_monitor);
wrapper->context = new CMonitorContext(m_monitor, owner);
IDARG_OUT_MONITORARRIVAL out = {};
status = IddCxMonitorArrival(m_monitor, &out);
status = IddCxMonitorArrival(monitor, &out);
if (FAILED(status))
{
{
CSRWExclusiveLock lock(m_lock);
if (m_monitor == monitor)
m_monitor = nullptr;
}
WdfObjectDelete((WDFOBJECT)monitor);
DEBUG_ERROR_HR(status, "IddCxMonitorArrival Failed");
return false;
}
@@ -99,12 +111,100 @@ bool CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
return true;
}
bool CMonitorManager::Disable()
{
std::lock_guard<std::mutex> lifecycleLock(m_lifecycleMutex);
IDDCX_MONITOR monitor = nullptr;
bool oldReplugMonitor = false;
bool oldReplugPending = false;
bool oldMonitorDeparted = false;
bool oldWaitForSwapChainRelease = false;
LONG oldCreateQueued = 0;
LONG oldReplugQueued = 0;
{
CSRWExclusiveLock lock(m_lock);
if (!m_enabled)
return true;
oldReplugMonitor = m_replugMonitor;
oldReplugPending = m_replugPending;
oldMonitorDeparted = m_monitorDeparted;
oldWaitForSwapChainRelease = m_waitForSwapChainRelease;
oldCreateQueued = Atomic::Load(m_createQueued);
oldReplugQueued = Atomic::Load(m_replugQueued);
m_enabled = false;
m_replugMonitor = false;
m_replugPending = false;
Atomic::Store(m_createQueued, 0);
Atomic::Store(m_replugQueued, 0);
monitor = m_monitor;
if (monitor == WDF_NO_HANDLE)
return true;
m_monitor = nullptr;
m_monitorDeparted = false;
m_waitForSwapChainRelease = m_swapChainAssigned;
}
DEBUG_INFO("Disabling the virtual monitor for recovery");
const NTSTATUS status = IddCxMonitorDeparture(monitor);
if (!NT_SUCCESS(status))
{
CSRWExclusiveLock lock(m_lock);
m_enabled = true;
m_monitor = monitor;
m_replugMonitor = oldReplugMonitor;
m_replugPending = oldReplugPending;
m_monitorDeparted = oldMonitorDeparted;
m_waitForSwapChainRelease = oldWaitForSwapChainRelease;
Atomic::Store(m_createQueued, oldCreateQueued);
Atomic::Store(m_replugQueued, oldReplugQueued);
DEBUG_ERROR_HR(status,
"Failed to disable the virtual monitor for recovery");
return false;
}
{
CSRWExclusiveLock lock(m_lock);
m_monitorDeparted = true;
}
DEBUG_INFO("Virtual monitor disabled for recovery");
return true;
}
void CMonitorManager::Enable()
{
std::lock_guard<std::mutex> lifecycleLock(m_lifecycleMutex);
bool create = false;
{
CSRWExclusiveLock lock(m_lock);
if (m_enabled)
return;
m_enabled = true;
create = m_monitor == WDF_NO_HANDLE &&
!m_waitForSwapChainRelease;
}
if (create)
Atomic::Store(m_createQueued, 1);
}
CMonitorManager::ReplugAction CMonitorManager::Replug()
{
std::lock_guard<std::mutex> lifecycleLock(m_lifecycleMutex);
IDDCX_MONITOR monitor;
{
CSRWExclusiveLock lock(m_lock);
if (!m_enabled)
return ReplugAction::NONE;
if (m_waitForSwapChainRelease)
return ReplugAction::NONE;
if (m_replugMonitor || (m_swapChainAssigned && !m_swapChainReady))
{
// Coalesce changes received while a swap chain is being initialized,
@@ -198,10 +298,11 @@ void CMonitorManager::OnSwapChainReleased()
CSRWExclusiveLock lock(m_lock);
m_swapChainAssigned = false;
m_swapChainReady = false;
if (m_replugMonitor && m_waitForSwapChainRelease)
if (m_waitForSwapChainRelease)
{
m_waitForSwapChainRelease = false;
rebuild = m_monitorDeparted;
rebuild = m_enabled && m_monitorDeparted &&
m_monitor == WDF_NO_HANDLE;
}
}
@@ -217,6 +318,9 @@ CMonitorManager::ReadyAction CMonitorManager::OnSwapChainReady()
{
CSRWExclusiveLock lock(m_lock);
m_swapChainReady = true;
if (!m_enabled || m_waitForSwapChainRelease)
return action;
if (m_replugMonitor)
{
m_replugMonitor = false;
@@ -250,16 +354,27 @@ CMonitorManager::ReadyAction CMonitorManager::OnSwapChainReady()
void CMonitorManager::QueueReplug()
{
CSRWSharedLock lock(m_lock);
if (!m_enabled)
return;
Atomic::Store(m_replugQueued, 1);
}
CMonitorManager::DeferredAction CMonitorManager::TakeDeferredAction()
{
if (Atomic::Swap(m_createQueued, 0))
return DeferredAction::CREATE;
{
CSRWSharedLock lock(m_lock);
if (m_enabled)
return DeferredAction::CREATE;
}
if (Atomic::Swap(m_replugQueued, 0))
return DeferredAction::REPLUG;
{
CSRWSharedLock lock(m_lock);
if (m_enabled)
return DeferredAction::REPLUG;
}
return DeferredAction::NONE;
}

View File

@@ -25,6 +25,7 @@
#include <Windows.h>
#include <wdf.h>
#include <IddCx.h>
#include <mutex>
#include <vector>
#include "config/CSettings.h"
@@ -61,9 +62,14 @@ private:
// IddCx callback threads, the swap-chain thread, and the transport timer.
CSRWLock m_lock;
// Serializes monitor arrival/departure calls. IddCx can finish adapter
// initialization on a different thread from transport recovery work.
std::mutex m_lifecycleMutex;
bool m_replugMonitor = false;
bool m_replugPending = false;
bool m_monitorDeparted = false;
bool m_enabled = true;
bool m_swapChainAssigned = false;
bool m_swapChainReady = false;
bool m_waitForSwapChainRelease = false;
@@ -77,6 +83,8 @@ private:
public:
bool Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
std::vector<BYTE> edid, CDeviceContext * owner);
bool Disable();
void Enable();
ReplugAction Replug();
void RequestMode(const CSettings::DisplayMode& mode);