Skip to content
Merged
221 changes: 106 additions & 115 deletions src/Features/HDRDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,147 +57,125 @@ typedef struct DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2
// https://github.com/Filoppi/Luma-Framework/blob/f1fbc2a36f2d24fd551721ce90f26821a8e754c1/Source/Core/utils/display.hpp
namespace
{
bool GetDisplayConfigPathInfo(HWND hwnd, DISPLAYCONFIG_PATH_INFO& outPathInfo)
// Returns the GDI device name for the output the swap chain is presenting to.
// Uses GetContainingOutput which works for both windowed and borderless-fullscreen.
// Returns false if the swap chain's output cannot be determined (e.g. Streamline
// replaces the swap chain with a wrapper that does not implement GetContainingOutput).
bool GetSwapChainOutputDeviceName(IDXGISwapChain* swapChain, WCHAR (&outDeviceName)[32])
{
winrt::com_ptr<IDXGIOutput> output;
if (FAILED(swapChain->GetContainingOutput(output.put())))
return false;
DXGI_OUTPUT_DESC desc{};
if (FAILED(output->GetDesc(&desc)))
return false;
wcsncpy_s(outDeviceName, desc.DeviceName, _TRUNCATE);
// DeviceName is ASCII (e.g. "\\.\DISPLAY1") — safe to log as narrow string
char narrowName[32]{};
WideCharToMultiByte(CP_UTF8, 0, desc.DeviceName, -1, narrowName, sizeof(narrowName), nullptr, nullptr);
logger::debug("[HDR] Swap chain output device: {}", narrowName);
return true;
}

bool GetDisplayConfigPathInfo(IDXGISwapChain* swapChain, DISPLAYCONFIG_PATH_INFO& outPathInfo)
{
WCHAR deviceName[32]{};
if (!GetSwapChainOutputDeviceName(swapChain, deviceName)) {
logger::warn("[HDR] GetContainingOutput failed - cannot determine monitor for HDR detection (Streamline/frame-gen?)");
return false;
}

uint32_t pathCount, modeCount;
if (ERROR_SUCCESS != GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount))
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount) != ERROR_SUCCESS)
return false;

std::vector<DISPLAYCONFIG_PATH_INFO> paths(pathCount);
std::vector<DISPLAYCONFIG_MODE_INFO> modes(modeCount);
if (ERROR_SUCCESS != QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(), &modeCount, modes.data(), nullptr))
if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(), &modeCount, modes.data(), nullptr) != ERROR_SUCCESS)
return false;

const HMONITOR monitorFromWindow = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
for (auto& pathInfo : paths) {
if (pathInfo.flags & DISPLAYCONFIG_PATH_ACTIVE && pathInfo.sourceInfo.statusFlags & DISPLAYCONFIG_SOURCE_IN_USE) {
const bool bVirtual = pathInfo.flags & DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE;
const uint32_t modeIndex = bVirtual ? pathInfo.sourceInfo.sourceModeInfoIdx : pathInfo.sourceInfo.modeInfoIdx;
if (modeIndex == DISPLAYCONFIG_PATH_MODE_IDX_INVALID || modeIndex >= modeCount)
continue;
const DISPLAYCONFIG_SOURCE_MODE& sourceMode = modes[modeIndex].sourceMode;

RECT rect{ sourceMode.position.x, sourceMode.position.y, sourceMode.position.x + (LONG)sourceMode.width, sourceMode.position.y + (LONG)sourceMode.height };
if (!IsRectEmpty(&rect)) {
const HMONITOR monitorFromMode = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
if (monitorFromMode != nullptr && monitorFromMode == monitorFromWindow) {
outPathInfo = pathInfo;
return true;
}
// DISPLAYCONFIG_SOURCE_IN_USE guards against inactive sources on multi-monitor
// setups where a disconnected display may still appear in the path table
if (!(pathInfo.flags & DISPLAYCONFIG_PATH_ACTIVE) ||
!(pathInfo.sourceInfo.statusFlags & DISPLAYCONFIG_SOURCE_IN_USE))
continue;

// QDC_ONLY_ACTIVE_PATHS never returns virtual-mode paths, so no
// DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE index selection is needed here
DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName{};
sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
sourceName.header.size = sizeof(sourceName);
sourceName.header.adapterId = pathInfo.sourceInfo.adapterId;
sourceName.header.id = pathInfo.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&sourceName.header) == ERROR_SUCCESS) {
if (wcscmp(sourceName.viewGdiDeviceName, deviceName) == 0) {
outPathInfo = pathInfo;
return true;
}
}
}
logger::warn("[HDR] No DisplayConfig path matched output device name");
return false;
}

