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 <alvinhochun@gmail.com>
  • Loading branch information
DarkKilauea and alvinhochun committed Oct 18, 2024
1 parent 80f0b33 commit 5abaebf
Show file tree
Hide file tree
Showing 35 changed files with 664 additions and 126 deletions.
2 changes: 2 additions & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,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 @@ -1622,6 +1636,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 @@ -1890,6 +1922,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 @@ -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.
</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
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 @@ -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;
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
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 @@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<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
2 changes: 2 additions & 0 deletions drivers/d3d12/rendering_device_driver_d3d12.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,13 +502,15 @@ 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;
virtual Error swap_chain_resize(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, uint32_t p_desired_framebuffer_count) override;
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
6 changes: 6 additions & 0 deletions drivers/metal/rendering_context_driver_metal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
20 changes: 20 additions & 0 deletions drivers/metal/rendering_context_driver_metal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions drivers/metal/rendering_device_driver_metal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions drivers/metal/rendering_device_driver_metal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 5abaebf

Please sign in to comment.