From ce076de31f88682f733842736ddf12452a809e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Sun, 6 Jul 2025 15:44:49 +0200 Subject: [PATCH] vo_gpu_next: add `--target-colorspace-hint-mode` option Useful to actually set target display paramters to colorspace hint. --- .../target-colorspace-hint-mode.txt | 1 + DOCS/man/options.rst | 84 ++++++++++++++++++- video/out/vo_gpu_next.c | 64 +++++++------- 3 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 DOCS/interface-changes/target-colorspace-hint-mode.txt diff --git a/DOCS/interface-changes/target-colorspace-hint-mode.txt b/DOCS/interface-changes/target-colorspace-hint-mode.txt new file mode 100644 index 0000000000..8ce2d69dad --- /dev/null +++ b/DOCS/interface-changes/target-colorspace-hint-mode.txt @@ -0,0 +1 @@ +add `--target-colorspace-hint-mode` option diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 80e64c8a14..c11c133d3b 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -7023,6 +7023,84 @@ them. if the display signals support for HDR colorspace. Requires a supporting driver and ``--vo=gpu-next``. (Default: ``no``) +``--target-colorspace-hint-mode=`` + Select which metadata to use for the ``--target-colorspace-hint``. + (Only for ``--vo=gpu-next``) + + target + Uses metadata based on the target display's actual capabilities. This + mode adapts the source content to the target display before output. + Note: HDR primaries are not overridden by the ``--target-prim`` option + this only affects the enclosing container for the colorspace. + + source + Uses the source content's metadata. This is the traditional + "HDR passthrough" mode (SDR too), where it is assumed that the compositor + and display will handle the colorspace directly and perform any necessary + mappings. + + Default is ``target``. If target display parameters are not available, this + will fall back to ``source``. Note that this is done on individual properties + basis, i.e. it will merge source params into target for unknown properties, + though not the other way around. + + ``--target-*`` options override the metadata in both modes. + + .. note:: + It is highly recommended to use ``--target-colorspace-hint=`` + to ensure the output colorspace is set correctly. This is crucial for + all non-sRGB content, even SDR, to allow the compositor, driver, and + display to properly interpret the signal. + + Unfortunately, it's not as easy as it sounds. While mpv performs + high-quality color processing, we cannot guarantee what will happen + after the signal leaves mpv. Therefore, you may need to adjust additional + settings to ensure proper output. A one-size-fits-all default is not + feasible. + + Now with the backstory out of the way: + + For HDR output the default of ``target`` should work fine, it will + automatically infer the best target HDR parameters and surface format. + For compatibility, displays are assumed to be in HDR mode, unless it's + reported otherwise. (you can override this with ``--target-trc``). + This way the HDR metadata is set and hopefully the compositor will + handle the rest. If the input is SDR, it will be converted to PQ with + primaries set to source values. + + For SDR output, for targets where mpv cannot determine whether the target + is HDR or SDR, you can use ``source`` mode. Metadata set will match the + input colorspace. In case of HDR input, it will be passthrough as-is. + Alternatively, you can use ``target`` mode and set ``--target-trc`` to + a SDR transfer function. This way any input will be converted to SDR. + + Use the stats display to verify the input and output colorspace settings. + + TL;DR: Use ``--target-colorspace-hint=auto`` and adjust ``--target-*`` + parameters to match your target display capabilities, until it looks best + for you. Use `Conditional auto profiles`_ for specific adjustments. Avoid + using ``--target-colorspace-hint=no`` unless it's sRGB content, but even + then it's better to set the colorspace metadata. + + .. note:: + Additional chatter about the "HDR passthrough" mode: There is a belief + that this mode should send the source HDR signal as-is to the display, + and the display will magically handle it, no matter what. This is not + always true. In some cases it is better to send the HDR signal + tone-mapped to the target display's capabilities, and the best way to do + this is within mpv itself. + + This is generally handled by either the compositor or the GPU driver. + You can probably find HDR "calibration" options somewhere in your system. + + You can choose which metadata to send to the display and manually tweak + it using the ``--target-*`` options. You can also try + ``--inverse-tone-mapping`` if you want to make everything appear more + HDR-like. + + Your mileage may vary, this highly depends on the target display, there + is no single answer, but try experimenting, you may be surprised. + ``--target-prim=`` Specifies the primaries of the display. Video colors will be adapted to this colorspace when ICC color management is not being used. Valid values @@ -7252,8 +7330,10 @@ them. Specifies the contrast (slope) at the knee point. Defaults to 1.0. ``--inverse-tone-mapping`` - If set, allows inverse tone mapping (expanding SDR to HDR). Not supported - by all tone mapping curves. Use with caution. (``--vo=gpu-next`` only) + If set, allows inverse tone mapping (expanding dynamic range). Can be used + for upscaling SDR content to HDR, or for making HDR content brighter. + Not supported by all tone mapping curves. Use with caution. + (``--vo=gpu-next`` only) ``--tone-mapping-max-boost=<1.0..10.0>`` Upper limit for how much the tone mapping algorithm is allowed to boost diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index cac8fe9798..7c8e09d446 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -174,6 +174,7 @@ struct gl_next_opts { struct user_lut image_lut; struct user_lut target_lut; int target_hint; + int target_hint_mode; char **raw_opts; }; @@ -205,6 +206,7 @@ const struct m_sub_options gl_next_conf = { {"image-lut-type", OPT_CHOICE_C(image_lut.type, lut_types)}, {"target-lut", OPT_STRING(target_lut.opt), .flags = M_OPT_FILE}, {"target-colorspace-hint", OPT_CHOICE(target_hint, {"auto", -1}, {"no", 0}, {"yes", 1})}, + {"target-colorspace-hint-mode", OPT_CHOICE(target_hint_mode, {"target", 0}, {"source", 1})}, // No `target-lut-type` because we don't support non-RGB targets {"libplacebo-opts", OPT_KEYVALUELIST(raw_opts)}, {0}, @@ -1003,12 +1005,15 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame) struct ra_swapchain *sw = p->ra_ctx->swapchain; bool pass_colorspace = false; - struct pl_color_space target_csp; - // Assume HDR is supported, if query is not available + struct pl_color_space target_csp = {0}; // TODO: Implement this for all backends - target_csp = sw->fns->target_csp - ? sw->fns->target_csp(sw) - : (struct pl_color_space){ .transfer = PL_COLOR_TRC_PQ }; + if (sw->fns->target_csp) + target_csp = sw->fns->target_csp(sw); + // Assume HDR is supported, if query is not available + if (target_csp.transfer == PL_COLOR_TRC_UNKNOWN) { + target_csp = (struct pl_color_space){ + .transfer = opts->target_trc ? opts->target_trc : PL_COLOR_TRC_PQ }; + } if (!pl_color_transfer_is_hdr(target_csp.transfer)) { // Don't use reported display peak in SDR mode. Mostly because libplacebo // forcefully switches to PQ if hinting hdr metadata, ignoring the transfer @@ -1016,6 +1021,8 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame) // very specific usecase, needs proper calibration, users can set it manually. target_csp.hdr.max_luma = 0; target_csp.hdr.min_luma = 0; + target_csp.hdr.max_cll = 0; + target_csp.hdr.max_fall = 0; } struct pl_color_space hint; @@ -1023,36 +1030,33 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame) (p->next_opts->target_hint == -1 && pl_color_transfer_is_hdr(target_csp.transfer)); if (target_hint && frame->current) { - hint = frame->current->params.color; - float target_peak = opts->target_peak ? opts->target_peak : target_csp.hdr.max_luma; - float target_trc = opts->target_trc; - if (!target_trc) { - // If we are targeting HDR display, repack SDR to target trc. This way - // we ensure that conversion is done by mpv and not by the compositor, - // which is often worse in terms of quality. Do this only if we are sure - // that display is in HDR mode, i.e. target_csp() reported HDR transfer. - // libplacebo already does the same when peak > 203, but we want this - // for any peak value. - // Note: We don't have information if the compositor/driver is able to - // reconfigure the display, currently target_csp() is only implemented - // for D3D11 where we know automatic reconfiguration never happens. - // Also I like to minimize possible reconfigurations, and outputting SDR - // in PQ is fine. - if (pl_color_transfer_is_hdr(target_csp.transfer) && target_csp.hdr.max_luma > 0 && - !pl_color_transfer_is_hdr(frame->current->params.color.transfer)) - { - target_trc = target_trc ? target_trc : target_csp.transfer; - } + const struct pl_color_space *source = &frame->current->params.color; + const struct pl_color_space *target = &target_csp; + hint = *(p->next_opts->target_hint_mode == 0 ? target : source); + if (pl_color_transfer_is_hdr(hint.transfer) && !pl_primaries_valid(&hint.hdr.prim)) + pl_color_space_merge(&hint, source); + // Infer missing bits now. This is important so that we don't lose + // information after user option overrides. For example, if the user + // sets target_trc to PQ, but the hint(source) is SDR, we want to fill + // in SDR luminance values instead of the default PQ range. + pl_color_space_infer(&hint); + // Always prefer target luminance and transfer for inverse tone mapping + if (pl_color_transfer_is_hdr(target->transfer) && opts->tone_map.inverse) { + hint.transfer = target->transfer; + hint.hdr.max_luma = target->hdr.max_luma; + hint.hdr.min_luma = target->hdr.min_luma; + hint.hdr.max_cll = target->hdr.max_cll; + hint.hdr.max_fall = target->hdr.max_fall; } if (p->ra_ctx->fns->pass_colorspace && p->ra_ctx->fns->pass_colorspace(p->ra_ctx)) pass_colorspace = true; if (opts->target_prim) hint.primaries = opts->target_prim; - if (target_trc) - hint.transfer = target_trc; - if (target_peak) - hint.hdr.max_luma = target_peak; - apply_target_contrast(p, &hint, target_csp.hdr.min_luma); + if (opts->target_trc) + hint.transfer = opts->target_trc; + if (opts->target_peak) + hint.hdr.max_luma = opts->target_peak; + apply_target_contrast(p, &hint, hint.hdr.min_luma); if (!pass_colorspace) pl_swapchain_colorspace_hint(p->sw, &hint); } else if (!target_hint) {