[idd] transport: abstract LGMP implementation

Introduce transport, frame, and control interfaces with an LGMP factory
backend.

Move LGMP and IVSHMEM implementation details under transport/lgmp and
expose direct frame-buffer memory through a neutral capability.
This commit is contained in:
Geoffrey McRae
2026-08-07 16:39:07 +10:00
parent 30a1383d5e
commit 6725133375
46 changed files with 1026 additions and 480 deletions

View File

@@ -21,7 +21,6 @@
#include "display/CDisplayConfiguration.h"
#include "CDebug.h"
#include "common/LGMPConfig.h"
#include "util/CSRWLock.h"
#include <d3d12.h>
@@ -59,12 +58,12 @@ bool CDisplayConfiguration::CalculateFrameSize(
bool CDisplayConfiguration::GetResolutionMemoryRequirements(
uint32_t width, uint32_t height, UINT64 alignment,
const FrameMemoryLimits& limits, UINT64& frameSize, UINT64& sharedSize)
const FrameMemoryLimits& limits, UINT64& frameSize, UINT64& requiredSize)
{
frameSize = 0;
sharedSize = 0;
frameSize = 0;
requiredSize = 0;
if (!alignment || !limits.frameMemoryOffset ||
if (!alignment || !limits.frameMemoryOffset || !limits.bufferCount ||
!CalculateFrameSize(width, height, frameSize))
return false;
@@ -76,12 +75,12 @@ bool CDisplayConfiguration::GetResolutionMemoryRequirements(
if (!AlignUp(limits.frameMemoryOffset, alignment, frameMemoryStart))
return false;
sharedSize = frameMemoryStart +
frameAllocationSize * LGMP_Q_FRAME_BUFFER_LEN;
requiredSize = frameMemoryStart +
frameAllocationSize * limits.bufferCount;
return true;
}
uint32_t CDisplayConfiguration::RecommendedIVSHMEMSizeMiB(
uint32_t CDisplayConfiguration::RecommendedMemorySizeMiB(
UINT64 requiredSize)
{
UINT64 sizeMiB = requiredSize / 1048576;
@@ -135,10 +134,10 @@ bool CDisplayConfiguration::LoadModes(const FrameMemoryLimits& limits)
for (const auto& configuredMode : configuredModes)
{
UINT64 frameSize;
UINT64 requiredIVSHMEMSize;
UINT64 requiredMemorySize;
if (!GetResolutionMemoryRequirements(configuredMode.width,
configuredMode.height, alignment, limits, frameSize,
requiredIVSHMEMSize))
requiredMemorySize))
{
DEBUG_WARN("Filtering invalid %s mode %ux%u@%.3f",
configuredMode.extraMode ? "extra" : "configured",
@@ -147,15 +146,15 @@ bool CDisplayConfiguration::LoadModes(const FrameMemoryLimits& limits)
continue;
}
if (requiredIVSHMEMSize > limits.sharedSize)
if (requiredMemorySize > limits.capacity)
{
DEBUG_WARN(
"Filtering %s mode %ux%u@%.3f: requires %llu bytes of IVSHMEM, only %llu bytes are available",
"Filtering %s mode %ux%u@%.3f: requires %llu bytes of transport memory, only %llu bytes are available",
configuredMode.extraMode ? "extra" : "configured",
configuredMode.width, configuredMode.height,
configuredMode.refreshMilliHz / 1000.0,
(unsigned long long)requiredIVSHMEMSize,
(unsigned long long)limits.sharedSize);
(unsigned long long)requiredMemorySize,
(unsigned long long)limits.capacity);
continue;
}
@@ -170,7 +169,7 @@ bool CDisplayConfiguration::LoadModes(const FrameMemoryLimits& limits)
if (newModes.empty())
{
DEBUG_ERROR("No configured display modes fit in IVSHMEM");
DEBUG_ERROR("No configured display modes fit in transport memory");
return false;
}
@@ -225,20 +224,20 @@ CDisplayConfiguration::SetResolution(
ResolutionResult result;
UINT64 frameSize;
UINT64 requiredIVSHMEMSize;
UINT64 requiredMemorySize;
if (!GetResolutionMemoryRequirements(width, height, limits.alignment,
limits, frameSize, requiredIVSHMEMSize))
limits, frameSize, requiredMemorySize))
{
DEBUG_WARN("Ignoring invalid resolution request: %ux%u", width, height);
return result;
}
if (requiredIVSHMEMSize > limits.sharedSize)
if (requiredMemorySize > limits.capacity)
{
result.status = ResolutionStatus::TOO_LARGE;
result.requiredMiB = RecommendedIVSHMEMSizeMiB(requiredIVSHMEMSize);
result.requiredMiB = RecommendedMemorySizeMiB(requiredMemorySize);
DEBUG_WARN(
"Refusing resolution %ux%u: frame requires %llu bytes, only %llu bytes are available; IVSHMEM must be at least %u MiB",
"Refusing resolution %ux%u: frame requires %llu bytes, only %llu bytes are available; transport memory must be at least %u MiB",
width, height,
(unsigned long long)frameSize,
(unsigned long long)limits.maxFrameSize,

View File

@@ -74,8 +74,8 @@ private:
UINT64& frameSize);
static bool GetResolutionMemoryRequirements(uint32_t width,
uint32_t height, UINT64 alignment, const FrameMemoryLimits& limits,
UINT64& frameSize, UINT64& sharedSize);
static uint32_t RecommendedIVSHMEMSizeMiB(UINT64 requiredSize);
UINT64& frameSize, UINT64& requiredSize);
static uint32_t RecommendedMemorySizeMiB(UINT64 requiredSize);
public:
explicit CDisplayConfiguration(CSettings& settings);

View File

@@ -57,7 +57,7 @@ private:
IDDCX_MONITOR m_monitor = nullptr;
// Guards the monitor/replug/swap-chain state. These values are touched by
// IddCx callback threads, the swap-chain thread, and the LGMP timer.
// IddCx callback threads, the swap-chain thread, and the transport timer.
SRWLOCK m_lock = SRWLOCK_INIT;
bool m_replugMonitor = false;

View File

@@ -22,6 +22,8 @@
#include "display/IddCxCompat.h"
#include "transport/CPipeServer.h"
#include "transport/IFrameTransport.h"
#include "transport/TransportFactory.h"
#include "CDebug.h"
#include <dxgi1_2.h>
@@ -33,8 +35,7 @@ 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_transport(CreateTransport()),
m_displayConfiguration(g_settings)
{
}
@@ -49,10 +50,10 @@ CDeviceContext::~CDeviceContext()
m_initTimer = nullptr;
}
if (m_lgmpTimer)
if (m_transportTimer)
{
WdfTimerStop(m_lgmpTimer, TRUE);
m_lgmpTimer = nullptr;
WdfTimerStop(m_transportTimer, TRUE);
m_transportTimer = nullptr;
}
}
@@ -156,19 +157,32 @@ void CDeviceContext::InitAdapter()
return;
}
// At boot the IVSHMEM PCI device may not have enumerated yet. Rather than
// At boot the selected transport may not be available 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)
// monitor), retry from a timer until it can be opened.
if (!m_transportOpened)
{
if (!m_ivshmem.Init() || !m_ivshmem.Open())
if (!m_transport)
{
DEBUG_WARN("IVSHMEM not available yet, scheduling init retry");
ScheduleInitRetry();
DEBUG_ERROR("Failed to create the frame transport");
m_initInProgress.store(0);
return;
}
m_ivshmemOpened = true;
const ITransport::OpenResult result = m_transport->Open();
if (result != ITransport::OpenResult::SUCCESS)
{
if (result == ITransport::OpenResult::RETRY)
{
DEBUG_WARN("Frame transport not available yet, scheduling init retry");
ScheduleInitRetry();
}
else
DEBUG_ERROR("Failed to open the frame transport");
m_initInProgress.store(0);
return;
}
m_transportOpened = true;
}
// Select the render adapter before advertising capabilities. If no hardware
@@ -234,14 +248,14 @@ void CDeviceContext::InitAdapter()
DEBUG_INFO("No hardware render adapter available; using SDR software mode");
QueryIddCxCapabilities();
DEBUG_TRACE("Initializing LGMP metadata");
if (!InitializeLGMP())
DEBUG_TRACE("Initializing frame transport metadata");
if (!InitializeTransport())
{
m_initInProgress.store(0);
return;
}
DEBUG_TRACE("Loading configured display modes");
if (!m_displayConfiguration.Load(m_frameTransport.GetMemoryLimits()))
if (!m_displayConfiguration.Load(m_transport->GetMemoryLimits()))
{
m_initInProgress.store(0);
return;
@@ -374,7 +388,7 @@ void CDeviceContext::ReplugMonitor()
void CDeviceContext::ReloadSettings()
{
if (!m_displayConfiguration.ReloadSettings(
m_frameTransport.GetMemoryLimits()))
m_transport->GetMemoryLimits()))
return;
ReplugMonitor();
@@ -417,7 +431,7 @@ void CDeviceContext::SetResolution(uint32_t width, uint32_t height)
{
const CDisplayConfiguration::ResolutionResult result =
m_displayConfiguration.SetResolution(
width, height, m_frameTransport.GetMemoryLimits());
width, height, m_transport->GetMemoryLimits());
switch (result.status)
{
@@ -437,33 +451,21 @@ void CDeviceContext::SetResolution(uint32_t width, uint32_t height)
}
}
// LGMP transport
// Frame transport
bool CDeviceContext::InitializeLGMP()
bool CDeviceContext::InitializeTransport()
{
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;
return m_transport && m_transport->Initialize();
}
bool CDeviceContext::SetupLGMP(size_t alignSize)
bool CDeviceContext::SetupTransport(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())
if (m_transport->Frames().GetMaxFrameSize())
return true;
if (!InitializeLGMP() || !m_frameTransport.Setup(alignSize))
if (!InitializeTransport() || !m_transport->Setup(alignSize))
return false;
WDF_TIMER_CONFIG config;
@@ -472,7 +474,7 @@ bool CDeviceContext::SetupLGMP(size_t alignSize)
{
WDFOBJECT parent = WdfTimerGetParentObject(timer);
auto wrapper = WdfObjectGet_CDeviceContextWrapper(parent);
wrapper->context->LGMPTimer();
wrapper->context->TransportTimer();
},
10);
config.AutomaticSerialization = FALSE;
@@ -487,18 +489,18 @@ bool CDeviceContext::SetupLGMP(size_t alignSize)
attribs.ExecutionLevel = WdfExecutionLevelDispatch;
NTSTATUS status = WdfTimerCreate(
&config, &attribs, &m_lgmpTimer);
&config, &attribs, &m_transportTimer);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR_HR(status, "Timer creation failed");
return false;
}
WdfTimerStart(m_lgmpTimer, WDF_REL_TIMEOUT_IN_MS(10));
WdfTimerStart(m_transportTimer, WDF_REL_TIMEOUT_IN_MS(10));
return true;
}
void CDeviceContext::LGMPTimer()
void CDeviceContext::TransportTimer()
{
// Monitor work is deferred off IddCx callback threads.
switch (m_monitorManager.TakeDeferredAction())
@@ -515,74 +517,15 @@ void CDeviceContext::LGMPTimer()
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();
m_transport->Process(*this);
}
void CDeviceContext::OnSetCursorPos(int32_t x, int32_t y)
{
g_pipe.SetCursorPos(x, y);
}
void CDeviceContext::OnSetResolution(uint32_t width, uint32_t height)
{
SetResolution(width, height);
}

