Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

video/out/gpu/context: add target_csp callback to ra_swapchain #15279

Merged
merged 3 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DOCS/interface-changes/target-hint.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
change `target-colorspace-hint` default to `yes`
change `target-colorspace-hint` default to `auto`
11 changes: 7 additions & 4 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6826,11 +6826,12 @@ them.
Fully replaces the color decoding. A LUT of this type should ingest the
image's native colorspace and output normalized non-linear RGB.

``--target-colorspace-hint``
``--target-colorspace-hint=<auto|yes|no>``
Automatically configure the output colorspace of the display to pass
through the input values of the stream (e.g. for HDR passthrough), if
possible. Requires a supporting driver and ``--vo=gpu-next``.
(Default: ``yes``)
possible. In ``auto`` mode (the default), the target colorspace is only set,
if the display signals support for HDR colorspace.
Requires a supporting driver and ``--vo=gpu-next``. (Default: ``auto``)

``--target-prim=<value>``
Specifies the primaries of the display. Video colors will be adapted to
Expand Down Expand Up @@ -6928,7 +6929,8 @@ them.

In ``auto`` mode (the default), the chosen peak is an appropriate value
based on the TRC in use. For SDR curves, it uses 203. For HDR curves, it
uses 203 * the transfer function's nominal peak.
uses 203 * the transfer function's nominal peak. If available, it will use
the target display's peak brightness as reported by the display.

.. note::

Expand All @@ -6953,6 +6955,7 @@ them.
black point. Used in black point compensation during HDR tone-mapping.
``auto`` is the default and assumes 1000:1 contrast as a typical SDR display
would have or an infinite contrast when HDR ``--target-trc`` is used.
If supported by the API, display contrast will be used as reported.
``inf`` contrast specifies display with perfect black level, in practice OLED.
(Only for ``--vo=gpu-next``)

Expand Down
45 changes: 45 additions & 0 deletions video/out/d3d11/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,50 @@ 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)
{
struct priv *p = sw->priv;

struct pl_color_space ret = {0};
DXGI_OUTPUT_DESC1 desc1;
if (!mp_get_dxgi_output_desc(p->swapchain, &desc1))
return ret;

ret.hdr.max_luma = desc1.MaxLuminance;
ret.hdr.min_luma = desc1.MinLuminance;
ret.hdr.prim.blue.x = desc1.BluePrimary[0];
ret.hdr.prim.blue.y = desc1.BluePrimary[1];
ret.hdr.prim.green.x = desc1.GreenPrimary[0];
ret.hdr.prim.green.y = desc1.GreenPrimary[1];
ret.hdr.prim.red.x = desc1.RedPrimary[0];
ret.hdr.prim.red.y = desc1.RedPrimary[1];

switch (desc1.ColorSpace) {
case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709:
ret.primaries = PL_COLOR_PRIM_BT_709;
ret.transfer = PL_COLOR_TRC_SRGB;
break;
case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709:
ret.primaries = PL_COLOR_PRIM_BT_709;
ret.transfer = PL_COLOR_TRC_LINEAR;
break;
case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020:
ret.primaries = PL_COLOR_PRIM_BT_2020;
ret.transfer = PL_COLOR_TRC_PQ;
break;
case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020:
ret.primaries = PL_COLOR_PRIM_BT_2020;
ret.transfer = PL_COLOR_TRC_SRGB;
break;
default:
ret.primaries = PL_COLOR_PRIM_UNKNOWN;
ret.transfer = PL_COLOR_TRC_UNKNOWN;
break;
}

return ret;
}

