feat: hdr support

This commit is contained in:
DreamMaoMao 2026-06-24 07:52:20 +08:00
parent 94d89bdefc
commit fc2c5ae49f
8 changed files with 211 additions and 63 deletions

View file

@ -12,6 +12,7 @@ description: Advanced settings for XWayland, focus behavior, and system integrat
| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. |
| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. |
| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). |
| `hdr_hdr_depth` | `0`/`1`/`2` | Set the hdr depth for the current display. `0` is Default, `1` is HDR8, `2` is HDR10. |
## Focus & Input

View file

@ -30,6 +30,7 @@ monitorrule=name:Values,Parameter:Values,Parameter:Values
| `y` | integer | 0-99999 | Y position |
| `scale` | float | 0.01-100.0 | Monitor scale |
| `vrr` | integer | 0, 1 | Enable variable refresh rate |
| `hdr` | integer | 0, 1 | Enable hdr support |
| `rr` | integer | 0-7 | Monitor transform |
| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) |

View file

@ -265,4 +265,9 @@ void client_set_group_mon(Client *c, Monitor *m) {
client_change_mon(cur, m);
cur = cur->group_next;
}
}
void handle_client_focus_change(Client *c) {
check_keep_idle_inhibit(c);
check_vrr_enable(c);
}

View file