bool GetAdvancedColorInfo(HWND hwnd, DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO& outColorInfo)
bool IsHDRSupportedAndEnabled(IDXGISwapChain* swapChain, bool& supported, bool& enabled)
{
DISPLAYCONFIG_PATH_INFO pathInfo{};
if (GetDisplayConfigPathInfo(hwnd, pathInfo)) {
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO colorInfo{};
colorInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
colorInfo.header.size = sizeof(colorInfo);
colorInfo.header.adapterId = pathInfo.targetInfo.adapterId;
colorInfo.header.id = pathInfo.targetInfo.id;
if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&colorInfo.header)) {
outColorInfo = colorInfo;
return true;
}
}
return false;
}
supported = false;
enabled = false;

// Win11 24H2+ API - uses runtime detection, will fail gracefully on older Windows
bool GetAdvancedColorInfo2(HWND hwnd, DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2& outColorInfo2)
{
DISPLAYCONFIG_PATH_INFO pathInfo{};
if (GetDisplayConfigPathInfo(hwnd, pathInfo)) {
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 colorInfo2{};
colorInfo2.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2;
colorInfo2.header.size = sizeof(colorInfo2);
colorInfo2.header.adapterId = pathInfo.targetInfo.adapterId;
colorInfo2.header.id = pathInfo.targetInfo.id;
// This will fail on older Windows versions that don't support the API
if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&colorInfo2.header)) {
outColorInfo2 = colorInfo2;
return true;
}
}
return false;
}

