Support output to HDR monitors
Co-authored-by: Alvin Wong <alvinhochun@gmail.com> Co-authored-by: Allen Pestaluky <allenpestaluky@gmail.com>
This commit is contained in:
parent
40448082ab
commit
b8389cc76b
65 changed files with 1579 additions and 233 deletions
|
|
@ -57,6 +57,7 @@
|
|||
#endif
|
||||
#if defined(D3D12_ENABLED)
|
||||
#include "drivers/d3d12/rendering_context_driver_d3d12.h"
|
||||
#include <dxgi1_6.h>
|
||||
#endif
|
||||
#if defined(GLES3_ENABLED)
|
||||
#include "drivers/gles3/rasterizer_gles3.h"
|
||||
|
|
@ -150,6 +151,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
|
|||
case FEATURE_STATUS_INDICATOR:
|
||||
case FEATURE_WINDOW_EMBEDDING:
|
||||
case FEATURE_WINDOW_DRAG:
|
||||
case FEATURE_HDR_OUTPUT:
|
||||
return true;
|
||||
case FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE:
|
||||
return (os_ver.dwBuildNumber >= 19041); // Fully supported on Windows 10 Vibranium R1 (2004)+ only, captured as black rect on older versions.
|
||||
|
|
@ -1192,6 +1194,24 @@ static BOOL CALLBACK _MonitorEnumProcCount(HMONITOR hMonitor, HDC hdcMonitor, LP
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL CALLBACK _MonitorEnumProcMonitor(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
|
||||
EnumScreenData *data = (EnumScreenData *)dwData;
|
||||
|
||||
if (data->screen == data->count) {
|
||||
data->monitor = hMonitor;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
data->count++;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static HMONITOR _get_hmonitor_of_screen(int p_screen) {
|
||||
EnumScreenData data = { 0, p_screen, nullptr };
|
||||
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcMonitor, (LPARAM)&data);
|
||||
return data.monitor;
|
||||
}
|
||||
|
||||
int DisplayServerWindows::get_screen_count() const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
|
|
@ -1565,6 +1585,101 @@ Ref<Image> DisplayServerWindows::screen_get_image_rect(const Rect2i &p_rect) con
|
|||
return img;
|
||||
}
|
||||
|
||||
#ifdef D3D12_ENABLED
|
||||
static bool _get_monitor_desc(HMONITOR p_monitor, IDXGIFactory2 *dxgi_factory, DXGI_OUTPUT_DESC1 &r_Desc) {
|
||||
r_Desc = {};
|
||||
|
||||
// Note: As of August, 2025 Microsoft's sample code only checks the default
|
||||
// adapter, but sometimes p_monitor may belong to another adapter.
|
||||
Microsoft::WRL::ComPtr<IDXGIAdapter1> dxgiAdapter;
|
||||
UINT adapter_i = 0;
|
||||
while (dxgi_factory->EnumAdapters1(adapter_i, &dxgiAdapter) == S_OK) {
|
||||
Microsoft::WRL::ComPtr<IDXGIOutput> dxgiOutput;
|
||||
DXGI_OUTPUT_DESC1 desc1;
|
||||
UINT output_i = 0;
|
||||
while (dxgiAdapter->EnumOutputs(output_i, &dxgiOutput) != DXGI_ERROR_NOT_FOUND) {
|
||||
Microsoft::WRL::ComPtr<IDXGIOutput6> output6;
|
||||
if (FAILED(dxgiOutput.As(&output6))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FAILED(output6->GetDesc1(&desc1))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desc1.Monitor == p_monitor) {
|
||||
r_Desc = desc1;
|
||||
return true;
|
||||
}
|
||||
|
||||
output_i++;
|
||||
}
|
||||
adapter_i++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif // D3D12_ENABLED
|
||||
|
||||
// Store a list of displays that have failed to get their SDR white level so we don't spam the error log.
|
||||
static HashSet<Vector3i> displays_with_white_level_error;
|
||||
|
||||
static float _get_sdr_white_level_for_hmonitor(HMONITOR p_monitor, const LocalVector<DISPLAYCONFIG_PATH_INFO> &p_paths) {
|
||||
if (!p_monitor) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
MONITORINFOEXW minfo;
|
||||
memset(&minfo, 0, sizeof(minfo));
|
||||
minfo.cbSize = sizeof(minfo);
|
||||
if (!GetMonitorInfoW(p_monitor, &minfo)) {
|
||||
ERR_FAIL_V_MSG(0.0f, vformat("Failed to get monitor info for HMONITOR: 0x%X", (intptr_t)p_monitor));
|
||||
}
|
||||
|
||||
for (const DISPLAYCONFIG_PATH_INFO &path : p_paths) {
|
||||
const Vector3i key = Vector3i((int32_t)path.sourceInfo.adapterId.HighPart, (int32_t)path.sourceInfo.adapterId.LowPart, (int32_t)path.sourceInfo.id);
|
||||
|
||||
DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name;
|
||||
memset(&source_name, 0, sizeof(source_name));
|
||||
source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
|
||||
source_name.header.size = sizeof(source_name);
|
||||
source_name.header.adapterId = path.sourceInfo.adapterId;
|
||||
source_name.header.id = path.sourceInfo.id;
|
||||
|
||||
if (DisplayConfigGetDeviceInfo(&source_name.header) != ERROR_SUCCESS) {
|
||||
if (!displays_with_white_level_error.has(key)) {
|
||||
WARN_PRINT(vformat("Failed to get source name for adapterId: 0x%08X%08X, id: %d", (uint32_t)path.sourceInfo.adapterId.HighPart, (uint32_t)path.sourceInfo.adapterId.LowPart, path.sourceInfo.id));
|
||||
displays_with_white_level_error.insert(key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wcscmp(minfo.szDevice, source_name.viewGdiDeviceName) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Query the SDR white level.
|
||||
DISPLAYCONFIG_SDR_WHITE_LEVEL sdr_white_level;
|
||||
memset(&sdr_white_level, 0, sizeof(sdr_white_level));
|
||||
sdr_white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
|
||||
sdr_white_level.header.size = sizeof(sdr_white_level);
|
||||
sdr_white_level.header.adapterId = path.targetInfo.adapterId;
|
||||
sdr_white_level.header.id = path.targetInfo.id;
|
||||
|
||||
if (DisplayConfigGetDeviceInfo(&sdr_white_level.header) != ERROR_SUCCESS) {
|
||||
if (!displays_with_white_level_error.has(key)) {
|
||||
WARN_PRINT(vformat("Failed to get SDR white level for adapterId: 0x%08X%08X, id: %d", (uint32_t)path.targetInfo.adapterId.HighPart, (uint32_t)path.targetInfo.adapterId.LowPart, path.targetInfo.id));
|
||||
displays_with_white_level_error.insert(key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return (float)sdr_white_level.SDRWhiteLevel / 1000.0f * 80.0f;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
|
|
@ -3196,6 +3311,96 @@ HWND DisplayServerWindows::_find_window_from_process_id(OS::ProcessID p_pid, HWN
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// Get screen HDR capabilities for internal use only.
|
||||
DisplayServerWindows::ScreenHdrData DisplayServerWindows::_get_screen_hdr_data(int p_screen) const {
|
||||
ScreenHdrData data;
|
||||
HMONITOR monitor = _get_hmonitor_of_screen(p_screen);
|
||||
if (!monitor) {
|
||||
return data;
|
||||
}
|
||||
|
||||
#ifdef D3D12_ENABLED
|
||||
// A dynamic cast is used here because the rendering context is not an Object and Object:cast is not supported.
|
||||
RenderingContextDriverD3D12 *rendering_context_d3d12 = dynamic_cast<RenderingContextDriverD3D12 *>(rendering_context);
|
||||
if (rendering_context_d3d12) {
|
||||
IDXGIFactory2 *dxgi_factory = rendering_context_d3d12->dxgi_factory_get();
|
||||
|
||||
DXGI_OUTPUT_DESC1 desc;
|
||||
if (_get_monitor_desc(monitor, dxgi_factory, desc)) {
|
||||
data.hdr_supported = desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
|
||||
data.min_luminance = desc.MinLuminance;
|
||||
data.max_luminance = desc.MaxLuminance;
|
||||
data.max_average_luminance = desc.MaxFullFrameLuminance;
|
||||
}
|
||||
}
|
||||
#endif // D3D12_ENABLED
|
||||
|
||||
uint32_t path_count = 0;
|
||||
uint32_t mode_count = 0;
|
||||
|
||||
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) == ERROR_SUCCESS) {
|
||||
LocalVector<DISPLAYCONFIG_PATH_INFO> paths;
|
||||
LocalVector<DISPLAYCONFIG_MODE_INFO> modes;
|
||||
paths.resize(path_count);
|
||||
modes.resize(mode_count);
|
||||
|
||||
if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_count, paths.ptr(), &mode_count, modes.ptr(), nullptr) == ERROR_SUCCESS) {
|
||||
data.sdr_white_level = _get_sdr_white_level_for_hmonitor(monitor, paths);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void DisplayServerWindows::_update_hdr_output_for_window(WindowID p_window, const WindowData &p_window_data, ScreenHdrData p_screen_data) {
|
||||
#ifdef RD_ENABLED
|
||||
if (rendering_context) {
|
||||
bool current_hdr_enabled = rendering_context->window_get_hdr_output_enabled(p_window);
|
||||
bool desired_hdr_enabled = p_window_data.hdr_output_requested && p_screen_data.hdr_supported;
|
||||
|
||||
if (current_hdr_enabled != desired_hdr_enabled) {
|
||||
rendering_context->window_set_hdr_output_enabled(p_window, desired_hdr_enabled);
|
||||
rendering_context->window_set_hdr_output_linear_luminance_scale(p_window, 80.0f);
|
||||
}
|
||||
|
||||
// If auto reference luminance is enabled, update it based on the current SDR white level.
|
||||
if (p_window_data.hdr_output_reference_luminance < 0.0f) {
|
||||
if (p_screen_data.sdr_white_level > 0.0f) {
|
||||
rendering_context->window_set_hdr_output_reference_luminance(p_window, p_screen_data.sdr_white_level);
|
||||
}
|
||||
// If we cannot get the SDR white level, leave the previous value unchanged.
|
||||
}
|
||||
|
||||
// If auto max luminance is enabled, update it based on the screen's max luminance.
|
||||
if (p_window_data.hdr_output_max_luminance < 0.0f) {
|
||||
if (p_screen_data.max_luminance > 0.0f) {
|
||||
rendering_context->window_set_hdr_output_max_luminance(p_window, p_screen_data.max_luminance);
|
||||
}
|
||||
// If we cannot get the screen's max luminance, leave the previous value unchanged.
|
||||
}
|
||||
}
|
||||
#endif // RD_ENABLED
|
||||
}
|
||||
|
||||
void DisplayServerWindows::_update_hdr_output_for_tracked_windows() {
|
||||
hdr_output_cache.clear();
|
||||
for (const KeyValue<WindowID, WindowData> &E : windows) {
|
||||
if (E.value.hdr_output_requested) {
|
||||
int screen = window_get_current_screen(E.key);
|
||||
|
||||
ScreenHdrData data;
|
||||
if (!hdr_output_cache.has(screen)) {
|
||||
data = _get_screen_hdr_data(screen);
|
||||
hdr_output_cache.insert(screen, data);
|
||||
} else {
|
||||
data = hdr_output_cache[screen];
|
||||
}
|
||||
|
||||
_update_hdr_output_for_window(E.key, E.value, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Error DisplayServerWindows::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
|
|
@ -3881,6 +4086,10 @@ void DisplayServerWindows::process_events() {
|
|||
Input::get_singleton()->flush_buffered_events();
|
||||
}
|
||||
|
||||
// Poll HDR output state for windows that have requested it.
|
||||
// Needed to detect changes in luminance values due to a lack of Windows events for such changes.
|
||||
_update_hdr_output_for_tracked_windows();
|
||||
|
||||
LocalVector<List<FileDialogData *>::Element *> to_remove;
|
||||
for (List<FileDialogData *>::Element *E = file_dialogs.front(); E; E = E->next()) {
|
||||
FileDialogData *fd = E->get();
|
||||
|
|
@ -4340,6 +4549,157 @@ DisplayServer::VSyncMode DisplayServerWindows::window_get_vsync_mode(WindowID p_
|
|||
return DisplayServer::VSYNC_ENABLED;
|
||||
}
|
||||
|
||||
bool DisplayServerWindows::window_is_hdr_output_supported(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
#if defined(RD_ENABLED)
|
||||
if (rendering_device && !rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) {
|
||||
return false; // HDR output is not supported by the rendering device.
|
||||
}
|
||||
#endif
|
||||
|
||||
// The window supports HDR if the screen it is on supports HDR.
|
||||
int screen = window_get_current_screen(p_window);
|
||||
DisplayServerWindows::ScreenHdrData data = _get_screen_hdr_data(screen);
|
||||
return data.hdr_supported;
|
||||
}
|
||||
|
||||
void DisplayServerWindows::window_request_hdr_output(const bool p_enable, WindowID p_window) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
#if defined(RD_ENABLED)
|
||||
ERR_FAIL_COND_EDMSG(p_enable && (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) == false, "HDR output is not supported by the rendering device.");
|
||||
#endif
|
||||
|
||||
WindowData &wd = windows[p_window];
|
||||
wd.hdr_output_requested = p_enable;
|
||||
|
||||
int screen = window_get_current_screen(p_window);
|
||||
DisplayServerWindows::ScreenHdrData data = _get_screen_hdr_data(screen);
|
||||
_update_hdr_output_for_window(p_window, wd, data);
|
||||
}
|
||||
|
||||
bool DisplayServerWindows::window_is_hdr_output_requested(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
const WindowData &wd = windows[p_window];
|
||||
return wd.hdr_output_requested;
|
||||
}
|
||||
|
||||
bool DisplayServerWindows::window_is_hdr_output_enabled(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
#if defined(RD_ENABLED)
|
||||
if (rendering_context) {
|
||||
return rendering_context->window_get_hdr_output_enabled(p_window);
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisplayServerWindows::window_set_hdr_output_reference_luminance(const float p_reference_luminance, WindowID p_window) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
WindowData &wd = windows[p_window];
|
||||
|
||||
if (Math::is_equal_approx(wd.hdr_output_reference_luminance, p_reference_luminance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
wd.hdr_output_reference_luminance = p_reference_luminance;
|
||||
|
||||
// Negative luminance means auto-adjust
|
||||
if (wd.hdr_output_reference_luminance < 0.0f) {
|
||||
int screen = window_get_current_screen(p_window);
|
||||
DisplayServerWindows::ScreenHdrData data = _get_screen_hdr_data(screen);
|
||||
_update_hdr_output_for_window(p_window, wd, data);
|
||||
} else {
|
||||
// Otherwise, apply the requested luminance
|
||||
#if defined(RD_ENABLED)
|
||||
if (rendering_context) {
|
||||
rendering_context->window_set_hdr_output_reference_luminance(p_window, p_reference_luminance);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
float DisplayServerWindows::window_get_hdr_output_reference_luminance(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
const WindowData &wd = windows[p_window];
|
||||
return wd.hdr_output_reference_luminance;
|
||||
}
|
||||
|
||||
float DisplayServerWindows::window_get_hdr_output_current_reference_luminance(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
#if defined(RD_ENABLED)
|
||||
if (rendering_context) {
|
||||
return rendering_context->window_get_hdr_output_reference_luminance(p_window);
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void DisplayServerWindows::window_set_hdr_output_max_luminance(const float p_max_luminance, WindowID p_window) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
WindowData &wd = windows[p_window];
|
||||
|
||||
if (Math::is_equal_approx(wd.hdr_output_max_luminance, p_max_luminance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
wd.hdr_output_max_luminance = p_max_luminance;
|
||||
|
||||
// Negative luminance means auto-adjust
|
||||
if (wd.hdr_output_max_luminance < 0.0f) {
|
||||
int screen = window_get_current_screen(p_window);
|
||||
DisplayServerWindows::ScreenHdrData data = _get_screen_hdr_data(screen);
|
||||
_update_hdr_output_for_window(p_window, wd, data);
|
||||
} else {
|
||||
// Otherwise, apply the requested luminance
|
||||
#if defined(RD_ENABLED)
|
||||
if (rendering_context) {
|
||||
rendering_context->window_set_hdr_output_max_luminance(p_window, p_max_luminance);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
float DisplayServerWindows::window_get_hdr_output_max_luminance(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
const WindowData &wd = windows[p_window];
|
||||
return wd.hdr_output_max_luminance;
|
||||
}
|
||||
|
||||
float DisplayServerWindows::window_get_hdr_output_current_max_luminance(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
#if defined(RD_ENABLED)
|
||||
if (rendering_context) {
|
||||
return rendering_context->window_get_hdr_output_max_luminance(p_window);
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float DisplayServerWindows::window_get_output_max_linear_value(WindowID p_window) const {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
#if defined(RD_ENABLED)
|
||||
if (rendering_context) {
|
||||
return rendering_context->window_get_output_max_linear_value(p_window);
|
||||
}
|
||||
#endif
|
||||
|
||||
return 1.0f; // SDR
|
||||
}
|
||||
|
||||
void DisplayServerWindows::window_start_drag(WindowID p_window) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
|
|
@ -5818,6 +6178,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
|
|||
}
|
||||
} break;
|
||||
|
||||
case WM_DISPLAYCHANGE: {
|
||||
// Update HDR capabilities and reference luminance when display changes.
|
||||
_update_hdr_output_for_tracked_windows();
|
||||
} break;
|
||||
|
||||
case WM_WINDOWPOSCHANGED: {
|
||||
WindowData &window = windows[window_id];
|
||||
|
||||
|
|
@ -5913,6 +6278,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
|
|||
window.rect_changed_callback.call(Rect2i(window.last_pos.x, window.last_pos.y, window.width, window.height));
|
||||
}
|
||||
|
||||
// Update HDR capabilities and reference luminance when window moves to different screen.
|
||||
_update_hdr_output_for_tracked_windows();
|
||||
|
||||
// Update cursor clip region after window rect has changed.
|
||||
if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) {
|
||||
RECT crect;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue