diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index e4cc0f9c03b..8c1b47b2d69 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1510,6 +1510,13 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/size/no_focus", false);
GLOBAL_DEF("display/window/size/sharp_corners", false);
+ GLOBAL_DEF("display/window/hdr/enabled", false);
+ GLOBAL_DEF("display/window/hdr/prefer_high_precision", false);
+ GLOBAL_DEF("display/window/hdr/use_screen_luminance", true);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "display/window/hdr/reference_luminance", PROPERTY_HINT_RANGE, "0,2000,1,or_greater"), 80.0f); // sRGB standard of 80 nits
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "display/window/hdr/min_luminance", PROPERTY_HINT_RANGE, "0,2000,1,or_greater"), 0.0f);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "display/window/hdr/max_luminance", PROPERTY_HINT_RANGE, "0,2000,1,or_greater"), 1000.0f);
+
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 6089f85bc77..e0e5c769d83 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1090,6 +1090,25 @@
[b]Note:[/b] On macOS, this method requires "Screen Recording" permission, if permission is not granted it will return desktop wallpaper color.
+
+
+
+
+ Returns the maximum full screen luminance of [param screen] in nits (cd/m²).
+ Some displays support brighter luminance in small areas, but this is the maximum luminance that can be achieved across the entire screen.
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] On Windows, this method is implemented only in builds with D3D12 enabled.
+
+
+
+
+
+
+ Returns the maximum luminance of a pixel on [param screen] in nits (cd/m²).
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] On Windows, this method is implemented only in builds with D3D12 enabled.
+
+
@@ -1098,6 +1117,15 @@
[b]Note:[/b] This method is implemented only on macOS.
+
+
+
+
+ Returns the minimum luminance of a pixel on [param screen] in nits (cd/m²).
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] On Windows, this method is implemented only in builds with D3D12 enabled.
+
+
@@ -1156,6 +1184,15 @@
[b]Note:[/b] This method is implemented on Android, iOS, Web, macOS, and Linux (Wayland).
+
+
+
+
+ Returns the standard dynamic range (SDR) white level of [param screen] in nits (cd/m²).
+ This is the maximum brightness SDR content (such as UI) should be rendered at to match other applications on the display.
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+
+
@@ -1170,6 +1207,15 @@
Returns the portion of the screen that is not obstructed by a status bar in pixels. See also [method screen_get_size].
+
+
+
+
+ Returns [code]true[/code] if the [param screen] supports HDR.
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] On Windows, this method is implemented only in builds with D3D12 enabled.
+
+
@@ -1481,6 +1527,30 @@
Returns the current value of the given window's [param flag].
+
+
+
+
+ Returns the maximum luminance in nits (cd/m²) set for HDR content for the window specified by [param window_id].
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+
+
+
+
+
+
+ Returns the minimum luminance in nits (cd/m²) set for HDR content for the window specified by [param window_id].
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+
+
+
+
+
+
+ Returns the SDR reference luminance in nits (cd/m²) set for HDR content for the window specified by [param window_id].
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+
+
@@ -1576,6 +1646,29 @@
Returns [code]true[/code] if the window specified by [param window_id] is focused.
+
+
+
+
+ Returns whether HDR output is requested for the window specified by [param window_id].
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+
+
+
+
+
+
+ Returns whether the window specified by [param window_id] prefers a high precision (16-bit per color) HDR framebuffer.
+
+
+
+
+
+
+ Returns whether the window specified by [param window_id] is using the screen's luminance for HDR output.
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+
+
@@ -1648,6 +1741,71 @@
Enables or disables the given window's given [param flag]. See [enum WindowFlags] for possible values and their behavior.
+
+
+
+
+
+ Sets whether HDR output should be enabled for the window specified by [param window_id], falling back to SDR if not supported.
+ Only available on platforms that support HDR output, have HDR enabled in the system settings, and have a compatible display connected.
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] Requires support by the rendering device.
+
+
+
+
+
+
+
+ Sets the maximum luminance in nits (cd/m²) for HDR content for the window specified by [param window_id].
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] Requires support by the rendering device.
+ [b]Note:[/b] Ignored if [method window_is_hdr_output_using_screen_luminance] is [code]true[/code].
+
+
+
+
+
+
+
+ Sets the minimum luminance in nits (cd/m²) for HDR content for the window specified by [param window_id].
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] Requires support by the rendering device.
+ [b]Note:[/b] Ignored if [method window_is_hdr_output_using_screen_luminance] is [code]true[/code].
+
+
+
+
+
+
+
+ Sets whether the window specified by [param window_id] prefers a high precision (16-bit per color) framebuffer.
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] Requires support by the rendering device.
+
+
+
+
+
+
+
+ Sets the SDR reference luminance in nits (cd/m²) for HDR content for the window specified by [param window_id].
+ This controls the brightness of SDR content (such as UI) when HDR is enabled.
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+ [b]Note:[/b] Requires support by the rendering device.
+ [b]Note:[/b] Ignored if [method window_is_hdr_output_using_screen_luminance] is [code]true[/code].
+
+
+
+
+
+
+
+ Sets whether the window specified by [param window_id] should use the screen's luminance for HDR output.
+ While this is enabled, the window will ignore attempts to set luminance values.
+ [b]Note:[/b] Requires support for [constant FEATURE_HDR].
+
+
@@ -1952,6 +2110,9 @@
Display server supports system emoji and symbol picker. [b]Windows, macOS[/b]
+
+ Display server supports HDR output. [b]Windows[/b]
+
Makes the mouse cursor visible if it is hidden.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1a29e2003e9..163452b08eb 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -836,6 +836,30 @@
The default screen orientation to use on mobile devices. See [enum DisplayServer.ScreenOrientation] for possible values.
[b]Note:[/b] When set to a portrait orientation, this project setting does not flip the project resolution's width and height automatically. Instead, you have to set [member display/window/size/viewport_width] and [member display/window/size/viewport_height] accordingly.
+
+ If [code]true[/code], enables HDR output on supported platforms, falling back to SDR if not supported.
+ Only available on platforms that support HDR output, have HDR enabled in the system settings, and have a compatible display connected.
+
+
+ Sets the maximum luminance of the display in nits (cd/m²) when HDR is enabled.
+
+
+ Sets the minimum luminance of the display in nits (cd/m²) when HDR is enabled.
+
+
+ If [code]true[/code], uses 16 bit per color buffers for the frame buffer when HDR is enabled.
+ If [code]false[/code], uses 10 bit per color buffers, with 2 bits for alpha, for the frame buffer when HDR is enabled.
+ This may improve color precision and reduce banding in the final image, but will use more memory and may reduce performance.
+ If transparent backgrounds are enabled, you may want to enable this setting to enable smooth blending of HDR content.
+
+
+ Sets the SDR reference luminance of the display in nits (cd/m²) when HDR is enabled.
+ This is used to scale the HDR effect to ensure sRGB content looks the same in both SDR and HDR.
+
+
+ If [code]true[/code], uses the screen's luminance as reported by the display server to set luminance for the window.
+ If information is missing for the display, will fall back to luminance values set in project settings.
+
If [code]true[/code], iOS devices that support high refresh rate/"ProMotion" will be allowed to render at up to 120 frames per second.
@@ -3184,6 +3208,9 @@
[b]Note:[/b] This setting will have no effect when using the Compatibility renderer, which always renders in low dynamic range for performance reasons.
[b]Note:[/b] This property is only read when the project starts. To toggle HDR 2D at runtime, set [member Viewport.use_hdr_2d] on the root [Viewport].
+
+ If [code]true[/code], enables [member Viewport.tonemap_to_window] on the root viewport.
+
If [code]true[/code], enables [member Viewport.transparent_bg] on the root viewport. This allows per-pixel transparency to be effective after also enabling [member display/window/size/transparent] and [member display/window/per_pixel_transparency/allowed].
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index 8db0272c75b..d2e310d5086 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -758,6 +758,22 @@
Returns [code]true[/code] if implementation supports using a texture of [param format] with the given [param sampler_filter].
+
+
+
+
+ Returns the color format of the given screen.
+ [b]Note:[/b] Only the main [RenderingDevice] returned by [method RenderingServer.get_rendering_device] has a format. If called on a local [RenderingDevice], this method prints an error and returns [constant DATA_FORMAT_MAX].
+
+
+
+
+
+
+ Returns the color space of the given screen.
+ [b]Note:[/b] Only the main [RenderingDevice] returned by [method RenderingServer.get_rendering_device] has a format. If called on a local [RenderingDevice], this method prints an error and returns [constant COLOR_SPACE_MAX].
+
+
@@ -1874,6 +1890,18 @@
Represents the size of the [enum DataFormat] enum.
+
+ Color space using Rec 709 primaries and linear gamma.
+
+
+ Color space using Rec 709 primaries and non-linear sRGB gamma.
+
+
+ Color space using Rec 2020 primaries and the ST 2084 transfer function.
+
+
+ Represents the size of the [enum ColorSpace] enum.
+
Vertex shader barrier mask.
@@ -2454,6 +2482,9 @@
Features support for buffer device address extension.
+
+ Features support for high dynamic range (HDR) output.
+
Maximum number of uniform sets that can be bound at a given time.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 7ccaf880ee2..c627ea4dee3 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -1493,6 +1493,16 @@
Sets the variables to be used with the "tonemap" post-process effect. See [Environment] for more details.
+
+
+
+
+
+
+ Sets the range of the tonemapper, defining the minimum and maximum values after tonemapping.
+ [b]Note:[/b] This is ignored when using [constant ENV_TONE_MAPPER_LINEAR].
+
+
@@ -4190,6 +4200,14 @@
[b]Note:[/b] When the 3D scaling mode is set to FSR 1.0, this value is used to adjust the automatic mipmap bias which is calculated internally based on the scale factor. The formula for this is [code]-log2(1.0 / scale) + mipmap_bias[/code].
+
+
+
+
+
+ If [code]true[/code], the viewport will tonemap its content to the window's HDR output luminance range, if HDR output is enabled.
+
+
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index a55c40d0187..0c3e69edf40 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -415,6 +415,10 @@
[b]Note:[/b] If [member scaling_3d_scale] is lower than [code]1.0[/code] (exclusive), [member texture_mipmap_bias] is used to adjust the automatic mipmap bias which is calculated internally based on the scale factor. The formula for this is [code]log2(scaling_3d_scale) + mipmap_bias[/code].
To control this property on the root viewport, set the [member ProjectSettings.rendering/textures/default_filters/texture_mipmap_bias] project setting.
+
+ If [code]true[/code], the viewport will tonemap its content to the window's HDR output luminance range, if HDR output is enabled.
+ This is useful for viewports that are not the root of the window, but still need their content to adjust to the capabilities of the display.
+
If [code]true[/code], the viewport should render its background as transparent.
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index 90eba0bdea8..87256d91e6b 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -614,6 +614,26 @@
If [code]true[/code], native window will be used regardless of parent viewport and project settings.
+
+ If [code]true[/code], the [Window] will output in HDR.
+ Only available on platforms that support HDR output, have HDR enabled in the system settings, and have a compatible display connected.
+
+
+ The maximum luminance in nits (cd/m²) when HDR is enabled.
+
+
+ The minimum luminance in nits (cd/m²) when HDR is enabled.
+
+
+ If [code]true[/code], the [Window] will prefer high-precision (16-bit per color) framebuffer when available and HDR is enabled.
+
+
+ The SDR reference luminance in nits (cd/m²) when HDR is enabled.
+ This controls the brightness of SDR content (such as UI) when HDR is enabled.
+
+
+ If [code]true[/code], the [Window] will use the screen's luminance to adjust the HDR output.
+
Specifies the initial type of position for the [Window]. See [enum WindowInitialPosition] constants.
@@ -693,6 +713,7 @@
The window's title. If the [Window] is native, title styles set in [Theme] will have no effect.
+
If [code]true[/code], the [Window] is transient, i.e. it's considered a child of another [Window]. The transient window will be destroyed with its transient parent and will return focus to their parent when closed. The transient window is displayed on top of a non-exclusive full-screen parent window. Transient windows can't enter full-screen mode.
Note that behavior might be different depending on the platform.
diff --git a/drivers/d3d12/rendering_context_driver_d3d12.cpp b/drivers/d3d12/rendering_context_driver_d3d12.cpp
index 7081d1730f3..46edbd6ca45 100644
--- a/drivers/d3d12/rendering_context_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_context_driver_d3d12.cpp
@@ -282,6 +282,58 @@ DisplayServer::VSyncMode RenderingContextDriverD3D12::surface_get_vsync_mode(Sur
return surface->vsync_mode;
}
+void RenderingContextDriverD3D12::surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_output = p_enabled;
+ surface->needs_resize = true;
+}
+
+bool RenderingContextDriverD3D12::surface_get_hdr_output_enabled(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_output;
+}
+
+void RenderingContextDriverD3D12::surface_set_hdr_output_prefer_high_precision(SurfaceID p_surface, bool p_enabled) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_prefer_high_precision = p_enabled;
+ surface->needs_resize = true;
+}
+
+bool RenderingContextDriverD3D12::surface_get_hdr_output_prefer_high_precision(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_prefer_high_precision;
+}
+
+void RenderingContextDriverD3D12::surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_reference_luminance = p_reference_luminance;
+}
+
+float RenderingContextDriverD3D12::surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_reference_luminance;
+}
+
+void RenderingContextDriverD3D12::surface_set_hdr_output_min_luminance(SurfaceID p_surface, float p_min_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_min_luminance = p_min_luminance;
+}
+
+float RenderingContextDriverD3D12::surface_get_hdr_output_min_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_min_luminance;
+}
+
+void RenderingContextDriverD3D12::surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_max_luminance = p_max_luminance;
+}
+
+float RenderingContextDriverD3D12::surface_get_hdr_output_max_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_max_luminance;
+}
+
uint32_t RenderingContextDriverD3D12::surface_get_width(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->width;
diff --git a/drivers/d3d12/rendering_context_driver_d3d12.h b/drivers/d3d12/rendering_context_driver_d3d12.h
index 3eed6444815..a7506dfd67d 100644
--- a/drivers/d3d12/rendering_context_driver_d3d12.h
+++ b/drivers/d3d12/rendering_context_driver_d3d12.h
@@ -109,6 +109,16 @@ public:
virtual void surface_set_size(SurfaceID p_surface, uint32_t p_width, uint32_t p_height) override;
virtual void surface_set_vsync_mode(SurfaceID p_surface, DisplayServer::VSyncMode p_vsync_mode) override;
virtual DisplayServer::VSyncMode surface_get_vsync_mode(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) override;
+ virtual bool surface_get_hdr_output_enabled(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_prefer_high_precision(SurfaceID p_surface, bool p_enabled) override;
+ virtual bool surface_get_hdr_output_prefer_high_precision(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) override;
+ virtual float surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_min_luminance(SurfaceID p_surface, float p_min_luminance) override;
+ virtual float surface_get_hdr_output_min_luminance(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) override;
+ virtual float surface_get_hdr_output_max_luminance(SurfaceID p_surface) const override;
virtual uint32_t surface_get_width(SurfaceID p_surface) const override;
virtual uint32_t surface_get_height(SurfaceID p_surface) const override;
virtual void surface_set_needs_resize(SurfaceID p_surface, bool p_needs_resize) override;
@@ -128,6 +138,13 @@ public:
uint32_t height = 0;
DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED;
bool needs_resize = false;
+
+ bool hdr_output = false;
+ bool hdr_prefer_high_precision = false;
+ float hdr_reference_luminance = 0.0f;
+ float hdr_min_luminance = 0.0f;
+ float hdr_max_luminance = 0.0f;
+
ComPtr composition_device;
ComPtr composition_target;
ComPtr composition_visual;
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp
index 29b36f14055..685fafd8316 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp
@@ -334,6 +334,12 @@ const RenderingDeviceDriverD3D12::D3D12Format RenderingDeviceDriverD3D12::RD_TO_
/* DATA_FORMAT_G16_B16_R16_3PLANE_444_UNORM */ {},
};
+const DXGI_COLOR_SPACE_TYPE RenderingDeviceDriverD3D12::RD_TO_DXGI_COLOR_SPACE_TYPE[RDD::COLOR_SPACE_MAX]{
+ /* COLOR_SPACE_SRGB_LINEAR */ DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709,
+ /* COLOR_SPACE_SRGB_NONLINEAR */ DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709,
+ /* COLOR_SPACE_HDR10_ST2084 */ DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020,
+};
+
Error RenderingDeviceDriverD3D12::DescriptorsHeap::allocate(ID3D12Device *p_device, D3D12_DESCRIPTOR_HEAP_TYPE p_type, uint32_t p_descriptor_count, bool p_for_gpu) {
ERR_FAIL_COND_V(heap, ERR_ALREADY_EXISTS);
ERR_FAIL_COND_V(p_descriptor_count == 0, ERR_INVALID_PARAMETER);
@@ -2405,6 +2411,11 @@ void RenderingDeviceDriverD3D12::command_buffer_execute_secondary(CommandBufferI
void RenderingDeviceDriverD3D12::_swap_chain_release(SwapChain *p_swap_chain) {
_swap_chain_release_buffers(p_swap_chain);
+ if (p_swap_chain->render_pass.id != 0) {
+ render_pass_free(p_swap_chain->render_pass);
+ p_swap_chain->render_pass = RenderPassID();
+ }
+
p_swap_chain->d3d_swap_chain.Reset();
}
@@ -2423,10 +2434,10 @@ void RenderingDeviceDriverD3D12::_swap_chain_release_buffers(SwapChain *p_swap_c
p_swap_chain->framebuffers.clear();
}
-RDD::SwapChainID RenderingDeviceDriverD3D12::swap_chain_create(RenderingContextDriver::SurfaceID p_surface) {
+RDD::RenderPassID RenderingDeviceDriverD3D12::_swap_chain_create_render_pass(RDD::DataFormat p_format) {
// Create the render pass that will be used to draw to the swap chain's framebuffers.
RDD::Attachment attachment;
- attachment.format = DATA_FORMAT_R8G8B8A8_UNORM;
+ attachment.format = p_format;
attachment.samples = RDD::TEXTURE_SAMPLES_1;
attachment.load_op = RDD::ATTACHMENT_LOAD_OP_CLEAR;
attachment.store_op = RDD::ATTACHMENT_STORE_OP_STORE;
@@ -2437,14 +2448,35 @@ RDD::SwapChainID RenderingDeviceDriverD3D12::swap_chain_create(RenderingContextD
color_ref.aspect.set_flag(RDD::TEXTURE_ASPECT_COLOR_BIT);
subpass.color_references.push_back(color_ref);
- RenderPassID render_pass = render_pass_create(attachment, subpass, {}, 1);
- ERR_FAIL_COND_V(!render_pass, SwapChainID());
+ return render_pass_create(attachment, subpass, {}, 1);
+}
- // Create the empty swap chain until it is resized.
+void RenderingDeviceDriverD3D12::_determine_swap_chain_format(SwapChain *p_swap_chain, DataFormat &r_format, ColorSpace &r_color_space) {
+ DEV_ASSERT(p_swap_chain);
+ DEV_ASSERT(p_swap_chain->surface != 0);
+
+ // Direct3D Hardware level 10 mandates support for all these formats.
+ // Godot requires at least Hardware level 11, so these formats are guaranteed to be supported.
+ if (context_driver->surface_get_hdr_output_enabled(p_swap_chain->surface)) {
+ if (context_driver->surface_get_hdr_output_prefer_high_precision(p_swap_chain->surface)) {
+ r_format = DATA_FORMAT_R16G16B16A16_SFLOAT;
+ r_color_space = COLOR_SPACE_SRGB_LINEAR;
+ } else {
+ r_format = DATA_FORMAT_A2R10G10B10_UNORM_PACK32;
+ r_color_space = COLOR_SPACE_HDR10_ST2084;
+ }
+ } else {
+ r_format = DATA_FORMAT_R8G8B8A8_UNORM;
+ r_color_space = COLOR_SPACE_SRGB_NONLINEAR;
+ }
+}
+
+RDD::SwapChainID RenderingDeviceDriverD3D12::swap_chain_create(RenderingContextDriver::SurfaceID p_surface) {
+ DEV_ASSERT(p_surface != 0);
+
+ // Create an empty swap chain until it is resized.
SwapChain *swap_chain = memnew(SwapChain);
swap_chain->surface = p_surface;
- swap_chain->data_format = attachment.format;
- swap_chain->render_pass = render_pass;
return SwapChainID(swap_chain);
}
@@ -2486,17 +2518,27 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue,
break;
}
- if (swap_chain->d3d_swap_chain != nullptr && creation_flags != swap_chain->creation_flags) {
- // The swap chain must be recreated if the creation flags are different.
+ RDD::DataFormat new_data_format;
+ RDD::ColorSpace new_color_space;
+ _determine_swap_chain_format(swap_chain, new_data_format, new_color_space);
+
+ if (swap_chain->d3d_swap_chain != nullptr && (creation_flags != swap_chain->creation_flags || new_data_format != swap_chain->data_format)) {
+ // The swap chain must be recreated if the creation flags or data format are different.
_swap_chain_release(swap_chain);
}
+ swap_chain->data_format = new_data_format;
+
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
if (swap_chain->d3d_swap_chain != nullptr) {
_swap_chain_release_buffers(swap_chain);
res = swap_chain->d3d_swap_chain->ResizeBuffers(p_desired_framebuffer_count, surface->width, surface->height, DXGI_FORMAT_UNKNOWN, creation_flags);
ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_UNAVAILABLE);
} else {
+ DEV_ASSERT(swap_chain->render_pass.id == 0);
+ swap_chain->render_pass = _swap_chain_create_render_pass(new_data_format);
+ ERR_FAIL_COND_V(!swap_chain->render_pass, ERR_CANT_CREATE);
+
swap_chain_desc.BufferCount = p_desired_framebuffer_count;
swap_chain_desc.Format = RD_TO_D3D12_FORMAT[swap_chain->data_format].general_format;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
@@ -2525,6 +2567,13 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue,
ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE);
}
+ if (swap_chain->color_space != new_color_space) {
+ res = swap_chain->d3d_swap_chain->SetColorSpace1(RD_TO_DXGI_COLOR_SPACE_TYPE[new_color_space]);
+ ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE);
+
+ swap_chain->color_space = new_color_space;
+ }
+
if (surface->composition_device.Get() == nullptr) {
using PFN_DCompositionCreateDevice = HRESULT(WINAPI *)(IDXGIDevice *, REFIID, void **);
PFN_DCompositionCreateDevice pfn_DCompositionCreateDevice = (PFN_DCompositionCreateDevice)(void *)GetProcAddress(context_driver->lib_dcomp, "DCompositionCreateDevice");
@@ -2634,10 +2683,14 @@ RDD::DataFormat RenderingDeviceDriverD3D12::swap_chain_get_format(SwapChainID p_
return swap_chain->data_format;
}
+RDD::ColorSpace RenderingDeviceDriverD3D12::swap_chain_get_color_space(SwapChainID p_swap_chain) {
+ const SwapChain *swap_chain = (const SwapChain *)(p_swap_chain.id);
+ return swap_chain->color_space;
+}
+
void RenderingDeviceDriverD3D12::swap_chain_free(SwapChainID p_swap_chain) {
SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
_swap_chain_release(swap_chain);
- render_pass_free(swap_chain->render_pass);
memdelete(swap_chain);
}
@@ -6280,6 +6333,8 @@ bool RenderingDeviceDriverD3D12::has_feature(Features p_feature) {
return true;
case SUPPORTS_BUFFER_DEVICE_ADDRESS:
return true;
+ case SUPPORTS_HDR_OUTPUT:
+ return true;
default:
return false;
}
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.h b/drivers/d3d12/rendering_device_driver_d3d12.h
index 50d0f2cddf8..2d17a141f40 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.h
+++ b/drivers/d3d12/rendering_device_driver_d3d12.h
@@ -100,6 +100,7 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver {
};
static const D3D12Format RD_TO_D3D12_FORMAT[RDD::DATA_FORMAT_MAX];
+ static const DXGI_COLOR_SPACE_TYPE RD_TO_DXGI_COLOR_SPACE_TYPE[RDD::COLOR_SPACE_MAX];
struct DeviceLimits {
uint64_t max_srvs_per_shader_stage = 0;
@@ -506,10 +507,13 @@ private:
TightLocalVector render_targets_info;
TightLocalVector framebuffers;
RDD::DataFormat data_format = DATA_FORMAT_MAX;
+ RDD::ColorSpace color_space = COLOR_SPACE_MAX;
};
void _swap_chain_release(SwapChain *p_swap_chain);
void _swap_chain_release_buffers(SwapChain *p_swap_chain);
+ RenderPassID _swap_chain_create_render_pass(RDD::DataFormat p_format);
+ void _determine_swap_chain_format(SwapChain *p_swap_chain, DataFormat &r_format, ColorSpace &r_color_space);
public:
virtual SwapChainID swap_chain_create(RenderingContextDriver::SurfaceID p_surface) override;
@@ -517,6 +521,7 @@ public:
virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override;
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 void swap_chain_free(SwapChainID p_swap_chain) override;
/*********************/
diff --git a/drivers/metal/rendering_context_driver_metal.h b/drivers/metal/rendering_context_driver_metal.h
index 2f6d46d71bb..0d8d1548820 100644
--- a/drivers/metal/rendering_context_driver_metal.h
+++ b/drivers/metal/rendering_context_driver_metal.h
@@ -76,6 +76,16 @@ public:
void surface_set_size(SurfaceID p_surface, uint32_t p_width, uint32_t p_height) final override;
void surface_set_vsync_mode(SurfaceID p_surface, DisplayServer::VSyncMode p_vsync_mode) final override;
DisplayServer::VSyncMode surface_get_vsync_mode(SurfaceID p_surface) const final override;
+ virtual void surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) final override;
+ virtual bool surface_get_hdr_output_enabled(SurfaceID p_surface) const final override;
+ virtual void surface_set_hdr_output_prefer_high_precision(SurfaceID p_surface, bool p_enabled) override;
+ virtual bool surface_get_hdr_output_prefer_high_precision(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) final override;
+ virtual float surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const final override;
+ virtual void surface_set_hdr_output_min_luminance(SurfaceID p_surface, float p_min_luminance) final override;
+ virtual float surface_get_hdr_output_min_luminance(SurfaceID p_surface) const final override;
+ virtual void surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) final override;
+ virtual float surface_get_hdr_output_max_luminance(SurfaceID p_surface) const final override;
uint32_t surface_get_width(SurfaceID p_surface) const final override;
uint32_t surface_get_height(SurfaceID p_surface) const final override;
void surface_set_needs_resize(SurfaceID p_surface, bool p_needs_resize) final override;
@@ -109,6 +119,12 @@ public:
bool needs_resize = false;
double present_minimum_duration = 0.0;
+ bool hdr_output = false;
+ bool hdr_prefer_high_precision = false;
+ float hdr_reference_luminance = 0.0f;
+ float hdr_min_luminance = 0.0f;
+ float hdr_max_luminance = 0.0f;
+
Surface(
#ifdef __OBJC__
id p_device
diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm
index 767424c6a48..31aba1add8d 100644
--- a/drivers/metal/rendering_context_driver_metal.mm
+++ b/drivers/metal/rendering_context_driver_metal.mm
@@ -211,6 +211,58 @@ DisplayServer::VSyncMode RenderingContextDriverMetal::surface_get_vsync_mode(Sur
return surface->vsync_mode;
}
+void RenderingContextDriverMetal::surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_output = p_enabled;
+ surface->needs_resize = true;
+}
+
+bool RenderingContextDriverMetal::surface_get_hdr_output_enabled(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_output;
+}
+
+void RenderingContextDriverMetal::surface_set_hdr_output_prefer_high_precision(SurfaceID p_surface, bool p_enabled) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_prefer_high_precision = p_enabled;
+ surface->needs_resize = true;
+}
+
+bool RenderingContextDriverMetal::surface_get_hdr_output_prefer_high_precision(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_prefer_high_precision;
+}
+
+void RenderingContextDriverMetal::surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_reference_luminance = p_reference_luminance;
+}
+
+float RenderingContextDriverMetal::surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_reference_luminance;
+}
+
+void RenderingContextDriverMetal::surface_set_hdr_output_min_luminance(SurfaceID p_surface, float p_min_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_min_luminance = p_min_luminance;
+}
+
+float RenderingContextDriverMetal::surface_get_hdr_output_min_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_min_luminance;
+}
+
+void RenderingContextDriverMetal::surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_max_luminance = p_max_luminance;
+}
+
+float RenderingContextDriverMetal::surface_get_hdr_output_max_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_max_luminance;
+}
+
uint32_t RenderingContextDriverMetal::surface_get_width(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->width;
diff --git a/drivers/metal/rendering_device_driver_metal.h b/drivers/metal/rendering_device_driver_metal.h
index 5ac1bab4c1b..b9978ba48ee 100644
--- a/drivers/metal/rendering_device_driver_metal.h
+++ b/drivers/metal/rendering_device_driver_metal.h
@@ -222,6 +222,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 void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) override final;
+ virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) override final;
virtual void swap_chain_free(SwapChainID p_swap_chain) override final;
#pragma mark - Frame Buffer
diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm
index 6c170034d90..ee126180ea9 100644
--- a/drivers/metal/rendering_device_driver_metal.mm
+++ b/drivers/metal/rendering_device_driver_metal.mm
@@ -1029,6 +1029,10 @@ void RenderingDeviceDriverMetal::swap_chain_set_max_fps(SwapChainID p_swap_chain
metal_surface->set_max_fps(p_max_fps);
}
+RDD::ColorSpace RenderingDeviceDriverMetal::swap_chain_get_color_space(SwapChainID p_swap_chain) {
+ return RDD::COLOR_SPACE_SRGB_NONLINEAR;
+}
+
void RenderingDeviceDriverMetal::swap_chain_free(SwapChainID p_swap_chain) {
SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
_swap_chain_release(swap_chain);
diff --git a/drivers/vulkan/rendering_context_driver_vulkan.cpp b/drivers/vulkan/rendering_context_driver_vulkan.cpp
index 3c1aa323f45..6628427e428 100644
--- a/drivers/vulkan/rendering_context_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_context_driver_vulkan.cpp
@@ -436,6 +436,9 @@ Error RenderingContextDriverVulkan::_initialize_instance_extensions() {
// This extension allows us to use the properties2 features to query additional device capabilities.
_register_requested_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false);
+ // This extension allows us to use colorspaces other than SRGB.
+ _register_requested_instance_extension(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, false);
+
#if defined(USE_VOLK) && (defined(MACOS_ENABLED) || defined(IOS_ENABLED))
_register_requested_instance_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, true);
#endif
@@ -991,6 +994,58 @@ DisplayServer::VSyncMode RenderingContextDriverVulkan::surface_get_vsync_mode(Su
return surface->vsync_mode;
}
+void RenderingContextDriverVulkan::surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_output = p_enabled;
+ surface->needs_resize = true;
+}
+
+bool RenderingContextDriverVulkan::surface_get_hdr_output_enabled(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_output;
+}
+
+void RenderingContextDriverVulkan::surface_set_hdr_output_prefer_high_precision(SurfaceID p_surface, bool p_enabled) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_prefer_high_precision = p_enabled;
+ surface->needs_resize = true;
+}
+
+bool RenderingContextDriverVulkan::surface_get_hdr_output_prefer_high_precision(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_prefer_high_precision;
+}
+
+void RenderingContextDriverVulkan::surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_reference_luminance = p_reference_luminance;
+}
+
+float RenderingContextDriverVulkan::surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_reference_luminance;
+}
+
+void RenderingContextDriverVulkan::surface_set_hdr_output_min_luminance(SurfaceID p_surface, float p_min_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_min_luminance = p_min_luminance;
+}
+
+float RenderingContextDriverVulkan::surface_get_hdr_output_min_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_min_luminance;
+}
+
+void RenderingContextDriverVulkan::surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) {
+ Surface *surface = (Surface *)(p_surface);
+ surface->hdr_max_luminance = p_max_luminance;
+}
+
+float RenderingContextDriverVulkan::surface_get_hdr_output_max_luminance(SurfaceID p_surface) const {
+ Surface *surface = (Surface *)(p_surface);
+ return surface->hdr_max_luminance;
+}
+
uint32_t RenderingContextDriverVulkan::surface_get_width(SurfaceID p_surface) const {
Surface *surface = (Surface *)(p_surface);
return surface->width;
@@ -1021,6 +1076,10 @@ bool RenderingContextDriverVulkan::is_debug_utils_enabled() const {
return enabled_instance_extension_names.has(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
+bool RenderingContextDriverVulkan::is_colorspace_supported() const {
+ return enabled_instance_extension_names.has(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME);
+}
+
VkInstance RenderingContextDriverVulkan::instance_get() const {
return instance;
}
diff --git a/drivers/vulkan/rendering_context_driver_vulkan.h b/drivers/vulkan/rendering_context_driver_vulkan.h
index 26de3862061..355316787cb 100644
--- a/drivers/vulkan/rendering_context_driver_vulkan.h
+++ b/drivers/vulkan/rendering_context_driver_vulkan.h
@@ -139,12 +139,23 @@ public:
virtual void surface_set_size(SurfaceID p_surface, uint32_t p_width, uint32_t p_height) override;
virtual void surface_set_vsync_mode(SurfaceID p_surface, DisplayServer::VSyncMode p_vsync_mode) override;
virtual DisplayServer::VSyncMode surface_get_vsync_mode(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) override;
+ virtual bool surface_get_hdr_output_enabled(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_prefer_high_precision(SurfaceID p_surface, bool p_enabled) override;
+ virtual bool surface_get_hdr_output_prefer_high_precision(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) override;
+ virtual float surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_min_luminance(SurfaceID p_surface, float p_min_luminance) override;
+ virtual float surface_get_hdr_output_min_luminance(SurfaceID p_surface) const override;
+ virtual void surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) override;
+ virtual float surface_get_hdr_output_max_luminance(SurfaceID p_surface) const override;
virtual uint32_t surface_get_width(SurfaceID p_surface) const override;
virtual uint32_t surface_get_height(SurfaceID p_surface) const override;
virtual void surface_set_needs_resize(SurfaceID p_surface, bool p_needs_resize) override;
virtual bool surface_get_needs_resize(SurfaceID p_surface) const override;
virtual void surface_destroy(SurfaceID p_surface) override;
virtual bool is_debug_utils_enabled() const override;
+ bool is_colorspace_supported() const;
// Vulkan-only methods.
struct Surface {
@@ -153,6 +164,12 @@ public:
uint32_t height = 0;
DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED;
bool needs_resize = false;
+
+ bool hdr_output = false;
+ bool hdr_prefer_high_precision = false;
+ float hdr_reference_luminance = 0.0f;
+ float hdr_min_luminance = 0.0f;
+ float hdr_max_luminance = 0.0f;
};
VkInstance instance_get() const;
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index ae15191dd42..8728e60dc11 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -2862,6 +2862,99 @@ void RenderingDeviceDriverVulkan::command_buffer_execute_secondary(CommandBuffer
/**** SWAP CHAIN ****/
/********************/
+struct FormatCandidate {
+ VkFormat format;
+ VkColorSpaceKHR colorspace;
+};
+
+bool RenderingDeviceDriverVulkan::_determine_swap_chain_format(RenderingContextDriver::SurfaceID p_surface, VkFormat &r_format, VkColorSpaceKHR &r_color_space) {
+ DEV_ASSERT(p_surface != 0);
+
+ RenderingContextDriverVulkan::Surface *surface = (RenderingContextDriverVulkan::Surface *)(p_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.
+ if (format_count == 1 && formats[0].format == VK_FORMAT_UNDEFINED) {
+ r_format = VK_FORMAT_B8G8R8A8_UNORM;
+ r_color_space = formats[0].colorSpace;
+ return true;
+ }
+
+ bool colorspace_supported = context_driver->is_colorspace_supported();
+ bool hdr_output_requested = context_driver->surface_get_hdr_output_enabled(p_surface);
+ bool high_precision_requested = context_driver->surface_get_hdr_output_prefer_high_precision(p_surface);
+
+ // Determine which formats to prefer based on the requested capabilities.
+ LocalVector preferred_formats = LocalVector();
+ preferred_formats.reserve(5);
+
+ // If the surface requests HDR output, try to get an HDR format.
+ if (hdr_output_requested && colorspace_supported) {
+ if (high_precision_requested) {
+ // If high precision is requested, add the high precision format to the top.
+ preferred_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT });
+
+ // Followed by the HDR10 formats
+ preferred_formats.push_back({ VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_HDR10_ST2084_EXT });
+ preferred_formats.push_back({ VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_COLOR_SPACE_HDR10_ST2084_EXT });
+ } else {
+ // Otherwise add the HDR10 formats to the top.
+ preferred_formats.push_back({ VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_HDR10_ST2084_EXT });
+ preferred_formats.push_back({ VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_COLOR_SPACE_HDR10_ST2084_EXT });
+
+ // Followed by the high precision format for devices that don't support HDR 10.
+ preferred_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT });
+ }
+ }
+
+ // These formats are always considered for SDR.
+ preferred_formats.push_back({ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
+ preferred_formats.push_back({ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
+
+ bool found = false;
+ for (const FormatCandidate &candidate : preferred_formats) {
+ for (uint32_t i = 0; i < format_count; i++) {
+ if (formats[i].format == candidate.format && formats[i].colorSpace == candidate.colorspace) {
+ r_format = formats[i].format;
+ r_color_space = formats[i].colorSpace;
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ break;
+ }
+ }
+
+ // Warnings for when HDR capabilities are requested but not found.
+ if (hdr_output_requested) {
+ if (!colorspace_supported) {
+ WARN_PRINT("HDR output requested but the vulkan driver does not support VK_EXT_swapchain_colorspace, falling back to SDR.");
+ }
+
+ if (high_precision_requested && r_format != VK_FORMAT_R16G16B16A16_SFLOAT) {
+ WARN_PRINT("HDR output requested with high precision but no high precision format was found.");
+ }
+
+ if (r_color_space == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ WARN_PRINT("HDR output requested but no HDR compatible format was found, falling back to SDR.");
+ }
+ }
+
+ return found;
+}
+
void RenderingDeviceDriverVulkan::_swap_chain_release(SwapChain *swap_chain) {
// Destroy views and framebuffers associated to the swapchain's images.
for (FramebufferID framebuffer : swap_chain->framebuffers) {
@@ -2890,6 +2983,11 @@ void RenderingDeviceDriverVulkan::_swap_chain_release(SwapChain *swap_chain) {
swap_chain->vk_swapchain = VK_NULL_HANDLE;
}
+ if (swap_chain->render_pass.id != 0) {
+ vkDestroyRenderPass(vk_device, VkRenderPass(swap_chain->render_pass.id), VKC::get_allocation_callbacks(VK_OBJECT_TYPE_RENDER_PASS));
+ swap_chain->render_pass = RenderPassID();
+ }
+
for (uint32_t i = 0; i < swap_chain->command_queues_acquired.size(); i++) {
_recreate_image_semaphore(swap_chain->command_queues_acquired[i], swap_chain->command_queues_acquired_semaphores[i], false);
}
@@ -2901,81 +2999,9 @@ void RenderingDeviceDriverVulkan::_swap_chain_release(SwapChain *swap_chain) {
RenderingDeviceDriver::SwapChainID RenderingDeviceDriverVulkan::swap_chain_create(RenderingContextDriver::SurfaceID p_surface) {
DEV_ASSERT(p_surface != 0);
- RenderingContextDriverVulkan::Surface *surface = (RenderingContextDriverVulkan::Surface *)(p_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, SwapChainID());
-
- 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, SwapChainID());
-
- VkFormat format = VK_FORMAT_UNDEFINED;
- VkColorSpaceKHR color_space = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
- if (format_count == 1 && formats[0].format == VK_FORMAT_UNDEFINED) {
- // If the format list includes just one entry of VK_FORMAT_UNDEFINED, the surface has no preferred format.
- format = VK_FORMAT_B8G8R8A8_UNORM;
- color_space = formats[0].colorSpace;
- } else if (format_count > 0) {
- // Use one of the supported formats, prefer B8G8R8A8_UNORM.
- const VkFormat preferred_format = VK_FORMAT_B8G8R8A8_UNORM;
- const VkFormat second_format = VK_FORMAT_R8G8B8A8_UNORM;
- for (uint32_t i = 0; i < format_count; i++) {
- if (formats[i].format == preferred_format || formats[i].format == second_format) {
- format = formats[i].format;
- if (formats[i].format == preferred_format) {
- // This is the preferred format, stop searching.
- break;
- }
- }
- }
- }
-
- // No formats are supported.
- ERR_FAIL_COND_V_MSG(format == VK_FORMAT_UNDEFINED, SwapChainID(), "Surface did not return any valid formats.");
-
- // Create the render pass for the chosen format.
- VkAttachmentDescription2KHR attachment = {};
- attachment.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR;
- attachment.format = format;
- attachment.samples = VK_SAMPLE_COUNT_1_BIT;
- attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
- attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
- attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
- attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
- attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
- attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-
- VkAttachmentReference2KHR color_reference = {};
- color_reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
- color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
- VkSubpassDescription2KHR subpass = {};
- subpass.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
- subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
- subpass.colorAttachmentCount = 1;
- subpass.pColorAttachments = &color_reference;
-
- VkRenderPassCreateInfo2KHR pass_info = {};
- pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
- pass_info.attachmentCount = 1;
- pass_info.pAttachments = &attachment;
- pass_info.subpassCount = 1;
- pass_info.pSubpasses = &subpass;
-
- VkRenderPass render_pass = VK_NULL_HANDLE;
- err = _create_render_pass(vk_device, &pass_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_RENDER_PASS), &render_pass);
- ERR_FAIL_COND_V(err != VK_SUCCESS, SwapChainID());
-
+ // Create an empty swap chain until it is resized.
SwapChain *swap_chain = memnew(SwapChain);
swap_chain->surface = p_surface;
- swap_chain->format = format;
- swap_chain->color_space = color_space;
- swap_chain->render_pass = RenderPassID(render_pass);
return SwapChainID(swap_chain);
}
@@ -3109,6 +3135,16 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
has_comp_alpha[(uint64_t)p_cmd_queue.id] = (composite_alpha != VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR);
}
+ // Determine the format and color space for the swap chain.
+ VkFormat format = VK_FORMAT_UNDEFINED;
+ VkColorSpaceKHR color_space = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+ if (!_determine_swap_chain_format(swap_chain->surface, format, color_space)) {
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Surface did not return any valid formats.");
+ } else {
+ swap_chain->format = format;
+ swap_chain->color_space = color_space;
+ }
+
VkSwapchainCreateInfoKHR swap_create_info = {};
swap_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swap_create_info.surface = surface->vk_surface;
@@ -3207,9 +3243,45 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
swap_chain->framebuffers.reserve(image_count);
+ // Create the render pass for the chosen format.
+ VkAttachmentDescription2KHR attachment = {};
+ attachment.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR;
+ attachment.format = format;
+ attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+ attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+ VkAttachmentReference2KHR color_reference = {};
+ color_reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+ VkSubpassDescription2KHR subpass = {};
+ subpass.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &color_reference;
+
+ VkRenderPassCreateInfo2KHR pass_info = {};
+ pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
+ pass_info.attachmentCount = 1;
+ pass_info.pAttachments = &attachment;
+ pass_info.subpassCount = 1;
+ pass_info.pSubpasses = &subpass;
+
+ VkRenderPass render_pass = VK_NULL_HANDLE;
+ err = _create_render_pass(vk_device, &pass_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_RENDER_PASS), &render_pass);
+ ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
+
+ DEV_ASSERT(swap_chain->render_pass.id == 0);
+ swap_chain->render_pass = RenderPassID(render_pass);
+
VkFramebufferCreateInfo fb_create_info = {};
fb_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
- fb_create_info.renderPass = VkRenderPass(swap_chain->render_pass.id);
+ fb_create_info.renderPass = render_pass;
fb_create_info.attachmentCount = 1;
fb_create_info.width = surface->width;
fb_create_info.height = surface->height;
@@ -3322,6 +3394,12 @@ RDD::DataFormat RenderingDeviceDriverVulkan::swap_chain_get_format(SwapChainID p
return DATA_FORMAT_B8G8R8A8_UNORM;
case VK_FORMAT_R8G8B8A8_UNORM:
return DATA_FORMAT_R8G8B8A8_UNORM;
+ case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+ return DATA_FORMAT_A2B10G10R10_UNORM_PACK32;
+ case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
+ return DATA_FORMAT_A2R10G10B10_UNORM_PACK32;
+ case VK_FORMAT_R16G16B16A16_SFLOAT:
+ return DATA_FORMAT_R16G16B16A16_SFLOAT;
default:
DEV_ASSERT(false && "Unknown swap chain format.");
return DATA_FORMAT_MAX;
@@ -3344,16 +3422,29 @@ void RenderingDeviceDriverVulkan::swap_chain_set_max_fps(SwapChainID p_swap_chai
#endif
}
+RDD::ColorSpace RenderingDeviceDriverVulkan::swap_chain_get_color_space(SwapChainID p_swap_chain) {
+ DEV_ASSERT(p_swap_chain.id != 0);
+
+ SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
+ switch (swap_chain->color_space) {
+ case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
+ return COLOR_SPACE_SRGB_NONLINEAR;
+ case VK_COLOR_SPACE_HDR10_ST2084_EXT:
+ return COLOR_SPACE_HDR10_ST2084;
+ case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT:
+ return COLOR_SPACE_SRGB_LINEAR;
+ default:
+ DEV_ASSERT(false && "Unknown swap chain color space.");
+ return COLOR_SPACE_MAX;
+ }
+}
+
void RenderingDeviceDriverVulkan::swap_chain_free(SwapChainID p_swap_chain) {
DEV_ASSERT(p_swap_chain.id != 0);
SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
_swap_chain_release(swap_chain);
- if (swap_chain->render_pass.id != 0) {
- vkDestroyRenderPass(vk_device, VkRenderPass(swap_chain->render_pass.id), VKC::get_allocation_callbacks(VK_OBJECT_TYPE_RENDER_PASS));
- }
-
memdelete(swap_chain);
}
@@ -5951,6 +6042,8 @@ bool RenderingDeviceDriverVulkan::has_feature(Features p_feature) {
return true;
case SUPPORTS_BUFFER_DEVICE_ADDRESS:
return buffer_device_address_support;
+ case SUPPORTS_HDR_OUTPUT:
+ return context_driver->is_colorspace_supported();
default:
return false;
}
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h
index ea12450a4cd..4d8aa8e175c 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.h
+++ b/drivers/vulkan/rendering_device_driver_vulkan.h
@@ -370,6 +370,7 @@ private:
#endif
};
+ bool _determine_swap_chain_format(RenderingContextDriver::SurfaceID p_surface, VkFormat &r_format, VkColorSpaceKHR &r_color_space);
void _swap_chain_release(SwapChain *p_swap_chain);
public:
@@ -380,6 +381,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 void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) override final;
+ virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) override final;
virtual void swap_chain_free(SwapChainID p_swap_chain) override final;
/*********************/
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 6b725240f9e..8e9ad1c9a92 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -412,6 +412,27 @@ void EditorNode::_update_from_settings() {
scene_root->set_default_canvas_item_texture_repeat(tr);
}
+ // Enable HDR if requested and available.
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HDR) && RD::get_singleton()->has_feature(RD::Features::SUPPORTS_HDR_OUTPUT)) {
+ bool hdr_enabled = GLOBAL_GET("display/window/hdr/enabled");
+ bool prefer_high_precision = GLOBAL_GET("display/window/hdr/prefer_high_precision");
+ bool use_screen_luminance = GLOBAL_GET("display/window/hdr/use_screen_luminance");
+
+ DisplayServer::get_singleton()->window_set_hdr_output_enabled(hdr_enabled);
+ DisplayServer::get_singleton()->window_set_hdr_output_prefer_high_precision(prefer_high_precision);
+ DisplayServer::get_singleton()->window_set_hdr_output_use_screen_luminance(use_screen_luminance);
+
+ if (!use_screen_luminance) {
+ float reference_luminance = GLOBAL_GET("display/window/hdr/reference_luminance");
+ float min_luminance = GLOBAL_GET("display/window/hdr/min_luminance");
+ float max_luminance = GLOBAL_GET("display/window/hdr/max_luminance");
+
+ DisplayServer::get_singleton()->window_set_hdr_output_reference_luminance(reference_luminance);
+ DisplayServer::get_singleton()->window_set_hdr_output_min_luminance(min_luminance);
+ DisplayServer::get_singleton()->window_set_hdr_output_max_luminance(max_luminance);
+ }
+ }
+
RS::DOFBokehShape dof_shape = RS::DOFBokehShape(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_shape")));
RS::get_singleton()->camera_attributes_set_dof_blur_bokeh_shape(dof_shape);
RS::DOFBlurQuality dof_quality = RS::DOFBlurQuality(int(GLOBAL_GET("rendering/camera/depth_of_field/depth_of_field_bokeh_quality")));
@@ -470,6 +491,10 @@ void EditorNode::_update_from_settings() {
scene_root->set_use_hdr_2d(use_hdr_2d);
get_viewport()->set_use_hdr_2d(use_hdr_2d);
+ const bool tonemap_to_window = GLOBAL_GET("rendering/viewport/tonemap_to_window");
+ scene_root->set_tonemap_to_window(tonemap_to_window);
+ get_viewport()->set_tonemap_to_window(tonemap_to_window);
+
float mesh_lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels");
scene_root->set_mesh_lod_threshold(mesh_lod_threshold);
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 058adbdb197..91ce7913ec3 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -2875,6 +2875,9 @@ void Node3DEditorViewport::_project_settings_changed() {
const bool use_hdr_2d = GLOBAL_GET("rendering/viewport/hdr_2d");
viewport->set_use_hdr_2d(use_hdr_2d);
+ const bool tonemap_to_window = GLOBAL_GET("rendering/viewport/tonemap_to_window");
+ viewport->set_tonemap_to_window(tonemap_to_window);
+
const bool use_debanding = GLOBAL_GET("rendering/anti_aliasing/quality/use_debanding");
viewport->set_use_debanding(use_debanding);
diff --git a/main/main.cpp b/main/main.cpp
index 10064b35037..17be86681bd 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -3237,6 +3237,27 @@ Error Main::setup2(bool p_show_boot_logo) {
}
}
+ // Enable HDR if requested and available.
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HDR) && RD::get_singleton()->has_feature(RD::Features::SUPPORTS_HDR_OUTPUT)) {
+ bool hdr_enabled = GLOBAL_GET("display/window/hdr/enabled");
+ bool prefer_high_precision = GLOBAL_GET("display/window/hdr/prefer_high_precision");
+ bool use_screen_luminance = GLOBAL_GET("display/window/hdr/use_screen_luminance");
+
+ DisplayServer::get_singleton()->window_set_hdr_output_enabled(hdr_enabled);
+ DisplayServer::get_singleton()->window_set_hdr_output_prefer_high_precision(prefer_high_precision);
+ DisplayServer::get_singleton()->window_set_hdr_output_use_screen_luminance(use_screen_luminance);
+
+ if (!use_screen_luminance) {
+ float reference_luminance = GLOBAL_GET("display/window/hdr/reference_luminance");
+ float min_luminance = GLOBAL_GET("display/window/hdr/min_luminance");
+ float max_luminance = GLOBAL_GET("display/window/hdr/max_luminance");
+
+ DisplayServer::get_singleton()->window_set_hdr_output_reference_luminance(reference_luminance);
+ DisplayServer::get_singleton()->window_set_hdr_output_min_luminance(min_luminance);
+ DisplayServer::get_singleton()->window_set_hdr_output_max_luminance(max_luminance);
+ }
+ }
+
Color clear = GLOBAL_DEF_BASIC("rendering/environment/defaults/default_clear_color", Color(0.3, 0.3, 0.3));
RenderingServer::get_singleton()->set_default_clear_color(clear);
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 20b9236e462..1da47df9b07 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -136,6 +136,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_WINDOW_EMBEDDING:
case FEATURE_WINDOW_DRAG:
case FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE:
+ case FEATURE_HDR:
return true;
case FEATURE_EMOJI_AND_SYMBOL_PICKER:
return (os_ver.dwBuildNumber >= 17134); // Windows 10 Redstone 4 (1803)+ only.
@@ -1181,6 +1182,14 @@ typedef struct {
float rate;
} EnumRefreshRateData;
+typedef struct {
+ Vector paths;
+ Vector modes;
+ int count;
+ int screen;
+ float sdrWhiteLevelInNits;
+} EnumSdrWhiteLevelData;
+
static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumSizeData *data = (EnumSizeData *)dwData;
if (data->count == data->screen) {
@@ -1491,6 +1500,172 @@ Ref DisplayServerWindows::screen_get_image_rect(const Rect2i &p_rect) con
return img;
}
+#ifdef D3D12_ENABLED
+static bool _get_screen_desc(int p_screen, DXGI_OUTPUT_DESC1 &r_Desc) {
+ ComPtr dxgi_factory;
+ r_Desc = {};
+
+ if (FAILED(CreateDXGIFactory2(0, IID_PPV_ARGS(&dxgi_factory)))) {
+ return false;
+ }
+
+ // Retrieve the current default adapter.
+ ComPtr dxgiAdapter;
+ if (FAILED(dxgi_factory->EnumAdapters1(0, &dxgiAdapter))) {
+ return false;
+ }
+
+ UINT i = 0;
+ ComPtr screenOutput;
+ while (dxgiAdapter->EnumOutputs(i, &screenOutput) != DXGI_ERROR_NOT_FOUND) {
+ if (i == (UINT)p_screen) {
+ break;
+ }
+
+ i++;
+ }
+
+ ComPtr output6;
+ if (FAILED(screenOutput.As(&output6))) {
+ return false;
+ }
+
+ DXGI_OUTPUT_DESC1 desc1;
+ if (FAILED(output6->GetDesc1(&desc1))) {
+ return false;
+ }
+
+ r_Desc = desc1;
+ return true;
+}
+#endif // D3D12_ENABLED
+
+bool DisplayServerWindows::screen_is_hdr_supported(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ p_screen = _get_screen_index(p_screen);
+#ifdef D3D12_ENABLED
+ DXGI_OUTPUT_DESC1 desc1;
+ if (!_get_screen_desc(p_screen, desc1)) {
+ return false;
+ }
+
+ return (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
+#else
+ return false;
+#endif // D3D12_ENABLED
+}
+
+float DisplayServerWindows::screen_get_min_luminance(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ p_screen = _get_screen_index(p_screen);
+#ifdef D3D12_ENABLED
+ DXGI_OUTPUT_DESC1 desc1;
+ if (!_get_screen_desc(p_screen, desc1)) {
+ return 0.0f;
+ }
+
+ return desc1.MinLuminance;
+#else
+ return 0.0f;
+#endif // D3D12_ENABLED
+}
+
+float DisplayServerWindows::screen_get_max_luminance(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ p_screen = _get_screen_index(p_screen);
+#ifdef D3D12_ENABLED
+ DXGI_OUTPUT_DESC1 desc1;
+ if (!_get_screen_desc(p_screen, desc1)) {
+ return 0.0f;
+ }
+
+ return desc1.MaxLuminance;
+#else
+ return 0.0f;
+#endif // D3D12_ENABLED
+}
+
+float DisplayServerWindows::screen_get_max_average_luminance(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ p_screen = _get_screen_index(p_screen);
+#ifdef D3D12_ENABLED
+ DXGI_OUTPUT_DESC1 desc1;
+ if (!_get_screen_desc(p_screen, desc1)) {
+ return 0.0f;
+ }
+
+ return desc1.MaxFullFrameLuminance;
+#else
+ return 0.0f;
+#endif // D3D12_ENABLED
+}
+
+static BOOL CALLBACK _MonitorEnumProcSdrWhiteLevel(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ EnumSdrWhiteLevelData *data = (EnumSdrWhiteLevelData *)dwData;
+ if (data->count == data->screen) {
+ MONITORINFOEXW minfo;
+ memset(&minfo, 0, sizeof(minfo));
+ minfo.cbSize = sizeof(minfo);
+ GetMonitorInfoW(hMonitor, &minfo);
+
+ // First, find this screen's path.
+ for (const DISPLAYCONFIG_PATH_INFO &path : data->paths) {
+ 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 (wcscmp(minfo.szDevice, source_name.viewGdiDeviceName) == 0) {
+ // Found it, now we can 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;
+
+ LONG result = DisplayConfigGetDeviceInfo(&sdr_white_level.header);
+ if (result == ERROR_SUCCESS) {
+ data->sdrWhiteLevelInNits = (float)(sdr_white_level.SDRWhiteLevel / 1000) * 80;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ data->count++;
+ return TRUE;
+}
+
+float DisplayServerWindows::screen_get_sdr_white_level(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ p_screen = _get_screen_index(p_screen);
+ EnumSdrWhiteLevelData data = { Vector(), Vector(), 0, p_screen, 0.0f };
+
+ uint32_t path_count = 0;
+ uint32_t mode_count = 0;
+ if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) == ERROR_SUCCESS) {
+ data.paths.resize(path_count);
+ data.modes.resize(mode_count);
+ if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_count, data.paths.ptrw(), &mode_count, data.modes.ptrw(), nullptr) != ERROR_SUCCESS) {
+ data.paths.clear();
+ data.modes.clear();
+ }
+ }
+
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcSdrWhiteLevel, (LPARAM)&data);
+ return data.sdrWhiteLevelInNits;
+}
+
float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_
@@ -2973,6 +3148,74 @@ HWND DisplayServerWindows::_find_window_from_process_id(OS::ProcessID p_pid, HWN
return NULL;
}
+// Get screen HDR capabilities for internal use only.
+// Do not report values from this method to the user.
+DisplayServerWindows::ScreenHdrData DisplayServerWindows::_get_screen_hdr_data(int p_screen) const {
+ ScreenHdrData data;
+#ifdef D3D12_ENABLED
+ DXGI_OUTPUT_DESC1 desc;
+ if (_get_screen_desc(p_screen, 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;
+ }
+#else
+ // If we don't have D3D12, assume HDR is supported to avoid blocking Vulkan only builds.
+ data.hdr_supported = true;
+ data.min_luminance = 0.0f;
+ data.max_luminance = 0.0f;
+ data.max_average_luminance = 0.0f;
+#endif // D3D12_ENABLED
+
+ data.sdr_white_level = screen_get_sdr_white_level(p_screen);
+ 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);
+ }
+
+ if (p_window_data.hdr_output_use_screen_luminance) {
+ 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 (p_screen_data.min_luminance > 0.0f) {
+ rendering_context->window_set_hdr_output_min_luminance(p_window, p_screen_data.min_luminance);
+ }
+ if (p_screen_data.max_luminance > 0.0f) {
+ rendering_context->window_set_hdr_output_max_luminance(p_window, p_screen_data.max_luminance);
+ }
+ }
+ }
+#endif // RD_ENABLED
+}
+
+void DisplayServerWindows::_update_hdr_output_for_tracked_windows() {
+ HashMap outputs;
+ for (const KeyValue &E : windows) {
+ if (E.value.hdr_output_requested) {
+ int screen = window_get_current_screen(E.key);
+
+ ScreenHdrData data;
+ if (!outputs.has(screen)) {
+ data = _get_screen_hdr_data(screen);
+ outputs.insert(screen, data);
+ } else {
+ data = outputs[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_
@@ -4095,6 +4338,155 @@ DisplayServer::VSyncMode DisplayServerWindows::window_get_vsync_mode(WindowID p_
return DisplayServer::VSYNC_ENABLED;
}
+void DisplayServerWindows::window_set_hdr_output_enabled(const bool p_enabled, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+#if defined(RD_ENABLED)
+ ERR_FAIL_COND_MSG((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_enabled;
+
+ 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_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_prefer_high_precision(const bool p_enabled, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+#if defined(RD_ENABLED)
+ if (rendering_context) {
+ if (p_enabled == rendering_context->window_get_hdr_output_prefer_high_precision(p_window)) {
+ return;
+ }
+
+ rendering_context->window_set_hdr_output_prefer_high_precision(p_window, p_enabled);
+ }
+#endif
+}
+
+bool DisplayServerWindows::window_is_hdr_output_preferring_high_precision(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+#if defined(RD_ENABLED)
+ if (rendering_context) {
+ return rendering_context->window_get_hdr_output_prefer_high_precision(p_window);
+ }
+#endif
+
+ return false;
+}
+
+void DisplayServerWindows::window_set_hdr_output_use_screen_luminance(const bool p_enabled, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ WindowData &wd = windows[p_window];
+ wd.hdr_output_use_screen_luminance = p_enabled;
+
+ 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_using_screen_luminance(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ const WindowData &wd = windows[p_window];
+ return wd.hdr_output_use_screen_luminance;
+}
+
+void DisplayServerWindows::window_set_hdr_output_reference_luminance(const float p_reference_luminance, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ const WindowData &wd = windows[p_window];
+ if (wd.hdr_output_use_screen_luminance) {
+ WARN_PRINT("Reference luminance is ignored when using screen luminance.");
+ return;
+ }
+
+#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_
+#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_min_luminance(const float p_min_luminance, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ const WindowData &wd = windows[p_window];
+ if (wd.hdr_output_use_screen_luminance) {
+ WARN_PRINT("Minimum luminance is ignored when using screen luminance.");
+ return;
+ }
+
+#if defined(RD_ENABLED)
+ if (rendering_context) {
+ rendering_context->window_set_hdr_output_min_luminance(p_window, p_min_luminance);
+ }
+#endif
+}
+
+float DisplayServerWindows::window_get_hdr_output_min_luminance(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+#if defined(RD_ENABLED)
+ if (rendering_context) {
+ return rendering_context->window_get_hdr_output_min_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_
+
+ const WindowData &wd = windows[p_window];
+ if (wd.hdr_output_use_screen_luminance) {
+ WARN_PRINT("Maximum luminance is ignored when using screen luminance.");
+ return;
+ }
+
+#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_
+#if defined(RD_ENABLED)
+ if (rendering_context) {
+ return rendering_context->window_get_hdr_output_max_luminance(p_window);
+ }
+#endif
+
+ return 0.0f;
+}
+
void DisplayServerWindows::window_start_drag(WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -5561,6 +5953,10 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
} break;
+ case WM_DISPLAYCHANGE: {
+ _update_hdr_output_for_tracked_windows();
+ } break;
+
case WM_WINDOWPOSCHANGED: {
Rect2i window_client_rect;
Rect2i window_rect;
@@ -5644,6 +6040,8 @@ 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_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;
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 4a6b51b5923..89ab3dda5a1 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -548,6 +548,10 @@ class DisplayServerWindows : public DisplayServer {
bool is_popup = false;
Rect2i parent_safe_rect;
+ // HDR
+ bool hdr_output_requested = false;
+ bool hdr_output_use_screen_luminance = false;
+
bool initialized = false;
HWND parent_hwnd = 0;
@@ -687,6 +691,17 @@ class DisplayServerWindows : public DisplayServer {
HWND _find_window_from_process_id(OS::ProcessID p_pid, HWND p_current_hwnd);
+ struct ScreenHdrData {
+ bool hdr_supported = false;
+ float min_luminance = 0.0f;
+ float max_luminance = 0.0f;
+ float max_average_luminance = 0.0f;
+ float sdr_white_level = 0.0f;
+ };
+ ScreenHdrData _get_screen_hdr_data(int p_screen) const;
+ void _update_hdr_output_for_window(WindowID p_window, const WindowData &p_window_data, ScreenHdrData p_screen_data);
+ void _update_hdr_output_for_tracked_windows();
+
public:
LRESULT WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
@@ -747,6 +762,13 @@ public:
virtual Ref screen_get_image(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Ref screen_get_image_rect(const Rect2i &p_rect) const override;
+ // Display capabilities for HDR.
+ virtual bool screen_is_hdr_supported(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_min_luminance(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_max_luminance(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_max_average_luminance(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_sdr_white_level(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
virtual void screen_set_keep_on(bool p_enable) override; //disable screensaver
virtual bool screen_is_kept_on() const override;
@@ -827,6 +849,19 @@ public:
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
+ virtual void window_set_hdr_output_enabled(const bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_is_hdr_output_enabled(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_hdr_output_prefer_high_precision(const bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_is_hdr_output_preferring_high_precision(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_hdr_output_use_screen_luminance(const bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_is_hdr_output_using_screen_luminance(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_hdr_output_reference_luminance(const float p_reference_luminance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual float window_get_hdr_output_reference_luminance(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_hdr_output_min_luminance(const float p_min_luminance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual float window_get_hdr_output_min_luminance(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_hdr_output_max_luminance(const float p_max_luminance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual float window_get_hdr_output_max_luminance(WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual void window_start_drag(WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_start_resize(WindowResizeEdge p_edge, WindowID p_window = MAIN_WINDOW_ID) override;
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 4043eb55ac9..f5df250aa9b 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -1900,6 +1900,9 @@ SceneTree::SceneTree() {
const bool use_hdr_2d = GLOBAL_GET("rendering/viewport/hdr_2d");
root->set_use_hdr_2d(use_hdr_2d);
+ const bool tonemap_to_window = GLOBAL_DEF("rendering/viewport/tonemap_to_window", true);
+ root->set_tonemap_to_window(tonemap_to_window);
+
const int ssaa_mode = GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/screen_space_aa", PROPERTY_HINT_ENUM, "Disabled (Fastest),FXAA (Fast)"), 0);
root->set_screen_space_aa(Viewport::ScreenSpaceAA(ssaa_mode));
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index d0054baeff2..ef0f82158dc 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1206,6 +1206,17 @@ bool Viewport::is_using_hdr_2d() const {
return use_hdr_2d;
}
+void Viewport::set_tonemap_to_window(bool p_enable) {
+ ERR_MAIN_THREAD_GUARD;
+ tonemap_to_window = p_enable;
+ RS::get_singleton()->viewport_set_tonemap_to_screen(viewport, p_enable);
+}
+
+bool Viewport::is_tonemapping_to_window() const {
+ ERR_READ_THREAD_GUARD_V(false);
+ return tonemap_to_window;
+}
+
void Viewport::set_world_2d(const Ref &p_world_2d) {
ERR_MAIN_THREAD_GUARD;
if (world_2d == p_world_2d) {
@@ -4777,6 +4788,9 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_hdr_2d", "enable"), &Viewport::set_use_hdr_2d);
ClassDB::bind_method(D_METHOD("is_using_hdr_2d"), &Viewport::is_using_hdr_2d);
+ ClassDB::bind_method(D_METHOD("set_tonemap_to_window", "enable"), &Viewport::set_tonemap_to_window);
+ ClassDB::bind_method(D_METHOD("is_tonemapping_to_window"), &Viewport::is_tonemapping_to_window);
+
ClassDB::bind_method(D_METHOD("set_msaa_2d", "msaa"), &Viewport::set_msaa_2d);
ClassDB::bind_method(D_METHOD("get_msaa_2d"), &Viewport::get_msaa_2d);
@@ -4955,6 +4969,7 @@ void Viewport::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mesh_lod_threshold", PROPERTY_HINT_RANGE, "0,1024,0.1"), "set_mesh_lod_threshold", "get_mesh_lod_threshold");
ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_draw", PROPERTY_HINT_ENUM, "Disabled,Unshaded,Lighting,Overdraw,Wireframe,Normal Buffer,VoxelGI Albedo,VoxelGI Lighting,VoxelGI Emission,Shadow Atlas,Directional Shadow Map,Scene Luminance,SSAO,SSIL,Directional Shadow Splits,Decal Atlas,SDFGI Cascades,SDFGI Probes,VoxelGI/SDFGI Buffer,Disable Mesh LOD,OmniLight3D Cluster,SpotLight3D Cluster,Decal Cluster,ReflectionProbe Cluster,Occlusion Culling Buffer,Motion Vectors,Internal Buffer"), "set_debug_draw", "get_debug_draw");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr_2d"), "set_use_hdr_2d", "is_using_hdr_2d");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tonemap_to_window"), "set_tonemap_to_window", "is_tonemapping_to_window");
#ifndef _3D_DISABLED
ADD_GROUP("Scaling 3D", "");
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 5260b513a95..10b98d55041 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -264,6 +264,7 @@ private:
bool transparent_bg = false;
bool use_hdr_2d = false;
bool gen_mipmaps = false;
+ bool tonemap_to_window = false;
bool snap_controls_to_pixels = true;
bool snap_2d_transforms_to_pixel = false;
@@ -541,6 +542,9 @@ public:
void set_use_hdr_2d(bool p_enable);
bool is_using_hdr_2d() const;
+ void set_tonemap_to_window(bool p_enable);
+ bool is_tonemapping_to_window() const;
+
Ref get_texture() const;
void set_positional_shadow_atlas_size(int p_size);
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 1bb01313e72..85d72f9d858 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -578,6 +578,126 @@ bool Window::get_flag(Flags p_flag) const {
return flags[p_flag];
}
+void Window::set_hdr_output_enabled(bool p_enabled) {
+ ERR_MAIN_THREAD_GUARD;
+
+ hdr_output_enabled = p_enabled;
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ DisplayServer::get_singleton()->window_set_hdr_output_enabled(hdr_output_enabled, window_id);
+ }
+}
+
+bool Window::is_hdr_output_enabled() const {
+ ERR_READ_THREAD_GUARD_V(false);
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ hdr_output_enabled = DisplayServer::get_singleton()->window_is_hdr_output_enabled(window_id);
+ }
+
+ return hdr_output_enabled;
+}
+
+void Window::set_hdr_output_prefer_high_precision(bool p_enabled) {
+ ERR_MAIN_THREAD_GUARD;
+
+ hdr_output_prefer_high_precision = p_enabled;
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ DisplayServer::get_singleton()->window_set_hdr_output_prefer_high_precision(hdr_output_prefer_high_precision, window_id);
+ }
+}
+
+bool Window::is_hdr_output_preferring_high_precision() const {
+ ERR_READ_THREAD_GUARD_V(false);
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ hdr_output_prefer_high_precision = DisplayServer::get_singleton()->window_is_hdr_output_preferring_high_precision(window_id);
+ }
+
+ return hdr_output_prefer_high_precision;
+}
+
+void Window::set_hdr_output_use_screen_luminance(bool p_enabled) {
+ ERR_MAIN_THREAD_GUARD;
+
+ hdr_output_use_screen_luminance = p_enabled;
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ DisplayServer::get_singleton()->window_set_hdr_output_use_screen_luminance(hdr_output_use_screen_luminance, window_id);
+ }
+}
+
+bool Window::is_hdr_output_using_screen_luminance() const {
+ ERR_READ_THREAD_GUARD_V(false);
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ hdr_output_use_screen_luminance = DisplayServer::get_singleton()->window_is_hdr_output_using_screen_luminance(window_id);
+ }
+
+ return hdr_output_use_screen_luminance;
+}
+
+void Window::set_hdr_output_reference_luminance(float p_reference_luminance) {
+ ERR_MAIN_THREAD_GUARD;
+
+ hdr_output_reference_luminance = p_reference_luminance;
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ DisplayServer::get_singleton()->window_set_hdr_output_reference_luminance(hdr_output_reference_luminance, window_id);
+ }
+}
+
+float Window::get_hdr_output_reference_luminance() const {
+ ERR_READ_THREAD_GUARD_V(0.0f);
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ hdr_output_reference_luminance = DisplayServer::get_singleton()->window_get_hdr_output_reference_luminance(window_id);
+ }
+
+ return hdr_output_reference_luminance;
+}
+
+void Window::set_hdr_output_min_luminance(float p_min_luminance) {
+ ERR_MAIN_THREAD_GUARD;
+
+ hdr_output_min_luminance = p_min_luminance;
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ DisplayServer::get_singleton()->window_set_hdr_output_min_luminance(hdr_output_min_luminance, window_id);
+ }
+}
+
+float Window::get_hdr_output_min_luminance() const {
+ ERR_READ_THREAD_GUARD_V(0.0f);
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ hdr_output_min_luminance = DisplayServer::get_singleton()->window_get_hdr_output_min_luminance(window_id);
+ }
+
+ return hdr_output_min_luminance;
+}
+
+void Window::set_hdr_output_max_luminance(float p_max_luminance) {
+ ERR_MAIN_THREAD_GUARD;
+
+ hdr_output_max_luminance = p_max_luminance;
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ DisplayServer::get_singleton()->window_set_hdr_output_max_luminance(hdr_output_max_luminance, window_id);
+ }
+}
+
+float Window::get_hdr_output_max_luminance() const {
+ ERR_READ_THREAD_GUARD_V(0.0f);
+
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ hdr_output_max_luminance = DisplayServer::get_singleton()->window_get_hdr_output_max_luminance(window_id);
+ }
+
+ return hdr_output_max_luminance;
+}
+
bool Window::is_maximize_allowed() const {
ERR_READ_THREAD_GUARD_V(false);
if (window_id != DisplayServer::INVALID_WINDOW_ID) {
@@ -678,6 +798,16 @@ void Window::_make_window() {
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id);
+ // Set HDR output settings.
+ DisplayServer::get_singleton()->window_set_hdr_output_enabled(hdr_output_enabled, window_id);
+ DisplayServer::get_singleton()->window_set_hdr_output_prefer_high_precision(hdr_output_prefer_high_precision, window_id);
+ DisplayServer::get_singleton()->window_set_hdr_output_use_screen_luminance(hdr_output_use_screen_luminance, window_id);
+ if (!hdr_output_use_screen_luminance) {
+ DisplayServer::get_singleton()->window_set_hdr_output_reference_luminance(hdr_output_reference_luminance, window_id);
+ DisplayServer::get_singleton()->window_set_hdr_output_min_luminance(hdr_output_min_luminance, window_id);
+ DisplayServer::get_singleton()->window_set_hdr_output_max_luminance(hdr_output_max_luminance, window_id);
+ }
+
_update_window_size();
if (transient_parent) {
@@ -1395,6 +1525,15 @@ void Window::_notification(int p_what) {
size = DisplayServer::get_singleton()->window_get_size(window_id);
focused = DisplayServer::get_singleton()->window_is_focused(window_id);
}
+ // Update HDR settings to reflect the current state of the main window.
+ {
+ hdr_output_enabled = DisplayServer::get_singleton()->window_is_hdr_output_enabled(window_id);
+ hdr_output_prefer_high_precision = DisplayServer::get_singleton()->window_is_hdr_output_preferring_high_precision(window_id);
+ hdr_output_use_screen_luminance = DisplayServer::get_singleton()->window_is_hdr_output_using_screen_luminance(window_id);
+ hdr_output_reference_luminance = DisplayServer::get_singleton()->window_get_hdr_output_reference_luminance(window_id);
+ hdr_output_min_luminance = DisplayServer::get_singleton()->window_get_hdr_output_min_luminance(window_id);
+ hdr_output_max_luminance = DisplayServer::get_singleton()->window_get_hdr_output_max_luminance(window_id);
+ }
_update_window_size(); // Inform DisplayServer of minimum and maximum size.
_update_viewport_size(); // Then feed back to the viewport.
_update_window_callbacks();
@@ -2916,6 +3055,24 @@ void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_flag", "flag", "enabled"), &Window::set_flag);
ClassDB::bind_method(D_METHOD("get_flag", "flag"), &Window::get_flag);
+ ClassDB::bind_method(D_METHOD("set_hdr_output_enabled", "enabled"), &Window::set_hdr_output_enabled);
+ ClassDB::bind_method(D_METHOD("is_hdr_output_enabled"), &Window::is_hdr_output_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_hdr_output_prefer_high_precision", "enabled"), &Window::set_hdr_output_prefer_high_precision);
+ ClassDB::bind_method(D_METHOD("is_hdr_output_preferring_high_precision"), &Window::is_hdr_output_preferring_high_precision);
+
+ ClassDB::bind_method(D_METHOD("set_hdr_output_use_screen_luminance", "enabled"), &Window::set_hdr_output_use_screen_luminance);
+ ClassDB::bind_method(D_METHOD("is_hdr_output_using_screen_luminance"), &Window::is_hdr_output_using_screen_luminance);
+
+ ClassDB::bind_method(D_METHOD("set_hdr_output_reference_luminance", "reference_luminance"), &Window::set_hdr_output_reference_luminance);
+ ClassDB::bind_method(D_METHOD("get_hdr_output_reference_luminance"), &Window::get_hdr_output_reference_luminance);
+
+ ClassDB::bind_method(D_METHOD("set_hdr_output_min_luminance", "min_luminance"), &Window::set_hdr_output_min_luminance);
+ ClassDB::bind_method(D_METHOD("get_hdr_output_min_luminance"), &Window::get_hdr_output_min_luminance);
+
+ ClassDB::bind_method(D_METHOD("set_hdr_output_max_luminance", "max_luminance"), &Window::set_hdr_output_max_luminance);
+ ClassDB::bind_method(D_METHOD("get_hdr_output_max_luminance"), &Window::get_hdr_output_max_luminance);
+
ClassDB::bind_method(D_METHOD("is_maximize_allowed"), &Window::is_maximize_allowed);
ClassDB::bind_method(D_METHOD("request_attention"), &Window::request_attention);
@@ -3098,6 +3255,14 @@ void Window::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_stretch", PROPERTY_HINT_ENUM, "Fractional,Integer"), "set_content_scale_stretch", "get_content_scale_stretch");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor", PROPERTY_HINT_RANGE, "0.5,8.0,0.01"), "set_content_scale_factor", "get_content_scale_factor");
+ ADD_GROUP("HDR Output", "hdr_output_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hdr_output_enabled"), "set_hdr_output_enabled", "is_hdr_output_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hdr_output_prefer_high_precision"), "set_hdr_output_prefer_high_precision", "is_hdr_output_preferring_high_precision");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hdr_output_use_screen_luminance"), "set_hdr_output_use_screen_luminance", "is_hdr_output_using_screen_luminance");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "hdr_output_reference_luminance", PROPERTY_HINT_RANGE, "0,2000,1,or_greater"), "set_hdr_output_reference_luminance", "get_hdr_output_reference_luminance");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "hdr_output_min_luminance", PROPERTY_HINT_RANGE, "0,2000,1,or_greater"), "set_hdr_output_min_luminance", "get_hdr_output_min_luminance");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "hdr_output_max_luminance", PROPERTY_HINT_RANGE, "0,2000,1,or_greater"), "set_hdr_output_max_luminance", "get_hdr_output_max_luminance");
+
#ifndef DISABLE_DEPRECATED
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_translate", "is_auto_translating");
#endif
@@ -3201,6 +3366,10 @@ Window::Window() {
theme_owner = memnew(ThemeOwner(this));
RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_DISABLED);
+
+ // Tonemap the root viewport of this window by default.
+ tonemap_to_window = true;
+ RS::get_singleton()->viewport_set_tonemap_to_screen(get_viewport_rid(), tonemap_to_window);
}
Window::~Window() {
diff --git a/scene/main/window.h b/scene/main/window.h
index 285fd95db10..4838d764db3 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -132,6 +132,13 @@ private:
WindowInitialPosition initial_position = WINDOW_INITIAL_POSITION_ABSOLUTE;
bool force_native = false;
+ mutable bool hdr_output_enabled = false;
+ mutable bool hdr_output_prefer_high_precision = false;
+ mutable bool hdr_output_use_screen_luminance = false;
+ mutable float hdr_output_reference_luminance = 80.0f;
+ mutable float hdr_output_min_luminance = 0.0f;
+ mutable float hdr_output_max_luminance = 1000.0f;
+
bool use_font_oversampling = false;
bool transient = false;
bool transient_to_focused = false;
@@ -311,6 +318,24 @@ public:
void set_flag(Flags p_flag, bool p_enabled);
bool get_flag(Flags p_flag) const;
+ void set_hdr_output_enabled(bool p_enabled);
+ bool is_hdr_output_enabled() const;
+
+ void set_hdr_output_prefer_high_precision(bool p_enabled);
+ bool is_hdr_output_preferring_high_precision() const;
+
+ void set_hdr_output_use_screen_luminance(bool p_enabled);
+ bool is_hdr_output_using_screen_luminance() const;
+
+ void set_hdr_output_reference_luminance(float p_reference_luminance);
+ float get_hdr_output_reference_luminance() const;
+
+ void set_hdr_output_min_luminance(float p_min_luminance);
+ float get_hdr_output_min_luminance() const;
+
+ void set_hdr_output_max_luminance(float p_max_luminance);
+ float get_hdr_output_max_luminance() const;
+
bool is_maximize_allowed() const;
void request_attention();
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index c9399d9c65f..8deb321993f 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -810,6 +810,54 @@ DisplayServer::VSyncMode DisplayServer::window_get_vsync_mode(WindowID p_window)
return VSyncMode::VSYNC_ENABLED;
}
+void DisplayServer::window_set_hdr_output_enabled(const bool p_enabled, WindowID p_window) {
+ WARN_PRINT("HDR output is not supported by this display server.");
+}
+
+bool DisplayServer::window_is_hdr_output_enabled(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServer::window_set_hdr_output_prefer_high_precision(const bool p_enabled, WindowID p_window) {
+ WARN_PRINT("HDR output is not supported by this display server.");
+}
+
+bool DisplayServer::window_is_hdr_output_preferring_high_precision(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServer::window_set_hdr_output_use_screen_luminance(const bool p_enabled, WindowID p_window) {
+ WARN_PRINT("HDR output is not supported by this display server.");
+}
+
+bool DisplayServer::window_is_hdr_output_using_screen_luminance(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServer::window_set_hdr_output_reference_luminance(const float p_reference_luminance, WindowID p_window) {
+ WARN_PRINT("HDR output is not supported by this display server.");
+}
+
+float DisplayServer::window_get_hdr_output_reference_luminance(WindowID p_window) const {
+ return 0.0f;
+}
+
+void DisplayServer::window_set_hdr_output_min_luminance(const float p_min_luminance, WindowID p_window) {
+ WARN_PRINT("HDR output is not supported by this display server.");
+}
+
+float DisplayServer::window_get_hdr_output_min_luminance(WindowID p_window) const {
+ return 0.0f;
+}
+
+void DisplayServer::window_set_hdr_output_max_luminance(const float p_max_luminance, WindowID p_window) {
+ WARN_PRINT("HDR output is not supported by this display server.");
+}
+
+float DisplayServer::window_get_hdr_output_max_luminance(WindowID p_window) const {
+ return 0.0f;
+}
+
DisplayServer::WindowID DisplayServer::get_focused_window() const {
return MAIN_WINDOW_ID; // Proper value for single windows.
}
@@ -945,6 +993,12 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("screen_get_image", "screen"), &DisplayServer::screen_get_image, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_image_rect", "rect"), &DisplayServer::screen_get_image_rect);
+ ClassDB::bind_method(D_METHOD("screen_is_hdr_supported", "screen"), &DisplayServer::screen_is_hdr_supported, DEFVAL(SCREEN_OF_MAIN_WINDOW));
+ ClassDB::bind_method(D_METHOD("screen_get_min_luminance", "screen"), &DisplayServer::screen_get_min_luminance, DEFVAL(SCREEN_OF_MAIN_WINDOW));
+ ClassDB::bind_method(D_METHOD("screen_get_max_luminance", "screen"), &DisplayServer::screen_get_max_luminance, DEFVAL(SCREEN_OF_MAIN_WINDOW));
+ ClassDB::bind_method(D_METHOD("screen_get_max_average_luminance", "screen"), &DisplayServer::screen_get_max_average_luminance, DEFVAL(SCREEN_OF_MAIN_WINDOW));
+ ClassDB::bind_method(D_METHOD("screen_get_sdr_white_level", "screen"), &DisplayServer::screen_get_sdr_white_level, DEFVAL(SCREEN_OF_MAIN_WINDOW));
+
ClassDB::bind_method(D_METHOD("screen_set_orientation", "orientation", "screen"), &DisplayServer::screen_set_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_orientation", "screen"), &DisplayServer::screen_get_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
@@ -1012,6 +1066,19 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("window_set_vsync_mode", "vsync_mode", "window_id"), &DisplayServer::window_set_vsync_mode, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_get_vsync_mode", "window_id"), &DisplayServer::window_get_vsync_mode, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_set_hdr_output_enabled", "enabled", "window_id"), &DisplayServer::window_set_hdr_output_enabled, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_is_hdr_output_enabled", "window_id"), &DisplayServer::window_is_hdr_output_enabled, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_set_hdr_output_prefer_high_precision", "enabled", "window_id"), &DisplayServer::window_set_hdr_output_prefer_high_precision, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_is_hdr_output_preferring_high_precision", "window_id"), &DisplayServer::window_is_hdr_output_preferring_high_precision, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_set_hdr_output_use_screen_luminance", "enabled", "window_id"), &DisplayServer::window_set_hdr_output_use_screen_luminance, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_is_hdr_output_using_screen_luminance", "window_id"), &DisplayServer::window_is_hdr_output_using_screen_luminance, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_set_hdr_output_reference_luminance", "reference_luminance", "window_id"), &DisplayServer::window_set_hdr_output_reference_luminance, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_get_hdr_output_reference_luminance", "window_id"), &DisplayServer::window_get_hdr_output_reference_luminance, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_set_hdr_output_min_luminance", "min_luminance", "window_id"), &DisplayServer::window_set_hdr_output_min_luminance, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_get_hdr_output_min_luminance", "window_id"), &DisplayServer::window_get_hdr_output_min_luminance, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_set_hdr_output_max_luminance", "max_luminance", "window_id"), &DisplayServer::window_set_hdr_output_max_luminance, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_get_hdr_output_max_luminance", "window_id"), &DisplayServer::window_get_hdr_output_max_luminance, DEFVAL(MAIN_WINDOW_ID));
+
ClassDB::bind_method(D_METHOD("window_is_maximize_allowed", "window_id"), &DisplayServer::window_is_maximize_allowed, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_maximize_on_title_dbl_click"), &DisplayServer::window_maximize_on_title_dbl_click);
ClassDB::bind_method(D_METHOD("window_minimize_on_title_dbl_click"), &DisplayServer::window_minimize_on_title_dbl_click);
@@ -1113,6 +1180,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_WINDOW_EMBEDDING);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_MIME);
BIND_ENUM_CONSTANT(FEATURE_EMOJI_AND_SYMBOL_PICKER);
+ BIND_ENUM_CONSTANT(FEATURE_HDR);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
diff --git a/servers/display_server.h b/servers/display_server.h
index 80bba6897ff..eab2ad5d9b4 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -162,6 +162,7 @@ public:
FEATURE_WINDOW_EMBEDDING,
FEATURE_NATIVE_DIALOG_FILE_MIME,
FEATURE_EMOJI_AND_SYMBOL_PICKER,
+ FEATURE_HDR,
};
virtual bool has_feature(Feature p_feature) const = 0;
@@ -362,6 +363,13 @@ public:
virtual Ref screen_get_image_rect(const Rect2i &p_rect) const { return Ref(); }
virtual bool is_touchscreen_available() const;
+ // Display capabilities for HDR.
+ virtual bool screen_is_hdr_supported(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return false; }
+ virtual float screen_get_min_luminance(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return 0.0f; }
+ virtual float screen_get_max_luminance(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return 0.0f; }
+ virtual float screen_get_max_average_luminance(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return 0.0f; }
+ virtual float screen_get_sdr_white_level(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return 0.0f; }
+
// Keep the ScreenOrientation enum values in sync with the `display/window/handheld/orientation`
// project setting hint.
enum ScreenOrientation {
@@ -483,6 +491,19 @@ public:
virtual void window_set_vsync_mode(VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID);
virtual VSyncMode window_get_vsync_mode(WindowID p_window) const;
+ virtual void window_set_hdr_output_enabled(const bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
+ virtual bool window_is_hdr_output_enabled(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_hdr_output_prefer_high_precision(const bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
+ virtual bool window_is_hdr_output_preferring_high_precision(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_hdr_output_use_screen_luminance(const bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
+ virtual bool window_is_hdr_output_using_screen_luminance(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_hdr_output_reference_luminance(const float p_reference_luminance, WindowID p_window = MAIN_WINDOW_ID);
+ virtual float window_get_hdr_output_reference_luminance(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_hdr_output_min_luminance(const float p_min_luminance, WindowID p_window = MAIN_WINDOW_ID);
+ virtual float window_get_hdr_output_min_luminance(WindowID p_window = MAIN_WINDOW_ID) const;
+ virtual void window_set_hdr_output_max_luminance(const float p_max_luminance, WindowID p_window = MAIN_WINDOW_ID);
+ virtual float window_get_hdr_output_max_luminance(WindowID p_window = MAIN_WINDOW_ID) const;
+
virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const = 0;
virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) = 0;
diff --git a/servers/rendering/renderer_rd/effects/tone_mapper.cpp b/servers/rendering/renderer_rd/effects/tone_mapper.cpp
index 83cf2ed7195..6e02cf0efa9 100644
--- a/servers/rendering/renderer_rd/effects/tone_mapper.cpp
+++ b/servers/rendering/renderer_rd/effects/tone_mapper.cpp
@@ -120,6 +120,13 @@ void ToneMapper::tonemapper(RID p_source_color, RID p_dst_framebuffer, const Ton
tonemap.push_constant.auto_exposure_scale = p_settings.auto_exposure_scale;
tonemap.push_constant.luminance_multiplier = p_settings.luminance_multiplier;
+ // Static values that work well in most cases
+ // Consider reading from the scene itself in the future.
+ tonemap.push_constant.source_min_value = 0.0f;
+ tonemap.push_constant.source_max_value = 16.0f;
+ tonemap.push_constant.dest_min_value = p_settings.min_value;
+ tonemap.push_constant.dest_max_value = p_settings.max_value;
+
tonemap.push_constant.flags |= p_settings.use_color_correction ? TONEMAP_FLAG_USE_COLOR_CORRECTION : 0;
tonemap.push_constant.flags |= p_settings.use_fxaa ? TONEMAP_FLAG_USE_FXAA : 0;
@@ -206,6 +213,13 @@ void ToneMapper::tonemapper(RD::DrawListID p_subpass_draw_list, RID p_source_col
tonemap.push_constant.white = p_settings.white;
tonemap.push_constant.auto_exposure_scale = p_settings.auto_exposure_scale;
+ // Static values that work well in most cases
+ // Consider reading from the scene itself in the future.
+ tonemap.push_constant.source_min_value = 0.0f;
+ tonemap.push_constant.source_max_value = 16.0f;
+ tonemap.push_constant.dest_min_value = p_settings.min_value;
+ tonemap.push_constant.dest_max_value = p_settings.max_value;
+
tonemap.push_constant.flags |= p_settings.use_color_correction ? TONEMAP_FLAG_USE_COLOR_CORRECTION : 0;
tonemap.push_constant.flags |= p_settings.use_debanding ? TONEMAP_FLAG_USE_DEBANDING : 0;
diff --git a/servers/rendering/renderer_rd/effects/tone_mapper.h b/servers/rendering/renderer_rd/effects/tone_mapper.h
index 92297d807e7..4e092aa174c 100644
--- a/servers/rendering/renderer_rd/effects/tone_mapper.h
+++ b/servers/rendering/renderer_rd/effects/tone_mapper.h
@@ -87,6 +87,11 @@ private:
float white; // 4 - 88
float auto_exposure_scale; // 4 - 92
float luminance_multiplier; // 4 - 96
+
+ float source_min_value; // 4 - 100
+ float source_max_value; // 4 - 104
+ float dest_min_value; // 4 - 108
+ float dest_max_value; // 4 - 112
};
/* tonemap actually writes to a framebuffer, which is
@@ -126,6 +131,8 @@ public:
RS::EnvironmentToneMapper tonemap_mode = RS::ENV_TONE_MAPPER_LINEAR;
float exposure = 1.0;
float white = 1.0;
+ float min_value = 0.0;
+ float max_value = 1.0;
bool use_auto_exposure = false;
float auto_exposure_scale = 0.5;
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
index 42780411f70..d6c1c11365e 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
@@ -43,9 +43,15 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID
return;
}
+ _ensure_blit_pipelines();
+
RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin_for_screen(p_screen);
ERR_FAIL_COND(draw_list == RD::INVALID_ID);
+ const RD::ColorSpace color_space = RD::get_singleton()->screen_get_color_space(p_screen);
+ const float reference_luminance = RD::get_singleton()->get_context_driver()->window_get_hdr_output_reference_luminance(p_screen);
+ const float reference_multiplier = _compute_reference_multiplier(color_space, reference_luminance);
+
for (int i = 0; i < p_amount; i++) {
RID rd_texture = texture_storage->render_target_get_rd_texture(p_render_targets[i].render_target);
ERR_CONTINUE(rd_texture.is_null());
@@ -65,6 +71,7 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID
Size2 screen_size(RD::get_singleton()->screen_get_width(p_screen), RD::get_singleton()->screen_get_height(p_screen));
BlitMode mode = p_render_targets[i].lens_distortion.apply ? BLIT_MODE_LENS : (p_render_targets[i].multi_view.use_layer ? BLIT_MODE_USE_LAYER : BLIT_MODE_NORMAL);
+
RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, blit.pipelines[mode]);
RD::get_singleton()->draw_list_bind_index_array(draw_list, blit.array);
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, render_target_descriptors[rd_texture], 0);
@@ -94,7 +101,9 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID
blit.push_constant.k2 = p_render_targets[i].lens_distortion.k2;
blit.push_constant.upscale = p_render_targets[i].lens_distortion.upscale;
blit.push_constant.aspect_ratio = p_render_targets[i].lens_distortion.aspect_ratio;
- blit.push_constant.convert_to_srgb = texture_storage->render_target_is_using_hdr(p_render_targets[i].render_target);
+ blit.push_constant.source_is_srgb = !texture_storage->render_target_is_using_hdr(p_render_targets[i].render_target);
+ blit.push_constant.target_color_space = color_space;
+ blit.push_constant.reference_multiplier = reference_multiplier;
RD::get_singleton()->draw_list_set_push_constant(draw_list, &blit.push_constant, sizeof(BlitPushConstant));
RD::get_singleton()->draw_list_draw(draw_list, true);
@@ -129,16 +138,8 @@ void RendererCompositorRD::initialize() {
blit_modes.push_back("\n");
blit.shader.initialize(blit_modes);
-
blit.shader_version = blit.shader.version_create();
- for (int i = 0; i < BLIT_MODE_MAX; i++) {
- blit.pipelines[i] = RD::get_singleton()->render_pipeline_create(blit.shader.version_get_shader(blit.shader_version, i), RD::get_singleton()->screen_get_framebuffer_format(DisplayServer::MAIN_WINDOW_ID), RD::INVALID_ID, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), i == BLIT_MODE_NORMAL_ALPHA ? RenderingDevice::PipelineColorBlendState::create_blend() : RenderingDevice::PipelineColorBlendState::create_disabled(), 0);
-
- // Unload shader modules to save memory.
- RD::get_singleton()->shader_destroy_modules(blit.shader.version_get_shader(blit.shader_version, i));
- }
-
//create index array for copy shader
Vector pv;
pv.resize(6 * 2);
@@ -178,6 +179,39 @@ void RendererCompositorRD::finalize() {
RD::get_singleton()->free(blit.sampler);
}
+void RendererCompositorRD::_ensure_blit_pipelines() {
+ RenderingDevice::FramebufferFormatID main_window_format = RD::get_singleton()->screen_get_framebuffer_format(DisplayServer::MAIN_WINDOW_ID);
+ if (main_window_format != prev_main_window_format) {
+ for (int i = 0; i < BLIT_MODE_MAX; i++) {
+ if (blit.pipelines[i].is_valid()) {
+ RD::get_singleton()->free(blit.pipelines[i]);
+ }
+
+ blit.pipelines[i] = RD::get_singleton()->render_pipeline_create(blit.shader.version_get_shader(blit.shader_version, i), main_window_format, RD::INVALID_ID, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), i == BLIT_MODE_NORMAL_ALPHA ? RenderingDevice::PipelineColorBlendState::create_blend() : RenderingDevice::PipelineColorBlendState::create_disabled(), 0);
+ }
+
+ prev_main_window_format = main_window_format;
+ }
+}
+
+float RendererCompositorRD::_compute_reference_multiplier(RD::ColorSpace p_color_space, const float p_reference_luminance) {
+ switch (p_color_space) {
+ case RD::COLOR_SPACE_HDR10_ST2084:
+ // Max brightness of ST2084 is 10000 nits, we output from 0 to 1.
+ return p_reference_luminance / 10000.0f;
+ case RD::COLOR_SPACE_SRGB_LINEAR:
+#ifdef WINDOWS_ENABLED
+ // Windows expects multiples of 80 nits.
+ return p_reference_luminance / 80.0f;
+#else
+ // Default to 100 nits.
+ return p_reference_luminance / 100.0f;
+#endif
+ default:
+ return 1.0f;
+ }
+}
+
void RendererCompositorRD::set_boot_image(const Ref &p_image, const Color &p_color, bool p_scale, bool p_use_filter) {
if (p_image.is_null() || p_image->is_empty()) {
return;
@@ -189,6 +223,8 @@ void RendererCompositorRD::set_boot_image(const Ref &p_image, const Color
return;
}
+ _ensure_blit_pipelines();
+
RID texture = texture_storage->texture_allocate();
texture_storage->texture_2d_initialize(texture, p_image);
RID rd_texture = texture_storage->texture_get_rd_texture(texture, false);
@@ -225,6 +261,10 @@ void RendererCompositorRD::set_boot_image(const Ref &p_image, const Color
screenrect.position /= window_size;
screenrect.size /= window_size;
+ const RD::ColorSpace color_space = RD::get_singleton()->screen_get_color_space(DisplayServer::MAIN_WINDOW_ID);
+ const float reference_luminance = RD::get_singleton()->get_context_driver()->window_get_hdr_output_reference_luminance(DisplayServer::MAIN_WINDOW_ID);
+ const float reference_multiplier = _compute_reference_multiplier(color_space, reference_luminance);
+
RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin_for_screen(DisplayServer::MAIN_WINDOW_ID, p_color);
RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, blit.pipelines[BLIT_MODE_NORMAL_ALPHA]);
@@ -250,7 +290,9 @@ void RendererCompositorRD::set_boot_image(const Ref &p_image, const Color
blit.push_constant.k2 = 0;
blit.push_constant.upscale = 1.0;
blit.push_constant.aspect_ratio = 1.0;
- blit.push_constant.convert_to_srgb = false;
+ blit.push_constant.source_is_srgb = true;
+ blit.push_constant.target_color_space = color_space;
+ blit.push_constant.reference_multiplier = reference_multiplier;
RD::get_singleton()->draw_list_set_push_constant(draw_list, &blit.push_constant, sizeof(BlitPushConstant));
RD::get_singleton()->draw_list_draw(draw_list, true);
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.h b/servers/rendering/renderer_rd/renderer_compositor_rd.h
index 41457e4f480..7c3d62937c1 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.h
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h
@@ -69,21 +69,23 @@ protected:
};
struct BlitPushConstant {
- float src_rect[4];
- float dst_rect[4];
+ float src_rect[4]; // 16 - 16
+ float dst_rect[4]; // 16 - 32
- float rotation_sin;
- float rotation_cos;
- float pad[2];
+ float rotation_sin; // 4 - 36
+ float rotation_cos; // 4 - 40
- float eye_center[2];
- float k1;
- float k2;
+ float eye_center[2]; // 8 - 48
+ float k1; // 4 - 52
+ float k2; // 4 - 56
- float upscale;
- float aspect_ratio;
- uint32_t layer;
- uint32_t convert_to_srgb;
+ float upscale; // 4 - 60
+ float aspect_ratio; // 4 - 64
+ uint32_t layer; // 4 - 68
+ uint32_t source_is_srgb; // 4 - 72
+
+ uint32_t target_color_space; // 4 - 76
+ float reference_multiplier; // 4 - 80
};
struct Blit {
@@ -104,6 +106,10 @@ protected:
static uint64_t frame;
static RendererCompositorRD *singleton;
+ RenderingDevice::FramebufferFormatID prev_main_window_format;
+ void _ensure_blit_pipelines();
+ float _compute_reference_multiplier(RD::ColorSpace p_color_space, const float p_reference_luminance);
+
public:
RendererUtilities *get_utilities() { return utilities; }
RendererLightStorage *get_light_storage() { return light_storage; }
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index 4d7e87534a7..a930026608e 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -630,6 +630,8 @@ void RendererSceneRenderRD::_render_buffers_post_process_and_tonemap(const Rende
tonemap.tonemap_mode = environment_get_tone_mapper(p_render_data->environment);
tonemap.white = environment_get_white(p_render_data->environment);
tonemap.exposure = environment_get_exposure(p_render_data->environment);
+ tonemap.min_value = environment_get_min_value(p_render_data->environment);
+ tonemap.max_value = environment_get_max_value(p_render_data->environment);
}
tonemap.use_color_correction = false;
@@ -724,6 +726,8 @@ void RendererSceneRenderRD::_post_process_subpass(RID p_source_texture, RID p_fr
tonemap.tonemap_mode = environment_get_tone_mapper(p_render_data->environment);
tonemap.exposure = environment_get_exposure(p_render_data->environment);
tonemap.white = environment_get_white(p_render_data->environment);
+ tonemap.min_value = environment_get_min_value(p_render_data->environment);
+ tonemap.max_value = environment_get_max_value(p_render_data->environment);
}
// We don't support glow or auto exposure here, if they are needed, don't use subpasses!
diff --git a/servers/rendering/renderer_rd/shaders/blit.glsl b/servers/rendering/renderer_rd/shaders/blit.glsl
index fe6416f03c6..bdb41ed4c9e 100644
--- a/servers/rendering/renderer_rd/shaders/blit.glsl
+++ b/servers/rendering/renderer_rd/shaders/blit.glsl
@@ -10,7 +10,6 @@ layout(push_constant, std140) uniform Pos {
float rotation_sin;
float rotation_cos;
- vec2 pad;
vec2 eye_center;
float k1;
@@ -19,7 +18,10 @@ layout(push_constant, std140) uniform Pos {
float upscale;
float aspect_ratio;
uint layer;
- bool convert_to_srgb;
+ bool source_is_srgb;
+
+ uint target_color_space;
+ float reference_multiplier;
}
data;
@@ -50,7 +52,6 @@ layout(push_constant, std140) uniform Pos {
float rotation_sin;
float rotation_cos;
- vec2 pad;
vec2 eye_center;
float k1;
@@ -59,7 +60,10 @@ layout(push_constant, std140) uniform Pos {
float upscale;
float aspect_ratio;
uint layer;
- bool convert_to_srgb;
+ bool source_is_srgb;
+
+ uint target_color_space;
+ float reference_multiplier;
}
data;
@@ -73,6 +77,15 @@ layout(binding = 0) uniform sampler2DArray src_rt;
layout(binding = 0) uniform sampler2D src_rt;
#endif
+// Keep in sync with RenderingDeviceCommons::ColorSpace
+#define COLOR_SPACE_SRGB_LINEAR 0
+#define COLOR_SPACE_SRGB_NONLINEAR 1
+#define COLOR_SPACE_HDR10_ST2084 2
+
+vec3 srgb_to_linear(vec3 color) {
+ return mix(pow((color.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), color.rgb * (1.0 / 12.92), lessThan(color.rgb, vec3(0.04045)));
+}
+
vec3 linear_to_srgb(vec3 color) {
// If going to srgb, clamp from 0 to 1.
color = clamp(color, vec3(0.0), vec3(1.0));
@@ -80,6 +93,29 @@ vec3 linear_to_srgb(vec3 color) {
return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
}
+vec3 rec709_to_rec2020(vec3 color) {
+ const mat3 conversion = mat3(
+ 0.627402, 0.069095, 0.016394,
+ 0.329292, 0.919544, 0.088028,
+ 0.043306, 0.011360, 0.895578);
+ return conversion * color;
+}
+
+vec3 linear_to_st2084(vec3 color) {
+ // Linear color should already be adjusted between 0 and 10,000 nits.
+ color = clamp(color, vec3(0.0), vec3(1.0));
+
+ // Apply ST2084 curve
+ const float c1 = 0.8359375;
+ const float c2 = 18.8515625;
+ const float c3 = 18.6875;
+ const float m1 = 0.1593017578125;
+ const float m2 = 78.84375;
+ vec3 cp = pow(abs(color), vec3(m1));
+
+ return pow((c1 + c2 * cp) / (1 + c3 * cp), vec3(m2));
+}
+
void main() {
#ifdef APPLY_LENS_DISTORTION
vec2 coords = uv * 2.0 - 1.0;
@@ -116,7 +152,33 @@ void main() {
color = texture(src_rt, uv);
#endif
- if (data.convert_to_srgb) {
- color.rgb = linear_to_srgb(color.rgb); // Regular linear -> SRGB conversion.
+ // Colorspace conversion for final blit
+ if (data.target_color_space == COLOR_SPACE_SRGB_LINEAR) {
+ if (data.source_is_srgb == true) {
+ // sRGB -> linear conversion
+ color.rgb = srgb_to_linear(color.rgb);
+ }
+
+ // Adjust brightness of SDR content to reference luminance
+ color.rgb *= data.reference_multiplier;
+ } else if (data.target_color_space == COLOR_SPACE_SRGB_NONLINEAR) {
+ if (data.source_is_srgb == false) {
+ // linear -> sRGB conversion
+ color.rgb = linear_to_srgb(color.rgb);
+ }
+ } else if (data.target_color_space == COLOR_SPACE_HDR10_ST2084) {
+ if (data.source_is_srgb == true) {
+ // sRGB -> linear conversion
+ color.rgb = srgb_to_linear(color.rgb);
+ }
+
+ // Convert to Rec.2020 primaries
+ color.rgb = rec709_to_rec2020(color.rgb);
+
+ // Adjust brightness of SDR content to reference luminance
+ color.rgb *= data.reference_multiplier;
+
+ // Apply the ST2084 curve
+ color.rgb = linear_to_st2084(color.rgb);
}
}
diff --git a/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl b/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl
index 3bb26d29d15..886f27843b0 100644
--- a/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl
@@ -97,6 +97,11 @@ layout(push_constant, std430) uniform Params {
float white;
float auto_exposure_scale;
float luminance_multiplier;
+
+ float source_min_value;
+ float source_max_value;
+ float dest_min_value;
+ float dest_max_value;
}
params;
@@ -344,6 +349,14 @@ vec3 linear_to_srgb(vec3 color) {
return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
}
+vec3 normalize(vec3 value, vec3 min, vec3 max) {
+ return (value - min) / (max - min);
+}
+
+vec3 denormalize(vec3 value, vec3 min, vec3 max) {
+ return value * (max - min) + min;
+}
+
#define TONEMAPPER_LINEAR 0
#define TONEMAPPER_REINHARD 1
#define TONEMAPPER_FILMIC 2
@@ -353,17 +366,26 @@ vec3 linear_to_srgb(vec3 color) {
vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR
// Ensure color values passed to tonemappers are positive.
// They can be negative in the case of negative lights, which leads to undesired behavior.
+ // Linear is special: it always passes through with no adjustments.
+
if (params.tonemapper == TONEMAPPER_LINEAR) {
return color;
} else if (params.tonemapper == TONEMAPPER_REINHARD) {
- return tonemap_reinhard(max(vec3(0.0f), color), white);
+ color = tonemap_reinhard(max(vec3(0.0f), color), white);
} else if (params.tonemapper == TONEMAPPER_FILMIC) {
- return tonemap_filmic(max(vec3(0.0f), color), white);
+ color = tonemap_filmic(max(vec3(0.0f), color), white);
} else if (params.tonemapper == TONEMAPPER_ACES) {
- return tonemap_aces(max(vec3(0.0f), color), white);
+ color = tonemap_aces(max(vec3(0.0f), color), white);
} else { // TONEMAPPER_AGX
- return tonemap_agx(color);
+ color = tonemap_agx(color);
}
+
+ // Expand color to the target output range for HDR render targets.
+ if (!bool(params.flags & FLAG_CONVERT_TO_SRGB)) {
+ color = denormalize(color, vec3(params.dest_min_value), vec3(params.dest_max_value));
+ }
+
+ return color;
}
#ifdef USE_MULTIVIEW
@@ -410,14 +432,33 @@ vec3 gather_glow(sampler2D tex, vec2 uv) { // sample all selected glow levels
#define GLOW_MODE_REPLACE 3
#define GLOW_MODE_MIX 4
-vec3 apply_glow(vec3 color, vec3 glow) { // apply glow using the selected blending mode
+vec3 apply_glow(vec3 color, vec3 glow, bool hdrInput) { // apply glow using the selected blending mode
if (params.glow_mode == GLOW_MODE_ADD) {
return color + glow;
} else if (params.glow_mode == GLOW_MODE_SCREEN) {
+ if (hdrInput) {
+ // Compress the color and glow from scene intensity to [0, 1] to avoid artifacts due to the color clamping.
+ color = normalize(color, vec3(params.source_min_value), vec3(params.source_max_value));
+ glow = normalize(glow, vec3(params.source_min_value), vec3(params.source_max_value));
+ }
+
// Needs color clamping.
glow.rgb = clamp(glow.rgb, vec3(0.0f), vec3(1.0f));
- return max((color + glow) - (color * glow), vec3(0.0));
+ color = max((color + glow) - (color * glow), vec3(0.0));
+
+ if (hdrInput) {
+ // Expand the color back to the original intensity range.
+ color = denormalize(color, vec3(params.source_min_value), vec3(params.source_max_value));
+ }
+
+ return color;
} else if (params.glow_mode == GLOW_MODE_SOFTLIGHT) {
+ if (hdrInput) {
+ // Compress the color and glow from scene intensity to [0, 1] to avoid artifacts due to the color clamping.
+ color = normalize(color, vec3(params.source_min_value), vec3(params.source_max_value));
+ glow = normalize(glow, vec3(params.source_min_value), vec3(params.source_max_value));
+ }
+
// Needs color clamping.
glow.rgb = clamp(glow.rgb, vec3(0.0f), vec3(1.0f));
glow = glow * vec3(0.5f) + vec3(0.5f);
@@ -425,6 +466,12 @@ vec3 apply_glow(vec3 color, vec3 glow) { // apply glow using the selected blendi
color.r = (glow.r <= 0.5f) ? (color.r - (1.0f - 2.0f * glow.r) * color.r * (1.0f - color.r)) : (((glow.r > 0.5f) && (color.r <= 0.25f)) ? (color.r + (2.0f * glow.r - 1.0f) * (4.0f * color.r * (4.0f * color.r + 1.0f) * (color.r - 1.0f) + 7.0f * color.r)) : (color.r + (2.0f * glow.r - 1.0f) * (sqrt(color.r) - color.r)));
color.g = (glow.g <= 0.5f) ? (color.g - (1.0f - 2.0f * glow.g) * color.g * (1.0f - color.g)) : (((glow.g > 0.5f) && (color.g <= 0.25f)) ? (color.g + (2.0f * glow.g - 1.0f) * (4.0f * color.g * (4.0f * color.g + 1.0f) * (color.g - 1.0f) + 7.0f * color.g)) : (color.g + (2.0f * glow.g - 1.0f) * (sqrt(color.g) - color.g)));
color.b = (glow.b <= 0.5f) ? (color.b - (1.0f - 2.0f * glow.b) * color.b * (1.0f - color.b)) : (((glow.b > 0.5f) && (color.b <= 0.25f)) ? (color.b + (2.0f * glow.b - 1.0f) * (4.0f * color.b * (4.0f * color.b + 1.0f) * (color.b - 1.0f) + 7.0f * color.b)) : (color.b + (2.0f * glow.b - 1.0f) * (sqrt(color.b) - color.b)));
+
+ if (hdrInput) {
+ // Expand the color back to the original intensity range.
+ color = denormalize(color, vec3(params.source_min_value), vec3(params.source_max_value));
+ }
+
return color;
} else { //replace
return glow;
@@ -579,7 +626,7 @@ void main() {
glow = linear_to_srgb(glow);
}
- color.rgb = apply_glow(color.rgb, glow);
+ color.rgb = apply_glow(color.rgb, glow, !bool(params.flags & FLAG_CONVERT_TO_SRGB));
}
#endif
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index 24f11ec1460..3a13e839a99 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -1266,9 +1266,12 @@ public:
// Tonemap
PASS4(environment_set_tonemap, RID, RS::EnvironmentToneMapper, float, float)
+ PASS3(environment_set_tonemap_range, RID, float, float)
PASS1RC(RS::EnvironmentToneMapper, environment_get_tone_mapper, RID)
PASS1RC(float, environment_get_exposure, RID)
PASS1RC(float, environment_get_white, RID)
+ PASS1RC(float, environment_get_min_value, RID)
+ PASS1RC(float, environment_get_max_value, RID)
// Fog
PASS11(environment_set_fog, RID, bool, const Color &, float, float, float, float, float, float, float, RS::EnvironmentFogMode)
diff --git a/servers/rendering/renderer_scene_render.cpp b/servers/rendering/renderer_scene_render.cpp
index 60516aeb609..5d175505e31 100644
--- a/servers/rendering/renderer_scene_render.cpp
+++ b/servers/rendering/renderer_scene_render.cpp
@@ -365,6 +365,10 @@ void RendererSceneRender::environment_set_tonemap(RID p_env, RS::EnvironmentTone
environment_storage.environment_set_tonemap(p_env, p_tone_mapper, p_exposure, p_white);
}
+void RendererSceneRender::environment_set_tonemap_range(RID p_env, float p_min_value, float p_max_value) {
+ environment_storage.environment_set_tonemap_range(p_env, p_min_value, p_max_value);
+}
+
RS::EnvironmentToneMapper RendererSceneRender::environment_get_tone_mapper(RID p_env) const {
return environment_storage.environment_get_tone_mapper(p_env);
}
@@ -377,6 +381,14 @@ float RendererSceneRender::environment_get_white(RID p_env) const {
return environment_storage.environment_get_white(p_env);
}
+float RendererSceneRender::environment_get_min_value(RID p_env) const {
+ return environment_storage.environment_get_min_value(p_env);
+}
+
+float RendererSceneRender::environment_get_max_value(RID p_env) const {
+ return environment_storage.environment_get_max_value(p_env);
+}
+
// Fog
void RendererSceneRender::environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode) {
diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h
index da524365a78..63d76dca028 100644
--- a/servers/rendering/renderer_scene_render.h
+++ b/servers/rendering/renderer_scene_render.h
@@ -136,9 +136,12 @@ public:
// Tonemap
void environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white);
+ void environment_set_tonemap_range(RID p_env, float p_min_value, float p_max_value);
RS::EnvironmentToneMapper environment_get_tone_mapper(RID p_env) const;
float environment_get_exposure(RID p_env) const;
float environment_get_white(RID p_env) const;
+ float environment_get_min_value(RID p_env) const;
+ float environment_get_max_value(RID p_env) const;
// Fog
void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode);
diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp
index b816ca7a8ab..b012deb84e9 100644
--- a/servers/rendering/renderer_viewport.cpp
+++ b/servers/rendering/renderer_viewport.cpp
@@ -333,6 +333,27 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
// The scene renderer will still copy over the last frame, so we need to clear the render target.
force_clear_render_target = true;
}
+
+ // Check if the viewport is tonemapping to the screen.
+ if (p_viewport->tonemap_to_screen) {
+ DisplayServer::WindowID parent_window = _get_containing_window(p_viewport);
+ if (parent_window != DisplayServer::INVALID_WINDOW_ID) {
+ RenderingContextDriver *context_driver = RD::get_singleton()->get_context_driver();
+
+ if (context_driver->window_get_hdr_output_enabled(parent_window)) {
+ float min_luminance = context_driver->window_get_hdr_output_min_luminance(parent_window);
+ float max_luminance = context_driver->window_get_hdr_output_max_luminance(parent_window);
+ float reference_luminance = context_driver->window_get_hdr_output_reference_luminance(parent_window);
+
+ float min_value = MAX(min_luminance / MAX(reference_luminance, 1.0f), 0.0f);
+ float max_value = MAX(max_luminance / MAX(reference_luminance, 1.0f), 1.0f);
+
+ RSG::scene->environment_set_tonemap_range(environment, min_value, max_value);
+ } else {
+ RSG::scene->environment_set_tonemap_range(environment, 0.0f, 1.0f);
+ }
+ }
+ }
}
}
@@ -710,6 +731,21 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
}
}
+DisplayServer::WindowID RendererViewport::_get_containing_window(Viewport *p_viewport) {
+ if (p_viewport->viewport_to_screen != DisplayServer::INVALID_WINDOW_ID) {
+ return p_viewport->viewport_to_screen;
+ }
+
+ if (p_viewport->parent.is_valid()) {
+ Viewport *parent = viewport_owner.get_or_null(p_viewport->parent);
+ if (parent) {
+ return _get_containing_window(parent);
+ }
+ }
+
+ return DisplayServer::INVALID_WINDOW_ID;
+}
+
void RendererViewport::draw_viewports(bool p_swap_buffers) {
timestamp_vp_map.clear();
@@ -1124,6 +1160,13 @@ void RendererViewport::viewport_set_render_direct_to_screen(RID p_viewport, bool
}
}
+void RendererViewport::viewport_set_tonemap_to_screen(RID p_viewport, bool p_enable) {
+ Viewport *viewport = viewport_owner.get_or_null(p_viewport);
+ ERR_FAIL_NULL(viewport);
+
+ viewport->tonemap_to_screen = p_enable;
+}
+
void RendererViewport::viewport_set_update_mode(RID p_viewport, RS::ViewportUpdateMode p_mode) {
Viewport *viewport = viewport_owner.get_or_null(p_viewport);
ERR_FAIL_NULL(viewport);
diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h
index 2e5876cc7b6..a7cb2fa9e87 100644
--- a/servers/rendering/renderer_viewport.h
+++ b/servers/rendering/renderer_viewport.h
@@ -87,6 +87,7 @@ public:
DisplayServer::WindowID viewport_to_screen;
Rect2 viewport_to_screen_rect;
bool viewport_render_direct_to_screen;
+ bool tonemap_to_screen;
bool disable_2d = false;
RS::ViewportEnvironmentMode disable_environment = RS::VIEWPORT_ENVIRONMENT_INHERIT;
@@ -211,6 +212,7 @@ private:
void _configure_3d_render_buffers(Viewport *p_viewport);
void _draw_3d(Viewport *p_viewport);
void _draw_viewport(Viewport *p_viewport);
+ DisplayServer::WindowID _get_containing_window(Viewport *p_viewport);
int occlusion_rays_per_thread = 512;
@@ -226,6 +228,7 @@ public:
void viewport_attach_to_screen(RID p_viewport, const Rect2 &p_rect = Rect2(), DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID);
void viewport_set_render_direct_to_screen(RID p_viewport, bool p_enable);
+ void viewport_set_tonemap_to_screen(RID p_viewport, bool p_enable);
void viewport_set_active(RID p_viewport, bool p_active);
void viewport_set_parent_viewport(RID p_viewport, RID p_parent_viewport);
diff --git a/servers/rendering/rendering_context_driver.cpp b/servers/rendering/rendering_context_driver.cpp
index b623be40980..93bcaaf42c4 100644
--- a/servers/rendering/rendering_context_driver.cpp
+++ b/servers/rendering/rendering_context_driver.cpp
@@ -75,6 +75,86 @@ DisplayServer::VSyncMode RenderingContextDriver::window_get_vsync_mode(DisplaySe
}
}
+void RenderingContextDriver::window_set_hdr_output_enabled(DisplayServer::WindowID p_window, bool p_enabled) {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ surface_set_hdr_output_enabled(surface, p_enabled);
+ }
+}
+
+bool RenderingContextDriver::window_get_hdr_output_enabled(DisplayServer::WindowID p_window) const {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ return surface_get_hdr_output_enabled(surface);
+ } else {
+ return false;
+ }
+}
+
+void RenderingContextDriver::window_set_hdr_output_prefer_high_precision(DisplayServer::WindowID p_window, bool p_enabled) {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ surface_set_hdr_output_prefer_high_precision(surface, p_enabled);
+ }
+}
+
+bool RenderingContextDriver::window_get_hdr_output_prefer_high_precision(DisplayServer::WindowID p_window) const {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ return surface_get_hdr_output_prefer_high_precision(surface);
+ } else {
+ return false;
+ }
+}
+
+void RenderingContextDriver::window_set_hdr_output_reference_luminance(DisplayServer::WindowID p_window, float p_reference_luminance) {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ surface_set_hdr_output_reference_luminance(surface, p_reference_luminance);
+ }
+}
+
+float RenderingContextDriver::window_get_hdr_output_reference_luminance(DisplayServer::WindowID p_window) const {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ return surface_get_hdr_output_reference_luminance(surface);
+ } else {
+ return 0.0f;
+ }
+}
+
+void RenderingContextDriver::window_set_hdr_output_min_luminance(DisplayServer::WindowID p_window, float p_min_luminance) {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ surface_set_hdr_output_min_luminance(surface, p_min_luminance);
+ }
+}
+
+float RenderingContextDriver::window_get_hdr_output_min_luminance(DisplayServer::WindowID p_window) const {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ return surface_get_hdr_output_min_luminance(surface);
+ } else {
+ return 0.0f;
+ }
+}
+
+void RenderingContextDriver::window_set_hdr_output_max_luminance(DisplayServer::WindowID p_window, float p_max_luminance) {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ surface_set_hdr_output_max_luminance(surface, p_max_luminance);
+ }
+}
+
+float RenderingContextDriver::window_get_hdr_output_max_luminance(DisplayServer::WindowID p_window) const {
+ SurfaceID surface = surface_get_from_window(p_window);
+ if (surface) {
+ return surface_get_hdr_output_max_luminance(surface);
+ } else {
+ return 0.0f;
+ }
+}
+
void RenderingContextDriver::window_destroy(DisplayServer::WindowID p_window) {
SurfaceID surface = surface_get_from_window(p_window);
if (surface) {
diff --git a/servers/rendering/rendering_context_driver.h b/servers/rendering/rendering_context_driver.h
index c715a4e899e..1276402cdbe 100644
--- a/servers/rendering/rendering_context_driver.h
+++ b/servers/rendering/rendering_context_driver.h
@@ -48,6 +48,16 @@ public:
void window_set_size(DisplayServer::WindowID p_window, uint32_t p_width, uint32_t p_height);
void window_set_vsync_mode(DisplayServer::WindowID p_window, DisplayServer::VSyncMode p_vsync_mode);
DisplayServer::VSyncMode window_get_vsync_mode(DisplayServer::WindowID p_window) const;
+ void window_set_hdr_output_enabled(DisplayServer::WindowID p_window, bool p_enabled);
+ bool window_get_hdr_output_enabled(DisplayServer::WindowID p_window) const;
+ void window_set_hdr_output_prefer_high_precision(DisplayServer::WindowID p_window, bool p_enabled);
+ bool window_get_hdr_output_prefer_high_precision(DisplayServer::WindowID p_window) const;
+ void window_set_hdr_output_reference_luminance(DisplayServer::WindowID p_window, float p_reference_luminance);
+ float window_get_hdr_output_reference_luminance(DisplayServer::WindowID p_window) const;
+ void window_set_hdr_output_min_luminance(DisplayServer::WindowID p_window, float p_min_luminance);
+ float window_get_hdr_output_min_luminance(DisplayServer::WindowID p_window) const;
+ void window_set_hdr_output_max_luminance(DisplayServer::WindowID p_window, float p_max_luminance);
+ float window_get_hdr_output_max_luminance(DisplayServer::WindowID p_window) const;
void window_destroy(DisplayServer::WindowID p_window);
public:
@@ -98,6 +108,16 @@ public:
virtual void surface_set_size(SurfaceID p_surface, uint32_t p_width, uint32_t p_height) = 0;
virtual void surface_set_vsync_mode(SurfaceID p_surface, DisplayServer::VSyncMode p_vsync_mode) = 0;
virtual DisplayServer::VSyncMode surface_get_vsync_mode(SurfaceID p_surface) const = 0;
+ virtual void surface_set_hdr_output_enabled(SurfaceID p_surface, bool p_enabled) = 0;
+ virtual bool surface_get_hdr_output_enabled(SurfaceID p_surface) const = 0;
+ virtual void surface_set_hdr_output_prefer_high_precision(SurfaceID p_surface, bool p_enabled) = 0;
+ virtual bool surface_get_hdr_output_prefer_high_precision(SurfaceID p_surface) const = 0;
+ virtual void surface_set_hdr_output_reference_luminance(SurfaceID p_surface, float p_reference_luminance) = 0;
+ virtual float surface_get_hdr_output_reference_luminance(SurfaceID p_surface) const = 0;
+ virtual void surface_set_hdr_output_min_luminance(SurfaceID p_surface, float p_min_luminance) = 0;
+ virtual float surface_get_hdr_output_min_luminance(SurfaceID p_surface) const = 0;
+ virtual void surface_set_hdr_output_max_luminance(SurfaceID p_surface, float p_max_luminance) = 0;
+ virtual float surface_get_hdr_output_max_luminance(SurfaceID p_surface) const = 0;
virtual uint32_t surface_get_width(SurfaceID p_surface) const = 0;
virtual uint32_t surface_get_height(SurfaceID p_surface) const = 0;
virtual void surface_set_needs_resize(SurfaceID p_surface, bool p_needs_resize) = 0;
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 9a3e4fd9d7f..67f78659edf 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -4209,6 +4209,28 @@ RenderingDevice::FramebufferFormatID RenderingDevice::screen_get_framebuffer_for
return const_cast(this)->framebuffer_format_create(screen_attachment);
}
+RenderingDevice::DataFormat RenderingDevice::screen_get_color_format(DisplayServer::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(), DATA_FORMAT_MAX, "Screen was never prepared.");
+
+ DataFormat format = driver->swap_chain_get_format(it->value);
+ ERR_FAIL_COND_V_MSG(format == DATA_FORMAT_MAX, DATA_FORMAT_MAX, "Unknown format.");
+ return format;
+}
+
+RenderingDevice::ColorSpace RenderingDevice::screen_get_color_space(DisplayServer::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(), COLOR_SPACE_MAX, "Screen was never prepared.");
+
+ ColorSpace color_space = driver->swap_chain_get_color_space(it->value);
+ ERR_FAIL_COND_V_MSG(color_space == COLOR_SPACE_MAX, COLOR_SPACE_MAX, "Unknown color space.");
+ return color_space;
+}
+
Error RenderingDevice::screen_free(DisplayServer::WindowID p_screen) {
_THREAD_SAFE_METHOD_
@@ -7337,6 +7359,8 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("screen_get_width", "screen"), &RenderingDevice::screen_get_width, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("screen_get_height", "screen"), &RenderingDevice::screen_get_height, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("screen_get_framebuffer_format", "screen"), &RenderingDevice::screen_get_framebuffer_format, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("screen_get_color_format", "screen"), &RenderingDevice::screen_get_color_format, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("screen_get_color_space", "screen"), &RenderingDevice::screen_get_color_space, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("draw_list_begin_for_screen", "screen", "clear_color"), &RenderingDevice::draw_list_begin_for_screen, DEFVAL(DisplayServer::MAIN_WINDOW_ID), DEFVAL(Color()));
@@ -7682,6 +7706,11 @@ void RenderingDevice::_bind_methods() {
BIND_ENUM_CONSTANT(DATA_FORMAT_G16_B16_R16_3PLANE_444_UNORM);
BIND_ENUM_CONSTANT(DATA_FORMAT_MAX);
+ BIND_ENUM_CONSTANT(COLOR_SPACE_SRGB_LINEAR);
+ BIND_ENUM_CONSTANT(COLOR_SPACE_SRGB_NONLINEAR);
+ BIND_ENUM_CONSTANT(COLOR_SPACE_HDR10_ST2084);
+ BIND_ENUM_CONSTANT(COLOR_SPACE_MAX);
+
#ifndef DISABLE_DEPRECATED
BIND_BITFIELD_FLAG(BARRIER_MASK_VERTEX);
BIND_BITFIELD_FLAG(BARRIER_MASK_FRAGMENT);
@@ -7906,6 +7935,7 @@ void RenderingDevice::_bind_methods() {
BIND_ENUM_CONSTANT(PIPELINE_SPECIALIZATION_CONSTANT_TYPE_FLOAT);
BIND_ENUM_CONSTANT(SUPPORTS_BUFFER_DEVICE_ADDRESS);
+ BIND_ENUM_CONSTANT(SUPPORTS_HDR_OUTPUT);
BIND_ENUM_CONSTANT(LIMIT_MAX_BOUND_UNIFORM_SETS);
BIND_ENUM_CONSTANT(LIMIT_MAX_FRAMEBUFFER_COLOR_ATTACHMENTS);
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 6d480258e12..2665b58aa92 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -1159,6 +1159,8 @@ public:
int screen_get_height(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
int screen_get_pre_rotation_degrees(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
FramebufferFormatID screen_get_framebuffer_format(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
+ DataFormat screen_get_color_format(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
+ ColorSpace screen_get_color_space(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const;
Error screen_free(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID);
/*************************/
@@ -1678,6 +1680,7 @@ VARIANT_ENUM_CAST(RenderingDevice::ShaderStage)
VARIANT_ENUM_CAST(RenderingDevice::ShaderLanguage)
VARIANT_ENUM_CAST(RenderingDevice::CompareOperator)
VARIANT_ENUM_CAST(RenderingDevice::DataFormat)
+VARIANT_ENUM_CAST(RenderingDevice::ColorSpace)
VARIANT_ENUM_CAST(RenderingDevice::TextureType)
VARIANT_ENUM_CAST(RenderingDevice::TextureSamples)
VARIANT_BITFIELD_CAST(RenderingDevice::TextureUsageBits)
diff --git a/servers/rendering/rendering_device_commons.h b/servers/rendering/rendering_device_commons.h
index aa8beed3578..39eb4146cf4 100644
--- a/servers/rendering/rendering_device_commons.h
+++ b/servers/rendering/rendering_device_commons.h
@@ -273,6 +273,13 @@ public:
DATA_FORMAT_MAX,
};
+ enum ColorSpace {
+ COLOR_SPACE_SRGB_LINEAR,
+ COLOR_SPACE_SRGB_NONLINEAR,
+ COLOR_SPACE_HDR10_ST2084,
+ COLOR_SPACE_MAX,
+ };
+
// Breadcrumb markers are useful for debugging GPU crashes (i.e. DEVICE_LOST). Internally
// they're just an uint32_t to "tag" a GPU command. These are only used for debugging and do not
// (or at least shouldn't) alter the execution behavior in any way.
@@ -887,6 +894,7 @@ public:
// If not supported, a fragment shader with only side effects (i.e., writes to buffers, but doesn't output to attachments), may be optimized down to no-op by the GPU driver.
SUPPORTS_FRAGMENT_SHADER_WITH_ONLY_SIDE_EFFECTS,
SUPPORTS_BUFFER_DEVICE_ADDRESS,
+ SUPPORTS_HDR_OUTPUT,
};
enum SubgroupOperations {
diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h
index 1f65cdcaa9a..e64998eda4e 100644
--- a/servers/rendering/rendering_device_driver.h
+++ b/servers/rendering/rendering_device_driver.h
@@ -473,6 +473,9 @@ public:
// 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) {}
+ // Retrieve the color space used by the swap chain's framebuffers.
+ virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) = 0;
+
// Wait until all rendering associated to the swap chain is finished before deleting it.
virtual void swap_chain_free(SwapChainID p_swap_chain) = 0;
diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h
index 34f11924ceb..49e810d62bd 100644
--- a/servers/rendering/rendering_method.h
+++ b/servers/rendering/rendering_method.h
@@ -186,10 +186,13 @@ public:
// Tonemap
virtual void environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white) = 0;
+ virtual void environment_set_tonemap_range(RID p_env, float p_min_value, float p_max_value) = 0;
virtual RS::EnvironmentToneMapper environment_get_tone_mapper(RID p_env) const = 0;
virtual float environment_get_exposure(RID p_env) const = 0;
virtual float environment_get_white(RID p_env) const = 0;
+ virtual float environment_get_min_value(RID p_env) const = 0;
+ virtual float environment_get_max_value(RID p_env) const = 0;
// Fog
virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode = RS::EnvironmentFogMode::ENV_FOG_MODE_EXPONENTIAL) = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index ffbc5c70950..1b85611ebac 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -690,6 +690,7 @@ public:
FUNC3(viewport_attach_to_screen, RID, const Rect2 &, int)
FUNC2(viewport_set_render_direct_to_screen, RID, bool)
+ FUNC2(viewport_set_tonemap_to_screen, RID, bool)
FUNC2(viewport_set_scaling_3d_mode, RID, ViewportScaling3DMode)
FUNC2(viewport_set_scaling_3d_scale, RID, float)
@@ -813,6 +814,7 @@ public:
FUNC1(environment_glow_set_use_bicubic_upscale, bool)
FUNC4(environment_set_tonemap, RID, EnvironmentToneMapper, float, float)
+ FUNC3(environment_set_tonemap_range, RID, float, float)
FUNC7(environment_set_adjustment, RID, bool, float, float, float, bool, RID)
diff --git a/servers/rendering/storage/environment_storage.cpp b/servers/rendering/storage/environment_storage.cpp
index bfb2852d5f0..82632064b60 100644
--- a/servers/rendering/storage/environment_storage.cpp
+++ b/servers/rendering/storage/environment_storage.cpp
@@ -211,6 +211,14 @@ void RendererEnvironmentStorage::environment_set_tonemap(RID p_env, RS::Environm
env->white = p_white;
}
+void RendererEnvironmentStorage::environment_set_tonemap_range(RID p_env, float p_min_value, float p_max_value) {
+ Environment *env = environment_owner.get_or_null(p_env);
+ ERR_FAIL_NULL(env);
+ ERR_FAIL_COND(p_min_value >= p_max_value);
+ env->min_value = p_min_value;
+ env->max_value = p_max_value;
+}
+
RS::EnvironmentToneMapper RendererEnvironmentStorage::environment_get_tone_mapper(RID p_env) const {
Environment *env = environment_owner.get_or_null(p_env);
ERR_FAIL_NULL_V(env, RS::ENV_TONE_MAPPER_LINEAR);
@@ -229,6 +237,18 @@ float RendererEnvironmentStorage::environment_get_white(RID p_env) const {
return env->white;
}
+float RendererEnvironmentStorage::environment_get_min_value(RID p_env) const {
+ Environment *env = environment_owner.get_or_null(p_env);
+ ERR_FAIL_NULL_V(env, 0.0);
+ return env->min_value;
+}
+
+float RendererEnvironmentStorage::environment_get_max_value(RID p_env) const {
+ Environment *env = environment_owner.get_or_null(p_env);
+ ERR_FAIL_NULL_V(env, 1.0);
+ return env->max_value;
+}
+
// Fog
void RendererEnvironmentStorage::environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_fog_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode) {
diff --git a/servers/rendering/storage/environment_storage.h b/servers/rendering/storage/environment_storage.h
index 6fdc047ba28..e7fcaaf62ea 100644
--- a/servers/rendering/storage/environment_storage.h
+++ b/servers/rendering/storage/environment_storage.h
@@ -63,6 +63,8 @@ private:
RS::EnvironmentToneMapper tone_mapper;
float exposure = 1.0;
float white = 1.0;
+ float min_value = 0.0;
+ float max_value = 1.0;
// Fog
bool fog_enabled = false;
@@ -201,9 +203,12 @@ public:
// Tonemap
void environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white);
+ void environment_set_tonemap_range(RID p_env, float p_min_value, float p_max_value);
RS::EnvironmentToneMapper environment_get_tone_mapper(RID p_env) const;
float environment_get_exposure(RID p_env) const;
float environment_get_white(RID p_env) const;
+ float environment_get_min_value(RID p_env) const;
+ float environment_get_max_value(RID p_env) const;
// Fog
void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect, RS::EnvironmentFogMode p_mode);
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 0b3c9182495..845db08417a 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2810,6 +2810,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("viewport_set_parent_viewport", "viewport", "parent_viewport"), &RenderingServer::viewport_set_parent_viewport);
ClassDB::bind_method(D_METHOD("viewport_attach_to_screen", "viewport", "rect", "screen"), &RenderingServer::viewport_attach_to_screen, DEFVAL(Rect2()), DEFVAL(DisplayServer::MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("viewport_set_render_direct_to_screen", "viewport", "enabled"), &RenderingServer::viewport_set_render_direct_to_screen);
+ ClassDB::bind_method(D_METHOD("viewport_set_tonemap_to_screen", "viewport", "enabled"), &RenderingServer::viewport_set_tonemap_to_screen);
ClassDB::bind_method(D_METHOD("viewport_set_canvas_cull_mask", "viewport", "canvas_cull_mask"), &RenderingServer::viewport_set_canvas_cull_mask);
ClassDB::bind_method(D_METHOD("viewport_set_scaling_3d_mode", "viewport", "scaling_3d_mode"), &RenderingServer::viewport_set_scaling_3d_mode);
@@ -3022,6 +3023,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("environment_set_ambient_light", "env", "color", "ambient", "energy", "sky_contribution", "reflection_source"), &RenderingServer::environment_set_ambient_light, DEFVAL(RS::ENV_AMBIENT_SOURCE_BG), DEFVAL(1.0), DEFVAL(0.0), DEFVAL(RS::ENV_REFLECTION_SOURCE_BG));
ClassDB::bind_method(D_METHOD("environment_set_glow", "env", "enable", "levels", "intensity", "strength", "mix", "bloom_threshold", "blend_mode", "hdr_bleed_threshold", "hdr_bleed_scale", "hdr_luminance_cap", "glow_map_strength", "glow_map"), &RenderingServer::environment_set_glow);
ClassDB::bind_method(D_METHOD("environment_set_tonemap", "env", "tone_mapper", "exposure", "white"), &RenderingServer::environment_set_tonemap);
+ ClassDB::bind_method(D_METHOD("environment_set_tonemap_range", "env", "min_value", "max_value"), &RenderingServer::environment_set_tonemap_range);
ClassDB::bind_method(D_METHOD("environment_set_adjustment", "env", "enable", "brightness", "contrast", "saturation", "use_1d_color_correction", "color_correction"), &RenderingServer::environment_set_adjustment);
ClassDB::bind_method(D_METHOD("environment_set_ssr", "env", "enable", "max_steps", "fade_in", "fade_out", "depth_tolerance"), &RenderingServer::environment_set_ssr);
ClassDB::bind_method(D_METHOD("environment_set_ssao", "env", "enable", "radius", "intensity", "power", "detail", "horizon", "sharpness", "light_affect", "ao_channel_affect"), &RenderingServer::environment_set_ssao);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index f889cce0482..8d7de11dbf2 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -966,6 +966,7 @@ public:
virtual void viewport_attach_to_screen(RID p_viewport, const Rect2 &p_rect = Rect2(), DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) = 0;
virtual void viewport_set_render_direct_to_screen(RID p_viewport, bool p_enable) = 0;
+ virtual void viewport_set_tonemap_to_screen(RID p_viewport, bool p_enable) = 0;
virtual void viewport_set_scaling_3d_mode(RID p_viewport, ViewportScaling3DMode p_scaling_3d_mode) = 0;
virtual void viewport_set_scaling_3d_scale(RID p_viewport, float p_scaling_3d_scale) = 0;
@@ -1257,6 +1258,7 @@ public:
};
virtual void environment_set_tonemap(RID p_env, EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white) = 0;
+ virtual void environment_set_tonemap_range(RID p_env, float p_min_value, float p_max_value) = 0;
virtual void environment_set_adjustment(RID p_env, bool p_enable, float p_brightness, float p_contrast, float p_saturation, bool p_use_1d_color_correction, RID p_color_correction) = 0;
virtual void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_in, float p_fade_out, float p_depth_tolerance) = 0;