static bool d3d11_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
{
struct priv *p = sw->priv;
Expand Down Expand Up @@ -448,6 +492,7 @@ static void d3d11_uninit(struct ra_ctx *ctx)

static const struct ra_swapchain_fns d3d11_swapchain = {
.color_depth = d3d11_color_depth,
.target_csp = d3d11_target_color_space,
.start_frame = d3d11_start_frame,
.submit_frame = d3d11_submit_frame,
.swap_buffers = d3d11_swap_buffers,
Expand Down
7 changes: 7 additions & 0 deletions video/out/gpu/context.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <libplacebo/colorspace.h>

#include "video/out/vo.h"
#include "video/csputils.h"

Expand Down Expand Up @@ -78,10 +80,15 @@ struct ra_fbo {
struct pl_color_space color_space;
};

typedef struct pl_color_space pl_color_space_t;

struct ra_swapchain_fns {
// Gets the current framebuffer depth in bits (0 if unknown). Optional.
int (*color_depth)(struct ra_swapchain *sw);

// Target device color space. Optional.
pl_color_space_t (*target_csp)(struct ra_swapchain *sw);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of nitpicky but is there a reason to return a colorspace? It seems like you only actually use the display's peak brightness.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial version was using only target peak, but it turns out it is also returned for SDR displays. Like I have one with 270 nits. Correct way is to check the color space returned from the API. Also primaries might be useful in the future.

There is still one thing to fix, because I noticed that lib placebo completely fails to render SDR correctly if the target peak is not 203. I may have time tomorrow to fix that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could only return the MaxLuminance value if the colorspace is actually an HDR one and return 203 otherwise.


// Called when rendering starts. Returns NULL on failure. This must be
// followed by submit_frame, to submit the rendered frame. This function
// can also fail sporadically, and such errors should be ignored unless
Expand Down
57 changes: 38 additions & 19 deletions video/out/vo_gpu_next.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ struct gl_next_opts {
struct user_lut lut;
struct user_lut image_lut;
struct user_lut target_lut;
bool target_hint;
int target_hint;
char **raw_opts;
};

Expand All @@ -197,15 +197,15 @@ const struct m_sub_options gl_next_conf = {
{"image-lut", OPT_STRING(image_lut.opt), .flags = M_OPT_FILE},
{"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_BOOL(target_hint)},
{"target-colorspace-hint", OPT_CHOICE(target_hint, {"auto", -1}, {"no", 0}, {"yes", 1})},
// No `target-lut-type` because we don't support non-RGB targets
{"libplacebo-opts", OPT_KEYVALUELIST(raw_opts)},
{0},
},
.defaults = &(struct gl_next_opts) {
.border_background = BACKGROUND_COLOR,
.inter_preserve = true,
.target_hint = true,
.target_hint = -1,
},
.size = sizeof(struct gl_next_opts),
.change_flags = UPDATE_VIDEO,
Expand Down Expand Up @@ -772,13 +772,15 @@ static void update_options(struct vo *vo)
pl_options_set_str(pars, kv[0], kv[1]);
}

static void apply_target_contrast(struct priv *p, struct pl_color_space *color)
static void apply_target_contrast(struct priv *p, struct pl_color_space *color, float min_luma)
{
const struct gl_video_opts *opts = p->opts_cache->opts;

// Auto mode, leave as is
if (!opts->target_contrast)
// Auto mode, use target value if available
if (!opts->target_contrast) {
color->hdr.min_luma = min_luma;
return;
}

// Infinite contrast
if (opts->target_contrast == -1) {
Expand All @@ -798,7 +800,8 @@ static void apply_target_contrast(struct priv *p, struct pl_color_space *color)
color->hdr.min_luma = color->hdr.max_luma / opts->target_contrast;
}

static void apply_target_options(struct priv *p, struct pl_frame *target)
static void apply_target_options(struct priv *p, struct pl_frame *target,
float target_peak, float min_luma)
{
update_lut(p, &p->next_opts->target_lut);
target->lut = p->next_opts->target_lut.lut;
Expand All @@ -813,10 +816,10 @@ static void apply_target_options(struct priv *p, struct pl_frame *target)
if (opts->target_trc)
target->color.transfer = opts->target_trc;
// If swapchain returned a value use this, override is used in hint
if (opts->target_peak && !target->color.hdr.max_luma)
target->color.hdr.max_luma = opts->target_peak;
if (target_peak && !target->color.hdr.max_luma)
target->color.hdr.max_luma = target_peak;
if (!target->color.hdr.min_luma)
apply_target_contrast(p, &target->color);
apply_target_contrast(p, &target->color, min_luma);
if (opts->target_gamut) {
// Ensure resulting gamut still fits inside container
const struct pl_raw_primaries *gamut, *container;
Expand Down Expand Up @@ -975,27 +978,43 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
p->last_id = id;
}

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
// 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 (!pl_color_transfer_is_hdr(target_csp.transfer)) {
target_csp.hdr.max_luma = 0;
target_csp.hdr.min_luma = 0;
}

float target_peak = opts->target_peak ? opts->target_peak : target_csp.hdr.max_luma;
struct pl_color_space hint;
if (p->next_opts->target_hint && frame->current) {
bool target_hint = p->next_opts->target_hint == 1 ||
(p->next_opts->target_hint == -1 &&
pl_color_transfer_is_hdr(target_csp.transfer));
if (target_hint && frame->current) {
hint = frame->current->params.color;
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 (opts->target_trc)
hint.transfer = opts->target_trc;
if (opts->target_peak)
hint.hdr.max_luma = opts->target_peak;
apply_target_contrast(p, &hint);
if (target_peak)
hint.hdr.max_luma = target_peak;
apply_target_contrast(p, &hint, target_csp.hdr.min_luma);
if (!pass_colorspace)
pl_swapchain_colorspace_hint(p->sw, &hint);
} else if (!p->next_opts->target_hint) {
} else if (!target_hint) {
pl_swapchain_colorspace_hint(p->sw, NULL);
}

struct pl_swapchain_frame swframe;
struct ra_swapchain *sw = p->ra_ctx->swapchain;
bool should_draw = sw->fns->start_frame(sw, NULL); // for wayland logic
if (!should_draw || !pl_swapchain_start_frame(p->sw, &swframe)) {
if (frame->current) {
Expand All @@ -1019,7 +1038,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
// Calculate target
struct pl_frame target;
pl_frame_from_swapchain(&target, &swframe);
apply_target_options(p, &target);
apply_target_options(p, &target, target_peak, target_csp.hdr.min_luma);
update_overlays(vo, p->osd_res,
(frame->current && opts->blend_subs) ? OSD_DRAW_OSD_ONLY : 0,
PL_OVERLAY_COORDS_DST_FRAME, &p->osd_state, &target, frame->current);
Expand Down Expand Up @@ -1391,9 +1410,10 @@ static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args)
},
};

const struct gl_video_opts *opts = p->opts_cache->opts;
if (args->scaled) {
// Apply target LUT, ICC profile and CSP override only in window mode
apply_target_options(p, &target);
apply_target_options(p, &target, opts->target_peak, 0);
} else if (args->native_csp) {
target.color = image.color;
} else {
Expand All @@ -1410,7 +1430,6 @@ static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args)
if (!args->osd)
osd_flags |= OSD_DRAW_SUB_ONLY;

const struct gl_video_opts *opts = p->opts_cache->opts;
struct frame_priv *fp = mpi->priv;
if (opts->blend_subs) {
float rx = pl_rect_w(dst) / pl_rect_w(image.crop);
Expand Down