bool CheckSwapChainHDRSupport(IDXGISwapChain* swapChain, bool& supported, bool& enabled)
{
winrt::com_ptr<IDXGIOutput> output;
if (SUCCEEDED(swapChain->GetContainingOutput(output.put()))) {
winrt::com_ptr<IDXGIOutput6> output6;
if (SUCCEEDED(output->QueryInterface(IID_PPV_ARGS(output6.put())))) {
DXGI_OUTPUT_DESC1 desc1;
if (SUCCEEDED(output6->GetDesc1(&desc1))) {
enabled = desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 ||
desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
supported |= enabled;
logger::debug("[HDR] DXGI output detection: colorSpace={}, maxLuminance={}", static_cast<int>(desc1.ColorSpace), desc1.MaxLuminance);
if (!GetDisplayConfigPathInfo(swapChain, pathInfo)) {
// GetContainingOutput can fail under Streamline/frame-gen wrappers. Fall back to
// IDXGIOutput6::GetDesc1 which reads the output's current color space directly.
// This only detects whether HDR is currently active, not whether the monitor
// supports it while Windows HDR is off, but is better than reporting nothing.
winrt::com_ptr<IDXGIOutput> output;
if (SUCCEEDED(swapChain->GetContainingOutput(output.put()))) {
winrt::com_ptr<IDXGIOutput6> output6;
if (SUCCEEDED(output->QueryInterface(IID_PPV_ARGS(output6.put())))) {
DXGI_OUTPUT_DESC1 desc1{};
if (SUCCEEDED(output6->GetDesc1(&desc1))) {
enabled = desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
supported = enabled;
logger::debug("[HDR] DXGI fallback detection: colorSpace={}", static_cast<int>(desc1.ColorSpace));
}
}
}
return false;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

winrt::com_ptr<IDXGISwapChain3> swapChain3;
if (SUCCEEDED(swapChain->QueryInterface(IID_PPV_ARGS(swapChain3.put())))) {
UINT colorSpaceSupported = 0;
if (SUCCEEDED(swapChain3->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, &colorSpaceSupported))) {
supported |= (colorSpaceSupported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) != 0;
}
if (SUCCEEDED(swapChain3->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709, &colorSpaceSupported))) {
supported |= (colorSpaceSupported & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) != 0;
}
}
return true;
}

bool IsHDRSupportedAndEnabled(HWND hwnd, bool& supported, bool& enabled, IDXGISwapChain* swapChain = nullptr)
{
supported = false;
enabled = false;

// Try Windows 11 24H2+ API first - distinguishes HDR from WCG
// Try Windows 11 24H2+ API first - directly reports HDR hardware capability
// Credits: renodx by clshortfuse (MIT License)
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 colorInfo2{};
if (GetAdvancedColorInfo2(hwnd, colorInfo2)) {
// WCG (Wide Color Gamut) allows wider color range without higher brightness peak
// We only consider true HDR mode, not WCG
colorInfo2.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2;
colorInfo2.header.size = sizeof(colorInfo2);
colorInfo2.header.adapterId = pathInfo.targetInfo.adapterId;
colorInfo2.header.id = pathInfo.targetInfo.id;
if (DisplayConfigGetDeviceInfo(&colorInfo2.header) == ERROR_SUCCESS) {
supported = colorInfo2.highDynamicRangeSupported != 0;
enabled = colorInfo2.activeColorMode == DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR;
supported = enabled || (colorInfo2.highDynamicRangeSupported && !colorInfo2.advancedColorLimitedByPolicy);
// Copy bitfield members to avoid non-const reference binding issues
UINT32 hdrSupported = colorInfo2.highDynamicRangeSupported;
UINT32 limitedByPolicy = colorInfo2.advancedColorLimitedByPolicy;
logger::debug("[HDR] Win11 24H2 detection: activeColorMode={}, hdrSupported={}, limitedByPolicy={}",
static_cast<int>(colorInfo2.activeColorMode), hdrSupported, limitedByPolicy);
UINT32 activeMode = static_cast<UINT32>(colorInfo2.activeColorMode);
logger::debug("[HDR] Win11 24H2 detection: highDynamicRangeSupported={}, activeColorMode={}", hdrSupported, activeMode);
return true;
}

// Fallback for older Windows versions
DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO colorInfo{};
if (GetAdvancedColorInfo(hwnd, colorInfo)) {
enabled = colorInfo.advancedColorEnabled;
supported = enabled || (colorInfo.advancedColorSupported && !colorInfo.advancedColorForceDisabled);
// Copy bitfield members to avoid non-const reference binding issues
UINT32 advancedEnabled = colorInfo.advancedColorEnabled;
colorInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
colorInfo.header.size = sizeof(colorInfo);
colorInfo.header.adapterId = pathInfo.targetInfo.adapterId;
colorInfo.header.id = pathInfo.targetInfo.id;
if (DisplayConfigGetDeviceInfo(&colorInfo.header) == ERROR_SUCCESS) {
supported = colorInfo.advancedColorSupported != 0;
enabled = colorInfo.advancedColorEnabled != 0;
UINT32 advancedSupported = colorInfo.advancedColorSupported;
UINT32 forceDisabled = colorInfo.advancedColorForceDisabled;
logger::debug("[HDR] Legacy detection: advancedColorEnabled={}, advancedColorSupported={}, forceDisabled={}",
advancedEnabled, advancedSupported, forceDisabled);
UINT32 advancedEnabled = colorInfo.advancedColorEnabled;
logger::debug("[HDR] Legacy detection: advancedColorSupported={}, advancedColorEnabled={}", advancedSupported, advancedEnabled);
return true;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Last resort: check swap chain color space support
if (swapChain) {
__try {
CheckSwapChainHDRSupport(swapChain, supported, enabled);
} __except (EXCEPTION_EXECUTE_HANDLER) {
logger::warn("[HDR] Exception during swap chain HDR detection (possibly due to Streamline interposer), skipping swap chain queries");
}
}

return false;
}

Expand Down Expand Up @@ -248,18 +226,19 @@ namespace
}

bool HDRDisplay::isHDRMonitor = false;
bool HDRDisplay::isHDRCapableMonitor = false;

bool HDRDisplay::DetectHDR()
{
bool hdrSupported = false;
bool hdrEnabled = false;

HWND hwnd = reinterpret_cast<HWND>(RE::BSGraphics::Renderer::GetSingleton()->GetCurrentRenderWindow());
IsHDRSupportedAndEnabled(hwnd, hdrSupported, hdrEnabled, globals::d3d::swapChain);
IsHDRSupportedAndEnabled(globals::d3d::swapChain, hdrSupported, hdrEnabled);

isHDRMonitor = hdrSupported;
isHDRMonitor = hdrEnabled;
isHDRCapableMonitor = hdrSupported;
logger::info("[HDR] HDR display detection: supported={}, enabled={}", hdrSupported, hdrEnabled);
return hdrSupported;
return hdrEnabled;
}

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
Expand All @@ -275,6 +254,12 @@ void HDRDisplay::DrawSettings()
{
if (isHDRMonitor) {
ImGui::TextColored(Util::Colors::GetSuccess(), "HDR Display Detected");
} else if (isHDRCapableMonitor) {
ImGui::TextColored(Util::Colors::GetWarning(), "HDR Capable Display (Windows HDR is off)");
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Your monitor supports HDR, but Windows HDR is currently disabled.");
ImGui::Text("Enable HDR in Windows Display Settings to allow auto-detection.");
}
} else {
ImGui::TextColored(Util::Colors::GetWarning(), "SDR Display (HDR not detected)");
}
Expand Down Expand Up @@ -318,12 +303,14 @@ void HDRDisplay::DrawSettings()
if (auto _tt = Util::HoverTooltipWrapper()) {
if (isHDRMonitor) {
ImGui::Text("Enable HDR output. Matches vanilla visuals with extended dynamic range.");
} else if (isHDRCapableMonitor) {
ImGui::Text("Monitor supports HDR but Windows HDR is off. Enable HDR in Windows Display Settings, then restart the game.");
} else {
ImGui::Text("HDR display not detected. Use Advanced button to override.");
}
}

// Advanced override button for SDR monitors
// Advanced override button — shown when HDR is neither active nor auto-detected
if (!isHDRMonitor && !oldEnableHDR) {
ImGui::SameLine();
if (ImGui::Button("Advanced")) {
Expand All @@ -348,7 +335,11 @@ void HDRDisplay::DrawSettings()
}
}
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Force enable HDR even without detection (not recommended).");
if (isHDRCapableMonitor) {
ImGui::Text("Enable Windows HDR instead of forcing it here.");
} else {
ImGui::Text("Force enable HDR even without detection (not recommended).");
}
}
}

Expand Down Expand Up @@ -528,8 +519,8 @@ void HDRDisplay::LoadSettings(json& o_json)

settings = o_json;

// Defer auto-detection to SetupResources where the renderer is available.
// DetectHDR() needs a valid HWND which doesn't exist during early plugin init.
// Defer auto-detection to SetupResources where the swap chain is available.
// DetectHDR() needs globals::d3d::swapChain which isn't valid during early plugin init.
// hdrAutoDetected starts false in defaults and is only set true after auto-detect
// completes in SetupResources, so this correctly triggers on first launch even
// when the default config was auto-generated with enableHDR: false.
Expand Down
5 changes: 3 additions & 2 deletions src/Features/HDRDisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ struct HDRDisplay : public Feature
ID3D11ComputeShader* uiBrightnessCS = nullptr;
ID3D11ComputeShader* GetUIBrightnessCS();

static bool DetectHDR();
static bool isHDRMonitor;
static bool DetectHDR(); // Returns true if Windows HDR is currently active (enabled in OS settings)
static bool isHDRMonitor; // Windows HDR is active (enabled in OS settings)
static bool isHDRCapableMonitor; // Monitor supports HDR but Windows HDR may be off
bool pendingAutoDetect = false;

float GetDisplayMaxLuminance() const;
Expand Down
Loading