Files
LookingGlass/idd/LGIdd/CIndirectDeviceContext.cpp
Geoffrey McRae 3ddc199bec [idd] capture: split software and hardware frame processors
Move cadence-aware hardware capture and immediate software capture
behind a common frame processor interface.

Keep shared damage tracking and frame-buffer ownership in the base
processor, and move stateless format, resource, and rectangle helpers
into CFrameProcessorUtil.

Decouple frame-buffer resources from CSwapChainProcessor by passing the
device dependencies they use directly.
2026-08-07 13:34:40 +10:00

2639 lines
82 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@%.3f",
configuredMode.extraMode ? "extra" : "configured",
configuredMode.width, configuredMode.height,
configuredMode.refreshMilliHz / 1000.0);
continue;
}
if (requiredIVSHMEMSize > m_ivshmem.GetSize())
{
DEBUG_WARN(
"Filtering %s mode %ux%u@%.3f: requires %llu bytes of IVSHMEM, only %llu bytes are available",
configuredMode.extraMode ? "extra" : "configured",
configuredMode.width, configuredMode.height,
configuredMode.refreshMilliHz / 1000.0,
(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::ReloadSettings()
{
bool modesLoaded = false;
AcquireSRWLockExclusive(&m_modeReloadLock);
CSettings::DisplayMode extraMode;
if (g_settings.GetExtraMode(extraMode))
{
const unsigned refreshMilliHz =
g_settings.GetDefaultRefreshMilliHz();
if (extraMode.refreshMilliHz != refreshMilliHz)
{
extraMode.refreshMilliHz = refreshMilliHz;
if (!g_settings.SetExtraMode(extraMode))
{
ReleaseSRWLockExclusive(&m_modeReloadLock);
return;
}
}
}
modesLoaded = PopulateDefaultModes();
ReleaseSRWLockExclusive(&m_modeReloadLock);
if (!modesLoaded)
{
DEBUG_ERROR("Failed to reload the display mode list");
return;
}
ReplugMonitor();
}
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.refreshMilliHz);
}
static UINT64 GreatestCommonDivisor(UINT64 a, UINT64 b)
{
while (b)
{
const UINT64 remainder = a % b;
a = b;
b = remainder;
}
return a;
}
static void SetSignalRate(DISPLAYCONFIG_RATIONAL& rate,
UINT64 numerator, UINT32 denominator)
{
const UINT64 divisor = GreatestCommonDivisor(numerator, denominator);
numerator /= divisor;
denominator /= (UINT32)divisor;
if (numerator <= UINT32_MAX)
{
rate.Numerator = (UINT32)numerator;
rate.Denominator = denominator;
return;
}
rate.Numerator =
(UINT32)((numerator + denominator / 2) / denominator);
rate.Denominator = 1;
}
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;
SetSignalRate(signal.vSyncFreq, mode.refreshMilliHz, 1000);
SetSignalRate(signal.hSyncFreq,
(UINT64)mode.refreshMilliHz * signal.totalSize.cy, 1000);
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.refreshMilliHz = g_settings.GetDefaultRefreshMilliHz();
mode.preferred = true;
bool modesLoaded = false;
AcquireSRWLockExclusive(&m_modeReloadLock);
if (g_settings.SetExtraMode(mode))
modesLoaded = PopulateDefaultModes();
ReleaseSRWLockExclusive(&m_modeReloadLock);
if (!modesLoaded)
{
DEBUG_ERROR("Failed to rebuild the display mode list");
return;
}
AcquireSRWLockExclusive(&m_stateLock);
m_setMode = mode;
m_doSetMode = true;
ReleaseSRWLockExclusive(&m_stateLock);
// 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_frameCompleted[i] = false;
}
m_maxFrameSize = maxFrameSize;
m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_deferredOwnerFrameIndex = -1;
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_deferredOwnerFrameIndex = -1;
m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence));
memset(m_frameCompleted, 0, sizeof(m_frameCompleted));
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_deferredOwnerFrameIndex = -1;
m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence));
memset(m_frameCompleted, 0, sizeof(m_frameCompleted));
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();
bool ownerSubscribed = false;
for (unsigned queueIndex = 0;
queueIndex < LGMP_Q_FRAME_LEN;
++queueIndex)
ownerSubscribed |=
lgmpHostQueueNewSubs(m_frameOwnerQueue[queueIndex]) != 0;
if (ownerSubscribed)
m_frameScheduler.RequestRepublish();
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;
// LGMP returns only the number of matching subscribers. Account the full
// snapshot: disappeared client IDs are harmless, while every surviving
// target received this post.
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;
}
bool CIndirectDeviceContext::FrameBufferReferenced(
unsigned frameIndex) const
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return true;
const FrameDelivery& delivery = m_frameDelivery[frameIndex];
if (delivery.ownerQueueMask || delivery.sharedPending ||
delivery.sharedOwnerPending ||
lgmpHostQueuePayloadPending(
m_frameQueue, m_frameMemory[frameIndex]))
return true;
for (unsigned queueIndex = 0;
queueIndex < LGMP_Q_FRAME_LEN;
++queueIndex)
if (lgmpHostQueuePayloadPending(
m_frameOwnerQueue[queueIndex], m_frameMemory[frameIndex]))
return true;
return false;
}
int CIndirectDeviceContext::FindAvailableFrameBuffer(
bool allowReady) 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) ||
FrameBufferReferenced(frameIndex))
continue;
if (available < 0 ||
m_frameLastPublishSequence[frameIndex] > newestPublish)
{
available = static_cast<int>(frameIndex);
newestPublish = m_frameLastPublishSequence[frameIndex];
}
}
if (available >= 0 || !allowReady || readyFrameIndex < 0 ||
m_frameInFlight[readyFrameIndex].load(std::memory_order_acquire) ||
FrameBufferReferenced(static_cast<unsigned>(readyFrameIndex)))
return available;
// A blocked owner can consume the other two buffers indefinitely. Once
// the retained frame has no queue references it is safe to replace it with
// a newer frame rather than waiting for the owner's LGMP timeout.
available = static_cast<int>(readyFrameIndex);
return available;
}
int CIndirectDeviceContext::FindNewestCompletedFrame(
unsigned excludeFrameIndex) const
{
int newestFrame = -1;
uint64_t newestSequence = 0;
for (unsigned frameIndex = 0;
frameIndex < LGMP_Q_FRAME_BUFFER_LEN;
++frameIndex)
{
if (frameIndex == excludeFrameIndex || !m_frameCompleted[frameIndex] ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire))
continue;
if (newestFrame < 0 ||
m_frameLastPublishSequence[frameIndex] > newestSequence)
{
newestFrame = static_cast<int>(frameIndex);
newestSequence = m_frameLastPublishSequence[frameIndex];
}
}
return newestFrame;
}
bool CIndirectDeviceContext::FrameBufferAvailable(
const CFrameScheduler::Schedule& schedule,
bool allowReadyReplacement)
{
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 allowReady = false;
// 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. Once both are occupied, a fully unreferenced buffer can
// still retain a newer frame for secondary delivery and later republish.
if (schedule.clientID)
{
const bool ownerBlocked =
CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN;
const bool ownerQueuesBlocked = FindAvailableOwnerQueue(0) < 0;
allowReady = allowReadyReplacement &&
(ownerBlocked || ownerQueuesBlocked);
}
else if (lgmpHostQueuePending(m_frameQueue) != 0)
{
ReleaseSRWLockShared(&m_framePublishLock);
return false;
}
// With no owner delivery lane available, a copy can still replace an
// unreferenced retained frame and be republished when a lane clears.
const bool available = FindAvailableFrameBuffer(allowReady) >= 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, const CFrameScheduler::Schedule& schedule,
bool allowReadyReplacement)
{
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 bool ownerBlocked = schedule.clientID &&
CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN;
const bool allowReady = allowReadyReplacement &&
(ownerBlocked ||
(schedule.clientID && FindAvailableOwnerQueue(0) < 0));
const int availableFrameIndex =
FindAvailableFrameBuffer(allowReady);
bool expected = false;
const bool acquired = availableFrameIndex >= 0 &&
m_frameInFlight[availableFrameIndex].compare_exchange_strong(
expected, true, std::memory_order_acq_rel);
if (acquired)
{
const LONG readyFrameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (availableFrameIndex == readyFrameIndex)
m_readyFrameIndex.store(
FindNewestCompletedFrame(
static_cast<unsigned>(availableFrameIndex)),
std::memory_order_release);
m_frameCompleted[availableFrameIndex] = false;
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->holdTime = 0;
fi->readyLeadTime = 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, bool& deliveredToOwner)
{
deliveredToOwner = false;
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)
{
// Both owner lanes still reference older frames. Retain the newest
// frame locally and serve any unblocked secondary clients without
// violating the lifetime of those outstanding LGMP payloads.
PostSharedFrame(frameIndex, schedule.clientID, now);
published = true;
}
else
{
const int ownerQueueIndex = FindAvailableOwnerQueue(frameIndex);
if (ownerQueueIndex < 0)
{
published = PostSharedOwnerFrame(frameIndex, schedule);
deliveredToOwner = published;
if (!published)
{
PostSharedFrame(frameIndex, schedule.clientID, now);
published = true;
}
}
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;
deliveredToOwner = true;
published = true;
PostSharedFrame(frameIndex, schedule.clientID, now);
}
}
}
}
else
{
published = PostSharedFrame(
frameIndex, 0, now) != SHARED_FRAME_FAILED;
deliveredToOwner = published;
}
if (published)
{
m_frameLastPublishSequence[frameIndex] = ++m_framePublishSequence;
m_deferredOwnerFrameIndex = schedule.clientID && !deliveredToOwner ?
static_cast<LONG>(frameIndex) : -1;
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;
}
LONG frameIndex = m_deferredOwnerFrameIndex;
if (frameIndex >= 0 &&
!m_frameCompleted[frameIndex] &&
!m_frameInFlight[frameIndex].load(std::memory_order_acquire))
{
m_deferredOwnerFrameIndex = -1;
frameIndex = -1;
}
if (frameIndex < 0)
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))
{
if (m_deferredOwnerFrameIndex == frameIndex)
m_deferredOwnerFrameIndex = -1;
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);
if (published && m_deferredOwnerFrameIndex == frameIndex)
m_deferredOwnerFrameIndex = -1;
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;
if (m_deferredOwnerFrameIndex == frameIndex)
m_deferredOwnerFrameIndex = -1;
}
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,
bool deliveredToOwner)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
const uint64_t now = CFrameScheduler::Nanotime();
if (deliveredToOwner)
m_frameScheduler.FramePublished(
schedule, m_frame[frameIndex]->frameSerial, now, periodic);
else
m_frameScheduler.FrameRetained(schedule, now, 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_frameCompleted[frameIndex] = false;
if (m_deferredOwnerFrameIndex == static_cast<LONG>(frameIndex))
m_deferredOwnerFrameIndex = -1;
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);
m_frameCompleted[frameIndex] = succeeded;
if (!succeeded && m_deferredOwnerFrameIndex == static_cast<LONG>(frameIndex))
m_deferredOwnerFrameIndex = -1;
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, uint64_t holdTime,
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->holdTime = holdTime;
frame->readyLeadTime = phaseValid &&
schedule.deadline >= completedAt ?
schedule.deadline - completedAt : 0;
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::WriteFrameBufferRows(unsigned frameIndex,
void * src, size_t offset, size_t rowBytes, size_t pitch,
unsigned rows) const
{
FrameBuffer * fb = m_frameBuffer[frameIndex];
uint8_t * dst = fb->data + offset;
uint8_t * source = static_cast<uint8_t *>(src) + offset;
for (unsigned row = 0; row < rows; ++row)
{
memcpy(dst, source, rowBytes);
dst += pitch;
source += pitch;
}
}
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);
std::shared_ptr<const D12ColorTransform> 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;
}
}