Skip to content

Commit

Permalink
Support output to HDR monitors
Browse files Browse the repository at this point in the history
  • Loading branch information
DarkKilauea committed Jul 23, 2024
1 parent 4e5ed0b commit e9742ba
Show file tree
Hide file tree
Showing 30 changed files with 438 additions and 72 deletions.
2 changes: 2 additions & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,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
Expand Down
35 changes: 35 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,20 @@
Returns the current value of the given window's [param flag].
</description>
</method>
<method name="window_get_hdr_output_enabled" qualifiers="const">
<return type="bool" />
<param index="0" name="window_id" type="int" default="0" />
<description>
Returns whether HDR output is requested for the given window.
</description>
</method>
<method name="window_get_hdr_output_max_luminance" qualifiers="const">
<return type="float" />
<param index="0" name="window_id" type="int" default="0" />
<description>
Returns the maximum luminance in nits (cd/m²) set for HDR output for the given window.
</description>
</method>
<method name="window_get_max_size" qualifiers="const">
<return type="Vector2i" />
<param index="0" name="window_id" type="int" default="0" />
Expand Down Expand Up @@ -1615,6 +1629,24 @@
Enables or disables the given window's given [param flag]. See [enum WindowFlags] for possible values and their behavior.
</description>
</method>
<method name="window_set_hdr_output_enabled">
<return type="void" />
<param index="0" name="enabled" type="bool" />
<param index="1" name="window_id" type="int" default="0" />
<description>
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.
</description>
</method>
<method name="window_set_hdr_output_max_luminance">
<return type="void" />
<param index="0" name="max_luminance" type="float" />
<param index="1" name="window_id" type="int" default="0" />
<description>
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.
</description>
</method>
<method name="window_set_ime_active">
<return type="void" />
<param index="0" name="active" type="bool" />
Expand Down Expand Up @@ -1883,6 +1915,9 @@
<constant name="FEATURE_NATIVE_DIALOG_FILE" value="25" enum="Feature">
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]
</constant>
<constant name="FEATURE_HDR" value="26" enum="Feature">
Display server supports HDR output. [b]Windows[/b]
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
</constant>
Expand Down
8 changes: 8 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,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.
</member>
<member name="display/window/hdr/enabled" type="bool" setter="" getter="" default="false">
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.
</member>
<member name="display/window/hdr/max_luminance" type="float" setter="" getter="" default="1000.0">
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.
</member>
<member name="display/window/ios/allow_high_refresh_rate" type="bool" setter="" getter="" default="true">
If [code]true[/code], iOS devices that support high refresh rate/"ProMotion" will be allowed to render at up to 120 frames per second.
</member>
Expand Down
20 changes: 20 additions & 0 deletions drivers/d3d12/rendering_context_driver_d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,26 @@ 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;
}

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;
Expand Down
6 changes: 6 additions & 0 deletions drivers/d3d12/rendering_context_driver_d3d12.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
};

Expand Down
4 changes: 4 additions & 0 deletions drivers/d3d12/rendering_device_driver_d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2616,6 +2616,10 @@ 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) {
return RDD::COLOR_SPACE_SRGB_NONLINEAR;
}

void RenderingDeviceDriverD3D12::swap_chain_free(SwapChainID p_swap_chain) {
SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
_swap_chain_release(swap_chain);
Expand Down
1 change: 1 addition & 0 deletions drivers/d3d12/rendering_device_driver_d3d12.h
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,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;

/*********************/
Expand Down
21 changes: 21 additions & 0 deletions drivers/vulkan/rendering_context_driver_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,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;
Expand Down
6 changes: 6 additions & 0 deletions drivers/vulkan/rendering_context_driver_vulkan.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ 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;
Expand All @@ -143,6 +147,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;
};

Expand Down
141 changes: 107 additions & 34 deletions drivers/vulkan/rendering_device_driver_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2473,6 +2473,80 @@ void RenderingDeviceDriverVulkan::command_buffer_execute_secondary(CommandBuffer
/**** SWAP CHAIN ****/
/********************/

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<VkSurfaceFormatKHR> 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 VK_SUCCESS;
}

// If the surface requests HDR output, try to get an HDR format.
if (context_driver->surface_get_hdr_output_enabled(p_surface)) {
// 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.
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)) {
r_format = formats[i].format;
r_color_space = formats[i].colorSpace;

// This is the preferred format, stop searching.
if (formats[i].format == preferred_format) {
return true;
}
}
}

WARN_PRINT("HDR output requested but no HDR compatible format was found, 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;

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)) {
r_format = formats[i].format;
r_color_space = formats[i].colorSpace;

// This is the preferred format, stop searching.
if (formats[i].format == preferred_format) {
return true;
}
}
}
}

// No formats are supported.
return false;
}

void RenderingDeviceDriverVulkan::_swap_chain_release(SwapChain *swap_chain) {
// Destroy views and framebuffers associated to the swapchain's images.
for (FramebufferID framebuffer : swap_chain->framebuffers) {
Expand Down Expand Up @@ -2504,43 +2578,13 @@ 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<VkSurfaceFormatKHR> 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());

// 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 (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;
}
}
}
if (!_determine_swap_chain_format(p_surface, format, color_space)) {
ERR_FAIL_V_MSG(SwapChainID(), "Surface did not return any valid formats.");
}

// 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;
Expand Down Expand Up @@ -2571,7 +2615,7 @@ RenderingDeviceDriver::SwapChainID RenderingDeviceDriverVulkan::swap_chain_creat
pass_info.pSubpasses = &subpass;

VkRenderPass render_pass = VK_NULL_HANDLE;
err = _create_render_pass(vk_device, &pass_info, nullptr, &render_pass);
VkResult err = _create_render_pass(vk_device, &pass_info, nullptr, &render_pass);
ERR_FAIL_COND_V(err != VK_SUCCESS, SwapChainID());

SwapChain *swap_chain = memnew(SwapChain);
Expand Down Expand Up @@ -2700,6 +2744,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;
Expand Down Expand Up @@ -2851,12 +2905,31 @@ 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);

Expand Down
2 changes: 2 additions & 0 deletions drivers/vulkan/rendering_device_driver_vulkan.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,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:
Expand All @@ -343,6 +344,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;

/*********************/
Expand Down
Loading

0 comments on commit e9742ba

Please sign in to comment.