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) {}