mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-07-21 14:52:04 +00:00
The virtual display transports HDR pixels rather than emitting light. Advertise approximately 10,000 nits for both peak and frame-average luminance, and leave the physical black level unspecified. This prevents Windows from mapping HDR into an arbitrary virtual display limit before the frame reaches the real host display.
663 lines
20 KiB
C++
663 lines
20 KiB
C++
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "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 = 0xe430;
|
|
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_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_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_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
|
|
// 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_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
|
|
{
|
|
BYTE lo;
|
|
BYTE hi;
|
|
};
|
|
|
|
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];
|
|
|
|
EdidLe16 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;
|
|
};
|
|
|
|
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(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(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,
|
|
"CTA data blocks exceed extension block space");
|
|
|
|
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;
|
|
}
|
|
|
|
#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));
|
|
}
|
|
|
|
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 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));
|
|
|
|
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;
|
|
SetChromaticityCoordinates(base.chromaticityCoordinates, hdr);
|
|
|
|
for (UINT i = 0; i < EDID_STANDARD_TIMING_COUNT; ++i)
|
|
base.standardTimings[i] = MakeUnusedStandardTiming();
|
|
|
|
base.extensionBlockCount = 1;
|
|
}
|
|
|
|
static bool MakeDetailedTiming(
|
|
EdidDetailedTimingDescriptor& timing,
|
|
const CSettings::DisplayMode& mode)
|
|
{
|
|
memset(&timing, 0, sizeof(timing));
|
|
|
|
const DWORD hActive = mode.width;
|
|
const DWORD vActive = mode.height;
|
|
const DWORD refresh = mode.refresh;
|
|
|
|
if (hActive == 0 || vActive == 0 || refresh == 0 ||
|
|
hActive > 4095 || vActive > 4095)
|
|
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;
|
|
|
|
DWORD hSync = std::max<DWORD>(32, hActive / 100);
|
|
hSync = (hSync + 7) & ~7UL;
|
|
|
|
DWORD hFront = std::max<DWORD>(48, hBlank / 3);
|
|
hFront = (hFront + 7) & ~7UL;
|
|
|
|
if (hFront + hSync >= hBlank)
|
|
{
|
|
hFront = 48;
|
|
hSync = 32;
|
|
}
|
|
|
|
const DWORD vFront = 3;
|
|
const DWORD vSync = 5;
|
|
if (vFront + vSync >= vBlank)
|
|
return false;
|
|
|
|
const UINT64 pixelClock = (UINT64)(hActive + hBlank) *
|
|
(UINT64)(vActive + vBlank) * (UINT64)refresh;
|
|
const UINT64 pixelClock10KHz = (pixelClock + 5000) / 10000;
|
|
if (pixelClock10KHz == 0 || pixelClock10KHz > 0xffff)
|
|
return false;
|
|
|
|
SetLe16(timing.pixelClock10KHz, (DWORD)pixelClock10KHz);
|
|
|
|
timing.hActiveLo = Lo8(hActive);
|
|
timing.hBlankLo = Lo8(hBlank);
|
|
timing.hActiveBlankHi = PackMsbNibbles(hActive, hBlank);
|
|
|
|
timing.vActiveLo = Lo8(vActive);
|
|
timing.vBlankLo = Lo8(vBlank);
|
|
timing.vActiveBlankHi = PackMsbNibbles(vActive, vBlank);
|
|
|
|
timing.hFrontPorchLo = Lo8(hFront);
|
|
timing.hSyncPulseWidthLo = Lo8(hSync);
|
|
timing.vFrontPorchSyncPulseWidthLo = PackLowNibbles(vFront, vSync);
|
|
timing.syncPorchPulseWidthHi = PackSyncPorchPulseWidthHi(hFront, hSync, vFront, 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);
|
|
|
|
timing.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 |
|
|
CTA_HDR_EOTF_HLG);
|
|
|
|
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 = (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;
|
|
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(const CSettings::DisplayModes& modes, 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);
|
|
|
|
CSettings::DisplayModes sorted = modes;
|
|
std::stable_sort(sorted.begin(), sorted.end(),
|
|
[](const CSettings::DisplayMode& a, const CSettings::DisplayMode& b)
|
|
{
|
|
if (a.preferred != b.preferred)
|
|
return a.preferred && !b.preferred;
|
|
if (a.width != b.width)
|
|
return a.width > b.width;
|
|
if (a.height != b.height)
|
|
return a.height > b.height;
|
|
return a.refresh > b.refresh;
|
|
});
|
|
|
|
UINT modeIndex = 0;
|
|
UINT baseDtdIndex = 0;
|
|
|
|
for (; modeIndex < sorted.size() &&
|
|
baseDtdIndex < EDID_BASE_DETAILED_TIMING_COUNT;
|
|
++modeIndex)
|
|
{
|
|
// never include the extra mode in the EDID
|
|
if (sorted[modeIndex].extraMode)
|
|
continue;
|
|
|
|
if (MakeDetailedTiming(
|
|
baseBlock.descriptors[baseDtdIndex].detailedTiming,
|
|
sorted[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 ());
|
|
AppendCtaDataBlock(cta, dataOffset, MakeCtaHdmiForumVendorSpecificDataBlock());
|
|
AppendCtaDataBlock(cta, dataOffset, MakeCtaHdmiVendorSpecificDataBlock ());
|
|
}
|
|
|
|
ctaBlock.dtdOffset = (BYTE)dataOffset;
|
|
ctaBlock.flags = 0x00;
|
|
|
|
UINT ctaDtdWrite = dataOffset;
|
|
for (; modeIndex < sorted.size() &&
|
|
ctaDtdWrite + EDID_DTD_SIZE <= EDID_BLOCK_SIZE - 1;
|
|
++modeIndex)
|
|
{
|
|
// never include the extra mode in the EDID
|
|
if (sorted[modeIndex].extraMode)
|
|
continue;
|
|
|
|
if (WriteDetailedTiming(cta + ctaDtdWrite, sorted[modeIndex]))
|
|
ctaDtdWrite += EDID_DTD_SIZE;
|
|
}
|
|
|
|
SetChecksum(cta);
|
|
memcpy(m_data.data() + sizeof(baseBlock), &ctaBlock, sizeof(ctaBlock));
|
|
}
|