From 5abaebfe10f54977c5b2ea249a2407a298f9b6c6 Mon Sep 17 00:00:00 2001 From: Josh Jones Date: Thu, 11 Jul 2024 23:45:36 -0700 Subject: [PATCH] Support output to HDR monitors Co-authored-by: Alvin Wong --- core/config/project_settings.cpp | 2 + doc/classes/DisplayServer.xml | 35 +++ doc/classes/ProjectSettings.xml | 8 + .../d3d12/rendering_context_driver_d3d12.cpp | 21 ++ .../d3d12/rendering_context_driver_d3d12.h | 6 + .../d3d12/rendering_device_driver_d3d12.cpp | 107 +++++++- drivers/d3d12/rendering_device_driver_d3d12.h | 2 + .../metal/rendering_context_driver_metal.h | 6 + .../metal/rendering_context_driver_metal.mm | 20 ++ drivers/metal/rendering_device_driver_metal.h | 1 + .../metal/rendering_device_driver_metal.mm | 4 + .../rendering_context_driver_vulkan.cpp | 28 +++ .../vulkan/rendering_context_driver_vulkan.h | 7 + .../vulkan/rendering_device_driver_vulkan.cpp | 236 ++++++++++++------ .../vulkan/rendering_device_driver_vulkan.h | 2 + editor/editor_node.cpp | 6 + main/main.cpp | 7 + platform/windows/display_server_windows.cpp | 41 +++ platform/windows/display_server_windows.h | 5 + servers/display_server.cpp | 24 ++ servers/display_server.h | 6 + .../renderer_rd/renderer_compositor_rd.cpp | 43 +++- .../renderer_rd/renderer_compositor_rd.h | 9 +- .../rendering/renderer_rd/shaders/blit.glsl | 30 ++- .../rendering/renderer_rd/shaders/canvas.glsl | 4 +- .../renderer_rd/shaders/color_space_inc.glsl | 43 ++++ .../shaders/effects/copy_to_fb.glsl | 11 +- .../renderer_rd/shaders/effects/tonemap.glsl | 7 +- .../shaders/environment/sdfgi_debug.glsl | 7 +- .../rendering/rendering_context_driver.cpp | 32 +++ servers/rendering/rendering_context_driver.h | 8 + servers/rendering/rendering_device.cpp | 11 + servers/rendering/rendering_device.h | 1 + servers/rendering/rendering_device_commons.h | 7 + servers/rendering/rendering_device_driver.h | 3 + 35 files changed, 664 insertions(+), 126 deletions(-) create mode 100644 servers/rendering/renderer_rd/shaders/color_space_inc.glsl diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 01f15f9c8e4d..9785f881b786 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1460,6 +1460,8 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("display/window/size/transparent", false); GLOBAL_DEF("display/window/size/extend_to_title", false); GLOBAL_DEF("display/window/size/no_focus", false); + GLOBAL_DEF("display/window/hdr/enabled", false); + GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "display/window/hdr/max_luminance", PROPERTY_HINT_RANGE, "0,1500,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 37bf265d20c4..a1573258c30d 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1455,6 +1455,20 @@ Returns the current value of the given window's [param flag]. + + + + + Returns whether HDR output is requested for the given window. + + + + + + + Returns the maximum luminance in nits (cd/m²) set for HDR output for the given window. + + @@ -1622,6 +1636,24 @@ 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]. + 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. + This is used to scale the HDR effect to avoid clipping. + + @@ -1890,6 +1922,9 @@ Display server supports spawning dialogs for selecting files or directories using the operating system's native look-and-feel. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/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 1684edb9b88e..96f7839209e0 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -820,6 +820,14 @@ 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. + This is used to scale the HDR effect to avoid clipping. + If [code]true[/code], iOS devices that support high refresh rate/"ProMotion" will be allowed to render at up to 120 frames per second. diff --git a/drivers/d3d12/rendering_context_driver_d3d12.cpp b/drivers/d3d12/rendering_context_driver_d3d12.cpp index 8fa495f5c4f3..ecf2205bad52 100644 --- a/drivers/d3d12/rendering_context_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_context_driver_d3d12.cpp @@ -276,6 +276,27 @@ 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_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 a2d828ded1ac..342b4d0c9d4c 100644 --- a/drivers/d3d12/rendering_context_driver_d3d12.h +++ b/drivers/d3d12/rendering_context_driver_d3d12.h @@ -95,6 +95,10 @@ class RenderingContextDriverD3D12 : public RenderingContextDriver { 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_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; @@ -113,6 +117,8 @@ class RenderingContextDriverD3D12 : public RenderingContextDriver { uint32_t width = 0; uint32_t height = 0; DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED; + bool hdr_output = false; + float hdr_max_luminance = 0.0f; bool needs_resize = false; }; diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index 479afbba93f7..744326c26d78 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -2398,10 +2398,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; @@ -2412,13 +2412,22 @@ 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); + return render_pass_create(attachment, subpass, {}, 1); +} + +RDD::SwapChainID RenderingDeviceDriverD3D12::swap_chain_create(RenderingContextDriver::SurfaceID p_surface) { + RDD::DataFormat format = DATA_FORMAT_R8G8B8A8_UNORM; + if (context_driver->surface_get_hdr_output_enabled(p_surface)) { + format = DATA_FORMAT_A2B10G10R10_UNORM_PACK32; + } + + RenderPassID render_pass = _swap_chain_create_render_pass(format); ERR_FAIL_COND_V(!render_pass, SwapChainID()); // Create the 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->data_format = format; swap_chain->render_pass = render_pass; return SwapChainID(swap_chain); } @@ -2463,11 +2472,27 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue, print_verbose("Using swap chain flags: " + itos(creation_flags) + ", sync interval: " + itos(sync_interval) + ", present flags: " + itos(present_flags)); - 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; + if (context_driver->surface_get_hdr_output_enabled(swap_chain->surface)) { + // DXGI_FORMAT_R10G10B10A2_UNORM for DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 + new_data_format = DATA_FORMAT_A2B10G10R10_UNORM_PACK32; + } else { + new_data_format = DATA_FORMAT_R8G8B8A8_UNORM; + } + + 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); } + if (new_data_format != swap_chain->data_format) { + render_pass_free(swap_chain->render_pass); + 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->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); @@ -2501,6 +2526,12 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue, swap_chain_1.As(&swap_chain->d3d_swap_chain); ERR_FAIL_NULL_V(swap_chain->d3d_swap_chain, ERR_CANT_CREATE); + if (swap_chain->data_format == DATA_FORMAT_A2B10G10R10_UNORM_PACK32) { + print_verbose("D3D12: Set HDR swap chain color space to BT.2020 (ST2084 PQ)"); + res = swap_chain->d3d_swap_chain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); + ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE); + } + res = context_driver->dxgi_factory_get()->MakeWindowAssociation(surface->hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES); ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE); } @@ -2584,6 +2615,19 @@ 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); + switch (swap_chain->data_format) { + case DATA_FORMAT_A2B10G10R10_UNORM_PACK32: + return COLOR_SPACE_HDR10_ST2084; + case DATA_FORMAT_R8G8B8A8_UNORM: + return RDD::COLOR_SPACE_SRGB_NONLINEAR; + default: + DEV_ASSERT(false && "Unknown swap chain color space."); + return COLOR_SPACE_MAX; + } +} + void RenderingDeviceDriverD3D12::swap_chain_free(SwapChainID p_swap_chain) { SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id); _swap_chain_release(swap_chain); @@ -6527,6 +6571,57 @@ Error RenderingDeviceDriverD3D12::_check_capabilities() { print_verbose("- Depth bounds test not supported"); } + // DEBUG + if (is_print_verbose_enabled()) { + print_line("- DXGI Outputs:"); + + ComPtr dxgi_output; + // FIXME: Using just one adapter doesn't work on NVIDIA Optimus! + for (int i = 0; (res = adapter->EnumOutputs(i, &dxgi_output)) != DXGI_ERROR_NOT_FOUND; i++) { + if (!SUCCEEDED(res)) { + print_error(vformat("EnumOutputs failed: 0x%08X", (int)res)); + break; + } + + ComPtr dxgi_output_6; + res = dxgi_output.As(&dxgi_output_6); + if (!SUCCEEDED(res)) { + print_error(vformat("Failed to get IDXGIOutput6: 0x%08X", (int)res)); + continue; + } + + DXGI_OUTPUT_DESC1 desc1; + res = dxgi_output_6->GetDesc1(&desc1); + if (!SUCCEEDED(res)) { + print_error(vformat("Failed to get DXGI_OUTPUT_DESC1: 0x%08X", (int)res)); + continue; + } + + print_line("Device Name:", String::utf16((const char16_t *)desc1.DeviceName, 32)); + print_line(vformat("hMonitor: 0x%08X", (uintptr_t)desc1.Monitor)); + print_line("Bits per color:", desc1.BitsPerColor); + switch (desc1.ColorSpace) { + case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709: + print_line("Color space: sRGB (SDR)"); + break; + case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020: + print_line("Color space: BT.2020 ST2084 PQ (HDR)"); + break; + default: + print_line("Color space: Other -", desc1.ColorSpace); + break; + } + print_line("RedPrimary:", desc1.RedPrimary[0], desc1.RedPrimary[1]); + print_line("GreenPrimary:", desc1.GreenPrimary[0], desc1.GreenPrimary[1]); + print_line("BluePrimary:", desc1.BluePrimary[0], desc1.BluePrimary[1]); + print_line("WhitePoint:", desc1.WhitePoint[0], desc1.WhitePoint[1]); + print_line("MinLuminance:", desc1.MinLuminance); + print_line("MaxLuminance:", desc1.MaxLuminance); + print_line("MaxFullFrameLuminance:", desc1.MaxFullFrameLuminance); + print_line(""); + } + } + return OK; } diff --git a/drivers/d3d12/rendering_device_driver_d3d12.h b/drivers/d3d12/rendering_device_driver_d3d12.h index b449a9087665..51a72eea6301 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.h +++ b/drivers/d3d12/rendering_device_driver_d3d12.h @@ -502,6 +502,7 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver { 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); public: virtual SwapChainID swap_chain_create(RenderingContextDriver::SurfaceID p_surface) override; @@ -509,6 +510,7 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver { 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 7e0b09186de7..58dcb4a66cf4 100644 --- a/drivers/metal/rendering_context_driver_metal.h +++ b/drivers/metal/rendering_context_driver_metal.h @@ -76,6 +76,10 @@ class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingContextDriverMetal : 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_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; @@ -106,6 +110,8 @@ class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingContextDriverMetal : public uint32_t width = 0; uint32_t height = 0; DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED; + bool hdr_output = false; + float hdr_max_luminance = 0.0f; bool needs_resize = false; Surface( diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm index b97b58635285..64165f41d906 100644 --- a/drivers/metal/rendering_context_driver_metal.mm +++ b/drivers/metal/rendering_context_driver_metal.mm @@ -207,6 +207,26 @@ void present(MDCommandBuffer *p_cmd_buffer) override final { 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; +} + +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_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 7c23624e43a5..672762505651 100644 --- a/drivers/metal/rendering_device_driver_metal.h +++ b/drivers/metal/rendering_device_driver_metal.h @@ -220,6 +220,7 @@ class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingDeviceDriverMetal : public virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override final; virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override final; virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final; + virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) override final; virtual 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 a4a408356aeb..b3ba8fb00efc 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -982,6 +982,10 @@ static const API_AVAILABLE(macos(11.0), ios(14.0)) MTLSamplerBorderColor SAMPLER return swap_chain->data_format; } +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 df9bd986240d..6bbb3a62f9d5 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,27 @@ 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_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 +1045,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 26de3862061b..aaaf1a7b42da 100644 --- a/drivers/vulkan/rendering_context_driver_vulkan.h +++ b/drivers/vulkan/rendering_context_driver_vulkan.h @@ -139,12 +139,17 @@ class RenderingContextDriverVulkan : public RenderingContextDriver { 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_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 { @@ -152,6 +157,8 @@ class RenderingContextDriverVulkan : public RenderingContextDriver { uint32_t width = 0; uint32_t height = 0; DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED; + bool hdr_output = false; + float hdr_max_luminance = 0.0f; bool needs_resize = false; }; diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 154095552bfe..eee45bc3ec0a 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -2666,35 +2666,7 @@ void RenderingDeviceDriverVulkan::command_buffer_execute_secondary(CommandBuffer /**** SWAP CHAIN ****/ /********************/ -void RenderingDeviceDriverVulkan::_swap_chain_release(SwapChain *swap_chain) { - // Destroy views and framebuffers associated to the swapchain's images. - for (FramebufferID framebuffer : swap_chain->framebuffers) { - framebuffer_free(framebuffer); - } - - for (VkImageView view : swap_chain->image_views) { - vkDestroyImageView(vk_device, view, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW)); - } - - swap_chain->image_index = UINT_MAX; - swap_chain->images.clear(); - swap_chain->image_views.clear(); - swap_chain->framebuffers.clear(); - - if (swap_chain->vk_swapchain != VK_NULL_HANDLE) { - device_functions.DestroySwapchainKHR(vk_device, swap_chain->vk_swapchain, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_SWAPCHAIN_KHR)); - swap_chain->vk_swapchain = VK_NULL_HANDLE; - } - - 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); - } - - swap_chain->command_queues_acquired.clear(); - swap_chain->command_queues_acquired_semaphores.clear(); -} - -RenderingDeviceDriver::SwapChainID RenderingDeviceDriverVulkan::swap_chain_create(RenderingContextDriver::SurfaceID p_surface) { +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); @@ -2703,75 +2675,125 @@ RenderingDeviceDriver::SwapChainID RenderingDeviceDriverVulkan::swap_chain_creat // 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()); + 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, SwapChainID()); + ERR_FAIL_COND_V(err != VK_SUCCESS, false); - VkFormat format = VK_FORMAT_UNDEFINED; - VkColorSpaceKHR color_space = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + // 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) { - // 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) { + r_format = VK_FORMAT_B8G8R8A8_UNORM; + r_color_space = formats[0].colorSpace; + return true; + } + + // If the surface requests HDR output, try to get an HDR format. + if (context_driver->surface_get_hdr_output_enabled(p_surface)) { + if (context_driver->is_colorspace_supported()) { + // Use one of the supported formats, prefer A2B10G10R10_UNORM_PACK32. + const VkFormat preferred_format = VK_FORMAT_A2B10G10R10_UNORM_PACK32; + const VkFormat second_format = VK_FORMAT_A2R10G10B10_UNORM_PACK32; + + // Only HDR10 is supported for now. + const VkColorSpaceKHR preferred_color_space = VK_COLOR_SPACE_HDR10_ST2084_EXT; + + // Search for a valid format. + bool found = false; + for (uint32_t i = 0; i < format_count; i++) { + if (formats[i].colorSpace == preferred_color_space && (formats[i].format == preferred_format || formats[i].format == second_format)) { + found = true; + r_format = formats[i].format; + r_color_space = formats[i].colorSpace; + + // This is the preferred format, stop searching. + if (formats[i].format == preferred_format) { + break; + } + } + } + + if (found) { + return true; + } + + WARN_PRINT("HDR output requested but no HDR compatible format was found, falling back to SDR."); + } else { + WARN_PRINT("HDR output requested but the vulkan driver does not support VK_EXT_swapchain_colorspace, falling back to SDR."); + } + } + + // If HDR output was not requested or we failed to find an HDR compatible format above, try to get an SDR format. + { // 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; + + // Only SRGB is supported and should be available everywhere. + const VkColorSpaceKHR preferred_color_space = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + + bool found = false; 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].colorSpace == preferred_color_space && (formats[i].format == preferred_format || formats[i].format == second_format)) { + found = true; + r_format = formats[i].format; + r_color_space = formats[i].colorSpace; + + // This is the preferred format, stop searching. if (formats[i].format == preferred_format) { - // This is the preferred format, stop searching. break; } } } + + if (found) { + return true; + } } // No formats are supported. - ERR_FAIL_COND_V_MSG(format == VK_FORMAT_UNDEFINED, SwapChainID(), "Surface did not return any valid formats."); + return false; +} - // 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; +void RenderingDeviceDriverVulkan::_swap_chain_release(SwapChain *swap_chain) { + // Destroy views and framebuffers associated to the swapchain's images. + for (FramebufferID framebuffer : swap_chain->framebuffers) { + framebuffer_free(framebuffer); + } - VkAttachmentReference2KHR color_reference = {}; - color_reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; - color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + for (VkImageView view : swap_chain->image_views) { + vkDestroyImageView(vk_device, view, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW)); + } - 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; + swap_chain->image_index = UINT_MAX; + swap_chain->images.clear(); + swap_chain->image_views.clear(); + swap_chain->framebuffers.clear(); - 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; + if (swap_chain->vk_swapchain != VK_NULL_HANDLE) { + device_functions.DestroySwapchainKHR(vk_device, swap_chain->vk_swapchain, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_SWAPCHAIN_KHR)); + swap_chain->vk_swapchain = VK_NULL_HANDLE; + } - 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()); + 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); + } + + swap_chain->command_queues_acquired.clear(); + swap_chain->command_queues_acquired_semaphores.clear(); +} + +RenderingDeviceDriver::SwapChainID RenderingDeviceDriverVulkan::swap_chain_create(RenderingContextDriver::SurfaceID p_surface) { + DEV_ASSERT(p_surface != 0); 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); } @@ -2893,6 +2915,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; @@ -2943,9 +2975,44 @@ 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); + + 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; @@ -3044,22 +3111,37 @@ 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; default: DEV_ASSERT(false && "Unknown swap chain format."); return DATA_FORMAT_MAX; } } +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; + 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); } diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h index 58f7a97ec0d9..10165d5e7585 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.h +++ b/drivers/vulkan/rendering_device_driver_vulkan.h @@ -352,6 +352,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver { uint32_t image_index = 0; }; + 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: @@ -360,6 +361,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver { virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override final; virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override final; virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final; + virtual ColorSpace swap_chain_get_color_space(SwapChainID p_swap_chain) override final; virtual void swap_chain_free(SwapChainID p_swap_chain) override final; /*********************/ diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index dd6c88ef25f0..29e9f0d6a4d6 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -397,6 +397,12 @@ 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)) { + DisplayServer::get_singleton()->window_set_hdr_output_enabled(GLOBAL_GET("display/window/hdr/enabled")); + DisplayServer::get_singleton()->window_set_hdr_output_max_luminance(GLOBAL_GET("display/window/hdr/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"))); diff --git a/main/main.cpp b/main/main.cpp index 5206e9b84c57..2440adb7056f 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2412,6 +2412,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (bool(GLOBAL_GET("display/window/size/no_focus"))) { window_flags |= DisplayServer::WINDOW_FLAG_NO_FOCUS_BIT; } + window_mode = (DisplayServer::WindowMode)(GLOBAL_GET("display/window/size/mode").operator int()); int initial_position_type = GLOBAL_GET("display/window/size/initial_position_type").operator int(); if (initial_position_type == 0) { // Absolute. @@ -3166,6 +3167,12 @@ Error Main::setup2(bool p_show_boot_logo) { DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP, true); } + // Enable HDR if requested and available. + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HDR)) { + DisplayServer::get_singleton()->window_set_hdr_output_enabled(GLOBAL_GET("display/window/hdr/enabled")); + DisplayServer::get_singleton()->window_set_hdr_output_max_luminance(GLOBAL_GET("display/window/hdr/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 ffa3840181b4..7fbf00f2c75f 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -122,6 +122,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_TEXT_TO_SPEECH: case FEATURE_SCREEN_CAPTURE: case FEATURE_STATUS_INDICATOR: + case FEATURE_HDR: return true; default: return false; @@ -3640,6 +3641,46 @@ 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) + if (rendering_context) { + rendering_context->window_set_hdr_output_enabled(p_window, p_enabled); + } +#endif +} + +bool DisplayServerWindows::window_get_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_max_luminance(const float p_max_luminance, WindowID p_window) { + _THREAD_SAFE_METHOD_ +#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::set_context(Context p_context) { } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 7d6a3e96a6bc..50ebdd214dbd 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -780,6 +780,11 @@ class DisplayServerWindows : public DisplayServer { 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_get_hdr_output_enabled(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 cursor_set_shape(CursorShape p_shape) override; virtual CursorShape cursor_get_shape() const override; virtual void cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index ce0d6cb99602..1c243bded534 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -767,6 +767,24 @@ 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("Changing the HDR mode is not supported by this display server."); +} + +bool DisplayServer::window_get_hdr_output_enabled(WindowID p_window) const { + WARN_PRINT("Changing the HDR mode is not supported by this display server."); + return false; +} + +void DisplayServer::window_set_hdr_output_max_luminance(const float p_max_luminance, WindowID p_window) { + WARN_PRINT("Changing the HDR mode is not supported by this display server."); +} + +float DisplayServer::window_get_hdr_output_max_luminance(WindowID p_window) const { + WARN_PRINT("Changing the HDR mode is not supported by this display server."); + return 0.0f; +} + DisplayServer::WindowID DisplayServer::get_focused_window() const { return MAIN_WINDOW_ID; // Proper value for single windows. } @@ -968,6 +986,11 @@ 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_get_hdr_output_enabled", "window_id"), &DisplayServer::window_get_hdr_output_enabled, 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); @@ -1056,6 +1079,7 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(FEATURE_NATIVE_HELP); BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_INPUT); BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE); + 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 36798bd011cd..cde3c6dda890 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -150,6 +150,7 @@ class DisplayServer : public Object { FEATURE_NATIVE_HELP, FEATURE_NATIVE_DIALOG_INPUT, FEATURE_NATIVE_DIALOG_FILE, + FEATURE_HDR, }; virtual bool has_feature(Feature p_feature) const = 0; @@ -460,6 +461,11 @@ class DisplayServer : public Object { 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_get_hdr_output_enabled(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/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp index 84ea6a5da203..a82251f9afd4 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp @@ -40,9 +40,14 @@ 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 max_luminance = RD::get_singleton()->get_context_driver()->window_get_hdr_output_max_luminance(p_screen); + 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()); @@ -62,6 +67,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); @@ -81,7 +87,11 @@ 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.max_display_luminance = max_luminance; + blit.push_constant.pad[0] = 0.0; + blit.push_constant.pad[1] = 0.0; RD::get_singleton()->draw_list_set_push_constant(draw_list, &blit.push_constant, sizeof(BlitPushConstant)); RD::get_singleton()->draw_list_draw(draw_list, true); @@ -118,13 +128,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); - } - //create index array for copy shader Vector pv; pv.resize(6 * 2); @@ -164,6 +169,21 @@ 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; + } +} + 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; @@ -175,6 +195,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); @@ -222,6 +244,9 @@ 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 max_luminance = RD::get_singleton()->get_context_driver()->window_get_hdr_output_max_luminance(DisplayServer::MAIN_WINDOW_ID); + 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]); @@ -243,7 +268,11 @@ 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.max_display_luminance = max_luminance; + blit.push_constant.pad[0] = 0.0; + blit.push_constant.pad[1] = 0.0; 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 dcd3e90e1b7c..fd5951fdba46 100644 --- a/servers/rendering/renderer_rd/renderer_compositor_rd.h +++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h @@ -80,7 +80,11 @@ class RendererCompositorRD : public RendererCompositor { float upscale; float aspect_ratio; uint32_t layer; - uint32_t convert_to_srgb; + uint32_t source_is_srgb; + + uint32_t target_color_space; + float max_display_luminance; + float pad[2]; }; struct Blit { @@ -101,6 +105,9 @@ class RendererCompositorRD : public RendererCompositor { static uint64_t frame; static RendererCompositorRD *singleton; + RenderingDevice::FramebufferFormatID prev_main_window_format; + void _ensure_blit_pipelines(); + public: RendererUtilities *get_utilities() { return utilities; }; RendererLightStorage *get_light_storage() { return light_storage; } diff --git a/servers/rendering/renderer_rd/shaders/blit.glsl b/servers/rendering/renderer_rd/shaders/blit.glsl index d451647bec66..cc6fb8bc3876 100644 --- a/servers/rendering/renderer_rd/shaders/blit.glsl +++ b/servers/rendering/renderer_rd/shaders/blit.glsl @@ -15,7 +15,11 @@ layout(push_constant, std140) uniform Pos { float upscale; float aspect_ratio; uint layer; - uint pad1; + bool source_is_srgb; + + uint target_color_space; + float max_display_luminance; + vec2 pad; } data; @@ -45,7 +49,11 @@ 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 max_display_luminance; + vec2 pad; } data; @@ -59,12 +67,7 @@ layout(binding = 0) uniform sampler2DArray src_rt; layout(binding = 0) uniform sampler2D src_rt; #endif -vec3 linear_to_srgb(vec3 color) { - // If going to srgb, clamp from 0 to 1. - color = clamp(color, vec3(0.0), vec3(1.0)); - const vec3 a = vec3(0.055f); - 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))); -} +#include "color_space_inc.glsl" void main() { #ifdef APPLY_LENS_DISTORTION @@ -102,7 +105,14 @@ void main() { color = texture(src_rt, uv); #endif - if (data.convert_to_srgb) { - color.rgb = linear_to_srgb(color.rgb); // Regular linear -> SRGB conversion. + if (data.target_color_space == COLOR_SPACE_SRGB_NONLINEAR && data.source_is_srgb == false) { + color.rgb = linear_to_srgb(color.rgb); // linear -> sRGB conversion. + } else if (data.target_color_space == COLOR_SPACE_HDR10_ST2084) { + if (data.source_is_srgb == true) { + // HACK: Converting back to linear since I'm not sure who else converts to sRGB. + color.rgb = srgb_to_linear(color.rgb); // sRGB -> linear conversion. + } + + color.rgb = linear_to_st2084(color.rgb, data.max_display_luminance); // linear -> ST2084 conversion. } } diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl index dafcce37adf5..c812f2d3cc54 100644 --- a/servers/rendering/renderer_rd/shaders/canvas.glsl +++ b/servers/rendering/renderer_rd/shaders/canvas.glsl @@ -51,9 +51,7 @@ layout(set = 1, binding = 0, std140) uniform MaterialUniforms { #GLOBALS #ifdef USE_ATTRIBUTES -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))); -} +#include "color_space_inc.glsl" #endif void main() { diff --git a/servers/rendering/renderer_rd/shaders/color_space_inc.glsl b/servers/rendering/renderer_rd/shaders/color_space_inc.glsl new file mode 100644 index 000000000000..85bd91d57ea0 --- /dev/null +++ b/servers/rendering/renderer_rd/shaders/color_space_inc.glsl @@ -0,0 +1,43 @@ +// Keep in sync with RenderingDeviceCommons::ColorSpace +const uint COLOR_SPACE_LINEAR = 0; +const uint COLOR_SPACE_SRGB_NONLINEAR = 1; +const uint 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)); + const vec3 a = vec3(0.055f); + 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 linear_to_st2084(vec3 color, float max_luminance) { + color = clamp(color, vec3(0.0), vec3(1.0)); + + // Rotate from Rec.709 to Rec.2020 primaries + // Color transformation matrix values taken from DirectXTK, may need verification. + const mat3 from709to2020 = mat3( + 0.6274040f, 0.0690970f, 0.0163916f, + 0.3292820f, 0.9195400f, 0.0880132f, + 0.0433136f, 0.0113612f, 0.8955950f); + color = from709to2020 * color; + + // Keep the output from blowing out the display + // max_luminance is the display's peak luminance in nits + // we map it here to the native 10000 nits range of ST2084 + float adjustment = max_luminance * 0.0001f; + color = color * adjustment; + + // 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)); +} diff --git a/servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl b/servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl index 26ee06aa03e7..26a1eb3443f5 100644 --- a/servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl @@ -118,16 +118,7 @@ layout(set = 1, binding = 0) uniform sampler2D source_color2; layout(location = 0) out vec4 frag_color; -vec3 linear_to_srgb(vec3 color) { - //if going to srgb, clamp from 0 to 1. - color = clamp(color, vec3(0.0), vec3(1.0)); - const vec3 a = vec3(0.055f); - 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 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))); -} +#include "../color_space_inc.glsl" void main() { #ifdef MODE_SET_COLOR diff --git a/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl b/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl index fa3b45a96274..7dcdaa3f146d 100644 --- a/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/tonemap.glsl @@ -264,12 +264,7 @@ vec3 tonemap_reinhard(vec3 color, float white) { return (white_squared_color + color * color) / (white_squared_color + white_squared); } -vec3 linear_to_srgb(vec3 color) { - //if going to srgb, clamp from 0 to 1. - color = clamp(color, vec3(0.0), vec3(1.0)); - const vec3 a = vec3(0.055f); - 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))); -} +#include "../color_space_inc.glsl" #define TONEMAPPER_LINEAR 0 #define TONEMAPPER_REINHARD 1 diff --git a/servers/rendering/renderer_rd/shaders/environment/sdfgi_debug.glsl b/servers/rendering/renderer_rd/shaders/environment/sdfgi_debug.glsl index 177dab16c763..f9cc74f163fb 100644 --- a/servers/rendering/renderer_rd/shaders/environment/sdfgi_debug.glsl +++ b/servers/rendering/renderer_rd/shaders/environment/sdfgi_debug.glsl @@ -49,12 +49,7 @@ layout(push_constant, std430) uniform Params { } params; -vec3 linear_to_srgb(vec3 color) { - //if going to srgb, clamp from 0 to 1. - color = clamp(color, vec3(0.0), vec3(1.0)); - const vec3 a = vec3(0.055f); - 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))); -} +#include "../color_space_inc.glsl" vec2 octahedron_wrap(vec2 v) { vec2 signVal; diff --git a/servers/rendering/rendering_context_driver.cpp b/servers/rendering/rendering_context_driver.cpp index b623be40980f..3d4a38fea49d 100644 --- a/servers/rendering/rendering_context_driver.cpp +++ b/servers/rendering/rendering_context_driver.cpp @@ -75,6 +75,38 @@ 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_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 2e5951ae4f83..0da448db05f7 100644 --- a/servers/rendering/rendering_context_driver.h +++ b/servers/rendering/rendering_context_driver.h @@ -49,6 +49,10 @@ class RenderingContextDriver { 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_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: @@ -95,6 +99,10 @@ class RenderingContextDriver { 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_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 942d5631e4d0..3ed4c9a5800e 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -3776,6 +3776,17 @@ RenderingDevice::FramebufferFormatID RenderingDevice::screen_get_framebuffer_for return const_cast(this)->framebuffer_format_create(screen_attachment); } +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_SRGB_NONLINEAR, "Screen was never prepared, defaulting to sRGB."); + + ColorSpace color_space = driver->swap_chain_get_color_space(it->value); + ERR_FAIL_COND_V_MSG(color_space == COLOR_SPACE_MAX, COLOR_SPACE_SRGB_NONLINEAR, "Unknown color space, defaulting to sRGB."); + return color_space; +} + Error RenderingDevice::screen_free(DisplayServer::WindowID p_screen) { _THREAD_SAFE_METHOD_ diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index 71ffbfbd885b..baf70dce2f97 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -1084,6 +1084,7 @@ class RenderingDevice : public RenderingDeviceCommons { int screen_get_width(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const; int screen_get_height(DisplayServer::WindowID p_screen = DisplayServer::MAIN_WINDOW_ID) const; FramebufferFormatID screen_get_framebuffer_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); /*************************/ diff --git a/servers/rendering/rendering_device_commons.h b/servers/rendering/rendering_device_commons.h index d516d968af62..4d9d243fb417 100644 --- a/servers/rendering/rendering_device_commons.h +++ b/servers/rendering/rendering_device_commons.h @@ -271,6 +271,13 @@ class RenderingDeviceCommons : public Object { DATA_FORMAT_MAX, }; + enum ColorSpace { + COLOR_SPACE_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. diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h index 637d52c0605b..63c716609758 100644 --- a/servers/rendering/rendering_device_driver.h +++ b/servers/rendering/rendering_device_driver.h @@ -457,6 +457,9 @@ class RenderingDeviceDriver : public RenderingDeviceCommons { // Retrieve the format used by the swap chain's framebuffers. virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) = 0; + // 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;