[idd] CEdid: add new helper class to build the EDID block

This commit is contained in:
Geoffrey McRae
2026-06-02 13:15:29 +10:00
committed by Geoffrey McRae
parent 83bebf4519
commit c2add993ac
5 changed files with 235 additions and 2 deletions

193
idd/LGIdd/CEdid.cpp Normal file
View File

@@ -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 <algorithm>
#include <string.h>
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<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;
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);
}

34
idd/LGIdd/CEdid.h Normal file
View 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.
*/
#pragma once
#include <Windows.h>
#include <stdint.h>
#include <vector>
#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<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);
};

View File

@@ -51,6 +51,8 @@ void CIndirectDeviceContext::PopulateDefaultModes()
m_displayModes.reserve(g_settings.GetDisplayModes().size()); m_displayModes.reserve(g_settings.GetDisplayModes().size());
for (auto& dm : g_settings.GetDisplayModes()) for (auto& dm : g_settings.GetDisplayModes())
m_displayModes.push_back(dm); m_displayModes.push_back(dm);
m_edid.Build(m_displayModes);
} }
void CIndirectDeviceContext::InitAdapter() void CIndirectDeviceContext::InitAdapter()
@@ -143,8 +145,8 @@ void CIndirectDeviceContext::FinishInit(UINT connectorIndex)
info.MonitorDescription.Size = sizeof(info.MonitorDescription); info.MonitorDescription.Size = sizeof(info.MonitorDescription);
info.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID; info.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID;
info.MonitorDescription.DataSize = 0; info.MonitorDescription.DataSize = m_edid.Size();
info.MonitorDescription.pData = nullptr; info.MonitorDescription.pData = const_cast<BYTE*>(m_edid.Data());
CoCreateGuid(&info.MonitorContainerId); CoCreateGuid(&info.MonitorContainerId);

View File

@@ -27,6 +27,7 @@
#include "CIVSHMEM.h" #include "CIVSHMEM.h"
#include "CSettings.h" #include "CSettings.h"
#include "CEdid.h"
extern "C" { extern "C" {
#include "lgmp/host.h" #include "lgmp/host.h"
@@ -89,6 +90,7 @@ private:
void ResendCursor(); void ResendCursor();
CSettings::DisplayModes m_displayModes; CSettings::DisplayModes m_displayModes;
CEdid m_edid;
CSettings::DisplayMode m_setMode; CSettings::DisplayMode m_setMode;
bool m_doSetMode; bool m_doSetMode;

View File

@@ -26,6 +26,7 @@
<ItemGroup> <ItemGroup>
<ClCompile Include="$(SolutionDir)LGCommon/*.cpp" /> <ClCompile Include="$(SolutionDir)LGCommon/*.cpp" />
<ClCompile Include="CD3D12CommandQueue.cpp" /> <ClCompile Include="CD3D12CommandQueue.cpp" />
<ClCompile Include="CEdid.cpp" />
<ClCompile Include="CFrameBufferPool.cpp" /> <ClCompile Include="CFrameBufferPool.cpp" />
<ClCompile Include="CFrameBufferResource.cpp" /> <ClCompile Include="CFrameBufferResource.cpp" />
<ClCompile Include="CIndirectDeviceContext.cpp" /> <ClCompile Include="CIndirectDeviceContext.cpp" />
@@ -45,6 +46,7 @@
<ItemGroup> <ItemGroup>
<ClInclude Include="$(SolutionDir)/LGCommon/*.h" /> <ClInclude Include="$(SolutionDir)/LGCommon/*.h" />
<ClInclude Include="CD3D12CommandQueue.h" /> <ClInclude Include="CD3D12CommandQueue.h" />
<ClInclude Include="CEdid.h" />
<ClInclude Include="CFrameBufferPool.h" /> <ClInclude Include="CFrameBufferPool.h" />
<ClInclude Include="CFrameBufferResource.h" /> <ClInclude Include="CFrameBufferResource.h" />
<ClInclude Include="CIndirectMonitorContext.h" /> <ClInclude Include="CIndirectMonitorContext.h" />