diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp deleted file mode 100644 index fd6d9c98..00000000 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ /dev/null @@ -1,2638 +0,0 @@ -/** - * 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 -#include - -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(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 = ∩︀ - 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 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(&kvmfr), sizeof(kvmfr)); - } - - { - const std::string & model = CPlatformInfo::GetCPUModel(); - - KVMFRRecord_VMInfo * vmInfo = static_cast(calloc(1, sizeof(*vmInfo))); - if (!vmInfo) - { - DEBUG_ERROR("Failed to allocate KVMFRRecord_VMInfo"); - return false; - } - vmInfo->cpus = static_cast(CPlatformInfo::GetProcCount ()); - vmInfo->cores = static_cast(CPlatformInfo::GetCoreCount ()); - vmInfo->sockets = static_cast(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(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(record ), sizeof(*record)); - ss.write(reinterpret_cast(vmInfo ), sizeof(*vmInfo)); - ss.write(reinterpret_cast(model.c_str()), model.length() + 1); - } - - { - KVMFRRecord_OSInfo * osInfo = static_cast(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(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(record), sizeof(*record)); - ss.write(reinterpret_cast(osInfo), sizeof(*osInfo)); - ss.write(reinterpret_cast(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(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(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(frameIndex) == readyFrameIndex || - m_frameInFlight[frameIndex].load(std::memory_order_acquire) || - FrameBufferReferenced(frameIndex)) - continue; - - if (available < 0 || - m_frameLastPublishSequence[frameIndex] > newestPublish) - { - available = static_cast(frameIndex); - newestPublish = m_frameLastPublishSequence[frameIndex]; - } - } - - if (available >= 0 || !allowReady || readyFrameIndex < 0 || - m_frameInFlight[readyFrameIndex].load(std::memory_order_acquire) || - FrameBufferReferenced(static_cast(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(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(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(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(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(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(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(frameIndex) : -1; - m_submittedFrameIndex.store( - static_cast(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(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(frameIndex)); - if (ownerQueueIndex < 0) - { - const bool published = PostSharedOwnerFrame( - static_cast(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(ownerQueueIndex); - OwnerDelivery& owner = m_ownerDelivery[queueIndex]; - owner.token = scheduleToken; - owner.clientID = schedule.clientID; - owner.frameIndex = static_cast(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(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(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(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(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 transform) -{ - AcquireSRWLockExclusive(&m_colorTransformLock); - m_colorTransform = std::move(transform); - ReleaseSRWLockExclusive(&m_colorTransformLock); - SendColorTransform(); -} - -std::shared_ptr -CIndirectDeviceContext::GetColorTransform() const -{ - AcquireSRWLockShared(&m_colorTransformLock); - std::shared_ptr 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; - } -} diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h deleted file mode 100644 index b8a1ffc9..00000000 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ /dev/null @@ -1,350 +0,0 @@ -/** - * Looking Glass - * Copyright © 2017-2026 The Looking Glass Authors - * https://looking-glass.io - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "CIVSHMEM.h" -#include "CSettings.h" -#include "CEdid.h" -#include "CPostProcessor.h" -#include "CFrameScheduler.h" - -extern "C" { - #include "lgmp/host.h" -} - -#include "common/KVMFR.h" - -// IddCx 1.10 HDR/WCG types are only visible when the WDK targets -// (NTDDI >= 0x0A000005) *and* the build flags IDDCX_VERSION_MAJOR/MINOR are set to >= 1.10. -#if defined(IDDCX_VERSION_MAJOR) && defined(IDDCX_VERSION_MINOR) && \ - (IDDCX_VERSION_MAJOR > 1 || (IDDCX_VERSION_MAJOR == 1 && IDDCX_VERSION_MINOR >= 10)) && \ - NTDDI_VERSION >= 0x0A000005 - #define HAS_IDDCX_110 -#endif - -#define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (512 * 512 * 4)) -#define COLOR_TRANSFORM_BUFFERS 3 -#define POINTER_SHAPE_BUFFERS 3 - -//FIXME: this should not really be done here, this is a hack -#pragma warning(push) -#pragma warning(disable: 4200) -struct FrameBuffer -{ - volatile uint32_t wp; - uint8_t data[0]; -}; -#pragma warning(pop) - -class CIndirectDeviceContext -{ -private: - WDFDEVICE m_wdfDevice; - IDDCX_ADAPTER m_adapter = nullptr; - IDDCX_MONITOR m_monitor = nullptr; - LUID m_preferredRenderAdapter = {}; - bool m_havePreferredRenderAdapter = false; - bool m_replugMonitor = false; - bool m_replugPending = false; - bool m_monitorDeparted = false; - bool m_swapChainAssigned = false; - bool m_swapChainReady = false; - bool m_waitForSwapChainRelease = false; - - // Guards the adapter/monitor init handshake and the replug state machine - // (monitor/replug/swap-chain state, m_doSetMode, m_setMode). These are - // touched from the IddCx callback threads, swap-chain thread and LGMP timer. - SRWLOCK m_stateLock = SRWLOCK_INIT; - - // Retry state for InitAdapter. At boot the IVSHMEM device may not have - // enumerated yet; if so we re-attempt from a timer instead of giving up. - WDFTIMER m_initTimer = nullptr; - bool m_ivshmemOpened = false; - std::atomic m_initInProgress = 0; - - CIVSHMEM m_ivshmem; - - PLGMPHost m_lgmp = nullptr; - WDFTIMER m_lgmpTimer = nullptr; - PLGMPHostQueue m_frameQueue = nullptr; - PLGMPHostQueue m_frameOwnerQueue[LGMP_Q_FRAME_LEN] = {}; - SRWLOCK m_lgmpProcessLock = SRWLOCK_INIT; - - CFrameScheduler m_frameScheduler; - - PLGMPHostQueue m_pointerQueue = nullptr; - PLGMPMemory m_pointerMemory [LGMP_Q_POINTER_LEN ] = {}; - PLGMPMemory m_pointerShapeMemory[POINTER_SHAPE_BUFFERS] = {}; - PLGMPMemory m_pointerTransformMemory[COLOR_TRANSFORM_BUFFERS] = {}; - PLGMPMemory m_pointerShape = nullptr; - int m_pointerMemoryIndex = 0; - int m_pointerShapeIndex = 0; - int m_pointerTransformIndex = 0; - bool m_cursorVisible = false; - int m_cursorX = 0, m_cursorY = 0; - - size_t m_alignSize = 0; - size_t m_frameMemoryOffset = 0; - size_t m_maxFrameSize = 0; - // LGMP publication precedes copy completion. Replay only completed frames; - // the deferred index tracks the newest frame still owed to the owner. - std::atomic m_submittedFrameIndex = -1; - std::atomic m_readyFrameIndex = -1; - LONG m_deferredOwnerFrameIndex = -1; - std::atomic m_frameInFlight[LGMP_Q_FRAME_BUFFER_LEN] = {}; - bool m_frameCompleted[LGMP_Q_FRAME_BUFFER_LEN] = {}; - SRWLOCK m_framePublishLock = SRWLOCK_INIT; - uint64_t m_framePublishSequence = 0; - uint64_t m_frameLastPublishSequence[LGMP_Q_FRAME_BUFFER_LEN] = {}; - - enum SharedFramePostResult - { - SHARED_FRAME_FAILED, - SHARED_FRAME_IDLE, - SHARED_FRAME_POSTED, - }; - - struct FrameDelivery - { - uint64_t sharedOwnerToken = 0; - unsigned ownerQueueMask = 0; - uint32_t sharedOwnerClientID = 0; - bool sharedOwnerPending = false; - bool sharedPending = false; - }; - - struct OwnerDelivery - { - uint64_t token = 0; - uint32_t clientID = 0; - unsigned frameIndex = 0; - bool active = false; - }; - - FrameDelivery m_frameDelivery[LGMP_Q_FRAME_BUFFER_LEN] = {}; - OwnerDelivery m_ownerDelivery[LGMP_Q_FRAME_LEN] = {}; - uint32_t m_formatVer = 0; - uint32_t m_frameSerial = 0; - PLGMPMemory m_frameMemory[LGMP_Q_FRAME_BUFFER_LEN] = {}; - KVMFRFrame * m_frame [LGMP_Q_FRAME_BUFFER_LEN] = {}; - FrameBuffer * m_frameBuffer[LGMP_Q_FRAME_BUFFER_LEN] = {}; - - unsigned m_width = 0; - unsigned m_height = 0; - unsigned m_frameWidth = 0; - unsigned m_frameHeight = 0; - unsigned m_pitch = 0; - DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN; - FrameType m_frameType = FRAME_TYPE_INVALID; - - UINT m_iddCxVersion = 0; - bool m_hasIddCx110DDIs = false; - bool m_canProcessFP16 = false; - bool m_softwareMode = true; - - // Previous HDR metadata used to detect changes for formatVer bumps - uint16_t m_lastHDRDisplayPrimary[3][2] = {}; - uint16_t m_lastHDRWhitePoint[2] = {}; - uint32_t m_lastHDRMaxDisplayLuminance = 0; - uint32_t m_lastHDRMinDisplayLuminance = 0; - uint32_t m_lastHDRMaxContentLightLevel = 0; - uint32_t m_lastHDRMaxFrameAverageLightLevel = 0; - uint32_t m_lastSDRWhiteLevel = 0; - bool m_lastHDRActive = false; - bool m_lastHDRMetadata = false; - - // Windows display calibration transform. The callback publishes immutable - // snapshots so the swap-chain thread never observes a partially updated - // matrix or LUT. - mutable SRWLOCK m_colorTransformLock = SRWLOCK_INIT; - std::shared_ptr m_colorTransform; - - void QueryIddCxCapabilities(); - - void ScheduleInitRetry(); - void StopInitRetry(); - - bool InitializeLGMP(); - void DeInitLGMP(); - void LGMPTimer(); - void ProcessFrameDeliveries(); - bool FrameBufferReferenced(unsigned frameIndex) const; - int FindAvailableFrameBuffer(bool allowReady) const; - int FindNewestCompletedFrame(unsigned excludeFrameIndex) const; - int FindAvailableOwnerQueue(unsigned preferredIndex) const; - unsigned CountOwnerDeliveries(uint32_t clientID) const; - bool HasMatchingOwnerDelivery(uint32_t clientID, unsigned frameIndex, - uint64_t token) const; - SharedFramePostResult PostSharedFrame(unsigned frameIndex, - uint32_t excludeClientID, uint64_t now); - bool PostSharedOwnerFrame(unsigned frameIndex, - const CFrameScheduler::Schedule& schedule); - void ResendCursor(); - void SendColorTransform(); - void InitializeEdid(); - bool GetResolutionMemoryRequirements(uint32_t width, uint32_t height, - UINT64 alignment, UINT64& frameSize, UINT64& ivshmemSize) const; - - // Guards m_displayModes and m_edid. The mode list is rebuilt on the LGMP - // timer thread (SetResolution) while IddCx concurrently enumerates it on its - // own callback threads. The EDID is initialized once and remains immutable. - // Never held across an IddCx API call - snapshot then call. - mutable SRWLOCK m_modeLock = SRWLOCK_INIT; - - // Serializes registry-backed mode changes with rebuilding m_displayModes. - // Reload requests arrive on the pipe thread while dynamic resolution - // requests arrive on the LGMP timer thread. - SRWLOCK m_modeReloadLock = SRWLOCK_INIT; - - CSettings::DisplayModes m_displayModes; - CEdid m_edid; - - CSettings::DisplayMode m_setMode = {}; - bool m_doSetMode = false; - - // Set by ReplugMonitor after a departure to rebuild the monitor from the LGMP - // timer, off the IddCx callback thread. - std::atomic m_finishInitQueued = 0; - std::atomic m_replugQueued = 0; - -public: - CIndirectDeviceContext(_In_ WDFDEVICE wdfDevice) : - m_wdfDevice(wdfDevice) {}; - - virtual ~CIndirectDeviceContext() { DeInitLGMP(); } - - bool SetupLGMP(size_t alignSize); - - bool PopulateDefaultModes(); - void InitAdapter(); - void FinishAdapterInit(UINT connectorIndex); - void FinishInit(UINT connectorIndex); - void ReloadSettings(); - void ReplugMonitor(); - - void OnMonitorDestroyed(IDDCX_MONITOR monitor); - void OnSwapChainAssigned(); - void OnSwapChainReleased(); - void OnSwapChainReady(); - - NTSTATUS ParseMonitorDescription( - const IDARG_IN_PARSEMONITORDESCRIPTION* inArgs, IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs); - NTSTATUS MonitorGetDefaultModes( - const IDARG_IN_GETDEFAULTDESCRIPTIONMODES* inArgs, IDARG_OUT_GETDEFAULTDESCRIPTIONMODES* outArgs); - NTSTATUS MonitorQueryTargetModes( - const IDARG_IN_QUERYTARGETMODES* inArgs, IDARG_OUT_QUERYTARGETMODES* outArgs); - -#ifdef HAS_IDDCX_110 - NTSTATUS ParseMonitorDescription2( - const IDARG_IN_PARSEMONITORDESCRIPTION2* inArgs, IDARG_OUT_PARSEMONITORDESCRIPTION* outArgs); - NTSTATUS MonitorQueryTargetModes2( - const IDARG_IN_QUERYTARGETMODES2* inArgs, IDARG_OUT_QUERYTARGETMODES* outArgs); -#endif - - void SetResolution(uint32_t width, uint32_t height); - - size_t GetAlignSize () const { return m_alignSize ; } - size_t GetMaxFrameSize() const { return m_maxFrameSize ; } - bool HasIddCx110DDIs() const { return m_hasIddCx110DDIs; } - bool CanProcessFP16 () const { return m_canProcessFP16; } - bool IsSoftwareMode () const { return m_softwareMode ; } - - struct PreparedFrameBuffer - { - unsigned frameIndex; - uint8_t* mem; - bool fullCopy; - }; - - bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule, - bool allowReadyReplacement = true); - bool HasPublishedFrame() const - { - return m_readyFrameIndex.load(std::memory_order_acquire) >= 0; - } - void ProcessFrameQueue(); - bool GetSharedFrameTarget(uint64_t now, uint64_t& target); - bool ReplaySharedFrame(uint64_t now, bool& retry); - PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, - const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, - const RECT * dirtyRects, unsigned nbDirtyRects, - const CFrameScheduler::Schedule& schedule, - bool allowReadyReplacement = true); - bool PublishFrameBuffer(unsigned frameIndex, - const CFrameScheduler::Schedule& schedule, bool& deliveredToOwner); - bool RepublishFrameBuffer(const CFrameScheduler::Schedule& schedule); - bool TryFrameSubmitted(unsigned frameIndex, - const CFrameScheduler::Schedule& schedule); - void CommitFrameBuffer(unsigned frameIndex, - const CFrameScheduler::Schedule& schedule, bool periodic, - bool deliveredToOwner); - void AbortFrameBuffer(unsigned frameIndex); - void FailFrameBuffer(unsigned frameIndex); - void CompleteFrameBuffer(unsigned frameIndex, bool succeeded); - void 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); - void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const; - void WriteFrameBufferRows(unsigned frameIndex, void * src, - size_t offset, size_t rowBytes, size_t pitch, unsigned rows) const; - void FinalizeFrameBuffer(unsigned frameIndex) const; - - void ObserveFrame(uint64_t now); - void ForceFrame(); - bool GetPublishTarget(uint64_t now, uint64_t& target, - CFrameScheduler::Schedule& schedule, bool& periodic, bool& republish); - void FrameMissed(const CFrameScheduler::Schedule& schedule, - uint64_t now, bool periodic); - void FrameSuperseded(); - HANDLE GetFrameScheduleEvent() const - { - return m_frameScheduler.GetWakeEvent(); - } - void TryRecordFrameTiming(uint64_t duration); - - void SendCursor(const IDARG_OUT_QUERY_HWCURSOR & info, const BYTE * data, - UINT sdrWhiteLevel); - - void SetColorTransform(std::shared_ptr transform); - std::shared_ptr GetColorTransform() const; - - CIVSHMEM &GetIVSHMEM() { return m_ivshmem; } -}; - -struct CIndirectDeviceContextWrapper -{ - CIndirectDeviceContext* context; - - void Cleanup() - { - delete context; - context = nullptr; - } -}; - -WDF_DECLARE_CONTEXT_TYPE(CIndirectDeviceContextWrapper); diff --git a/idd/LGIdd/CInteropResource.h b/idd/LGIdd/CInteropResource.h deleted file mode 100644 index 07b6f16f..00000000 --- a/idd/LGIdd/CInteropResource.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Looking Glass - * Copyright © 2017-2026 The Looking Glass Authors - * https://looking-glass.io - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#pragma once - -#include -#include -#include -#include - -#include "CD3D11Device.h" -#include "CD3D12Device.h" -#include "CD3D12CommandQueue.h" - -using namespace Microsoft::WRL; - -#define LG_MAX_DIRTY_RECTS 256 - -class CInteropResource -{ - private: - std::shared_ptr m_dx11Device; - std::shared_ptr m_dx12Device; - - /* this value is likely released, it is only used to check if the texture supplied - is different, do not rely on it pointing to valid memory */ - void * m_srcTex; - - ComPtr m_d12Res; - D3D11_TEXTURE2D_DESC m_format; - ComPtr m_d11Fence; - ComPtr m_d12Fence; - UINT64 m_fenceValue; - bool m_ready; - - RECT m_dirtyRects[LG_MAX_DIRTY_RECTS]; - unsigned m_nbDirtyRects; - - public: - bool Init(std::shared_ptr dx11Device, std::shared_ptr dx12Device, ComPtr srcTex); - void Reset(); - - bool IsReady() { return m_ready; } - bool Compare(const ComPtr& srcTex); - bool Signal(); - bool Sync(CD3D12CommandSlot& slot); - void SetFullDamage(); - void SetDirtyRects(const RECT * dirtyRects, unsigned nbDirtyRects); - - const ComPtr& GetRes() { return m_d12Res; } - const D3D11_TEXTURE2D_DESC& GetFormat() { return m_format; } - const RECT * GetDirtyRects() { return m_dirtyRects; } - unsigned GetDirtyRectCount() { return m_nbDirtyRects; } -}; diff --git a/idd/LGIdd/Device.cpp b/idd/LGIdd/Device.cpp index b5201268..beb0a5ef 100644 --- a/idd/LGIdd/Device.cpp +++ b/idd/LGIdd/Device.cpp @@ -32,10 +32,13 @@ #include #include "CDebug.h" -#include "CIndirectDeviceContext.h" -#include "CIndirectMonitorContext.h" -#include "CPipeServer.h" -#include "CSettings.h" +#include "display/CDisplayConfiguration.h" +#include "display/IddCxCompat.h" +#include "display/device/CDeviceContext.h" +#include "display/monitor/Context.h" +#include "transport/CLGMPControl.h" +#include "transport/CPipeServer.h" +#include "config/CSettings.h" WDFDEVICE l_wdfDevice = nullptr; @@ -65,7 +68,7 @@ NTSTATUS LGIddDeviceD0Entry(WDFDEVICE device, WDF_POWER_DEVICE_STATE previousSta UNREFERENCED_PARAMETER(previousState); DEBUG_INFO("Device entered D0, starting adapter initialization"); - auto * wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(device); + auto * wrapper = WdfObjectGet_CDeviceContextWrapper(device); wrapper->context->InitAdapter(); DEBUG_INFO("Device D0 entry completed"); @@ -77,7 +80,7 @@ NTSTATUS LGIddAdapterInitFinished(IDDCX_ADAPTER adapter, const IDARG_IN_ADAPTER_ DEBUG_INFO("Adapter initialization callback completed with status 0x%08x", args->AdapterInitStatus); - auto * wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(adapter); + auto * wrapper = WdfObjectGet_CDeviceContextWrapper(adapter); if (!NT_SUCCESS(args->AdapterInitStatus)) { DEBUG_ERROR_HR(args->AdapterInitStatus, @@ -110,22 +113,27 @@ NTSTATUS LGIddParseMonitorDescription(const IDARG_IN_PARSEMONITORDESCRIPTION* in if (!l_wdfDevice) return STATUS_INVALID_PARAMETER; - auto* wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(l_wdfDevice); - return wrapper->context->ParseMonitorDescription(inArgs, outArgs); + auto * wrapper = WdfObjectGet_CDeviceContextWrapper(l_wdfDevice); + return wrapper->context->GetDisplayConfiguration().ParseMonitorDescription( + inArgs, outArgs); } NTSTATUS LGIddMonitorGetDefaultModes(IDDCX_MONITOR monitor, const IDARG_IN_GETDEFAULTDESCRIPTIONMODES * inArgs, IDARG_OUT_GETDEFAULTDESCRIPTIONMODES * outArgs) { - auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); - return wrapper->context->GetDeviceContext()->MonitorGetDefaultModes(inArgs, outArgs); + auto * wrapper = WdfObjectGet_CMonitorContextWrapper(monitor); + auto * context = wrapper->context->GetDeviceContext(); + return context->GetDisplayConfiguration().MonitorGetDefaultModes( + inArgs, outArgs); } NTSTATUS LGIddMonitorQueryTargetModes(IDDCX_MONITOR monitor, const IDARG_IN_QUERYTARGETMODES * inArgs, IDARG_OUT_QUERYTARGETMODES * outArgs) { - auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); - return wrapper->context->GetDeviceContext()->MonitorQueryTargetModes(inArgs, outArgs); + auto * wrapper = WdfObjectGet_CMonitorContextWrapper(monitor); + auto * context = wrapper->context->GetDeviceContext(); + return context->GetDisplayConfiguration().MonitorQueryTargetModes( + inArgs, outArgs); } @@ -136,8 +144,9 @@ NTSTATUS LGIddParseMonitorDescription2(const IDARG_IN_PARSEMONITORDESCRIPTION2* if (!l_wdfDevice) return STATUS_INVALID_PARAMETER; - auto* wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(l_wdfDevice); - return wrapper->context->ParseMonitorDescription2(inArgs, outArgs); + auto * wrapper = WdfObjectGet_CDeviceContextWrapper(l_wdfDevice); + return wrapper->context->GetDisplayConfiguration().ParseMonitorDescription2( + inArgs, outArgs); } NTSTATUS LGIddAdapterQueryTargetInfo(IDDCX_ADAPTER adapter, @@ -145,7 +154,7 @@ NTSTATUS LGIddAdapterQueryTargetInfo(IDDCX_ADAPTER adapter, { UNREFERENCED_PARAMETER(inArgs); - auto* wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(adapter); + auto * wrapper = WdfObjectGet_CDeviceContextWrapper(adapter); const bool hdr = wrapper && wrapper->context && wrapper->context->CanProcessFP16(); @@ -182,19 +191,20 @@ NTSTATUS LGIddMonitorSetDefaultHdrMetadata(IDDCX_MONITOR monitor, NTSTATUS LGIddMonitorSetGammaRamp(IDDCX_MONITOR monitor, const IDARG_IN_SET_GAMMARAMP* inArgs) { - auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); - auto* ctx = wrapper->context->GetDeviceContext(); + auto * wrapper = WdfObjectGet_CMonitorContextWrapper(monitor); + auto * ctx = wrapper->context->GetDeviceContext(); + auto & control = ctx->GetLGMPControl(); if (ctx->IsSoftwareMode()) { - ctx->SetColorTransform(nullptr); + control.SetColorTransform(nullptr); DEBUG_INFO("Ignoring display color transform in software mode"); return STATUS_SUCCESS; } if (inArgs->Type == IDDCX_GAMMARAMP_TYPE_DEFAULT) { - ctx->SetColorTransform(nullptr); + control.SetColorTransform(nullptr); DEBUG_INFO("Display color transform reset to default"); return STATUS_SUCCESS; } @@ -214,7 +224,7 @@ NTSTATUS LGIddMonitorSetGammaRamp(IDDCX_MONITOR monitor, const IDARG_IN_SET_GAMM return STATUS_INVALID_PARAMETER; } - const auto* input = static_cast< + const auto * input = static_cast< const IDDCX_GAMMARAMP_3X4_COLORSPACE_TRANSFORM*>( inArgs->pGammaRampData); auto transform = std::make_shared(); @@ -233,11 +243,11 @@ NTSTATUS LGIddMonitorSetGammaRamp(IDDCX_MONITOR monitor, const IDARG_IN_SET_GAMM if (IsIdentityColorTransform(*transform)) { - ctx->SetColorTransform(nullptr); + control.SetColorTransform(nullptr); return STATUS_SUCCESS; } - ctx->SetColorTransform(std::move(transform)); + control.SetColorTransform(std::move(transform)); DEBUG_INFO("Display color transform updated (matrix:%d lut:%d)", input->MatrixEnabled, input->LutEnabled); return STATUS_SUCCESS; @@ -246,15 +256,17 @@ NTSTATUS LGIddMonitorSetGammaRamp(IDDCX_MONITOR monitor, const IDARG_IN_SET_GAMM NTSTATUS LGIddMonitorQueryTargetModes2(IDDCX_MONITOR monitor, const IDARG_IN_QUERYTARGETMODES2* inArgs, IDARG_OUT_QUERYTARGETMODES* outArgs) { - auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); - return wrapper->context->GetDeviceContext()->MonitorQueryTargetModes2(inArgs, outArgs); + auto * wrapper = WdfObjectGet_CMonitorContextWrapper(monitor); + auto * context = wrapper->context->GetDeviceContext(); + return context->GetDisplayConfiguration().MonitorQueryTargetModes2( + inArgs, outArgs); } #endif NTSTATUS LGIddMonitorAssignSwapChain(IDDCX_MONITOR monitor, const IDARG_IN_SETSWAPCHAIN* inArgs) { DEBUG_INFO("Swap chain assigned to monitor %p", monitor); - auto * wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); + auto * wrapper = WdfObjectGet_CMonitorContextWrapper(monitor); return wrapper->context->AssignSwapChain( inArgs->hSwapChain, inArgs->RenderAdapterLuid, inArgs->hNextSurfaceAvailable); } @@ -262,7 +274,7 @@ NTSTATUS LGIddMonitorAssignSwapChain(IDDCX_MONITOR monitor, const IDARG_IN_SETSW NTSTATUS LGIddMonitorUnassignSwapChain(IDDCX_MONITOR monitor) { DEBUG_INFO("Swap chain unassigned from monitor %p", monitor); - auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); + auto * wrapper = WdfObjectGet_CMonitorContextWrapper(monitor); wrapper->context->UnassignSwapChain(); return STATUS_SUCCESS; } @@ -316,10 +328,10 @@ NTSTATUS LGIddCreateDevice(_Inout_ PWDFDEVICE_INIT deviceInit) return status; WDF_OBJECT_ATTRIBUTES deviceAttributes; - WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, CIndirectDeviceContextWrapper); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, CDeviceContextWrapper); deviceAttributes.EvtCleanupCallback = [](WDFOBJECT object) { - auto * wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(object); + auto * wrapper = WdfObjectGet_CDeviceContextWrapper(object); if (wrapper) { g_pipe.SetDeviceContext(nullptr); @@ -339,8 +351,8 @@ NTSTATUS LGIddCreateDevice(_Inout_ PWDFDEVICE_INIT deviceInit) * callbacks that resolve the context via l_wdfDevice (down-level IddCx that * provides no adapter/monitor context) must never observe a null context. */ - auto wrapper = WdfObjectGet_CIndirectDeviceContextWrapper(device); - wrapper->context = new CIndirectDeviceContext(device); + auto wrapper = WdfObjectGet_CDeviceContextWrapper(device); + wrapper->context = new CDeviceContext(device); l_wdfDevice = device; diff --git a/idd/LGIdd/Driver.cpp b/idd/LGIdd/Driver.cpp index 4ff8656b..47ea777d 100644 --- a/idd/LGIdd/Driver.cpp +++ b/idd/LGIdd/Driver.cpp @@ -22,9 +22,9 @@ #include "driver.tmh" #include "CDebug.h" -#include "CPlatformInfo.h" +#include "platform/CPlatformInfo.h" #include "VersionInfo.h" -#include "CPipeServer.h" +#include "transport/CPipeServer.h" NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { @@ -98,4 +98,4 @@ VOID LGIddEvtDriverContextCleanup(_In_ WDFOBJECT DriverObject) #else WPP_CLEANUP(WdfDriverWdmGetDriverObject((WDFDRIVER)DriverObject)); #endif -} \ No newline at end of file +} diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index 26f5df8d..572607a5 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -24,68 +24,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -198,7 +215,7 @@ true trace.h /EHsc /D_ATL_NO_WIN_SUPPORT /DIDDCX_VERSION_MAJOR=1 /DIDDCX_VERSION_MINOR=10 /DIDDCX_MINIMUM_VERSION_REQUIRED=4 %(AdditionalOptions) - $(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib @@ -213,7 +230,7 @@ true trace.h /EHsc /D_ATL_NO_WIN_SUPPORT /DIDDCX_VERSION_MAJOR=1 /DIDDCX_VERSION_MINOR=10 /DIDDCX_MINIMUM_VERSION_REQUIRED=4 %(AdditionalOptions) - $(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib @@ -228,7 +245,7 @@ true trace.h /EHsc /D_ATL_NO_WIN_SUPPORT /DIDDCX_VERSION_MAJOR=1 /DIDDCX_VERSION_MINOR=10 /DIDDCX_MINIMUM_VERSION_REQUIRED=4 %(AdditionalOptions) - $(SolutionDir)LGCommon;$(SolutionDir)..\repos\LGMP\lgmp\include;$(SolutionDir)..\vendor;$(SolutionDir)..\common\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib @@ -243,7 +260,7 @@ true trace.h /EHsc /D_ATL_NO_WIN_SUPPORT /DIDDCX_VERSION_MAJOR=1 /DIDDCX_VERSION_MINOR=10 /DIDDCX_MINIMUM_VERSION_REQUIRED=4 %(AdditionalOptions) - $(SolutionDir)LGCommon;$(SolutionDir)..\repos\LGMP\lgmp\include;$(SolutionDir)..\vendor;$(SolutionDir)..\common\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index 0e8524ac..621f6037 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -1,27 +1,45 @@  - + - + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd + + {D2C16E51-6087-4E08-8DA4-5AD1A60EF58E} - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + {83AAC4D0-9E4C-4B25-BD54-9186713B2010} - - {8E41214B-6785-4CFE-B992-037D68949A14} - inf;inv;inx;mof;mc; + + {3AF4227D-EBC0-45FB-9670-192E371313D5} - + + {202B79B2-5CCF-4AD4-B4C5-F5602419683D} + + + {4B3C58D3-5F40-49AB-894E-B83EB44E6CF5} + + + {A2E40D7C-854F-42E2-8730-A1BFD37A6BB0} + + {52F1286F-0C64-41B9-92A9-4453C80B1001} - - {DB623F3B-5D6A-4F8C-8884-83588A1B58D2} + + {22FDF1F6-0A8C-4B96-9E74-53D74733CC0A} + + + {82810F1C-E51C-4DBD-86B2-1CDE8C16A2A9} + + + {1C677205-7587-4037-9E36-92DFE600B985} + + + {938E49D6-F954-4EBE-80AB-E0F67E677F27} + + + {98768720-86D6-4A83-9EBD-2C8BFB51D793} @@ -30,187 +48,242 @@ - Driver Files + Driver + + Common + - Header Files + Driver - Header Files + Driver - Header Files + Driver - Header Files + Driver - - Header Files + + Display - - Header Files + + Display - - Header Files + + Display - - Header Files + + Display - - Header Files + + Display\Device - - Header Files + + Display\Monitor - - Header Files + + Capture - - Header Files + + Capture - - Header Files + + Capture - - Header Files + + Capture - - Header Files + + Capture - - Header Files + + Capture - - Header Files + + Capture - - Header Files + + Capture - - Header Files + + Capture - - Header Files + + D3D - - - Header Files + + D3D - - - - Header Files + + D3D - - Header Files + + D3D - - Header Files + + D3D - - Header Files + + Post-processing - - Header Files\effect + + Post-processing - - Header Files\effect + + Post-processing\Effects - - Header Files\effect + + Post-processing\Effects - - Header Files\effect + + Post-processing\Effects + + + Post-processing\Effects + + + Post-processing\Effects + + + Transport + + + Transport + + + Transport + + + Transport + + + Transport + + + Transport + + + Configuration + + + Platform + + + Utilities - - - - Source Files + + Common - Source Files + Driver - - Source Files + + Driver - - Source Files + + Display - - Source Files + + Display - - Source Files + + Display - - Source Files + + Display\Device - - Source Files + + Display\Monitor - - Source Files + + Capture - - Source Files + + Capture - - Source Files + + Capture - - Source Files + + Capture - - Source Files + + Capture - - Source Files + + Capture - - Source Files + + Capture - - Source Files + + Capture - - Source Files + + Capture - - Source Files + + Capture - - - Source Files + + D3D - - - Source Files + + D3D - - Source Files + + D3D - - Source Files + + D3D - - Source Files + + D3D - - Source Files\effect + + Post-processing - - Source Files\effect + + Post-processing\Effects - - Source Files\effect + + Post-processing\Effects - - Source Files\effect + + Post-processing\Effects + + + Post-processing\Effects + + + Post-processing\Effects + + + Transport + + + Transport + + + Transport + + + Transport + + + Transport + + + Configuration + + + Platform - diff --git a/idd/LGIdd/CFrameBufferPool.cpp b/idd/LGIdd/capture/CFrameBufferPool.cpp similarity index 83% rename from idd/LGIdd/CFrameBufferPool.cpp rename to idd/LGIdd/capture/CFrameBufferPool.cpp index 80461408..7157d1df 100644 --- a/idd/LGIdd/CFrameBufferPool.cpp +++ b/idd/LGIdd/capture/CFrameBufferPool.cpp @@ -18,15 +18,15 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CFrameBufferPool.h" +#include "capture/CFrameBufferPool.h" #include void CFrameBufferPool::Init( - CIndirectDeviceContext * device, CD3D12Device * dx12) + CFrameTransport * transport, CD3D12Device * dx12) { - m_device = device; - m_dx12 = dx12; + m_transport = transport; + m_dx12 = dx12; } void CFrameBufferPool::Reset() @@ -36,14 +36,14 @@ void CFrameBufferPool::Reset() } CFrameBufferResource * CFrameBufferPool::Get( - const CIndirectDeviceContext::PreparedFrameBuffer& buffer, + const PreparedFrameBuffer& buffer, size_t minSize, const D3D12_RESOURCE_DESC * textureDesc) { if (buffer.frameIndex > ARRAYSIZE(m_buffers) - 1) return nullptr; CFrameBufferResource * fbr = &m_buffers[buffer.frameIndex]; - if (!fbr->Init(m_device, m_dx12, buffer.frameIndex, buffer.mem, + if (!fbr->Init(m_transport, m_dx12, buffer.frameIndex, buffer.mem, minSize, textureDesc)) return nullptr; diff --git a/idd/LGIdd/CFrameBufferPool.h b/idd/LGIdd/capture/CFrameBufferPool.h similarity index 68% rename from idd/LGIdd/CFrameBufferPool.h rename to idd/LGIdd/capture/CFrameBufferPool.h index 3a0b15e6..50062db0 100644 --- a/idd/LGIdd/CFrameBufferPool.h +++ b/idd/LGIdd/capture/CFrameBufferPool.h @@ -20,25 +20,26 @@ #pragma once -#include "CFrameBufferResource.h" -#include "CIndirectDeviceContext.h" +#include "capture/CFrameBufferResource.h" +#include "capture/FrameBufferTypes.h" #include "common/KVMFR.h" struct CD3D12Device; +class CFrameTransport; class CFrameBufferPool { - CIndirectDeviceContext * m_device = nullptr; - CD3D12Device * m_dx12 = nullptr; +private: + CFrameTransport * m_transport = nullptr; + CD3D12Device * m_dx12 = nullptr; CFrameBufferResource m_buffers[LGMP_Q_FRAME_BUFFER_LEN]; - public: - void Init(CIndirectDeviceContext * device, CD3D12Device * dx12); - void Reset(); +public: + void Init(CFrameTransport * transport, CD3D12Device * dx12); + void Reset(); - CFrameBufferResource * Get( - const CIndirectDeviceContext::PreparedFrameBuffer& buffer, - size_t minSize, - const D3D12_RESOURCE_DESC * textureDesc = nullptr); + CFrameBufferResource * Get(const PreparedFrameBuffer& buffer, + size_t minSize, + const D3D12_RESOURCE_DESC * textureDesc = nullptr); }; diff --git a/idd/LGIdd/CFrameBufferResource.cpp b/idd/LGIdd/capture/CFrameBufferResource.cpp similarity index 94% rename from idd/LGIdd/CFrameBufferResource.cpp rename to idd/LGIdd/capture/CFrameBufferResource.cpp index 65bbfa09..ca7cb350 100644 --- a/idd/LGIdd/CFrameBufferResource.cpp +++ b/idd/LGIdd/capture/CFrameBufferResource.cpp @@ -18,21 +18,22 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CFrameBufferResource.h" -#include "CFrameProcessorUtil.h" -#include "CD3D12Device.h" -#include "CIndirectDeviceContext.h" +#include "capture/CFrameBufferResource.h" +#include "capture/CFrameProcessorUtil.h" +#include "d3d/CD3D12Device.h" +#include "transport/CFrameTransport.h" +#include "transport/CIVSHMEM.h" #include "CDebug.h" #include -bool CFrameBufferResource::Init(CIndirectDeviceContext * device, +bool CFrameBufferResource::Init(CFrameTransport * transport, CD3D12Device * dx12, unsigned frameIndex, uint8_t * base, size_t size, const D3D12_RESOURCE_DESC * textureDesc) { m_frameIndex = frameIndex; - if (size > device->GetMaxFrameSize()) + if (size > transport->GetMaxFrameSize()) { DEBUG_ERROR("Frame size of %llu is too large to fit in shared ram", (unsigned long long)size); @@ -131,14 +132,14 @@ bool CFrameBufferResource::Init(CIndirectDeviceContext * device, { const UINT64 heapOffset = (uintptr_t)base - - (uintptr_t)device->GetIVSHMEM().GetMem(); + (uintptr_t)transport->GetIVSHMEM().GetMem(); const D3D12_RESOURCE_ALLOCATION_INFO allocation = dx12->GetDevice()->GetResourceAllocationInfo(0, 1, &desc); allocationSize = allocation.SizeInBytes; const D3D12_HEAP_DESC heapDesc = dx12->GetHeap()->GetDesc(); if (!allocation.Alignment || heapOffset % allocation.Alignment || - allocation.SizeInBytes > device->GetMaxFrameSize() || + allocation.SizeInBytes > transport->GetMaxFrameSize() || heapOffset > heapDesc.SizeInBytes || allocation.SizeInBytes > heapDesc.SizeInBytes - heapOffset) { diff --git a/idd/LGIdd/CFrameBufferResource.h b/idd/LGIdd/capture/CFrameBufferResource.h similarity index 96% rename from idd/LGIdd/CFrameBufferResource.h rename to idd/LGIdd/capture/CFrameBufferResource.h index c14e4478..0ae0e1a8 100644 --- a/idd/LGIdd/CFrameBufferResource.h +++ b/idd/LGIdd/capture/CFrameBufferResource.h @@ -27,11 +27,11 @@ #include #include -#include "CFrameScheduler.h" -#include "CInteropResource.h" +#include "capture/CFrameScheduler.h" +#include "d3d/CInteropResource.h" struct CD3D12Device; -class CIndirectDeviceContext; +class CFrameTransport; using namespace Microsoft::WRL; @@ -70,7 +70,7 @@ class CFrameBufferResource void * m_map = nullptr; public: - bool Init(CIndirectDeviceContext * device, CD3D12Device * dx12, + bool Init(CFrameTransport * transport, CD3D12Device * dx12, unsigned frameIndex, uint8_t * base, size_t size, const D3D12_RESOURCE_DESC * textureDesc = nullptr); void Reset(); diff --git a/idd/LGIdd/CFrameProcessor.cpp b/idd/LGIdd/capture/CFrameProcessor.cpp similarity index 90% rename from idd/LGIdd/CFrameProcessor.cpp rename to idd/LGIdd/capture/CFrameProcessor.cpp index 3d62257b..52407b00 100644 --- a/idd/LGIdd/CFrameProcessor.cpp +++ b/idd/LGIdd/capture/CFrameProcessor.cpp @@ -18,27 +18,27 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CFrameProcessor.h" -#include "CFrameProcessorUtil.h" -#include "CHardwareFrameProcessor.h" -#include "CSoftwareFrameProcessor.h" +#include "capture/CFrameProcessor.h" +#include "capture/CFrameProcessorUtil.h" +#include "capture/CHardwareFrameProcessor.h" +#include "capture/CSoftwareFrameProcessor.h" #include #include #include -CFrameProcessor::CFrameProcessor(CIndirectDeviceContext * device, +CFrameProcessor::CFrameProcessor(CFrameTransport * transport, std::shared_ptr dx12, CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], SRWLOCK * pipelineLock, HANDLE terminateEvent) : - m_device(device), + m_transport(transport), m_dx12(std::move(dx12)), m_postProcessors(postProcessors), m_pipelineLock(pipelineLock), m_terminateEvent(terminateEvent) { m_readyEvent.Attach(CreateEvent(nullptr, FALSE, FALSE, nullptr)); - m_frameBuffers.Init(m_device, m_dx12.get()); + m_frameBuffers.Init(m_transport, m_dx12.get()); } bool CFrameProcessor::IsValid() const @@ -154,7 +154,7 @@ void CFrameProcessor::GetPreviousDamage( } std::unique_ptr CreateFrameProcessor( - bool software, CIndirectDeviceContext * device, + bool software, CFrameTransport * transport, std::shared_ptr dx12, CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], SRWLOCK * pipelineLock, HANDLE terminateEvent) @@ -162,11 +162,11 @@ std::unique_ptr CreateFrameProcessor( std::unique_ptr processor; if (software) processor.reset(new (std::nothrow) CSoftwareFrameProcessor( - device, std::move(dx12), postProcessors, + transport, std::move(dx12), postProcessors, pipelineLock, terminateEvent)); else processor.reset(new (std::nothrow) CHardwareFrameProcessor( - device, std::move(dx12), postProcessors, + transport, std::move(dx12), postProcessors, pipelineLock, terminateEvent)); if (!processor || !processor->IsValid()) diff --git a/idd/LGIdd/CFrameProcessor.h b/idd/LGIdd/capture/CFrameProcessor.h similarity index 83% rename from idd/LGIdd/CFrameProcessor.h rename to idd/LGIdd/capture/CFrameProcessor.h index dd421234..951f29f7 100644 --- a/idd/LGIdd/CFrameProcessor.h +++ b/idd/LGIdd/capture/CFrameProcessor.h @@ -20,17 +20,18 @@ #pragma once -#include "CD3D12Device.h" -#include "CFrameBufferPool.h" -#include "CIndirectDeviceContext.h" -#include "CInteropResource.h" -#include "CPostProcessor.h" +#include "d3d/CD3D12Device.h" +#include "capture/CFrameBufferPool.h" +#include "d3d/CInteropResource.h" +#include "postprocess/CPostProcessor.h" #include #include using namespace Microsoft::WRL; +class CFrameTransport; + struct FrameSubmission { CInteropResource * source; @@ -45,13 +46,13 @@ struct FrameSubmission class CFrameProcessor { protected: - CIndirectDeviceContext * m_device; - std::shared_ptr m_dx12; - CPostProcessor * m_postProcessors; - SRWLOCK * m_pipelineLock; - HANDLE m_terminateEvent; - CFrameBufferPool m_frameBuffers; - Wrappers::Event m_readyEvent; + CFrameTransport * m_transport; + std::shared_ptr m_dx12; + CPostProcessor * m_postProcessors; + SRWLOCK * m_pipelineLock; + HANDLE m_terminateEvent; + CFrameBufferPool m_frameBuffers; + Wrappers::Event m_readyEvent; mutable SRWLOCK m_damageLock = SRWLOCK_INIT; RECT m_previousDamage[LG_MAX_DIRTY_RECTS] = {}; @@ -71,7 +72,7 @@ protected: virtual void SetFullDamageLocked(); public: - CFrameProcessor(CIndirectDeviceContext * device, + CFrameProcessor(CFrameTransport * transport, std::shared_ptr dx12, CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], SRWLOCK * pipelineLock, HANDLE terminateEvent); @@ -93,7 +94,7 @@ public: }; std::unique_ptr CreateFrameProcessor( - bool software, CIndirectDeviceContext * device, + bool software, CFrameTransport * transport, std::shared_ptr dx12, CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], SRWLOCK * pipelineLock, HANDLE terminateEvent); diff --git a/idd/LGIdd/CFrameProcessorUtil.cpp b/idd/LGIdd/capture/CFrameProcessorUtil.cpp similarity index 98% rename from idd/LGIdd/CFrameProcessorUtil.cpp rename to idd/LGIdd/capture/CFrameProcessorUtil.cpp index b3d0b0f6..fa1b8726 100644 --- a/idd/LGIdd/CFrameProcessorUtil.cpp +++ b/idd/LGIdd/capture/CFrameProcessorUtil.cpp @@ -18,7 +18,9 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CFrameProcessorUtil.h" +#include "capture/CFrameProcessorUtil.h" +#include "d3d/CInteropResource.h" +#include "postprocess/CPostProcessor.h" #include diff --git a/idd/LGIdd/CFrameProcessorUtil.h b/idd/LGIdd/capture/CFrameProcessorUtil.h similarity index 84% rename from idd/LGIdd/CFrameProcessorUtil.h rename to idd/LGIdd/capture/CFrameProcessorUtil.h index 687a3c3e..eb740ac4 100644 --- a/idd/LGIdd/CFrameProcessorUtil.h +++ b/idd/LGIdd/capture/CFrameProcessorUtil.h @@ -20,25 +20,9 @@ #pragma once -#include "CInteropResource.h" -#include "CPostProcessor.h" +#include "postprocess/D12FrameFormat.h" -class CFrameProcessorSharedLock -{ -private: - SRWLOCK * m_lock; - -public: - explicit CFrameProcessorSharedLock(SRWLOCK * lock) : m_lock(lock) - { - AcquireSRWLockShared(m_lock); - } - - ~CFrameProcessorSharedLock() - { - ReleaseSRWLockShared(m_lock); - } -}; +class CPostProcessor; class CFrameProcessorUtil { diff --git a/idd/LGIdd/CFrameScheduler.cpp b/idd/LGIdd/capture/CFrameScheduler.cpp similarity index 99% rename from idd/LGIdd/CFrameScheduler.cpp rename to idd/LGIdd/capture/CFrameScheduler.cpp index 30ed30f7..e9ab4f89 100644 --- a/idd/LGIdd/CFrameScheduler.cpp +++ b/idd/LGIdd/capture/CFrameScheduler.cpp @@ -18,7 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CFrameScheduler.h" +#include "capture/CFrameScheduler.h" #include "CDebug.h" diff --git a/idd/LGIdd/CFrameScheduler.h b/idd/LGIdd/capture/CFrameScheduler.h similarity index 100% rename from idd/LGIdd/CFrameScheduler.h rename to idd/LGIdd/capture/CFrameScheduler.h diff --git a/idd/LGIdd/CHardwareFrameProcessor.cpp b/idd/LGIdd/capture/CHardwareFrameProcessor.cpp similarity index 94% rename from idd/LGIdd/CHardwareFrameProcessor.cpp rename to idd/LGIdd/capture/CHardwareFrameProcessor.cpp index 258a90f9..7ac4b4a8 100644 --- a/idd/LGIdd/CHardwareFrameProcessor.cpp +++ b/idd/LGIdd/capture/CHardwareFrameProcessor.cpp @@ -18,8 +18,10 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CHardwareFrameProcessor.h" -#include "CFrameProcessorUtil.h" +#include "capture/CHardwareFrameProcessor.h" +#include "capture/CFrameProcessorUtil.h" +#include "transport/CFrameTransport.h" +#include "util/CSRWLock.h" #include "CDebug.h" #include @@ -69,10 +71,10 @@ public: }; CHardwareFrameProcessor::CHardwareFrameProcessor( - CIndirectDeviceContext * device, std::shared_ptr dx12, + CFrameTransport * transport, std::shared_ptr dx12, CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], SRWLOCK * pipelineLock, HANDLE terminateEvent) : - CFrameProcessor(device, std::move(dx12), postProcessors, + CFrameProcessor(transport, std::move(dx12), postProcessors, pipelineLock, terminateEvent) { m_candidateAvailableEvent.Attach( @@ -203,7 +205,7 @@ int CHardwareFrameProcessor::AcquireCandidate( ReleaseSRWLockExclusive(&m_candidateLock); if (superseded) - m_device->FrameSuperseded(); + m_transport->FrameSuperseded(); return selected; } @@ -327,10 +329,10 @@ void CHardwareFrameProcessor::CandidateCompletionFunction( if (!result) { processor->SetFullDamage(); - processor->m_device->ForceFrame(); + processor->m_transport->ForceFrame(); } else if (forceFrame) - processor->m_device->ForceFrame(); + processor->m_transport->ForceFrame(); processor->SignalCandidateState(); } @@ -343,9 +345,9 @@ void CHardwareFrameProcessor::CompletionFunction( if (!result) { - processor->m_device->FailFrameBuffer(fbRes->GetFrameIndex()); + processor->m_transport->FailFrameBuffer(fbRes->GetFrameIndex()); processor->SetFullDamage(); - processor->m_device->ForceFrame(); + processor->m_transport->ForceFrame(); processor->ReleaseCandidate(candidateIndex); return; } @@ -374,7 +376,7 @@ void CHardwareFrameProcessor::CompletionFunction( if (processor->m_dx12->IsIndirectCopy()) { const uint64_t indirectCopyStart = CFrameScheduler::Nanotime(); - processor->m_device->WriteFrameBuffer( + processor->m_transport->WriteFrameBuffer( fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); indirectCopyTime = CFrameScheduler::Nanotime() - indirectCopyStart; @@ -401,7 +403,7 @@ void CHardwareFrameProcessor::CompletionFunction( const uint64_t copyTime = prepareCopyTime + publishCopyTime; - processor->m_device->FinalizeFrameBuffer(fbRes->GetFrameIndex()); + processor->m_transport->FinalizeFrameBuffer(fbRes->GetFrameIndex()); const uint64_t publishedAt = CFrameScheduler::Nanotime(); const uint64_t prepareElapsed = prepareReady >= postProcessStart ? prepareReady - postProcessStart : 0; @@ -416,10 +418,10 @@ void CHardwareFrameProcessor::CompletionFunction( const uint64_t holdTime = publishStart >= prepareReady ? publishStart - prepareReady : 0; - processor->m_device->SetFrameTiming(fbRes->GetFrameIndex(), + processor->m_transport->SetFrameTiming(fbRes->GetFrameIndex(), fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, holdTime, fbRes->GetSchedule(), publishedAt); - processor->m_device->TryRecordFrameTiming(publishedAt - publishStart); + processor->m_transport->TryRecordFrameTiming(publishedAt - publishStart); const uint64_t timingToken = fbRes->GetTimingToken(); if (timingToken && timingStart && prepareReady >= timingStart && @@ -432,7 +434,7 @@ void CHardwareFrameProcessor::CompletionFunction( fbRes->IsFullCopy(), totalTime); } - processor->m_device->CompleteFrameBuffer(fbRes->GetFrameIndex(), true); + processor->m_transport->CompleteFrameBuffer(fbRes->GetFrameIndex(), true); processor->ReleaseCandidate(candidateIndex); } @@ -442,7 +444,7 @@ bool CHardwareFrameProcessor::Publish( { CPublishPending publishPending( &m_copySubmitLock, &m_publishPending, m_copySubmitEvent.Get()); - CFrameProcessorSharedLock pipelineLock(m_pipelineLock); + CSRWSharedLock pipelineLock(m_pipelineLock); int selectedCandidate = -1; uint64_t newestSequence = 0; @@ -491,7 +493,7 @@ bool CHardwareFrameProcessor::Publish( CPostProcessor& postProcessor = m_postProcessors[candidateIndex]; const uint64_t candidateSequence = candidate.sequence; - auto buffer = m_device->PrepareFrameBuffer( + auto buffer = m_transport->PrepareFrameBuffer( candidate.pitch, candidate.srcFormat, candidate.dstFormat, candidate.dirtyRects, candidate.nbDirtyRects, schedule); if (!buffer.mem) @@ -504,7 +506,7 @@ bool CHardwareFrameProcessor::Publish( m_frameBuffers.Get(buffer, candidate.frameSize); if (!fbRes) { - m_device->AbortFrameBuffer(buffer.frameIndex); + m_transport->AbortFrameBuffer(buffer.frameIndex); restoreCandidate(); DEBUG_ERROR("Failed to get a CFrameBufferResource from the pool"); SetFullDamage(); @@ -514,7 +516,7 @@ bool CHardwareFrameProcessor::Publish( CD3D12CommandSlot * copySlot = m_dx12->GetCopySlot(candidateIndex); if (!copySlot) { - m_device->AbortFrameBuffer(buffer.frameIndex); + m_transport->AbortFrameBuffer(buffer.frameIndex); restoreCandidate(); DEBUG_ERROR("Failed to get a copy CommandSlot for publication"); SetFullDamage(); @@ -548,17 +550,17 @@ bool CHardwareFrameProcessor::Publish( copySlot->EndTiming(); bool deliveredToOwner; - if (!m_device->PublishFrameBuffer( + if (!m_transport->PublishFrameBuffer( buffer.frameIndex, schedule, deliveredToOwner)) { copySlot->Cancel(); - m_device->AbortFrameBuffer(buffer.frameIndex); + m_transport->AbortFrameBuffer(buffer.frameIndex); restoreCandidate(); return false; } CFrameScheduler::Schedule frameSchedule = schedule; if (!deliveredToOwner || - !m_device->TryFrameSubmitted(buffer.frameIndex, schedule)) + !m_transport->TryFrameSubmitted(buffer.frameIndex, schedule)) frameSchedule.phaseEligible = false; fbRes->SetSchedule(frameSchedule); @@ -591,15 +593,15 @@ bool CHardwareFrameProcessor::Publish( ReleaseSRWLockShared(&m_candidateLock); if (callbackPending && !copySlot->HasSubmittedWork()) { - m_device->FailFrameBuffer(buffer.frameIndex); + m_transport->FailFrameBuffer(buffer.frameIndex); ReleaseCandidate(candidateIndex); } - m_device->ForceFrame(); + m_transport->ForceFrame(); SignalCandidateState(); return false; } - m_device->CommitFrameBuffer( + m_transport->CommitFrameBuffer( buffer.frameIndex, schedule, periodic, deliveredToOwner); unsigned superseded = 0; @@ -613,7 +615,7 @@ bool CHardwareFrameProcessor::Publish( } ReleaseSRWLockExclusive(&m_candidateLock); for (unsigned i = 0; i < superseded; ++i) - m_device->FrameSuperseded(); + m_transport->FrameSuperseded(); SignalCandidateState(); return true; } @@ -645,14 +647,14 @@ bool CHardwareFrameProcessor::Submit(const FrameSubmission& submission) } if (selectedCandidate < 0) { - m_device->FrameSuperseded(); + m_transport->FrameSuperseded(); return true; } const unsigned candidateIndex = static_cast(selectedCandidate); FrameCandidate& candidate = m_candidates[candidateIndex]; - CFrameProcessorSharedLock pipelineLock(m_pipelineLock); + CSRWSharedLock pipelineLock(m_pipelineLock); CPostProcessor& postProcessor = m_postProcessors[candidateIndex]; const D12FrameFormat& dstFormat = postProcessor.GetOutputFormat(); @@ -813,7 +815,7 @@ bool CHardwareFrameProcessor::Submit(const FrameSubmission& submission) ReleaseCandidate(candidateIndex); } SetFullDamage(); - m_device->ForceFrame(); + m_transport->ForceFrame(); return false; } diff --git a/idd/LGIdd/CHardwareFrameProcessor.h b/idd/LGIdd/capture/CHardwareFrameProcessor.h similarity index 97% rename from idd/LGIdd/CHardwareFrameProcessor.h rename to idd/LGIdd/capture/CHardwareFrameProcessor.h index d240880a..b25116b3 100644 --- a/idd/LGIdd/CHardwareFrameProcessor.h +++ b/idd/LGIdd/capture/CHardwareFrameProcessor.h @@ -20,7 +20,7 @@ #pragma once -#include "CFrameProcessor.h" +#include "capture/CFrameProcessor.h" class CHardwareFrameProcessor final : public CFrameProcessor { @@ -89,7 +89,7 @@ private: void SetFullDamageLocked() override; public: - CHardwareFrameProcessor(CIndirectDeviceContext * device, + CHardwareFrameProcessor(CFrameTransport * transport, std::shared_ptr dx12, CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], SRWLOCK * pipelineLock, HANDLE terminateEvent); diff --git a/idd/LGIdd/CSoftwareFrameProcessor.cpp b/idd/LGIdd/capture/CSoftwareFrameProcessor.cpp similarity index 85% rename from idd/LGIdd/CSoftwareFrameProcessor.cpp rename to idd/LGIdd/capture/CSoftwareFrameProcessor.cpp index f34ef95b..c2806792 100644 --- a/idd/LGIdd/CSoftwareFrameProcessor.cpp +++ b/idd/LGIdd/capture/CSoftwareFrameProcessor.cpp @@ -18,17 +18,20 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CSoftwareFrameProcessor.h" -#include "CFrameProcessorUtil.h" +#include "capture/CSoftwareFrameProcessor.h" +#include "capture/CFrameProcessorUtil.h" +#include "capture/FrameBufferTypes.h" +#include "transport/CFrameTransport.h" +#include "util/CSRWLock.h" #include "CDebug.h" #include CSoftwareFrameProcessor::CSoftwareFrameProcessor( - CIndirectDeviceContext * device, std::shared_ptr dx12, + CFrameTransport * transport, std::shared_ptr dx12, CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], SRWLOCK * pipelineLock, HANDLE terminateEvent) : - CFrameProcessor(device, std::move(dx12), postProcessors, + CFrameProcessor(transport, std::move(dx12), postProcessors, pipelineLock, terminateEvent), m_directTexture( !m_dx12->IsIndirectCopy() && m_dx12->CanUseIVSHMEMTexture()) @@ -44,9 +47,9 @@ void CSoftwareFrameProcessor::CompletionFunction( if (!result) { - processor->m_device->FailFrameBuffer(fbRes->GetFrameIndex()); + processor->m_transport->FailFrameBuffer(fbRes->GetFrameIndex()); processor->SetFullDamage(); - processor->m_device->ForceFrame(); + processor->m_transport->ForceFrame(); return; } @@ -55,7 +58,7 @@ void CSoftwareFrameProcessor::CompletionFunction( { const uint64_t indirectCopyStart = CFrameScheduler::Nanotime(); if (fbRes->IsFullCopy()) - processor->m_device->WriteFrameBuffer(fbRes->GetFrameIndex(), + processor->m_transport->WriteFrameBuffer(fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false); else { @@ -70,7 +73,7 @@ void CSoftwareFrameProcessor::CompletionFunction( (size_t)rect->left * bytesPerPixel; const size_t rowBytes = (size_t)(rect->right - rect->left) * bytesPerPixel; - processor->m_device->WriteFrameBufferRows(fbRes->GetFrameIndex(), + processor->m_transport->WriteFrameBufferRows(fbRes->GetFrameIndex(), fbRes->GetMap(), rowOffset, rowBytes, pitch, (unsigned)(rect->bottom - rect->top)); } @@ -83,7 +86,7 @@ void CSoftwareFrameProcessor::CompletionFunction( const uint64_t copyReady = CFrameScheduler::Nanotime(); const bool gpuTimingValid = slot->GetGPUTimes(gpuStart, gpuEnd); - processor->m_device->FinalizeFrameBuffer(fbRes->GetFrameIndex()); + processor->m_transport->FinalizeFrameBuffer(fbRes->GetFrameIndex()); const uint64_t publishedAt = CFrameScheduler::Nanotime(); const uint64_t postProcessStart = fbRes->GetPostProcessStart(); const uint64_t copyStart = fbRes->GetCopyStart(); @@ -103,15 +106,15 @@ void CSoftwareFrameProcessor::CompletionFunction( const uint64_t measured = postProcessTime + copyTime; const uint64_t readyTime = elapsed > measured ? elapsed - measured : 0; - processor->m_device->SetFrameTiming(fbRes->GetFrameIndex(), + processor->m_transport->SetFrameTiming(fbRes->GetFrameIndex(), fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, 0, fbRes->GetSchedule(), publishedAt); - processor->m_device->CompleteFrameBuffer(fbRes->GetFrameIndex(), true); + processor->m_transport->CompleteFrameBuffer(fbRes->GetFrameIndex(), true); } bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) { - CFrameProcessorSharedLock pipelineLock(m_pipelineLock); + CSRWSharedLock pipelineLock(m_pipelineLock); CPostProcessor& postProcessor = m_postProcessors[0]; const D12FrameFormat& dstFormat = postProcessor.GetOutputFormat(); @@ -140,7 +143,7 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) &textureDesc, 0, 1, 0, &layout, nullptr, nullptr, nullptr); const unsigned texturePitch = layout.Footprint.RowPitch; if (texturePitch && textureDesc.Height <= - m_device->GetMaxFrameSize() / texturePitch) + m_transport->GetMaxFrameSize() / texturePitch) { pitch = texturePitch; frameSize = (size_t)pitch * textureDesc.Height; @@ -158,7 +161,7 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) DEBUG_WARN("Post-processor output cannot use an IVSHMEM texture"); } - if (!pitch || !frameSize || frameSize > m_device->GetMaxFrameSize()) + if (!pitch || !frameSize || frameSize > m_transport->GetMaxFrameSize()) { DEBUG_ERROR("Software frame does not fit in shared memory"); SetFullDamage(); @@ -170,10 +173,10 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) for (;;) { - CFrameScheduler::Schedule commitSchedule = {}; - CFrameScheduler::Schedule deliverySchedule = {}; - CIndirectDeviceContext::PreparedFrameBuffer buffer = {}; - CD3D12CommandSlot * copySlot = nullptr; + CFrameScheduler::Schedule commitSchedule = {}; + CFrameScheduler::Schedule deliverySchedule = {}; + PreparedFrameBuffer buffer = {}; + CD3D12CommandSlot * copySlot = nullptr; RECT currentDirtyRects[LG_MAX_DIRTY_RECTS] = {}; unsigned nbDirtyRects = 0; bool hasDamage = false; @@ -181,19 +184,19 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) uint64_t ignoredTarget = 0; bool ignoredPeriodic = false; bool ignoredRepublish = false; - m_device->GetPublishTarget(CFrameScheduler::Nanotime(), + m_transport->GetPublishTarget(CFrameScheduler::Nanotime(), ignoredTarget, commitSchedule, ignoredPeriodic, ignoredRepublish); deliverySchedule = commitSchedule; deliverySchedule.deliveryDeadlineSerial = 0; deliverySchedule.phaseEligible = false; - m_device->ProcessFrameQueue(); - if (!m_device->FrameBufferAvailable( + m_transport->ProcessFrameQueue(); + if (!m_transport->FrameBufferAvailable( deliverySchedule, submission.noImageUpdate)) { if (!submission.noImageUpdate) { - m_device->FrameSuperseded(); + m_transport->FrameSuperseded(); return true; } @@ -207,7 +210,7 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) { if (!submission.noImageUpdate) { - m_device->FrameSuperseded(); + m_transport->FrameSuperseded(); return true; } @@ -220,7 +223,7 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) CFrameProcessorUtil::ClipDirtyRects( currentDirtyRects, &nbDirtyRects, dstFormat.width, dstFormat.height); - buffer = m_device->PrepareFrameBuffer( + buffer = m_transport->PrepareFrameBuffer( pitch, submission.sourceFormat, dstFormat, currentDirtyRects, nbDirtyRects, deliverySchedule, submission.noImageUpdate); @@ -231,7 +234,7 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) currentDirtyRects, nbDirtyRects, hasDamage); if (!submission.noImageUpdate) { - m_device->FrameSuperseded(); + m_transport->FrameSuperseded(); return true; } @@ -251,7 +254,7 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) if (FAILED(deviceStatus)) { copySlot->Cancel(); - m_device->AbortFrameBuffer(buffer.frameIndex); + m_transport->AbortFrameBuffer(buffer.frameIndex); RestorePendingDamage( currentDirtyRects, nbDirtyRects, hasDamage); DEBUG_ERROR_HR(deviceStatus, @@ -272,7 +275,7 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) if (!fbRes) { copySlot->Cancel(); - m_device->AbortFrameBuffer(buffer.frameIndex); + m_transport->AbortFrameBuffer(buffer.frameIndex); RestorePendingDamage( currentDirtyRects, nbDirtyRects, hasDamage); DEBUG_ERROR("Failed to get a framebuffer for software capture"); @@ -284,7 +287,7 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) !submission.source->Sync(*copySlot)) { copySlot->Cancel(); - m_device->AbortFrameBuffer(buffer.frameIndex); + m_transport->AbortFrameBuffer(buffer.frameIndex); RestorePendingDamage( currentDirtyRects, nbDirtyRects, hasDamage); SetFullDamage(); @@ -322,16 +325,16 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) copySlot->EndTiming(); bool deliveredToOwner; - if (!m_device->PublishFrameBuffer( + if (!m_transport->PublishFrameBuffer( buffer.frameIndex, deliverySchedule, deliveredToOwner)) { copySlot->Cancel(); - m_device->AbortFrameBuffer(buffer.frameIndex); + m_transport->AbortFrameBuffer(buffer.frameIndex); RestorePendingDamage( currentDirtyRects, nbDirtyRects, hasDamage); if (!submission.noImageUpdate) { - m_device->FrameSuperseded(); + m_transport->FrameSuperseded(); return true; } @@ -346,18 +349,18 @@ bool CSoftwareFrameProcessor::Submit(const FrameSubmission& submission) const bool submittedWork = copySlot->HasSubmittedWork(); const bool completionHandled = fbRes->CompletionHandled(); if (!submittedWork && !completionHandled) - m_device->FailFrameBuffer(buffer.frameIndex); + m_transport->FailFrameBuffer(buffer.frameIndex); RestorePendingDamage( currentDirtyRects, nbDirtyRects, hasDamage); if (!submittedWork && !completionHandled) { SetFullDamage(); - m_device->ForceFrame(); + m_transport->ForceFrame(); } return false; } - m_device->CommitFrameBuffer( + m_transport->CommitFrameBuffer( buffer.frameIndex, commitSchedule, false, deliveredToOwner); return true; } diff --git a/idd/LGIdd/CSoftwareFrameProcessor.h b/idd/LGIdd/capture/CSoftwareFrameProcessor.h similarity index 94% rename from idd/LGIdd/CSoftwareFrameProcessor.h rename to idd/LGIdd/capture/CSoftwareFrameProcessor.h index 21a9082d..b0dea032 100644 --- a/idd/LGIdd/CSoftwareFrameProcessor.h +++ b/idd/LGIdd/capture/CSoftwareFrameProcessor.h @@ -20,7 +20,7 @@ #pragma once -#include "CFrameProcessor.h" +#include "capture/CFrameProcessor.h" class CSoftwareFrameProcessor final : public CFrameProcessor { @@ -31,7 +31,7 @@ private: CD3D12CommandSlot * slot, bool result, void * param1, void * param2); public: - CSoftwareFrameProcessor(CIndirectDeviceContext * device, + CSoftwareFrameProcessor(CFrameTransport * transport, std::shared_ptr dx12, CPostProcessor postProcessors[LGMP_Q_FRAME_LEN], SRWLOCK * pipelineLock, HANDLE terminateEvent); diff --git a/idd/LGIdd/capture/CSwapChainCursor.cpp b/idd/LGIdd/capture/CSwapChainCursor.cpp new file mode 100644 index 00000000..a0872df9 --- /dev/null +++ b/idd/LGIdd/capture/CSwapChainCursor.cpp @@ -0,0 +1,122 @@ +/** + * 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 "capture/CSwapChainProcessor.h" + +#include "display/IddCxCompat.h" +#include "display/device/CDeviceContext.h" +#include "transport/CLGMPControl.h" + +#include "CDebug.h" + +DWORD CALLBACK CSwapChainProcessor::_CursorThread(LPVOID arg) +{ + reinterpret_cast(arg)->CursorThread(); + return 0; +} + +bool CSwapChainProcessor::QueryHWCursor() +{ + IDARG_IN_QUERY_HWCURSOR in = {}; + in.LastShapeId = m_lastShapeId; + in.pShapeBuffer = m_shapeBuffer; + in.ShapeBufferSizeInBytes = 512 * 512 * 4; + + IDARG_OUT_QUERY_HWCURSOR out = {}; + UINT cursorWhiteLevel = m_sdrWhiteLevel.load(std::memory_order_relaxed); + NTSTATUS status; +#ifdef HAS_IDDCX_110 + if (m_devContext->HasIddCx110DDIs()) + { + IDARG_OUT_QUERY_HWCURSOR3 out3 = {}; + status = IddCxMonitorQueryHardwareCursor3(m_monitor, &in, &out3); + out.IsCursorVisible = out3.IsCursorVisible; + out.X = out3.X; + out.Y = out3.Y; + out.IsCursorShapeUpdated = out3.IsCursorShapeUpdated; + out.CursorShapeInfo = out3.CursorShapeInfo; + if (out3.SdrWhiteLevel) + cursorWhiteLevel = out3.SdrWhiteLevel; + } + else +#endif + { + status = IddCxMonitorQueryHardwareCursor(m_monitor, &in, &out); + } + + if (FAILED(status)) + { + // this occurs if the display went away (ie, screen blanking or disabled) + if (status == STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY) + { + SetEvent(m_terminateEvent.Get()); + return false; + } + + DEBUG_ERROR("IddCxMonitorQueryHardwareCursor failed (0x%08x)", status); + return false; + } + + if (out.IsCursorShapeUpdated) + m_lastShapeId = out.CursorShapeInfo.ShapeId; + + m_control.SendCursor(out, m_shapeBuffer, cursorWhiteLevel); + return true; +} + +void CSwapChainProcessor::CursorThread() +{ + HRESULT hr = 0; + bool running = true; + + while (running) + { + HANDLE waitHandles[] = + { + m_cursorDataEvent.Get(), + m_terminateEvent.Get() + }; + + DWORD waitResult = WaitForMultipleObjects( + ARRAYSIZE(waitHandles), waitHandles, FALSE, 100); + + switch (waitResult) + { + case WAIT_TIMEOUT: + continue; + + // cursorDataEvent + case WAIT_OBJECT_0: + if (!QueryHWCursor()) + return; + continue; + + // terminateEvent + case WAIT_OBJECT_0 + 1: + running = false; + continue; + + default: + hr = HRESULT_FROM_WIN32(waitResult); + DEBUG_ERROR_HR(hr, "WaitForMultipleObjects"); + return; + } + } +} diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/capture/CSwapChainProcessor.cpp similarity index 70% rename from idd/LGIdd/CSwapChainProcessor.cpp rename to idd/LGIdd/capture/CSwapChainProcessor.cpp index caf606a5..191c88d4 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/capture/CSwapChainProcessor.cpp @@ -18,15 +18,20 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CSwapChainProcessor.h" -#include "CFrameProcessorUtil.h" -#include "CIndirectMonitorContext.h" -#include "CPlatformInfo.h" +#include "capture/CSwapChainProcessor.h" +#include "capture/CFrameProcessorUtil.h" +#include "display/IddCxCompat.h" +#include "display/device/CDeviceContext.h" +#include "display/monitor/Context.h" +#include "platform/CPlatformInfo.h" +#include "transport/CFrameTransport.h" +#include "transport/CLGMPControl.h" +#include "util/CSRWLock.h" #include #include #include "CDebug.h" -#include "CPipeServer.h" +#include "transport/CPipeServer.h" #ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION #define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002 @@ -34,34 +39,18 @@ static const uint32_t HDR_PQ_MIN_LUMINANCE = 50; static const uint32_t HDR_PQ_MAX_LUMINANCE = 10000; -static const uint64_t PUBLISH_RETRY_NS = 1000000ULL; -class CSRWExclusiveLock -{ -private: - SRWLOCK * m_lock; - -public: - explicit CSRWExclusiveLock(SRWLOCK * lock) : m_lock(lock) - { - AcquireSRWLockExclusive(m_lock); - } - - ~CSRWExclusiveLock() - { - ReleaseSRWLockExclusive(m_lock); - } -}; - -CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContext, +CSwapChainProcessor::CSwapChainProcessor(CMonitorContext * monitorContext, UINT64 assignmentGeneration, IDDCX_MONITOR monitor, - CIndirectDeviceContext * devContext, IDDCX_SWAPCHAIN hSwapChain, + CDeviceContext * devContext, IDDCX_SWAPCHAIN hSwapChain, LUID renderAdapter, std::shared_ptr dx11Device, HANDLE newFrameEvent) : m_monitorContext(monitorContext), m_assignmentGeneration(assignmentGeneration), m_monitor(monitor), m_devContext(devContext), + m_transport(devContext->GetFrameTransport()), + m_control(devContext->GetLGMPControl()), m_hSwapChain(hSwapChain), m_renderAdapter(renderAdapter), m_dx11Device(dx11Device), @@ -110,7 +99,7 @@ bool CSwapChainProcessor::InitializePipeline() UINT64 alignSize = CPlatformInfo::GetPageSize(); auto dx12Device = std::make_shared(m_renderAdapter); const CD3D12Device::InitResult result = dx12Device->Init( - m_devContext->GetIVSHMEM(), alignSize, !m_dx11Device->IsSoftware()); + m_transport.GetIVSHMEM(), alignSize, !m_dx11Device->IsSoftware()); if (result == CD3D12Device::RETRY) { const HRESULT deviceStatus = @@ -175,7 +164,7 @@ bool CSwapChainProcessor::InitializePipeline() } m_frameProcessor = CreateFrameProcessor(m_dx11Device->IsSoftware(), - m_devContext, m_dx12Device, m_postProcessors, + &m_transport, m_dx12Device, m_postProcessors, &m_pipelineLock, m_terminateEvent.Get()); if (!m_frameProcessor) { @@ -230,246 +219,6 @@ DWORD CALLBACK CSwapChainProcessor::_SwapChainThread(LPVOID arg) return 0; } -static bool ArmPublishTimer(HANDLE timer, uint64_t delay) -{ - if (!timer) - return false; - - LARGE_INTEGER due = {}; - due.QuadPart = -static_cast((delay + 99) / 100); - if (!due.QuadPart) - due.QuadPart = -1; - return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE; -} - -DWORD CALLBACK CSwapChainProcessor::_PublisherThread(LPVOID arg) -{ - reinterpret_cast(arg)->PublisherThread(); - return 0; -} - -void CSwapChainProcessor::PublisherThread() -{ - DWORD avTask = 0; - HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask); - if (avTaskHandle && - !AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH)) - DEBUG_WARN("Failed to raise publisher MMCSS priority: %lu", - GetLastError()); - - const HANDLE scheduleEvent = m_devContext->GetFrameScheduleEvent(); - HANDLE idleHandles[] = - { - m_terminateEvent.Get(), - m_frameProcessor->GetReadyEvent(), - scheduleEvent, - }; - HANDLE timerHandles[] = - { - m_terminateEvent.Get(), - m_frameProcessor->GetReadyEvent(), - scheduleEvent, - m_publishTimer.Get(), - }; - const bool cadenceEnabled = m_frameProcessor->UsesCadence(); - - for (;;) - { - const uint64_t now = CFrameScheduler::Nanotime(); - uint64_t target; - CFrameScheduler::Schedule schedule; - bool periodic; - bool republish; - m_devContext->GetPublishTarget( - now, target, schedule, periodic, republish); - - const bool ready = m_frameProcessor->HasReadyFrame(); - if (!ready) - { - m_devContext->ProcessFrameQueue(); - if (m_frameProcessor->HasReadyFrame()) - continue; - - uint64_t current = CFrameScheduler::Nanotime(); - uint64_t cadenceTarget = 0; - if (cadenceEnabled && schedule.deliveryDeadlineSerial && periodic) - { - if (schedule.deadline <= current) - { - m_devContext->FrameMissed(schedule, current, periodic); - continue; - } - cadenceTarget = schedule.deadline; - } - - if (republish && m_devContext->HasPublishedFrame()) - { - if (m_devContext->RepublishFrameBuffer(schedule)) - continue; - - current = CFrameScheduler::Nanotime(); - if (cadenceTarget && cadenceTarget <= current) - { - m_devContext->FrameMissed(schedule, current, periodic); - continue; - } - - uint64_t retryTarget = current + PUBLISH_RETRY_NS; - if (cadenceTarget) - retryTarget = min(retryTarget, cadenceTarget); - ArmPublishTimer(m_publishTimer.Get(), retryTarget - current); - if (WaitForMultipleObjects( - ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == - WAIT_OBJECT_0) - break; - continue; - } - - uint64_t replayTarget; - if (m_devContext->GetSharedFrameTarget(current, replayTarget)) - { - bool retry = false; - if (replayTarget <= current) - { - if (m_devContext->ReplaySharedFrame(current, retry)) - continue; - - current = CFrameScheduler::Nanotime(); - if (cadenceTarget && cadenceTarget <= current) - { - m_devContext->FrameMissed(schedule, current, periodic); - continue; - } - - if (retry) - replayTarget = current + PUBLISH_RETRY_NS; - else - { - if (cadenceTarget) - replayTarget = cadenceTarget; - else - { - if (m_publishTimer.Get()) - CancelWaitableTimer(m_publishTimer.Get()); - if (WaitForMultipleObjects( - ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) == - WAIT_OBJECT_0) - break; - continue; - } - } - } - - if (cadenceTarget) - replayTarget = min(replayTarget, cadenceTarget); - - current = CFrameScheduler::Nanotime(); - if (cadenceTarget && cadenceTarget <= current) - { - m_devContext->FrameMissed(schedule, current, periodic); - continue; - } - if (replayTarget <= current) - continue; - - ArmPublishTimer(m_publishTimer.Get(), replayTarget - current); - if (WaitForMultipleObjects( - ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == - WAIT_OBJECT_0) - break; - continue; - } - - if (cadenceTarget) - { - current = CFrameScheduler::Nanotime(); - if (cadenceTarget <= current) - { - m_devContext->FrameMissed(schedule, current, periodic); - continue; - } - - ArmPublishTimer(m_publishTimer.Get(), cadenceTarget - current); - if (WaitForMultipleObjects( - ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == - WAIT_OBJECT_0) - break; - continue; - } - - if (m_publishTimer.Get()) - CancelWaitableTimer(m_publishTimer.Get()); - if (WaitForMultipleObjects( - ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) == - WAIT_OBJECT_0) - break; - continue; - } - - uint64_t current = CFrameScheduler::Nanotime(); - uint64_t replayTarget; - if (m_devContext->GetSharedFrameTarget(current, replayTarget) && - replayTarget < target) - { - if (replayTarget <= current) - { - m_devContext->ProcessFrameQueue(); - current = CFrameScheduler::Nanotime(); - bool retry = false; - if (m_devContext->ReplaySharedFrame(current, retry)) - continue; - - current = CFrameScheduler::Nanotime(); - if (retry) - replayTarget = current + PUBLISH_RETRY_NS; - else - replayTarget = target; - } - - replayTarget = min(replayTarget, target); - current = CFrameScheduler::Nanotime(); - if (target > current) - { - if (replayTarget <= current) - continue; - - ArmPublishTimer(m_publishTimer.Get(), replayTarget - current); - if (WaitForMultipleObjects( - ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == - WAIT_OBJECT_0) - break; - continue; - } - } - - current = CFrameScheduler::Nanotime(); - if (target > current) - { - ArmPublishTimer(m_publishTimer.Get(), target - current); - if (WaitForMultipleObjects( - ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == - WAIT_OBJECT_0) - break; - continue; - } - - const uint64_t publishStart = CFrameScheduler::Nanotime(); - m_devContext->ProcessFrameQueue(); - if (!m_devContext->FrameBufferAvailable(schedule) || - !m_frameProcessor->Publish(schedule, periodic, publishStart)) - { - ArmPublishTimer(m_publishTimer.Get(), PUBLISH_RETRY_NS); - if (WaitForMultipleObjects( - ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == - WAIT_OBJECT_0) - break; - } - } - - if (avTaskHandle) - AvRevertMmThreadCharacteristics(avTaskHandle); -} - void CSwapChainProcessor::SwapChainThread() { DWORD avTask = 0; @@ -813,7 +562,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer D3D12_RESOURCE_DESC srcDesc = srcRes->GetRes()->GetDesc(); if (!noImageUpdate) { - m_devContext->ObserveFrame(postProcessStart); + m_transport.ObserveFrame(postProcessStart); m_frameProcessor->AccumulateDamage( srcRes->GetDirtyRects(), srcRes->GetDirtyRectCount()); } @@ -824,7 +573,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer srcFormat.height = srcDesc.Height; srcFormat.format = CFrameProcessorUtil::GetFrameType(srcDesc.Format); srcFormat.sdrWhiteLevel = sdrWhiteLevel; - srcFormat.colorTransform = m_devContext->GetColorTransform(); + srcFormat.colorTransform = m_control.GetColorTransform(); switch (colorSpace) { @@ -973,7 +722,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer } if (needsReconfigure || postProcessFormatChanged || frameMetadataChanged) - m_devContext->ForceFrame(); + m_transport.ForceFrame(); const FrameSubmission submission = { @@ -987,98 +736,3 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer }; return m_frameProcessor->Submit(submission); } - -DWORD CALLBACK CSwapChainProcessor::_CursorThread(LPVOID arg) -{ - reinterpret_cast(arg)->CursorThread(); - return 0; -} - -bool CSwapChainProcessor::QueryHWCursor() -{ - IDARG_IN_QUERY_HWCURSOR in = {}; - in.LastShapeId = m_lastShapeId; - in.pShapeBuffer = m_shapeBuffer; - in.ShapeBufferSizeInBytes = 512 * 512 * 4; - - IDARG_OUT_QUERY_HWCURSOR out = {}; - UINT cursorWhiteLevel = m_sdrWhiteLevel.load(std::memory_order_relaxed); - NTSTATUS status; -#ifdef HAS_IDDCX_110 - if (m_devContext->HasIddCx110DDIs()) - { - IDARG_OUT_QUERY_HWCURSOR3 out3 = {}; - status = IddCxMonitorQueryHardwareCursor3(m_monitor, &in, &out3); - out.IsCursorVisible = out3.IsCursorVisible; - out.X = out3.X; - out.Y = out3.Y; - out.IsCursorShapeUpdated = out3.IsCursorShapeUpdated; - out.CursorShapeInfo = out3.CursorShapeInfo; - if (out3.SdrWhiteLevel) - cursorWhiteLevel = out3.SdrWhiteLevel; - } - else -#endif - { - status = IddCxMonitorQueryHardwareCursor(m_monitor, &in, &out); - } - - if (FAILED(status)) - { - // this occurs if the display went away (ie, screen blanking or disabled) - if (status == STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY) - { - SetEvent(m_terminateEvent.Get()); - return false; - } - - DEBUG_ERROR("IddCxMonitorQueryHardwareCursor failed (0x%08x)", status); - return false; - } - - if (out.IsCursorShapeUpdated) - m_lastShapeId = out.CursorShapeInfo.ShapeId; - - m_devContext->SendCursor(out, m_shapeBuffer, cursorWhiteLevel); - return true; -} - -void CSwapChainProcessor::CursorThread() -{ - HRESULT hr = 0; - bool running = true; - - while (running) - { - HANDLE waitHandles[] = - { - m_cursorDataEvent.Get(), - m_terminateEvent.Get() - }; - - DWORD waitResult = WaitForMultipleObjects( - ARRAYSIZE(waitHandles), waitHandles, FALSE, 100); - - switch (waitResult) - { - case WAIT_TIMEOUT: - continue; - - // cursorDataEvent - case WAIT_OBJECT_0: - if (!QueryHWCursor()) - return; - continue; - - // terminateEvent - case WAIT_OBJECT_0 + 1: - running = false; - continue; - - default: - hr = HRESULT_FROM_WIN32(waitResult); - DEBUG_ERROR_HR(hr, "WaitForMultipleObjects"); - return; - } - } -} diff --git a/idd/LGIdd/CSwapChainProcessor.h b/idd/LGIdd/capture/CSwapChainProcessor.h similarity index 68% rename from idd/LGIdd/CSwapChainProcessor.h rename to idd/LGIdd/capture/CSwapChainProcessor.h index c6524b40..450927de 100644 --- a/idd/LGIdd/CSwapChainProcessor.h +++ b/idd/LGIdd/capture/CSwapChainProcessor.h @@ -20,41 +20,46 @@ #pragma once -#include "CD3D11Device.h" -#include "CD3D12Device.h" -#include "CIndirectDeviceContext.h" -#include "CInteropResourcePool.h" -#include "CFrameProcessor.h" -#include "CPostProcessor.h" +#include "d3d/CD3D11Device.h" +#include "d3d/CD3D12Device.h" +#include "display/IddCxCompat.h" +#include "d3d/CInteropResourcePool.h" +#include "capture/CFrameProcessor.h" +#include "common/KVMFR.h" +#include "postprocess/CPostProcessor.h" #include #include -#include #include #include using namespace Microsoft::WRL; -class CIndirectMonitorContext; +class CMonitorContext; +class CDeviceContext; +class CFrameTransport; +class CLGMPControl; class CSwapChainProcessor { private: - CIndirectMonitorContext * m_monitorContext; - UINT64 m_assignmentGeneration; - IDDCX_MONITOR m_monitor; - CIndirectDeviceContext * m_devContext; - IDDCX_SWAPCHAIN m_hSwapChain; - LUID m_renderAdapter; - std::shared_ptr m_dx11Device; - std::shared_ptr m_dx12Device; - HANDLE m_newFrameEvent; + CMonitorContext * m_monitorContext; + UINT64 m_assignmentGeneration; + IDDCX_MONITOR m_monitor; + CDeviceContext * m_devContext; + CFrameTransport & m_transport; + CLGMPControl & m_control; + IDDCX_SWAPCHAIN m_hSwapChain; + LUID m_renderAdapter; + std::shared_ptr m_dx11Device; + std::shared_ptr m_dx12Device; + HANDLE m_newFrameEvent; - CInteropResourcePool m_resPool; - CPostProcessor m_postProcessors[LGMP_Q_FRAME_LEN]; + CInteropResourcePool m_resPool; + CPostProcessor m_postProcessors[LGMP_Q_FRAME_LEN]; std::unique_ptr m_frameProcessor; // Reconfiguration is exclusive while per-candidate recording is shared. - SRWLOCK m_pipelineLock = SRWLOCK_INIT; + SRWLOCK m_pipelineLock = SRWLOCK_INIT; Wrappers::HandleT m_thread[3]; Wrappers::Event m_terminateEvent; @@ -94,8 +99,9 @@ private: uint64_t captureStart, bool duplicateFrame); public: - CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration, - IDDCX_MONITOR monitor, CIndirectDeviceContext * devContext, IDDCX_SWAPCHAIN hSwapChain, + CSwapChainProcessor(CMonitorContext * monitorContext, + UINT64 assignmentGeneration, IDDCX_MONITOR monitor, + CDeviceContext * devContext, IDDCX_SWAPCHAIN hSwapChain, LUID renderAdapter, std::shared_ptr dx11Device, HANDLE newFrameEvent); ~CSwapChainProcessor(); diff --git a/idd/LGIdd/capture/CSwapChainPublisher.cpp b/idd/LGIdd/capture/CSwapChainPublisher.cpp new file mode 100644 index 00000000..b972bf91 --- /dev/null +++ b/idd/LGIdd/capture/CSwapChainPublisher.cpp @@ -0,0 +1,268 @@ +/** + * 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 "capture/CSwapChainProcessor.h" + +#include "transport/CFrameTransport.h" + +#include +#include "CDebug.h" + +static const uint64_t PUBLISH_RETRY_NS = 1000000ULL; + +static bool ArmPublishTimer(HANDLE timer, uint64_t delay) +{ + if (!timer) + return false; + + LARGE_INTEGER due = {}; + due.QuadPart = -static_cast((delay + 99) / 100); + if (!due.QuadPart) + due.QuadPart = -1; + return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE; +} + +DWORD CALLBACK CSwapChainProcessor::_PublisherThread(LPVOID arg) +{ + reinterpret_cast(arg)->PublisherThread(); + return 0; +} + +void CSwapChainProcessor::PublisherThread() +{ + DWORD avTask = 0; + HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask); + if (avTaskHandle && + !AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH)) + DEBUG_WARN("Failed to raise publisher MMCSS priority: %lu", + GetLastError()); + + const HANDLE scheduleEvent = m_transport.GetFrameScheduleEvent(); + HANDLE idleHandles[] = + { + m_terminateEvent.Get(), + m_frameProcessor->GetReadyEvent(), + scheduleEvent, + }; + HANDLE timerHandles[] = + { + m_terminateEvent.Get(), + m_frameProcessor->GetReadyEvent(), + scheduleEvent, + m_publishTimer.Get(), + }; + const bool cadenceEnabled = m_frameProcessor->UsesCadence(); + + for (;;) + { + const uint64_t now = CFrameScheduler::Nanotime(); + uint64_t target; + CFrameScheduler::Schedule schedule; + bool periodic; + bool republish; + m_transport.GetPublishTarget( + now, target, schedule, periodic, republish); + + const bool ready = m_frameProcessor->HasReadyFrame(); + if (!ready) + { + m_transport.ProcessFrameQueue(); + if (m_frameProcessor->HasReadyFrame()) + continue; + + uint64_t current = CFrameScheduler::Nanotime(); + uint64_t cadenceTarget = 0; + if (cadenceEnabled && schedule.deliveryDeadlineSerial && periodic) + { + if (schedule.deadline <= current) + { + m_transport.FrameMissed(schedule, current, periodic); + continue; + } + cadenceTarget = schedule.deadline; + } + + if (republish && m_transport.HasPublishedFrame()) + { + if (m_transport.RepublishFrameBuffer(schedule)) + continue; + + current = CFrameScheduler::Nanotime(); + if (cadenceTarget && cadenceTarget <= current) + { + m_transport.FrameMissed(schedule, current, periodic); + continue; + } + + uint64_t retryTarget = current + PUBLISH_RETRY_NS; + if (cadenceTarget) + retryTarget = min(retryTarget, cadenceTarget); + ArmPublishTimer(m_publishTimer.Get(), retryTarget - current); + if (WaitForMultipleObjects( + ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == + WAIT_OBJECT_0) + break; + continue; + } + + uint64_t replayTarget; + if (m_transport.GetSharedFrameTarget(current, replayTarget)) + { + bool retry = false; + if (replayTarget <= current) + { + if (m_transport.ReplaySharedFrame(current, retry)) + continue; + + current = CFrameScheduler::Nanotime(); + if (cadenceTarget && cadenceTarget <= current) + { + m_transport.FrameMissed(schedule, current, periodic); + continue; + } + + if (retry) + replayTarget = current + PUBLISH_RETRY_NS; + else + { + if (cadenceTarget) + replayTarget = cadenceTarget; + else + { + if (m_publishTimer.Get()) + CancelWaitableTimer(m_publishTimer.Get()); + if (WaitForMultipleObjects( + ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) == + WAIT_OBJECT_0) + break; + continue; + } + } + } + + if (cadenceTarget) + replayTarget = min(replayTarget, cadenceTarget); + + current = CFrameScheduler::Nanotime(); + if (cadenceTarget && cadenceTarget <= current) + { + m_transport.FrameMissed(schedule, current, periodic); + continue; + } + if (replayTarget <= current) + continue; + + ArmPublishTimer(m_publishTimer.Get(), replayTarget - current); + if (WaitForMultipleObjects( + ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == + WAIT_OBJECT_0) + break; + continue; + } + + if (cadenceTarget) + { + current = CFrameScheduler::Nanotime(); + if (cadenceTarget <= current) + { + m_transport.FrameMissed(schedule, current, periodic); + continue; + } + + ArmPublishTimer(m_publishTimer.Get(), cadenceTarget - current); + if (WaitForMultipleObjects( + ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == + WAIT_OBJECT_0) + break; + continue; + } + + if (m_publishTimer.Get()) + CancelWaitableTimer(m_publishTimer.Get()); + if (WaitForMultipleObjects( + ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) == + WAIT_OBJECT_0) + break; + continue; + } + + uint64_t current = CFrameScheduler::Nanotime(); + uint64_t replayTarget; + if (m_transport.GetSharedFrameTarget(current, replayTarget) && + replayTarget < target) + { + if (replayTarget <= current) + { + m_transport.ProcessFrameQueue(); + current = CFrameScheduler::Nanotime(); + bool retry = false; + if (m_transport.ReplaySharedFrame(current, retry)) + continue; + + current = CFrameScheduler::Nanotime(); + if (retry) + replayTarget = current + PUBLISH_RETRY_NS; + else + replayTarget = target; + } + + replayTarget = min(replayTarget, target); + current = CFrameScheduler::Nanotime(); + if (target > current) + { + if (replayTarget <= current) + continue; + + ArmPublishTimer(m_publishTimer.Get(), replayTarget - current); + if (WaitForMultipleObjects( + ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == + WAIT_OBJECT_0) + break; + continue; + } + } + + current = CFrameScheduler::Nanotime(); + if (target > current) + { + ArmPublishTimer(m_publishTimer.Get(), target - current); + if (WaitForMultipleObjects( + ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == + WAIT_OBJECT_0) + break; + continue; + } + + const uint64_t publishStart = CFrameScheduler::Nanotime(); + m_transport.ProcessFrameQueue(); + if (!m_transport.FrameBufferAvailable(schedule) || + !m_frameProcessor->Publish(schedule, periodic, publishStart)) + { + ArmPublishTimer(m_publishTimer.Get(), PUBLISH_RETRY_NS); + if (WaitForMultipleObjects( + ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) == + WAIT_OBJECT_0) + break; + } + } + + if (avTaskHandle) + AvRevertMmThreadCharacteristics(avTaskHandle); +} diff --git a/idd/LGIdd/capture/FrameBufferTypes.h b/idd/LGIdd/capture/FrameBufferTypes.h new file mode 100644 index 00000000..c607d098 --- /dev/null +++ b/idd/LGIdd/capture/FrameBufferTypes.h @@ -0,0 +1,40 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include + +// FrameBuffer overlays LGMP shared memory and has a variable-length payload. +#pragma warning(push) +#pragma warning(disable: 4200) +struct FrameBuffer +{ + volatile uint32_t wp; + uint8_t data[0]; +}; +#pragma warning(pop) + +struct PreparedFrameBuffer +{ + unsigned frameIndex; + uint8_t * mem; + bool fullCopy; +}; diff --git a/idd/LGIdd/CSettings.cpp b/idd/LGIdd/config/CSettings.cpp similarity index 99% rename from idd/LGIdd/CSettings.cpp rename to idd/LGIdd/config/CSettings.cpp index 56176863..a9efdd1a 100644 --- a/idd/LGIdd/CSettings.cpp +++ b/idd/LGIdd/config/CSettings.cpp @@ -18,7 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CSettings.h" +#include "config/CSettings.h" #include "CDebug.h" #include "DefaultDisplayModes.h" #include "RefreshRate.h" diff --git a/idd/LGIdd/CSettings.h b/idd/LGIdd/config/CSettings.h similarity index 100% rename from idd/LGIdd/CSettings.h rename to idd/LGIdd/config/CSettings.h diff --git a/idd/LGIdd/CD3D11Device.cpp b/idd/LGIdd/d3d/CD3D11Device.cpp similarity index 98% rename from idd/LGIdd/CD3D11Device.cpp rename to idd/LGIdd/d3d/CD3D11Device.cpp index a179845f..5d10f167 100644 --- a/idd/LGIdd/CD3D11Device.cpp +++ b/idd/LGIdd/d3d/CD3D11Device.cpp @@ -18,7 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CD3D11Device.h" +#include "d3d/CD3D11Device.h" #include "CDebug.h" HRESULT CD3D11Device::Init() @@ -72,4 +72,4 @@ HRESULT CD3D11Device::Init() return hr; return S_OK; -} \ No newline at end of file +} diff --git a/idd/LGIdd/CD3D11Device.h b/idd/LGIdd/d3d/CD3D11Device.h similarity index 100% rename from idd/LGIdd/CD3D11Device.h rename to idd/LGIdd/d3d/CD3D11Device.h diff --git a/idd/LGIdd/CD3D12CommandQueue.cpp b/idd/LGIdd/d3d/CD3D12CommandQueue.cpp similarity index 99% rename from idd/LGIdd/CD3D12CommandQueue.cpp rename to idd/LGIdd/d3d/CD3D12CommandQueue.cpp index f8693696..62557182 100644 --- a/idd/LGIdd/CD3D12CommandQueue.cpp +++ b/idd/LGIdd/d3d/CD3D12CommandQueue.cpp @@ -18,7 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CD3D12CommandQueue.h" +#include "d3d/CD3D12CommandQueue.h" #include "CDebug.h" static uint64_t ScaleTicks(uint64_t ticks, uint64_t targetFrequency, diff --git a/idd/LGIdd/CD3D12CommandQueue.h b/idd/LGIdd/d3d/CD3D12CommandQueue.h similarity index 100% rename from idd/LGIdd/CD3D12CommandQueue.h rename to idd/LGIdd/d3d/CD3D12CommandQueue.h diff --git a/idd/LGIdd/CD3D12Device.cpp b/idd/LGIdd/d3d/CD3D12Device.cpp similarity index 99% rename from idd/LGIdd/CD3D12Device.cpp rename to idd/LGIdd/d3d/CD3D12Device.cpp index 1ac701de..434bc1ac 100644 --- a/idd/LGIdd/CD3D12Device.cpp +++ b/idd/LGIdd/d3d/CD3D12Device.cpp @@ -18,7 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CD3D12Device.h" +#include "d3d/CD3D12Device.h" #include "CDebug.h" bool CD3D12Device::m_indirectCopy = false; diff --git a/idd/LGIdd/CD3D12Device.h b/idd/LGIdd/d3d/CD3D12Device.h similarity index 97% rename from idd/LGIdd/CD3D12Device.h rename to idd/LGIdd/d3d/CD3D12Device.h index f91e5377..d5bb27fe 100644 --- a/idd/LGIdd/CD3D12Device.h +++ b/idd/LGIdd/d3d/CD3D12Device.h @@ -26,8 +26,8 @@ #include #include -#include "CIVSHMEM.h" -#include "CD3D12CommandQueue.h" +#include "transport/CIVSHMEM.h" +#include "d3d/CD3D12CommandQueue.h" using namespace Microsoft::WRL; diff --git a/idd/LGIdd/CInteropResource.cpp b/idd/LGIdd/d3d/CInteropResource.cpp similarity index 94% rename from idd/LGIdd/CInteropResource.cpp rename to idd/LGIdd/d3d/CInteropResource.cpp index cc2792c0..9412f7bc 100644 --- a/idd/LGIdd/CInteropResource.cpp +++ b/idd/LGIdd/d3d/CInteropResource.cpp @@ -18,10 +18,13 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CInteropResource.h" +#include "d3d/CInteropResource.h" #include "CDebug.h" -bool CInteropResource::Init(std::shared_ptr dx11Device, std::shared_ptr dx12Device, ComPtr srcTex) +bool CInteropResource::Init( + std::shared_ptr dx11Device, + std::shared_ptr dx12Device, + ComPtr srcTex) { HRESULT hr; @@ -112,14 +115,15 @@ void CInteropResource::Reset() m_dx11Device.reset(); } -bool CInteropResource::Compare(const ComPtr& srcTex) +bool CInteropResource::Compare( + const ComPtr& srcTex) const { if (srcTex.Get() != m_srcTex) return false; D3D11_TEXTURE2D_DESC format; srcTex->GetDesc(&format); - + return m_format.Width == format.Width && m_format.Height == format.Height && @@ -163,6 +167,7 @@ void CInteropResource::SetFullDamage() m_dirtyRects[0].bottom = m_format.Height; m_nbDirtyRects = 1; } + void CInteropResource::SetDirtyRects(const RECT * dirtyRects, unsigned nbDirtyRects) { if (nbDirtyRects > LG_MAX_DIRTY_RECTS) diff --git a/idd/LGIdd/d3d/CInteropResource.h b/idd/LGIdd/d3d/CInteropResource.h new file mode 100644 index 00000000..e3fea42e --- /dev/null +++ b/idd/LGIdd/d3d/CInteropResource.h @@ -0,0 +1,73 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include +#include +#include +#include + +#include "d3d/CD3D11Device.h" +#include "d3d/CD3D12Device.h" +#include "d3d/CD3D12CommandQueue.h" + +using namespace Microsoft::WRL; + +#define LG_MAX_DIRTY_RECTS 256 + +class CInteropResource +{ +private: + std::shared_ptr m_dx11Device; + std::shared_ptr m_dx12Device; + + // This value is likely released. It is only used to check if the supplied + // texture is different; do not rely on it pointing to valid memory. + void * m_srcTex; + + ComPtr m_d12Res; + D3D11_TEXTURE2D_DESC m_format; + ComPtr m_d11Fence; + ComPtr m_d12Fence; + UINT64 m_fenceValue; + bool m_ready; + + RECT m_dirtyRects[LG_MAX_DIRTY_RECTS]; + unsigned m_nbDirtyRects; + +public: + bool Init(std::shared_ptr dx11Device, + std::shared_ptr dx12Device, + ComPtr srcTex); + void Reset(); + + bool IsReady() const { return m_ready; } + bool Compare(const ComPtr& srcTex) const; + bool Signal(); + bool Sync(CD3D12CommandSlot& slot); + void SetFullDamage(); + void SetDirtyRects(const RECT * dirtyRects, unsigned nbDirtyRects); + + const ComPtr& GetRes() const { return m_d12Res; } + const D3D11_TEXTURE2D_DESC& GetFormat() const { return m_format; } + const RECT * GetDirtyRects() const { return m_dirtyRects; } + unsigned GetDirtyRectCount() const { return m_nbDirtyRects; } +}; diff --git a/idd/LGIdd/CInteropResourcePool.cpp b/idd/LGIdd/d3d/CInteropResourcePool.cpp similarity index 86% rename from idd/LGIdd/CInteropResourcePool.cpp rename to idd/LGIdd/d3d/CInteropResourcePool.cpp index 354ba4fd..a2a0d837 100644 --- a/idd/LGIdd/CInteropResourcePool.cpp +++ b/idd/LGIdd/d3d/CInteropResourcePool.cpp @@ -18,10 +18,12 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CInteropResourcePool.h" +#include "d3d/CInteropResourcePool.h" #include "CDebug.h" -void CInteropResourcePool::Init(std::shared_ptr dx11Device, std::shared_ptr dx12Device) +void CInteropResourcePool::Init( + std::shared_ptr dx11Device, + std::shared_ptr dx12Device) { Reset(); m_dx11Device = dx11Device; @@ -36,7 +38,8 @@ void CInteropResourcePool::Reset() m_dx12Device.reset(); } -CInteropResource* CInteropResourcePool::Get(ComPtr srcTex) +CInteropResource * CInteropResourcePool::Get( + ComPtr srcTex) { CInteropResource * res; unsigned freeSlot = POOL_SIZE; @@ -64,4 +67,4 @@ CInteropResource* CInteropResourcePool::Get(ComPtr srcTex) return nullptr; return res; -} \ No newline at end of file +} diff --git a/idd/LGIdd/CInteropResourcePool.h b/idd/LGIdd/d3d/CInteropResourcePool.h similarity index 70% rename from idd/LGIdd/CInteropResourcePool.h rename to idd/LGIdd/d3d/CInteropResourcePool.h index 98266d3a..6e6afa2a 100644 --- a/idd/LGIdd/CInteropResourcePool.h +++ b/idd/LGIdd/d3d/CInteropResourcePool.h @@ -24,23 +24,24 @@ #include #include #include -#include "CInteropResource.h" +#include "d3d/CInteropResource.h" using namespace Microsoft::WRL; -#define POOL_SIZE 10 - class CInteropResourcePool { - private: - CInteropResource m_pool[POOL_SIZE]; - - std::shared_ptr m_dx11Device; - std::shared_ptr m_dx12Device; - - public: - void Init(std::shared_ptr dx11Device, std::shared_ptr dx12Device); - void Reset(); +private: + static constexpr unsigned POOL_SIZE = 10; - CInteropResource* Get(ComPtr srcTex); -}; \ No newline at end of file + CInteropResource m_pool[POOL_SIZE]; + + std::shared_ptr m_dx11Device; + std::shared_ptr m_dx12Device; + +public: + void Init(std::shared_ptr dx11Device, + std::shared_ptr dx12Device); + void Reset(); + + CInteropResource * Get(ComPtr srcTex); +}; diff --git a/idd/LGIdd/display/CDisplayConfiguration.cpp b/idd/LGIdd/display/CDisplayConfiguration.cpp new file mode 100644 index 00000000..728b5edb --- /dev/null +++ b/idd/LGIdd/display/CDisplayConfiguration.cpp @@ -0,0 +1,496 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "display/CDisplayConfiguration.h" + +#include "CDebug.h" +#include "common/LGMPConfig.h" +#include "util/CSRWLock.h" + +#include +#include +#include + +static const UINT64 FRAME_BYTES_PER_PIXEL = 4; + +bool CDisplayConfiguration::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; +} + +bool CDisplayConfiguration::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; +} + +bool CDisplayConfiguration::GetResolutionMemoryRequirements( + uint32_t width, uint32_t height, UINT64 alignment, + const FrameMemoryLimits& limits, UINT64& frameSize, UINT64& sharedSize) +{ + frameSize = 0; + sharedSize = 0; + + if (!alignment || !limits.frameMemoryOffset || + !CalculateFrameSize(width, height, frameSize)) + return false; + + UINT64 frameAllocationSize; + if (!AlignUp(frameSize + alignment, alignment, frameAllocationSize)) + return false; + + UINT64 frameMemoryStart; + if (!AlignUp(limits.frameMemoryOffset, alignment, frameMemoryStart)) + return false; + + sharedSize = frameMemoryStart + + frameAllocationSize * LGMP_Q_FRAME_BUFFER_LEN; + return true; +} + +uint32_t CDisplayConfiguration::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 + +CDisplayConfiguration::CDisplayConfiguration(CSettings& settings) : + m_settings(settings) +{ +} + +bool CDisplayConfiguration::LoadModes(const FrameMemoryLimits& limits) +{ + const CSettings::DisplayModes configuredModes = + m_settings.LoadModes(); + + // Build the new mode list into a local first so readers never observe a + // reallocation of the live container. Publishing it is a pointer swap. + CSettings::DisplayModes newModes; + newModes.reserve(configuredModes.size()); + + const UINT64 alignment = limits.alignment ? limits.alignment : + D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + + bool hasPreferred = false; + for (const auto& configuredMode : configuredModes) + { + UINT64 frameSize; + UINT64 requiredIVSHMEMSize; + if (!GetResolutionMemoryRequirements(configuredMode.width, + configuredMode.height, alignment, limits, 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 > limits.sharedSize) + { + 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)limits.sharedSize); + 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; + + CSRWExclusiveLock lock(&m_modeLock); + m_modes = std::move(newModes); + return true; +} + +bool CDisplayConfiguration::Load(const FrameMemoryLimits& limits) +{ + return LoadModes(limits); +} + +bool CDisplayConfiguration::ReloadSettings( + const FrameMemoryLimits& limits) +{ + bool modesLoaded = false; + { + CSRWExclusiveLock reloadLock(&m_reloadLock); + + bool settingsUpdated = true; + CSettings::DisplayMode extraMode = {}; + if (m_settings.GetExtraMode(extraMode)) + { + const unsigned refreshMilliHz = + m_settings.GetDefaultRefreshMilliHz(); + if (extraMode.refreshMilliHz != refreshMilliHz) + { + extraMode.refreshMilliHz = refreshMilliHz; + settingsUpdated = m_settings.SetExtraMode(extraMode); + } + } + + if (settingsUpdated) + modesLoaded = LoadModes(limits); + } + + if (!modesLoaded) + DEBUG_ERROR("Failed to reload the display mode list"); + return modesLoaded; +} + +CDisplayConfiguration::ResolutionResult +CDisplayConfiguration::SetResolution( + uint32_t width, uint32_t height, const FrameMemoryLimits& limits) +{ + ResolutionResult result; + + UINT64 frameSize; + UINT64 requiredIVSHMEMSize; + if (!GetResolutionMemoryRequirements(width, height, limits.alignment, + limits, frameSize, requiredIVSHMEMSize)) + { + DEBUG_WARN("Ignoring invalid resolution request: %ux%u", width, height); + return result; + } + + if (requiredIVSHMEMSize > limits.sharedSize) + { + result.status = ResolutionStatus::TOO_LARGE; + result.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)limits.maxFrameSize, + result.requiredMiB); + return result; + } + + CSettings::DisplayMode mode = {}; + mode.width = width; + mode.height = height; + mode.refreshMilliHz = m_settings.GetDefaultRefreshMilliHz(); + mode.preferred = true; + + { + CSRWExclusiveLock reloadLock(&m_reloadLock); + if (!m_settings.SetExtraMode(mode)) + result.status = ResolutionStatus::SETTINGS_FAILED; + else if (!LoadModes(limits)) + result.status = ResolutionStatus::MODES_FAILED; + else + { + result.status = ResolutionStatus::SUCCESS; + result.mode = mode; + } + } + + if (result.status != ResolutionStatus::SUCCESS) + DEBUG_ERROR("Failed to rebuild the display mode list"); + return result; +} + +void CDisplayConfiguration::InitializeEdid(bool hdr) +{ + CSRWExclusiveLock lock(&m_modeLock); + if (m_edid.Size()) + return; + + m_edid.Build(hdr); + m_hdrEnabled = hdr; +} + +void CDisplayConfiguration::RebuildEdid(bool hdr) +{ + CSRWExclusiveLock lock(&m_modeLock); + m_edid.Build(hdr); + m_hdrEnabled = hdr; +} + +CDisplayConfiguration::Description +CDisplayConfiguration::GetDescription() const +{ + Description result; + CSRWSharedLock lock(&m_modeLock); + result.modeCount = m_modes.size(); + if (m_edid.Size()) + result.edid.assign(m_edid.Data(), m_edid.Data() + m_edid.Size()); + return result; +} + +CSettings::DisplayModes CDisplayConfiguration::SnapshotModes( + bool * hdrEnabled) const +{ + CSRWSharedLock lock(&m_modeLock); + if (hdrEnabled) + *hdrEnabled = m_hdrEnabled; + return m_modes; +} + +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 CDisplayConfiguration::ParseMonitorDescription( + const IDARG_IN_PARSEMONITORDESCRIPTION * inArgs, + IDARG_OUT_PARSEMONITORDESCRIPTION * outArgs) const +{ + const CSettings::DisplayModes modes = SnapshotModes(); + + 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 CDisplayConfiguration::MonitorGetDefaultModes( + const IDARG_IN_GETDEFAULTDESCRIPTIONMODES * inArgs, + IDARG_OUT_GETDEFAULTDESCRIPTIONMODES * outArgs) const +{ + const CSettings::DisplayModes modes = SnapshotModes(); + + 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 CDisplayConfiguration::MonitorQueryTargetModes( + const IDARG_IN_QUERYTARGETMODES * inArgs, + IDARG_OUT_QUERYTARGETMODES * outArgs) const +{ + const CSettings::DisplayModes modes = SnapshotModes(); + + 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 CDisplayConfiguration::ParseMonitorDescription2( + const IDARG_IN_PARSEMONITORDESCRIPTION2 * inArgs, + IDARG_OUT_PARSEMONITORDESCRIPTION * outArgs) const +{ + bool hdrEnabled = false; + const CSettings::DisplayModes modes = SnapshotModes(&hdrEnabled); + + 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(hdrEnabled); + + if (it->preferred) + outArgs->PreferredMonitorModeIdx = + (UINT)std::distance(modes.cbegin(), it); + } + + return STATUS_SUCCESS; +} + +NTSTATUS CDisplayConfiguration::MonitorQueryTargetModes2( + const IDARG_IN_QUERYTARGETMODES2 * inArgs, + IDARG_OUT_QUERYTARGETMODES * outArgs) const +{ + bool hdrEnabled = false; + const CSettings::DisplayModes modes = SnapshotModes(&hdrEnabled); + + 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(hdrEnabled); + } + + return STATUS_SUCCESS; +} +#endif diff --git a/idd/LGIdd/display/CDisplayConfiguration.h b/idd/LGIdd/display/CDisplayConfiguration.h new file mode 100644 index 00000000..8b073b31 --- /dev/null +++ b/idd/LGIdd/display/CDisplayConfiguration.h @@ -0,0 +1,113 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "config/CSettings.h" +#include "display/CEdid.h" +#include "display/IddCxCompat.h" +#include "transport/FrameMemoryLimits.h" + +#include +#include +#include + +class CDisplayConfiguration +{ +public: + enum class ResolutionStatus + { + SUCCESS, + INVALID, + TOO_LARGE, + SETTINGS_FAILED, + MODES_FAILED, + }; + + struct ResolutionResult + { + ResolutionStatus status = ResolutionStatus::INVALID; + CSettings::DisplayMode mode = {}; + uint32_t requiredMiB = 0; + }; + + struct Description + { + size_t modeCount = 0; + std::vector edid; + }; + +private: + CSettings& m_settings; + + // Registry-backed changes are serialized before publishing a replacement + // mode list. Readers only hold m_modeLock long enough to take a snapshot. + SRWLOCK m_reloadLock = SRWLOCK_INIT; + mutable SRWLOCK m_modeLock = SRWLOCK_INIT; + + CSettings::DisplayModes m_modes; + CEdid m_edid; + bool m_hdrEnabled = false; + + bool LoadModes(const FrameMemoryLimits& limits); + CSettings::DisplayModes SnapshotModes(bool * hdrEnabled = nullptr) const; + + static bool AlignUp(UINT64 value, UINT64 alignment, UINT64& result); + static bool CalculateFrameSize(uint32_t width, uint32_t height, + UINT64& frameSize); + static bool GetResolutionMemoryRequirements(uint32_t width, + uint32_t height, UINT64 alignment, const FrameMemoryLimits& limits, + UINT64& frameSize, UINT64& sharedSize); + static uint32_t RecommendedIVSHMEMSizeMiB(UINT64 requiredSize); + +public: + explicit CDisplayConfiguration(CSettings& settings); + + CDisplayConfiguration(const CDisplayConfiguration&) = delete; + CDisplayConfiguration& operator=(const CDisplayConfiguration&) = delete; + + bool Load(const FrameMemoryLimits& limits); + bool ReloadSettings(const FrameMemoryLimits& limits); + ResolutionResult SetResolution(uint32_t width, uint32_t height, + const FrameMemoryLimits& limits); + + void InitializeEdid(bool hdr); + void RebuildEdid(bool hdr); + Description GetDescription() const; + + NTSTATUS ParseMonitorDescription( + const IDARG_IN_PARSEMONITORDESCRIPTION * inArgs, + IDARG_OUT_PARSEMONITORDESCRIPTION * outArgs) const; + NTSTATUS MonitorGetDefaultModes( + const IDARG_IN_GETDEFAULTDESCRIPTIONMODES * inArgs, + IDARG_OUT_GETDEFAULTDESCRIPTIONMODES * outArgs) const; + NTSTATUS MonitorQueryTargetModes( + const IDARG_IN_QUERYTARGETMODES * inArgs, + IDARG_OUT_QUERYTARGETMODES * outArgs) const; + +#ifdef HAS_IDDCX_110 + NTSTATUS ParseMonitorDescription2( + const IDARG_IN_PARSEMONITORDESCRIPTION2 * inArgs, + IDARG_OUT_PARSEMONITORDESCRIPTION * outArgs) const; + NTSTATUS MonitorQueryTargetModes2( + const IDARG_IN_QUERYTARGETMODES2 * inArgs, + IDARG_OUT_QUERYTARGETMODES * outArgs) const; +#endif +}; diff --git a/idd/LGIdd/CEdid.cpp b/idd/LGIdd/display/CEdid.cpp similarity index 99% rename from idd/LGIdd/CEdid.cpp rename to idd/LGIdd/display/CEdid.cpp index 7ff46827..b0972920 100644 --- a/idd/LGIdd/CEdid.cpp +++ b/idd/LGIdd/display/CEdid.cpp @@ -18,7 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CEdid.h" +#include "display/CEdid.h" #include #include diff --git a/idd/LGIdd/CEdid.h b/idd/LGIdd/display/CEdid.h similarity index 98% rename from idd/LGIdd/CEdid.h rename to idd/LGIdd/display/CEdid.h index 8ba87057..0c63cd35 100644 --- a/idd/LGIdd/CEdid.h +++ b/idd/LGIdd/display/CEdid.h @@ -24,7 +24,7 @@ #include #include -#include "CSettings.h" +#include "config/CSettings.h" class CEdid { diff --git a/idd/LGIdd/display/CMonitorManager.cpp b/idd/LGIdd/display/CMonitorManager.cpp new file mode 100644 index 00000000..e8e2b655 --- /dev/null +++ b/idd/LGIdd/display/CMonitorManager.cpp @@ -0,0 +1,252 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "display/CMonitorManager.h" + +#include "display/monitor/Context.h" +#include "CDebug.h" + +void CMonitorManager::Create(UINT connectorIndex, IDDCX_ADAPTER adapter, + std::vector edid, CDeviceContext * owner) +{ + 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_lock); + const bool haveMonitor = m_monitor != WDF_NO_HANDLE; + ReleaseSRWLockExclusive(&m_lock); + if (haveMonitor) + { + DEBUG_WARN("FinishInit skipped: a monitor already exists"); + return; + } + + WDF_OBJECT_ATTRIBUTES attr; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, CMonitorContextWrapper); + + 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(adapter, &create, &createOut); + if (!NT_SUCCESS(status)) + { + DEBUG_ERROR_HR(status, "IddCxMonitorCreate Failed"); + return; + } + + DEBUG_INFO("Monitor object created (%p)", createOut.MonitorObject); + + AcquireSRWLockExclusive(&m_lock); + m_monitor = createOut.MonitorObject; + ReleaseSRWLockExclusive(&m_lock); + + auto * wrapper = WdfObjectGet_CMonitorContextWrapper(m_monitor); + wrapper->context = new CMonitorContext(m_monitor, owner); + + 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"); +} + +CMonitorManager::ReplugAction CMonitorManager::Replug() +{ + AcquireSRWLockExclusive(&m_lock); + + 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_lock); + return ReplugAction::NONE; + } + + IDDCX_MONITOR monitor = m_monitor; + if (monitor == WDF_NO_HANDLE) + { + m_replugMonitor = true; + m_monitorDeparted = true; + ReleaseSRWLockExclusive(&m_lock); + // Either no monitor yet, or one is already pending; build it now and + // cancel any queued rebuild so we do not create two. + m_createQueued.store(0); + return ReplugAction::CREATE; + } + + // Clear the handle before departing so nothing calls an IddCx monitor API + // on a departing/destroyed handle. Create publishes the new one. + m_replugMonitor = true; + m_monitorDeparted = false; + m_waitForSwapChainRelease = m_swapChainAssigned; + m_monitor = nullptr; + ReleaseSRWLockExclusive(&m_lock); + + DEBUG_TRACE("ReplugMonitor"); + NTSTATUS status = IddCxMonitorDeparture(monitor); + if (!NT_SUCCESS(status)) + { + AcquireSRWLockExclusive(&m_lock); + m_replugMonitor = false; + m_replugPending = false; + m_monitorDeparted = false; + m_waitForSwapChainRelease = false; + m_monitor = monitor; + ReleaseSRWLockExclusive(&m_lock); + DEBUG_ERROR("IddCxMonitorDeparture Failed (0x%08x)", status); + return ReplugAction::NONE; + } + + AcquireSRWLockExclusive(&m_lock); + m_monitorDeparted = true; + const bool rebuild = !m_waitForSwapChainRelease; + ReleaseSRWLockExclusive(&m_lock); + + // If there was no swap chain there will be no unassign callback to queue + // the rebuild. Otherwise OnSwapChainReleased does so after teardown drains. + if (rebuild) + m_createQueued.store(1); + + return ReplugAction::NONE; +} + +void CMonitorManager::RequestMode(const CSettings::DisplayMode& mode) +{ + AcquireSRWLockExclusive(&m_lock); + m_setMode = mode; + m_doSetMode = true; + ReleaseSRWLockExclusive(&m_lock); +} + +void CMonitorManager::OnDestroyed(IDDCX_MONITOR monitor) +{ + AcquireSRWLockExclusive(&m_lock); + if (m_monitor == monitor) + m_monitor = nullptr; + ReleaseSRWLockExclusive(&m_lock); +} + +void CMonitorManager::OnSwapChainAssigned() +{ + AcquireSRWLockExclusive(&m_lock); + m_swapChainAssigned = true; + m_swapChainReady = false; + ReleaseSRWLockExclusive(&m_lock); +} + +void CMonitorManager::OnSwapChainReleased() +{ + bool rebuild = false; + + AcquireSRWLockExclusive(&m_lock); + m_swapChainAssigned = false; + m_swapChainReady = false; + if (m_replugMonitor && m_waitForSwapChainRelease) + { + m_waitForSwapChainRelease = false; + rebuild = m_monitorDeparted; + } + ReleaseSRWLockExclusive(&m_lock); + + if (rebuild) + m_createQueued.store(1); +} + +CMonitorManager::ReadyAction CMonitorManager::OnSwapChainReady() +{ + ReadyAction action = {}; + bool replug = false; + + AcquireSRWLockExclusive(&m_lock); + 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) + { + action.mode = m_setMode; + m_doSetMode = false; + action.setMode = true; + } + ReleaseSRWLockExclusive(&m_lock); + + action.replug = replug; + + return action; +} + +void CMonitorManager::QueueReplug() +{ + m_replugQueued.store(1); +} + +CMonitorManager::DeferredAction CMonitorManager::TakeDeferredAction() +{ + if (m_createQueued.exchange(0)) + return DeferredAction::CREATE; + + if (m_replugQueued.exchange(0)) + return DeferredAction::REPLUG; + + return DeferredAction::NONE; +} diff --git a/idd/LGIdd/display/CMonitorManager.h b/idd/LGIdd/display/CMonitorManager.h new file mode 100644 index 00000000..d34bee63 --- /dev/null +++ b/idd/LGIdd/display/CMonitorManager.h @@ -0,0 +1,89 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "config/CSettings.h" + +class CDeviceContext; + +class CMonitorManager +{ +public: + enum class ReplugAction + { + NONE, + CREATE, + }; + + enum class DeferredAction + { + NONE, + CREATE, + REPLUG, + }; + + struct ReadyAction + { + CSettings::DisplayMode mode = {}; + bool setMode = false; + bool replug = false; + }; + +private: + IDDCX_MONITOR m_monitor = nullptr; + + // Guards the monitor/replug/swap-chain state. These values are touched by + // IddCx callback threads, the swap-chain thread, and the LGMP timer. + SRWLOCK m_lock = SRWLOCK_INIT; + + bool m_replugMonitor = false; + bool m_replugPending = false; + bool m_monitorDeparted = false; + bool m_swapChainAssigned = false; + bool m_swapChainReady = false; + bool m_waitForSwapChainRelease = false; + + CSettings::DisplayMode m_setMode = {}; + bool m_doSetMode = false; + + std::atomic m_createQueued = 0; + std::atomic m_replugQueued = 0; + +public: + void Create(UINT connectorIndex, IDDCX_ADAPTER adapter, + std::vector edid, CDeviceContext * owner); + ReplugAction Replug(); + void RequestMode(const CSettings::DisplayMode& mode); + + void OnDestroyed(IDDCX_MONITOR monitor); + void OnSwapChainAssigned(); + void OnSwapChainReleased(); + ReadyAction OnSwapChainReady(); + void QueueReplug(); + + DeferredAction TakeDeferredAction(); +}; diff --git a/idd/LGIdd/display/IddCxCompat.h b/idd/LGIdd/display/IddCxCompat.h new file mode 100644 index 00000000..ac9041e7 --- /dev/null +++ b/idd/LGIdd/display/IddCxCompat.h @@ -0,0 +1,34 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include +#include +#include + +// IddCx 1.10 HDR/WCG types are only visible when the WDK targets +// (NTDDI >= 0x0A000005) and the build flags select IddCx 1.10 or newer. +#if defined(IDDCX_VERSION_MAJOR) && defined(IDDCX_VERSION_MINOR) && \ + (IDDCX_VERSION_MAJOR > 1 || \ + (IDDCX_VERSION_MAJOR == 1 && IDDCX_VERSION_MINOR >= 10)) && \ + NTDDI_VERSION >= 0x0A000005 + #define HAS_IDDCX_110 +#endif diff --git a/idd/LGIdd/display/device/CDeviceContext.cpp b/idd/LGIdd/display/device/CDeviceContext.cpp new file mode 100644 index 00000000..543dca0d --- /dev/null +++ b/idd/LGIdd/display/device/CDeviceContext.cpp @@ -0,0 +1,588 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "display/device/CDeviceContext.h" + +#include "display/IddCxCompat.h" +#include "transport/CPipeServer.h" +#include "CDebug.h" + +#include +#include + +// Adapter and monitor lifecycle + +static const UINT IDDCX_VERSION_1_10 = 0x1A00; + +CDeviceContext::CDeviceContext(WDFDEVICE wdfDevice) : + m_wdfDevice(wdfDevice), + m_lgmpControl(m_lgmpHost), + m_frameTransport(m_lgmpHost, m_ivshmem), + m_displayConfiguration(g_settings) +{ +} + +CDeviceContext::~CDeviceContext() +{ + // Both callbacks dereference this context. Drain them before the subsystem + // members are destroyed in frame, control, host order. + if (m_initTimer) + { + WdfTimerStop(m_initTimer, TRUE); + m_initTimer = nullptr; + } + + if (m_lgmpTimer) + { + WdfTimerStop(m_lgmpTimer, TRUE); + m_lgmpTimer = nullptr; + } +} + +void CDeviceContext::QueryIddCxCapabilities() +{ + IDARG_OUT_GETVERSION ver = {}; + NTSTATUS status = IddCxGetVersion(&ver); + if (!NT_SUCCESS(status)) + { + m_iddCxVersion = 0; + m_hasIddCx110DDIs = false; + m_canProcessFP16 = false; + DEBUG_ERROR_HR(status, "IddCxGetVersion Failed"); + return; + } + + m_iddCxVersion = ver.IddCxVersion; + +#ifdef HAS_IDDCX_110 + const bool hasIddCx110DDIs = + !!IDD_IS_FUNCTION_AVAILABLE(IddCxSwapChainReleaseAndAcquireBuffer2) && + !!IDD_IS_FUNCTION_AVAILABLE(IddCxMonitorQueryHardwareCursor3) && + !!IDD_IS_FUNCTION_AVAILABLE(IddCxMonitorUpdateModes2) && + IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxAdapterQueryTargetInfo) && + IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxAdapterCommitModes2) && + IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxParseMonitorDescription2) && + IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxMonitorQueryTargetModes2) && + IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxMonitorSetDefaultHdrMetaData) && + IDD_IS_FIELD_AVAILABLE(IDD_CX_CLIENT_CONFIG, EvtIddCxMonitorSetGammaRamp); +#else + const bool hasIddCx110DDIs = false; +#endif + + m_hasIddCx110DDIs = + m_iddCxVersion >= IDDCX_VERSION_1_10 && hasIddCx110DDIs; + m_canProcessFP16 = !m_softwareMode && m_hasIddCx110DDIs; + + DEBUG_INFO("IddCx version: 0x%04x", m_iddCxVersion); + DEBUG_INFO("IddCx 1.10 HDR/WCG DDIs: %s", + m_hasIddCx110DDIs ? "available" : "unavailable"); + if (m_softwareMode && m_hasIddCx110DDIs) + DEBUG_INFO("HDR/WCG disabled for software rendering"); +} + +void CDeviceContext::ScheduleInitRetry() +{ + // Create the retry timer once; if it already exists it is either running or + // will be (re)started below. + if (!m_initTimer) + { + WDF_TIMER_CONFIG config; + WDF_TIMER_CONFIG_INIT_PERIODIC(&config, + [](WDFTIMER timer) -> void + { + WDFOBJECT parent = WdfTimerGetParentObject(timer); + auto wrapper = WdfObjectGet_CDeviceContextWrapper(parent); + wrapper->context->InitAdapter(); + }, + 500); + config.AutomaticSerialization = FALSE; + + WDF_OBJECT_ATTRIBUTES attribs; + WDF_OBJECT_ATTRIBUTES_INIT(&attribs); + attribs.ParentObject = m_wdfDevice; + attribs.ExecutionLevel = WdfExecutionLevelDispatch; + + NTSTATUS status = WdfTimerCreate(&config, &attribs, &m_initTimer); + if (!NT_SUCCESS(status)) + { + DEBUG_ERROR_HR(status, "Init retry timer creation failed"); + m_initTimer = nullptr; + return; + } + } + + WdfTimerStart(m_initTimer, WDF_REL_TIMEOUT_IN_MS(500)); +} + +void CDeviceContext::StopInitRetry() +{ + if (m_initTimer) + WdfTimerStop(m_initTimer, FALSE); +} + +void CDeviceContext::InitAdapter() +{ + DEBUG_TRACE("InitAdapter"); + + // The adapter only needs to be created once. D0Entry and the retry timer can + // both land here, so guard against re-entrancy and repeated creation. + if (m_adapter) + { + DEBUG_TRACE("Adapter initialization skipped: adapter already exists"); + return; + } + + LONG initExpected = 0; + if (!m_initInProgress.compare_exchange_strong(initExpected, 1)) + { + DEBUG_TRACE("Adapter initialization skipped: initialization already in progress"); + return; + } + + // At boot the IVSHMEM PCI device may not have enumerated yet. Rather than + // silently abandoning the adapter (leaving the device loaded but with no + // monitor), retry from a timer until the shared memory becomes available. + if (!m_ivshmemOpened) + { + if (!m_ivshmem.Init() || !m_ivshmem.Open()) + { + DEBUG_WARN("IVSHMEM not available yet, scheduling init retry"); + ScheduleInitRetry(); + m_initInProgress.store(0); + return; + } + m_ivshmemOpened = true; + } + + // Select the render adapter before advertising capabilities. If no hardware + // adapter is available, this is a software-rendered display and must remain + // SDR-only; the software path must never depend on compute processing. + m_havePreferredRenderAdapter = false; + m_preferredRenderAdapter = {}; + IDXGIFactory1 * factory = NULL; + HRESULT factoryStatus = CreateDXGIFactory1( + __uuidof(IDXGIFactory1), (void **)&factory); + if (FAILED(factoryStatus)) + DEBUG_ERROR_HR(factoryStatus, "CreateDXGIFactory Failed"); + else + { + for (UINT i = 0;; ++i) + { + IDXGIAdapter1 * dxgiAdapter = nullptr; + HRESULT enumStatus = factory->EnumAdapters1(i, &dxgiAdapter); + if (enumStatus == DXGI_ERROR_NOT_FOUND) + break; + if (FAILED(enumStatus)) + { + DEBUG_ERROR_HR(enumStatus, "Failed to enumerate DXGI adapter %u", i); + break; + } + + DXGI_ADAPTER_DESC1 adapterDesc = {}; + HRESULT descStatus = dxgiAdapter->GetDesc1(&adapterDesc); + dxgiAdapter->Release(); + if (FAILED(descStatus)) + { + DEBUG_ERROR_HR(descStatus, "Failed to query DXGI adapter %u", i); + continue; + } + + if ((adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) || + (adapterDesc.VendorId == 0x1414 && adapterDesc.DeviceId == 0x008c)) + { + DEBUG_INFO("Ignoring software render adapter %ls", adapterDesc.Description); + continue; + } + + if ((adapterDesc.VendorId == 0x1b36 && adapterDesc.DeviceId == 0x000d) || // QXL + (adapterDesc.VendorId == 0x1234 && adapterDesc.DeviceId == 0x1111)) // QEMU Standard VGA + { + DEBUG_INFO("Ignoring display-only adapter %ls (vendor 0x%04x, device 0x%04x)", + adapterDesc.Description, adapterDesc.VendorId, adapterDesc.DeviceId); + continue; + } + + DEBUG_INFO("Selected render adapter %ls (vendor 0x%04x, device 0x%04x)", + adapterDesc.Description, adapterDesc.VendorId, adapterDesc.DeviceId); + m_preferredRenderAdapter = adapterDesc.AdapterLuid; + m_havePreferredRenderAdapter = true; + break; + } + + factory->Release(); + } + + m_softwareMode = !m_havePreferredRenderAdapter; + if (m_softwareMode) + DEBUG_INFO("No hardware render adapter available; using SDR software mode"); + + QueryIddCxCapabilities(); + DEBUG_TRACE("Initializing LGMP metadata"); + if (!InitializeLGMP()) + { + m_initInProgress.store(0); + return; + } + DEBUG_TRACE("Loading configured display modes"); + if (!m_displayConfiguration.Load(m_frameTransport.GetMemoryLimits())) + { + m_initInProgress.store(0); + return; + } + DEBUG_TRACE("Initializing monitor EDID"); + m_displayConfiguration.InitializeEdid(CanProcessFP16()); + + const CDisplayConfiguration::Description description = + m_displayConfiguration.GetDescription(); + DEBUG_INFO("Initializing adapter with %llu modes and a %u-byte EDID", + (unsigned long long)description.modeCount, + (UINT)description.edid.size()); + + IDDCX_ADAPTER_CAPS caps = {}; + caps.Size = sizeof(caps); + + /** + * For some reason if we do not set this flag sometimes windows will + * refuse to enumerate our virtual monitor. Intel also noted in their + * sources that if this is not set dynamic resolution changes from this + * driver will not work. This behaviour is not documented by Microsoft. + */ + caps.Flags = IDDCX_ADAPTER_FLAGS_USE_SMALLEST_MODE; +#ifdef HAS_IDDCX_110 + if (CanProcessFP16()) + caps.Flags |= IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16; +#endif + + caps.MaxMonitorsSupported = 1; + caps.StaticDesktopReencodeFrameCount = 1; + + caps.EndPointDiagnostics.Size = sizeof(caps.EndPointDiagnostics); + caps.EndPointDiagnostics.GammaSupport = IDDCX_FEATURE_IMPLEMENTATION_NONE; + caps.EndPointDiagnostics.TransmissionType = IDDCX_TRANSMISSION_TYPE_OTHER; + + caps.EndPointDiagnostics.pEndPointFriendlyName = L"Looking Glass IDD Driver"; + caps.EndPointDiagnostics.pEndPointManufacturerName = L"Looking Glass"; + caps.EndPointDiagnostics.pEndPointModelName = L"Looking Glass"; + + IDDCX_ENDPOINT_VERSION ver = {}; + ver.Size = sizeof(ver); + ver.MajorVer = 1; + caps.EndPointDiagnostics.pFirmwareVersion = &ver; + caps.EndPointDiagnostics.pHardwareVersion = &ver; + + WDF_OBJECT_ATTRIBUTES attr; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, CDeviceContextWrapper); + + IDARG_IN_ADAPTER_INIT init = {}; + init.WdfDevice = m_wdfDevice; + init.pCaps = ∩︀ + init.ObjectAttributes = &attr; + + IDARG_OUT_ADAPTER_INIT initOut = {}; + DEBUG_INFO("Calling IddCxAdapterInitAsync with flags 0x%08x", + caps.Flags); + NTSTATUS status = IddCxAdapterInitAsync(&init, &initOut); + if (!NT_SUCCESS(status) && CanProcessFP16()) + { + DEBUG_WARN( + "IddCxAdapterInitAsync rejected FP16 adapter capabilities (0x%08x), retrying without HDR/WCG", + status); + m_canProcessFP16 = false; + // The monitor has not been created yet, so replace the provisional HDR + // EDID before Windows can observe it. + m_displayConfiguration.RebuildEdid(false); + caps.Flags = (IDDCX_ADAPTER_FLAGS)(caps.Flags & ~IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16); + ZeroMemory(&initOut, sizeof(initOut)); + status = IddCxAdapterInitAsync(&init, &initOut); + } + + if (!NT_SUCCESS(status)) + { + DEBUG_ERROR_HR(status, "IddCxAdapterInitAsync Failed"); + m_initInProgress.store(0); + return; + } + + m_adapter = initOut.AdapterObject; + if (!m_adapter) + { + DEBUG_ERROR("IddCxAdapterInitAsync succeeded without returning an adapter object"); + m_initInProgress.store(0); + return; + } + + auto * wrapper = WdfObjectGet_CDeviceContextWrapper(m_adapter); + wrapper->context = this; + DEBUG_INFO("IddCxAdapterInitAsync started successfully (adapter %p)", + m_adapter); + DEBUG_INFO("Adapter context attached; waiting for initialization callback"); + + // Adapter is up; no need to keep retrying. + StopInitRetry(); + m_initInProgress.store(0); + DEBUG_INFO("Adapter initialization request complete; returning to IddCx"); +} + +void CDeviceContext::FinishAdapterInit(UINT connectorIndex) +{ + // Try to co-exist with the virtual video device by telling IddCx which + // hardware adapter we prefer to render on. Do this only after the adapter + // has finished initializing, but before adding its monitor. + if (m_havePreferredRenderAdapter) + { + IDARG_IN_ADAPTERSETRENDERADAPTER args = {}; + args.PreferredRenderAdapter = m_preferredRenderAdapter; + IddCxAdapterSetRenderAdapter(m_adapter, &args); + DEBUG_INFO("Preferred render adapter set"); + } + + FinishInit(connectorIndex); +} + +void CDeviceContext::FinishInit(UINT connectorIndex) +{ + CDisplayConfiguration::Description description = + m_displayConfiguration.GetDescription(); + m_monitorManager.Create( + connectorIndex, m_adapter, std::move(description.edid), this); +} + +void CDeviceContext::ReplugMonitor() +{ + if (m_monitorManager.Replug() == + CMonitorManager::ReplugAction::CREATE) + FinishInit(0); +} + +void CDeviceContext::ReloadSettings() +{ + if (!m_displayConfiguration.ReloadSettings( + m_frameTransport.GetMemoryLimits())) + return; + + ReplugMonitor(); +} + +void CDeviceContext::OnMonitorDestroyed(IDDCX_MONITOR monitor) +{ + m_monitorManager.OnDestroyed(monitor); +} + +void CDeviceContext::OnSwapChainAssigned() +{ + m_monitorManager.OnSwapChainAssigned(); +} + +void CDeviceContext::OnSwapChainReleased() +{ + m_monitorManager.OnSwapChainReleased(); +} + +void CDeviceContext::OnSwapChainReady() +{ + const CMonitorManager::ReadyAction action = + m_monitorManager.OnSwapChainReady(); + + // Do not expose the context to pipe reload requests until the initial swap + // chain has reached the same ready state used by the replug gate. + g_pipe.SetDeviceContext(this); + + if (action.replug) + m_monitorManager.QueueReplug(); + else if (action.setMode) + g_pipe.SetDisplayMode( + action.mode.width, action.mode.height, action.mode.refreshMilliHz); +} + +// Display configuration + +void CDeviceContext::SetResolution(uint32_t width, uint32_t height) +{ + const CDisplayConfiguration::ResolutionResult result = + m_displayConfiguration.SetResolution( + width, height, m_frameTransport.GetMemoryLimits()); + + switch (result.status) + { + case CDisplayConfiguration::ResolutionStatus::SUCCESS: + m_monitorManager.RequestMode(result.mode); + // IddCxMonitorUpdateModes[2] does not invalidate Windows' cached mode + // list, so depart and re-arrive the monitor to rebuild the topology. + ReplugMonitor(); + break; + + case CDisplayConfiguration::ResolutionStatus::TOO_LARGE: + g_pipe.ResolutionRejected(width, height, result.requiredMiB); + break; + + default: + break; + } +} + +// LGMP transport + +bool CDeviceContext::InitializeLGMP() +{ + if (m_lgmpHost.IsInitialized()) + return true; + + if (!m_lgmpHost.Initialize(m_ivshmem)) + return false; + + // Preserve the shared-memory layout: frame queues precede the pointer queue + // and its retained cursor and color-transform allocations. + if (!m_frameTransport.Initialize() || !m_lgmpControl.Initialize()) + return false; + + m_frameTransport.SealMemoryLayout(); + return true; +} + +bool CDeviceContext::SetupLGMP(size_t alignSize) +{ + // Frame buffers cannot be allocated until the GPU-specific alignment is + // known. The swap-chain path may call this again after setup completed. + if (m_frameTransport.GetMaxFrameSize()) + return true; + + if (!InitializeLGMP() || !m_frameTransport.Setup(alignSize)) + return false; + + WDF_TIMER_CONFIG config; + WDF_TIMER_CONFIG_INIT_PERIODIC(&config, + [](WDFTIMER timer) -> void + { + WDFOBJECT parent = WdfTimerGetParentObject(timer); + auto wrapper = WdfObjectGet_CDeviceContextWrapper(parent); + wrapper->context->LGMPTimer(); + }, + 10); + config.AutomaticSerialization = FALSE; + + /** + * Documentation states that Dispatch is not available under UMDF, however + * using Passive returns a not-supported error and Dispatch works. + */ + WDF_OBJECT_ATTRIBUTES attribs; + WDF_OBJECT_ATTRIBUTES_INIT(&attribs); + attribs.ParentObject = m_wdfDevice; + attribs.ExecutionLevel = WdfExecutionLevelDispatch; + + NTSTATUS status = WdfTimerCreate( + &config, &attribs, &m_lgmpTimer); + if (!NT_SUCCESS(status)) + { + DEBUG_ERROR_HR(status, "Timer creation failed"); + return false; + } + + WdfTimerStart(m_lgmpTimer, WDF_REL_TIMEOUT_IN_MS(10)); + return true; +} + +void CDeviceContext::LGMPTimer() +{ + // Monitor work is deferred off IddCx callback threads. + switch (m_monitorManager.TakeDeferredAction()) + { + case CMonitorManager::DeferredAction::CREATE: + FinishInit(0); + return; + + case CMonitorManager::DeferredAction::REPLUG: + ReplugMonitor(); + return; + + case CMonitorManager::DeferredAction::NONE: + break; + } + + const LGMP_STATUS processStatus = m_lgmpHost.Process(); + if (processStatus != LGMP_OK) + { + if (processStatus == LGMP_ERR_CORRUPTED) + { + DEBUG_WARN( + "LGMP reported the shared memory has been corrupted, attempting to recover\n"); + // TODO: reinitialize LGMP. + return; + } + + DEBUG_ERROR("lgmpHostProcess Failed: %s", + lgmpStatusString(processStatus)); + // TODO: shut down LGMP. + return; + } + + const uint64_t now = CFrameScheduler::Nanotime(); + + // Take the frame subscriber snapshot before processing scheduling messages, + // then publish both updates together just as the original timer did. + const CFrameTransport::SubscriberSnapshot subscribers = + m_frameTransport.SnapshotSubscribers(); + + uint8_t data[LGMP_MSGS_SIZE]; + size_t size; + uint32_t sourceClientID; + LGMP_STATUS status; + while ((status = m_lgmpControl.ReadDataWithSource( + data, &size, &sourceClientID)) == LGMP_OK) + { + KVMFRMessage * msg = reinterpret_cast(data); + switch (msg->type) + { + case KVMFR_MESSAGE_SETCURSORPOS: + { + KVMFRSetCursorPos * position = + reinterpret_cast(msg); + g_pipe.SetCursorPos(position->x, position->y); + break; + } + + case KVMFR_MESSAGE_WINDOWSIZE: + { + KVMFRWindowSize * window = + reinterpret_cast(msg); + SetResolution(window->w, window->h); + break; + } + + case KVMFR_MESSAGE_FRAME_SCHEDULE: + { + const KVMFRFrameSchedule * schedule = + reinterpret_cast(msg); + const bool valid = size == sizeof(*schedule) && + m_frameTransport.UpdateSchedule( + sourceClientID, *schedule, now); + if (!valid) + DEBUG_WARN("Ignoring invalid KVMFR frame schedule"); + break; + } + } + + m_lgmpControl.AckData(); + } + + m_frameTransport.FinalizeSubscribers(subscribers, now); + + if (m_lgmpControl.HasNewSubscribers()) + m_lgmpControl.ResendState(); +} diff --git a/idd/LGIdd/display/device/CDeviceContext.h b/idd/LGIdd/display/device/CDeviceContext.h new file mode 100644 index 00000000..fe4aa792 --- /dev/null +++ b/idd/LGIdd/display/device/CDeviceContext.h @@ -0,0 +1,126 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include "display/CDisplayConfiguration.h" +#include "display/CMonitorManager.h" +#include "transport/CFrameTransport.h" +#include "transport/CIVSHMEM.h" +#include "transport/CLGMPControl.h" +#include "transport/CLGMPHost.h" + +class CDeviceContext +{ +private: + WDFDEVICE m_wdfDevice; + IDDCX_ADAPTER m_adapter = nullptr; + LUID m_preferredRenderAdapter = {}; + bool m_havePreferredRenderAdapter = false; + + // At boot IVSHMEM may not have enumerated yet. The retry timer and atomic + // gate keep adapter creation single-threaded until it becomes available. + WDFTIMER m_initTimer = nullptr; + bool m_ivshmemOpened = false; + std::atomic m_initInProgress = 0; + + CIVSHMEM m_ivshmem; + CLGMPHost m_lgmpHost; + CLGMPControl m_lgmpControl; + CFrameTransport m_frameTransport; + CDisplayConfiguration m_displayConfiguration; + CMonitorManager m_monitorManager; + + WDFTIMER m_lgmpTimer = nullptr; + + UINT m_iddCxVersion = 0; + bool m_hasIddCx110DDIs = false; + bool m_canProcessFP16 = false; + bool m_softwareMode = true; + + void QueryIddCxCapabilities(); + + void ScheduleInitRetry(); + void StopInitRetry(); + + bool InitializeLGMP(); + void LGMPTimer(); + void SetResolution(uint32_t width, uint32_t height); + +public: + explicit CDeviceContext(_In_ WDFDEVICE wdfDevice); + ~CDeviceContext(); + + CDeviceContext(const CDeviceContext&) = delete; + CDeviceContext& operator=(const CDeviceContext&) = delete; + + bool SetupLGMP(size_t alignSize); + + void InitAdapter(); + void FinishAdapterInit(UINT connectorIndex); + void FinishInit(UINT connectorIndex); + void ReloadSettings(); + void ReplugMonitor(); + + void OnMonitorDestroyed(IDDCX_MONITOR monitor); + void OnSwapChainAssigned(); + void OnSwapChainReleased(); + void OnSwapChainReady(); + + bool HasIddCx110DDIs() const { return m_hasIddCx110DDIs; } + bool CanProcessFP16 () const { return m_canProcessFP16; } + bool IsSoftwareMode () const { return m_softwareMode; } + + CFrameTransport& GetFrameTransport() + { + return m_frameTransport; + } + + CLGMPControl& GetLGMPControl() + { + return m_lgmpControl; + } + + CDisplayConfiguration& GetDisplayConfiguration() + { + return m_displayConfiguration; + } +}; + +struct CDeviceContextWrapper +{ + CDeviceContext * context; + + void Cleanup() + { + delete context; + context = nullptr; + } +}; + +WDF_DECLARE_CONTEXT_TYPE(CDeviceContextWrapper); diff --git a/idd/LGIdd/CIndirectMonitorContext.cpp b/idd/LGIdd/display/monitor/Context.cpp similarity index 90% rename from idd/LGIdd/CIndirectMonitorContext.cpp rename to idd/LGIdd/display/monitor/Context.cpp index efffca58..9455c4f7 100644 --- a/idd/LGIdd/CIndirectMonitorContext.cpp +++ b/idd/LGIdd/display/monitor/Context.cpp @@ -18,23 +18,26 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CIndirectMonitorContext.h" +#include "display/monitor/Context.h" +#include "display/device/CDeviceContext.h" +#include "capture/CSwapChainProcessor.h" +#include "d3d/CD3D11Device.h" #include "CDebug.h" -#include "CPipeServer.h" -CIndirectMonitorContext::CIndirectMonitorContext(_In_ IDDCX_MONITOR monitor, CIndirectDeviceContext * device) : +CMonitorContext::CMonitorContext( + _In_ IDDCX_MONITOR monitor, CDeviceContext * device) : m_monitor(monitor), m_devContext(device) { } -CIndirectMonitorContext::~CIndirectMonitorContext() +CMonitorContext::~CMonitorContext() { UnassignSwapChain(); m_devContext->OnMonitorDestroyed(m_monitor); } -NTSTATUS CIndirectMonitorContext::AssignSwapChain( +NTSTATUS CMonitorContext::AssignSwapChain( IDDCX_SWAPCHAIN swapChain, LUID renderAdapter, HANDLE newFrameEvent) { std::lock_guard assignGuard(m_assignMutex); @@ -89,7 +92,7 @@ NTSTATUS CIndirectMonitorContext::AssignSwapChain( return STATUS_SUCCESS; } -void CIndirectMonitorContext::DetachSwapChain() +void CMonitorContext::DetachSwapChain() { // Invalidate setup in progress before waiting for m_lock. This also lets a // worker about to call SetDevice observe an unassign whose callback is @@ -115,7 +118,7 @@ void CIndirectMonitorContext::DetachSwapChain() m_devContext->OnSwapChainReleased(); } -void CIndirectMonitorContext::UnassignSwapChain() +void CMonitorContext::UnassignSwapChain() { DetachSwapChain(); } diff --git a/idd/LGIdd/CIndirectMonitorContext.h b/idd/LGIdd/display/monitor/Context.h similarity index 81% rename from idd/LGIdd/CIndirectMonitorContext.h rename to idd/LGIdd/display/monitor/Context.h index 30d58aeb..70ab31a0 100644 --- a/idd/LGIdd/CIndirectMonitorContext.h +++ b/idd/LGIdd/display/monitor/Context.h @@ -27,12 +27,12 @@ #include #include #include -#include "CIndirectDeviceContext.h" -#include "CSwapChainProcessor.h" -using namespace Microsoft::WRL; +struct CD3D11Device; +class CDeviceContext; +class CSwapChainProcessor; -class CIndirectMonitorContext +class CMonitorContext { private: IDDCX_MONITOR m_monitor; @@ -48,7 +48,7 @@ private: std::mutex m_assignMutex; std::shared_ptr m_dx11Device; - CIndirectDeviceContext * m_devContext; + CDeviceContext * m_devContext; std::unique_ptr m_swapChain; // Incremented whenever the current assignment is replaced or unassigned. @@ -59,10 +59,11 @@ private: void DetachSwapChain(); public: - CIndirectMonitorContext(_In_ IDDCX_MONITOR monitor, CIndirectDeviceContext * device); + CMonitorContext( + _In_ IDDCX_MONITOR monitor, CDeviceContext * device); + + virtual ~CMonitorContext(); - virtual ~CIndirectMonitorContext(); - NTSTATUS AssignSwapChain( IDDCX_SWAPCHAIN swapChain, LUID renderAdapter, HANDLE newFrameEvent); void UnassignSwapChain(); @@ -71,12 +72,12 @@ public: return m_assignmentGeneration.load(std::memory_order_acquire) == generation; } - CIndirectDeviceContext * GetDeviceContext() { return m_devContext; } + CDeviceContext * GetDeviceContext() { return m_devContext; } }; -struct CIndirectMonitorContextWrapper +struct CMonitorContextWrapper { - CIndirectMonitorContext* context; + CMonitorContext * context; void Cleanup() { @@ -85,4 +86,4 @@ struct CIndirectMonitorContextWrapper } }; -WDF_DECLARE_CONTEXT_TYPE(CIndirectMonitorContextWrapper); +WDF_DECLARE_CONTEXT_TYPE(CMonitorContextWrapper); diff --git a/idd/LGIdd/CPlatformInfo.cpp b/idd/LGIdd/platform/CPlatformInfo.cpp similarity index 99% rename from idd/LGIdd/CPlatformInfo.cpp rename to idd/LGIdd/platform/CPlatformInfo.cpp index ab4791f3..069f3e6a 100644 --- a/idd/LGIdd/CPlatformInfo.cpp +++ b/idd/LGIdd/platform/CPlatformInfo.cpp @@ -18,7 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CPlatformInfo.h" +#include "platform/CPlatformInfo.h" #include "CDebug.h" #include @@ -310,4 +310,4 @@ void CPlatformInfo::InitCPUInfo() } _freea(buffer); -} \ No newline at end of file +} diff --git a/idd/LGIdd/CPlatformInfo.h b/idd/LGIdd/platform/CPlatformInfo.h similarity index 99% rename from idd/LGIdd/CPlatformInfo.h rename to idd/LGIdd/platform/CPlatformInfo.h index b93a0d61..6ce5a9b9 100644 --- a/idd/LGIdd/CPlatformInfo.h +++ b/idd/LGIdd/platform/CPlatformInfo.h @@ -48,4 +48,4 @@ public: inline static int GetCoreCount() { return m_cores; } inline static int GetProcCount() { return m_procs; } inline static int GetSocketCount() { return m_sockets; } -}; \ No newline at end of file +}; diff --git a/idd/LGIdd/CPostProcessor.cpp b/idd/LGIdd/postprocess/CPostProcessor.cpp similarity index 98% rename from idd/LGIdd/CPostProcessor.cpp rename to idd/LGIdd/postprocess/CPostProcessor.cpp index e321c6ec..3bb359bc 100644 --- a/idd/LGIdd/CPostProcessor.cpp +++ b/idd/LGIdd/postprocess/CPostProcessor.cpp @@ -18,14 +18,14 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CPostProcessor.h" +#include "postprocess/CPostProcessor.h" -#include "CD3D12Device.h" +#include "d3d/CD3D12Device.h" #include "CDebug.h" -#include "effect/CColorTransformEffect.h" -#include "effect/CDownsampleEffect.h" -#include "effect/CHDR16to10Effect.h" -#include "effect/CRGB24Effect.h" +#include "postprocess/effect/CColorTransformEffect.h" +#include "postprocess/effect/CDownsampleEffect.h" +#include "postprocess/effect/CHDR16to10Effect.h" +#include "postprocess/effect/CRGB24Effect.h" #include #include diff --git a/idd/LGIdd/CPostProcessor.h b/idd/LGIdd/postprocess/CPostProcessor.h similarity index 77% rename from idd/LGIdd/CPostProcessor.h rename to idd/LGIdd/postprocess/CPostProcessor.h index ac80f037..7ea627f8 100644 --- a/idd/LGIdd/CPostProcessor.h +++ b/idd/LGIdd/postprocess/CPostProcessor.h @@ -23,15 +23,12 @@ #include #include #include -#include #include #include -struct CD3D12Device; +#include "postprocess/D12FrameFormat.h" -extern "C" { - #include "common/types.h" -} +struct CD3D12Device; using namespace Microsoft::WRL; @@ -42,46 +39,6 @@ enum class PostProcessStatus FAILED }; -struct D12ColorTransform -{ - bool matrixEnabled = false; - float matrix[3][4] = {}; - float scalar = 1.0f; - bool lutEnabled = false; - float lut[4096][4] = {}; -}; - -bool IsIdentityColorTransform(const D12ColorTransform& transform); - -struct D12FrameFormat -{ - D3D12_RESOURCE_DESC desc = {}; - unsigned dataWidth = 0; - unsigned dataHeight = 0; - unsigned pitch = 0; - unsigned width = 0; - unsigned height = 0; - FrameType format = FRAME_TYPE_INVALID; - bool hdr = false; - bool hdrPQ = false; - bool hdrMetadata = false; - uint32_t sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT; - std::shared_ptr colorTransform; - - // HDR static metadata (SMPTE ST 2086) - // Display color primaries in 0.00002 units (xy coordinates) - uint16_t displayPrimary[3][2]; - // White point in 0.00002 units - uint16_t whitePoint[2]; - // Max mastering display luminance in whole cd/m² - uint32_t maxDisplayLuminance; - // Min mastering display luminance in 0.0001 cd/m² units - uint32_t minDisplayLuminance; - // MaxCLL and MaxFALL in cd/m² - uint32_t maxContentLightLevel; - uint32_t maxFrameAverageLightLevel; -}; - class CPostProcessEffect { public: diff --git a/idd/LGIdd/postprocess/D12FrameFormat.h b/idd/LGIdd/postprocess/D12FrameFormat.h new file mode 100644 index 00000000..0f5b2bb9 --- /dev/null +++ b/idd/LGIdd/postprocess/D12FrameFormat.h @@ -0,0 +1,70 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include +#include +#include +#include + +extern "C" { + #include "common/types.h" +} + +struct D12ColorTransform +{ + bool matrixEnabled = false; + float matrix[3][4] = {}; + float scalar = 1.0f; + bool lutEnabled = false; + float lut[4096][4] = {}; +}; + +bool IsIdentityColorTransform(const D12ColorTransform& transform); + +struct D12FrameFormat +{ + D3D12_RESOURCE_DESC desc = {}; + unsigned dataWidth = 0; + unsigned dataHeight = 0; + unsigned pitch = 0; + unsigned width = 0; + unsigned height = 0; + FrameType format = FRAME_TYPE_INVALID; + bool hdr = false; + bool hdrPQ = false; + bool hdrMetadata = false; + uint32_t sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT; + std::shared_ptr colorTransform; + + // HDR static metadata (SMPTE ST 2086) + // Display color primaries in 0.00002 units (xy coordinates) + uint16_t displayPrimary[3][2]; + // White point in 0.00002 units + uint16_t whitePoint[2]; + // Max mastering display luminance in whole cd/m² + uint32_t maxDisplayLuminance; + // Min mastering display luminance in 0.0001 cd/m² units + uint32_t minDisplayLuminance; + // MaxCLL and MaxFALL in cd/m² + uint32_t maxContentLightLevel; + uint32_t maxFrameAverageLightLevel; +}; diff --git a/idd/LGIdd/effect/CColorTransformEffect.cpp b/idd/LGIdd/postprocess/effect/CColorTransformEffect.cpp similarity index 100% rename from idd/LGIdd/effect/CColorTransformEffect.cpp rename to idd/LGIdd/postprocess/effect/CColorTransformEffect.cpp diff --git a/idd/LGIdd/effect/CColorTransformEffect.h b/idd/LGIdd/postprocess/effect/CColorTransformEffect.h similarity index 100% rename from idd/LGIdd/effect/CColorTransformEffect.h rename to idd/LGIdd/postprocess/effect/CColorTransformEffect.h diff --git a/idd/LGIdd/effect/CComputeEffect.cpp b/idd/LGIdd/postprocess/effect/CComputeEffect.cpp similarity index 100% rename from idd/LGIdd/effect/CComputeEffect.cpp rename to idd/LGIdd/postprocess/effect/CComputeEffect.cpp diff --git a/idd/LGIdd/effect/CComputeEffect.h b/idd/LGIdd/postprocess/effect/CComputeEffect.h similarity index 98% rename from idd/LGIdd/effect/CComputeEffect.h rename to idd/LGIdd/postprocess/effect/CComputeEffect.h index 188dbb2a..21120131 100644 --- a/idd/LGIdd/effect/CComputeEffect.h +++ b/idd/LGIdd/postprocess/effect/CComputeEffect.h @@ -20,7 +20,7 @@ #pragma once -#include "../CPostProcessor.h" +#include "postprocess/CPostProcessor.h" #define POST_PROCESS_THREADS_STR "8" diff --git a/idd/LGIdd/effect/CDownsampleEffect.cpp b/idd/LGIdd/postprocess/effect/CDownsampleEffect.cpp similarity index 99% rename from idd/LGIdd/effect/CDownsampleEffect.cpp rename to idd/LGIdd/postprocess/effect/CDownsampleEffect.cpp index df56d001..6e91820e 100644 --- a/idd/LGIdd/effect/CDownsampleEffect.cpp +++ b/idd/LGIdd/postprocess/effect/CDownsampleEffect.cpp @@ -21,7 +21,7 @@ #include "CDownsampleEffect.h" #include "CDebug.h" -#include "../CSettings.h" +#include "config/CSettings.h" #include #include diff --git a/idd/LGIdd/effect/CDownsampleEffect.h b/idd/LGIdd/postprocess/effect/CDownsampleEffect.h similarity index 100% rename from idd/LGIdd/effect/CDownsampleEffect.h rename to idd/LGIdd/postprocess/effect/CDownsampleEffect.h diff --git a/idd/LGIdd/effect/CHDR16to10Effect.cpp b/idd/LGIdd/postprocess/effect/CHDR16to10Effect.cpp similarity index 100% rename from idd/LGIdd/effect/CHDR16to10Effect.cpp rename to idd/LGIdd/postprocess/effect/CHDR16to10Effect.cpp diff --git a/idd/LGIdd/effect/CHDR16to10Effect.h b/idd/LGIdd/postprocess/effect/CHDR16to10Effect.h similarity index 100% rename from idd/LGIdd/effect/CHDR16to10Effect.h rename to idd/LGIdd/postprocess/effect/CHDR16to10Effect.h diff --git a/idd/LGIdd/effect/CRGB24Effect.cpp b/idd/LGIdd/postprocess/effect/CRGB24Effect.cpp similarity index 99% rename from idd/LGIdd/effect/CRGB24Effect.cpp rename to idd/LGIdd/postprocess/effect/CRGB24Effect.cpp index 127a37f9..570dbd44 100644 --- a/idd/LGIdd/effect/CRGB24Effect.cpp +++ b/idd/LGIdd/postprocess/effect/CRGB24Effect.cpp @@ -21,7 +21,7 @@ #include "CRGB24Effect.h" #include "CDebug.h" -#include "../CSettings.h" +#include "config/CSettings.h" #include "common/LGMPConfig.h" #include diff --git a/idd/LGIdd/effect/CRGB24Effect.h b/idd/LGIdd/postprocess/effect/CRGB24Effect.h similarity index 100% rename from idd/LGIdd/effect/CRGB24Effect.h rename to idd/LGIdd/postprocess/effect/CRGB24Effect.h diff --git a/idd/LGIdd/transport/CFrameTransport.cpp b/idd/LGIdd/transport/CFrameTransport.cpp new file mode 100644 index 00000000..b65a671c --- /dev/null +++ b/idd/LGIdd/transport/CFrameTransport.cpp @@ -0,0 +1,1296 @@ +/** + * 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 "transport/CFrameTransport.h" + +#include "transport/CIVSHMEM.h" +#include "transport/CLGMPHost.h" +#include "CDebug.h" + +#include + +static const struct LGMPQueueConfig FRAME_QUEUE_CONFIG = +{ + LGMP_Q_FRAME, // queueID + LGMP_Q_FRAME_LEN, // numMessages + 1000 // subTimeout +}; + +static uint64_t FrameScheduleToken( + const CFrameScheduler::Schedule& schedule) +{ + return static_cast(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; +} + +CFrameTransport::CFrameTransport( + CLGMPHost& host, CIVSHMEM& ivshmem) : + m_host(host), + m_ivshmem(ivshmem) +{ +} + +CFrameTransport::~CFrameTransport() +{ + DeInit(); +} + +bool CFrameTransport::Initialize() +{ + if (m_frameQueue) + { + for (PLGMPHostQueue queue : m_frameOwnerQueue) + if (!queue) + return false; + return true; + } + + LGMP_STATUS status = m_host.CreateQueue( + FRAME_QUEUE_CONFIG, &m_frameQueue); + if (status != 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 + }; + status = m_host.CreateQueue(config, &m_frameOwnerQueue[i]); + if (status != LGMP_OK) + { + DEBUG_ERROR("lgmpHostQueueCreate Failed (Frame Owner %u): %s", + i, lgmpStatusString(status)); + return false; + } + } + + return true; +} + +void CFrameTransport::SealMemoryLayout() +{ + m_frameMemoryOffset = + m_ivshmem.GetSize() - m_host.Available(); +} + +bool CFrameTransport::Setup(size_t alignSize) +{ + // This may get called multiple times as frame buffers cannot be allocated + // until the GPU-specific alignment is known. + if (m_maxFrameSize) + return true; + + 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 = m_host.Available(); + + const size_t alignmentMask = m_alignSize - 1; + const size_t alignedFrameMemoryOffset = + (m_frameMemoryOffset + alignmentMask) & ~alignmentMask; + const size_t alignmentPadding = + alignedFrameMemoryOffset - m_frameMemoryOffset; + if (available <= alignmentPadding) + { + DEBUG_ERROR("Insufficient shared memory for frame buffers"); + return false; + } + + 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) + { + status = m_host.AllocateAligned( + (uint32_t)frameAllocationSize, (uint32_t)m_alignSize, + &m_frameMemory[i]); + if (status != LGMP_OK) + { + DEBUG_ERROR("lgmpHostMemAllocAligned Failed (Frame): %s", + lgmpStatusString(status)); + return false; + } + + m_frame[i] = + static_cast(lgmpHostMemPtr(m_frameMemory[i])); + + /** + * Put the framebuffer on the border of the next page, this is to allow + * for aligned DMA transfers by the receiver. + */ + const size_t alignOffset = alignSize - sizeof(FrameBuffer); + m_frame[i]->offset = (uint32_t)alignOffset; + m_frameBuffer[i] = reinterpret_cast( + reinterpret_cast(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 = {}; + + return true; +} + +void CFrameTransport::DeInit() +{ + 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); + lgmpHostMemFree(&m_frameMemory[i]); + m_frame[i] = nullptr; + m_frameBuffer[i] = nullptr; + } + + m_frameQueue = nullptr; + memset(m_frameOwnerQueue, 0, sizeof(m_frameOwnerQueue)); +} + +FrameMemoryLimits CFrameTransport::GetMemoryLimits() const +{ + FrameMemoryLimits limits; + limits.sharedSize = m_ivshmem.GetSize(); + limits.frameMemoryOffset = m_frameMemoryOffset; + limits.alignment = m_alignSize; + limits.maxFrameSize = m_maxFrameSize; + return limits; +} + +CFrameTransport::SubscriberSnapshot +CFrameTransport::SnapshotSubscribers() const +{ + SubscriberSnapshot snapshot; + snapshot.status = lgmpHostGetClientIDs( + m_frameQueue, snapshot.clientIDs, &snapshot.clientCount); + if (snapshot.status == LGMP_OK) + { + memcpy(snapshot.ownerClientIDs, snapshot.clientIDs, + snapshot.clientCount * sizeof(*snapshot.ownerClientIDs)); + snapshot.ownerClientCount = snapshot.clientCount; + } + + for (unsigned queueIndex = 0; + snapshot.status == LGMP_OK && queueIndex < LGMP_Q_FRAME_LEN; + ++queueIndex) + { + uint32_t queueClientIDs[LGMP_MAX_CLIENTS] = {}; + unsigned queueClientCount = 0; + snapshot.status = lgmpHostGetClientIDs( + m_frameOwnerQueue[queueIndex], queueClientIDs, &queueClientCount); + + unsigned commonCount = 0; + for (unsigned i = 0; + snapshot.status == LGMP_OK && i < snapshot.ownerClientCount; + ++i) + for (unsigned candidate = 0; + candidate < queueClientCount; ++candidate) + if (snapshot.ownerClientIDs[i] == queueClientIDs[candidate]) + { + snapshot.ownerClientIDs[commonCount++] = + snapshot.ownerClientIDs[i]; + break; + } + snapshot.ownerClientCount = commonCount; + } + + return snapshot; +} + +void CFrameTransport::FinalizeSubscribers( + const SubscriberSnapshot& snapshot, uint64_t now) +{ + if (snapshot.status == LGMP_OK) + m_frameScheduler.UpdateSubscribers( + snapshot.clientIDs, snapshot.clientCount, + snapshot.ownerClientIDs, snapshot.ownerClientCount, now); + else + DEBUG_WARN("Failed to query LGMP frame subscribers: %s", + lgmpStatusString(snapshot.status)); + + 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(); +} + +bool CFrameTransport::UpdateSchedule(uint32_t sourceClientID, + const KVMFRFrameSchedule& schedule, uint64_t now) +{ + return m_frameScheduler.UpdateSchedule(sourceClientID, schedule, now); +} + +CFrameTransport::SharedFramePostResult +CFrameTransport::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 CFrameTransport::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 CFrameTransport::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 CFrameTransport::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(queueIndex); + } + + return -1; +} + +unsigned CFrameTransport::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 CFrameTransport::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 CFrameTransport::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 CFrameTransport::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(frameIndex) == readyFrameIndex || + m_frameInFlight[frameIndex].load(std::memory_order_acquire) || + FrameBufferReferenced(frameIndex)) + continue; + + if (available < 0 || + m_frameLastPublishSequence[frameIndex] > newestPublish) + { + available = static_cast(frameIndex); + newestPublish = m_frameLastPublishSequence[frameIndex]; + } + } + + if (available >= 0 || !allowReady || readyFrameIndex < 0 || + m_frameInFlight[readyFrameIndex].load(std::memory_order_acquire) || + FrameBufferReferenced(static_cast(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. + return static_cast(readyFrameIndex); +} + +int CFrameTransport::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(frameIndex); + newestSequence = m_frameLastPublishSequence[frameIndex]; + } + } + + return newestFrame; +} + +bool CFrameTransport::FrameBufferAvailable( + const CFrameScheduler::Schedule& schedule, + bool allowReadyReplacement) +{ + if (!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 CFrameTransport::ProcessFrameQueue() +{ + if (!m_host.IsInitialized()) + return; + + const LGMP_STATUS status = m_host.Process(); + + if (status != LGMP_OK && status != LGMP_ERR_CORRUPTED) + DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status)); + + if (status == LGMP_OK) + ProcessFrameDeliveries(); +} + +bool CFrameTransport::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 CFrameTransport::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(frameIndex), 0, now); + ReleaseSRWLockExclusive(&m_framePublishLock); + retry = result == SHARED_FRAME_FAILED; + return result == SHARED_FRAME_POSTED; +} + +PreparedFrameBuffer CFrameTransport::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(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(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 CFrameTransport::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(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(frameIndex) : -1; + m_submittedFrameIndex.store( + static_cast(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 CFrameTransport::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(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(frameIndex)); + if (ownerQueueIndex < 0) + { + const bool published = PostSharedOwnerFrame( + static_cast(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(ownerQueueIndex); + OwnerDelivery& owner = m_ownerDelivery[queueIndex]; + owner.token = scheduleToken; + owner.clientID = schedule.clientID; + owner.frameIndex = static_cast(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 CFrameTransport::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 CFrameTransport::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 CFrameTransport::ObserveFrame(uint64_t now) +{ + m_frameScheduler.ObserveFrame(now); +} + +void CFrameTransport::ForceFrame() +{ + m_frameScheduler.ForceFrame(); +} + +bool CFrameTransport::GetPublishTarget(uint64_t now, + uint64_t& target, CFrameScheduler::Schedule& schedule, bool& periodic, + bool& republish) +{ + return m_frameScheduler.GetPublishTarget( + now, target, schedule, periodic, republish); +} + +void CFrameTransport::FrameMissed( + const CFrameScheduler::Schedule& schedule, uint64_t now, bool periodic) +{ + m_frameScheduler.FrameMissed(schedule, now, periodic); +} + +void CFrameTransport::FrameSuperseded() +{ + m_frameScheduler.FrameSuperseded(); +} + +void CFrameTransport::TryRecordFrameTiming(uint64_t duration) +{ + m_frameScheduler.TryRecordFrameTiming(duration); +} + +void CFrameTransport::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(frameIndex)) + m_deferredOwnerFrameIndex = -1; + m_frameInFlight[frameIndex].store(false, std::memory_order_release); + ReleaseSRWLockExclusive(&m_framePublishLock); +} + +void CFrameTransport::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 CFrameTransport::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(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(frameIndex), std::memory_order_release); + } + m_frameInFlight[frameIndex].store(false, std::memory_order_release); + ReleaseSRWLockExclusive(&m_framePublishLock); +} + +void CFrameTransport::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 CFrameTransport::WriteFrameBuffer(unsigned frameIndex, void * src, + size_t offset, size_t len, bool setWritePos) const +{ + FrameBuffer * fb = m_frameBuffer[frameIndex]; + + memcpy( + reinterpret_cast( + reinterpret_cast(fb->data) + offset), + reinterpret_cast( + reinterpret_cast(src) + offset), + len); + + if (setWritePos) + fb->wp = (uint32_t)(offset + len); +} + +void CFrameTransport::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(src) + offset; + for (unsigned row = 0; row < rows; ++row) + { + memcpy(dst, source, rowBytes); + dst += pitch; + source += pitch; + } +} + +void CFrameTransport::FinalizeFrameBuffer(unsigned frameIndex) const +{ + const KVMFRFrame * frame = m_frame[frameIndex]; + FrameBuffer * fb = m_frameBuffer[frameIndex]; + fb->wp = frame->dataHeight * frame->pitch; +} diff --git a/idd/LGIdd/transport/CFrameTransport.h b/idd/LGIdd/transport/CFrameTransport.h new file mode 100644 index 00000000..00132ae4 --- /dev/null +++ b/idd/LGIdd/transport/CFrameTransport.h @@ -0,0 +1,209 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include +#include +#include + +extern "C" { + #include "lgmp/host.h" +} + +#include "capture/CFrameScheduler.h" +#include "capture/FrameBufferTypes.h" +#include "common/KVMFR.h" +#include "postprocess/D12FrameFormat.h" +#include "transport/FrameMemoryLimits.h" + +class CIVSHMEM; +class CLGMPHost; + +class CFrameTransport +{ +public: + struct SubscriberSnapshot + { + uint32_t clientIDs [LGMP_MAX_CLIENTS] = {}; + uint32_t ownerClientIDs[LGMP_MAX_CLIENTS] = {}; + unsigned clientCount = 0; + unsigned ownerClientCount = 0; + LGMP_STATUS status = LGMP_OK; + }; + +private: + enum SharedFramePostResult + { + SHARED_FRAME_FAILED, + SHARED_FRAME_IDLE, + SHARED_FRAME_POSTED, + }; + + struct FrameDelivery + { + uint64_t sharedOwnerToken = 0; + unsigned ownerQueueMask = 0; + uint32_t sharedOwnerClientID = 0; + bool sharedOwnerPending = false; + bool sharedPending = false; + }; + + struct OwnerDelivery + { + uint64_t token = 0; + uint32_t clientID = 0; + unsigned frameIndex = 0; + bool active = false; + }; + + CLGMPHost& m_host; + CIVSHMEM& m_ivshmem; + + PLGMPHostQueue m_frameQueue = nullptr; + PLGMPHostQueue m_frameOwnerQueue[LGMP_Q_FRAME_LEN] = {}; + + CFrameScheduler m_frameScheduler; + + size_t m_alignSize = 0; + size_t m_frameMemoryOffset = 0; + size_t m_maxFrameSize = 0; + + // LGMP publication precedes copy completion. Replay only completed frames; + // the deferred index tracks the newest frame still owed to the owner. + std::atomic m_submittedFrameIndex = -1; + std::atomic m_readyFrameIndex = -1; + LONG m_deferredOwnerFrameIndex = -1; + std::atomic m_frameInFlight[LGMP_Q_FRAME_BUFFER_LEN] = {}; + bool m_frameCompleted[LGMP_Q_FRAME_BUFFER_LEN] = {}; + SRWLOCK m_framePublishLock = SRWLOCK_INIT; + uint64_t m_framePublishSequence = 0; + uint64_t m_frameLastPublishSequence[LGMP_Q_FRAME_BUFFER_LEN] = {}; + + FrameDelivery m_frameDelivery[LGMP_Q_FRAME_BUFFER_LEN] = {}; + OwnerDelivery m_ownerDelivery[LGMP_Q_FRAME_LEN] = {}; + uint32_t m_formatVer = 0; + uint32_t m_frameSerial = 0; + PLGMPMemory m_frameMemory[LGMP_Q_FRAME_BUFFER_LEN] = {}; + KVMFRFrame * m_frame [LGMP_Q_FRAME_BUFFER_LEN] = {}; + FrameBuffer * m_frameBuffer[LGMP_Q_FRAME_BUFFER_LEN] = {}; + + unsigned m_width = 0; + unsigned m_height = 0; + unsigned m_frameWidth = 0; + unsigned m_frameHeight = 0; + unsigned m_pitch = 0; + DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN; + FrameType m_frameType = FRAME_TYPE_INVALID; + + // Previous HDR metadata used to detect changes for formatVer bumps. + uint16_t m_lastHDRDisplayPrimary[3][2] = {}; + uint16_t m_lastHDRWhitePoint[2] = {}; + uint32_t m_lastHDRMaxDisplayLuminance = 0; + uint32_t m_lastHDRMinDisplayLuminance = 0; + uint32_t m_lastHDRMaxContentLightLevel = 0; + uint32_t m_lastHDRMaxFrameAverageLightLevel = 0; + uint32_t m_lastSDRWhiteLevel = 0; + bool m_lastHDRActive = false; + bool m_lastHDRMetadata = false; + + void ProcessFrameDeliveries(); + bool FrameBufferReferenced(unsigned frameIndex) const; + int FindAvailableFrameBuffer(bool allowReady) const; + int FindNewestCompletedFrame(unsigned excludeFrameIndex) const; + int FindAvailableOwnerQueue(unsigned preferredIndex) const; + unsigned CountOwnerDeliveries(uint32_t clientID) const; + bool HasMatchingOwnerDelivery(uint32_t clientID, unsigned frameIndex, + uint64_t token) const; + SharedFramePostResult PostSharedFrame(unsigned frameIndex, + uint32_t excludeClientID, uint64_t now); + bool PostSharedOwnerFrame(unsigned frameIndex, + const CFrameScheduler::Schedule& schedule); + +public: + CFrameTransport(CLGMPHost& host, CIVSHMEM& ivshmem); + ~CFrameTransport(); + + CFrameTransport(const CFrameTransport&) = delete; + CFrameTransport& operator=(const CFrameTransport&) = delete; + + bool Initialize(); + void SealMemoryLayout(); + bool Setup(size_t alignSize); + void DeInit(); + + FrameMemoryLimits GetMemoryLimits() const; + size_t GetMaxFrameSize() const { return m_maxFrameSize; } + CIVSHMEM& GetIVSHMEM() { return m_ivshmem; } + + SubscriberSnapshot SnapshotSubscribers() const; + void FinalizeSubscribers( + const SubscriberSnapshot& snapshot, uint64_t now); + bool UpdateSchedule(uint32_t sourceClientID, + const KVMFRFrameSchedule& schedule, uint64_t now); + + bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule, + bool allowReadyReplacement = true); + bool HasPublishedFrame() const + { + return m_readyFrameIndex.load(std::memory_order_acquire) >= 0; + } + void ProcessFrameQueue(); + bool GetSharedFrameTarget(uint64_t now, uint64_t& target); + bool ReplaySharedFrame(uint64_t now, bool& retry); + PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, + const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, + const RECT * dirtyRects, unsigned nbDirtyRects, + const CFrameScheduler::Schedule& schedule, + bool allowReadyReplacement = true); + bool PublishFrameBuffer(unsigned frameIndex, + const CFrameScheduler::Schedule& schedule, bool& deliveredToOwner); + bool RepublishFrameBuffer(const CFrameScheduler::Schedule& schedule); + bool TryFrameSubmitted(unsigned frameIndex, + const CFrameScheduler::Schedule& schedule); + void CommitFrameBuffer(unsigned frameIndex, + const CFrameScheduler::Schedule& schedule, bool periodic, + bool deliveredToOwner); + void AbortFrameBuffer(unsigned frameIndex); + void FailFrameBuffer(unsigned frameIndex); + void CompleteFrameBuffer(unsigned frameIndex, bool succeeded); + void 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); + void WriteFrameBuffer(unsigned frameIndex, void * src, size_t offset, + size_t len, bool setWritePos) const; + void WriteFrameBufferRows(unsigned frameIndex, void * src, + size_t offset, size_t rowBytes, size_t pitch, unsigned rows) const; + void FinalizeFrameBuffer(unsigned frameIndex) const; + + void ObserveFrame(uint64_t now); + void ForceFrame(); + bool GetPublishTarget(uint64_t now, uint64_t& target, + CFrameScheduler::Schedule& schedule, bool& periodic, bool& republish); + void FrameMissed(const CFrameScheduler::Schedule& schedule, + uint64_t now, bool periodic); + void FrameSuperseded(); + HANDLE GetFrameScheduleEvent() const + { + return m_frameScheduler.GetWakeEvent(); + } + void TryRecordFrameTiming(uint64_t duration); +}; diff --git a/idd/LGIdd/CIVSHMEM.cpp b/idd/LGIdd/transport/CIVSHMEM.cpp similarity index 99% rename from idd/LGIdd/CIVSHMEM.cpp rename to idd/LGIdd/transport/CIVSHMEM.cpp index a7a66f98..a9de4fd1 100644 --- a/idd/LGIdd/CIVSHMEM.cpp +++ b/idd/LGIdd/transport/CIVSHMEM.cpp @@ -18,7 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CIVSHMEM.h" +#include "transport/CIVSHMEM.h" #include #include @@ -205,4 +205,4 @@ void CIVSHMEM::Close() m_size = 0; m_mem = nullptr; -} \ No newline at end of file +} diff --git a/idd/LGIdd/CIVSHMEM.h b/idd/LGIdd/transport/CIVSHMEM.h similarity index 100% rename from idd/LGIdd/CIVSHMEM.h rename to idd/LGIdd/transport/CIVSHMEM.h diff --git a/idd/LGIdd/transport/CLGMPControl.cpp b/idd/LGIdd/transport/CLGMPControl.cpp new file mode 100644 index 00000000..01a9f148 --- /dev/null +++ b/idd/LGIdd/transport/CLGMPControl.cpp @@ -0,0 +1,301 @@ +/** + * 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 "transport/CLGMPControl.h" + +#include "CDebug.h" + +#include + +#include + +static const uint32_t MAX_POINTER_SIZE = + (uint32_t)(sizeof(KVMFRCursor) + (512 * 512 * 4)); + +static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG = +{ + LGMP_Q_POINTER, //queueID + LGMP_Q_POINTER_LEN, //numMesages + 1000 //subTimeout +}; + +CLGMPControl::~CLGMPControl() +{ + DeInit(); +} + +bool CLGMPControl::Initialize() +{ + if (m_pointerQueue) + return true; + + LGMP_STATUS status; + if ((status = m_host.CreateQueue( + 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 = m_host.Allocate( + 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 = m_host.Allocate( + 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 = m_host.Allocate( + 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)); + } + + return true; +} + +void CLGMPControl::DeInit() +{ + 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]); + + m_pointerQueue = nullptr; + m_pointerShape = nullptr; + m_pointerMemoryIndex = 0; + m_pointerShapeIndex = 0; + m_pointerTransformIndex = 0; +} + +LGMP_STATUS CLGMPControl::ReadDataWithSource(void * data, size_t * size, + uint32_t * sourceClientID) +{ + return lgmpHostReadDataWithSource( + m_pointerQueue, data, size, sourceClientID); +} + +LGMP_STATUS CLGMPControl::AckData() +{ + return lgmpHostAckData(m_pointerQueue); +} + +bool CLGMPControl::HasNewSubscribers() +{ + return lgmpHostQueueNewSubs(m_pointerQueue) != 0; +} + +void CLGMPControl::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 CLGMPControl::SetColorTransform( + std::shared_ptr transform) +{ + AcquireSRWLockExclusive(&m_colorTransformLock); + m_colorTransform = std::move(transform); + ReleaseSRWLockExclusive(&m_colorTransformLock); + SendColorTransform(); +} + +std::shared_ptr +CLGMPControl::GetColorTransform() const +{ + AcquireSRWLockShared(&m_colorTransformLock); + std::shared_ptr transform = m_colorTransform; + ReleaseSRWLockShared(&m_colorTransformLock); + return transform; +} + +void CLGMPControl::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 CLGMPControl::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; + } +} + +void CLGMPControl::ResendState() +{ + ResendCursor(); + SendColorTransform(); +} diff --git a/idd/LGIdd/transport/CLGMPControl.h b/idd/LGIdd/transport/CLGMPControl.h new file mode 100644 index 00000000..10e28729 --- /dev/null +++ b/idd/LGIdd/transport/CLGMPControl.h @@ -0,0 +1,82 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "transport/CLGMPHost.h" +#include "postprocess/D12FrameFormat.h" + +#include "common/KVMFR.h" + +#include +#include +#include + +#include + +class CLGMPControl +{ +private: + static constexpr int POINTER_SHAPE_BUFFERS = 3; + static constexpr int COLOR_TRANSFORM_BUFFERS = 3; + + CLGMPHost& m_host; + + PLGMPHostQueue m_pointerQueue = nullptr; + PLGMPMemory m_pointerMemory[LGMP_Q_POINTER_LEN] = {}; + PLGMPMemory m_pointerShapeMemory[POINTER_SHAPE_BUFFERS] = {}; + PLGMPMemory m_pointerTransformMemory[COLOR_TRANSFORM_BUFFERS] = {}; + PLGMPMemory m_pointerShape = nullptr; + int m_pointerMemoryIndex = 0; + int m_pointerShapeIndex = 0; + int m_pointerTransformIndex = 0; + bool m_cursorVisible = false; + int m_cursorX = 0; + int m_cursorY = 0; + + mutable SRWLOCK m_colorTransformLock = SRWLOCK_INIT; + std::shared_ptr m_colorTransform; + + void SendColorTransform(); + void ResendCursor(); + +public: + explicit CLGMPControl(CLGMPHost& host) : + m_host(host) {} + ~CLGMPControl(); + + CLGMPControl(const CLGMPControl&) = delete; + CLGMPControl& operator=(const CLGMPControl&) = delete; + + bool Initialize(); + void DeInit(); + + LGMP_STATUS ReadDataWithSource(void * data, size_t * size, + uint32_t * sourceClientID); + LGMP_STATUS AckData(); + bool HasNewSubscribers(); + + void SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, const BYTE * data, + UINT sdrWhiteLevel); + void SetColorTransform( + std::shared_ptr transform); + std::shared_ptr GetColorTransform() const; + void ResendState(); +}; diff --git a/idd/LGIdd/transport/CLGMPHost.cpp b/idd/LGIdd/transport/CLGMPHost.cpp new file mode 100644 index 00000000..1c437400 --- /dev/null +++ b/idd/LGIdd/transport/CLGMPHost.cpp @@ -0,0 +1,165 @@ +/** + * 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 "transport/CLGMPHost.h" + +#include "transport/CIVSHMEM.h" +#include "platform/CPlatformInfo.h" +#include "CDebug.h" +#include "VersionInfo.h" + +#include "common/KVMFR.h" + +#include +#include + +#include +#include + +CLGMPHost::~CLGMPHost() +{ + DeInit(); +} + +bool CLGMPHost::Initialize(CIVSHMEM& ivshmem) +{ + if (m_host) + 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(&kvmfr), sizeof(kvmfr)); + } + + { + const std::string & model = CPlatformInfo::GetCPUModel(); + + KVMFRRecord_VMInfo * vmInfo = static_cast(calloc(1, sizeof(*vmInfo))); + if (!vmInfo) + { + DEBUG_ERROR("Failed to allocate KVMFRRecord_VMInfo"); + return false; + } + vmInfo->cpus = static_cast(CPlatformInfo::GetProcCount ()); + vmInfo->cores = static_cast(CPlatformInfo::GetCoreCount ()); + vmInfo->sockets = static_cast(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(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(record ), sizeof(*record)); + ss.write(reinterpret_cast(vmInfo ), sizeof(*vmInfo)); + ss.write(reinterpret_cast(model.c_str()), model.length() + 1); + } + + { + KVMFRRecord_OSInfo * osInfo = static_cast(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(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(record), sizeof(*record)); + ss.write(reinterpret_cast(osInfo), sizeof(*osInfo)); + ss.write(reinterpret_cast(osName.c_str()), osName.length() + 1); + } + + LGMP_STATUS status; + std::string udata = ss.str(); + + if ((status = lgmpHostInit(ivshmem.GetMem(), (uint32_t)ivshmem.GetSize(), + &m_host, (uint32_t)udata.size(), (uint8_t*)&udata[0])) != LGMP_OK) + { + DEBUG_ERROR("lgmpHostInit Failed: %s", lgmpStatusString(status)); + return false; + } + + return true; +} + +void CLGMPHost::DeInit() +{ + if (m_host) + lgmpHostFree(&m_host); +} + +LGMP_STATUS CLGMPHost::Process() +{ + AcquireSRWLockExclusive(&m_processLock); + const LGMP_STATUS status = lgmpHostProcess(m_host); + ReleaseSRWLockExclusive(&m_processLock); + return status; +} + +LGMP_STATUS CLGMPHost::CreateQueue( + const struct LGMPQueueConfig& config, PLGMPHostQueue * queue) +{ + return lgmpHostQueueNew(m_host, config, queue); +} + +LGMP_STATUS CLGMPHost::Allocate(uint32_t size, PLGMPMemory * memory) +{ + return lgmpHostMemAlloc(m_host, size, memory); +} + +LGMP_STATUS CLGMPHost::AllocateAligned(uint32_t size, + uint32_t alignment, PLGMPMemory * memory) +{ + return lgmpHostMemAllocAligned(m_host, size, alignment, memory); +} + +size_t CLGMPHost::Available() const +{ + return lgmpHostMemAvail(m_host); +} diff --git a/idd/LGIdd/transport/CLGMPHost.h b/idd/LGIdd/transport/CLGMPHost.h new file mode 100644 index 00000000..55eac167 --- /dev/null +++ b/idd/LGIdd/transport/CLGMPHost.h @@ -0,0 +1,59 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include + +#include +#include + +extern "C" { + #include "lgmp/host.h" +} + +class CIVSHMEM; + +class CLGMPHost +{ +private: + PLGMPHost m_host = nullptr; + SRWLOCK m_processLock = SRWLOCK_INIT; + +public: + CLGMPHost() = default; + ~CLGMPHost(); + + CLGMPHost(const CLGMPHost&) = delete; + CLGMPHost& operator=(const CLGMPHost&) = delete; + + bool Initialize(CIVSHMEM& ivshmem); + void DeInit(); + + bool IsInitialized() const { return m_host != nullptr; } + LGMP_STATUS Process(); + + LGMP_STATUS CreateQueue(const struct LGMPQueueConfig& config, + PLGMPHostQueue * queue); + LGMP_STATUS Allocate(uint32_t size, PLGMPMemory * memory); + LGMP_STATUS AllocateAligned(uint32_t size, uint32_t alignment, + PLGMPMemory * memory); + size_t Available() const; +}; diff --git a/idd/LGIdd/CPipeServer.cpp b/idd/LGIdd/transport/CPipeServer.cpp similarity index 98% rename from idd/LGIdd/CPipeServer.cpp rename to idd/LGIdd/transport/CPipeServer.cpp index fcb6e03c..9a2d0db7 100644 --- a/idd/LGIdd/CPipeServer.cpp +++ b/idd/LGIdd/transport/CPipeServer.cpp @@ -18,9 +18,9 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "CPipeServer.h" +#include "transport/CPipeServer.h" #include "CDebug.h" -#include "CIndirectDeviceContext.h" +#include "display/device/CDeviceContext.h" CPipeServer g_pipe; @@ -266,7 +266,7 @@ void CPipeServer::HandleReloadSettings() ReleaseSRWLockShared(&m_deviceContextLock); } -void CPipeServer::SetDeviceContext(CIndirectDeviceContext* context) +void CPipeServer::SetDeviceContext(CDeviceContext * context) { AcquireSRWLockExclusive(&m_deviceContextLock); m_deviceContext = context; diff --git a/idd/LGIdd/CPipeServer.h b/idd/LGIdd/transport/CPipeServer.h similarity index 90% rename from idd/LGIdd/CPipeServer.h rename to idd/LGIdd/transport/CPipeServer.h index 861371f9..7e1f5138 100644 --- a/idd/LGIdd/CPipeServer.h +++ b/idd/LGIdd/transport/CPipeServer.h @@ -32,7 +32,7 @@ using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace Microsoft::WRL::Wrappers::HandleTraits; -class CIndirectDeviceContext; +class CDeviceContext; class CPipeServer { @@ -45,8 +45,8 @@ class CPipeServer bool m_running = false; bool m_connected = false; - SRWLOCK m_deviceContextLock = SRWLOCK_INIT; - CIndirectDeviceContext* m_deviceContext = nullptr; + SRWLOCK m_deviceContextLock = SRWLOCK_INIT; + CDeviceContext * m_deviceContext = nullptr; void _DeInit(); @@ -63,7 +63,7 @@ class CPipeServer bool Init(); void DeInit(); - void SetDeviceContext(CIndirectDeviceContext* context); + void SetDeviceContext(CDeviceContext * context); void SetCursorPos(uint32_t x, uint32_t y); void SetDisplayMode( diff --git a/idd/LGIdd/transport/FrameMemoryLimits.h b/idd/LGIdd/transport/FrameMemoryLimits.h new file mode 100644 index 00000000..dbe0b7bc --- /dev/null +++ b/idd/LGIdd/transport/FrameMemoryLimits.h @@ -0,0 +1,31 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include + +struct FrameMemoryLimits +{ + uint64_t sharedSize = 0; + uint64_t frameMemoryOffset = 0; + uint64_t alignment = 0; + uint64_t maxFrameSize = 0; +}; diff --git a/idd/LGIdd/util/CSRWLock.h b/idd/LGIdd/util/CSRWLock.h new file mode 100644 index 00000000..8eaabeec --- /dev/null +++ b/idd/LGIdd/util/CSRWLock.h @@ -0,0 +1,63 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include + +class CSRWSharedLock +{ +private: + SRWLOCK * m_lock; + +public: + explicit CSRWSharedLock(SRWLOCK * lock) : m_lock(lock) + { + AcquireSRWLockShared(m_lock); + } + + ~CSRWSharedLock() + { + ReleaseSRWLockShared(m_lock); + } + + CSRWSharedLock(const CSRWSharedLock&) = delete; + CSRWSharedLock& operator=(const CSRWSharedLock&) = delete; +}; + +class CSRWExclusiveLock +{ +private: + SRWLOCK * m_lock; + +public: + explicit CSRWExclusiveLock(SRWLOCK * lock) : m_lock(lock) + { + AcquireSRWLockExclusive(m_lock); + } + + ~CSRWExclusiveLock() + { + ReleaseSRWLockExclusive(m_lock); + } + + CSRWExclusiveLock(const CSRWExclusiveLock&) = delete; + CSRWExclusiveLock& operator=(const CSRWExclusiveLock&) = delete; +};