Skip to content
Open
9 changes: 9 additions & 0 deletions video/out/gpu/d3d11_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,15 @@ struct pl_color_space mp_dxgi_desc_to_color_space(const DXGI_OUTPUT_DESC1 *desc)
break;
}

if (!pl_color_transfer_is_hdr(ret.transfer)) {
// Don't use reported display peak in SDR mode, setting target peak in
// SDR mode is very specific usecase, needs proper calibration, users
// can set it manually.
ret.hdr.max_luma = 0;
ret.hdr.max_cll = 0;
ret.hdr.max_fall = 0;
}

return ret;
}

Expand Down
6 changes: 6 additions & 0 deletions video/out/opengl/context_wayland.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ static bool wayland_egl_check_visible(struct ra_ctx *ctx)
return vo_wayland_check_visible(ctx->vo);
}

static pl_color_space_t wayland_egl_preferred_csp(struct ra_ctx *ctx)
{
return vo_wayland_preferred_csp(ctx->vo);
}

static bool wayland_egl_set_color(struct ra_ctx *ctx, struct mp_image_params *params)
{
vo_wayland_handle_color(ctx->vo->wl, params);
Expand Down Expand Up @@ -112,6 +117,7 @@ static bool egl_create_context(struct ra_ctx *ctx)

struct ra_ctx_params params = {
.check_visible = wayland_egl_check_visible,
.preferred_csp = wayland_egl_preferred_csp,
.set_color = wayland_egl_set_color,
.swap_buffers = wayland_egl_swap_buffers,
.get_vsync = wayland_egl_get_vsync,
Expand Down
2 changes: 1 addition & 1 deletion video/out/vo_dmabuf_wayland.c
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame)
pts = frame->current ? frame->current->pts : 0;
if (frame->current) {
buf = buffer_get(vo, frame);
vo_wayland_handle_color(wl, &frame->current->params);
vo_wayland_handle_color(wl, &p->target_params);

if (buf && buf->frame) {
struct mp_image *image = buf->frame->current;
Expand Down
22 changes: 4 additions & 18 deletions video/out/vo_gpu_next.c
Original file line number Diff line number Diff line change
Expand Up @@ -956,10 +956,11 @@ static bool set_colorspace_hint(struct priv *p, struct pl_color_space *hint)
},
};

if (sw->fns->set_color && sw->fns->set_color(sw, &params)) {
if (hint)
if (sw->fns->set_color && sw->fns->set_color(sw, hint ? &params : NULL)) {
if (hint) {
*hint = params.color;
return true;
return true;
}
}
pl_swapchain_colorspace_hint(p->sw, hint);
return false;
Expand Down Expand Up @@ -1113,13 +1114,6 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame)
// limit min_luma to 1000:1 contrast ratio in SDR mode
if (target_csp.hdr.min_luma > PL_COLOR_SDR_WHITE / PL_COLOR_SDR_CONTRAST)
target_csp.hdr.min_luma = 0;
// Don't use reported display peak in SDR mode. Mostly because libplacebo
// forcefully switches to PQ if hinting hdr metadata, ignoring the transfer
// set in the hint. But also because setting target peak in SDR mode is
// very specific usecase, needs proper calibration, users can set it manually.
target_csp.hdr.max_luma = 0;
target_csp.hdr.max_cll = 0;
target_csp.hdr.max_fall = 0;
}
// maxFALL in display metadata is in fact MaxFullFrameLuminance. Wayland
// reports it as maxFALL directly, but this doesn't mean the same thing.
Expand Down Expand Up @@ -1151,14 +1145,6 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame)
pl_color_space_merge(&hint, source);
if (target_unknown && !opts->target_trc && !pl_color_transfer_is_hdr(source->transfer))
hint = *source;
// Vulkan doesn't have support for gamma 2.2 transfer function,
// so even though requested preferred color space is gamma 2.2, we
// fallback to sRGB. sRGB itself is ambiguous, but at least we have
// options to control the behavior.
// TODO: Revise this after fix for linear transfers lands in libplacebo.
// <https://code.videolan.org/videolan/libplacebo/-/merge_requests/759>
if (hint.transfer == PL_COLOR_TRC_GAMMA22)
hint.transfer = PL_COLOR_TRC_SRGB;
// Restore target luminance if it was present, note that we check
// max_luma only, this make sure that max_cll/max_fall is not take
// from source.
Expand Down
2 changes: 1 addition & 1 deletion video/out/vo_wlshm.c
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame)
}
}
if (src) {
vo_wayland_handle_color(wl, &src->params);
vo_wayland_handle_color(wl, &p->sws->dst);
struct mp_image dst = buf->mpi;
struct mp_rect src_rc;
struct mp_rect dst_rc;
Expand Down
27 changes: 21 additions & 6 deletions video/out/vulkan/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -528,13 +528,28 @@ static void get_vsync(struct ra_swapchain *sw,

static bool set_color(struct ra_swapchain *sw, struct mp_image_params *params)
{
// Needs VK_COLOR_SPACE_PASS_THROUGH_EXT for Wayland. This is currently the
// only backend that supports set_color, and likely will stay that way.
// Everything else can use Vulkan API colorspace without issues.
bool supported = strcmp(sw->ctx->fns->name, "waylandvk") != 0;
struct priv *p = sw->priv;
if (supported && p->params.set_color)
return p->params.set_color(sw->ctx, params);

// Vulkan Wayland needs special handling to avoid duplicated color surface.
bool waylandvk = strcmp(sw->ctx->fns->name, "waylandvk") == 0;
// Assume anything else is supported when set_color is defined. However,
// everything else can use Vulkan API colorspace without issues, so unlikely
// that set_color is used outside of Wayland.
bool supported = PL_API_VER >= 361 || !waylandvk;
if (supported && p->params.set_color) {
if (waylandvk && params) {
// Request VK_COLOR_SPACE_PASS_THROUGH_EXT
pl_swapchain_colorspace_hint(p->vk->swapchain, &(struct pl_color_space){0});
// Do the resize in case surface format needs to change.
pl_swapchain_resize(p->vk->swapchain, &(int){0}, &(int){0});
}
bool ret = p->params.set_color(sw->ctx, params);
// To avoid ping-pong between VK_COLOR_SPACE_PASS_THROUGH_EXT and others,
// we assume that internal Wayland set_color always work, and update
// params to fallback values if needed.
mp_assert(ret || !waylandvk);
return ret;
}
// Technically we could call pl_swapchain_colorspace_hint() here directly,
// but we want to know when parameters are set "externally".
return false;
Expand Down
116 changes: 98 additions & 18 deletions video/out/wayland_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -2029,6 +2029,10 @@ static void supported_feature(void *data, struct wp_color_manager_v1 *color_mana
MP_VERBOSE(wl, "Compositor supports setting mastering display primaries.\n");
wl->supports_display_primaries = true;
break;
case WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES:
MP_VERBOSE(wl, "Compositor supports setting primary color luminances.\n");
wl->supports_set_luminances = true;
break;
}
}

Expand Down Expand Up @@ -2146,20 +2150,31 @@ static void info_done(void *data, struct wp_image_description_info_v1 *image_des
if (!wd->icc_file) {
MP_VERBOSE(wl, "Preferred surface feedback received:\n");
log_color_space(wl->log, wd);
// We don't support extended ranges output where luminance exceeds
// maximum nominal luminance range (1.0), so switch to PQ.
if (fabsf(wd->csp.hdr.max_luma / wd->ref_luma - 1.0f) > 1e-4f &&
!pl_color_transfer_is_hdr(wd->csp.transfer)) {
MP_VERBOSE(wl, "Setting preferred transfer to PQ for HDR output.\n");
wd->csp.transfer = PL_COLOR_TRC_PQ;
}
// Wayland luminances are always in reference to the reference luminance. That is,
// if max_luma == 2*ref_luma, then there is 2x headroom above paper white. On the
// other hand, libplacebo hardcodes PL_COLOR_SDR_WHITE as the reference luminance.
// We must scale all wayland values to correspond to the libplacebo scale,
// otherwise libplacebo will assume that there is too little or too much headroom
// when ref_luma != PL_COLOR_SDR_WHITE.
float a = wd->min_luma;
float b = (PL_COLOR_SDR_WHITE - PL_COLOR_HDR_BLACK) / (wd->ref_luma - a);
wd->csp.hdr.min_luma = (wd->csp.hdr.min_luma - a) * b + PL_COLOR_HDR_BLACK;
wd->csp.hdr.max_luma = (wd->csp.hdr.max_luma - a) * b + PL_COLOR_HDR_BLACK;
// Wayland treats all transfers as display referred, don't scale min
// luminance, and hope compositors will do the right thing mapping it,
// or to be specific, not mapping it, because we set the same value.
float c = wd->min_luma;
float b = (PL_COLOR_SDR_WHITE - c) / (wd->ref_luma - a);
wd->csp.hdr.min_luma = (wd->csp.hdr.min_luma - a) * b + c;
wd->csp.hdr.max_luma = (wd->csp.hdr.max_luma - a) * b + c;
if (wd->csp.hdr.max_cll != 0)
wd->csp.hdr.max_cll = (wd->csp.hdr.max_cll - a) * b + PL_COLOR_HDR_BLACK;
wd->csp.hdr.max_cll = (wd->csp.hdr.max_cll - a) * b + c;
if (wd->csp.hdr.max_fall != 0)
wd->csp.hdr.max_fall = (wd->csp.hdr.max_fall - a) * b + PL_COLOR_HDR_BLACK;
wd->csp.hdr.max_fall = (wd->csp.hdr.max_fall - a) * b + c;
// Ensure that min_luma doesn't become negative.
wd->csp.hdr.min_luma = MPMAX(wd->csp.hdr.min_luma, 0.0);
// Since we want to do some exact comparisons of max_luma with PL_COLOR_SDR_WHITE,
Expand All @@ -2172,10 +2187,6 @@ static void info_done(void *data, struct wp_image_description_info_v1 *image_des
wd->csp.hdr.max_fall = MPMIN(wd->csp.hdr.max_fall, wd->csp.hdr.max_luma);
}
wl->preferred_csp = wd->csp;
if (wd->csp.hdr.max_luma != PL_COLOR_SDR_WHITE && !pl_color_transfer_is_hdr(wd->csp.transfer)) {
MP_VERBOSE(wl, "Setting preferred transfer to PQ for HDR output.\n");
wl->preferred_csp.transfer = PL_COLOR_TRC_PQ;
}
} else {
if (wl->icc_size) {
munmap(wl->icc_file, wl->icc_size);
Expand Down Expand Up @@ -2353,6 +2364,10 @@ static void supported_coefficients_and_ranges(void *data, struct wp_color_repres
wl->coefficients_map[PL_COLOR_SYSTEM_BT_2020_C] = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020_CL;
wl->range_map[PL_COLOR_SYSTEM_BT_2020_C + offset] = range;
break;
case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_ICTCP:
wl->coefficients_map[PL_COLOR_SYSTEM_BT_2100_PQ] = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_ICTCP;
wl->range_map[PL_COLOR_SYSTEM_BT_2100_PQ + offset] = range;
break;
}
}

Expand Down Expand Up @@ -3489,7 +3504,7 @@ static void seat_create_text_input(struct vo_wayland_seat *seat)
static void set_color_management(struct vo_wayland_state *wl, struct pl_color_space *color)
{
#if HAVE_WAYLAND_PROTOCOLS_1_41
if (!wl->color_surface || !wl->supports_parametric)
if (!wl->color_surface || !wl->color_queue || !wl->supports_parametric)
goto nosupport;

int primaries = wl->primaries_map[color->primaries];
Expand All @@ -3515,6 +3530,48 @@ static void set_color_management(struct vo_wayland_state *wl, struct pl_color_sp
wp_image_description_creator_params_v1_set_tf_named(image_creator_params, transfer);

struct pl_hdr_metadata hdr = color->hdr;

if (wl->supports_set_luminances) {
switch (color->transfer) {
case PL_COLOR_TRC_PQ:
// Set min luminance to 0 for PQ as per SMPTE ST 2084
wp_image_description_creator_params_v1_set_luminances(image_creator_params,
0 * WAYLAND_MIN_LUM_FACTOR, 10000, PL_COLOR_SDR_WHITE);
MP_VERBOSE(wl, "Setting PQ luminance range: min=0, max=10000, ref=%.2f\n",
PL_COLOR_SDR_WHITE);
// Mastering luminances will be set below
break;
case PL_COLOR_TRC_LINEAR:
// Our linear output is absolute scaled, meaning the 0 is absolute
// black, similar to PQ transfer. Configure it in the same way as PQ.
if (hdr.max_luma) {
wp_image_description_creator_params_v1_set_luminances(image_creator_params,
0 * WAYLAND_MIN_LUM_FACTOR, hdr.max_luma, PL_COLOR_SDR_WHITE);
MP_VERBOSE(wl, "Setting linear luminance range: min=0, max=%.5f, ref=%.2f\n",
hdr.max_luma, PL_COLOR_SDR_WHITE);
if (hdr.min_luma && hdr.max_luma) {
wp_image_description_creator_params_v1_set_mastering_luminance(image_creator_params,
lrintf(hdr.min_luma * WAYLAND_MIN_LUM_FACTOR), lrintf(hdr.max_luma));
MP_VERBOSE(wl, "Setting linear luminace mastering range: min=%.5f, max=%.2f\n",
hdr.min_luma, hdr.max_luma);
}
}
break;
case PL_COLOR_TRC_HLG:
// Leave default for HLG, we wouldn't output it directly, except for pass-through
break;
default:
// Set SDR luminance range for all relative transfers
if (hdr.min_luma && hdr.max_luma) {
wp_image_description_creator_params_v1_set_luminances(image_creator_params,
hdr.min_luma * WAYLAND_MIN_LUM_FACTOR, hdr.max_luma, PL_COLOR_SDR_WHITE);
MP_VERBOSE(wl, "Setting relative luminance range: min=%.5f, max=%.2f, ref=%.2f\n",
hdr.min_luma, hdr.max_luma, PL_COLOR_SDR_WHITE);
}
break;
}
}

bool is_hdr = pl_color_transfer_is_hdr(color->transfer);
bool use_metadata = hdr_metadata_valid(wl, &hdr);
if (!use_metadata)
Expand Down Expand Up @@ -3591,6 +3648,10 @@ static void set_color_representation(struct vo_wayland_state *wl, struct mp_imag
m_opt_choice_str(pl_csp_names, params->repr.sys),
m_opt_choice_str(pl_csp_levels_names, params->repr.levels));
wp_color_representation_surface_v1_set_coefficients_and_range(wl->color_representation_surface, coefficients, range);
} else {
MP_WARN(wl, "Color representation '%s / %s' not supported! Output may be incorrect.\n",
m_opt_choice_str(pl_csp_names, params->repr.sys),
m_opt_choice_str(pl_csp_levels_names, params->repr.levels));
}

if (alpha) {
Expand Down Expand Up @@ -4208,12 +4269,34 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg)

void vo_wayland_handle_color(struct vo_wayland_state *wl, struct mp_image_params *params)
{
bool color_space_changed = !pl_color_space_equal(&wl->current_params.color, &params->color);
bool color_repr_changed = !pl_color_repr_equal(&wl->current_params.repr, &params->repr) ||
wl->current_params.chroma_location != params->chroma_location;
#if HAVE_WAYLAND_PROTOCOLS_1_41
if (!params) {
if (wl->color_surface) {
wp_color_management_surface_v1_destroy(wl->color_surface);
wl->color_surface = NULL;
}
if (wl->color_representation_surface) {
wp_color_representation_surface_v1_destroy(wl->color_representation_surface);
wl->color_representation_surface = NULL;
}
wl->last_hint_params = wl->current_params = (struct mp_image_params){0};
return;
}
if (wl->color_manager) {
if (!wl->color_surface)
wl->color_surface = wp_color_manager_v1_get_surface(wl->color_manager, wl->callback_surface);
}
#endif

if (!color_space_changed && !color_repr_changed)
bool color_space_changed = !pl_color_space_equal(&wl->last_hint_params.color, &params->color);
bool color_repr_changed = !pl_color_repr_equal(&wl->last_hint_params.repr, &params->repr) ||
wl->last_hint_params.chroma_location != params->chroma_location;

if (!color_space_changed && !color_repr_changed) {
*params = wl->current_params;
return;
}
wl->last_hint_params = *params;
if (color_space_changed)
set_color_management(wl, &params->color);
if (color_repr_changed)
Expand Down Expand Up @@ -4340,11 +4423,8 @@ bool vo_wayland_init(struct vo *vo)
if (wl->color_manager) {
wl->color_surface_feedback = wp_color_manager_v1_get_surface_feedback(wl->color_manager, wl->callback_surface);
wp_color_management_surface_feedback_v1_add_listener(wl->color_surface_feedback, &surface_feedback_listener, wl);
// Only bind color surface to vo_dmabuf_wayland for now to avoid conflicting with graphics drivers
if (!strcmp(wl->vo->driver->name, "dmabuf-wayland")) {
wl->color_surface = wp_color_manager_v1_get_surface(wl->color_manager, wl->callback_surface);
if (!wl->color_queue)
wl->color_queue = wl_display_create_queue_with_name(wl->display, "image description creator queue");
}
} else {
MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
wp_color_manager_v1_interface.name);
Expand Down
2 changes: 2 additions & 0 deletions video/out/wayland_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ struct vo_wayland_state {
struct wp_color_management_surface_v1 *color_surface;
struct wp_color_management_surface_feedback_v1 *color_surface_feedback;
struct wp_image_description_creator_icc_v1 *icc_creator;
struct mp_image_params last_hint_params;
struct mp_image_params current_params;
bool supports_parametric;
bool supports_display_primaries;
bool supports_set_luminances;
int primaries_map[PL_COLOR_PRIM_COUNT];
int transfer_map[PL_COLOR_TRC_COUNT];
void *icc_file;
Expand Down
Loading