diff --git a/idd/LGIdd/CEdid.cpp b/idd/LGIdd/CEdid.cpp new file mode 100644 index 00000000..993353d6 --- /dev/null +++ b/idd/LGIdd/CEdid.cpp @@ -0,0 +1,193 @@ +/** + * 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. + */ + +#include "CEdid.h" + +#include +#include + +static const UINT EDID_BLOCK_SIZE = 128; +static const UINT EDID_DTD_SIZE = 18; + +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) +{ + memset(desc, 0, EDID_DTD_SIZE); + desc[3] = 0xfc; + desc[4] = 0x00; + + UINT i = 0; + for (; i < 13 && name[i]; ++i) + desc[5 + i] = (BYTE)name[i]; + if (i < 13) + desc[5 + i++] = 0x0a; + for (; i < 13; ++i) + desc[5 + i] = 0x20; +} + +bool CEdid::WriteDetailedTiming(BYTE* dtd, const CSettings::DisplayMode& mode) +{ + memset(dtd, 0, EDID_DTD_SIZE); + + 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(160, ((hActive / 20) + 7) & ~7UL); + DWORD vBlank = std::max(30, vActive / 20); + if (hBlank > 4095 || vBlank > 4095) + return false; + + DWORD hSync = std::max(32, hActive / 100); + hSync = (hSync + 7) & ~7UL; + DWORD hFront = std::max(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; + + dtd[0] = (BYTE)(pixelClock10KHz & 0xff); + dtd[1] = (BYTE)((pixelClock10KHz >> 8) & 0xff); + dtd[2] = (BYTE)(hActive & 0xff); + dtd[3] = (BYTE)(hBlank & 0xff); + dtd[4] = (BYTE)(((hActive >> 8) & 0x0f) << 4 | ((hBlank >> 8) & 0x0f)); + dtd[5] = (BYTE)(vActive & 0xff); + dtd[6] = (BYTE)(vBlank & 0xff); + dtd[7] = (BYTE)(((vActive >> 8) & 0x0f) << 4 | ((vBlank >> 8) & 0x0f)); + dtd[8] = (BYTE)(hFront & 0xff); + dtd[9] = (BYTE)(hSync & 0xff); + dtd[10] = (BYTE)((vFront & 0x0f) << 4 | (vSync & 0x0f)); + dtd[11] = (BYTE)(((hFront >> 8) & 0x03) << 6 | + (((hSync >> 8) & 0x03) << 4) | + (((vFront >> 4) & 0x03) << 2) | + ((vSync >> 4) & 0x03)); + + // 52cm x 29cm is a conventional 16:9 desktop monitor size. + const DWORD imageWidth = 520; + const DWORD imageHeight = 290; + dtd[12] = (BYTE)(imageWidth & 0xff); + dtd[13] = (BYTE)(imageHeight & 0xff); + dtd[14] = (BYTE)(((imageWidth >> 8) & 0x0f) << 4 | ((imageHeight >> 8) & 0x0f)); + dtd[17] = 0x1e; // digital separate sync, positive hsync/vsync + return true; +} + +void CEdid::Build(const CSettings::DisplayModes& modes) +{ + m_data.assign(EDID_BLOCK_SIZE * 2, 0); + BYTE* base = m_data.data(); + BYTE* cta = base + EDID_BLOCK_SIZE; + + static const BYTE header[8] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; + memcpy(base, header, sizeof(header)); + + // Manufacturer ID: LGD (5-bit EISA ID), product/serial values are arbitrary. + base[8] = 0x30; + base[9] = 0xe4; + base[10] = 0xdd; + base[11] = 0x1d; + base[12] = 0x01; + base[16] = 1; + base[17] = 36; // 1990 + 36 = 2026 + base[18] = 1; + base[19] = 4; + base[20] = 0xa5; // digital input, 10-bit capable + base[21] = 52; + base[22] = 29; + base[23] = 0x78; // gamma 2.2 + base[24] = 0x0a; // preferred timing, RGB color + + for (UINT i = 38; i < 54; i += 2) + { + base[i] = 0x01; + base[i + 1] = 0x01; + } + + 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 dtdOffset = 54; + for (; modeIndex < sorted.size() && dtdOffset < 54 + EDID_DTD_SIZE * 3; ++modeIndex) + { + if (WriteDetailedTiming(base + dtdOffset, sorted[modeIndex])) + dtdOffset += EDID_DTD_SIZE; + } + WriteMonitorName(base + 54 + EDID_DTD_SIZE * 3, "Looking Glass"); + + base[126] = 1; + SetChecksum(base); + + cta[0] = 0x02; // CTA-861 extension + cta[1] = 0x03; + + UINT dataOffset = 4; + // CTA extended colorimetry data block: advertise BT.2020 colorimetry. + cta[dataOffset++] = (7 << 5) | 3; + cta[dataOffset++] = 0x05; + cta[dataOffset++] = 0xe0; + cta[dataOffset++] = 0x00; + + // CTA HDR Static Metadata data block: SDR, traditional HDR, PQ and HLG with type 1 metadata. + cta[dataOffset++] = (7 << 5) | 4; + cta[dataOffset++] = 0x06; + cta[dataOffset++] = 0x0f; + cta[dataOffset++] = 0x01; + cta[dataOffset++] = 0x00; + + UINT ctaDtdOffset = dataOffset; + if (ctaDtdOffset < 4) + ctaDtdOffset = 4; + + UINT ctaDtdWrite = ctaDtdOffset; + for (; modeIndex < sorted.size() && ctaDtdWrite + EDID_DTD_SIZE <= EDID_BLOCK_SIZE - 1; ++modeIndex) + { + if (WriteDetailedTiming(cta + ctaDtdWrite, sorted[modeIndex])) + ctaDtdWrite += EDID_DTD_SIZE; + } + + cta[2] = (BYTE)ctaDtdOffset; + cta[3] = 0x00; + SetChecksum(cta); +} diff --git a/idd/LGIdd/CEdid.h b/idd/LGIdd/CEdid.h new file mode 100644 index 00000000..54d822c1 --- /dev/null +++ b/idd/LGIdd/CEdid.h @@ -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. + */ + +#pragma once + +#include +#include +#include + +#include "CSettings.h" + +class CEdid +{ +public: + void Build(const CSettings::DisplayModes& modes); + + const BYTE* Data() const { return m_data.empty() ? nullptr : m_data.data(); } + UINT Size() const { return (UINT)m_data.size(); } + +private: + std::vector m_data; + + static void SetChecksum(BYTE* block); + static bool WriteDetailedTiming(BYTE* dtd, const CSettings::DisplayMode& mode); + static void WriteMonitorName(BYTE* desc, const char* name); +}; diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index c7c1e886..7cd61ccb 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -51,6 +51,8 @@ void CIndirectDeviceContext::PopulateDefaultModes() m_displayModes.reserve(g_settings.GetDisplayModes().size()); for (auto& dm : g_settings.GetDisplayModes()) m_displayModes.push_back(dm); + + m_edid.Build(m_displayModes); } void CIndirectDeviceContext::InitAdapter() @@ -143,8 +145,8 @@ void CIndirectDeviceContext::FinishInit(UINT connectorIndex) info.MonitorDescription.Size = sizeof(info.MonitorDescription); info.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID; - info.MonitorDescription.DataSize = 0; - info.MonitorDescription.pData = nullptr; + info.MonitorDescription.DataSize = m_edid.Size(); + info.MonitorDescription.pData = const_cast(m_edid.Data()); CoCreateGuid(&info.MonitorContainerId); diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index 25af272c..1dcaf5e6 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -27,6 +27,7 @@ #include "CIVSHMEM.h" #include "CSettings.h" +#include "CEdid.h" extern "C" { #include "lgmp/host.h" @@ -89,6 +90,7 @@ private: void ResendCursor(); CSettings::DisplayModes m_displayModes; + CEdid m_edid; CSettings::DisplayMode m_setMode; bool m_doSetMode; diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index 970f4c29..71451aaa 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -26,6 +26,7 @@ + @@ -45,6 +46,7 @@ +