[wsi] Add getMonitorEdid function

What an unbelievable pain this is to do on Windows...

No-op on SDL2 right now, as there is nothing for that.
This commit is contained in:
Joshua Ashton 2022-11-16 16:29:17 +00:00 committed by Philip Rebohle
parent ad3c316d0c
commit 69b1aa251d
3 changed files with 174 additions and 1 deletions

View File

@ -144,4 +144,9 @@ namespace dxvk::wsi {
return true;
}
std::vector<uint8_t> getMonitorEdid(HMONITOR hMonitor) {
Logger::err("getMonitorEdid not implemented on this platform.");
return {};
}
}

View File

@ -1,9 +1,14 @@
#include "../wsi_monitor.h"
#include "../../util/log/log.h"
#include "../../util/util_string.h"
#include <cstring>
#include <setupapi.h>
#include <ntddvdeo.h>
#include <cfgmgr32.h>
namespace dxvk::wsi {
HMONITOR getDefaultMonitor() {
@ -131,4 +136,152 @@ namespace dxvk::wsi {
return retrieveDisplayMode(hMonitor, ENUM_REGISTRY_SETTINGS, pMode);
}
static std::wstring getMonitorDevicePath(HMONITOR hMonitor) {
// Get the device name of the monitor.
MONITORINFOEXW monInfo;
monInfo.cbSize = sizeof(monInfo);
if (!::GetMonitorInfoW(hMonitor, &monInfo)) {
Logger::err("getMonitorDevicePath: Failed to get monitor info.");
return {};
}
// Try and find the monitor device path that matches
// the name of our HMONITOR from the monitor info.
LONG result = ERROR_SUCCESS;
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
do {
uint32_t pathCount = 0, modeCount = 0;
if ((result = ::GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount)) != ERROR_SUCCESS) {
Logger::err(str::format("getMonitorDevicePath: GetDisplayConfigBufferSizes failed. ret: ", result, " LastError: ", GetLastError()));
return {};
}
paths.resize(pathCount);
modes.resize(modeCount);
result = ::QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(), &modeCount, modes.data(), nullptr);
} while (result == ERROR_INSUFFICIENT_BUFFER);
if (result != ERROR_SUCCESS) {
Logger::err(str::format("getMonitorDevicePath: QueryDisplayConfig failed. ret: ", result, " LastError: ", GetLastError()));
return {};
}
// Link a source name -> target name
for (const auto& path : paths) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName;
sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
sourceName.header.size = sizeof(sourceName);
sourceName.header.adapterId = path.sourceInfo.adapterId;
sourceName.header.id = path.sourceInfo.id;
if ((result = ::DisplayConfigGetDeviceInfo(&sourceName.header)) != ERROR_SUCCESS) {
Logger::err(str::format("getMonitorDevicePath: DisplayConfigGetDeviceInfo with DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME failed. ret: ", result, " LastError: ", GetLastError()));
continue;
}
DISPLAYCONFIG_TARGET_DEVICE_NAME targetName;
targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
targetName.header.size = sizeof(targetName);
targetName.header.adapterId = path.targetInfo.adapterId;
targetName.header.id = path.targetInfo.id;
if ((result = ::DisplayConfigGetDeviceInfo(&targetName.header)) != ERROR_SUCCESS) {
Logger::err(str::format("getMonitorDevicePath: DisplayConfigGetDeviceInfo with DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME failed. ret: ", result, " LastError: ", GetLastError()));
continue;
}
// Does the source match the GDI device we are looking for?
// If so, return the target back.
if (!wcscmp(sourceName.viewGdiDeviceName, monInfo.szDevice))
return targetName.monitorDevicePath;
}
Logger::err("getMonitorDevicePath: Failed to find a link from source -> target.");
return {};
}
static WsiEdidData readMonitorEdidFromKey(HKEY deviceRegKey) {
DWORD edidSize = 0;
if (::RegQueryValueExW(deviceRegKey, L"EDID", nullptr, nullptr, nullptr, &edidSize) != ERROR_SUCCESS) {
Logger::err("readMonitorEdidFromKey: Failed to get EDID reg key size");
return {};
}
WsiEdidData edidData(edidSize);
if (::RegQueryValueExW(deviceRegKey, L"EDID", nullptr, nullptr, edidData.data(), &edidSize) != ERROR_SUCCESS) {
Logger::err("readMonitorEdidFromKey: Failed to get EDID reg key data");
return {};
}
return edidData;
}
struct DxvkDeviceInterfaceDetail {
// SP_DEVICE_INTERFACE_DETAIL_DATA_W contains an array called
// "ANYSIZE_ARRAY" which is just 1 wchar_t in size.
// Incredible, safe, and smart API design.
// Allocate some chars after so it can fill these in.
SP_DEVICE_INTERFACE_DETAIL_DATA_W base;
wchar_t extraChars[MAX_DEVICE_ID_LEN];
};
WsiEdidData getMonitorEdid(HMONITOR hMonitor) {
static constexpr GUID GUID_DEVINTERFACE_MONITOR = { 0xe6f07b5f, 0xee97, 0x4a90, 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 };
static auto pfnSetupDiGetClassDevsW = reinterpret_cast<decltype(SetupDiGetClassDevsW)*> (::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiGetClassDevsW"));
static auto pfnSetupDiEnumDeviceInterfaces = reinterpret_cast<decltype(SetupDiEnumDeviceInterfaces)*> (::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiEnumDeviceInterfaces"));
static auto pfnSetupDiGetDeviceInterfaceDetailW = reinterpret_cast<decltype(SetupDiGetDeviceInterfaceDetailW)*>(::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiGetDeviceInterfaceDetailW"));
static auto pfnSetupDiOpenDevRegKey = reinterpret_cast<decltype(SetupDiOpenDevRegKey)*> (::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiOpenDevRegKey"));
static auto pfnSetupDiGetDeviceInstanceIdW = reinterpret_cast<decltype(SetupDiGetDeviceInstanceIdW)*> (::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiGetDeviceInstanceIdW"));
if (!pfnSetupDiGetClassDevsW || !pfnSetupDiEnumDeviceInterfaces || !pfnSetupDiGetDeviceInterfaceDetailW || !pfnSetupDiOpenDevRegKey || !pfnSetupDiGetDeviceInstanceIdW) {
Logger::err("getMonitorEdid: Failed to load functions from setupapi.");
return {};
}
std::wstring monitorDevicePath = getMonitorDevicePath(hMonitor);
if (monitorDevicePath.empty()) {
Logger::err("getMonitorEdid: Failed to get monitor device path.");
return {};
}
const HDEVINFO devInfo = pfnSetupDiGetClassDevsW(&GUID_DEVINTERFACE_MONITOR, nullptr, nullptr, DIGCF_DEVICEINTERFACE);
SP_DEVICE_INTERFACE_DATA interfaceData;
memset(&interfaceData, 0, sizeof(interfaceData));
interfaceData.cbSize = sizeof(interfaceData);
for (DWORD monitorIdx = 0; pfnSetupDiEnumDeviceInterfaces(devInfo, nullptr, &GUID_DEVINTERFACE_MONITOR, monitorIdx, &interfaceData); monitorIdx++) {
DxvkDeviceInterfaceDetail detailData;
// Josh: I'm taking no chances here. I don't trust this API at all.
memset(&detailData, 0, sizeof(detailData));
detailData.base.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
SP_DEVINFO_DATA devInfoData;
memset(&devInfoData, 0, sizeof(devInfoData));
devInfoData.cbSize = sizeof(devInfoData);
if (!pfnSetupDiGetDeviceInterfaceDetailW(devInfo, &interfaceData, &detailData.base, sizeof(detailData), nullptr, &devInfoData))
continue;
// Check that this monitor matches the same one we are looking for.
// Note: For some reason the casing mismatches here, because this
// is a well-designed API.
// If it isn't what we are looking for, move along.
if (_wcsicmp(monitorDevicePath.c_str(), detailData.base.DevicePath) != 0)
continue;
HKEY deviceRegKey = pfnSetupDiOpenDevRegKey(devInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (deviceRegKey == INVALID_HANDLE_VALUE) {
Logger::err("getMonitorEdid: Failed to open monitor device registry key.");
return {};
}
auto edidData = readMonitorEdidFromKey(deviceRegKey);
::RegCloseKey(deviceRegKey);
return edidData;
}
Logger::err("getMonitorEdid: Failed to find device interface for monitor using setupapi.");
return {};
}
}

View File

@ -3,6 +3,7 @@
#include <windows.h>
#include <array>
#include <vector>
#include <cstdint>
namespace dxvk::wsi {
@ -123,5 +124,19 @@ namespace dxvk::wsi {
if (pHeight)
*pHeight = rect.bottom - rect.top;
}
using WsiEdidData = std::vector<uint8_t>;
/**
* \brief Get the EDID of a monitor
*
* Helper function to grab the EDID of a monitor.
* This is needed to get the HDR static metadata + colorimetry
* info of a display for exposing HDR.
*
* \param [in] hMonitor The monitor
* \returns \c EDID if successful, an empty vector if failure.
*/
WsiEdidData getMonitorEdid(HMONITOR hMonitor);
}