@ -22,6 +22,11 @@ enum { NUM_TYPE_MINUS, NUM_TYPE_PLUS, NUM_TYPE_DEFAULT };
enum { KEY_TYPE_CODE, KEY_TYPE_SYM };
enum render_bit_depth {
MANGO_RENDER_BIT_DEPTH_DEFAULT = 0,
MANGO_RENDER_BIT_DEPTH_8,
MANGO_RENDER_BIT_DEPTH_10,
};
typedef struct {
uint32_t keycode1;
uint32_t keycode2;
@ -115,6 +120,7 @@ typedef struct {
float refresh; // Refresh rate
int32_t vrr; // variable refresh rate
int32_t custom; // enable custom mode
int32_t hdr; // enable hdr mode
} ConfigMonitorRule;
// 修改后的宏定义
@ -413,6 +419,8 @@ typedef struct {
struct xkb_keymap *keymap;
DecorateDrawData jumplabeldata;
DecorateDrawData tabdata;
int32_t hdr_depth;
} Config;
typedef int32_t (*FuncType)(const Arg *);
@ -1520,6 +1528,8 @@ bool parse_option(Config *config, char *key, char *value) {
config->drag_floating_refresh_interval = atof(value);
} else if (strcmp(key, "allow_tearing") == 0) {
config->allow_tearing = atoi(value);
} else if (strcmp(key, "hdr_depth") == 0) {
config->hdr_depth = atoi(value);
} else if (strcmp(key, "allow_shortcuts_inhibit") == 0) {
config->allow_shortcuts_inhibit = atoi(value);
} else if (strcmp(key, "allow_lock_transparent") == 0) {
@ -2116,6 +2126,7 @@ bool parse_option(Config *config, char *key, char *value) {
rule->height = -1;
rule->refresh = 0.0f;
rule->vrr = 0;
rule->hdr = 0;
rule->custom = 0;
bool parse_error = false;
@ -2154,6 +2165,8 @@ bool parse_option(Config *config, char *key, char *value) {
rule->refresh = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f);
} else if (strcmp(key, "vrr") == 0) {
rule->vrr = CLAMP_INT(atoi(val), 0, 1);
} else if (strcmp(key, "hdr") == 0) {
rule->hdr = CLAMP_INT(atoi(val), 0, 1);
} else if (strcmp(key, "custom") == 0) {
rule->custom = CLAMP_INT(atoi(val), 0, 1);
} else {
@ -3512,6 +3525,7 @@ void override_config(void) {
config.drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1);
config.drag_tile_small = CLAMP_INT(config.drag_tile_small, 0, 1);
config.allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2);
config.hdr_depth = CLAMP_INT(config.hdr_depth, 0, 2);
config.allow_shortcuts_inhibit =
CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1);
config.allow_lock_transparent =
@ -3704,6 +3718,7 @@ void set_value_default() {
config.drag_tile_refresh_interval = 8.0f;
config.drag_floating_refresh_interval = 8.0f;
config.allow_tearing = TEARING_DISABLED;
config.hdr_depth = MANGO_RENDER_BIT_DEPTH_DEFAULT;
config.allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE;
config.allow_lock_transparent = 0;
config.no_border_when_single = 0;
@ -4034,13 +4049,12 @@ void reapply_monitor_rules(void) {
Monitor *m = NULL;
int32_t ji, vrr, custom;
int32_t mx, my;
struct wlr_output_state state;
wl_list_for_each(m, &mons, link) {
if (!m->wlr_output->enabled)
continue;
wlr_output_state_init(&state);
wlr_output_state_init(&m->pending);
for (ji = 0; ji < config.monitor_rules_count; ji++) {
if (config.monitor_rules_count < 1)
@ -4053,16 +4067,31 @@ void reapply_monitor_rules(void) {
my = mr->y == INT32_MAX ? m->m.y : mr->y;
vrr = mr->vrr >= 0 ? mr->vrr : 0;
custom = mr->custom >= 0 ? mr->custom : 0;
m->hdr_enable = mr->hdr >= 0 ? mr->hdr : 0;
(void)apply_rule_to_state(m, mr, &state, vrr, custom);
(void)apply_rule_to_state(m, mr, &m->pending, vrr, custom);
wlr_output_layout_add(output_layout, m->wlr_output, mx, my);
wlr_output_commit_state(m->wlr_output, &state);
break;
}
}
wlr_output_state_finish(&state);
wlr_output_state_set_enabled(&m->pending, true);
if (m->hdr_enable) {
output_state_setup_hdr(m, false);
bool success = wlr_output_commit_state(m->wlr_output, &m->pending);
if (!success) { // 多尝试一次
output_state_setup_hdr(m, true);
}
} else {
wlr_output_commit_state(m->wlr_output, &m->pending);
}
wlr_output_state_finish(&m->pending);
wlr_output_effective_resolution(m->wlr_output, &m->m.width,
&m->m.height);
}
updatemons(NULL, NULL);
}

View file

@ -1,6 +1,7 @@
#include "dwl-ipc.h"
#include "ext-workspace.h"
#include "foreign-toplevel.h"
#include "hdr.h"
#include "tablet.h"
#include "tearing.h"
#include "text-input.h"

128
src/ext-protocol/hdr.h Normal file
View file

@ -0,0 +1,128 @@
#include <drm_fourcc.h>
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
static uint32_t output_formats_8bit[] = {
DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888,
DRM_FORMAT_BGRX8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888,
DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888, DRM_FORMAT_RGB888,
DRM_FORMAT_BGR888,
};
static uint32_t output_formats_10bit[] = {
DRM_FORMAT_XRGB2101010, DRM_FORMAT_XBGR2101010, DRM_FORMAT_RGBX1010102,
DRM_FORMAT_BGRX1010102, DRM_FORMAT_ARGB2101010, DRM_FORMAT_ABGR2101010,
DRM_FORMAT_RGBA1010102, DRM_FORMAT_BGRA1010102,
};
static bool output_set_render_format(Monitor *m, uint32_t candidates[],
size_t count) {
for (size_t i = 0; i < count; i++) {
wlr_output_state_set_render_format(&m->pending, candidates[i]);
if (wlr_output_test_state(m->wlr_output, &m->pending))
return true;
}
return false;
}
static bool output_format_in_candidates(uint32_t format, uint32_t candidates[],
size_t count) {
for (size_t i = 0; i < count; i++)
if (candidates[i] == format)
return true;
return false;
}
static enum render_bit_depth bit_depth_from_format(uint32_t render_format) {
if (output_format_in_candidates(render_format, output_formats_10bit,
ARRAY_SIZE(output_formats_10bit)))
return MANGO_RENDER_BIT_DEPTH_10;
if (output_format_in_candidates(render_format, output_formats_8bit,
ARRAY_SIZE(output_formats_8bit)))
return MANGO_RENDER_BIT_DEPTH_8;
return MANGO_RENDER_BIT_DEPTH_DEFAULT;
}
static bool output_supports_hdr(const struct wlr_output *output,
const char **reason) {
const char *r = NULL;
if (!(output->supported_primaries & WLR_COLOR_NAMED_PRIMARIES_BT2020))
r = "BT2020 primaries not supported";
else if (!(output->supported_transfer_functions &
WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ))
r = "PQ transfer function not supported";
else if (!drw->features.output_color_transform)
r = "renderer doesn't support output color transforms";
if (reason)
*reason = r;
return !r;
}
void output_enable_hdr(Monitor *m, struct wlr_output_state *os, bool enabled,
bool silent) {
if (enabled && !output_supports_hdr(m->wlr_output, NULL))
enabled = false;
if (!enabled) {
if (m->wlr_output->supported_primaries ||
m->wlr_output->supported_transfer_functions) {
if (!silent)
wlr_log(WLR_DEBUG, "Disabling HDR on output %s",
m->wlr_output->name);
wlr_output_state_set_image_description(os, NULL);
}
return;
}
if (!silent)
wlr_log(WLR_DEBUG, "Enabling HDR on output %s", m->wlr_output->name);
struct wlr_output_image_description desc = {
.primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020,
.transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ,
};
wlr_output_state_set_image_description(os, &desc);
}
void output_state_setup_hdr(Monitor *m, bool silent) {
uint32_t render_format = m->wlr_output->render_format;
const char *unsupported_reason = NULL;
bool hdr_supported =
output_supports_hdr(m->wlr_output, &unsupported_reason);
bool hdr_succeeded = false;
enum render_bit_depth depth = config.hdr_depth;
if (depth == MANGO_RENDER_BIT_DEPTH_DEFAULT)
depth = bit_depth_from_format(render_format);
if (!hdr_supported && depth == MANGO_RENDER_BIT_DEPTH_10) {
if (!silent)
wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s",
m->wlr_output->name, unsupported_reason);
depth = MANGO_RENDER_BIT_DEPTH_8;
}
if (depth == MANGO_RENDER_BIT_DEPTH_10 &&
bit_depth_from_format(render_format) == depth) {
hdr_succeeded = true; // 上次已经成功设置10位直接复用
} else if (depth == MANGO_RENDER_BIT_DEPTH_10) {
hdr_succeeded = output_set_render_format(
m, output_formats_10bit, ARRAY_SIZE(output_formats_10bit));
if (!hdr_succeeded) {
if (!silent)
wlr_log(WLR_INFO,
"No 10 bit color formats supported, HDR disabled.");
if (!output_set_render_format(m, output_formats_8bit,
ARRAY_SIZE(output_formats_8bit)))
if (!silent)
wlr_log(WLR_ERROR, "No 8 bit color formats either!");
}
} else {
// 明确要求8位或自动降级
if (!output_set_render_format(m, output_formats_8bit,
ARRAY_SIZE(output_formats_8bit)))
if (!silent)
wlr_log(WLR_ERROR, "No 8 bit color formats supported!");
}
output_enable_hdr(m, &m->pending, hdr_succeeded, silent);
}

View file

@ -100,73 +100,44 @@ bool custom_wlr_scene_output_commit(struct wlr_scene_output *scene_output,
struct wlr_output *wlr_output = scene_output->output;
Monitor *m = wlr_output->data;
// 检查是否需要帧
if (!wlr_scene_output_needs_frame(scene_output)) {
wlr_log(WLR_DEBUG, "No frame needed for output %s", wlr_output->name);
if (!wlr_scene_output_needs_frame(scene_output))
return true;
}
// 构建输出状态
if (!wlr_scene_output_build_state(scene_output, state, NULL)) {
wlr_log(WLR_ERROR, "Failed to build output state for %s",
wlr_output->name);
// 构建状态,将场景的 Buffer 附着到 state 上
if (!wlr_scene_output_build_state(scene_output, state, NULL))
return false;
// 测试是否支持撕裂
if (!wlr_output_test_state(wlr_output, state)) {
// 如果 DRM 拒绝(例如当前输出/驱动不支持撕裂),降级关闭撕裂
state->tearing_page_flip = false;
}
// 测试撕裂翻页
if (state->tearing_page_flip) {
if (!wlr_output_test_state(wlr_output, state)) {
state->tearing_page_flip = false;
}
}
// 尝试提交
// 提交状态
bool committed = wlr_output_commit_state(wlr_output, state);
// 如果启用撕裂翻页但提交失败,重试禁用撕裂翻页
if (!committed && state->tearing_page_flip) {
wlr_log(WLR_DEBUG, "Retrying commit without tearing for %s",
wlr_output->name);
// 重试一次
state->tearing_page_flip = false;
committed = wlr_output_commit_state(wlr_output, state);
}
// 处理状态清理
if (committed) {
wlr_log(WLR_DEBUG, "Successfully committed output %s",
wlr_output->name);
if (state == &m->pending) {
wlr_output_state_finish(&m->pending);
wlr_output_state_init(&m->pending);
}
} else {
wlr_log(WLR_ERROR, "Failed to commit output %s", wlr_output->name);
// 即使提交失败,也清理状态避免积累
if (state == &m->pending) {
wlr_output_state_finish(&m->pending);
wlr_output_state_init(&m->pending);
}
return false;
}
return true;
return committed;
}
void apply_tear_state(Monitor *m) {
if (wlr_scene_output_needs_frame(m->scene_output)) {
wlr_output_state_init(&m->pending);
if (wlr_scene_output_build_state(m->scene_output, &m->pending, NULL)) {
struct wlr_output_state *pending = &m->pending;
pending->tearing_page_flip = true;
if (!wlr_scene_output_needs_frame(m->scene_output))
return;
if (!custom_wlr_scene_output_commit(m->scene_output, pending)) {
wlr_log(WLR_ERROR, "Failed to commit output %s",
m->scene_output->output->name);
}
} else {
wlr_log(WLR_ERROR, "Failed to build state for output %s",
m->scene_output->output->name);
wlr_output_state_finish(&m->pending);
}
wlr_output_state_init(&m->pending);
m->pending.tearing_page_flip = true;
if (!custom_wlr_scene_output_commit(m->scene_output, &m->pending)) {
wlr_log(WLR_ERROR, "Failed to commit output %s",
m->scene_output->output->name);
}
}

View file

@ -598,6 +598,7 @@ struct Monitor {
int8_t carousel_anim_dir;
bool vrr_global_enable;
bool is_vrr_opening;
bool hdr_enable;
};
typedef struct {
@ -952,6 +953,9 @@ static void global_draw_group_bar(Client *c, int32_t x, int32_t y,
static void client_reparent_group(Client *c);
static void client_change_mon(Client *c, Monitor *m);
static void check_vrr_enable(Client *c);
static void output_state_setup_hdr(Monitor *m, bool silent);
static void output_enable_hdr(Monitor *m, struct wlr_output_state *os,
bool enabled, bool silent);
#include "data/static_keymap.h"
#include "dispatch/bind_declare.h"
@ -3428,7 +3432,6 @@ void createmon(struct wl_listener *listener, void *data) {
const ConfigMonitorRule *r;
uint32_t i;
int32_t ji, vrr, custom;
struct wlr_output_state state;
Monitor *m = NULL;
bool custom_monitor_mode = false;
@ -3456,6 +3459,8 @@ void createmon(struct wl_listener *listener, void *data) {
m->vrr_global_enable = false;
m->is_vrr_opening = false;
m->hdr_enable = false;
m->wlr_output = wlr_output;
m->wlr_output->data = m;
@ -3476,9 +3481,9 @@ void createmon(struct wl_listener *listener, void *data) {
float scale = 1;
enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL;
wlr_output_state_init(&state);
wlr_output_state_set_scale(&state, scale);
wlr_output_state_set_transform(&state, rr);
wlr_output_state_init(&m->pending);
wlr_output_state_set_scale(&m->pending, scale);
wlr_output_state_set_transform(&m->pending, rr);
for (ji = 0; ji < config.monitor_rules_count; ji++) {
if (config.monitor_rules_count < 1)
@ -3493,8 +3498,9 @@ void createmon(struct wl_listener *listener, void *data) {
custom = r->custom >= 0 ? r->custom : 0;
scale = r->scale;
rr = r->rr;
m->hdr_enable = r->hdr;
if (apply_rule_to_state(m, r, &state, vrr, custom)) {
if (apply_rule_to_state(m, r, &m->pending, vrr, custom)) {
custom_monitor_mode = true;
}
break; // 只应用第一个匹配规则
@ -3502,7 +3508,7 @@ void createmon(struct wl_listener *listener, void *data) {
}
if (!custom_monitor_mode)
wlr_output_state_set_mode(&state,
wlr_output_state_set_mode(&m->pending,
wlr_output_preferred_mode(wlr_output));
/* Set up event listeners */
@ -3511,9 +3517,16 @@ void createmon(struct wl_listener *listener, void *data) {
LISTEN(&wlr_output->events.request_state, &m->request_state,
requestmonstate);
wlr_output_state_set_enabled(&state, 1);
wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state);
wlr_output_state_set_enabled(&m->pending, 1);
if (m->hdr_enable) {
output_state_setup_hdr(m, false);
}
wlr_output_commit_state(wlr_output, &m->pending);
wlr_output_state_finish(&m->pending);
wlr_output_effective_resolution(m->wlr_output, &m->m.width, &m->m.height);
wl_list_insert(&mons, &m->link);
@ -3996,8 +4009,7 @@ void focusclient(Client *c, int32_t lift) {
selmon->sel = c;
c->isfocusing = true;
check_keep_idle_inhibit(c);
check_vrr_enable(c);
handle_client_focus_change(c);
if (last_focus_client && !last_focus_client->iskilling &&
last_focus_client != c) {