Skip to content

Commit 5f87424

Browse files
committed
vo_gpu_next: linearize sRGB as a pure 2.2 power function
sRGB reference display is defined a 2.2 gamma device. To preserve the look of the sRGB as mastered on such device, linearize it as such. Note that sRGB encoding is piecewise with linear segment, which creates mismatch to pure power 2.2 function, but this is intended to be viewed on such display. See: IEC 61966-2-1-1999 https://community.acescentral.com/t/srgb-piece-wise-eotf-vs-pure-gamma/4024 KhronosGroup/DataFormat#19 https://gitlab.freedesktop.org/pq/color-and-hdr/-/issues/12 https://github.com/dylanraga/win11hdr-srgb-to-gamma2.2-icm
1 parent 532bac0 commit 5f87424

File tree

5 files changed

+64
-0
lines changed

5 files changed

+64
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add `--treat-srgb-as-power22`

DOCS/man/options.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7354,6 +7354,16 @@ them.
73547354
transfer function is SDR. This way you can control SDR output separately
73557355
from HDR output.
73567356

7357+
``--treat-srgb-as-power22=<no|input|output|both|auto>``
7358+
When enabled, sRGB is (de)linearized using a pure power 2.2 curve instead of
7359+
the standard sRGB piecewise transfer function.
7360+
7361+
``auto`` behaves like ``both``, with possible platform-specific adjustments
7362+
to ensure a consistent appearance. Depending on the platform, the sRGB EOTF
7363+
used by the system compositor may differ.
7364+
7365+
The default is ``input``. (Only for ``--vo=gpu-next``)
7366+
73577367
``--tone-mapping=<value>``
73587368
Specifies the algorithm used for tone-mapping images onto the target
73597369
display. This is relevant for both HDR->SDR conversion as well as gamut

video/out/gpu/video.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ static const struct gl_video_opts gl_video_opts_def = {
409409
.early_flush = -1,
410410
.shader_cache = true,
411411
.hwdec_interop = "auto",
412+
.treat_srgb_as_power22 = 1, // input
412413
};
413414

414415
static OPT_STRING_VALIDATE_FUNC(validate_error_diffusion_opt);
@@ -445,6 +446,8 @@ const struct m_sub_options gl_video_conf = {
445446
M_RANGE(10, 10000)},
446447
{"hdr-reference-white", OPT_CHOICE(hdr_reference_white, {"auto", 0}),
447448
M_RANGE(10, 10000)},
449+
{"treat-srgb-as-power22", OPT_CHOICE(treat_srgb_as_power22,
450+
{"no", 0}, {"input", 1}, {"output", 2}, {"both", 1|2}, {"auto", 1|2|4})},
448451
{"target-contrast", OPT_CHOICE(target_contrast, {"auto", 0}, {"inf", -1}),
449452
M_RANGE(10, 10 / PL_COLOR_HDR_BLACK)},
450453
{"target-gamut", OPT_CHOICE_C(target_gamut, pl_csp_prim_names)},

video/out/gpu/video.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ struct gl_video_opts {
139139
int target_trc;
140140
int target_peak;
141141
int hdr_reference_white;
142+
int treat_srgb_as_power22;
142143
int target_contrast;
143144
int target_gamut;
144145
struct gl_tone_map_opts tone_map;

video/out/vo_gpu_next.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,13 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src
675675
if (opts->hdr_reference_white && !pl_color_transfer_is_hdr(frame->color.transfer))
676676
frame->color.hdr.max_luma = opts->hdr_reference_white;
677677

678+
679+
if (opts->treat_srgb_as_power22 & 1 && frame->color.transfer == PL_COLOR_TRC_SRGB) {
680+
// The sRGB EOTF is a pure gamma 2.2 function. See reference display in
681+
// IEC 61966-2-1-1999. Linearize sRGB to display light.
682+
frame->color.transfer = PL_COLOR_TRC_GAMMA22;
683+
}
684+
678685
if (fp->hwdec) {
679686

680687
struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(par.imgfmt);
@@ -1246,6 +1253,37 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame)
12461253
pl_frame_from_swapchain(&target, &swframe);
12471254
bool strict_sw_params = target_hint && !pass_colorspace && p->next_opts->target_hint_strict;
12481255
apply_target_options(p, &target, hint.hdr.min_luma, strict_sw_params);
1256+
if (target.color.transfer == PL_COLOR_TRC_SRGB) {
1257+
// sRGB reference display is pure 2.2 power function, see IEC 61966-2-1-1999.
1258+
if (opts->treat_srgb_as_power22 & 2)
1259+
target.color.transfer = PL_COLOR_TRC_GAMMA22;
1260+
1261+
// TODO: Vulkan on Wayland currently interprets VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
1262+
// in ambiguous way, depending if compositor advertises sRGB support.
1263+
// For now, our default `treat_srgb_as_power22` value, assumes that sRGB
1264+
// is handled as pure gamma 2.2 function, but at the same time, we
1265+
// select sRGB only if it's explicitly requested.
1266+
// There is currently no clear path forward to resolve this ambiguity.
1267+
// Depending how it's resolved in Wayland Protocol, Mesa, things will
1268+
// change.
1269+
// See: <https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/456>
1270+
#ifdef _WIN32
1271+
// Windows uses the sRGB piecewise function. Send piecewise sRGB to
1272+
// Windows in HDR mode so that it can be converted to PQ, the same way
1273+
// as mpv does internally. Note that in SDR mode, even with ACM enabled,
1274+
// Windows assumes the display is sRGB. It doesn't perform gamma
1275+
// conversion, or any conversions would roundtrip back to sRGB.
1276+
// In which case the EOTF depends on the display.
1277+
// Ideally, compositors would agree on how to handle sRGB, but I’ll
1278+
// leave that part of the story for the reader to explore.
1279+
// Note: Older Windows versions, without ACM, were not able to convert
1280+
// sRGB to PQ output. We are not concerned about this case, as it would
1281+
// look wrong anyway.
1282+
bool target_pq = !target_unknown && target_csp.transfer == PL_COLOR_TRC_PQ;
1283+
if (opts->treat_srgb_as_power22 & 4 && target_pq)
1284+
target.color.transfer = PL_COLOR_TRC_SRGB;
1285+
#endif
1286+
}
12491287
update_overlays(vo, p->osd_res,
12501288
(frame->current && opts->blend_subs) ? OSD_DRAW_OSD_ONLY : 0,
12511289
PL_OVERLAY_COORDS_DST_FRAME, &p->osd_state, &target, frame->current);
@@ -1623,6 +1661,17 @@ static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args)
16231661
target.color = pl_color_space_srgb;
16241662
}
16251663

1664+
// sRGB reference display is pure 2.2 power function, see IEC 61966-2-1-1999.
1665+
// Round-trip back to sRGB if the source is also sRGB. In other cases, we
1666+
// use piecewise sRGB transfer function, as this is likely the be expected
1667+
// for file encoding.
1668+
if (opts->treat_srgb_as_power22 & 1 &&
1669+
target.color.transfer == PL_COLOR_TRC_SRGB &&
1670+
mpi->params.color.transfer == PL_COLOR_TRC_SRGB)
1671+
{
1672+
target.color.transfer = PL_COLOR_TRC_GAMMA22;
1673+
}
1674+
16261675
apply_crop(&image, src, mpi->params.w, mpi->params.h);
16271676
apply_crop(&target, dst, fbo->params.w, fbo->params.h);
16281677
update_tm_viz(&pars->color_map_params, &target);

0 commit comments

Comments
 (0)