mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
[idd] project: organize driver sources by responsibility
Group the IDD sources and Visual Studio filters by subsystem. Split the device and swap-chain implementations into focused units, rename the context classes, and reduce header coupling.
This commit is contained in:
588
idd/LGIdd/display/device/CDeviceContext.cpp
Normal file
588
idd/LGIdd/display/device/CDeviceContext.cpp
Normal file
@@ -0,0 +1,588 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "display/device/CDeviceContext.h"
|
||||
|
||||
#include "display/IddCxCompat.h"
|
||||
#include "transport/CPipeServer.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
#include <dxgi1_2.h>
|
||||
#include <utility>
|
||||
|
||||
// Adapter and monitor lifecycle
|
||||
|
||||
static const UINT IDDCX_VERSION_1_10 = 0x1A00;
|
||||
|
||||
CDeviceContext::CDeviceContext(WDFDEVICE wdfDevice) :
|
||||
m_wdfDevice(wdfDevice),
|
||||
m_lgmpControl(m_lgmpHost),
|
||||
m_frameTransport(m_lgmpHost, m_ivshmem),
|
||||
m_displayConfiguration(g_settings)
|
||||
{
|
||||
}
|
||||
|
||||
CDeviceContext::~CDeviceContext()
|
||||
{
|
||||
// Both callbacks dereference this context. Drain them before the subsystem
|
||||
// members are destroyed in frame, control, host order.
|
||||
if (m_initTimer)
|
||||
{
|
||||
WdfTimerStop(m_initTimer, TRUE);
|
||||
m_initTimer = nullptr;
|
||||
}
|
||||
|
||||
if (m_lgmpTimer)
|
||||
{
|
||||
WdfTimerStop(m_lgmpTimer, TRUE);
|
||||
m_lgmpTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CDeviceContext::QueryIddCxCapabilities()
|
||||
{
|
||||
IDARG_OUT_GETVERSION ver = {};
|
||||
NTSTATUS status = IddCxGetVersion(&ver);
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
m_iddCxVersion = 0;
|
||||
m_hasIddCx110DDIs = false;
|
||||
m_canProcessFP16 = false;
|
||||
DEBUG_ERROR_HR(status, "IddCxGetVersion Failed");
|
||||
return;
|
||||
}
|
||||
|
||||
m_iddCxVersion = ver.IddCxVersion;
|
||||
|
||||
#ifdef HAS_IDDCX_110
|
||||
const bool hasIddCx110DDIs =
|
||||
!!IDD_IS_FUNCTION_AVAILABLE(IddCxSwapChainReleaseAndAcquireBuffer2) &&
|
||||
!!IDD_IS_FUNCTION_AVAILABLE(IddCxMonitorQueryHardwareCursor3) &&
|
||||
!!IDD_IS_FUNCTION_AVAILABLE(IddCxMonitorUpdateModes2) &&
|
||||
IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxAdapterQueryTargetInfo) &&
|
||||
IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxAdapterCommitModes2) &&
|
||||
IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxParseMonitorDescription2) &&
|
||||
IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxMonitorQueryTargetModes2) &&
|
||||
IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxMonitorSetDefaultHdrMetaData) &&
|
||||
IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxMonitorSetGammaRamp);
|
||||
#else
|
||||
const bool hasIddCx110DDIs = false;
|
||||
#endif
|
||||
|
||||
m_hasIddCx110DDIs =
|
||||
m_iddCxVersion >= IDDCX_VERSION_1_10 && hasIddCx110DDIs;
|
||||
m_canProcessFP16 = !m_softwareMode && m_hasIddCx110DDIs;
|
||||
|
||||
DEBUG_INFO("IddCx version: 0x%04x", m_iddCxVersion);
|
||||
DEBUG_INFO("IddCx 1.10 HDR/WCG DDIs: %s",
|
||||
m_hasIddCx110DDIs ? "available" : "unavailable");
|
||||
if (m_softwareMode && m_hasIddCx110DDIs)
|
||||
DEBUG_INFO("HDR/WCG disabled for software rendering");
|
||||
}
|
||||
|
||||
void CDeviceContext::ScheduleInitRetry()
|
||||
{
|
||||
// Create the retry timer once; if it already exists it is either running or
|
||||
// will be (re)started below.
|
||||
if (!m_initTimer)
|
||||
{
|
||||
WDF_TIMER_CONFIG config;
|
||||
WDF_TIMER_CONFIG_INIT_PERIODIC(&config,
|
||||
[](WDFTIMER timer) -> void
|
||||
{
|
||||
WDFOBJECT parent = WdfTimerGetParentObject(timer);
|
||||
auto wrapper = WdfObjectGet_CDeviceContextWrapper(parent);
|
||||
wrapper->context->InitAdapter();
|
||||
},
|
||||
500);
|
||||
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_initTimer);
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DEBUG_ERROR_HR(status, "Init retry timer creation failed");
|
||||
m_initTimer = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
WdfTimerStart(m_initTimer, WDF_REL_TIMEOUT_IN_MS(500));
|
||||
}
|
||||
|
||||
void CDeviceContext::StopInitRetry()
|
||||
{
|
||||
if (m_initTimer)
|
||||
WdfTimerStop(m_initTimer, FALSE);
|
||||
}
|
||||
|
||||
void CDeviceContext::InitAdapter()
|
||||
{
|
||||
DEBUG_TRACE("InitAdapter");
|
||||
|
||||
// The adapter only needs to be created once. D0Entry and the retry timer can
|
||||
// both land here, so guard against re-entrancy and repeated creation.
|
||||
if (m_adapter)
|
||||
{
|
||||
DEBUG_TRACE("Adapter initialization skipped: adapter already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
LONG initExpected = 0;
|
||||
if (!m_initInProgress.compare_exchange_strong(initExpected, 1))
|
||||
{
|
||||
DEBUG_TRACE("Adapter initialization skipped: initialization already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
// At boot the IVSHMEM PCI device may not have enumerated yet. Rather than
|
||||
// silently abandoning the adapter (leaving the device loaded but with no
|
||||
// monitor), retry from a timer until the shared memory becomes available.
|
||||
if (!m_ivshmemOpened)
|
||||
{
|
||||
if (!m_ivshmem.Init() || !m_ivshmem.Open())
|
||||
{
|
||||
DEBUG_WARN("IVSHMEM not available yet, scheduling init retry");
|
||||
ScheduleInitRetry();
|
||||
m_initInProgress.store(0);
|
||||
return;
|
||||
}
|
||||
m_ivshmemOpened = true;
|
||||
}
|
||||
|
||||
// Select the render adapter before advertising capabilities. If no hardware
|
||||
// adapter is available, this is a software-rendered display and must remain
|
||||
// SDR-only; the software path must never depend on compute processing.
|
||||
m_havePreferredRenderAdapter = false;
|
||||
m_preferredRenderAdapter = {};
|
||||
IDXGIFactory1 * factory = NULL;
|
||||
HRESULT factoryStatus = CreateDXGIFactory1(
|
||||
__uuidof(IDXGIFactory1), (void **)&factory);
|
||||
if (FAILED(factoryStatus))
|
||||
DEBUG_ERROR_HR(factoryStatus, "CreateDXGIFactory Failed");
|
||||
else
|
||||
{
|
||||
for (UINT i = 0;; ++i)
|
||||
{
|
||||
IDXGIAdapter1 * dxgiAdapter = nullptr;
|
||||
HRESULT enumStatus = factory->EnumAdapters1(i, &dxgiAdapter);
|
||||
if (enumStatus == DXGI_ERROR_NOT_FOUND)
|
||||
break;
|
||||
if (FAILED(enumStatus))
|
||||
{
|
||||
DEBUG_ERROR_HR(enumStatus, "Failed to enumerate DXGI adapter %u", i);
|
||||
break;
|
||||
}
|
||||
|
||||
DXGI_ADAPTER_DESC1 adapterDesc = {};
|
||||
HRESULT descStatus = dxgiAdapter->GetDesc1(&adapterDesc);
|
||||
dxgiAdapter->Release();
|
||||
if (FAILED(descStatus))
|
||||
{
|
||||
DEBUG_ERROR_HR(descStatus, "Failed to query DXGI adapter %u", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) ||
|
||||
(adapterDesc.VendorId == 0x1414 && adapterDesc.DeviceId == 0x008c))
|
||||
{
|
||||
DEBUG_INFO("Ignoring software render adapter %ls", adapterDesc.Description);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((adapterDesc.VendorId == 0x1b36 && adapterDesc.DeviceId == 0x000d) || // QXL
|
||||
(adapterDesc.VendorId == 0x1234 && adapterDesc.DeviceId == 0x1111)) // QEMU Standard VGA
|
||||
{
|
||||
DEBUG_INFO("Ignoring display-only adapter %ls (vendor 0x%04x, device 0x%04x)",
|
||||
adapterDesc.Description, adapterDesc.VendorId, adapterDesc.DeviceId);
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Selected render adapter %ls (vendor 0x%04x, device 0x%04x)",
|
||||
adapterDesc.Description, adapterDesc.VendorId, adapterDesc.DeviceId);
|
||||
m_preferredRenderAdapter = adapterDesc.AdapterLuid;
|
||||
m_havePreferredRenderAdapter = true;
|
||||
break;
|
||||
}
|
||||
|
||||
factory->Release();
|
||||
}
|
||||
|
||||
m_softwareMode = !m_havePreferredRenderAdapter;
|
||||
if (m_softwareMode)
|
||||
DEBUG_INFO("No hardware render adapter available; using SDR software mode");
|
||||
|
||||
QueryIddCxCapabilities();
|
||||
DEBUG_TRACE("Initializing LGMP metadata");
|
||||
if (!InitializeLGMP())
|
||||
{
|
||||
m_initInProgress.store(0);
|
||||
return;
|
||||
}
|
||||
DEBUG_TRACE("Loading configured display modes");
|
||||
if (!m_displayConfiguration.Load(m_frameTransport.GetMemoryLimits()))
|
||||
{
|
||||
m_initInProgress.store(0);
|
||||
return;
|
||||
}
|
||||
DEBUG_TRACE("Initializing monitor EDID");
|
||||
m_displayConfiguration.InitializeEdid(CanProcessFP16());
|
||||
|
||||
const CDisplayConfiguration::Description description =
|
||||
m_displayConfiguration.GetDescription();
|
||||
DEBUG_INFO("Initializing adapter with %llu modes and a %u-byte EDID",
|
||||
(unsigned long long)description.modeCount,
|
||||
(UINT)description.edid.size());
|
||||
|
||||
IDDCX_ADAPTER_CAPS caps = {};
|
||||
caps.Size = sizeof(caps);
|
||||
|
||||
/**
|
||||
* For some reason if we do not set this flag sometimes windows will
|
||||
* refuse to enumerate our virtual monitor. Intel also noted in their
|
||||
* sources that if this is not set dynamic resolution changes from this
|
||||
* driver will not work. This behaviour is not documented by Microsoft.
|
||||
*/
|
||||
caps.Flags = IDDCX_ADAPTER_FLAGS_USE_SMALLEST_MODE;
|
||||
#ifdef HAS_IDDCX_110
|
||||
if (CanProcessFP16())
|
||||
caps.Flags |= IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16;
|
||||
#endif
|
||||
|
||||
caps.MaxMonitorsSupported = 1;
|
||||
caps.StaticDesktopReencodeFrameCount = 1;
|
||||
|
||||
caps.EndPointDiagnostics.Size = sizeof(caps.EndPointDiagnostics);
|
||||
caps.EndPointDiagnostics.GammaSupport = IDDCX_FEATURE_IMPLEMENTATION_NONE;
|
||||
caps.EndPointDiagnostics.TransmissionType = IDDCX_TRANSMISSION_TYPE_OTHER;
|
||||
|
||||
caps.EndPointDiagnostics.pEndPointFriendlyName = L"Looking Glass IDD Driver";
|
||||
caps.EndPointDiagnostics.pEndPointManufacturerName = L"Looking Glass";
|
||||
caps.EndPointDiagnostics.pEndPointModelName = L"Looking Glass";
|
||||
|
||||
IDDCX_ENDPOINT_VERSION ver = {};
|
||||
ver.Size = sizeof(ver);
|
||||
ver.MajorVer = 1;
|
||||
caps.EndPointDiagnostics.pFirmwareVersion = &ver;
|
||||
caps.EndPointDiagnostics.pHardwareVersion = &ver;
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES attr;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, CDeviceContextWrapper);
|
||||
|
||||
IDARG_IN_ADAPTER_INIT init = {};
|
||||
init.WdfDevice = m_wdfDevice;
|
||||
init.pCaps = ∩︀
|
||||
init.ObjectAttributes = &attr;
|
||||
|
||||
IDARG_OUT_ADAPTER_INIT initOut = {};
|
||||
DEBUG_INFO("Calling IddCxAdapterInitAsync with flags 0x%08x",
|
||||
caps.Flags);
|
||||
NTSTATUS status = IddCxAdapterInitAsync(&init, &initOut);
|
||||
if (!NT_SUCCESS(status) && CanProcessFP16())
|
||||
{
|
||||
DEBUG_WARN(
|
||||
"IddCxAdapterInitAsync rejected FP16 adapter capabilities (0x%08x), retrying without HDR/WCG",
|
||||
status);
|
||||
m_canProcessFP16 = false;
|
||||
// The monitor has not been created yet, so replace the provisional HDR
|
||||
// EDID before Windows can observe it.
|
||||
m_displayConfiguration.RebuildEdid(false);
|
||||
caps.Flags = (IDDCX_ADAPTER_FLAGS)(caps.Flags & ~IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16);
|
||||
ZeroMemory(&initOut, sizeof(initOut));
|
||||
status = IddCxAdapterInitAsync(&init, &initOut);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DEBUG_ERROR_HR(status, "IddCxAdapterInitAsync Failed");
|
||||
m_initInProgress.store(0);
|
||||
return;
|
||||
}
|
||||
|
||||
m_adapter = initOut.AdapterObject;
|
||||
if (!m_adapter)
|
||||
{
|
||||
DEBUG_ERROR("IddCxAdapterInitAsync succeeded without returning an adapter object");
|
||||
m_initInProgress.store(0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto * wrapper = WdfObjectGet_CDeviceContextWrapper(m_adapter);
|
||||
wrapper->context = this;
|
||||
DEBUG_INFO("IddCxAdapterInitAsync started successfully (adapter %p)",
|
||||
m_adapter);
|
||||
DEBUG_INFO("Adapter context attached; waiting for initialization callback");
|
||||
|
||||
// Adapter is up; no need to keep retrying.
|
||||
StopInitRetry();
|
||||
m_initInProgress.store(0);
|
||||
DEBUG_INFO("Adapter initialization request complete; returning to IddCx");
|
||||
}
|
||||
|
||||
void CDeviceContext::FinishAdapterInit(UINT connectorIndex)
|
||||
{
|
||||
// Try to co-exist with the virtual video device by telling IddCx which
|
||||
// hardware adapter we prefer to render on. Do this only after the adapter
|
||||
// has finished initializing, but before adding its monitor.
|
||||
if (m_havePreferredRenderAdapter)
|
||||
{
|
||||
IDARG_IN_ADAPTERSETRENDERADAPTER args = {};
|
||||
args.PreferredRenderAdapter = m_preferredRenderAdapter;
|
||||
IddCxAdapterSetRenderAdapter(m_adapter, &args);
|
||||
DEBUG_INFO("Preferred render adapter set");
|
||||
}
|
||||
|
||||
FinishInit(connectorIndex);
|
||||
}
|
||||
|
||||
void CDeviceContext::FinishInit(UINT connectorIndex)
|
||||
{
|
||||
CDisplayConfiguration::Description description =
|
||||
m_displayConfiguration.GetDescription();
|
||||
m_monitorManager.Create(
|
||||
connectorIndex, m_adapter, std::move(description.edid), this);
|
||||
}
|
||||
|
||||
void CDeviceContext::ReplugMonitor()
|
||||
{
|
||||
if (m_monitorManager.Replug() ==
|
||||
CMonitorManager::ReplugAction::CREATE)
|
||||
FinishInit(0);
|
||||
}
|
||||
|
||||
void CDeviceContext::ReloadSettings()
|
||||
{
|
||||
if (!m_displayConfiguration.ReloadSettings(
|
||||
m_frameTransport.GetMemoryLimits()))
|
||||
return;
|
||||
|
||||
ReplugMonitor();
|
||||
}
|
||||
|
||||
void CDeviceContext::OnMonitorDestroyed(IDDCX_MONITOR monitor)
|
||||
{
|
||||
m_monitorManager.OnDestroyed(monitor);
|
||||
}
|
||||
|
||||
void CDeviceContext::OnSwapChainAssigned()
|
||||
{
|
||||
m_monitorManager.OnSwapChainAssigned();
|
||||
}
|
||||
|
||||
void CDeviceContext::OnSwapChainReleased()
|
||||
{
|
||||
m_monitorManager.OnSwapChainReleased();
|
||||
}
|
||||
|
||||
void CDeviceContext::OnSwapChainReady()
|
||||
{
|
||||
const CMonitorManager::ReadyAction action =
|
||||
m_monitorManager.OnSwapChainReady();
|
||||
|
||||
// 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 (action.replug)
|
||||
m_monitorManager.QueueReplug();
|
||||
else if (action.setMode)
|
||||
g_pipe.SetDisplayMode(
|
||||
action.mode.width, action.mode.height, action.mode.refreshMilliHz);
|
||||
}
|
||||
|
||||
// Display configuration
|
||||
|
||||
void CDeviceContext::SetResolution(uint32_t width, uint32_t height)
|
||||
{
|
||||
const CDisplayConfiguration::ResolutionResult result =
|
||||
m_displayConfiguration.SetResolution(
|
||||
width, height, m_frameTransport.GetMemoryLimits());
|
||||
|
||||
switch (result.status)
|
||||
{
|
||||
case CDisplayConfiguration::ResolutionStatus::SUCCESS:
|
||||
m_monitorManager.RequestMode(result.mode);
|
||||
// IddCxMonitorUpdateModes[2] does not invalidate Windows' cached mode
|
||||
// list, so depart and re-arrive the monitor to rebuild the topology.
|
||||
ReplugMonitor();
|
||||
break;
|
||||
|
||||
case CDisplayConfiguration::ResolutionStatus::TOO_LARGE:
|
||||
g_pipe.ResolutionRejected(width, height, result.requiredMiB);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// LGMP transport
|
||||
|
||||
bool CDeviceContext::InitializeLGMP()
|
||||
{
|
||||
if (m_lgmpHost.IsInitialized())
|
||||
return true;
|
||||
|
||||
if (!m_lgmpHost.Initialize(m_ivshmem))
|
||||
return false;
|
||||
|
||||
// Preserve the shared-memory layout: frame queues precede the pointer queue
|
||||
// and its retained cursor and color-transform allocations.
|
||||
if (!m_frameTransport.Initialize() || !m_lgmpControl.Initialize())
|
||||
return false;
|
||||
|
||||
m_frameTransport.SealMemoryLayout();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDeviceContext::SetupLGMP(size_t alignSize)
|
||||
{
|
||||
// Frame buffers cannot be allocated until the GPU-specific alignment is
|
||||
// known. The swap-chain path may call this again after setup completed.
|
||||
if (m_frameTransport.GetMaxFrameSize())
|
||||
return true;
|
||||
|
||||
if (!InitializeLGMP() || !m_frameTransport.Setup(alignSize))
|
||||
return false;
|
||||
|
||||
WDF_TIMER_CONFIG config;
|
||||
WDF_TIMER_CONFIG_INIT_PERIODIC(&config,
|
||||
[](WDFTIMER timer) -> void
|
||||
{
|
||||
WDFOBJECT parent = WdfTimerGetParentObject(timer);
|
||||
auto wrapper = WdfObjectGet_CDeviceContextWrapper(parent);
|
||||
wrapper->context->LGMPTimer();
|
||||
},
|
||||
10);
|
||||
config.AutomaticSerialization = FALSE;
|
||||
|
||||
/**
|
||||
* Documentation states that Dispatch is not available under UMDF, however
|
||||
* using Passive returns a not-supported error and Dispatch works.
|
||||
*/
|
||||
WDF_OBJECT_ATTRIBUTES attribs;
|
||||
WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
|
||||
attribs.ParentObject = m_wdfDevice;
|
||||
attribs.ExecutionLevel = WdfExecutionLevelDispatch;
|
||||
|
||||
NTSTATUS status = WdfTimerCreate(
|
||||
&config, &attribs, &m_lgmpTimer);
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
DEBUG_ERROR_HR(status, "Timer creation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
WdfTimerStart(m_lgmpTimer, WDF_REL_TIMEOUT_IN_MS(10));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDeviceContext::LGMPTimer()
|
||||
{
|
||||
// Monitor work is deferred off IddCx callback threads.
|
||||
switch (m_monitorManager.TakeDeferredAction())
|
||||
{
|
||||
case CMonitorManager::DeferredAction::CREATE:
|
||||
FinishInit(0);
|
||||
return;
|
||||
|
||||
case CMonitorManager::DeferredAction::REPLUG:
|
||||
ReplugMonitor();
|
||||
return;
|
||||
|
||||
case CMonitorManager::DeferredAction::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
const LGMP_STATUS processStatus = m_lgmpHost.Process();
|
||||
if (processStatus != LGMP_OK)
|
||||
{
|
||||
if (processStatus == LGMP_ERR_CORRUPTED)
|
||||
{
|
||||
DEBUG_WARN(
|
||||
"LGMP reported the shared memory has been corrupted, attempting to recover\n");
|
||||
// TODO: reinitialize LGMP.
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_ERROR("lgmpHostProcess Failed: %s",
|
||||
lgmpStatusString(processStatus));
|
||||
// TODO: shut down LGMP.
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t now = CFrameScheduler::Nanotime();
|
||||
|
||||
// Take the frame subscriber snapshot before processing scheduling messages,
|
||||
// then publish both updates together just as the original timer did.
|
||||
const CFrameTransport::SubscriberSnapshot subscribers =
|
||||
m_frameTransport.SnapshotSubscribers();
|
||||
|
||||
uint8_t data[LGMP_MSGS_SIZE];
|
||||
size_t size;
|
||||
uint32_t sourceClientID;
|
||||
LGMP_STATUS status;
|
||||
while ((status = m_lgmpControl.ReadDataWithSource(
|
||||
data, &size, &sourceClientID)) == LGMP_OK)
|
||||
{
|
||||
KVMFRMessage * msg = reinterpret_cast<KVMFRMessage *>(data);
|
||||
switch (msg->type)
|
||||
{
|
||||
case KVMFR_MESSAGE_SETCURSORPOS:
|
||||
{
|
||||
KVMFRSetCursorPos * position =
|
||||
reinterpret_cast<KVMFRSetCursorPos *>(msg);
|
||||
g_pipe.SetCursorPos(position->x, position->y);
|
||||
break;
|
||||
}
|
||||
|
||||
case KVMFR_MESSAGE_WINDOWSIZE:
|
||||
{
|
||||
KVMFRWindowSize * window =
|
||||
reinterpret_cast<KVMFRWindowSize *>(msg);
|
||||
SetResolution(window->w, window->h);
|
||||
break;
|
||||
}
|
||||
|
||||
case KVMFR_MESSAGE_FRAME_SCHEDULE:
|
||||
{
|
||||
const KVMFRFrameSchedule * schedule =
|
||||
reinterpret_cast<KVMFRFrameSchedule *>(msg);
|
||||
const bool valid = size == sizeof(*schedule) &&
|
||||
m_frameTransport.UpdateSchedule(
|
||||
sourceClientID, *schedule, now);
|
||||
if (!valid)
|
||||
DEBUG_WARN("Ignoring invalid KVMFR frame schedule");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_lgmpControl.AckData();
|
||||
}
|
||||
|
||||
m_frameTransport.FinalizeSubscribers(subscribers, now);
|
||||
|
||||
if (m_lgmpControl.HasNewSubscribers())
|
||||
m_lgmpControl.ResendState();
|
||||
}
|
||||
126
idd/LGIdd/display/device/CDeviceContext.h
Normal file
126
idd/LGIdd/display/device/CDeviceContext.h
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wdf.h>
|
||||
#include <IddCx.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "display/CDisplayConfiguration.h"
|
||||
#include "display/CMonitorManager.h"
|
||||
#include "transport/CFrameTransport.h"
|
||||
#include "transport/CIVSHMEM.h"
|
||||
#include "transport/CLGMPControl.h"
|
||||
#include "transport/CLGMPHost.h"
|
||||
|
||||
class CDeviceContext
|
||||
{
|
||||
private:
|
||||
WDFDEVICE m_wdfDevice;
|
||||
IDDCX_ADAPTER m_adapter = nullptr;
|
||||
LUID m_preferredRenderAdapter = {};
|
||||
bool m_havePreferredRenderAdapter = false;
|
||||
|
||||
// At boot IVSHMEM may not have enumerated yet. The retry timer and atomic
|
||||
// gate keep adapter creation single-threaded until it becomes available.
|
||||
WDFTIMER m_initTimer = nullptr;
|
||||
bool m_ivshmemOpened = false;
|
||||
std::atomic<LONG> m_initInProgress = 0;
|
||||
|
||||
CIVSHMEM m_ivshmem;
|
||||
CLGMPHost m_lgmpHost;
|
||||
CLGMPControl m_lgmpControl;
|
||||
CFrameTransport m_frameTransport;
|
||||
CDisplayConfiguration m_displayConfiguration;
|
||||
CMonitorManager m_monitorManager;
|
||||
|
||||
WDFTIMER m_lgmpTimer = nullptr;
|
||||
|
||||
UINT m_iddCxVersion = 0;
|
||||
bool m_hasIddCx110DDIs = false;
|
||||
bool m_canProcessFP16 = false;
|
||||
bool m_softwareMode = true;
|
||||
|
||||
void QueryIddCxCapabilities();
|
||||
|
||||
void ScheduleInitRetry();
|
||||
void StopInitRetry();
|
||||
|
||||
bool InitializeLGMP();
|
||||
void LGMPTimer();
|
||||
void SetResolution(uint32_t width, uint32_t height);
|
||||
|
||||
public:
|
||||
explicit CDeviceContext(_In_ WDFDEVICE wdfDevice);
|
||||
~CDeviceContext();
|
||||
|
||||
CDeviceContext(const CDeviceContext&) = delete;
|
||||
CDeviceContext& operator=(const CDeviceContext&) = delete;
|
||||
|
||||
bool SetupLGMP(size_t alignSize);
|
||||
|
||||
void InitAdapter();
|
||||
void FinishAdapterInit(UINT connectorIndex);
|
||||
void FinishInit(UINT connectorIndex);
|
||||
void ReloadSettings();
|
||||
void ReplugMonitor();
|
||||
|
||||
void OnMonitorDestroyed(IDDCX_MONITOR monitor);
|
||||
void OnSwapChainAssigned();
|
||||
void OnSwapChainReleased();
|
||||
void OnSwapChainReady();
|
||||
|
||||
bool HasIddCx110DDIs() const { return m_hasIddCx110DDIs; }
|
||||
bool CanProcessFP16 () const { return m_canProcessFP16; }
|
||||
bool IsSoftwareMode () const { return m_softwareMode; }
|
||||
|
||||
CFrameTransport& GetFrameTransport()
|
||||
{
|
||||
return m_frameTransport;
|
||||
}
|
||||
|
||||
CLGMPControl& GetLGMPControl()
|
||||
{
|
||||
return m_lgmpControl;
|
||||
}
|
||||
|
||||
CDisplayConfiguration& GetDisplayConfiguration()
|
||||
{
|
||||
return m_displayConfiguration;
|
||||
}
|
||||
};
|
||||
|
||||
struct CDeviceContextWrapper
|
||||
{
|
||||
CDeviceContext * context;
|
||||
|
||||
void Cleanup()
|
||||
{
|
||||
delete context;
|
||||
context = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
WDF_DECLARE_CONTEXT_TYPE(CDeviceContextWrapper);
|
||||
Reference in New Issue
Block a user