From 0b902c40f043ea9beae840282929b32da070801a Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Tue, 24 Sep 2024 17:57:04 -0500 Subject: [PATCH] wayland: add support for frog-color-management-v1 The big color-management MR in wayland-protocols has been in development for a very long time and honestly that protocol is super complex and painful to parse. The frog protocol stuff is relatively new but it turns out that kwin actually has support for this. Additionally, Arch and Fedora even package these protocols. Given that mpv is certainly a client that benefits from color stuff, it's not too much work to plug in this simple protocol so users can actually start using it. This adds an --wayland-colorspace-hint option which passes hdr metadata to the compositor. Unlike the existing solution (using vulkan), this is completely graphics API agnostic and will work with any wayland backend. --- meson.build | 2 + options/options.c | 1 + options/options.h | 1 + video/out/meson.build | 5 ++ video/out/opengl/context_wayland.c | 2 + video/out/vo_dmabuf_wayland.c | 20 +++++++ video/out/vo_wlshm.c | 2 + video/out/vulkan/context_wayland.c | 2 + video/out/wayland_common.c | 86 ++++++++++++++++++++++++++++++ video/out/wayland_common.h | 6 +++ 10 files changed, 127 insertions(+) diff --git a/meson.build b/meson.build index 67b7b3d107452..3c097e0865e51 100644 --- a/meson.build +++ b/meson.build @@ -1025,6 +1025,8 @@ endforeach features += {'wayland': wayland_deps and wayland['header'] and wayland['scanner'].found()} if features['wayland'] + frog_protocols = dependency('frog-protocols', required: false) + features += {'frog-protocols': frog_protocols.found()} subdir(join_paths('video', 'out')) endif diff --git a/options/options.c b/options/options.c index 46b532a65480f..6706a2948dcd5 100644 --- a/options/options.c +++ b/options/options.c @@ -194,6 +194,7 @@ static const m_option_t mp_vo_opt_list[] = { {"x11-wid-title", OPT_BOOL(x11_wid_title)}, #endif #if HAVE_WAYLAND + {"wayland-colorspace-hint", OPT_BOOL(wl_colorspace_hint)}, {"wayland-configure-bounds", OPT_CHOICE(wl_configure_bounds, {"auto", -1}, {"no", 0}, {"yes", 1})}, {"wayland-content-type", OPT_CHOICE(wl_content_type, {"auto", -1}, {"none", 0}, diff --git a/options/options.h b/options/options.h index 9bed9ca291ace..9e99fc052a30f 100644 --- a/options/options.h +++ b/options/options.h @@ -35,6 +35,7 @@ typedef struct mp_vo_opts { bool cursor_passthrough; bool native_keyrepeat; + bool wl_colorspace_hint; int wl_configure_bounds; int wl_content_type; bool wl_disable_vsync; diff --git a/video/out/meson.build b/video/out/meson.build index de93508217e0b..5b7c0922daf3a 100644 --- a/video/out/meson.build +++ b/video/out/meson.build @@ -11,6 +11,11 @@ protocols = [[wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'] wl_protocols_source = [] wl_protocols_headers = [] +if features['frog-protocols'] + frog_protocol_dir = frog_protocols.get_variable(pkgconfig: 'pkgdatadir', internal: 'pkgdatadir') + protocols += [[frog_protocol_dir, 'frog-color-management-v1.xml']] +endif + foreach v: ['1.32'] features += {'wayland-protocols-' + v.replace('.', '-'): wayland['deps'][2].version().version_compare('>=' + v)} diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c index b4b200cfb398a..3475a652b2520 100644 --- a/video/out/opengl/context_wayland.c +++ b/video/out/opengl/context_wayland.c @@ -67,6 +67,8 @@ static void wayland_egl_swap_buffers(struct ra_ctx *ctx) struct priv *p = ctx->priv; struct vo_wayland_state *wl = ctx->vo->wl; + vo_wayland_handle_hdr_metadata(wl); + eglSwapBuffers(p->egl_display, p->egl_surface); if (!wl->opts->wl_disable_vsync) diff --git a/video/out/vo_dmabuf_wayland.c b/video/out/vo_dmabuf_wayland.c index 9a79eb947ceff..c24a3819995b8 100644 --- a/video/out/vo_dmabuf_wayland.c +++ b/video/out/vo_dmabuf_wayland.c @@ -100,6 +100,8 @@ struct priv { bool destroy_buffers; bool force_window; enum hwdec_type hwdec_type; + + struct mp_image_params target_params; uint32_t drm_format; uint64_t drm_modifier; }; @@ -538,6 +540,12 @@ static void resize(struct vo *vo) lround(vo->dheight / wl->scaling_factor)); wl_subsurface_set_position(wl->osd_subsurface, lround((0 - dst.x0) / wl->scaling_factor), lround((0 - dst.y0) / wl->scaling_factor)); set_viewport_source(vo, src); + + mp_mutex_lock(&vo->params_mutex); + vo->target_params->w = mp_rect_w(dst); + vo->target_params->h = mp_rect_h(dst); + vo->target_params->rotate = (vo->params->rotate % 90) * 90; + mp_mutex_unlock(&vo->params_mutex); } static bool draw_osd(struct vo *vo, struct mp_image *cur, double pts) @@ -609,6 +617,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) pts = frame->current ? frame->current->pts : 0; if (frame->current) { + vo_wayland_handle_hdr_metadata(wl); buf = buffer_get(vo, frame); if (buf && buf->frame) { @@ -691,6 +700,17 @@ static int reconfig(struct vo *vo, struct mp_image *img) if (!vo_wayland_reconfig(vo)) return VO_ERROR; + mp_mutex_lock(&vo->params_mutex); + p->target_params = img->params; + // Restore fallback layer parameters if available. + mp_image_params_restore_dovi_mapping(&p->target_params); + // Strip metadata that are not understood anyway. + struct pl_hdr_metadata *hdr = &p->target_params.color.hdr; + hdr->scene_max[0] = hdr->scene_max[1] = hdr->scene_max[2] = 0; + hdr->scene_avg = hdr->max_pq_y = hdr->avg_pq_y = 0; + vo->target_params = &p->target_params; + mp_mutex_unlock(&vo->params_mutex); + wl_surface_set_buffer_transform(vo->wl->video_surface, img->params.rotate / 90); // Immediately destroy all buffers if params change. diff --git a/video/out/vo_wlshm.c b/video/out/vo_wlshm.c index 3132d7a563959..e6b304c1343cc 100644 --- a/video/out/vo_wlshm.c +++ b/video/out/vo_wlshm.c @@ -250,6 +250,8 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) if (!render) return; + vo_wayland_handle_hdr_metadata(wl); + buf = p->free_buffers; if (buf) { p->free_buffers = buf->next; diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c index 700822c9a49a1..e839ea8006c16 100644 --- a/video/out/vulkan/context_wayland.c +++ b/video/out/vulkan/context_wayland.c @@ -36,6 +36,8 @@ static void wayland_vk_swap_buffers(struct ra_ctx *ctx) { struct vo_wayland_state *wl = ctx->vo->wl; + vo_wayland_handle_hdr_metadata(wl); + if (!wl->opts->wl_disable_vsync) vo_wayland_wait_frame(wl); diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index d40206cee7e11..b7bc1998e82de 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -32,6 +32,7 @@ #include "osdep/poll_wrapper.h" #include "osdep/timer.h" #include "present_sync.h" +#include "video/mp_image.h" #include "wayland_common.h" #include "win_state.h" @@ -56,6 +57,10 @@ #include "cursor-shape-v1.h" #endif +#if HAVE_FROG_PROTOCOLS +#include "frog-color-management-v1.h" +#endif + #if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 22 #define HAVE_WAYLAND_1_22 #endif @@ -148,6 +153,19 @@ static const struct mp_keymap keymap[] = { {0, 0} }; +#if HAVE_FROG_PROTOCOLS +int primaries_map[PL_COLOR_PRIM_COUNT] = { + [PL_COLOR_PRIM_BT_709] = FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC709, + [PL_COLOR_PRIM_BT_2020] = FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC2020, +}; + +int transfer_map[PL_COLOR_TRC_COUNT] = { + [PL_COLOR_TRC_SRGB] = FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SRGB, + [PL_COLOR_TRC_GAMMA22] = FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22, + [PL_COLOR_TRC_PQ] = FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ, +}; +#endif + struct compositor_format { uint32_t format; uint32_t padding; @@ -1590,6 +1608,13 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id } #endif +#if HAVE_FROG_PROTOCOLS + if (!strcmp(interface, frog_color_management_factory_v1_interface.name) && found++) { + ver = 1; + wl->color_management = wl_registry_bind(reg, id, &frog_color_management_factory_v1_interface, ver); + } +#endif + if (!strcmp(interface, wp_presentation_interface.name) && found++) { ver = 1; wl->presentation = wl_registry_bind(reg, id, &wp_presentation_interface, ver); @@ -2149,6 +2174,18 @@ static int handle_round(int scale, int n) return (scale * n + WAYLAND_SCALE_FACTOR / 2) / WAYLAND_SCALE_FACTOR; } +#if HAVE_FROG_PROTOCOLS +static int pl_primaries_to_frog(enum pl_color_primaries primaries) +{ + return primaries_map[primaries]; +} + +static int pl_transfer_to_frog(enum pl_color_transfer transfer) +{ + return transfer_map[transfer]; +} +#endif + static void prepare_resize(struct vo_wayland_state *wl) { int32_t width = mp_rect_w(wl->geometry) / wl->scaling_factor; @@ -2243,6 +2280,17 @@ static void remove_seat(struct vo_wayland_seat *seat) return; } +static void set_colorspace(struct vo_wayland_state *wl) +{ + if (!wl->color_surface || !wl->vo->target_params) + return; +#if HAVE_FROG_PROTOCOLS + struct pl_color_space color = wl->vo->target_params->color; + frog_color_managed_surface_set_known_container_color_volume(wl->color_surface, pl_primaries_to_frog(color.primaries)); + frog_color_managed_surface_set_known_transfer_function(wl->color_surface, pl_transfer_to_frog(color.transfer)); +#endif +} + static void set_content_type(struct vo_wayland_state *wl) { if (!wl->content_type_manager) @@ -2657,6 +2705,8 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) &wl->opts->border); } } + if (opt == &opts->wl_colorspace_hint) + vo_wayland_handle_hdr_metadata(wl); if (opt == &opts->wl_content_type) set_content_type(wl); if (opt == &opts->cursor_passthrough) @@ -2767,6 +2817,23 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) return VO_NOTIMPL; } +void vo_wayland_handle_hdr_metadata(struct vo_wayland_state *wl) +{ + if (wl->reset_colorspace) { + set_colorspace(wl); + wl->reset_colorspace = false; + } + + if (!wl->color_surface || !wl->opts->wl_colorspace_hint || !pl_color_space_is_hdr(&wl->vo->target_params->color)) + return; +#if HAVE_FROG_PROTOCOLS + struct pl_hdr_metadata hdr = wl->vo->target_params->color.hdr; + frog_color_managed_surface_set_hdr_metadata(wl->color_surface, hdr.prim.red.x, hdr.prim.red.y, hdr.prim.green.x, + hdr.prim.green.y, hdr.prim.blue.x, hdr.prim.blue.y, hdr.prim.white.x, + hdr.prim.white.y, hdr.max_luma, hdr.min_luma, hdr.max_cll, hdr.max_fall); +#endif +} + void vo_wayland_handle_scale(struct vo_wayland_state *wl) { wp_viewport_set_destination(wl->viewport, lround(mp_rect_w(wl->geometry) / wl->scaling_factor), @@ -2906,6 +2973,15 @@ bool vo_wayland_init(struct vo *vo) } #endif +#if HAVE_FROG_PROTOCOLS + if (wl->color_management) { + wl->color_surface = frog_color_management_factory_v1_get_color_managed_surface(wl->color_management, wl->surface); + } else { + MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", + frog_color_management_factory_v1_interface.name); + } +#endif + if (wl->dnd_devman) { struct vo_wayland_seat *seat; wl_list_for_each(seat, &wl->seat_list, link) { @@ -2976,6 +3052,8 @@ bool vo_wayland_reconfig(struct vo *vo) MP_VERBOSE(wl, "Reconfiguring!\n"); + wl->reset_colorspace = true; + if (!wl->current_output) { wl->current_output = find_output(wl); if (!wl->current_output) @@ -3054,6 +3132,14 @@ void vo_wayland_uninit(struct vo *vo) wp_cursor_shape_manager_v1_destroy(wl->cursor_shape_manager); #endif +#if HAVE_FROG_PROTOCOLS + if (wl->color_management) + frog_color_management_factory_v1_destroy(wl->color_management); + + if (wl->color_surface) + frog_color_managed_surface_destroy(wl->color_surface); +#endif + if (wl->cursor_surface) wl_surface_destroy(wl->cursor_surface); diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index 23cca5b8e9594..db352ad40e4f0 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -85,6 +85,11 @@ struct vo_wayland_state { int timeout_count; int wakeup_pipe[2]; + /* color-management */ + void *color_management; + void *color_surface; + bool reset_colorspace; + /* content-type */ struct wp_content_type_manager_v1 *content_type_manager; struct wp_content_type_v1 *content_type; @@ -167,6 +172,7 @@ bool vo_wayland_reconfig(struct vo *vo); int vo_wayland_allocate_memfd(struct vo *vo, size_t size); int vo_wayland_control(struct vo *vo, int *events, int request, void *arg); +void vo_wayland_handle_hdr_metadata(struct vo_wayland_state *wl); void vo_wayland_handle_scale(struct vo_wayland_state *wl); void vo_wayland_set_opaque_region(struct vo_wayland_state *wl, bool alpha); void vo_wayland_sync_swap(struct vo_wayland_state *wl);