Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2751,7 +2751,7 @@
Display server supports interaction with screen reader or Braille display. [b]Linux (X11/Wayland), macOS, Windows[/b]
</constant>
<constant name="FEATURE_HDR_OUTPUT" value="35" enum="Feature">
Display server supports HDR output. [b]macOS, iOS, visionOS, Windows[/b]
Display server supports HDR output. [b]Linux (Wayland), macOS, iOS, visionOS, Windows[/b]
</constant>
<constant name="ROLE_UNKNOWN" value="0" enum="AccessibilityRole" deprecated="Use [AccessibilityServer] instead.">
Unknown or custom role.
Expand Down
1 change: 1 addition & 0 deletions drivers/vulkan/rendering_context_driver_vulkan.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ class RenderingContextDriverVulkan : public RenderingContextDriver {
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;
virtual bool is_colorspace_externally_managed() const { return false; }
bool is_colorspace_supported() const;

// Vulkan-only methods.
Expand Down
50 changes: 30 additions & 20 deletions drivers/vulkan/rendering_device_driver_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3427,9 +3427,10 @@ void RenderingDeviceDriverVulkan::command_buffer_execute_secondary(CommandBuffer
struct FormatCandidate {
VkFormat format;
VkColorSpaceKHR colorspace;
RDD::ColorSpace rdd_colorspace;
};

bool RenderingDeviceDriverVulkan::_determine_swap_chain_format(RenderingContextDriver::SurfaceID p_surface, VkFormat &r_format, VkColorSpaceKHR &r_color_space) {
bool RenderingDeviceDriverVulkan::_determine_swap_chain_format(RenderingContextDriver::SurfaceID p_surface, VkFormat &r_format, VkColorSpaceKHR &r_color_space, RDD::ColorSpace &r_rdd_color_space) {
DEV_ASSERT(p_surface != 0);

RenderingContextDriverVulkan::Surface *surface = (RenderingContextDriverVulkan::Surface *)(p_surface);
Expand All @@ -3456,24 +3457,39 @@ bool RenderingDeviceDriverVulkan::_determine_swap_chain_format(RenderingContextD
bool hdr_output_requested = context_driver->surface_get_hdr_output_enabled(p_surface);

// Determine which formats to prefer based on the requested capabilities.
FixedVector<FormatCandidate, 3> preferred_formats;
FixedVector<FormatCandidate, 6> preferred_formats;
if (hdr_output_requested) {
// Our preferred HDR format is 16-bit float + extended linear.
if (context_driver->is_colorspace_externally_managed()) {
// When the colorspace is managed externally to the driver we need to disable its color management.
// The colorspace which disables color management is VK_COLOR_SPACE_PASS_THROUGH_EXT.
preferred_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_PASS_THROUGH_EXT, COLOR_SPACE_REC709_LINEAR });

// SRGB_NONLINEAR_KHR is required for some NVIDIA drivers that support HDR output but do not support PASS_THROUGH_EXT.
preferred_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, COLOR_SPACE_REC709_LINEAR });
} else if (colorspace_supported) {
preferred_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT, COLOR_SPACE_REC709_LINEAR });
}
}

