Files
LookingGlass/idd/LGIdd/display/CDisplayConfiguration.cpp
Geoffrey McRae 6725133375 [idd] transport: abstract LGMP implementation
Introduce transport, frame, and control interfaces with an LGMP factory
backend.

Move LGMP and IVSHMEM implementation details under transport/lgmp and
expose direct frame-buffer memory through a neutral capability.
2026-08-07 17:53:00 +10:00

496 lines
14 KiB
C++

/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "display/CDisplayConfiguration.h"
#include "CDebug.h"
#include "util/CSRWLock.h"
#include <d3d12.h>
#include <iterator>
#include <utility>
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& requiredSize)
{
frameSize = 0;
requiredSize = 0;
if (!alignment || !limits.frameMemoryOffset || !limits.bufferCount ||
!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;
requiredSize = frameMemoryStart +
frameAllocationSize * limits.bufferCount;
return true;
}
uint32_t CDisplayConfiguration::RecommendedMemorySizeMiB(
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 requiredMemorySize;
if (!GetResolutionMemoryRequirements(configuredMode.width,
configuredMode.height, alignment, limits, frameSize,
requiredMemorySize))
{
DEBUG_WARN("Filtering invalid %s mode %ux%u@%.3f",
configuredMode.extraMode ? "extra" : "configured",
configuredMode.width, configuredMode.height,
configuredMode.refreshMilliHz / 1000.0);
continue;
}
if (requiredMemorySize > limits.capacity)
{
DEBUG_WARN(
"Filtering %s mode %ux%u@%.3f: requires %llu bytes of transport memory, only %llu bytes are available",
configuredMode.extraMode ? "extra" : "configured",
configuredMode.width, configuredMode.height,
configuredMode.refreshMilliHz / 1000.0,
(unsigned long long)requiredMemorySize,
(unsigned long long)limits.capacity);
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 transport memory");
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 requiredMemorySize;
if (!GetResolutionMemoryRequirements(width, height, limits.alignment,
limits, frameSize, requiredMemorySize))
{
DEBUG_WARN("Ignoring invalid resolution request: %ux%u", width, height);
return result;
}
if (requiredMemorySize > limits.capacity)
{
result.status = ResolutionStatus::TOO_LARGE;
result.requiredMiB = RecommendedMemorySizeMiB(requiredMemorySize);
DEBUG_WARN(
"Refusing resolution %ux%u: frame requires %llu bytes, only %llu bytes are available; transport memory must be at least %u MiB",
width, height,
(unsigned long long)frameSize,
(unsigned long long)limits.maxFrameSize,
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