mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
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
build / client-tests (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client-tests (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client-tests (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client-tests (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client-tests (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client-tests (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client-tests (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client-tests (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
Store refresh rates in millihertz and preserve three decimal places. Advertise exact rational rates while accepting legacy integer values. Accept rates from 23.900 through 1000.000 Hz.
607 lines
18 KiB
C++
607 lines
18 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 = 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));
|
|
}
|