// If the surface requests HDR output, try to get an HDR format.
if (hdr_output_requested && colorspace_supported) {
// This format is preferred for HDR output.
preferred_formats.push_back({ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT });
// Some drivers may use wp-color-management even when performing SDR.
Comment thread
ArchercatNEO marked this conversation as resolved.
// https://github.com/godotengine/godot/pull/102987#discussion_r2913373482
if (context_driver->is_colorspace_externally_managed()) {
preferred_formats.push_back({ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT, COLOR_SPACE_REC709_NONLINEAR_SRGB });
preferred_formats.push_back({ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT, COLOR_SPACE_REC709_NONLINEAR_SRGB });
}
Comment thread
ArchercatNEO marked this conversation as resolved.

// These formats are always considered for SDR.
preferred_formats.push_back({ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
preferred_formats.push_back({ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR });
preferred_formats.push_back({ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, COLOR_SPACE_REC709_NONLINEAR_SRGB });
preferred_formats.push_back({ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, COLOR_SPACE_REC709_NONLINEAR_SRGB });

bool found = false;
for (const FormatCandidate &candidate : preferred_formats) {
for (uint32_t i = 0; i < format_count; i++) {
if (formats[i].format == candidate.format && formats[i].colorSpace == candidate.colorspace) {
r_format = formats[i].format;
r_color_space = formats[i].colorSpace;
r_rdd_color_space = candidate.rdd_colorspace;
found = true;
break;
}
Expand All @@ -3486,11 +3502,11 @@ bool RenderingDeviceDriverVulkan::_determine_swap_chain_format(RenderingContextD

// Warnings for when HDR capabilities are requested but not found.
if (hdr_output_requested) {
if (!colorspace_supported) {
if (!colorspace_supported && !context_driver->is_colorspace_externally_managed()) {
WARN_PRINT("HDR output requested but the vulkan driver does not support VK_EXT_swapchain_colorspace, falling back to SDR.");
}

if (r_color_space == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
if (r_rdd_color_space == COLOR_SPACE_REC709_NONLINEAR_SRGB) {
WARN_PRINT("HDR output requested but no HDR compatible format was found, falling back to SDR.");
}
}
Expand Down Expand Up @@ -3687,11 +3703,13 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
// 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)) {
RDD::ColorSpace rdd_color_space = COLOR_SPACE_REC709_NONLINEAR_SRGB;
if (!_determine_swap_chain_format(swap_chain->surface, format, color_space, rdd_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;
swap_chain->rdd_color_space = rdd_color_space;
}

VkSwapchainCreateInfoKHR swap_create_info = {};
Expand Down Expand Up @@ -3973,15 +3991,7 @@ RDD::ColorSpace RenderingDeviceDriverVulkan::swap_chain_get_color_space(SwapChai
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_REC709_NONLINEAR_SRGB;
case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT:
return COLOR_SPACE_REC709_LINEAR;
default:
DEV_ASSERT(false && "Unknown swap chain color space.");
return COLOR_SPACE_MAX;
}
return swap_chain->rdd_color_space;
}

void RenderingDeviceDriverVulkan::swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) {
Expand Down
3 changes: 2 additions & 1 deletion drivers/vulkan/rendering_device_driver_vulkan.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
RenderingContextDriver::SurfaceID surface = RenderingContextDriver::SurfaceID();
VkFormat format = VK_FORMAT_UNDEFINED;
VkColorSpaceKHR color_space = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
RDD::ColorSpace rdd_color_space = RDD::COLOR_SPACE_REC709_NONLINEAR_SRGB;
TightLocalVector<VkImage> images;
TightLocalVector<VkImageView> image_views;
TightLocalVector<VkSemaphore> present_semaphores;
Expand All @@ -432,7 +433,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
#endif
};

bool _determine_swap_chain_format(RenderingContextDriver::SurfaceID p_surface, VkFormat &r_format, VkColorSpaceKHR &r_color_space);
bool _determine_swap_chain_format(RenderingContextDriver::SurfaceID p_surface, VkFormat &r_format, VkColorSpaceKHR &r_color_space, RDD::ColorSpace &r_rdd_color_space);
void _swap_chain_release(SwapChain *p_swap_chain);

public:
Expand Down
3 changes: 3 additions & 0 deletions platform/linuxbsd/wayland/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ generated_sources = [
generate_from_xml("viewporter", "#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml"),
generate_from_xml("xdg_shell", "#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml"),
# Staging protocols
generate_from_xml(
"color_management", "#thirdparty/wayland-protocols/staging/color-management/color-management-v1.xml"
),
generate_from_xml("cursor_shape", "#thirdparty/wayland-protocols/staging/cursor-shape/cursor-shape-v1.xml"),
generate_from_xml(
"fractional_scale", "#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml"
Expand Down
160 changes: 159 additions & 1 deletion platform/linuxbsd/wayland/display_server_wayland.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ bool DisplayServerWayland::has_feature(DisplayServerEnums::Feature p_feature) co
case DisplayServerEnums::FEATURE_CLIPBOARD_PRIMARY:
case DisplayServerEnums::FEATURE_SUBWINDOWS:
case DisplayServerEnums::FEATURE_WINDOW_EMBEDDING:
case DisplayServerEnums::FEATURE_SELF_FITTING_WINDOWS: {
case DisplayServerEnums::FEATURE_SELF_FITTING_WINDOWS:
case DisplayServerEnums::FEATURE_HDR_OUTPUT: {
return true;
} break;

Expand Down Expand Up @@ -1461,6 +1462,125 @@ void DisplayServerWayland::window_start_resize(DisplayServerEnums::WindowResizeE
wayland_thread.window_start_resize(p_edge, p_window);
}

void DisplayServerWayland::_window_update_hdr_state(WindowData &p_window) {
DisplayServerEnums::WindowID window_id = p_window.id;

#if defined(RD_ENABLED)
if (rendering_context) {
// The `display/window/hdr/request_hdr_output` project setting makes the main window "request" HDR.
// On Windows, this means enable HDR for the main window if it is on an HDR screen.
// Since all screens support HDR on Wayland, we use whether the window "prefers" HDR or not instead.
bool hdr_preferred = p_window.color_profile.target_max_luminance > p_window.color_profile.reference_luminance;
bool hdr_desired = wayland_thread.supports_hdr() && hdr_preferred && p_window.hdr_requested;

if (rendering_context->window_get_hdr_output_enabled(window_id) != hdr_desired) {
rendering_context->window_set_hdr_output_enabled(window_id, hdr_desired);
}

if (hdr_desired) {
rendering_context->window_set_hdr_output_max_luminance(window_id, p_window.color_profile.target_max_luminance);
rendering_context->window_set_hdr_output_reference_luminance(window_id, p_window.color_profile.reference_luminance);
rendering_context->window_set_hdr_output_linear_luminance_scale(window_id, p_window.color_profile.target_max_luminance);

p_window.color_profile.named_primary = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB;
p_window.color_profile.named_transfer_function = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR;
} else {
p_window.color_profile.named_primary = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB;
p_window.color_profile.named_transfer_function = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22;
}

if (p_window.visible) {
MutexLock mutex_lock(wayland_thread.mutex);
wayland_thread.window_set_color_profile(window_id, p_window.color_profile);
}
}
#endif
}

bool DisplayServerWayland::window_is_hdr_output_supported(DisplayServerEnums::WindowID p_window_id) const {
ERR_FAIL_COND_V(!windows.has(p_window_id), false);
const WindowData &wd = windows[p_window_id];

return wd.color_profile.target_max_luminance > wd.color_profile.reference_luminance;
Comment thread
ArchercatNEO marked this conversation as resolved.
}

void DisplayServerWayland::window_request_hdr_output(const bool p_enabled, DisplayServerEnums::WindowID p_window_id) {
#if defined(RD_ENABLED)
ERR_FAIL_COND_MSG(!(rendering_device && rendering_device->has_feature(RenderingDevice::Features::SUPPORTS_HDR_OUTPUT)), "HDR output is not supported by the rendering device.");
#endif

ERR_FAIL_COND(!windows.has(p_window_id));
WindowData &wd = windows[p_window_id];
wd.hdr_requested = p_enabled;

_window_update_hdr_state(wd);
}

bool DisplayServerWayland::window_is_hdr_output_requested(DisplayServerEnums::WindowID p_window_id) const {
ERR_FAIL_COND_V(!windows.has(p_window_id), false);
const WindowData &wd = windows[p_window_id];
return wd.hdr_requested;
}

bool DisplayServerWayland::window_is_hdr_output_enabled(DisplayServerEnums::WindowID p_window_id) const {
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_hdr_output_enabled(p_window_id);
}
#endif
return false;
}

void DisplayServerWayland::window_set_hdr_output_reference_luminance(const float p_reference_luminance, DisplayServerEnums::WindowID p_window_id) {
ERR_FAIL_COND(!windows.has(p_window_id));
if (p_reference_luminance >= 0.0f) {
ERR_PRINT_ONCE("Manually setting reference white luminance is not supported on Linux devices, as they provide a user-facing brightness setting that directly controls reference white luminance.");
}
}

float DisplayServerWayland::window_get_hdr_output_reference_luminance(DisplayServerEnums::WindowID p_window_id) const {
return -1.0;
}

float DisplayServerWayland::window_get_hdr_output_current_reference_luminance(DisplayServerEnums::WindowID p_window_id) const {
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_hdr_output_reference_luminance(p_window_id);
}
#endif
return 0.0f;
}

void DisplayServerWayland::window_set_hdr_output_max_luminance(const float p_max_luminance, DisplayServerEnums::WindowID p_window_id) {
ERR_FAIL_COND(!windows.has(p_window_id));
if (p_max_luminance >= 0.0f) {
ERR_PRINT_ONCE("Manually setting max luminance is not supported on Linux devices as they provide a built-in method of calibrating max luminance without the need for additional apps or tools.");
}
}

float DisplayServerWayland::window_get_hdr_output_max_luminance(DisplayServerEnums::WindowID p_window_id) const {
return -1.0;
}

float DisplayServerWayland::window_get_hdr_output_current_max_luminance(DisplayServerEnums::WindowID p_window_id) const {
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_hdr_output_max_luminance(p_window_id);
}
#endif
return 0.0f;
}

float DisplayServerWayland::window_get_output_max_linear_value(DisplayServerEnums::WindowID p_window_id) const {
#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_output_max_linear_value(p_window_id);
}
#endif

return 1.0f;
}

void DisplayServerWayland::cursor_set_shape(DisplayServerEnums::CursorShape p_shape) {
ERR_FAIL_INDEX(p_shape, DisplayServerEnums::CURSOR_MAX);

Expand Down Expand Up @@ -1715,6 +1835,35 @@ void DisplayServerWayland::process_events() {

wayland_thread.keyboard_echo_keys();

#if defined(RD_ENABLED)
// Enabling HDR may have failed, in which case we need to clear the color profile.
// NOTE: this happens _before_ reading events because the rendering driver is only updated the frame _after_ we try to enable HDR.
if (rendering_device && (!OS::get_singleton()->is_in_low_processor_usage_mode() || RS::get_singleton()->has_changed())) {
for (KeyValue<DisplayServerEnums::WindowID, WindowData> &pair : windows) {
const RD::ColorSpace color_space = rendering_device->screen_get_color_space(pair.key);

bool dirty_srgb = color_space == RDD::COLOR_SPACE_REC709_NONLINEAR_SRGB && pair.value.color_profile.named_transfer_function != WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22;
bool dirty_linear = color_space == RDD::COLOR_SPACE_REC709_LINEAR && pair.value.color_profile.named_transfer_function != WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR;

if (dirty_srgb) {
pair.value.color_profile.named_primary = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB;
pair.value.color_profile.named_transfer_function = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22;

if (pair.value.visible) {
wayland_thread.window_set_color_profile(pair.key, pair.value.color_profile);
}
} else if (dirty_linear) {
pair.value.color_profile.named_primary = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB;
pair.value.color_profile.named_transfer_function = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR;

if (pair.value.visible) {
wayland_thread.window_set_color_profile(pair.key, pair.value.color_profile);
}
}
}
}
#endif

Comment thread
ArchercatNEO marked this conversation as resolved.
while (wayland_thread.has_message()) {
Ref<WaylandThread::Message> msg = wayland_thread.pop_message();

Expand Down Expand Up @@ -1846,6 +1995,15 @@ void DisplayServerWayland::process_events() {
}
continue;
}

Ref<WaylandThread::ColorProfileMessage> color_profile_msg = msg;
if (color_profile_msg.is_valid()) {
WindowData &wd = windows[color_profile_msg->id];
wd.color_profile = color_profile_msg->color_profile;

_window_update_hdr_state(wd);
Comment thread
ArchercatNEO marked this conversation as resolved.
continue;
}
}

switch (suspend_state) {
Expand Down
21 changes: 21 additions & 0 deletions platform/linuxbsd/wayland/display_server_wayland.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ class DisplayServerWayland : public DisplayServer {

DisplayServerEnums::WindowMode mode = DisplayServerEnums::WINDOW_MODE_WINDOWED;

bool hdr_requested = false;
WaylandThread::ColorProfile color_profile;

Callable rect_changed_callback;
Callable window_event_callback;
Callable input_event_callback;
Expand Down Expand Up @@ -176,6 +179,8 @@ class DisplayServerWayland : public DisplayServer {

void _update_window_rect(const Rect2i &p_rect, DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID);

void _window_update_hdr_state(WindowData &p_window);

void try_suspend();

void initialize_tts() const;
Expand Down Expand Up @@ -317,6 +322,22 @@ class DisplayServerWayland : public DisplayServer {
virtual void window_start_drag(DisplayServerEnums::WindowID p_window = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual void window_start_resize(DisplayServerEnums::WindowResizeEdge p_edge, DisplayServerEnums::WindowID p_window) override;

virtual bool window_is_hdr_output_supported(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) const override;

virtual void window_request_hdr_output(const bool p_enabled, DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual bool window_is_hdr_output_requested(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual bool window_is_hdr_output_enabled(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) const override;

virtual void window_set_hdr_output_reference_luminance(const float p_reference_luminance, DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual float window_get_hdr_output_reference_luminance(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual float window_get_hdr_output_current_reference_luminance(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) const override;

virtual void window_set_hdr_output_max_luminance(const float p_max_luminance, DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) override;
virtual float window_get_hdr_output_max_luminance(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) const override;
virtual float window_get_hdr_output_current_max_luminance(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) const override;

virtual float window_get_output_max_linear_value(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID) const override;

virtual void cursor_set_shape(DisplayServerEnums::CursorShape p_shape) override;
virtual DisplayServerEnums::CursorShape cursor_get_shape() const override;
virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, DisplayServerEnums::CursorShape p_shape, const Vector2 &p_hotspot) override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
class RenderingContextDriverVulkanWayland : public RenderingContextDriverVulkan {
private:
virtual const char *_get_platform_surface_extension() const override final;
// If wp-color-management is supported, we will perform color management externally to the driver.
// If wp-color-management is not supported, the driver would not be able to perform color management anyway.
virtual bool is_colorspace_externally_managed() const override final { return true; }

protected:
SurfaceID surface_create(const void *p_platform_data) override final;
Expand Down
Loading
Loading