mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 16:51:31 +00:00
[idd] project: organize driver sources by responsibility
Group the IDD sources and Visual Studio filters by subsystem. Split the device and swap-chain implementations into focused units, rename the context classes, and reduce header coupling.
This commit is contained in:
496
idd/LGIdd/display/CDisplayConfiguration.cpp
Normal file
496
idd/LGIdd/display/CDisplayConfiguration.cpp
Normal file
@@ -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 <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& 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
|
||||
113
idd/LGIdd/display/CDisplayConfiguration.h
Normal file
113
idd/LGIdd/display/CDisplayConfiguration.h
Normal file
@@ -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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
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<BYTE> 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
|
||||
};
|
||||
606
idd/LGIdd/display/CEdid.cpp
Normal file
606
idd/LGIdd/display/CEdid.cpp
Normal file
@@ -0,0 +1,606 @@
|
||||
/**
|
||||
* 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/CEdid.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
static const UINT EDID_BLOCK_SIZE = 128;
|
||||
static const UINT EDID_DTD_SIZE = 18;
|
||||
|
||||
static const UINT EDID_STANDARD_TIMING_COUNT = 8;
|
||||
static const UINT EDID_BASE_DESCRIPTOR_COUNT = 4;
|
||||
static const UINT EDID_BASE_DETAILED_TIMING_COUNT = 3;
|
||||
static const UINT EDID_BASE_MONITOR_NAME_DESCRIPTOR_INDEX = 3;
|
||||
|
||||
static const UINT CTA_HEADER_SIZE = 4;
|
||||
static const UINT CTA_DATA_BLOCK_MAX_PAYLOAD_SIZE = 31;
|
||||
|
||||
static const BYTE EDID_HEADER[8] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
|
||||
|
||||
static const WORD EDID_MANUFACTURER_ID_LGD = 0x30e4;
|
||||
static const WORD EDID_PRODUCT_CODE = 0x1ddd;
|
||||
static const BYTE EDID_SERIAL_NUMBER[4] = { 0x01, 0x00, 0x00, 0x00 };
|
||||
|
||||
static const BYTE EDID_MANUFACTURE_WEEK = 1;
|
||||
static const BYTE EDID_MANUFACTURE_YEAR_2026 = 36; // 1990 + 36 = 2026
|
||||
static const BYTE EDID_VERSION = 1;
|
||||
static const BYTE EDID_REVISION = 4;
|
||||
|
||||
static const BYTE EDID_VIDEO_INPUT_DIGITAL_8BPC = 0xa0;
|
||||
static const BYTE EDID_VIDEO_INPUT_DIGITAL_10BPC = 0xb0;
|
||||
static const BYTE EDID_DISPLAY_GAMMA_2_2 = 0x78;
|
||||
static const BYTE EDID_FEATURES_PREFERRED_TIMING_RGB = 0x0a;
|
||||
static const BYTE EDID_FEATURES_PREFERRED_TIMING_SRGB = 0x0e;
|
||||
|
||||
static const BYTE EDID_STANDARD_TIMING_UNUSED_X = 0x01;
|
||||
static const BYTE EDID_STANDARD_TIMING_UNUSED_AR_REFRESH = 0x01;
|
||||
|
||||
static const BYTE EDID_DESCRIPTOR_MONITOR_NAME = 0xfc;
|
||||
static const BYTE EDID_DTD_FLAGS_DIGITAL_SEPARATE_SYNC_POSITIVE = 0x1e;
|
||||
|
||||
static const BYTE CTA_EXTENSION_TAG = 0x02;
|
||||
static const BYTE CTA_REVISION = 0x03;
|
||||
|
||||
static const BYTE CTA_DATA_BLOCK_TAG_EXTENDED = 0x07;
|
||||
static const BYTE CTA_DATA_BLOCK_LENGTH_MASK = 0x1f;
|
||||
|
||||
static const BYTE CTA_EXTENDED_TAG_COLORIMETRY = 0x05;
|
||||
static const BYTE CTA_EXTENDED_TAG_HDR_STATIC_METADATA = 0x06;
|
||||
|
||||
static const BYTE CTA_HDR_EOTF_TRADITIONAL_SDR = (BYTE)(1 << 0);
|
||||
static const BYTE CTA_HDR_EOTF_SMPTE_ST_2084 = (BYTE)(1 << 2);
|
||||
static const BYTE CTA_HDR_STATIC_METADATA_TYPE_1 = (BYTE)(1 << 0);
|
||||
// The virtual display is a transport rather than a physical light-emitting
|
||||
// device. Advertise the complete PQ range so Windows preserves HDR content
|
||||
// for the real host display instead of mapping it to an arbitrary virtual
|
||||
// peak or frame-average limit. The maximum values encode approximately
|
||||
// 10,000 cd/m^2, while zero leaves the nonexistent physical black level
|
||||
// unspecified.
|
||||
static const BYTE CTA_HDR_DESIRED_MAX_LUMINANCE = 245;
|
||||
static const BYTE CTA_HDR_DESIRED_MAX_FRAME_AVG_LUMINANCE = 245;
|
||||
static const BYTE CTA_HDR_DESIRED_MIN_LUMINANCE = 0;
|
||||
|
||||
static const BYTE CTA_COLORIMETRY_BT2020_RGB = (BYTE)(1 << 7);
|
||||
|
||||
// Keep the EDID mode list independent of the configured and dynamically
|
||||
// requested modes. Windows uses the EDID as part of the monitor identity, so
|
||||
// changing these timings at runtime can make it treat the IDD as a new monitor.
|
||||
static const CSettings::DisplayMode EDID_DISPLAY_MODES[] =
|
||||
{
|
||||
{ 1024, 768, 60000, true , false },
|
||||
{ 800, 600, 60000, false, false }
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EdidLe16
|
||||
{
|
||||
BYTE lo;
|
||||
BYTE hi;
|
||||
};
|
||||
|
||||
struct EdidBe16
|
||||
{
|
||||
BYTE hi;
|
||||
BYTE lo;
|
||||
};
|
||||
|
||||
struct EdidStandardTiming
|
||||
{
|
||||
BYTE horizontalActivePixels;
|
||||
BYTE aspectRatioAndRefreshRate;
|
||||
};
|
||||
|
||||
struct EdidDetailedTimingDescriptor
|
||||
{
|
||||
EdidLe16 pixelClock10KHz;
|
||||
|
||||
BYTE hActiveLo;
|
||||
BYTE hBlankLo;
|
||||
BYTE hActiveBlankHi;
|
||||
|
||||
BYTE vActiveLo;
|
||||
BYTE vBlankLo;
|
||||
BYTE vActiveBlankHi;
|
||||
|
||||
BYTE hFrontPorchLo;
|
||||
BYTE hSyncPulseWidthLo;
|
||||
BYTE vFrontPorchSyncPulseWidthLo;
|
||||
BYTE syncPorchPulseWidthHi;
|
||||
|
||||
BYTE imageWidthMmLo;
|
||||
BYTE imageHeightMmLo;
|
||||
BYTE imageSizeMmHi;
|
||||
|
||||
BYTE hBorder;
|
||||
BYTE vBorder;
|
||||
BYTE flags;
|
||||
};
|
||||
|
||||
struct EdidMonitorNameDescriptor
|
||||
{
|
||||
EdidLe16 pixelClock;
|
||||
BYTE reserved0;
|
||||
BYTE descriptorTag;
|
||||
BYTE reserved1;
|
||||
char name[13];
|
||||
};
|
||||
|
||||
union EdidDescriptor
|
||||
{
|
||||
EdidDetailedTimingDescriptor detailedTiming;
|
||||
EdidMonitorNameDescriptor monitorName;
|
||||
BYTE raw[EDID_DTD_SIZE];
|
||||
};
|
||||
|
||||
struct EdidBaseBlock
|
||||
{
|
||||
BYTE header[8];
|
||||
|
||||
EdidBe16 manufacturerId;
|
||||
EdidLe16 productCode;
|
||||
BYTE serialNumber[4];
|
||||
|
||||
BYTE manufactureWeek;
|
||||
BYTE manufactureYear;
|
||||
BYTE version;
|
||||
BYTE revision;
|
||||
|
||||
BYTE videoInputDefinition;
|
||||
BYTE horizontalSizeCm;
|
||||
BYTE verticalSizeCm;
|
||||
BYTE displayGamma;
|
||||
BYTE supportedFeatures;
|
||||
|
||||
BYTE chromaticityCoordinates[10];
|
||||
BYTE establishedTimings[3];
|
||||
EdidStandardTiming standardTimings[EDID_STANDARD_TIMING_COUNT];
|
||||
|
||||
EdidDescriptor descriptors[EDID_BASE_DESCRIPTOR_COUNT];
|
||||
|
||||
BYTE extensionBlockCount;
|
||||
BYTE checksum;
|
||||
};
|
||||
|
||||
struct CtaDataBlockHeader
|
||||
{
|
||||
BYTE value;
|
||||
};
|
||||
|
||||
struct CtaExtensionBlock
|
||||
{
|
||||
BYTE tag;
|
||||
BYTE revision;
|
||||
BYTE dtdOffset;
|
||||
BYTE flags;
|
||||
BYTE payload[EDID_BLOCK_SIZE - CTA_HEADER_SIZE - 1];
|
||||
BYTE checksum;
|
||||
};
|
||||
|
||||
struct CtaHdrStaticMetadataDataBlock
|
||||
{
|
||||
CtaDataBlockHeader header;
|
||||
BYTE extendedTag;
|
||||
BYTE eotf;
|
||||
BYTE staticMetadataDescriptor;
|
||||
BYTE desiredContentMaxLuminance;
|
||||
BYTE desiredContentMaxFrameAverageLuminance;
|
||||
BYTE desiredContentMinLuminance;
|
||||
};
|
||||
|
||||
struct CtaColorimetryDataBlock
|
||||
{
|
||||
CtaDataBlockHeader header;
|
||||
BYTE extendedTag;
|
||||
BYTE colorimetry;
|
||||
BYTE metadataAndAdditionalColorimetry;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(EdidLe16) == 2, "Unexpected EDID little-endian word size");
|
||||
static_assert(sizeof(EdidBe16) == 2, "Unexpected EDID big-endian word size");
|
||||
static_assert(sizeof(EdidStandardTiming) == 2, "Unexpected EDID standard timing size");
|
||||
static_assert(sizeof(EdidDetailedTimingDescriptor) == EDID_DTD_SIZE,
|
||||
"Unexpected EDID detailed timing descriptor size");
|
||||
static_assert(sizeof(EdidMonitorNameDescriptor) == EDID_DTD_SIZE,
|
||||
"Unexpected EDID monitor name descriptor size");
|
||||
static_assert(sizeof(EdidDescriptor) == EDID_DTD_SIZE,
|
||||
"Unexpected EDID descriptor size");
|
||||
static_assert(sizeof(EdidBaseBlock) == EDID_BLOCK_SIZE,
|
||||
"Unexpected EDID base block size");
|
||||
|
||||
static_assert(sizeof(CtaExtensionBlock) == EDID_BLOCK_SIZE,
|
||||
"Unexpected CTA extension block size");
|
||||
static_assert(sizeof(CtaHdrStaticMetadataDataBlock) == 7,
|
||||
"Unexpected HDR static metadata data block size");
|
||||
static_assert(sizeof(CtaColorimetryDataBlock) == 4,
|
||||
"Unexpected colorimetry data block size");
|
||||
static_assert(CTA_HEADER_SIZE +
|
||||
sizeof(CtaHdrStaticMetadataDataBlock) +
|
||||
sizeof(CtaColorimetryDataBlock) <= EDID_BLOCK_SIZE - 1,
|
||||
"CTA data blocks exceed extension block space");
|
||||
|
||||
static void SetBe16(EdidBe16& dst, DWORD value)
|
||||
{
|
||||
dst.hi = (BYTE)((value >> 8) & 0xff);
|
||||
dst.lo = (BYTE)(value & 0xff);
|
||||
}
|
||||
|
||||
static void SetLe16(EdidLe16& dst, DWORD value)
|
||||
{
|
||||
dst.lo = (BYTE)(value & 0xff);
|
||||
dst.hi = (BYTE)((value >> 8) & 0xff);
|
||||
}
|
||||
|
||||
static BYTE Lo8(DWORD value)
|
||||
{
|
||||
return (BYTE)(value & 0xff);
|
||||
}
|
||||
|
||||
static BYTE PackMsbNibbles(DWORD upperValue, DWORD lowerValue)
|
||||
{
|
||||
return (BYTE)((((upperValue >> 8) & 0x0f) << 4) |
|
||||
((lowerValue >> 8) & 0x0f));
|
||||
}
|
||||
|
||||
static BYTE PackLowNibbles(DWORD upperValue, DWORD lowerValue)
|
||||
{
|
||||
return (BYTE)(((upperValue & 0x0f) << 4) |
|
||||
(lowerValue & 0x0f));
|
||||
}
|
||||
|
||||
static BYTE PackSyncPorchPulseWidthHi(
|
||||
DWORD hFrontPorch,
|
||||
DWORD hSyncPulseWidth,
|
||||
DWORD vFrontPorch,
|
||||
DWORD vSyncPulseWidth)
|
||||
{
|
||||
return (BYTE)(
|
||||
(((hFrontPorch >> 8) & 0x03) << 6) |
|
||||
(((hSyncPulseWidth >> 8) & 0x03) << 4) |
|
||||
(((vFrontPorch >> 4) & 0x03) << 2) |
|
||||
((vSyncPulseWidth >> 4) & 0x03));
|
||||
}
|
||||
|
||||
static EdidStandardTiming MakeUnusedStandardTiming()
|
||||
{
|
||||
EdidStandardTiming timing = {};
|
||||
timing.horizontalActivePixels = EDID_STANDARD_TIMING_UNUSED_X;
|
||||
timing.aspectRatioAndRefreshRate = EDID_STANDARD_TIMING_UNUSED_AR_REFRESH;
|
||||
return timing;
|
||||
}
|
||||
|
||||
static WORD EdidChromaticity(double value)
|
||||
{
|
||||
return (WORD)min(1023.0, max(0.0, value * 1024.0 + 0.5));
|
||||
}
|
||||
|
||||
static void SetChromaticityCoordinates(BYTE coordinates[10], bool hdr)
|
||||
{
|
||||
// Describe the wire gamut. Accelerated HDR uses the BT.2020 container;
|
||||
// software rendering is SDR-only and uses the standard sRGB/BT.709 gamut.
|
||||
const WORD rx = EdidChromaticity(hdr ? 0.7080 : 0.6400);
|
||||
const WORD ry = EdidChromaticity(hdr ? 0.2920 : 0.3300);
|
||||
const WORD gx = EdidChromaticity(hdr ? 0.1700 : 0.3000);
|
||||
const WORD gy = EdidChromaticity(hdr ? 0.7970 : 0.6000);
|
||||
const WORD bx = EdidChromaticity(hdr ? 0.1310 : 0.1500);
|
||||
const WORD by = EdidChromaticity(hdr ? 0.0460 : 0.0600);
|
||||
const WORD wx = EdidChromaticity(0.3127);
|
||||
const WORD wy = EdidChromaticity(0.3290);
|
||||
|
||||
coordinates[0] = (BYTE)(((rx & 3) << 6) | ((ry & 3) << 4) |
|
||||
((gx & 3) << 2) | (gy & 3));
|
||||
coordinates[1] = (BYTE)(((bx & 3) << 6) | ((by & 3) << 4) |
|
||||
((wx & 3) << 2) | (wy & 3));
|
||||
coordinates[2] = (BYTE)(rx >> 2);
|
||||
coordinates[3] = (BYTE)(ry >> 2);
|
||||
coordinates[4] = (BYTE)(gx >> 2);
|
||||
coordinates[5] = (BYTE)(gy >> 2);
|
||||
coordinates[6] = (BYTE)(bx >> 2);
|
||||
coordinates[7] = (BYTE)(by >> 2);
|
||||
coordinates[8] = (BYTE)(wx >> 2);
|
||||
coordinates[9] = (BYTE)(wy >> 2);
|
||||
}
|
||||
|
||||
static BYTE GetVideoInputDefinition(bool hdr)
|
||||
{
|
||||
return hdr ?
|
||||
EDID_VIDEO_INPUT_DIGITAL_10BPC :
|
||||
EDID_VIDEO_INPUT_DIGITAL_8BPC;
|
||||
}
|
||||
|
||||
static void InitEdidBaseBlock(EdidBaseBlock& base, bool hdr)
|
||||
{
|
||||
memcpy(base.header, EDID_HEADER, sizeof(base.header));
|
||||
|
||||
// Manufacturer ID: LGD, product/serial values identify the virtual monitor.
|
||||
SetBe16(base.manufacturerId, EDID_MANUFACTURER_ID_LGD);
|
||||
SetLe16(base.productCode , EDID_PRODUCT_CODE);
|
||||
memcpy (base.serialNumber , EDID_SERIAL_NUMBER, sizeof(base.serialNumber));
|
||||
|
||||
base.manufactureWeek = EDID_MANUFACTURE_WEEK;
|
||||
base.manufactureYear = EDID_MANUFACTURE_YEAR_2026;
|
||||
base.version = EDID_VERSION;
|
||||
base.revision = EDID_REVISION;
|
||||
|
||||
base.videoInputDefinition = GetVideoInputDefinition(hdr);
|
||||
// This is a transport endpoint rather than a physical panel, so leave its
|
||||
// physical dimensions unspecified instead of imposing a false DPI/aspect.
|
||||
base.horizontalSizeCm = 0;
|
||||
base.verticalSizeCm = 0;
|
||||
base.displayGamma = EDID_DISPLAY_GAMMA_2_2;
|
||||
base.supportedFeatures = hdr ?
|
||||
EDID_FEATURES_PREFERRED_TIMING_RGB :
|
||||
EDID_FEATURES_PREFERRED_TIMING_SRGB;
|
||||
SetChromaticityCoordinates(base.chromaticityCoordinates, hdr);
|
||||
|
||||
for (UINT i = 0; i < EDID_STANDARD_TIMING_COUNT; ++i)
|
||||
base.standardTimings[i] = MakeUnusedStandardTiming();
|
||||
|
||||
base.extensionBlockCount = 1;
|
||||
}
|
||||
|
||||
bool CEdid::GetTiming(Timing& timing, const CSettings::DisplayMode& mode)
|
||||
{
|
||||
timing = {};
|
||||
|
||||
timing.hActive = mode.width;
|
||||
timing.vActive = mode.height;
|
||||
|
||||
if (timing.hActive == 0 || timing.vActive == 0 ||
|
||||
mode.refreshMilliHz == 0)
|
||||
return false;
|
||||
|
||||
timing.hBlank = std::max<DWORD>(160,
|
||||
((timing.hActive / 20) + 7) & ~7UL);
|
||||
timing.vBlank = std::max<DWORD>(30, timing.vActive / 20);
|
||||
|
||||
timing.hSync = std::max<DWORD>(32, timing.hActive / 100);
|
||||
timing.hSync = (timing.hSync + 7) & ~7UL;
|
||||
|
||||
timing.hFront = std::max<DWORD>(48, timing.hBlank / 3);
|
||||
timing.hFront = (timing.hFront + 7) & ~7UL;
|
||||
|
||||
if (timing.hFront + timing.hSync >= timing.hBlank)
|
||||
{
|
||||
timing.hFront = 48;
|
||||
timing.hSync = 32;
|
||||
}
|
||||
|
||||
timing.vFront = 3;
|
||||
timing.vSync = 5;
|
||||
if (timing.vFront + timing.vSync >= timing.vBlank)
|
||||
return false;
|
||||
|
||||
const UINT64 pixelClockMilliHz =
|
||||
(UINT64)(timing.hActive + timing.hBlank) *
|
||||
(UINT64)(timing.vActive + timing.vBlank) *
|
||||
(UINT64)mode.refreshMilliHz;
|
||||
timing.pixelClock = (pixelClockMilliHz + 500) / 1000;
|
||||
return timing.pixelClock != 0;
|
||||
}
|
||||
|
||||
static bool MakeDetailedTiming(
|
||||
EdidDetailedTimingDescriptor& descriptor,
|
||||
const CSettings::DisplayMode& mode)
|
||||
{
|
||||
memset(&descriptor, 0, sizeof(descriptor));
|
||||
|
||||
CEdid::Timing timing;
|
||||
if (!CEdid::GetTiming(timing, mode) ||
|
||||
timing.hActive > 4095 || timing.vActive > 4095 ||
|
||||
timing.hBlank > 4095 || timing.vBlank > 4095)
|
||||
return false;
|
||||
|
||||
const UINT64 pixelClock10KHz = (timing.pixelClock + 5000) / 10000;
|
||||
if (pixelClock10KHz == 0 || pixelClock10KHz > 0xffff)
|
||||
return false;
|
||||
|
||||
SetLe16(descriptor.pixelClock10KHz, (DWORD)pixelClock10KHz);
|
||||
|
||||
descriptor.hActiveLo = Lo8(timing.hActive);
|
||||
descriptor.hBlankLo = Lo8(timing.hBlank);
|
||||
descriptor.hActiveBlankHi = PackMsbNibbles(timing.hActive, timing.hBlank);
|
||||
|
||||
descriptor.vActiveLo = Lo8(timing.vActive);
|
||||
descriptor.vBlankLo = Lo8(timing.vBlank);
|
||||
descriptor.vActiveBlankHi = PackMsbNibbles(timing.vActive, timing.vBlank);
|
||||
|
||||
descriptor.hFrontPorchLo = Lo8(timing.hFront);
|
||||
descriptor.hSyncPulseWidthLo = Lo8(timing.hSync);
|
||||
descriptor.vFrontPorchSyncPulseWidthLo =
|
||||
PackLowNibbles(timing.vFront, timing.vSync);
|
||||
descriptor.syncPorchPulseWidthHi = PackSyncPorchPulseWidthHi(
|
||||
timing.hFront, timing.hSync, timing.vFront, timing.vSync);
|
||||
|
||||
descriptor.imageWidthMmLo = 0;
|
||||
descriptor.imageHeightMmLo = 0;
|
||||
descriptor.imageSizeMmHi = 0;
|
||||
|
||||
descriptor.flags = EDID_DTD_FLAGS_DIGITAL_SEPARATE_SYNC_POSITIVE;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void MakeMonitorName(
|
||||
EdidMonitorNameDescriptor& monitorName,
|
||||
const char* name)
|
||||
{
|
||||
memset(&monitorName, 0, sizeof(monitorName));
|
||||
|
||||
monitorName.descriptorTag = EDID_DESCRIPTOR_MONITOR_NAME;
|
||||
|
||||
UINT len = 0;
|
||||
for (; len < sizeof(monitorName.name) && name[len]; ++len)
|
||||
monitorName.name[len] = name[len];
|
||||
|
||||
if (len < sizeof(monitorName.name))
|
||||
monitorName.name[len++] = '\n';
|
||||
|
||||
for (; len < sizeof(monitorName.name); ++len)
|
||||
monitorName.name[len] = ' ';
|
||||
}
|
||||
|
||||
static CtaDataBlockHeader MakeCtaDataBlockHeader(BYTE tag, UINT payloadSize)
|
||||
{
|
||||
CtaDataBlockHeader header = {};
|
||||
|
||||
if (payloadSize > CTA_DATA_BLOCK_MAX_PAYLOAD_SIZE)
|
||||
payloadSize = CTA_DATA_BLOCK_MAX_PAYLOAD_SIZE;
|
||||
|
||||
header.value = (BYTE)((tag << 5) |
|
||||
(payloadSize & CTA_DATA_BLOCK_LENGTH_MASK));
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void AppendCtaDataBlock(BYTE* cta, UINT& offset, const T& block)
|
||||
{
|
||||
static_assert(sizeof(T) > sizeof(CtaDataBlockHeader),
|
||||
"CTA data block has no payload");
|
||||
static_assert(sizeof(T) - sizeof(CtaDataBlockHeader) <=
|
||||
CTA_DATA_BLOCK_MAX_PAYLOAD_SIZE,
|
||||
"CTA data block payload exceeds header length field");
|
||||
|
||||
memcpy(cta + offset, &block, sizeof(block));
|
||||
offset += (UINT)sizeof(block);
|
||||
}
|
||||
|
||||
static CtaHdrStaticMetadataDataBlock MakeCtaHdrStaticMetadataDataBlock()
|
||||
{
|
||||
CtaHdrStaticMetadataDataBlock block = {};
|
||||
|
||||
block.header = MakeCtaDataBlockHeader(CTA_DATA_BLOCK_TAG_EXTENDED,
|
||||
(UINT)(sizeof(block) - sizeof(block.header)));
|
||||
|
||||
block.extendedTag = CTA_EXTENDED_TAG_HDR_STATIC_METADATA;
|
||||
block.eotf = (BYTE)(
|
||||
CTA_HDR_EOTF_TRADITIONAL_SDR |
|
||||
CTA_HDR_EOTF_SMPTE_ST_2084);
|
||||
|
||||
block.staticMetadataDescriptor = CTA_HDR_STATIC_METADATA_TYPE_1;
|
||||
block.desiredContentMaxLuminance = CTA_HDR_DESIRED_MAX_LUMINANCE;
|
||||
block.desiredContentMaxFrameAverageLuminance = CTA_HDR_DESIRED_MAX_FRAME_AVG_LUMINANCE;
|
||||
block.desiredContentMinLuminance = CTA_HDR_DESIRED_MIN_LUMINANCE;
|
||||
return block;
|
||||
}
|
||||
|
||||
static CtaColorimetryDataBlock MakeCtaColorimetryDataBlock()
|
||||
{
|
||||
CtaColorimetryDataBlock block = {};
|
||||
|
||||
block.header = MakeCtaDataBlockHeader(CTA_DATA_BLOCK_TAG_EXTENDED,
|
||||
(UINT)(sizeof(block) - sizeof(block.header)));
|
||||
block.extendedTag = CTA_EXTENDED_TAG_COLORIMETRY;
|
||||
block.colorimetry = CTA_COLORIMETRY_BT2020_RGB;
|
||||
block.metadataAndAdditionalColorimetry = 0;
|
||||
return block;
|
||||
}
|
||||
|
||||
void CEdid::SetChecksum(BYTE* block)
|
||||
{
|
||||
BYTE sum = 0;
|
||||
for (UINT i = 0; i < EDID_BLOCK_SIZE - 1; ++i)
|
||||
sum = (BYTE)(sum + block[i]);
|
||||
|
||||
block[EDID_BLOCK_SIZE - 1] = (BYTE)(0 - sum);
|
||||
}
|
||||
|
||||
void CEdid::WriteMonitorName(BYTE* desc, const char* name)
|
||||
{
|
||||
EdidMonitorNameDescriptor monitorName = {};
|
||||
MakeMonitorName(monitorName, name);
|
||||
memcpy(desc, &monitorName, sizeof(monitorName));
|
||||
}
|
||||
|
||||
bool CEdid::WriteDetailedTiming(BYTE* dtd, const CSettings::DisplayMode& mode)
|
||||
{
|
||||
EdidDetailedTimingDescriptor timing = {};
|
||||
|
||||
if (!MakeDetailedTiming(timing, mode))
|
||||
return false;
|
||||
|
||||
memcpy(dtd, &timing, sizeof(timing));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CEdid::Build(bool hdr)
|
||||
{
|
||||
m_data.assign(
|
||||
static_cast<std::vector<BYTE, std::allocator<BYTE>>::size_type>(
|
||||
EDID_BLOCK_SIZE) * 2,
|
||||
0);
|
||||
|
||||
EdidBaseBlock baseBlock = {};
|
||||
InitEdidBaseBlock(baseBlock, hdr);
|
||||
|
||||
UINT modeIndex = 0;
|
||||
UINT baseDtdIndex = 0;
|
||||
|
||||
for (; modeIndex < ARRAYSIZE(EDID_DISPLAY_MODES) &&
|
||||
baseDtdIndex < EDID_BASE_DETAILED_TIMING_COUNT;
|
||||
++modeIndex)
|
||||
{
|
||||
if (MakeDetailedTiming(
|
||||
baseBlock.descriptors[baseDtdIndex].detailedTiming,
|
||||
EDID_DISPLAY_MODES[modeIndex]))
|
||||
{
|
||||
++baseDtdIndex;
|
||||
}
|
||||
}
|
||||
|
||||
MakeMonitorName(
|
||||
baseBlock.descriptors[EDID_BASE_MONITOR_NAME_DESCRIPTOR_INDEX].monitorName,
|
||||
"Looking Glass");
|
||||
|
||||
SetChecksum(reinterpret_cast<BYTE*>(&baseBlock));
|
||||
memcpy(m_data.data(), &baseBlock, sizeof(baseBlock));
|
||||
|
||||
CtaExtensionBlock ctaBlock = {};
|
||||
BYTE* cta = reinterpret_cast<BYTE*>(&ctaBlock);
|
||||
|
||||
ctaBlock.tag = CTA_EXTENSION_TAG;
|
||||
ctaBlock.revision = CTA_REVISION;
|
||||
|
||||
UINT dataOffset = CTA_HEADER_SIZE;
|
||||
if (hdr)
|
||||
{
|
||||
AppendCtaDataBlock(cta, dataOffset, MakeCtaHdrStaticMetadataDataBlock ());
|
||||
AppendCtaDataBlock(cta, dataOffset, MakeCtaColorimetryDataBlock ());
|
||||
}
|
||||
|
||||
ctaBlock.dtdOffset = (BYTE)dataOffset;
|
||||
ctaBlock.flags = 0x00;
|
||||
|
||||
UINT ctaDtdWrite = dataOffset;
|
||||
for (; modeIndex < ARRAYSIZE(EDID_DISPLAY_MODES) &&
|
||||
ctaDtdWrite + EDID_DTD_SIZE <= EDID_BLOCK_SIZE - 1;
|
||||
++modeIndex)
|
||||
{
|
||||
if (WriteDetailedTiming(cta + ctaDtdWrite,
|
||||
EDID_DISPLAY_MODES[modeIndex]))
|
||||
ctaDtdWrite += EDID_DTD_SIZE;
|
||||
}
|
||||
|
||||
SetChecksum(cta);
|
||||
memcpy(m_data.data() + sizeof(baseBlock), &ctaBlock, sizeof(ctaBlock));
|
||||
}
|
||||
59
idd/LGIdd/display/CEdid.h
Normal file
59
idd/LGIdd/display/CEdid.h
Normal file
@@ -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 <Windows.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include "config/CSettings.h"
|
||||
|
||||
class CEdid
|
||||
{
|
||||
public:
|
||||
struct Timing
|
||||
{
|
||||
DWORD hActive;
|
||||
DWORD hBlank;
|
||||
DWORD hFront;
|
||||
DWORD hSync;
|
||||
DWORD vActive;
|
||||
DWORD vBlank;
|
||||
DWORD vFront;
|
||||
DWORD vSync;
|
||||
UINT64 pixelClock;
|
||||
};
|
||||
|
||||
void Build(bool hdr);
|
||||
|
||||
static bool GetTiming(Timing& timing,
|
||||
const CSettings::DisplayMode& mode);
|
||||
|
||||
const BYTE* Data() const { return m_data.empty() ? nullptr : m_data.data(); }
|
||||
UINT Size() const { return (UINT)m_data.size(); }
|
||||
|
||||
private:
|
||||
std::vector<BYTE> m_data;
|
||||
|
||||
static void SetChecksum(BYTE* block);
|
||||
static bool WriteDetailedTiming(BYTE* dtd, const CSettings::DisplayMode& mode);
|
||||
static void WriteMonitorName(BYTE* desc, const char* name);
|
||||
};
|
||||
252
idd/LGIdd/display/CMonitorManager.cpp
Normal file
252
idd/LGIdd/display/CMonitorManager.cpp
Normal file
@@ -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<BYTE> 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;
|
||||
}
|
||||
89
idd/LGIdd/display/CMonitorManager.h
Normal file
89
idd/LGIdd/display/CMonitorManager.h
Normal file
@@ -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 <Windows.h>
|
||||
#include <wdf.h>
|
||||
#include <IddCx.h>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#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<LONG> m_createQueued = 0;
|
||||
std::atomic<LONG> m_replugQueued = 0;
|
||||
|
||||
public:
|
||||
void Create(UINT connectorIndex, IDDCX_ADAPTER adapter,
|
||||
std::vector<BYTE> 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();
|
||||
};
|
||||
34
idd/LGIdd/display/IddCxCompat.h
Normal file
34
idd/LGIdd/display/IddCxCompat.h
Normal file
@@ -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 <Windows.h>
|
||||
#include <wdf.h>
|
||||
#include <IddCx.h>
|
||||
|
||||
// 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
|
||||
588
idd/LGIdd/display/device/CDeviceContext.cpp
Normal file
588
idd/LGIdd/display/device/CDeviceContext.cpp
Normal file
@@ -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 <dxgi1_2.h>
|
||||
#include <utility>
|
||||
|
||||
// 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<KVMFRMessage *>(data);
|
||||
switch (msg->type)
|
||||
{
|
||||
case KVMFR_MESSAGE_SETCURSORPOS:
|
||||
{
|
||||
KVMFRSetCursorPos * position =
|
||||
reinterpret_cast<KVMFRSetCursorPos *>(msg);
|
||||
g_pipe.SetCursorPos(position->x, position->y);
|
||||
break;
|
||||
}
|
||||
|
||||
case KVMFR_MESSAGE_WINDOWSIZE:
|
||||
{
|
||||
KVMFRWindowSize * window =
|
||||
reinterpret_cast<KVMFRWindowSize *>(msg);
|
||||
SetResolution(window->w, window->h);
|
||||
break;
|
||||
}
|
||||
|
||||
case KVMFR_MESSAGE_FRAME_SCHEDULE:
|
||||
{
|
||||
const KVMFRFrameSchedule * schedule =
|
||||
reinterpret_cast<KVMFRFrameSchedule *>(msg);
|
||||
const bool valid = size == sizeof(*schedule) &&
|
||||
m_frameTransport.UpdateSchedule(
|
||||
sourceClientID, *schedule, now);
|
||||
if (!valid)
|
||||
DEBUG_WARN("Ignoring invalid KVMFR frame schedule");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_lgmpControl.AckData();
|
||||
}
|
||||
|
||||
m_frameTransport.FinalizeSubscribers(subscribers, now);
|
||||
|
||||
if (m_lgmpControl.HasNewSubscribers())
|
||||
m_lgmpControl.ResendState();
|
||||
}
|
||||
126
idd/LGIdd/display/device/CDeviceContext.h
Normal file
126
idd/LGIdd/display/device/CDeviceContext.h
Normal file
@@ -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 <Windows.h>
|
||||
#include <wdf.h>
|
||||
#include <IddCx.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "display/CDisplayConfiguration.h"
|
||||
#include "display/CMonitorManager.h"
|
||||
#include "transport/CFrameTransport.h"
|
||||
#include "transport/CIVSHMEM.h"
|
||||
#include "transport/CLGMPControl.h"
|
||||
#include "transport/CLGMPHost.h"
|
||||
|
||||
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<LONG> 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);
|
||||
124
idd/LGIdd/display/monitor/Context.cpp
Normal file
124
idd/LGIdd/display/monitor/Context.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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/monitor/Context.h"
|
||||
#include "display/device/CDeviceContext.h"
|
||||
#include "capture/CSwapChainProcessor.h"
|
||||
#include "d3d/CD3D11Device.h"
|
||||
#include "CDebug.h"
|
||||
|
||||
CMonitorContext::CMonitorContext(
|
||||
_In_ IDDCX_MONITOR monitor, CDeviceContext * device) :
|
||||
m_monitor(monitor),
|
||||
m_devContext(device)
|
||||
{
|
||||
}
|
||||
|
||||
CMonitorContext::~CMonitorContext()
|
||||
{
|
||||
UnassignSwapChain();
|
||||
m_devContext->OnMonitorDestroyed(m_monitor);
|
||||
}
|
||||
|
||||
NTSTATUS CMonitorContext::AssignSwapChain(
|
||||
IDDCX_SWAPCHAIN swapChain, LUID renderAdapter, HANDLE newFrameEvent)
|
||||
{
|
||||
std::lock_guard<std::mutex> assignGuard(m_assignMutex);
|
||||
|
||||
// Finish tearing down the previous assignment before reserving a generation
|
||||
// for the new one. Deleting the old processor can itself cause IddCx to
|
||||
// re-enter UnassignSwapChain, and that old callback must happen before the
|
||||
// new generation is established.
|
||||
DetachSwapChain();
|
||||
|
||||
const UINT64 assignmentGeneration =
|
||||
m_assignmentGeneration.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
|
||||
// Build the D3D11 device into a local so the member is never observed
|
||||
// half-constructed. The worker binds it before performing the expensive
|
||||
// D3D12, LGMP and post-processing initialization.
|
||||
auto dx11Device = std::make_shared<CD3D11Device>(renderAdapter);
|
||||
const HRESULT initStatus = dx11Device->Init();
|
||||
if (FAILED(initStatus))
|
||||
{
|
||||
DEBUG_ERROR_HR(initStatus, "Failed to initialize D3D11 device");
|
||||
return STATUS_GRAPHICS_INDIRECT_DISPLAY_ABANDON_SWAPCHAIN;
|
||||
}
|
||||
|
||||
AcquireSRWLockExclusive(&m_lock);
|
||||
if (!IsAssignmentCurrent(assignmentGeneration))
|
||||
{
|
||||
ReleaseSRWLockExclusive(&m_lock);
|
||||
DEBUG_INFO("Swap chain assignment canceled before processor startup");
|
||||
return STATUS_GRAPHICS_INDIRECT_DISPLAY_ABANDON_SWAPCHAIN;
|
||||
}
|
||||
|
||||
// Publish the assignment atomically with starting its worker. An unassign
|
||||
// now blocks on m_lock until m_swapChain exists, at which point it can
|
||||
// signal and join the processor normally.
|
||||
m_devContext->OnSwapChainAssigned();
|
||||
m_dx11Device = std::move(dx11Device);
|
||||
m_swapChain.reset(new CSwapChainProcessor(
|
||||
this, assignmentGeneration, m_monitor, m_devContext, swapChain,
|
||||
renderAdapter, m_dx11Device, newFrameEvent));
|
||||
if (!m_swapChain->Start())
|
||||
{
|
||||
auto processor = std::move(m_swapChain);
|
||||
dx11Device = std::move(m_dx11Device);
|
||||
ReleaseSRWLockExclusive(&m_lock);
|
||||
processor.reset();
|
||||
dx11Device.reset();
|
||||
m_devContext->OnSwapChainReleased();
|
||||
return STATUS_GRAPHICS_INDIRECT_DISPLAY_ABANDON_SWAPCHAIN;
|
||||
}
|
||||
ReleaseSRWLockExclusive(&m_lock);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
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
|
||||
// blocked waiting for the processor to be published.
|
||||
m_assignmentGeneration.fetch_add(1, std::memory_order_acq_rel);
|
||||
|
||||
// Detach under the lock, then destroy outside it. Destroying the processor
|
||||
// joins its worker thread, whose teardown (WdfObjectDelete) re-enters this
|
||||
// method on another thread - holding the lock across that would deadlock.
|
||||
std::unique_ptr<CSwapChainProcessor> processor;
|
||||
std::shared_ptr<CD3D11Device> dx11Device;
|
||||
|
||||
AcquireSRWLockExclusive(&m_lock);
|
||||
processor = std::move(m_swapChain);
|
||||
dx11Device = std::move(m_dx11Device);
|
||||
ReleaseSRWLockExclusive(&m_lock);
|
||||
|
||||
const bool hadSwapChain = !!processor;
|
||||
processor.reset();
|
||||
dx11Device.reset();
|
||||
|
||||
if (hadSwapChain)
|
||||
m_devContext->OnSwapChainReleased();
|
||||
}
|
||||
|
||||
void CMonitorContext::UnassignSwapChain()
|
||||
{
|
||||
DetachSwapChain();
|
||||
}
|
||||
89
idd/LGIdd/display/monitor/Context.h
Normal file
89
idd/LGIdd/display/monitor/Context.h
Normal file
@@ -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 <Windows.h>
|
||||
#include <wdf.h>
|
||||
#include <IddCx.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
struct CD3D11Device;
|
||||
class CDeviceContext;
|
||||
class CSwapChainProcessor;
|
||||
|
||||
class CMonitorContext
|
||||
{
|
||||
private:
|
||||
IDDCX_MONITOR m_monitor;
|
||||
|
||||
// Guards the swap chain and device pointers. Assign and unassign can run
|
||||
// concurrently (an unassign triggered by the worker's WdfObjectDelete can
|
||||
// race the next assign), and shared_ptr copy/reset is not thread safe.
|
||||
SRWLOCK m_lock = SRWLOCK_INIT;
|
||||
|
||||
// IddCx can issue a replacement assignment before an earlier assignment
|
||||
// has finished creating its devices. Serialize those expensive setup paths
|
||||
// while still allowing UnassignSwapChain to cancel the active one.
|
||||
std::mutex m_assignMutex;
|
||||
std::shared_ptr<CD3D11Device> m_dx11Device;
|
||||
|
||||
CDeviceContext * m_devContext;
|
||||
std::unique_ptr<CSwapChainProcessor> m_swapChain;
|
||||
|
||||
// Incremented whenever the current assignment is replaced or unassigned.
|
||||
// Device creation is performed outside m_lock, so this lets an unassign
|
||||
// cancel that work before a processor is started on a stale swap chain.
|
||||
std::atomic<UINT64> m_assignmentGeneration = 0;
|
||||
|
||||
void DetachSwapChain();
|
||||
|
||||
public:
|
||||
CMonitorContext(
|
||||
_In_ IDDCX_MONITOR monitor, CDeviceContext * device);
|
||||
|
||||
virtual ~CMonitorContext();
|
||||
|
||||
NTSTATUS AssignSwapChain(
|
||||
IDDCX_SWAPCHAIN swapChain, LUID renderAdapter, HANDLE newFrameEvent);
|
||||
void UnassignSwapChain();
|
||||
bool IsAssignmentCurrent(UINT64 generation) const
|
||||
{
|
||||
return m_assignmentGeneration.load(std::memory_order_acquire) == generation;
|
||||
}
|
||||
|
||||
CDeviceContext * GetDeviceContext() { return m_devContext; }
|
||||
};
|
||||
|
||||
struct CMonitorContextWrapper
|
||||
{
|
||||
CMonitorContext * context;
|
||||
|
||||
void Cleanup()
|
||||
{
|
||||
delete context;
|
||||
context = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
WDF_DECLARE_CONTEXT_TYPE(CMonitorContextWrapper);
|
||||
Reference in New Issue
Block a user