[idd] hdr: correct virtual monitor capabilities
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled

This commit is contained in:
Geoffrey McRae
2026-07-19 22:26:44 +10:00
parent 7353e8766f
commit 23e394fd57
5 changed files with 162 additions and 200 deletions

View File

@@ -36,7 +36,7 @@ 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 = 0xe430;
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 };
@@ -45,35 +45,29 @@ 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_HDMI_A = 0xa2;
static const BYTE EDID_VIDEO_INPUT_DIGITAL_10BPC_HDMI_A = 0xb2;
static const BYTE EDID_HORIZONTAL_SIZE_CM = 52;
static const BYTE EDID_VERTICAL_SIZE_CM = 29;
static const BYTE EDID_DISPLAY_GAMMA_2_2 = 0x78;
static const BYTE EDID_FEATURES_PREFERRED_TIMING_RGB = 0x0a;
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 DWORD EDID_IMAGE_WIDTH_MM = 520;
static const DWORD EDID_IMAGE_HEIGHT_MM = 290;
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_VENDOR_SPECIFIC = 0x03;
static const BYTE CTA_DATA_BLOCK_TAG_EXTENDED = 0x07;
static const BYTE CTA_DATA_BLOCK_LENGTH_MASK = 0x1f;
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_EOTF_HLG = (BYTE)(1 << 3);
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
@@ -85,24 +79,7 @@ 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_OPYCC_601 = (BYTE)(1 << 3);
static const BYTE CTA_COLORIMETRY_OPRGB = (BYTE)(1 << 4);
static const BYTE CTA_COLORIMETRY_BT2020_YCC = (BYTE)(1 << 6);
static const BYTE CTA_COLORIMETRY_BT2020_RGB = (BYTE)(1 << 7);
static const BYTE CTA_COLORIMETRY_DCI_P3 = (BYTE)(1 << 7);
static const BYTE CTA_HDMI_FORUM_OUI [3] = { 0xd8, 0x5d, 0xc4 };
static const BYTE CTA_HDMI_LICENSING_OUI[3] = { 0x03, 0x0c, 0x00 };
static const BYTE CTA_HDMI_FORUM_VERSION_1 = 0x01;
static const BYTE CTA_HDMI_FORUM_MAX_TMDS_CHARACTER_RATE = 0x6e;
static const BYTE CTA_HDMI_FORUM_SCDC_PRESENT = 0x80;
static const BYTE CTA_HDMI_PHYSICAL_ADDRESS_A_B = 0x00;
static const BYTE CTA_HDMI_PHYSICAL_ADDRESS_C_D = 0x00;
static const BYTE CTA_HDMI_DEEP_COLOR_30_36 = 0x30;
static const BYTE CTA_HDMI_MAX_TMDS_CLOCK_UNSPECIFIED = 0x00;
static const BYTE CTA_HDMI_LATENCY_FIELDS_NONE = 0x0b;
#pragma pack(push, 1)
struct EdidLe16
@@ -111,6 +88,12 @@ struct EdidLe16
BYTE hi;
};
struct EdidBe16
{
BYTE hi;
BYTE lo;
};
struct EdidStandardTiming
{
BYTE horizontalActivePixels;
@@ -163,7 +146,7 @@ struct EdidBaseBlock
{
BYTE header[8];
EdidLe16 manufacturerId;
EdidBe16 manufacturerId;
EdidLe16 productCode;
BYTE serialNumber[4];
@@ -222,29 +205,10 @@ struct CtaColorimetryDataBlock
BYTE metadataAndAdditionalColorimetry;
};
struct CtaHdmiForumVendorSpecificDataBlock
{
CtaDataBlockHeader header;
BYTE ieeeOui[3];
BYTE version;
BYTE maxTmdsCharacterRate;
BYTE flags;
BYTE reserved;
};
struct CtaHdmiVendorSpecificDataBlock
{
CtaDataBlockHeader header;
BYTE ieeeOui[3];
BYTE physicalAddressAB;
BYTE physicalAddressCD;
BYTE flags;
BYTE maxTmdsClock;
BYTE latencyFields;
};
#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");
@@ -261,17 +225,17 @@ static_assert(sizeof(CtaHdrStaticMetadataDataBlock) == 7,
"Unexpected HDR static metadata data block size");
static_assert(sizeof(CtaColorimetryDataBlock) == 4,
"Unexpected colorimetry data block size");
static_assert(sizeof(CtaHdmiForumVendorSpecificDataBlock) == 8,
"Unexpected HDMI Forum VSDB size");
static_assert(sizeof(CtaHdmiVendorSpecificDataBlock) == 9,
"Unexpected HDMI VSDB size");
static_assert(CTA_HEADER_SIZE +
sizeof(CtaHdrStaticMetadataDataBlock) +
sizeof(CtaColorimetryDataBlock) +
sizeof(CtaHdmiForumVendorSpecificDataBlock) +
sizeof(CtaHdmiVendorSpecificDataBlock) <= EDID_BLOCK_SIZE - 1,
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);
@@ -316,11 +280,9 @@ static EdidStandardTiming MakeUnusedStandardTiming()
return timing;
}
#include <algorithm> // Ensure this header is included for std::min and std::max
static WORD EdidChromaticity(double value)
{
return (WORD)min(1023.0, max(0.0, value * 1024.0 + 0.5));
return (WORD)min(1023.0, max(0.0, value * 1024.0 + 0.5));
}
static void SetChromaticityCoordinates(BYTE coordinates[10], bool hdr)
@@ -350,27 +312,36 @@ static void SetChromaticityCoordinates(BYTE coordinates[10], bool hdr)
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 are arbitrary.
SetLe16(base.manufacturerId, EDID_MANUFACTURER_ID_LGD);
SetLe16(base.productCode, EDID_PRODUCT_CODE);
memcpy(base.serialNumber, EDID_SERIAL_NUMBER, sizeof(base.serialNumber));
// 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 = hdr ?
EDID_VIDEO_INPUT_DIGITAL_10BPC_HDMI_A :
EDID_VIDEO_INPUT_DIGITAL_8BPC_HDMI_A;
base.horizontalSizeCm = EDID_HORIZONTAL_SIZE_CM;
base.verticalSizeCm = EDID_VERTICAL_SIZE_CM;
base.displayGamma = EDID_DISPLAY_GAMMA_2_2;
base.supportedFeatures = EDID_FEATURES_PREFERRED_TIMING_RGB;
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)
@@ -379,68 +350,84 @@ static void InitEdidBaseBlock(EdidBaseBlock& base, bool hdr)
base.extensionBlockCount = 1;
}
static bool MakeDetailedTiming(
EdidDetailedTimingDescriptor& timing,
const CSettings::DisplayMode& mode)
bool CEdid::GetTiming(Timing& timing, const CSettings::DisplayMode& mode)
{
memset(&timing, 0, sizeof(timing));
timing = {};
const DWORD hActive = mode.width;
const DWORD vActive = mode.height;
const DWORD refresh = mode.refresh;
timing.hActive = mode.width;
timing.vActive = mode.height;
if (hActive == 0 || vActive == 0 || refresh == 0 ||
hActive > 4095 || vActive > 4095)
if (timing.hActive == 0 || timing.vActive == 0 || mode.refresh == 0)
return false;
DWORD hBlank = std::max<DWORD>(160, ((hActive / 20) + 7) & ~7UL);
DWORD vBlank = std::max<DWORD>(30, vActive / 20);
if (hBlank > 4095 || vBlank > 4095)
return false;
timing.hBlank = std::max<DWORD>(160,
((timing.hActive / 20) + 7) & ~7UL);
timing.vBlank = std::max<DWORD>(30, timing.vActive / 20);
DWORD hSync = std::max<DWORD>(32, hActive / 100);
hSync = (hSync + 7) & ~7UL;
timing.hSync = std::max<DWORD>(32, timing.hActive / 100);
timing.hSync = (timing.hSync + 7) & ~7UL;
DWORD hFront = std::max<DWORD>(48, hBlank / 3);
hFront = (hFront + 7) & ~7UL;
timing.hFront = std::max<DWORD>(48, timing.hBlank / 3);
timing.hFront = (timing.hFront + 7) & ~7UL;
if (hFront + hSync >= hBlank)
if (timing.hFront + timing.hSync >= timing.hBlank)
{
hFront = 48;
hSync = 32;
timing.hFront = 48;
timing.hSync = 32;
}
const DWORD vFront = 3;
const DWORD vSync = 5;
if (vFront + vSync >= vBlank)
timing.vFront = 3;
timing.vSync = 5;
if (timing.vFront + timing.vSync >= timing.vBlank)
return false;
const UINT64 pixelClock = (UINT64)(hActive + hBlank) *
(UINT64)(vActive + vBlank) * (UINT64)refresh;
const UINT64 pixelClock =
(UINT64)(timing.hActive + timing.hBlank) *
(UINT64)(timing.vActive + timing.vBlank) *
(UINT64)mode.refresh;
const UINT64 pixelClock10KHz = (pixelClock + 5000) / 10000;
timing.pixelClock = pixelClock10KHz * 10000;
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 / 10000;
if (pixelClock10KHz == 0 || pixelClock10KHz > 0xffff)
return false;
SetLe16(timing.pixelClock10KHz, (DWORD)pixelClock10KHz);
SetLe16(descriptor.pixelClock10KHz, (DWORD)pixelClock10KHz);
timing.hActiveLo = Lo8(hActive);
timing.hBlankLo = Lo8(hBlank);
timing.hActiveBlankHi = PackMsbNibbles(hActive, hBlank);
descriptor.hActiveLo = Lo8(timing.hActive);
descriptor.hBlankLo = Lo8(timing.hBlank);
descriptor.hActiveBlankHi = PackMsbNibbles(timing.hActive, timing.hBlank);
timing.vActiveLo = Lo8(vActive);
timing.vBlankLo = Lo8(vBlank);
timing.vActiveBlankHi = PackMsbNibbles(vActive, vBlank);
descriptor.vActiveLo = Lo8(timing.vActive);
descriptor.vBlankLo = Lo8(timing.vBlank);
descriptor.vActiveBlankHi = PackMsbNibbles(timing.vActive, timing.vBlank);
timing.hFrontPorchLo = Lo8(hFront);
timing.hSyncPulseWidthLo = Lo8(hSync);
timing.vFrontPorchSyncPulseWidthLo = PackLowNibbles(vFront, vSync);
timing.syncPorchPulseWidthHi = PackSyncPorchPulseWidthHi(hFront, hSync, vFront, vSync);
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);
timing.imageWidthMmLo = Lo8(EDID_IMAGE_WIDTH_MM);
timing.imageHeightMmLo = Lo8(EDID_IMAGE_HEIGHT_MM);
timing.imageSizeMmHi = PackMsbNibbles(EDID_IMAGE_WIDTH_MM, EDID_IMAGE_HEIGHT_MM);
descriptor.imageWidthMmLo = 0;
descriptor.imageHeightMmLo = 0;
descriptor.imageSizeMmHi = 0;
timing.flags = EDID_DTD_FLAGS_DIGITAL_SEPARATE_SYNC_POSITIVE;
descriptor.flags = EDID_DTD_FLAGS_DIGITAL_SEPARATE_SYNC_POSITIVE;
return true;
}
@@ -499,8 +486,7 @@ static CtaHdrStaticMetadataDataBlock MakeCtaHdrStaticMetadataDataBlock()
block.extendedTag = CTA_EXTENDED_TAG_HDR_STATIC_METADATA;
block.eotf = (BYTE)(
CTA_HDR_EOTF_TRADITIONAL_SDR |
CTA_HDR_EOTF_SMPTE_ST_2084 |
CTA_HDR_EOTF_HLG);
CTA_HDR_EOTF_SMPTE_ST_2084);
block.staticMetadataDescriptor = CTA_HDR_STATIC_METADATA_TYPE_1;
block.desiredContentMaxLuminance = CTA_HDR_DESIRED_MAX_LUMINANCE;
@@ -515,41 +501,9 @@ static CtaColorimetryDataBlock MakeCtaColorimetryDataBlock()
block.header = MakeCtaDataBlockHeader(CTA_DATA_BLOCK_TAG_EXTENDED,
(UINT)(sizeof(block) - sizeof(block.header)));
block.extendedTag = CTA_EXTENDED_TAG_COLORIMETRY;
block.colorimetry = (BYTE)(CTA_COLORIMETRY_OPYCC_601 |
CTA_COLORIMETRY_OPRGB |
CTA_COLORIMETRY_BT2020_YCC |
CTA_COLORIMETRY_BT2020_RGB);
block.metadataAndAdditionalColorimetry = CTA_COLORIMETRY_DCI_P3;
return block;
}
static CtaHdmiForumVendorSpecificDataBlock MakeCtaHdmiForumVendorSpecificDataBlock()
{
CtaHdmiForumVendorSpecificDataBlock block = {};
block.header = MakeCtaDataBlockHeader(CTA_DATA_BLOCK_TAG_VENDOR_SPECIFIC,
(UINT)(sizeof(block) - sizeof(block.header)));
memcpy(block.ieeeOui, CTA_HDMI_FORUM_OUI, sizeof(block.ieeeOui));
block.version = CTA_HDMI_FORUM_VERSION_1;
block.maxTmdsCharacterRate = CTA_HDMI_FORUM_MAX_TMDS_CHARACTER_RATE;
block.flags = CTA_HDMI_FORUM_SCDC_PRESENT;
block.reserved = 0x00;
return block;
}
static CtaHdmiVendorSpecificDataBlock MakeCtaHdmiVendorSpecificDataBlock()
{
CtaHdmiVendorSpecificDataBlock block = {};
block.header = MakeCtaDataBlockHeader(CTA_DATA_BLOCK_TAG_VENDOR_SPECIFIC,
(UINT)(sizeof(block) - sizeof(block.header)));
memcpy(block.ieeeOui, CTA_HDMI_LICENSING_OUI, sizeof(block.ieeeOui));
block.physicalAddressAB = CTA_HDMI_PHYSICAL_ADDRESS_A_B;
block.physicalAddressCD = CTA_HDMI_PHYSICAL_ADDRESS_C_D;
block.flags = CTA_HDMI_DEEP_COLOR_30_36;
block.maxTmdsClock = CTA_HDMI_MAX_TMDS_CLOCK_UNSPECIFIED;
block.latencyFields = CTA_HDMI_LATENCY_FIELDS_NONE;
block.extendedTag = CTA_EXTENDED_TAG_COLORIMETRY;
block.colorimetry = CTA_COLORIMETRY_BT2020_RGB;
block.metadataAndAdditionalColorimetry = 0;
return block;
}
@@ -582,7 +536,10 @@ bool CEdid::WriteDetailedTiming(BYTE* dtd, const CSettings::DisplayMode& mode)
void CEdid::Build(const CSettings::DisplayModes& modes, bool hdr)
{
m_data.assign(static_cast<std::vector<BYTE, std::allocator<BYTE>>::size_type>(EDID_BLOCK_SIZE) * 2, 0);
m_data.assign(
static_cast<std::vector<BYTE, std::allocator<BYTE>>::size_type>(
EDID_BLOCK_SIZE) * 2,
0);
EdidBaseBlock baseBlock = {};
InitEdidBaseBlock(baseBlock, hdr);
@@ -637,8 +594,6 @@ void CEdid::Build(const CSettings::DisplayModes& modes, bool hdr)
{
AppendCtaDataBlock(cta, dataOffset, MakeCtaHdrStaticMetadataDataBlock ());
AppendCtaDataBlock(cta, dataOffset, MakeCtaColorimetryDataBlock ());
AppendCtaDataBlock(cta, dataOffset, MakeCtaHdmiForumVendorSpecificDataBlock());
AppendCtaDataBlock(cta, dataOffset, MakeCtaHdmiVendorSpecificDataBlock ());
}
ctaBlock.dtdOffset = (BYTE)dataOffset;