diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd
index 09f7b3eb..fadbcd88 100644
--- a/docs/labwc-config.5.scd
+++ b/docs/labwc-config.5.scd
@@ -175,6 +175,7 @@ this is for compatibility with Openbox.
no
no
yes
+ no
no
no
yes
@@ -240,6 +241,17 @@ this is for compatibility with Openbox.
'pkill kanshi ; wlopm --off \*' resume 'kanshi & wlopm --on \*'
```
+** [yes|no]
+ Automatically enable HDR support on outputs when configuring them,
+ where supported by the particular output device and display. Default
+ is no.
+
+ Note: Enabling this option will also currently make all non-HDR
+ outputs render to 10 bits per channel as well, but still sRGB.
+ Any configuration with `wlr-randr` or `kanshi` does not seem to
+ support passing this flag to my knowledge, so the only way to
+ override it is to enable this option.
+
** [yes|no]
Try to re-use the existing output mode (resolution / refresh rate).
This may prevent unnecessary screenblank delays when starting labwc
diff --git a/docs/rc.xml.all b/docs/rc.xml.all
index bbec9d0b..09da0673 100644
--- a/docs/rc.xml.all
+++ b/docs/rc.xml.all
@@ -13,6 +13,7 @@
no
no
yes
+ no
no
no
yes
diff --git a/include/config/rcxml.h b/include/config/rcxml.h
index 517cd907..d1841444 100644
--- a/include/config/rcxml.h
+++ b/include/config/rcxml.h
@@ -36,6 +36,17 @@ enum tearing_mode {
LAB_TEARING_FULLSCREEN_FORCED,
};
+enum hdr_mode {
+ LAB_HDR_DISABLED = 0,
+ LAB_HDR_ENABLED,
+};
+
+enum render_bit_depth {
+ LAB_RENDER_BIT_DEPTH_DEFAULT = 0,
+ LAB_RENDER_BIT_DEPTH_8,
+ LAB_RENDER_BIT_DEPTH_10,
+};
+
enum tiling_events_mode {
LAB_TILING_EVENTS_NEVER = 0,
LAB_TILING_EVENTS_REGION = 1 << 0,
@@ -74,6 +85,7 @@ struct rcxml {
int gap;
enum adaptive_sync_mode adaptive_sync;
enum tearing_mode allow_tearing;
+ enum hdr_mode hdr;
bool auto_enable_outputs;
bool reuse_output_mode;
bool xwayland_persistence;
diff --git a/include/output.h b/include/output.h
index 89d8be04..c17defa4 100644
--- a/include/output.h
+++ b/include/output.h
@@ -71,6 +71,9 @@ struct wlr_box output_usable_area_in_layout_coords(struct output *output);
void handle_output_power_manager_set_mode(struct wl_listener *listener,
void *data);
void output_enable_adaptive_sync(struct output *output, bool enabled);
+void output_enable_hdr(struct output *output, struct wlr_output_state *os,
+ bool enabled, bool silent);
+void output_state_setup_hdr(struct output *output, bool silent);
/**
* Notifies whether a fullscreen view is displayed on the given output.
diff --git a/meson.build b/meson.build
index 2c2d8e57..c6c6dee9 100644
--- a/meson.build
+++ b/meson.build
@@ -60,7 +60,7 @@ wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true'
have_libsfdo = not get_option('icon').disabled()
wayland_server = dependency('wayland-server', version: '>=1.22.90')
-wayland_protos = dependency('wayland-protocols', version: '>=1.39')
+wayland_protos = dependency('wayland-protocols', version: '>=1.47')
xkbcommon = dependency('xkbcommon')
xcb = dependency('xcb', required: get_option('xwayland'))
xcb_ewmh = dependency('xcb-ewmh', required: get_option('xwayland'))
diff --git a/src/config/rcxml.c b/src/config/rcxml.c
index a3eeed63..27dc3fd7 100644
--- a/src/config/rcxml.c
+++ b/src/config/rcxml.c
@@ -1032,6 +1032,16 @@ set_tearing_mode(const char *str, enum tearing_mode *variable)
}
}
+static void
+set_hdr_mode(const char *str, enum hdr_mode *variable)
+{
+ if (parse_bool(str, -1) == 1) {
+ *variable = LAB_HDR_ENABLED;
+ } else {
+ *variable = LAB_HDR_DISABLED;
+ }
+}
+
/* Returns true if the node's children should also be traversed */
static bool
entry(xmlNode *node, char *nodename, char *content)
@@ -1096,6 +1106,8 @@ entry(xmlNode *node, char *nodename, char *content)
set_adaptive_sync_mode(content, &rc.adaptive_sync);
} else if (!strcasecmp(nodename, "allowTearing.core")) {
set_tearing_mode(content, &rc.allow_tearing);
+ } else if (!strcasecmp(nodename, "Hdr.core")) {
+ set_hdr_mode(content, &rc.hdr);
} else if (!strcasecmp(nodename, "autoEnableOutputs.core")) {
set_bool(content, &rc.auto_enable_outputs);
} else if (!strcasecmp(nodename, "reuseOutputMode.core")) {
@@ -1457,6 +1469,7 @@ rcxml_init(void)
rc.gap = 0;
rc.adaptive_sync = LAB_ADAPTIVE_SYNC_DISABLED;
rc.allow_tearing = LAB_TEARING_DISABLED;
+ rc.hdr = LAB_HDR_DISABLED;
rc.auto_enable_outputs = true;
rc.reuse_output_mode = false;
rc.xwayland_persistence = false;
diff --git a/src/output-state.c b/src/output-state.c
index 7da1108b..5be75866 100644
--- a/src/output-state.c
+++ b/src/output-state.c
@@ -24,6 +24,10 @@ output_state_init(struct output *output)
backup_config, output->wlr_output);
wlr_output_head_v1_state_apply(&backup_head->state, &output->pending);
+
+ // This must be applied outside of config
+ output_state_setup_hdr(output, true);
+
wlr_output_configuration_v1_destroy(backup_config);
}
@@ -35,6 +39,7 @@ output_state_commit(struct output *output)
if (committed) {
wlr_output_state_finish(&output->pending);
wlr_output_state_init(&output->pending);
+ output_state_setup_hdr(output, true);
} else {
wlr_log(WLR_ERROR, "Failed to commit frame");
}
diff --git a/src/output.c b/src/output.c
index aea35062..f358d0f0 100644
--- a/src/output.c
+++ b/src/output.c
@@ -9,6 +9,7 @@
#define _POSIX_C_SOURCE 200809L
#include "output.h"
#include
+#include
#include
#include
#include
@@ -51,6 +52,45 @@
#include
#endif
+static enum render_bit_depth
+bit_depth_from_format(uint32_t render_format)
+{
+ if (render_format == DRM_FORMAT_XRGB2101010 || render_format == DRM_FORMAT_XBGR2101010) {
+ return LAB_RENDER_BIT_DEPTH_10;
+ } else if (render_format == DRM_FORMAT_XRGB8888 || render_format == DRM_FORMAT_ARGB8888 ||
+ render_format == DRM_FORMAT_XBGR8888 || render_format == DRM_FORMAT_ABGR8888) {
+ return LAB_RENDER_BIT_DEPTH_8;
+ }
+ return LAB_RENDER_BIT_DEPTH_DEFAULT;
+}
+
+static enum render_bit_depth
+get_config_render_bit_depth(void)
+{
+ if (rc.hdr == LAB_HDR_ENABLED) {
+ return LAB_RENDER_BIT_DEPTH_10;
+ }
+ return LAB_RENDER_BIT_DEPTH_8;
+}
+
+void
+output_state_setup_hdr(struct output *output, bool silent)
+{
+ uint32_t render_format = output->wlr_output->render_format;
+ enum render_bit_depth render_bit_depth = get_config_render_bit_depth();
+ if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10 &&
+ bit_depth_from_format(render_format) == render_bit_depth) {
+ // 10-bit was set successfully before, try to save some tests by reusing the format
+ wlr_output_state_set_render_format(&output->pending, render_format);
+ } else if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10) {
+ wlr_output_state_set_render_format(&output->pending, DRM_FORMAT_XBGR2101010);
+ } else {
+ wlr_output_state_set_render_format(&output->pending, DRM_FORMAT_XBGR8888);
+ }
+ bool hdr = rc.hdr == LAB_HDR_ENABLED;
+ output_enable_hdr(output, &output->pending, hdr, silent);
+}
+
bool
output_get_tearing_allowance(struct output *output)
{
@@ -371,6 +411,8 @@ configure_new_output(struct output *output)
output_enable_adaptive_sync(output, true);
}
+ output_state_setup_hdr(output, false);
+
output_state_commit(output);
wlr_output_effective_resolution(wlr_output,
@@ -653,6 +695,7 @@ output_config_apply(struct wlr_output_configuration_v1 *config)
wlr_output_state_set_transform(os, head->state.transform);
output_enable_adaptive_sync(output,
head->state.adaptive_sync_enabled);
+ output_state_setup_hdr(output, false);
}
if (!output_state_commit(output)) {
/*
@@ -1136,3 +1179,55 @@ output_set_has_fullscreen_view(struct output *output, bool has_fullscreen_view)
output_enable_adaptive_sync(output, has_fullscreen_view);
output_state_commit(output);
}
+
+static bool
+output_supports_hdr(const struct wlr_output *output, const char **unsupported_reason_ptr)
+{
+ const char *unsupported_reason = NULL;
+ if (!(output->supported_primaries & WLR_COLOR_NAMED_PRIMARIES_BT2020)) {
+ unsupported_reason = "BT2020 primaries not supported by output";
+ } else if (!(output->supported_transfer_functions &
+ WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ)) {
+ unsupported_reason = "PQ transfer function not supported by output";
+ } else if (!server.renderer->features.output_color_transform) {
+ unsupported_reason = "renderer doesn't support output color transforms";
+ }
+ if (unsupported_reason_ptr) {
+ *unsupported_reason_ptr = unsupported_reason;
+ }
+ return !unsupported_reason;
+}
+
+void
+output_enable_hdr(struct output *output, struct wlr_output_state *os, bool enabled, bool silent)
+{
+ const char *unsupported_reason = NULL;
+ if (enabled && !output_supports_hdr(output->wlr_output, &unsupported_reason)) {
+ if (!silent) {
+ wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s",
+ output->wlr_output->name, unsupported_reason);
+ }
+ enabled = false;
+ }
+
+ if (!enabled) {
+ if (output->wlr_output->supported_primaries != 0 ||
+ output->wlr_output->supported_transfer_functions != 0) {
+ if (!silent) {
+ wlr_log(WLR_DEBUG, "Disabling HDR on output %s",
+ output->wlr_output->name);
+ }
+ wlr_output_state_set_image_description(os, NULL);
+ }
+ return;
+ }
+
+ if (!silent) {
+ wlr_log(WLR_DEBUG, "Enabling HDR on output %s", output->wlr_output->name);
+ }
+ const struct wlr_output_image_description image_desc = {
+ .primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020,
+ .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ,
+ };
+ wlr_output_state_set_image_description(os, &image_desc);
+}
diff --git a/src/server.c b/src/server.c
index fd48efed..78513aab 100644
--- a/src/server.c
+++ b/src/server.c
@@ -9,6 +9,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -724,6 +726,47 @@ server_init(void)
server.tablet_manager = wlr_tablet_v2_create(server.wl_display);
+ if (server.renderer->features.input_color_transform) {
+ const enum wp_color_manager_v1_render_intent render_intents[] = {
+ WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL,
+ };
+ size_t transfer_functions_len = 0;
+ enum wp_color_manager_v1_transfer_function *transfer_functions =
+ wlr_color_manager_v1_transfer_function_list_from_renderer(
+ server.renderer,
+ &transfer_functions_len
+ );
+ size_t primaries_len = 0;
+ enum wp_color_manager_v1_primaries *primaries =
+ wlr_color_manager_v1_primaries_list_from_renderer(
+ server.renderer,
+ &primaries_len
+ );
+ struct wlr_color_manager_v1 *cm = wlr_color_manager_v1_create(
+ server.wl_display, 2, &(struct wlr_color_manager_v1_options){
+ .features = {
+ .parametric = true,
+ .set_mastering_display_primaries = true,
+ },
+ .render_intents = render_intents,
+ .render_intents_len = ARRAY_SIZE(render_intents),
+ .transfer_functions = transfer_functions,
+ .transfer_functions_len = transfer_functions_len,
+ .primaries = primaries,
+ .primaries_len = primaries_len,
+ });
+ free(transfer_functions);
+ free(primaries);
+ if (!cm) {
+ wlr_log(WLR_ERROR, "Failed to create color manager");
+ } else {
+ wlr_scene_set_color_manager_v1(server.scene, cm);
+ }
+ }
+
+ wlr_color_representation_manager_v1_create_with_renderer(
+ server.wl_display, 1, server.renderer);
+
layers_init();
/* These get cleaned up automatically on display destroy */