View File

@@ -25,17 +25,15 @@
#include <IddCx.h>
#include <atomic>
#include <memory>
#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"
#include "transport/ITransport.h"
class CDeviceContext
class CDeviceContext : private ITransportEvents
{
private:
WDFDEVICE m_wdfDevice;
@@ -43,20 +41,17 @@ private:
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;
// At boot the selected transport may not be available yet. The retry timer
// and atomic gate keep adapter creation single-threaded until it is ready.
WDFTIMER m_initTimer = nullptr;
bool m_transportOpened = 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;
std::unique_ptr<ITransport> m_transport;
CDisplayConfiguration m_displayConfiguration;
CMonitorManager m_monitorManager;
WDFTIMER m_lgmpTimer = nullptr;
WDFTIMER m_transportTimer = nullptr;
UINT m_iddCxVersion = 0;
bool m_hasIddCx110DDIs = false;
@@ -68,8 +63,10 @@ private:
void ScheduleInitRetry();
void StopInitRetry();
bool InitializeLGMP();
void LGMPTimer();
bool InitializeTransport();
void TransportTimer();
void OnSetCursorPos(int32_t x, int32_t y) override;
void OnSetResolution(uint32_t width, uint32_t height) override;
void SetResolution(uint32_t width, uint32_t height);
public:
@@ -79,7 +76,7 @@ public:
CDeviceContext(const CDeviceContext&) = delete;
CDeviceContext& operator=(const CDeviceContext&) = delete;
bool SetupLGMP(size_t alignSize);
bool SetupTransport(size_t alignSize);
void InitAdapter();
void FinishAdapterInit(UINT connectorIndex);
@@ -96,14 +93,9 @@ public:
bool CanProcessFP16 () const { return m_canProcessFP16; }
bool IsSoftwareMode () const { return m_softwareMode; }
CFrameTransport& GetFrameTransport()
ITransport& GetTransport()
{
return m_frameTransport;
}
CLGMPControl& GetLGMPControl()
{
return m_lgmpControl;
return *m_transport;
}
CDisplayConfiguration& GetDisplayConfiguration()

View File

@@ -53,7 +53,7 @@ NTSTATUS CMonitorContext::AssignSwapChain(
// Build the D3D11 device into a local so the member is never observed
// half-constructed. The worker binds it before performing the expensive
// D3D12, LGMP and post-processing initialization.
// D3D12, transport and post-processing initialization.
auto dx11Device = std::make_shared<CD3D11Device>(renderAdapter);
const HRESULT initStatus = dx11Device->Init();
if (FAILED(initStatus))