summaryrefslogtreecommitdiffstats
path: root/video/out
diff options
context:
space:
mode:
authorNiklas Haas <git@haasn.xyz>2017-06-14 20:06:56 +0200
committerwm4 <wm4@nowhere>2017-06-18 20:54:44 +0200
commit1f3000b03c5ca8208858634ba57833d7dbb009cc (patch)
tree9bc913987fed190f4425030cc360248616b56e6b /video/out
parentfe1227883a865b845c0c271bafd281f288404acb (diff)
downloadmpv-1f3000b03c5ca8208858634ba57833d7dbb009cc.tar.bz2
mpv-1f3000b03c5ca8208858634ba57833d7dbb009cc.tar.xz
vo_opengl: implement support for OOTFs and non-display referred content
This introduces (yet another..) mp_colorspace members, an enum `light` (for lack of a better name) which basically tells us whether we're dealing with scene-referred or display-referred light, but also a bit more metadata (in which way is the scene-referred light expected to be mapped to the display?). The addition of this parameter accomplishes two goals: 1. Allows us to actually support HLG more-or-less correctly[1] 2. Allows people playing back direct “camera” content (e.g. v-log or s-log2) to treat it as scene-referred instead of display-referred [1] Even better would be to use the display-referred OOTF instead of the idealized OOTF, but this would require either native HLG support in LittleCMS (unlikely) or more communication between lcms.c and video_shaders.c than I'm remotely comfortable with That being said, in principle we could switch our usage of the BT.1886 EOTF to the BT.709 OETF instead and treat BT.709 content as being scene-referred under application of the 709+1886 OOTF; which moves that particular conversion from the 3dlut to the shader code; but also allows a) users like UliZappe to turn it off and b) supporting the full HLG OOTF in the same framework. But I think I prefer things as they are right now.
Diffstat (limited to 'video/out')
-rw-r--r--video/out/opengl/video.c2
-rw-r--r--video/out/opengl/video_shaders.c90
-rw-r--r--video/out/opengl/video_shaders.h2
3 files changed, 86 insertions, 8 deletions
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index cdb4383011..541e58a534 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -2056,6 +2056,7 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
struct mp_colorspace dst = {
.gamma = p->opts.target_trc,
.primaries = p->opts.target_prim,
+ .light = MP_CSP_LIGHT_DISPLAY,
};
if (p->use_lut_3d) {
@@ -2265,6 +2266,7 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts,
static const struct mp_colorspace csp_srgb = {
.primaries = MP_CSP_PRIM_BT_709,
.gamma = MP_CSP_TRC_SRGB,
+ .light = MP_CSP_LIGHT_DISPLAY,
};
pass_colormanage(p, csp_srgb, true);
diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c
index 14120d434b..f56c687903 100644
--- a/video/out/opengl/video_shaders.c
+++ b/video/out/opengl/video_shaders.c
@@ -368,6 +368,81 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
}
}
+// Apply the OOTF mapping from a given light type to display-referred light.
+// The extra peak parameter is used to scale the values before and after
+// the OOTF, and can be inferred using mp_trc_nom_peak
+void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak)
+{
+ if (light == MP_CSP_LIGHT_DISPLAY)
+ return;
+
+ GLSLF("// apply ootf\n", sc);
+ GLSLF("color.rgb *= vec3(%f);\n", peak);
+
+ switch (light)
+ {
+ case MP_CSP_LIGHT_SCENE_HLG:
+ // HLG OOTF from BT.2100, assuming a reference display with a
+ // peak of 1000 cd/m² -> gamma = 1.2
+ GLSL(float luma = dot(color.rgb, vec3(0.2627, 0.6780, 0.0593));)
+ GLSLF("color.rgb *= vec3(%f * pow(luma, 0.2));\n",
+ (1000 / MP_REF_WHITE) / pow(12, 1.2));
+ break;
+ case MP_CSP_LIGHT_SCENE_709_1886:
+ // This OOTF is defined by encoding the result as 709 and then decoding
+ // it as 1886; although this is called 709_1886 we actually use the
+ // more precise (by one decimal) values from BT.2020 instead
+ GLSL(color.rgb = mix(color.rgb * vec3(4.5),
+ vec3(1.0993) * pow(color.rgb, vec3(0.45)) - vec3(0.0993),
+ lessThan(vec3(0.0181), color.rgb));)
+ GLSL(color.rgb = pow(color.rgb, vec3(2.4));)
+ break;
+ case MP_CSP_LIGHT_SCENE_1_2:
+ GLSL(color.rgb = pow(color.rgb, vec3(1.2));)
+ break;
+ default:
+ abort();
+ }
+
+ GLSLF("color.rgb /= vec3(%f);\n", peak);
+}
+
+// Inverse of the function pass_ootf, for completeness' sake. Note that the
+// inverse OOTF for MP_CSP_LIGHT_SCENE_HLG has no analytical solution and is
+// therefore unimplemented. Care must be used to never call this function
+// in that way.(In principle, a iterative algorithm can approach
+// the solution numerically, but this is tricky and we don't really need it
+// since mpv currently only supports outputting display-referred light)
+void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak)
+{
+ if (light == MP_CSP_LIGHT_DISPLAY)
+ return;
+
+ GLSLF("// apply inverse ootf\n");
+ GLSLF("color.rgb *= vec3(%f);\n", peak);
+
+ switch (light)
+ {
+ case MP_CSP_LIGHT_SCENE_HLG:
+ // Has no analytical solution
+ abort();
+ break;
+ case MP_CSP_LIGHT_SCENE_709_1886:
+ GLSL(color.rgb = pow(color.rgb, vec3(1/2.4));)
+ GLSL(color.rgb = mix(color.rgb / vec3(4.5),
+ pow((color.rgb + vec3(0.0993)) / vec3(1.0993), vec3(1/0.45)),
+ lessThan(vec3(0.08145), color.rgb));)
+ break;
+ case MP_CSP_LIGHT_SCENE_1_2:
+ GLSL(color.rgb = pow(color.rgb, vec3(1/1.2));)
+ break;
+ default:
+ abort();
+ }
+
+ GLSLF("color.rgb /= vec3(%f);\n", peak);
+}
+
// Tone map from a known peak brightness to the range [0,1]
static void pass_tone_map(struct gl_shader_cache *sc, float ref_peak,
enum tone_mapping algo, float param)
@@ -452,20 +527,16 @@ void pass_color_map(struct gl_shader_cache *sc,
bool need_gamma = src.gamma != dst.gamma ||
src.primaries != dst.primaries ||
src_range != dst_range ||
- src.sig_peak > dst_range;
+ src.sig_peak > dst_range ||
+ src.light != dst.light;
if (need_gamma && !is_linear) {
pass_linearize(sc, src.gamma);
is_linear= true;
}
- // NOTE: When src.gamma = MP_CSP_TRC_ARIB_STD_B67, we would technically
- // need to apply the reference OOTF as part of the EOTF (which is what we
- // implement with pass_linearize), since HLG considers OOTF to be part of
- // the display's EOTF (as opposed to the camera's OETF) - although arguably
- // in our case this would be part of the ICC profile, not mpv. Either way,
- // in case somebody ends up complaining about HLG looking different from a
- // reference HLG display, this comment might be why.
+ if (src.light != dst.light)
+ pass_ootf(sc, src.light, mp_trc_nom_peak(src.gamma));
// Rescale the signal to compensate for differences in the encoding range
// and reference white level. This is necessary because of how mpv encodes
@@ -490,6 +561,9 @@ void pass_color_map(struct gl_shader_cache *sc,
GLSL(color.rgb = cms_matrix * color.rgb;)
}
+ if (src.light != dst.light)
+ pass_inverse_ootf(sc, dst.light, mp_trc_nom_peak(dst.gamma));
+
if (is_linear)
pass_delinearize(sc, dst.gamma);
}
diff --git a/video/out/opengl/video_shaders.h b/video/out/opengl/video_shaders.h
index 076a835754..207824b169 100644
--- a/video/out/opengl/video_shaders.h
+++ b/video/out/opengl/video_shaders.h
@@ -37,6 +37,8 @@ void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler,
void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
+void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak);
+void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak);
void pass_color_map(struct gl_shader_cache *sc,
struct mp_colorspace src, struct mp_colorspace dst,