diff --git a/src/wsi/sdl2/wsi_monitor_sdl2.cpp b/src/wsi/sdl2/wsi_monitor_sdl2.cpp index 1ab82301c..bb2fd3f86 100644 --- a/src/wsi/sdl2/wsi_monitor_sdl2.cpp +++ b/src/wsi/sdl2/wsi_monitor_sdl2.cpp @@ -144,4 +144,9 @@ namespace dxvk::wsi { return true; } + std::vector getMonitorEdid(HMONITOR hMonitor) { + Logger::err("getMonitorEdid not implemented on this platform."); + return {}; + } + } \ No newline at end of file diff --git a/src/wsi/win32/wsi_monitor_win32.cpp b/src/wsi/win32/wsi_monitor_win32.cpp index be466db58..3d7913e87 100644 --- a/src/wsi/win32/wsi_monitor_win32.cpp +++ b/src/wsi/win32/wsi_monitor_win32.cpp @@ -1,9 +1,14 @@ #include "../wsi_monitor.h" #include "../../util/log/log.h" +#include "../../util/util_string.h" #include +#include +#include +#include + 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 paths; + std::vector 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 (::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiGetClassDevsW")); + static auto pfnSetupDiEnumDeviceInterfaces = reinterpret_cast (::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiEnumDeviceInterfaces")); + static auto pfnSetupDiGetDeviceInterfaceDetailW = reinterpret_cast(::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiGetDeviceInterfaceDetailW")); + static auto pfnSetupDiOpenDevRegKey = reinterpret_cast (::GetProcAddress(::GetModuleHandleW(L"setupapi.dll"), "SetupDiOpenDevRegKey")); + static auto pfnSetupDiGetDeviceInstanceIdW = reinterpret_cast (::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 {}; + } + } \ No newline at end of file diff --git a/src/wsi/wsi_monitor.h b/src/wsi/wsi_monitor.h index c7e33578e..84c00b087 100644 --- a/src/wsi/wsi_monitor.h +++ b/src/wsi/wsi_monitor.h @@ -3,6 +3,7 @@ #include #include +#include #include namespace dxvk::wsi { @@ -123,5 +124,19 @@ namespace dxvk::wsi { if (pHeight) *pHeight = rect.bottom - rect.top; } - + + using WsiEdidData = std::vector; + + /** + * \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); + }