From 3f29efc8a2063e694f3c9ac02cf14bf617da5029 Mon Sep 17 00:00:00 2001 From: ArchercatNEO Date: Wed, 29 Apr 2026 14:01:37 +0100 Subject: [PATCH] HDR: Implement surface supports HDR output. Previously the Wayland display server would attempt to enable HDR output and try to detect if it failed afterwards which had some issues. Now we can query the rendering driver for support and avoid ever enabling HDR output when this would fail. --- .../display_server_apple_embedded.mm | 13 +++++ .../d3d12/rendering_device_driver_d3d12.cpp | 5 ++ drivers/d3d12/rendering_device_driver_d3d12.h | 1 + .../metal/rendering_device_driver_metal.cpp | 5 ++ drivers/metal/rendering_device_driver_metal.h | 1 + .../vulkan/rendering_device_driver_vulkan.cpp | 52 +++++++++++++++++++ .../vulkan/rendering_device_driver_vulkan.h | 1 + .../wayland/display_server_wayland.cpp | 48 +++++------------ platform/macos/display_server_macos_base.mm | 13 +++++ platform/windows/display_server_windows.cpp | 13 +++++ servers/rendering/rendering_device.cpp | 9 ++++ servers/rendering/rendering_device.h | 1 + servers/rendering/rendering_device_driver.h | 3 ++ 13 files changed, 130 insertions(+), 35 deletions(-) diff --git a/drivers/apple_embedded/display_server_apple_embedded.mm b/drivers/apple_embedded/display_server_apple_embedded.mm index 03ce56b999..bfa1de4c53 100644 --- a/drivers/apple_embedded/display_server_apple_embedded.mm +++ b/drivers/apple_embedded/display_server_apple_embedded.mm @@ -854,30 +854,43 @@ void DisplayServerAppleEmbedded::current_edr_headroom_changed() { bool DisplayServerAppleEmbedded::window_is_hdr_output_supported(DisplayServerEnums::WindowID p_window) const { bool renderer_supports_hdr_output = false; + bool surface_supports_hdr_output = false; #if defined(RD_ENABLED) if (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) { renderer_supports_hdr_output = true; + surface_supports_hdr_output = rendering_device->screen_get_hdr_output_supported(p_window); } #endif if (!renderer_supports_hdr_output) { return false; } + if (!surface_supports_hdr_output) { + return false; + } + return _screen_hdr_is_supported(); } void DisplayServerAppleEmbedded::window_request_hdr_output(const bool p_enabled, DisplayServerEnums::WindowID p_window) { if (p_enabled) { bool renderer_supports_hdr_output = false; + bool surface_supports_hdr_output = false; #if defined(RD_ENABLED) if (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) { renderer_supports_hdr_output = true; + surface_supports_hdr_output = rendering_device->screen_get_hdr_output_supported(p_window); } #endif if (!renderer_supports_hdr_output) { WARN_PRINT("HDR output requested, but is not supported by the renderer or rendering device driver."); return; } + + if (!surface_supports_hdr_output) { + WARN_PRINT("HDR output requested, but the window does not support an HDR format."); + return; + } } edr_requested = p_enabled; diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index 21d2d3c002..d5b0158998 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -2977,6 +2977,11 @@ RDD::ColorSpace RenderingDeviceDriverD3D12::swap_chain_get_color_space(SwapChain return swap_chain->color_space; } +bool RenderingDeviceDriverD3D12::swap_chain_get_hdr_output_supported(SwapChainID p_swap_chain) { + // Our minimum supported version of D3D12 requires support for our HDR colorspaces. + return true; +} + void RenderingDeviceDriverD3D12::swap_chain_free(SwapChainID p_swap_chain) { SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id); _swap_chain_release(swap_chain); diff --git a/drivers/d3d12/rendering_device_driver_d3d12.h b/drivers/d3d12/rendering_device_driver_d3d12.h index e045349241..14b3c419de 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.h +++ b/drivers/d3d12/rendering_device_driver_d3d12.h @@ -551,6 +551,7 @@ public: virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override; virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override; virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) override; + virtual bool swap_chain_get_hdr_output_supported(SwapChainID p_swap_chain) override; virtual void swap_chain_free(SwapChainID p_swap_chain) override; /*********************/ diff --git a/drivers/metal/rendering_device_driver_metal.cpp b/drivers/metal/rendering_device_driver_metal.cpp index fa255150fa..bb045e47dd 100644 --- a/drivers/metal/rendering_device_driver_metal.cpp +++ b/drivers/metal/rendering_device_driver_metal.cpp @@ -983,6 +983,11 @@ RDD::ColorSpace RenderingDeviceDriverMetal::swap_chain_get_color_space(SwapChain return swap_chain->color_space; } +bool RenderingDeviceDriverMetal::swap_chain_get_hdr_output_supported(SwapChainID p_swap_chain) { + // Our minimum supported version of metal requires support for EDR. + return true; +} + void RenderingDeviceDriverMetal::swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) { SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id); RenderingContextDriverMetal::Surface *metal_surface = (RenderingContextDriverMetal::Surface *)(swap_chain->surface); diff --git a/drivers/metal/rendering_device_driver_metal.h b/drivers/metal/rendering_device_driver_metal.h index e809db8ce0..6e9050b438 100644 --- a/drivers/metal/rendering_device_driver_metal.h +++ b/drivers/metal/rendering_device_driver_metal.h @@ -316,6 +316,7 @@ public: virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override final; virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final; virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) override final; + virtual bool swap_chain_get_hdr_output_supported(SwapChainID p_swap_chain) override final; virtual void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) override final; virtual void swap_chain_free(SwapChainID p_swap_chain) override final; diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 2dbc50b74f..3383e2310f 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -4008,6 +4008,58 @@ RDD::ColorSpace RenderingDeviceDriverVulkan::swap_chain_get_color_space(SwapChai return swap_chain->rdd_color_space; } +bool RenderingDeviceDriverVulkan::swap_chain_get_hdr_output_supported(SwapChainID p_swap_chain) { + DEV_ASSERT(p_swap_chain.id != 0); + + SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id); + RenderingContextDriverVulkan::Surface *surface = (RenderingContextDriverVulkan::Surface *)(swap_chain->surface); + const RenderingContextDriverVulkan::Functions &functions = context_driver->functions_get(); + + // Retrieve the formats supported by the surface. + uint32_t format_count = 0; + VkResult err = functions.GetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface->vk_surface, &format_count, nullptr); + ERR_FAIL_COND_V(err != VK_SUCCESS, false); + + TightLocalVector formats; + formats.resize(format_count); + err = functions.GetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface->vk_surface, &format_count, formats.ptr()); + ERR_FAIL_COND_V(err != VK_SUCCESS, false); + + // If the format list includes just one entry of VK_FORMAT_UNDEFINED, the surface has no preferred format. + // Just to be safe, we assume this means HDR will not be supported. + if (format_count == 1 && formats[0].format == VK_FORMAT_UNDEFINED) { + return false; + } + + bool colorspace_supported = context_driver->is_colorspace_supported(); + + // Determine which formats to prefer based on the requested capabilities. + // Our preferred HDR format is 16-bit float + extended linear. + FixedVector hdr_formats; + if (context_driver->is_colorspace_externally_managed()) { + // When the colorspace is managed externally to the driver we need to disable its color management. + // The colorspace which disables color management is VK_COLOR_SPACE_PASS_THROUGH_EXT. + hdr_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_PASS_THROUGH_EXT, COLOR_SPACE_REC709_LINEAR }); + + // SRGB_NONLINEAR_KHR is required for some NVIDIA drivers that support HDR output but do not support PASS_THROUGH_EXT. + hdr_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, COLOR_SPACE_REC709_LINEAR }); + } else if (colorspace_supported) { + hdr_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, COLOR_SPACE_REC709_LINEAR }); + } else { + return false; + } + + for (const FormatCandidate &candidate : hdr_formats) { + for (uint32_t i = 0; i < format_count; i++) { + if (formats[i].format == candidate.format && formats[i].colorSpace == candidate.colorspace) { + return true; + } + } + } + + return false; +} + void RenderingDeviceDriverVulkan::swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) { DEV_ASSERT(p_swap_chain.id != 0); diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h index 4fbe34d0fe..8e1e458c15 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.h +++ b/drivers/vulkan/rendering_device_driver_vulkan.h @@ -445,6 +445,7 @@ public: virtual int swap_chain_get_pre_rotation_degrees(SwapChainID p_swap_chain) override final; virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final; virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) override final; + virtual bool swap_chain_get_hdr_output_supported(SwapChainID p_swap_chain) override final; virtual void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) override final; virtual void swap_chain_free(SwapChainID p_swap_chain) override final; diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index b1086baab1..2e36bdbc0b 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -1536,15 +1536,21 @@ void DisplayServerWayland::_window_update_hdr_state(WindowData &p_window) { bool DisplayServerWayland::window_is_hdr_output_supported(DisplayServerEnums::WindowID p_window_id) const { ERR_FAIL_COND_V(!windows.has(p_window_id), false); bool renderer_supports_hdr_output = false; + bool surface_supports_hdr_output = false; #if defined(RD_ENABLED) if (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) { renderer_supports_hdr_output = true; + surface_supports_hdr_output = rendering_device->screen_get_hdr_output_supported(p_window_id); } #endif if (!renderer_supports_hdr_output) { return false; } + if (!surface_supports_hdr_output) { + return false; + } + const WindowData &wd = windows[p_window_id]; return wd.color_profile.target_max_luminance > wd.color_profile.reference_luminance; @@ -1553,15 +1559,22 @@ bool DisplayServerWayland::window_is_hdr_output_supported(DisplayServerEnums::Wi void DisplayServerWayland::window_request_hdr_output(const bool p_enabled, DisplayServerEnums::WindowID p_window_id) { if (p_enabled) { bool renderer_supports_hdr_output = false; + bool surface_supports_hdr_output = false; #if defined(RD_ENABLED) if (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) { renderer_supports_hdr_output = true; + surface_supports_hdr_output = rendering_device->screen_get_hdr_output_supported(p_window_id); } #endif if (!renderer_supports_hdr_output) { WARN_PRINT("HDR output requested, but is not supported by the renderer or rendering device driver."); return; } + + if (!surface_supports_hdr_output) { + WARN_PRINT("HDR output requested, but the window does not support an HDR format."); + return; + } } ERR_FAIL_COND(!windows.has(p_window_id)); @@ -1894,41 +1907,6 @@ void DisplayServerWayland::process_events() { wayland_thread.keyboard_echo_keys(); -#if defined(RD_ENABLED) - // Enabling HDR may have failed, in which case we need to clear the color profile. - // NOTE: this happens _before_ reading events because the rendering driver is only updated the frame _after_ we try to enable HDR. - if (rendering_device && (!OS::get_singleton()->is_in_low_processor_usage_mode() || RS::get_singleton()->has_changed())) { - for (KeyValue &pair : windows) { - const RD::ColorSpace color_space = rendering_device->screen_get_color_space(pair.key); - - bool dirty_srgb = color_space == RDD::COLOR_SPACE_REC709_NONLINEAR_SRGB && pair.value.color_profile.named_transfer_function != WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22; - bool dirty_linear = color_space == RDD::COLOR_SPACE_REC709_LINEAR && pair.value.color_profile.named_transfer_function != WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; - - if (dirty_srgb) { - pair.value.color_profile.named_primary = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB; - pair.value.color_profile.named_transfer_function = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22; - - if (pair.value.visible) { - wayland_thread.window_set_color_profile(pair.key, pair.value.color_profile); - } - - rendering_context->window_set_hdr_output_enabled(pair.key, false); - _send_window_event(DisplayServerEnums::WINDOW_EVENT_OUTPUT_MAX_LINEAR_VALUE_CHANGED, pair.key); - } else if (dirty_linear) { - pair.value.color_profile.named_primary = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB; - pair.value.color_profile.named_transfer_function = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; - - if (pair.value.visible) { - wayland_thread.window_set_color_profile(pair.key, pair.value.color_profile); - } - - rendering_context->window_set_hdr_output_enabled(pair.key, true); - _send_window_event(DisplayServerEnums::WINDOW_EVENT_OUTPUT_MAX_LINEAR_VALUE_CHANGED, pair.key); - } - } - } -#endif - while (wayland_thread.has_message()) { Ref msg = wayland_thread.pop_message(); diff --git a/platform/macos/display_server_macos_base.mm b/platform/macos/display_server_macos_base.mm index 0514aa8910..2d7d53b8db 100644 --- a/platform/macos/display_server_macos_base.mm +++ b/platform/macos/display_server_macos_base.mm @@ -591,15 +591,21 @@ bool DisplayServerMacOSBase::window_is_hdr_output_supported(DisplayServerEnums:: ERR_FAIL_COND_V(!has_window(p_window), false); bool renderer_supports_hdr_output = false; + bool surface_supports_hdr_output = false; #if defined(RD_ENABLED) if (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) { renderer_supports_hdr_output = true; + surface_supports_hdr_output = rendering_device->screen_get_hdr_output_supported(p_window); } #endif if (!renderer_supports_hdr_output) { return false; } + if (!surface_supports_hdr_output) { + return false; + } + CGFloat max_potential_edr; window_get_edr_values(p_window, &max_potential_edr, nullptr); return max_potential_edr > 1.0f; @@ -611,15 +617,22 @@ void DisplayServerMacOSBase::window_request_hdr_output(const bool p_enabled, Dis ERR_FAIL_COND(!has_window(p_window)); if (p_enabled) { bool renderer_supports_hdr_output = false; + bool surface_supports_hdr_output = false; #if defined(RD_ENABLED) if (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) { renderer_supports_hdr_output = true; + surface_supports_hdr_output = rendering_device->screen_get_hdr_output_supported(p_window); } #endif if (!renderer_supports_hdr_output) { WARN_PRINT("HDR output requested, but is not supported by the renderer or rendering device driver."); return; } + + if (!surface_supports_hdr_output) { + WARN_PRINT("HDR output requested, but the window does not support an HDR format."); + return; + } } HDROutput &hdr = _get_hdr_output(p_window); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 39ebadebb1..f416e502bf 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -4781,15 +4781,21 @@ bool DisplayServerWindows::window_is_hdr_output_supported(DisplayServerEnums::Wi ERR_FAIL_COND_V(!windows.has(p_window), false); bool renderer_supports_hdr_output = false; + bool surface_supports_hdr_output = false; #if defined(RD_ENABLED) if (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) { renderer_supports_hdr_output = true; + surface_supports_hdr_output = rendering_device->screen_get_hdr_output_supported(p_window); } #endif if (!renderer_supports_hdr_output) { return false; } + if (!surface_supports_hdr_output) { + return false; + } + // The window supports HDR if the screen it is on supports HDR. DisplayServerWindows::ScreenHdrData data = _get_screen_hdr_data(p_window, false); return data.hdr_supported; @@ -4801,15 +4807,22 @@ void DisplayServerWindows::window_request_hdr_output(const bool p_enable, Displa ERR_FAIL_COND(!windows.has(p_window)); if (p_enable) { bool renderer_supports_hdr_output = false; + bool surface_supports_hdr_output = false; #if defined(RD_ENABLED) if (rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)) { renderer_supports_hdr_output = true; + surface_supports_hdr_output = rendering_device->screen_get_hdr_output_supported(p_window); } #endif if (!renderer_supports_hdr_output) { WARN_PRINT("HDR output requested, but is not supported by the renderer or rendering device driver."); return; } + + if (!surface_supports_hdr_output) { + WARN_PRINT("HDR output requested, but the window does not support an HDR format."); + return; + } } WindowData &wd = windows[p_window]; diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 5643d82933..e6ac252543 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -5467,6 +5467,15 @@ RenderingDevice::ColorSpace RenderingDevice::screen_get_color_space(DisplayServe return color_space; } +bool RenderingDevice::screen_get_hdr_output_supported(DisplayServerEnums::WindowID p_screen) const { + _THREAD_SAFE_METHOD_ + + HashMap::ConstIterator it = screen_swap_chains.find(p_screen); + ERR_FAIL_COND_V_MSG(it == screen_swap_chains.end(), false, "Screen was never prepared."); + + return driver->swap_chain_get_hdr_output_supported(it->value); +} + Error RenderingDevice::screen_free(DisplayServerEnums::WindowID p_screen) { _THREAD_SAFE_METHOD_ diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index 9f191982ba..30797742f3 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -1312,6 +1312,7 @@ public: int screen_get_pre_rotation_degrees(DisplayServerEnums::WindowID p_screen = DisplayServerEnums::MAIN_WINDOW_ID) const; FramebufferFormatID screen_get_framebuffer_format(DisplayServerEnums::WindowID p_screen = DisplayServerEnums::MAIN_WINDOW_ID) const; ColorSpace screen_get_color_space(DisplayServerEnums::WindowID p_screen = DisplayServerEnums::MAIN_WINDOW_ID) const; + bool screen_get_hdr_output_supported(DisplayServerEnums::WindowID p_screen = DisplayServerEnums::MAIN_WINDOW_ID) const; Error screen_free(DisplayServerEnums::WindowID p_screen = DisplayServerEnums::MAIN_WINDOW_ID); private: diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h index d903cc52c8..eb6640591d 100644 --- a/servers/rendering/rendering_device_driver.h +++ b/servers/rendering/rendering_device_driver.h @@ -484,6 +484,9 @@ public: // Retrieve the color space used by the swap chain's framebuffers. virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) = 0; + // Retrieve whether the swapchain supports our preferred HDR formats. + virtual bool swap_chain_get_hdr_output_supported(SwapChainID p_swap_chain) = 0; + // Tells the swapchain the max_fps so it can use the proper frame pacing. // Android uses this with Swappy library. Some implementations or platforms may ignore this hint. virtual void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) {}