Files
LookingGlass/idd/LGIdd/CIndirectDeviceContext.cpp
Geoffrey McRae 515f4ba06f [client/host/idd/obs] scheduler: validate phase feedback
Bump KVMFR to version 28 and attach generation, epoch, and deadline
identities to cadence deliveries and their timing records.

Accept phase corrections only for the exact completed publication. Keep
VM and client clocks independent by exchanging relative phase errors.

Make submission, completion, and work-estimate accounting best effort
so scheduler state cannot delay GPU submission or frame publication.
2026-08-06 18:02:08 +10:00

2416 lines
75 KiB
C++

/**
* 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 "CIndirectDeviceContext.h"
#include "CIndirectMonitorContext.h"
#include "CSettings.h"
#include "CPlatformInfo.h"
#include "CPipeServer.h"
#include "CDebug.h"
#include "VersionInfo.h"
#include <dxgi1_2.h>
#include <sstream>
static const struct LGMPQueueConfig FRAME_QUEUE_CONFIG =
{
LGMP_Q_FRAME, //queueID
LGMP_Q_FRAME_LEN, //numMessages
1000 //subTimeout
};
static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
{
LGMP_Q_POINTER, //queueID
LGMP_Q_POINTER_LEN, //numMesages
1000 //subTimeout
};
static uint64_t FrameScheduleToken(
const CFrameScheduler::Schedule& schedule)
{
return static_cast<uint64_t>(schedule.epoch) << 32 |
schedule.deliveryDeadlineSerial;
}
static bool FrameScheduleMatches(
const CFrameScheduler::Schedule& a,
const CFrameScheduler::Schedule& b)
{
return a.clientID == b.clientID &&
a.generation == b.generation &&
a.epoch == b.epoch;
}
static const UINT IDDCX_VERSION_1_10 = 0x1A00;
static const UINT64 FRAME_BYTES_PER_PIXEL = 4;
static bool AlignUp(UINT64 value, UINT64 alignment, UINT64& result)
{
if (!alignment || (alignment & (alignment - 1)))
return false;
const UINT64 mask = alignment - 1;
result = (value + mask) & ~mask;
return true;
}
static bool CalculateFrameSize(uint32_t width, uint32_t height,
UINT64& frameSize)
{
frameSize = 0;
if (!width || !height)
return false;
UINT64 pitch;
if (!AlignUp((UINT64)width * FRAME_BYTES_PER_PIXEL,
D3D12_TEXTURE_DATA_PITCH_ALIGNMENT, pitch))
return false;
frameSize = pitch * height;
return true;
}
static uint32_t RecommendedIVSHMEMSizeMiB(UINT64 requiredSize)
{
UINT64 sizeMiB = requiredSize / 1048576;
if (requiredSize % 1048576)
++sizeMiB;
UINT64 result = 1;
while (result < sizeMiB && result <= UINT32_MAX / 2)
result <<= 1;
return result < sizeMiB ? UINT32_MAX : (uint32_t)result;
}
#ifdef HAS_IDDCX_110
static inline IDDCX_WIRE_BITS_PER_COMPONENT GetWireBitsPerComponent(bool hdr)
{
IDDCX_WIRE_BITS_PER_COMPONENT bits = {};
// This describes the virtual monitor wire, not the swap-chain format.
// HDR uses a 10-bpc PQ wire while CAN_PROCESS_FP16 requests the FP16/scRGB
// source surface that Looking Glass converts for transport.
bits.Rgb = IDDCX_BITS_PER_COMPONENT_8;
if (hdr)
bits.Rgb = (IDDCX_BITS_PER_COMPONENT)(bits.Rgb |
IDDCX_BITS_PER_COMPONENT_10);
bits.YCbCr444 = IDDCX_BITS_PER_COMPONENT_NONE;
bits.YCbCr422 = IDDCX_BITS_PER_COMPONENT_NONE;
bits.YCbCr420 = IDDCX_BITS_PER_COMPONENT_NONE;
return bits;
}
#endif
void CIndirectDeviceContext::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");
}
bool CIndirectDeviceContext::PopulateDefaultModes()
{
const CSettings::DisplayModes configuredModes =
g_settings.LoadModes();
// Build the new mode list into a local first so we only hold the lock for
// the swap. IddCx readers may be iterating the live container on another
// thread; a clear()/push_back() under them would reallocate the backing
// store and crash. std::move makes the publish a pointer swap.
CSettings::DisplayModes newModes;
newModes.reserve(configuredModes.size());
const UINT64 alignment = m_alignSize ? m_alignSize :
D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
bool hasPreferred = false;
for (const auto& configuredMode : configuredModes)
{
UINT64 frameSize;
UINT64 requiredIVSHMEMSize;
if (!GetResolutionMemoryRequirements(configuredMode.width,
configuredMode.height, alignment, frameSize, requiredIVSHMEMSize))
{
DEBUG_WARN("Filtering invalid %s mode %ux%u@%u",
configuredMode.extraMode ? "extra" : "configured",
configuredMode.width, configuredMode.height,
configuredMode.refresh);
continue;
}
if (requiredIVSHMEMSize > m_ivshmem.GetSize())
{
DEBUG_WARN(
"Filtering %s mode %ux%u@%u: requires %llu bytes of IVSHMEM, only %llu bytes are available",
configuredMode.extraMode ? "extra" : "configured",
configuredMode.width, configuredMode.height,
configuredMode.refresh,
(unsigned long long)requiredIVSHMEMSize,
(unsigned long long)m_ivshmem.GetSize());
continue;
}
CSettings::DisplayMode mode = configuredMode;
if (mode.preferred)
{
mode.preferred = !hasPreferred;
hasPreferred = true;
}
newModes.push_back(mode);
}
if (newModes.empty())
{
DEBUG_ERROR("No configured display modes fit in IVSHMEM");
return false;
}
// ExtraMode may have been the preferred mode. If it did not fit, promote
// the first remaining mode so the list still has a valid preference.
if (!hasPreferred)
newModes.front().preferred = true;
AcquireSRWLockExclusive(&m_modeLock);
m_displayModes = std::move(newModes);
ReleaseSRWLockExclusive(&m_modeLock);
return true;
}
void CIndirectDeviceContext::InitializeEdid()
{
AcquireSRWLockExclusive(&m_modeLock);
if (!m_edid.Size())
m_edid.Build(CanProcessFP16());
ReleaseSRWLockExclusive(&m_modeLock);
}
void CIndirectDeviceContext::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_CIndirectDeviceContextWrapper(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 CIndirectDeviceContext::StopInitRetry()
{
if (m_initTimer)
WdfTimerStop(m_initTimer, FALSE);
}
void CIndirectDeviceContext::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 (!PopulateDefaultModes())
{
m_initInProgress.store(0);
return;
}
DEBUG_TRACE("Initializing monitor EDID");
InitializeEdid();
AcquireSRWLockShared(&m_modeLock);
const size_t modeCount = m_displayModes.size();
const UINT edidSize = m_edid.Size();
ReleaseSRWLockShared(&m_modeLock);
DEBUG_INFO("Initializing adapter with %llu modes and a %u-byte EDID",
(unsigned long long)modeCount, edidSize);
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, CIndirectDeviceContextWrapper);
IDARG_IN_ADAPTER_INIT init = {};
init.WdfDevice = m_wdfDevice;
init.pCaps = &caps;
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.
AcquireSRWLockExclusive(&m_modeLock);
m_edid.Build(false);
ReleaseSRWLockExclusive(&m_modeLock);
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_CIndirectDeviceContextWrapper(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 CIndirectDeviceContext::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 CIndirectDeviceContext::FinishInit(UINT connectorIndex)
{
DEBUG_INFO("Creating monitor on connector %u", connectorIndex);
// We support a single monitor; never create a second one if one already
// exists (a replug must clear m_monitor via departure first).
AcquireSRWLockExclusive(&m_stateLock);
bool haveMonitor = m_monitor != WDF_NO_HANDLE;
ReleaseSRWLockExclusive(&m_stateLock);
if (haveMonitor)
{
DEBUG_WARN("FinishInit skipped: a monitor already exists");
return;
}
WDF_OBJECT_ATTRIBUTES attr;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, CIndirectMonitorContextWrapper);
// Take a private copy of the immutable EDID. The copy lives for the duration
// of the synchronous create call below.
std::vector<BYTE> edid;
AcquireSRWLockShared(&m_modeLock);
edid.assign(m_edid.Data(), m_edid.Data() + m_edid.Size());
ReleaseSRWLockShared(&m_modeLock);
DEBUG_INFO("Using %llu-byte monitor EDID", (unsigned long long)edid.size());
IDDCX_MONITOR_INFO info = {};
info.Size = sizeof(info);
info.MonitorType = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
info.ConnectorIndex = connectorIndex;
info.MonitorDescription.Size = sizeof(info.MonitorDescription);
info.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID;
info.MonitorDescription.DataSize = (UINT)edid.size();
info.MonitorDescription.pData = edid.empty() ? nullptr : edid.data();
HRESULT hr = CoCreateGuid(&info.MonitorContainerId);
if (FAILED(hr))
{
DEBUG_ERROR_HR(hr, "Failed to create the monitor container ID");
return;
}
IDARG_IN_MONITORCREATE create = {};
create.ObjectAttributes = &attr;
create.pMonitorInfo = &info;
IDARG_OUT_MONITORCREATE createOut = {};
NTSTATUS status = IddCxMonitorCreate(m_adapter, &create, &createOut);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR_HR(status, "IddCxMonitorCreate Failed");
return;
}
DEBUG_INFO("Monitor object created (%p)", createOut.MonitorObject);
AcquireSRWLockExclusive(&m_stateLock);
m_monitor = createOut.MonitorObject;
ReleaseSRWLockExclusive(&m_stateLock);
auto * wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(m_monitor);
wrapper->context = new CIndirectMonitorContext(m_monitor, this);
IDARG_OUT_MONITORARRIVAL out = {};
status = IddCxMonitorArrival(m_monitor, &out);
if (FAILED(status))
{
DEBUG_ERROR_HR(status, "IddCxMonitorArrival Failed");
return;
}
DEBUG_INFO("Monitor arrival reported successfully");
}
void CIndirectDeviceContext::ReplugMonitor()
{
AcquireSRWLockExclusive(&m_stateLock);
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;
}
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.
m_finishInitQueued.store(0);
FinishInit(0);
return;
}
// 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_monitorDeparted = false;
m_waitForSwapChainRelease = m_swapChainAssigned;
m_monitor = nullptr;
ReleaseSRWLockExclusive(&m_stateLock);
DEBUG_TRACE("ReplugMonitor");
NTSTATUS status = IddCxMonitorDeparture(monitor);
if (!NT_SUCCESS(status))
{
AcquireSRWLockExclusive(&m_stateLock);
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;
}
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)
m_finishInitQueued.store(1);
}
void CIndirectDeviceContext::OnMonitorDestroyed(IDDCX_MONITOR monitor)
{
AcquireSRWLockExclusive(&m_stateLock);
if (m_monitor == monitor)
m_monitor = nullptr;
ReleaseSRWLockExclusive(&m_stateLock);
}
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)
m_finishInitQueued.store(1);
}
void CIndirectDeviceContext::OnSwapChainReady()
{
bool replug = false;
bool doSetMode = false;
CSettings::DisplayMode mode = {};
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;
}
// Do not consume the requested mode on an intermediate replacement swap
// chain. The last coalesced replug must be the one that applies it.
if (!replug && m_doSetMode)
{
mode = m_setMode;
m_doSetMode = false;
doSetMode = 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)
m_replugQueued.store(1);
else if (doSetMode)
g_pipe.SetDisplayMode(mode.width, mode.height, mode.refresh);
}
static inline void FillSignalInfo(DISPLAYCONFIG_VIDEO_SIGNAL_INFO& signal,
const CSettings::DisplayMode& mode, bool monitorMode)
{
CEdid::Timing timing;
if (!CEdid::GetTiming(timing, mode))
return;
signal.activeSize.cx = timing.hActive;
signal.activeSize.cy = timing.vActive;
signal.totalSize.cx = timing.hActive + timing.hBlank;
signal.totalSize.cy = timing.vActive + timing.vBlank;
signal.AdditionalSignalInfo.vSyncFreqDivider = monitorMode ? 0 : 1;
signal.AdditionalSignalInfo.videoStandard = 255;
signal.vSyncFreq.Numerator = mode.refresh;
signal.vSyncFreq.Denominator = 1;
signal.hSyncFreq.Numerator = mode.refresh * signal.totalSize.cy;
signal.hSyncFreq.Denominator = 1;
signal.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;
signal.pixelRate = timing.pixelClock;
}
NTSTATUS CIndirectDeviceContext::ParseMonitorDescription(
const IDARG_IN_PARSEMONITORDESCRIPTION* inArgs,
IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs)
{
CSettings::DisplayModes modes;
AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->MonitorModeBufferOutputCount = (UINT)modes.size();
outArgs->PreferredMonitorModeIdx = 0;
if (inArgs->MonitorModeBufferInputCount < (UINT)modes.size())
return (inArgs->MonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
auto * mode = inArgs->pMonitorModes;
for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{
mode->Size = sizeof(IDDCX_MONITOR_MODE);
mode->Origin = IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
FillSignalInfo(mode->MonitorVideoSignalInfo, *it, true);
if (it->preferred)
outArgs->PreferredMonitorModeIdx =
(UINT)std::distance(modes.cbegin(), it);
}
return STATUS_SUCCESS;
}
NTSTATUS CIndirectDeviceContext::MonitorGetDefaultModes(
const IDARG_IN_GETDEFAULTDESCRIPTIONMODES* inArgs,
IDARG_OUT_GETDEFAULTDESCRIPTIONMODES* outArgs)
{
CSettings::DisplayModes modes;
AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->DefaultMonitorModeBufferOutputCount = (UINT)modes.size();
outArgs->PreferredMonitorModeIdx = 0;
if (inArgs->DefaultMonitorModeBufferInputCount < (UINT)modes.size())
return (inArgs->DefaultMonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
auto* mode = inArgs->pDefaultMonitorModes;
for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{
mode->Size = sizeof(IDDCX_MONITOR_MODE);
mode->Origin = IDDCX_MONITOR_MODE_ORIGIN_DRIVER;
FillSignalInfo(mode->MonitorVideoSignalInfo, *it, true);
if (it->preferred)
outArgs->PreferredMonitorModeIdx =
(UINT)std::distance(modes.cbegin(), it);
}
return STATUS_SUCCESS;
}
NTSTATUS CIndirectDeviceContext::MonitorQueryTargetModes(
const IDARG_IN_QUERYTARGETMODES* inArgs,
IDARG_OUT_QUERYTARGETMODES* outArgs)
{
CSettings::DisplayModes modes;
AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->TargetModeBufferOutputCount = (UINT)modes.size();
if (inArgs->TargetModeBufferInputCount < (UINT)modes.size())
return (inArgs->TargetModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
auto* mode = inArgs->pTargetModes;
for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{
mode->Size = sizeof(IDDCX_TARGET_MODE);
FillSignalInfo(mode->TargetVideoSignalInfo.targetVideoSignalInfo, *it, false);
}
return STATUS_SUCCESS;
}
#ifdef HAS_IDDCX_110
NTSTATUS CIndirectDeviceContext::ParseMonitorDescription2(
const IDARG_IN_PARSEMONITORDESCRIPTION2* inArgs,
IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs)
{
CSettings::DisplayModes modes;
AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->MonitorModeBufferOutputCount = (UINT)modes.size();
outArgs->PreferredMonitorModeIdx = 0;
if (inArgs->MonitorModeBufferInputCount < (UINT)modes.size())
return (inArgs->MonitorModeBufferInputCount > 0) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS;
auto * mode = inArgs->pMonitorModes;
for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{
ZeroMemory(mode, sizeof(*mode));
mode->Size = sizeof(IDDCX_MONITOR_MODE2);
mode->Origin = IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR;
FillSignalInfo(mode->MonitorVideoSignalInfo, *it, true);
mode->BitsPerComponent = GetWireBitsPerComponent(CanProcessFP16());
if (it->preferred)
outArgs->PreferredMonitorModeIdx =
(UINT)std::distance(modes.cbegin(), it);
}
return STATUS_SUCCESS;
}
NTSTATUS CIndirectDeviceContext::MonitorQueryTargetModes2(
const IDARG_IN_QUERYTARGETMODES2* inArgs,
IDARG_OUT_QUERYTARGETMODES* outArgs)
{
CSettings::DisplayModes modes;
AcquireSRWLockShared(&m_modeLock);
modes = m_displayModes;
ReleaseSRWLockShared(&m_modeLock);
outArgs->TargetModeBufferOutputCount = (UINT)modes.size();
if (inArgs->TargetModeBufferInputCount < (UINT)modes.size())
return STATUS_SUCCESS;
if (!inArgs->pTargetModes)
return STATUS_INVALID_PARAMETER;
auto* mode = inArgs->pTargetModes;
for (auto it = modes.cbegin(); it != modes.cend(); ++it, ++mode)
{
ZeroMemory(mode, sizeof(*mode));
mode->Size = sizeof(IDDCX_TARGET_MODE2);
FillSignalInfo(mode->TargetVideoSignalInfo.targetVideoSignalInfo, *it, false);
mode->BitsPerComponent = GetWireBitsPerComponent(CanProcessFP16());
}
return STATUS_SUCCESS;
}
#endif
bool CIndirectDeviceContext::GetResolutionMemoryRequirements(
uint32_t width, uint32_t height, UINT64 alignment,
UINT64& frameSize, UINT64& ivshmemSize) const
{
frameSize = 0;
ivshmemSize = 0;
if (!alignment || !m_frameMemoryOffset ||
!CalculateFrameSize(width, height, frameSize))
return false;
UINT64 frameAllocationSize;
if (!AlignUp(frameSize + alignment, alignment,
frameAllocationSize))
return false;
UINT64 frameMemoryStart;
if (!AlignUp(m_frameMemoryOffset, alignment, frameMemoryStart))
return false;
ivshmemSize = frameMemoryStart +
frameAllocationSize * LGMP_Q_FRAME_BUFFER_LEN;
return true;
}
void CIndirectDeviceContext::SetResolution(uint32_t width, uint32_t height)
{
UINT64 frameSize;
UINT64 requiredIVSHMEMSize;
if (!GetResolutionMemoryRequirements(width, height, m_alignSize,
frameSize, requiredIVSHMEMSize))
{
DEBUG_WARN("Ignoring invalid resolution request: %ux%u", width, height);
return;
}
if (requiredIVSHMEMSize > m_ivshmem.GetSize())
{
const uint32_t requiredMiB =
RecommendedIVSHMEMSizeMiB(requiredIVSHMEMSize);
DEBUG_WARN(
"Refusing resolution %ux%u: frame requires %llu bytes, only %llu bytes are available; IVSHMEM must be at least %u MiB",
width, height,
(unsigned long long)frameSize,
(unsigned long long)m_maxFrameSize,
requiredMiB);
g_pipe.ResolutionRejected(width, height, requiredMiB);
return;
}
CSettings::DisplayMode mode = {};
mode.width = width;
mode.height = height;
mode.refresh = g_settings.GetDefaultRefresh();
mode.preferred = true;
AcquireSRWLockExclusive(&m_stateLock);
m_setMode = mode;
m_doSetMode = true;
ReleaseSRWLockExclusive(&m_stateLock);
g_settings.SetExtraMode(mode);
if (!PopulateDefaultModes())
{
DEBUG_ERROR("Failed to rebuild the display mode list");
return;
}
// IddCxMonitorUpdateModes[2] does not invalidate Windows' cached mode list,
// so the only reliable way to apply a new mode is to depart and re-arrive the
// monitor, forcing Windows to rebuild the topology from the new mode list.
ReplugMonitor();
}
bool CIndirectDeviceContext::InitializeLGMP()
{
if (m_lgmp)
return true;
std::stringstream ss;
{
KVMFR kvmfr = {};
memcpy_s(kvmfr.magic, sizeof(kvmfr.magic), KVMFR_MAGIC, sizeof(KVMFR_MAGIC) - 1);
kvmfr.version = KVMFR_VERSION;
kvmfr.features =
KVMFR_FEATURE_SETCURSORPOS |
KVMFR_FEATURE_WINDOWSIZE |
KVMFR_FEATURE_FRAME_SCHEDULE;
strncpy_s(kvmfr.hostver, LG_VERSION_STR, sizeof(kvmfr.hostver) - 1);
ss.write(reinterpret_cast<const char *>(&kvmfr), sizeof(kvmfr));
}
{
const std::string & model = CPlatformInfo::GetCPUModel();
KVMFRRecord_VMInfo * vmInfo = static_cast<KVMFRRecord_VMInfo *>(calloc(1, sizeof(*vmInfo)));
if (!vmInfo)
{
DEBUG_ERROR("Failed to allocate KVMFRRecord_VMInfo");
return false;
}
vmInfo->cpus = static_cast<uint8_t>(CPlatformInfo::GetProcCount ());
vmInfo->cores = static_cast<uint8_t>(CPlatformInfo::GetCoreCount ());
vmInfo->sockets = static_cast<uint8_t>(CPlatformInfo::GetSocketCount());
const uint8_t * uuid = CPlatformInfo::GetUUID();
memcpy_s (vmInfo->uuid, sizeof(vmInfo->uuid), uuid, 16);
strncpy_s(vmInfo->capture, "Looking Glass IDD Driver", sizeof(vmInfo->capture));
KVMFRRecord * record = static_cast<KVMFRRecord *>(calloc(1, sizeof(*record)));
if (!record)
{
DEBUG_ERROR("Failed to allocate KVMFRRecord");
return false;
}
record->type = KVMFR_RECORD_VMINFO;
record->size = sizeof(*vmInfo) + (uint32_t)model.length() + 1;
ss.write(reinterpret_cast<const char*>(record ), sizeof(*record));
ss.write(reinterpret_cast<const char*>(vmInfo ), sizeof(*vmInfo));
ss.write(reinterpret_cast<const char*>(model.c_str()), model.length() + 1);
}
{
KVMFRRecord_OSInfo * osInfo = static_cast<KVMFRRecord_OSInfo *>(calloc(1, sizeof(*osInfo)));
if (!osInfo)
{
DEBUG_ERROR("Failed to allocate KVMFRRecord_OSInfo");
return false;
}
osInfo->os = KVMFR_OS_WINDOWS;
const std::string & osName = CPlatformInfo::GetProductName();
KVMFRRecord* record = static_cast<KVMFRRecord*>(calloc(1, sizeof(*record)));
if (!record)
{
DEBUG_ERROR("Failed to allocate KVMFRRecord");
return false;
}
record->type = KVMFR_RECORD_OSINFO;
record->size = sizeof(*osInfo) + (uint32_t)osName.length() + 1;
ss.write(reinterpret_cast<const char*>(record), sizeof(*record));
ss.write(reinterpret_cast<const char*>(osInfo), sizeof(*osInfo));
ss.write(reinterpret_cast<const char*>(osName.c_str()), osName.length() + 1);
}
LGMP_STATUS status;
std::string udata = ss.str();
if ((status = lgmpHostInit(m_ivshmem.GetMem(), (uint32_t)m_ivshmem.GetSize(),
&m_lgmp, (uint32_t)udata.size(), (uint8_t*)&udata[0])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostInit Failed: %s", lgmpStatusString(status));
return false;
}
if ((status = lgmpHostQueueNew(m_lgmp, FRAME_QUEUE_CONFIG, &m_frameQueue)) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueCreate Failed (Frame): %s", lgmpStatusString(status));
return false;
}
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
{
const struct LGMPQueueConfig config =
{
LGMP_Q_FRAME_OWNER + i, //queueID
LGMP_Q_FRAME_LEN, //numMessages
1000 //subTimeout
};
if ((status = lgmpHostQueueNew(
m_lgmp, config, &m_frameOwnerQueue[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueCreate Failed (Frame Owner %u): %s",
i, lgmpStatusString(status));
return false;
}
}
if ((status = lgmpHostQueueNew(m_lgmp, POINTER_QUEUE_CONFIG, &m_pointerQueue)) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueCreate Failed (Pointer): %s", lgmpStatusString(status));
return false;
}
for (int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
{
if ((status = lgmpHostMemAlloc(m_lgmp, MAX_POINTER_SIZE, &m_pointerMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerMemory[i]), 0, MAX_POINTER_SIZE);
}
for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
{
if ((status = lgmpHostMemAlloc(m_lgmp, MAX_POINTER_SIZE, &m_pointerShapeMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Shapes): %s", lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerShapeMemory[i]), 0, MAX_POINTER_SIZE);
}
for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
{
if ((status = lgmpHostMemAlloc(m_lgmp,
sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform),
&m_pointerTransformMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Transform): %s",
lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerTransformMemory[i]), 0,
sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform));
}
m_frameMemoryOffset = m_ivshmem.GetSize() - lgmpHostMemAvail(m_lgmp);
return true;
}
bool CIndirectDeviceContext::SetupLGMP(size_t alignSize)
{
// This may get called multiple times as we need to delay allocating the
// frame buffers until the GPU-specific alignment is known.
if (m_maxFrameSize)
return true;
if (!InitializeLGMP())
return false;
m_alignSize = alignSize;
if (!m_alignSize || (m_alignSize & (m_alignSize - 1)) ||
m_alignSize < sizeof(KVMFRFrame) + sizeof(FrameBuffer))
{
DEBUG_ERROR("Invalid frame buffer alignment: %llu",
(unsigned long long)m_alignSize);
return false;
}
const size_t available = lgmpHostMemAvail(m_lgmp);
UINT64 alignedFrameMemoryOffset;
if (!AlignUp(m_frameMemoryOffset, m_alignSize,
alignedFrameMemoryOffset))
{
DEBUG_ERROR("Unable to align the frame memory offset");
return false;
}
const size_t alignmentPadding =
(size_t)(alignedFrameMemoryOffset - m_frameMemoryOffset);
if (available <= alignmentPadding)
{
DEBUG_ERROR("Insufficient shared memory for frame buffers");
return false;
}
const size_t alignmentMask = m_alignSize - 1;
size_t frameAllocationSize =
(available - alignmentPadding) / LGMP_Q_FRAME_BUFFER_LEN;
frameAllocationSize &= ~alignmentMask;
if (frameAllocationSize <= m_alignSize ||
frameAllocationSize > UINT32_MAX)
{
DEBUG_ERROR("Invalid frame allocation size: %llu",
(unsigned long long)frameAllocationSize);
return false;
}
// The KVMFR frame header and FrameBuffer write position occupy the first
// alignment unit. Only the bytes after it are usable for pixel data.
const size_t maxFrameSize = frameAllocationSize - m_alignSize;
DEBUG_INFO("Max Frame Data Size: %u MiB",
(unsigned int)(maxFrameSize / 1048576));
LGMP_STATUS status;
for (int i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
{
if ((status = lgmpHostMemAllocAligned(m_lgmp,
(uint32_t)frameAllocationSize,
(uint32_t)m_alignSize, &m_frameMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAllocAligned Failed (Frame): %s", lgmpStatusString(status));
return false;
}
m_frame[i] = (KVMFRFrame *)lgmpHostMemPtr(m_frameMemory[i]);
/**
* put the framebuffer on the border of the next page, this is to allow for
* aligned DMA tranfers by the reciever */
const size_t alignOffset = alignSize - sizeof(FrameBuffer);
m_frame[i]->offset = (uint32_t)alignOffset;
m_frameBuffer[i] = (FrameBuffer*)(((uint8_t*)m_frame[i]) + alignOffset);
m_frameInFlight[i].store(false, std::memory_order_release);
}
m_maxFrameSize = maxFrameSize;
m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence));
for (FrameDelivery& delivery : m_frameDelivery)
delivery = {};
for (OwnerDelivery& delivery : m_ownerDelivery)
delivery = {};
WDF_TIMER_CONFIG config;
WDF_TIMER_CONFIG_INIT_PERIODIC(&config,
[](WDFTIMER timer) -> void
{
WDFOBJECT parent = WdfTimerGetParentObject(timer);
auto wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(parent);
wrapper->context->LGMPTimer();
},
10);
config.AutomaticSerialization = FALSE;
/**
* documentation states that Dispatch is not available under the UDMF, 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 s = WdfTimerCreate(&config, &attribs, &m_lgmpTimer);
if (!NT_SUCCESS(s))
{
DEBUG_ERROR_HR(s, "Timer creation failed");
return false;
}
WdfTimerStart(m_lgmpTimer, WDF_REL_TIMEOUT_IN_MS(10));
return true;
}
void CIndirectDeviceContext::DeInitLGMP()
{
// The retry timer callback dereferences this context, so make sure it is
// stopped and drained before we tear anything down. Wait for any in-flight
// callback to complete.
if (m_initTimer)
{
WdfTimerStop(m_initTimer, TRUE);
m_initTimer = nullptr;
}
if (m_lgmp == nullptr)
{
m_frameScheduler.Reset();
m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence));
for (FrameDelivery& delivery : m_frameDelivery)
delivery = {};
for (OwnerDelivery& delivery : m_ownerDelivery)
delivery = {};
return;
}
if (m_lgmpTimer)
{
WdfTimerStop(m_lgmpTimer, TRUE);
m_lgmpTimer = nullptr;
}
m_frameScheduler.Reset();
AcquireSRWLockExclusive(&m_framePublishLock);
m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence));
for (FrameDelivery& delivery : m_frameDelivery)
delivery = {};
for (OwnerDelivery& delivery : m_ownerDelivery)
delivery = {};
ReleaseSRWLockExclusive(&m_framePublishLock);
for (int i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
m_frameInFlight[i].store(false, std::memory_order_release);
for (int i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
lgmpHostMemFree(&m_frameMemory[i]);
for (int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
lgmpHostMemFree(&m_pointerMemory[i]);
for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
lgmpHostMemFree(&m_pointerShapeMemory[i]);
for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
lgmpHostMemFree(&m_pointerTransformMemory[i]);
lgmpHostFree(&m_lgmp);
}
void CIndirectDeviceContext::LGMPTimer()
{
// Rebuild the monitor queued by ReplugMonitor, off the IddCx callback thread.
if (m_finishInitQueued.exchange(0))
{
FinishInit(0);
return;
}
if (m_replugQueued.exchange(0))
{
ReplugMonitor();
return;
}
LGMP_STATUS status;
AcquireSRWLockExclusive(&m_lgmpProcessLock);
status = lgmpHostProcess(m_lgmp);
ReleaseSRWLockExclusive(&m_lgmpProcessLock);
if (status != LGMP_OK)
{
if (status == LGMP_ERR_CORRUPTED)
{
DEBUG_WARN("LGMP reported the shared memory has been corrupted, attempting to recover\n");
//TODO: fixme - reinit
return;
}
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
//TODO: fixme - shutdown
return;
}
const uint64_t now = CFrameScheduler::Nanotime();
uint32_t clientIDs[LGMP_MAX_CLIENTS] = {};
uint32_t ownerClientIDs[LGMP_MAX_CLIENTS] = {};
unsigned clientCount = 0;
unsigned ownerClientCount = 0;
LGMP_STATUS subscriberStatus = lgmpHostGetClientIDs(
m_frameQueue, clientIDs, &clientCount);
if (subscriberStatus == LGMP_OK)
{
memcpy(ownerClientIDs, clientIDs,
clientCount * sizeof(*ownerClientIDs));
ownerClientCount = clientCount;
}
for (unsigned queueIndex = 0;
subscriberStatus == LGMP_OK && queueIndex < LGMP_Q_FRAME_LEN;
++queueIndex)
{
uint32_t queueClientIDs[LGMP_MAX_CLIENTS] = {};
unsigned queueClientCount = 0;
subscriberStatus = lgmpHostGetClientIDs(
m_frameOwnerQueue[queueIndex], queueClientIDs, &queueClientCount);
unsigned commonCount = 0;
for (unsigned i = 0;
subscriberStatus == LGMP_OK && i < ownerClientCount;
++i)
for (unsigned candidate = 0; candidate < queueClientCount; ++candidate)
if (ownerClientIDs[i] == queueClientIDs[candidate])
{
ownerClientIDs[commonCount++] = ownerClientIDs[i];
break;
}
ownerClientCount = commonCount;
}
uint8_t data[LGMP_MSGS_SIZE];
size_t size;
uint32_t sourceClientID;
while ((status = lgmpHostReadDataWithSource(
m_pointerQueue, &data, &size, &sourceClientID)) == LGMP_OK)
{
KVMFRMessage * msg = (KVMFRMessage *)data;
switch (msg->type)
{
case KVMFR_MESSAGE_SETCURSORPOS:
{
KVMFRSetCursorPos* sp = (KVMFRSetCursorPos*)msg;
g_pipe.SetCursorPos(sp->x, sp->y);
break;
}
case KVMFR_MESSAGE_WINDOWSIZE:
{
KVMFRWindowSize* ws = (KVMFRWindowSize*)msg;
SetResolution(ws->w, ws->h);
break;
}
case KVMFR_MESSAGE_FRAME_SCHEDULE:
{
const KVMFRFrameSchedule * frameSchedule =
reinterpret_cast<KVMFRFrameSchedule *>(msg);
const bool valid = size == sizeof(*frameSchedule) &&
m_frameScheduler.UpdateSchedule(
sourceClientID, *frameSchedule, now);
if (!valid)
DEBUG_WARN("Ignoring invalid KVMFR frame schedule");
break;
}
}
lgmpHostAckData(m_pointerQueue);
}
if (subscriberStatus == LGMP_OK)
m_frameScheduler.UpdateSubscribers(
clientIDs, clientCount,
ownerClientIDs, ownerClientCount, now);
else
DEBUG_WARN("Failed to query LGMP frame subscribers: %s",
lgmpStatusString(subscriberStatus));
m_frameScheduler.LogStatistics(now);
if (lgmpHostQueueNewSubs(m_frameQueue))
m_frameScheduler.NotifyPublisher();
ProcessFrameDeliveries();
if (lgmpHostQueueNewSubs(m_pointerQueue))
{
ResendCursor();
SendColorTransform();
}
}
CIndirectDeviceContext::SharedFramePostResult
CIndirectDeviceContext::PostSharedFrame(unsigned frameIndex,
uint32_t excludeClientID, uint64_t now)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN ||
lgmpHostQueuePending(m_frameQueue) != 0)
return SHARED_FRAME_FAILED;
uint32_t clientIDs[LGMP_MAX_CLIENTS] = {};
unsigned clientCount = 0;
LGMP_STATUS status =
lgmpHostGetClientIDs(m_frameQueue, clientIDs, &clientCount);
if (status != LGMP_OK)
{
DEBUG_ERROR("Failed to query shared frame subscribers: %s",
lgmpStatusString(status));
return SHARED_FRAME_FAILED;
}
uint32_t recipients[LGMP_MAX_CLIENTS] = {};
const uint32_t frameSerial = m_frame[frameIndex]->frameSerial;
const unsigned recipientCount =
m_frameScheduler.GetSecondaryRecipients(
clientIDs, clientCount, frameSerial, now, recipients);
unsigned targetCount = 0;
for (unsigned i = 0; i < recipientCount; ++i)
{
bool excluded = recipients[i] == excludeClientID;
for (const OwnerDelivery& delivery : m_ownerDelivery)
if (delivery.active && delivery.clientID == recipients[i])
{
excluded = true;
break;
}
if (!excluded)
recipients[targetCount++] = recipients[i];
}
if (!targetCount)
{
m_frameDelivery[frameIndex].sharedOwnerToken = 0;
m_frameDelivery[frameIndex].sharedOwnerClientID = 0;
m_frameDelivery[frameIndex].sharedOwnerPending = false;
m_frameDelivery[frameIndex].sharedPending = false;
return SHARED_FRAME_IDLE;
}
unsigned postedCount = 0;
status = lgmpHostQueuePostForClients(
m_frameQueue, 0, m_frameMemory[frameIndex],
recipients, targetCount, &postedCount);
if (status != LGMP_OK)
{
if (status != LGMP_ERR_QUEUE_FULL)
DEBUG_ERROR("Failed to publish shared frame: %s",
lgmpStatusString(status));
return SHARED_FRAME_FAILED;
}
if (!postedCount)
{
m_frameDelivery[frameIndex].sharedOwnerToken = 0;
m_frameDelivery[frameIndex].sharedOwnerClientID = 0;
m_frameDelivery[frameIndex].sharedOwnerPending = false;
m_frameDelivery[frameIndex].sharedPending = false;
return SHARED_FRAME_IDLE;
}
m_frameDelivery[frameIndex].sharedOwnerToken = 0;
m_frameDelivery[frameIndex].sharedOwnerClientID = 0;
m_frameDelivery[frameIndex].sharedOwnerPending = false;
m_frameDelivery[frameIndex].sharedPending = true;
if (postedCount == targetCount)
m_frameScheduler.FrameDelivered(
recipients, targetCount, frameSerial, now);
return SHARED_FRAME_POSTED;
}
bool CIndirectDeviceContext::PostSharedOwnerFrame(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN || !schedule.clientID ||
m_frameDelivery[frameIndex].sharedOwnerPending ||
lgmpHostQueuePending(m_frameQueue) >= LGMP_Q_FRAME_LEN)
return false;
unsigned recipientCount = 0;
const LGMP_STATUS status = lgmpHostQueuePostForClients(
m_frameQueue, FrameScheduleToken(schedule), m_frameMemory[frameIndex],
&schedule.clientID, 1, &recipientCount);
if (status != LGMP_OK || !recipientCount)
{
if (status != LGMP_OK && status != LGMP_ERR_QUEUE_FULL)
DEBUG_ERROR("Failed to publish shared owner frame: %s",
lgmpStatusString(status));
return false;
}
FrameDelivery& delivery = m_frameDelivery[frameIndex];
delivery.sharedOwnerToken = FrameScheduleToken(schedule);
delivery.sharedOwnerClientID = schedule.clientID;
delivery.sharedOwnerPending = true;
delivery.sharedPending = true;
return true;
}
void CIndirectDeviceContext::ProcessFrameDeliveries()
{
if (!m_frameQueue)
return;
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
if (!m_frameOwnerQueue[i])
return;
AcquireSRWLockExclusive(&m_framePublishLock);
bool released = false;
for (unsigned i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
{
if (m_frameDelivery[i].sharedOwnerPending &&
!lgmpHostQueueMessagePending(
m_frameQueue, m_frameMemory[i],
m_frameDelivery[i].sharedOwnerToken))
{
m_frameDelivery[i].sharedOwnerPending = false;
released = true;
}
if (m_frameDelivery[i].sharedPending &&
!lgmpHostQueuePayloadPending(m_frameQueue, m_frameMemory[i]))
{
m_frameDelivery[i].sharedPending = false;
released = true;
}
}
for (unsigned queueIndex = 0;
queueIndex < LGMP_Q_FRAME_LEN;
++queueIndex)
{
OwnerDelivery& owner = m_ownerDelivery[queueIndex];
if (!owner.active ||
lgmpHostQueuePayloadPending(
m_frameOwnerQueue[queueIndex],
m_frameMemory[owner.frameIndex]))
continue;
const unsigned frameIndex = owner.frameIndex;
m_frameDelivery[frameIndex].ownerQueueMask &=
~(1U << queueIndex);
owner = {};
released = true;
}
ReleaseSRWLockExclusive(&m_framePublishLock);
if (released)
m_frameScheduler.NotifyPublisher();
}
int CIndirectDeviceContext::FindAvailableOwnerQueue(
unsigned preferredIndex) const
{
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
{
const unsigned queueIndex =
(preferredIndex + i) % LGMP_Q_FRAME_LEN;
if (!m_ownerDelivery[queueIndex].active &&
m_frameOwnerQueue[queueIndex] &&
lgmpHostQueuePending(m_frameOwnerQueue[queueIndex]) == 0)
return static_cast<int>(queueIndex);
}
return -1;
}
unsigned CIndirectDeviceContext::CountOwnerDeliveries(
uint32_t clientID) const
{
unsigned count = 0;
for (const OwnerDelivery& delivery : m_ownerDelivery)
if (delivery.active && delivery.clientID == clientID)
++count;
for (const FrameDelivery& delivery : m_frameDelivery)
if (delivery.sharedOwnerPending &&
delivery.sharedOwnerClientID == clientID)
++count;
return count;
}
bool CIndirectDeviceContext::HasMatchingOwnerDelivery(
uint32_t clientID, unsigned frameIndex, uint64_t token) const
{
for (const OwnerDelivery& delivery : m_ownerDelivery)
if (delivery.active &&
delivery.clientID == clientID &&
delivery.frameIndex == frameIndex &&
delivery.token == token)
return true;
for (unsigned i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
{
const FrameDelivery& delivery = m_frameDelivery[i];
if (i == frameIndex &&
delivery.sharedOwnerPending &&
delivery.sharedOwnerClientID == clientID &&
delivery.sharedOwnerToken == token)
return true;
}
return false;
}
int CIndirectDeviceContext::FindAvailableFrameBuffer() const
{
const LONG readyFrameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
int available = -1;
uint64_t newestPublish = 0;
for (unsigned frameIndex = 0;
frameIndex < LGMP_Q_FRAME_BUFFER_LEN;
++frameIndex)
{
if (static_cast<LONG>(frameIndex) == readyFrameIndex ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
m_frameDelivery[frameIndex].ownerQueueMask ||
m_frameDelivery[frameIndex].sharedPending ||
lgmpHostQueuePayloadPending(
m_frameQueue, m_frameMemory[frameIndex]))
continue;
bool ownerPending = false;
for (unsigned queueIndex = 0;
!ownerPending && queueIndex < LGMP_Q_FRAME_LEN;
++queueIndex)
ownerPending = lgmpHostQueuePayloadPending(
m_frameOwnerQueue[queueIndex], m_frameMemory[frameIndex]);
if (ownerPending)
continue;
if (available < 0 ||
m_frameLastPublishSequence[frameIndex] > newestPublish)
{
available = static_cast<int>(frameIndex);
newestPublish = m_frameLastPublishSequence[frameIndex];
}
}
return available;
}
bool CIndirectDeviceContext::FrameBufferAvailable(
const CFrameScheduler::Schedule& schedule)
{
if (!m_lgmp || !m_frameQueue)
return false;
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
if (!m_frameOwnerQueue[i])
return false;
AcquireSRWLockShared(&m_framePublishLock);
bool deliveryAvailable;
// Pipeline one frame through each independent owner lane. Count the shared
// fallback against the same limit so it cannot become a third delivery for
// the same owner.
if (schedule.clientID)
deliveryAvailable =
CountOwnerDeliveries(schedule.clientID) < LGMP_Q_FRAME_LEN &&
(FindAvailableOwnerQueue(0) >= 0 ||
lgmpHostQueuePending(m_frameQueue) < LGMP_Q_FRAME_LEN);
else
deliveryAvailable = lgmpHostQueuePending(m_frameQueue) == 0;
const bool available = deliveryAvailable &&
FindAvailableFrameBuffer() >= 0;
ReleaseSRWLockShared(&m_framePublishLock);
return available;
}
void CIndirectDeviceContext::ProcessFrameQueue()
{
if (!m_lgmp)
return;
AcquireSRWLockExclusive(&m_lgmpProcessLock);
const LGMP_STATUS status = lgmpHostProcess(m_lgmp);
ReleaseSRWLockExclusive(&m_lgmpProcessLock);
if (status != LGMP_OK && status != LGMP_ERR_CORRUPTED)
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
if (status == LGMP_OK)
ProcessFrameDeliveries();
}
bool CIndirectDeviceContext::GetSharedFrameTarget(uint64_t now,
uint64_t& target)
{
if (!m_frameQueue)
return false;
AcquireSRWLockShared(&m_framePublishLock);
const LONG frameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
lgmpHostQueuePending(m_frameQueue) != 0)
{
ReleaseSRWLockShared(&m_framePublishLock);
return false;
}
uint32_t blockedClientIDs[LGMP_Q_FRAME_LEN] = {};
unsigned blockedCount = 0;
for (const OwnerDelivery& delivery : m_ownerDelivery)
if (delivery.active)
blockedClientIDs[blockedCount++] = delivery.clientID;
const bool result = m_frameScheduler.GetSecondaryTarget(
m_frame[frameIndex]->frameSerial, now,
blockedClientIDs, blockedCount, target);
ReleaseSRWLockShared(&m_framePublishLock);
return result;
}
bool CIndirectDeviceContext::ReplaySharedFrame(uint64_t now, bool& retry)
{
retry = false;
if (!m_frameQueue)
return false;
AcquireSRWLockExclusive(&m_framePublishLock);
const LONG frameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
lgmpHostQueuePending(m_frameQueue) != 0)
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
const SharedFramePostResult result = PostSharedFrame(
static_cast<unsigned>(frameIndex), 0, now);
ReleaseSRWLockExclusive(&m_framePublishLock);
retry = result == SHARED_FRAME_FAILED;
return result == SHARED_FRAME_POSTED;
}
CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrameBuffer(
unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat,
const RECT * dirtyRects, unsigned nbDirtyRects)
{
PreparedFrameBuffer result = {};
const unsigned dataWidth = dstFormat.dataWidth ?
dstFormat.dataWidth : (unsigned)dstFormat.desc.Width;
const unsigned dataHeight = dstFormat.dataHeight ?
dstFormat.dataHeight : dstFormat.desc.Height;
if (dstFormat.format == FRAME_TYPE_INVALID)
{
DEBUG_ERROR("Unsupported frame format, skipping frame");
return result;
}
AcquireSRWLockExclusive(&m_framePublishLock);
const int availableFrameIndex = FindAvailableFrameBuffer();
bool expected = false;
const bool acquired = availableFrameIndex >= 0 &&
m_frameInFlight[availableFrameIndex].compare_exchange_strong(
expected, true, std::memory_order_acq_rel);
if (acquired)
m_frameDelivery[availableFrameIndex] = {};
const bool fullCopy = acquired &&
(!m_frameLastPublishSequence[availableFrameIndex] ||
m_framePublishSequence >
m_frameLastPublishSequence[availableFrameIndex] + 1);
ReleaseSRWLockExclusive(&m_framePublishLock);
if (!acquired)
return result;
const unsigned frameIndex = static_cast<unsigned>(availableFrameIndex);
if (m_width != dataWidth ||
m_height != dataHeight ||
m_frameWidth != dstFormat.width ||
m_frameHeight != dstFormat.height ||
m_pitch != pitch ||
m_format != dstFormat.desc.Format ||
m_frameType != dstFormat.format)
{
m_width = dataWidth;
m_height = dataHeight;
m_frameWidth = dstFormat.width;
m_frameHeight = dstFormat.height;
m_pitch = pitch;
m_format = dstFormat.desc.Format;
m_frameType = dstFormat.format;
++m_formatVer;
}
// Detect HDR metadata changes that require a format version bump
// so the client knows to re-apply the HDR image description.
//
// Use dstFormat so post-processing can propagate any metadata adjustments.
if (dstFormat.hdr)
{
const bool metadataChanged =
m_lastHDRMetadata != dstFormat.hdrMetadata ||
(dstFormat.hdrMetadata &&
(memcmp(m_lastHDRDisplayPrimary, dstFormat.displayPrimary, sizeof(m_lastHDRDisplayPrimary)) != 0 ||
memcmp(m_lastHDRWhitePoint , dstFormat.whitePoint , sizeof(m_lastHDRWhitePoint )) != 0 ||
m_lastHDRMaxDisplayLuminance != dstFormat.maxDisplayLuminance ||
m_lastHDRMinDisplayLuminance != dstFormat.minDisplayLuminance ||
m_lastHDRMaxContentLightLevel != dstFormat.maxContentLightLevel ||
m_lastHDRMaxFrameAverageLightLevel != dstFormat.maxFrameAverageLightLevel));
if (!m_lastHDRActive || metadataChanged || m_lastSDRWhiteLevel != dstFormat.sdrWhiteLevel)
++m_formatVer;
}
else if (m_lastHDRActive)
{
// HDR was turned off
++m_formatVer;
}
m_lastHDRActive = dstFormat.hdr;
m_lastHDRMetadata = dstFormat.hdrMetadata;
memcpy(m_lastHDRDisplayPrimary, dstFormat.displayPrimary, sizeof(m_lastHDRDisplayPrimary));
memcpy(m_lastHDRWhitePoint , dstFormat.whitePoint , sizeof(m_lastHDRWhitePoint ));
m_lastHDRMaxDisplayLuminance = dstFormat.maxDisplayLuminance;
m_lastHDRMinDisplayLuminance = dstFormat.minDisplayLuminance;
m_lastHDRMaxContentLightLevel = dstFormat.maxContentLightLevel;
m_lastHDRMaxFrameAverageLightLevel = dstFormat.maxFrameAverageLightLevel;
m_lastSDRWhiteLevel = dstFormat.sdrWhiteLevel;
KVMFRFrame * fi = m_frame[frameIndex];
const unsigned maxRows = (unsigned)(m_maxFrameSize / pitch);
const int bpp = dstFormat.format == FRAME_TYPE_RGBA16F ? 8 : 4;
KVMFRFrameFlags flags =
(dstFormat.hdr ? FRAME_FLAG_HDR : 0) |
(dstFormat.hdrPQ ? FRAME_FLAG_HDR_PQ : 0) |
(dstFormat.hdrMetadata ? FRAME_FLAG_HDR_METADATA : 0);
if (maxRows < dataHeight)
flags |= FRAME_FLAG_TRUNCATED;
fi->formatVer = m_formatVer;
fi->frameSerial = m_frameSerial++;
fi->screenWidth = srcFormat.width;
fi->screenHeight = srcFormat.height;
fi->dataWidth = dataWidth;
fi->dataHeight = min(maxRows, dataHeight);
fi->frameWidth = dstFormat.width;
fi->frameHeight = dstFormat.height;
fi->stride = pitch / bpp;
fi->pitch = pitch;
// fi->offset is initialized at startup
fi->flags = flags;
fi->sdrWhiteLevel = dstFormat.sdrWhiteLevel;
fi->captureTime = 0;
fi->postProcessTime = 0;
fi->copyTime = 0;
fi->readyTime = 0;
fi->timingSerial = 0;
fi->timingFlags = 0;
fi->scheduleGeneration = 0;
fi->scheduleEpoch = 0;
fi->scheduleDeadlineSerial = 0;
InterlockedExchange((volatile LONG *)&fi->timingValid, 0);
fi->rotation = FRAME_ROT_0;
fi->type = dstFormat.format;
if (flags & FRAME_FLAG_HDR_METADATA)
{
memcpy(fi->hdrDisplayPrimary, dstFormat.displayPrimary, sizeof(fi->hdrDisplayPrimary));
memcpy(fi->hdrWhitePoint , dstFormat.whitePoint , sizeof(fi->hdrWhitePoint));
fi->hdrMaxDisplayLuminance = dstFormat.maxDisplayLuminance;
fi->hdrMinDisplayLuminance = dstFormat.minDisplayLuminance;
fi->hdrMaxContentLightLevel = dstFormat.maxContentLightLevel;
fi->hdrMaxFrameAverageLightLevel = dstFormat.maxFrameAverageLightLevel;
}
else
{
memset(fi->hdrDisplayPrimary, 0, sizeof(fi->hdrDisplayPrimary));
memset(fi->hdrWhitePoint , 0, sizeof(fi->hdrWhitePoint ));
fi->hdrMaxDisplayLuminance = 0;
fi->hdrMinDisplayLuminance = 0;
fi->hdrMaxContentLightLevel = 0;
fi->hdrMaxFrameAverageLightLevel = 0;
}
fi->damageRectsCount = 0;
if (nbDirtyRects <= ARRAYSIZE(fi->damageRects))
{
fi->damageRectsCount = nbDirtyRects;
for (unsigned i = 0; i < nbDirtyRects; ++i)
{
fi->damageRects[i].x = dirtyRects[i].left;
fi->damageRects[i].y = dirtyRects[i].top;
fi->damageRects[i].width = dirtyRects[i].right - dirtyRects[i].left;
fi->damageRects[i].height = dirtyRects[i].bottom - dirtyRects[i].top;
}
}
FrameBuffer* fb = m_frameBuffer[frameIndex];
fb->wp = 0;
result.frameIndex = frameIndex;
result.mem = fb->data;
result.fullCopy = fullCopy;
return result;
}
bool CIndirectDeviceContext::PublishFrameBuffer(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule)
{
if (!m_frameQueue || frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return false;
const uint64_t now = CFrameScheduler::Nanotime();
AcquireSRWLockExclusive(&m_framePublishLock);
CFrameScheduler::Schedule currentSchedule = {};
const bool scheduling =
m_frameScheduler.GetSchedule(currentSchedule);
if (scheduling != (schedule.clientID != 0) ||
(scheduling && !FrameScheduleMatches(schedule, currentSchedule)))
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
KVMFRFrame * frame = m_frame[frameIndex];
frame->timingFlags = 0;
frame->scheduleGeneration = schedule.generation;
frame->scheduleEpoch = schedule.epoch;
frame->scheduleDeadlineSerial = schedule.deliveryDeadlineSerial;
LGMP_STATUS status = LGMP_OK;
bool published = false;
if (schedule.clientID)
{
if (CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN)
status = LGMP_ERR_QUEUE_FULL;
else
{
const int ownerQueueIndex = FindAvailableOwnerQueue(frameIndex);
if (ownerQueueIndex < 0)
published = PostSharedOwnerFrame(frameIndex, schedule);
else
{
unsigned recipientCount = 0;
status = lgmpHostQueuePostForClients(
m_frameOwnerQueue[ownerQueueIndex], FrameScheduleToken(schedule),
m_frameMemory[frameIndex],
&schedule.clientID, 1, &recipientCount);
if (status == LGMP_OK && recipientCount)
{
const unsigned queueIndex =
static_cast<unsigned>(ownerQueueIndex);
OwnerDelivery& owner = m_ownerDelivery[queueIndex];
owner.token = FrameScheduleToken(schedule);
owner.clientID = schedule.clientID;
owner.frameIndex = frameIndex;
owner.active = true;
FrameDelivery& delivery = m_frameDelivery[frameIndex];
delivery.ownerQueueMask |= 1U << queueIndex;
published = true;
PostSharedFrame(frameIndex, schedule.clientID, now);
}
}
}
}
else
published = PostSharedFrame(frameIndex, 0, now) != SHARED_FRAME_FAILED;
if (published)
{
m_frameLastPublishSequence[frameIndex] = ++m_framePublishSequence;
m_submittedFrameIndex.store(
static_cast<LONG>(frameIndex), std::memory_order_release);
}
ReleaseSRWLockExclusive(&m_framePublishLock);
if (!published)
{
if (status != LGMP_OK && status != LGMP_ERR_QUEUE_FULL)
DEBUG_ERROR("Failed to publish frame: %s",
lgmpStatusString(status));
return false;
}
return true;
}
bool CIndirectDeviceContext::RepublishFrameBuffer(
const CFrameScheduler::Schedule& schedule)
{
if (!schedule.clientID)
return false;
AcquireSRWLockExclusive(&m_framePublishLock);
CFrameScheduler::Schedule currentSchedule = {};
if (!m_frameScheduler.GetSchedule(currentSchedule) ||
!FrameScheduleMatches(schedule, currentSchedule))
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
const LONG frameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire))
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
CFrameScheduler::Schedule deliverySchedule = schedule;
deliverySchedule.deliveryDeadlineSerial = 0;
deliverySchedule.phaseEligible = false;
const uint64_t scheduleToken = FrameScheduleToken(deliverySchedule);
const uint32_t frameSerial = m_frame[frameIndex]->frameSerial;
if (HasMatchingOwnerDelivery(schedule.clientID,
static_cast<unsigned>(frameIndex), scheduleToken))
{
ReleaseSRWLockExclusive(&m_framePublishLock);
m_frameScheduler.FrameRepublished(schedule, frameSerial);
return true;
}
if (CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN)
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
const int ownerQueueIndex =
FindAvailableOwnerQueue(static_cast<unsigned>(frameIndex));
if (ownerQueueIndex < 0)
{
const bool published = PostSharedOwnerFrame(
static_cast<unsigned>(frameIndex), deliverySchedule);
ReleaseSRWLockExclusive(&m_framePublishLock);
if (published)
m_frameScheduler.FrameRepublished(schedule, frameSerial);
return published;
}
unsigned recipientCount = 0;
const LGMP_STATUS status = lgmpHostQueuePostForClients(
m_frameOwnerQueue[ownerQueueIndex], scheduleToken,
m_frameMemory[frameIndex],
&schedule.clientID, 1, &recipientCount);
if (status == LGMP_OK && recipientCount)
{
const unsigned queueIndex = static_cast<unsigned>(ownerQueueIndex);
OwnerDelivery& owner = m_ownerDelivery[queueIndex];
owner.token = scheduleToken;
owner.clientID = schedule.clientID;
owner.frameIndex = static_cast<unsigned>(frameIndex);
owner.active = true;
FrameDelivery& delivery = m_frameDelivery[frameIndex];
delivery.ownerQueueMask |= 1U << queueIndex;
}
ReleaseSRWLockExclusive(&m_framePublishLock);
if (status != LGMP_OK || !recipientCount)
{
if (status != LGMP_OK && status != LGMP_ERR_QUEUE_FULL)
DEBUG_ERROR("Failed to republish frame: %s",
lgmpStatusString(status));
return false;
}
m_frameScheduler.FrameRepublished(schedule, frameSerial);
return true;
}
void CIndirectDeviceContext::CommitFrameBuffer(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule, bool periodic)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
m_frameScheduler.FramePublished(
schedule, m_frame[frameIndex]->frameSerial,
CFrameScheduler::Nanotime(), periodic);
}
bool CIndirectDeviceContext::TryFrameSubmitted(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return false;
return m_frameScheduler.TryFrameSubmitted(
schedule, m_frame[frameIndex]->frameSerial);
}
void CIndirectDeviceContext::ObserveFrame(uint64_t now)
{
m_frameScheduler.ObserveFrame(now);
}
void CIndirectDeviceContext::ForceFrame()
{
m_frameScheduler.ForceFrame();
}
bool CIndirectDeviceContext::GetPublishTarget(uint64_t now,
uint64_t& target, CFrameScheduler::Schedule& schedule, bool& periodic,
bool& republish)
{
return m_frameScheduler.GetPublishTarget(
now, target, schedule, periodic, republish);
}
void CIndirectDeviceContext::FrameMissed(
const CFrameScheduler::Schedule& schedule, uint64_t now, bool periodic)
{
m_frameScheduler.FrameMissed(schedule, now, periodic);
}
void CIndirectDeviceContext::FrameSuperseded()
{
m_frameScheduler.FrameSuperseded();
}
void CIndirectDeviceContext::TryRecordFrameTiming(uint64_t duration)
{
m_frameScheduler.TryRecordFrameTiming(duration);
}
void CIndirectDeviceContext::AbortFrameBuffer(unsigned frameIndex)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
AcquireSRWLockExclusive(&m_framePublishLock);
m_frameBuffer[frameIndex]->wp = 0;
InterlockedExchange(
(volatile LONG *)&m_frame[frameIndex]->timingValid, 0);
m_frameInFlight[frameIndex].store(false, std::memory_order_release);
ReleaseSRWLockExclusive(&m_framePublishLock);
}
void CIndirectDeviceContext::FailFrameBuffer(unsigned frameIndex)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
InterlockedExchange((volatile LONG *)&m_frame[frameIndex]->timingValid, 0);
FinalizeFrameBuffer(frameIndex);
CompleteFrameBuffer(frameIndex, false);
}
void CIndirectDeviceContext::CompleteFrameBuffer(
unsigned frameIndex, bool succeeded)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
AcquireSRWLockExclusive(&m_framePublishLock);
if (succeeded)
{
// Completion callbacks may run out of order. Never replace a newer ready
// frame with an older submission.
const uint64_t sequence = m_frameLastPublishSequence[frameIndex];
const LONG readyFrameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (sequence &&
(readyFrameIndex < 0 ||
sequence > m_frameLastPublishSequence[readyFrameIndex]))
m_readyFrameIndex.store(
static_cast<LONG>(frameIndex), std::memory_order_release);
}
m_frameInFlight[frameIndex].store(false, std::memory_order_release);
ReleaseSRWLockExclusive(&m_framePublishLock);
}
void CIndirectDeviceContext::SetFrameTiming(unsigned frameIndex,
uint64_t captureTime, uint64_t postProcessTime, uint64_t copyTime,
uint64_t readyTime, const CFrameScheduler::Schedule& schedule,
uint64_t completedAt)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
KVMFRFrame * frame = m_frame[frameIndex];
const bool phaseValid = m_frameScheduler.TryFrameCompleted(
schedule, frame->frameSerial, completedAt);
frame->captureTime = captureTime;
frame->postProcessTime = postProcessTime;
frame->copyTime = copyTime;
frame->readyTime = readyTime;
frame->timingFlags = phaseValid ?
KVMFR_FRAME_TIMING_PHASE_VALID : 0;
frame->timingSerial = frame->frameSerial;
InterlockedExchange((volatile LONG *)&frame->timingValid, 1);
}
void CIndirectDeviceContext::WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const
{
FrameBuffer * fb = m_frameBuffer[frameIndex];
memcpy(
(void *)((uintptr_t)fb->data + offset),
(void *)((uintptr_t)src + offset),
len);
if (setWritePos)
fb->wp = (uint32_t)(offset + len);
}
void CIndirectDeviceContext::FinalizeFrameBuffer(unsigned frameIndex) const
{
const KVMFRFrame * frame = m_frame[frameIndex];
FrameBuffer * fb = m_frameBuffer[frameIndex];
fb->wp = frame->dataHeight * frame->pitch;
}
void CIndirectDeviceContext::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info,
const BYTE * data, UINT sdrWhiteLevel)
{
PLGMPMemory mem;
if (info.CursorShapeInfo.CursorType == IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED)
{
mem = m_pointerMemory[m_pointerMemoryIndex];
if (++m_pointerMemoryIndex == LGMP_Q_POINTER_LEN)
m_pointerMemoryIndex = 0;
}
else
{
mem = m_pointerShapeMemory[m_pointerShapeIndex];
if (++m_pointerShapeIndex == POINTER_SHAPE_BUFFERS)
m_pointerShapeIndex = 0;
}
KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
cursor->sdrWhiteLevel = sdrWhiteLevel ?
sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT;
m_cursorVisible = info.IsCursorVisible;
uint32_t flags = CURSOR_FLAG_VISIBLE_VALID;
if (info.IsCursorVisible)
{
m_cursorX = info.X;
m_cursorY = info.Y;
cursor->x = (int16_t)info.X;
cursor->y = (int16_t)info.Y;
flags |= CURSOR_FLAG_POSITION | CURSOR_FLAG_VISIBLE;
}
if (info.CursorShapeInfo.CursorType != IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED)
{
memcpy(cursor + 1, data,
(size_t)info.CursorShapeInfo.Height * info.CursorShapeInfo.Pitch);
cursor->hx = (int8_t )info.CursorShapeInfo.XHot;
cursor->hy = (int8_t )info.CursorShapeInfo.YHot;
cursor->width = (uint32_t)info.CursorShapeInfo.Width;
cursor->height = (uint32_t)info.CursorShapeInfo.Height;
cursor->pitch = (uint32_t)info.CursorShapeInfo.Pitch;
switch (info.CursorShapeInfo.CursorType)
{
case IDDCX_CURSOR_SHAPE_TYPE_ALPHA:
cursor->type = CURSOR_TYPE_COLOR;
break;
case IDDCX_CURSOR_SHAPE_TYPE_MASKED_COLOR:
cursor->type = CURSOR_TYPE_MASKED_COLOR;
break;
}
flags |= CURSOR_FLAG_SHAPE;
m_pointerShape = mem;
}
LGMP_STATUS status;
while ((status = lgmpHostQueuePost(m_pointerQueue, flags, mem)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_FULL)
{
Sleep(1);
continue;
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s", lgmpStatusString(status));
break;
}
}
void CIndirectDeviceContext::SetColorTransform(
std::shared_ptr<const D12ColorTransform> transform)
{
AcquireSRWLockExclusive(&m_colorTransformLock);
m_colorTransform = std::move(transform);
ReleaseSRWLockExclusive(&m_colorTransformLock);
SendColorTransform();
}
std::shared_ptr<const D12ColorTransform>
CIndirectDeviceContext::GetColorTransform() const
{
AcquireSRWLockShared(&m_colorTransformLock);
auto transform = m_colorTransform;
ReleaseSRWLockShared(&m_colorTransformLock);
return transform;
}
void CIndirectDeviceContext::SendColorTransform()
{
if (!m_pointerQueue || !m_pointerTransformMemory[0])
return;
PLGMPMemory mem = m_pointerTransformMemory[m_pointerTransformIndex];
if (++m_pointerTransformIndex == COLOR_TRANSFORM_BUFFERS)
m_pointerTransformIndex = 0;
KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
KVMFRColorTransform * output =
(KVMFRColorTransform *)(cursor + 1);
const auto transform = GetColorTransform();
output->flags = 0;
if (transform)
{
if (transform->matrixEnabled)
output->flags |= KVMFR_COLOR_TRANSFORM_MATRIX;
if (transform->lutEnabled)
output->flags |= KVMFR_COLOR_TRANSFORM_LUT;
memcpy(output->matrix, transform->matrix, sizeof(output->matrix));
output->scalar = transform->scalar;
memcpy(output->lut, transform->lut, sizeof(output->lut));
}
LGMP_STATUS status;
while ((status = lgmpHostQueuePost(m_pointerQueue,
CURSOR_FLAG_COLOR_TRANSFORM, mem)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_FULL)
{
Sleep(1);
continue;
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer Transform): %s",
lgmpStatusString(status));
break;
}
}
void CIndirectDeviceContext::ResendCursor()
{
PLGMPMemory mem = m_pointerShape;
if (!mem)
return;
KVMFRCursor* cursor = (KVMFRCursor*)lgmpHostMemPtr(mem);
cursor->x = (int16_t)m_cursorX;
cursor->y = (int16_t)m_cursorY;
const uint32_t flags =
CURSOR_FLAG_POSITION | CURSOR_FLAG_SHAPE | CURSOR_FLAG_VISIBLE_VALID |
(m_cursorVisible ? CURSOR_FLAG_VISIBLE : 0);
LGMP_STATUS status;
while ((status = lgmpHostQueuePost(m_pointerQueue, flags, mem)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_FULL)
{
Sleep(1);
continue;
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s", lgmpStatusString(status));
break;
}
}