diff --git a/video/out/d3d11/context.c b/video/out/d3d11/context.c index 0b21bcd982c2d..8272cac967179 100644 --- a/video/out/d3d11/context.c +++ b/video/out/d3d11/context.c @@ -203,7 +203,8 @@ static int d3d11_color_depth(struct ra_swapchain *sw) return MPMIN(ra_fmt->component_depth[0], desc1.BitsPerColor); } -static struct pl_color_space d3d11_target_color_space(struct ra_swapchain *sw) +static struct pl_color_space d3d11_target_color_space(struct ra_swapchain *sw, + float source_max_luma) { if (sw->ctx->opts.composition) return (struct pl_color_space){0}; diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h index dd0f99f83fd93..09fe2a6986fb0 100644 --- a/video/out/gpu/context.h +++ b/video/out/gpu/context.h @@ -79,7 +79,7 @@ struct ra_ctx_params { int (*color_depth)(struct ra_ctx *ctx); // Preferred device color space. Optional. - pl_color_space_t (*preferred_csp)(struct ra_ctx *ctx); + pl_color_space_t (*preferred_csp)(struct ra_ctx *ctx, float source_max_luma); // See ra_swapchain_fns.get_vsync. void (*get_vsync)(struct ra_ctx *ctx, struct vo_vsync_info *info); @@ -114,7 +114,7 @@ struct ra_swapchain_fns { int (*color_depth)(struct ra_swapchain *sw); // Target device color space. Optional. - pl_color_space_t (*target_csp)(struct ra_swapchain *sw); + pl_color_space_t (*target_csp)(struct ra_swapchain *sw, float source_max_luma); // Called when rendering starts. Returns NULL on failure. This must be // followed by submit_frame, to submit the rendered frame. This function diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c index 86e8d5daac8b4..1c0a36f946172 100644 --- a/video/out/opengl/context.c +++ b/video/out/opengl/context.c @@ -305,11 +305,11 @@ static void ra_gl_ctx_get_vsync(struct ra_swapchain *sw, p->params.get_vsync(sw->ctx, info); } -static pl_color_space_t ra_gl_ctx_target_csp(struct ra_swapchain *sw) +static pl_color_space_t ra_gl_ctx_target_csp(struct ra_swapchain *sw, float source_max_luma) { struct priv *p = sw->priv; if (p->params.preferred_csp) - return p->params.preferred_csp(sw->ctx); + return p->params.preferred_csp(sw->ctx, source_max_luma); return (pl_color_space_t){0}; } diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 2d95eb85b8834..265c83d3091de 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -1089,8 +1089,16 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame) bool pass_colorspace = false; struct pl_color_space target_csp = {0}; // TODO: Implement this for all backends - if (sw->fns->target_csp) - target_csp = sw->fns->target_csp(sw); + if (sw->fns->target_csp) { + float source_max_luma = 0.0f; + if (frame->current) { + source_max_luma = frame->current->params.color.hdr.max_luma; + if (opts->tone_map.inverse) + // Sentinel value to use the maximum luminance supported by the display. + source_max_luma = INFINITY; + } + target_csp = sw->fns->target_csp(sw, source_max_luma); + } if (target_csp.primaries == PL_COLOR_PRIM_UNKNOWN) target_csp.primaries = get_best_prim_container(&target_csp.hdr.prim); if (!pl_color_transfer_is_hdr(target_csp.transfer)) { diff --git a/video/out/vulkan/context.c b/video/out/vulkan/context.c index 7c196eb3fe3ed..893380ae9951a 100644 --- a/video/out/vulkan/context.c +++ b/video/out/vulkan/context.c @@ -518,11 +518,11 @@ static void get_vsync(struct ra_swapchain *sw, p->params.get_vsync(sw->ctx, info); } -static pl_color_space_t target_csp(struct ra_swapchain *sw) +static pl_color_space_t target_csp(struct ra_swapchain *sw, float soucre_max_luma) { struct priv *p = sw->priv; if (p->params.preferred_csp) - return p->params.preferred_csp(sw->ctx); + return p->params.preferred_csp(sw->ctx, soucre_max_luma); return (pl_color_space_t){0}; } diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c index 86e5e78b08669..5596b7dc436d1 100644 --- a/video/out/vulkan/context_wayland.c +++ b/video/out/vulkan/context_wayland.c @@ -33,9 +33,9 @@ static bool wayland_vk_check_visible(struct ra_ctx *ctx) return vo_wayland_check_visible(ctx->vo); } -static pl_color_space_t wayland_vk_preferred_csp(struct ra_ctx *ctx) +static pl_color_space_t wayland_vk_preferred_csp(struct ra_ctx *ctx, float source_max_luma) { - return vo_wayland_preferred_csp(ctx->vo); + return vo_wayland_preferred_csp(ctx->vo, source_max_luma); } static void wayland_vk_swap_buffers(struct ra_ctx *ctx) diff --git a/video/out/vulkan/context_win.c b/video/out/vulkan/context_win.c index 43bdcf2e483b2..734d5ee1cc427 100644 --- a/video/out/vulkan/context_win.c +++ b/video/out/vulkan/context_win.c @@ -52,7 +52,7 @@ static int color_depth(struct ra_ctx *ctx) return -1; } -static struct pl_color_space preferred_csp(struct ra_ctx *ctx) +static struct pl_color_space preferred_csp(struct ra_ctx *ctx, float source_max_luma) { struct priv *p = ctx->priv; diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index ec16702a12fc1..8cfc0bbd432dc 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -2146,13 +2146,32 @@ static void info_done(void *data, struct wp_image_description_info_v1 *image_des struct vo_wayland_state *wl = wd->wl; wp_image_description_info_v1_destroy(image_description_info); if (!wd->icc_file) { - wl->preferred_csp = wd->csp; MP_VERBOSE(wl, "Preferred surface feedback received:\n"); log_color_space(wl->log, wd); - if (wd->csp.hdr.max_luma > wd->ref_luma) { - MP_VERBOSE(wl, "Setting preferred transfer to PQ for HDR output.\n"); - wl->preferred_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 - a) / (wd->ref_luma - a); + wd->csp.hdr.min_luma = (wd->csp.hdr.min_luma - a) * b + a; + wd->csp.hdr.max_luma = (wd->csp.hdr.max_luma - a) * b + a; + if (wd->csp.hdr.max_cll != 0) + wd->csp.hdr.max_cll = (wd->csp.hdr.max_cll - a) * b + a; + if (wd->csp.hdr.max_fall != 0) + wd->csp.hdr.max_fall = (wd->csp.hdr.max_fall - a) * b + a; + // Since we want to do some exact comparisons of max_luma with PL_COLOR_SDR_WHITE, + // we need to round it. + if (fabsf(wd->csp.hdr.max_luma - PL_COLOR_SDR_WHITE) < 1e-2f) { + wd->csp.hdr.max_luma = PL_COLOR_SDR_WHITE; + if (wd->csp.hdr.max_cll != 0) + wd->csp.hdr.max_cll = MPMIN(wd->csp.hdr.max_cll, wd->csp.hdr.max_luma); + if (wd->csp.hdr.max_fall != 0) + wd->csp.hdr.max_fall = MPMIN(wd->csp.hdr.max_fall, wd->csp.hdr.max_luma); } + wl->preferred_csp = wd->csp; } else { if (wl->icc_size) { munmap(wl->icc_file, wl->icc_size); @@ -3959,10 +3978,59 @@ bool vo_wayland_check_visible(struct vo *vo) return render; } -struct pl_color_space vo_wayland_preferred_csp(struct vo *vo) +struct pl_color_space vo_wayland_preferred_csp(struct vo *vo, float source_max_luma) { struct vo_wayland_state *wl = vo->wl; - return wl->preferred_csp; + struct pl_color_space csp = wl->preferred_csp; + if (!pl_color_transfer_is_hdr(csp.transfer)) { + // For transfer functions for which pl_color_transfer_is_hdr returns false, mpv + // discards the HDR metadata and assumes a max_luma of PL_COLOR_SDR_WHITE. + // Similarly, mesa discards the HDR metadata for such transfer functions. + // Therefore, if there is any reason for us to preserve the HDR metadata, we need + // to switch to a transfer function for which pl_color_transfer_is_hdr returns + // true. PL_COLOR_TRC_PQ is the most widely supported transfer function of that + // kind. If VK_COLOR_SPACE_HDR10_ST2084_EXT is not available, then libplacebo will + // still try to fall back to another HDR transfer function so that mesa passes the + // metadata on to the compositor. (Therefore, using any HDR transfer function here + // would do since we cannot actually know which transfer function the compositor + // prefers.) + + // The default assumption by all components in the pipeline is that the maximum + // luminance for non-HDR transfer functions is paper white. Therefore, we don't + // have to handle this case. + if (csp.hdr.max_luma != PL_COLOR_SDR_WHITE) { + // Otherwise, if we are doing inverse tone mapping, which is indicated by + // source_max_luma == INFINITY, then libplacebo always needs to know the exact + // max_luma of the display since that is the target we want to hit. + if (source_max_luma == INFINITY) + csp.transfer = PL_COLOR_TRC_PQ; + // Otherwise, if the max_luma of the source is brighter than paper white, then + // libplacebo will always perform tone mapping. To do this correctly, it will + // need to know the exact max_luma of the display. + if (source_max_luma > PL_COLOR_SDR_WHITE) + csp.transfer = PL_COLOR_TRC_PQ; + // Otherwise, if max_luma of the display is less than paper white, there are + // two cases: + // 1. source_max_luma > csp.hdr.max_luma: In this case, we want libplacebo + // to tone map to the max_luma of the display. + // 2. source_max_luma <= csp.hdr.max_luma: In this case, we still need to + // tell the compositor that the max_luma of our images is + // < csp.hdr.max_luma since otherwise it will assume that the max_luma of + // or images is paper white and might do its own tone mapping down to + // csp.hdr.max_luma. + // So in both cases we need to pass the actual max_luma of the display through + // the pipeline. + if (csp.hdr.max_luma < PL_COLOR_SDR_WHITE) + csp.transfer = PL_COLOR_TRC_PQ; + + // NOTE: If source_max_luma > csp.hdr.max_luma, which seems to be missing from + // the conditions above, then either source_max_luma > PL_COLOR_SDR_WHITE, in + // which case we hit the second branch above, or + // csp.hdr.max_luma < source_max_luma <= PL_COLOR_WHITE in which case we hit + // the third branch above. + } + } + return csp; } int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index ed71848d27df1..8824e39fa3b1f 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -192,7 +192,7 @@ struct vo_wayland_state { }; bool vo_wayland_check_visible(struct vo *vo); -struct pl_color_space vo_wayland_preferred_csp(struct vo *vo); +struct pl_color_space vo_wayland_preferred_csp(struct vo *vo, float source_max_luma); bool vo_wayland_valid_format(struct vo_wayland_state *wl, uint32_t drm_format, uint64_t modifier); bool vo_wayland_init(struct vo *vo); bool vo_wayland_reconfig(struct vo *vo);