Skip to content

Commit

Permalink
Support output to HDR monitors
Browse files Browse the repository at this point in the history
Co-authored-by: Alvin Wong <[email protected]>
  • Loading branch information
DarkKilauea and alvinhochun committed Jan 5, 2025
1 parent bdf625b commit 880618a
Show file tree
Hide file tree
Showing 35 changed files with 1,005 additions and 133 deletions.
3 changes: 3 additions & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,9 @@ 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(PropertyInfo(Variant::FLOAT, "display/window/hdr/reference_luminance", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), 200.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
104 changes: 104 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,18 @@
[b]Note:[/b] On macOS, this method requires "Screen Recording" permission, if permission is not granted it will return desktop wallpaper color.
</description>
</method>
<method name="screen_get_max_average_luminance" qualifiers="const">
<return type="float" />
<param index="0" name="screen" type="int" default="-1" />
<description>
</description>
</method>
<method name="screen_get_max_luminance" qualifiers="const">
<return type="float" />
<param index="0" name="screen" type="int" default="-1" />
<description>
</description>
</method>
<method name="screen_get_max_scale" qualifiers="const">
<return type="float" />
<description>
Expand All @@ -1096,6 +1108,12 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
<method name="screen_get_min_luminance" qualifiers="const">
<return type="float" />
<param index="0" name="screen" type="int" default="-1" />
<description>
</description>
</method>
<method name="screen_get_orientation" qualifiers="const">
<return type="int" enum="DisplayServer.ScreenOrientation" />
<param index="0" name="screen" type="int" default="-1" />
Expand Down Expand Up @@ -1154,6 +1172,12 @@
[b]Note:[/b] This method is implemented on Android, iOS, Web, macOS, and Linux (Wayland).
</description>
</method>
<method name="screen_get_sdr_white_level" qualifiers="const">
<return type="float" />
<param index="0" name="screen" type="int" default="-1" />
<description>
</description>
</method>
<method name="screen_get_size" qualifiers="const">
<return type="Vector2i" />
<param index="0" name="screen" type="int" default="-1" />
Expand All @@ -1168,6 +1192,12 @@
Returns the portion of the screen that is not obstructed by a status bar in pixels. See also [method screen_get_size].
</description>
</method>
<method name="screen_is_hdr_supported" qualifiers="const">
<return type="bool" />
<param index="0" name="screen" type="int" default="-1" />
<description>
</description>
</method>
<method name="screen_is_kept_on" qualifiers="const">
<return type="bool" />
<description>
Expand Down Expand Up @@ -1472,6 +1502,31 @@
Returns the current value of the given window's [param flag].
</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>
</description>
</method>
<method name="window_get_hdr_output_min_luminance" qualifiers="const">
<return type="float" />
<param index="0" name="window_id" type="int" default="0" />
<description>
</description>
</method>
<method name="window_get_hdr_output_prefer_high_precision" qualifiers="const">
<return type="bool" />
<param index="0" name="window_id" type="int" default="0" />
<description>
</description>
</method>
<method name="window_get_hdr_output_reference_luminance" qualifiers="const">
<return type="float" />
<param index="0" name="window_id" type="int" default="0" />
<description>
Returns the SDR reference 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 @@ -1567,6 +1622,13 @@
Returns [code]true[/code] if the window specified by [param window_id] is focused.
</description>
</method>
<method name="window_is_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_is_maximize_allowed" qualifiers="const">
<return type="bool" />
<param index="0" name="window_id" type="int" default="0" />
Expand Down Expand Up @@ -1639,6 +1701,45 @@
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>
</description>
</method>
<method name="window_set_hdr_output_min_luminance">
<return type="void" />
<param index="0" name="min_luminance" type="float" />
<param index="1" name="window_id" type="int" default="0" />
<description>
</description>
</method>
<method name="window_set_hdr_output_prefer_high_precision">
<return type="void" />
<param index="0" name="enabled" type="bool" />
<param index="1" name="window_id" type="int" default="0" />
<description>
</description>
</method>
<method name="window_set_hdr_output_reference_luminance">
<return type="void" />
<param index="0" name="reference_luminance" type="float" />
<param index="1" name="window_id" type="int" default="0" />
<description>
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 UI and other sRGB content looks correct when HDR is enabled.
</description>
</method>
<method name="window_set_ime_active">
<return type="void" />
<param index="0" name="active" type="bool" />
Expand Down Expand Up @@ -1928,6 +2029,9 @@
<constant name="FEATURE_WINDOW_EMBEDDING" value="29" enum="Feature">
Display server supports embedding a window from another process. [b]Windows, Linux (X11)[/b]
</constant>
<constant name="FEATURE_HDR" value="30" 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 @@ -833,6 +833,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/reference_luminance" type="float" setter="" getter="" default="200.0">
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.
</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
21 changes: 21 additions & 0 deletions drivers/d3d12/rendering_context_driver_d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,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_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;
}

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 @@ -109,6 +109,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_reference_luminance(SurfaceID p_surface, float p_reference_luminance) override;
virtual float surface_get_hdr_output_reference_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 @@ -127,6 +131,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_reference_luminance = 0.0f;
bool needs_resize = false;
ComPtr<IDCompositionDevice> composition_device;
ComPtr<IDCompositionTarget> composition_target;
Expand Down
107 changes: 101 additions & 6 deletions drivers/d3d12/rendering_device_driver_d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2418,10 +2418,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;
Expand All @@ -2432,13 +2432,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);
}
Expand Down Expand Up @@ -2481,11 +2490,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;
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);
Expand Down Expand Up @@ -2516,6 +2541,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);
}
Expand Down Expand Up @@ -2629,6 +2660,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);
Expand Down Expand Up @@ -6604,6 +6648,57 @@ Error RenderingDeviceDriverD3D12::_check_capabilities() {
print_verbose("- Depth bounds test not supported");
}

// DEBUG
if (is_print_verbose_enabled()) {
print_line("- DXGI Outputs:");

ComPtr<IDXGIOutput> 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<IDXGIOutput6> 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;
}

Expand Down
Loading

0 comments on commit 880618a

Please sign in to comment.