summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/options.rst3
-rw-r--r--video/out/gpu/video.c5
-rw-r--r--video/out/gpu/video.h1
-rw-r--r--video/out/gpu/video_shaders.c59
4 files changed, 61 insertions, 7 deletions
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 4850439a8a..209b857850 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -6058,6 +6058,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
color/brightness accuracy. This is roughly equivalent to
``--tone-mapping=reinhard --tone-mapping-param=0.24``. If possible,
you should also enable ``--hdr-compute-peak`` for the best results.
+ bt.2390
+ Perceptual tone mapping curve (EETF) specified in ITU-R Report BT.2390.
+ This is the recommended curve to use for typical HDR-mastered content.
(Default)
gamma
Fits a logarithmic transfer between the tone curves.
diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c
index 500ec6af22..dedeed7001 100644
--- a/video/out/gpu/video.c
+++ b/video/out/gpu/video.c
@@ -322,7 +322,7 @@ static const struct gl_video_opts gl_video_opts_def = {
.background = {0, 0, 0, 255},
.gamma = 1.0f,
.tone_map = {
- .curve = TONE_MAPPING_HABLE,
+ .curve = TONE_MAPPING_BT_2390,
.curve_param = NAN,
.max_boost = 1.0,
.decay_rate = 100.0,
@@ -382,7 +382,8 @@ const struct m_sub_options gl_video_conf = {
{"reinhard", TONE_MAPPING_REINHARD},
{"hable", TONE_MAPPING_HABLE},
{"gamma", TONE_MAPPING_GAMMA},
- {"linear", TONE_MAPPING_LINEAR})},
+ {"linear", TONE_MAPPING_LINEAR},
+ {"bt.2390", TONE_MAPPING_BT_2390})},
{"hdr-compute-peak", OPT_CHOICE(tone_map.compute_peak,
{"auto", 0},
{"yes", 1},
diff --git a/video/out/gpu/video.h b/video/out/gpu/video.h
index 4c079d463c..4e442a5e6d 100644
--- a/video/out/gpu/video.h
+++ b/video/out/gpu/video.h
@@ -94,6 +94,7 @@ enum tone_mapping {
TONE_MAPPING_HABLE,
TONE_MAPPING_GAMMA,
TONE_MAPPING_LINEAR,
+ TONE_MAPPING_BT_2390,
};
struct gl_tone_map_opts {
diff --git a/video/out/gpu/video_shaders.c b/video/out/gpu/video_shaders.c
index ba7caeaeec..5bc32dcd3b 100644
--- a/video/out/gpu/video_shaders.c
+++ b/video/out/gpu/video_shaders.c
@@ -649,6 +649,15 @@ static void hdr_update_peak(struct gl_shader_cache *sc,
GLSL(})
}
+static inline float pq_delinearize(float x)
+{
+ x *= MP_REF_WHITE / 10000.0;
+ x = powf(x, PQ_M1);
+ x = (PQ_C1 + PQ_C2 * x) / (1.0 + PQ_C3 * x);
+ x = pow(x, PQ_M2);
+ return x;
+}
+
// Tone map from a known peak brightness to the range [0,1]. If ref_peak
// is 0, we will use peak detection instead
static void pass_tone_map(struct gl_shader_cache *sc,
@@ -672,12 +681,18 @@ static void pass_tone_map(struct gl_shader_cache *sc,
GLSLF("vec3 sig = color.rgb;\n");
+ // This function always operates on an absolute scale, so ignore the
+ // dst_peak normalization for it
+ float dst_scale = dst_peak;
+ if (opts->curve == TONE_MAPPING_BT_2390)
+ dst_scale = 1.0;
+
// Rescale the variables in order to bring it into a representation where
// 1.0 represents the dst_peak. This is because all of the tone mapping
// algorithms are defined in such a way that they map to the range [0.0, 1.0].
- if (dst_peak > 1.0) {
- GLSLF("sig *= 1.0/%f;\n", dst_peak);
- GLSLF("sig_peak *= 1.0/%f;\n", dst_peak);
+ if (dst_scale > 1.0) {
+ GLSLF("sig *= 1.0/%f;\n", dst_scale);
+ GLSLF("sig_peak *= 1.0/%f;\n", dst_scale);
}
GLSL(float sig_orig = sig[sig_idx];)
@@ -744,6 +759,40 @@ static void pass_tone_map(struct gl_shader_cache *sc,
break;
}
+ case TONE_MAPPING_BT_2390:
+ // We first need to encode both sig and sig_peak into PQ space
+ GLSLF("vec4 sig_pq = vec4(sig.rgb, sig_peak); \n"
+ "sig_pq *= vec4(1.0/%f); \n"
+ "sig_pq = pow(sig_pq, vec4(%f)); \n"
+ "sig_pq = (vec4(%f) + vec4(%f) * sig_pq) \n"
+ " / (vec4(1.0) + vec4(%f) * sig_pq); \n"
+ "sig_pq = pow(sig_pq, vec4(%f)); \n",
+ 10000.0 / MP_REF_WHITE, PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
+ // Encode both the signal and the target brightness to be relative to
+ // the source peak brightness, and figure out the target peak in this space
+ GLSLF("float scale = 1.0 / sig_pq.a; \n"
+ "sig_pq.rgb *= vec3(scale); \n"
+ "float maxLum = %f * scale; \n",
+ pq_delinearize(dst_peak));
+ // Apply piece-wise hermite spline
+ GLSLF("float ks = 1.5 * maxLum - 0.5; \n"
+ "vec3 tb = (sig_pq.rgb - vec3(ks)) / vec3(1.0 - ks); \n"
+ "vec3 tb2 = tb * tb; \n"
+ "vec3 tb3 = tb2 * tb; \n"
+ "vec3 pb = (2.0 * tb3 - 3.0 * tb2 + vec3(1.0)) * vec3(ks) + \n"
+ " (tb3 - 2.0 * tb2 + tb) * vec3(1.0 - ks) + \n"
+ " (-2.0 * tb3 + 3.0 * tb2) * vec3(maxLum); \n"
+ "sig = mix(pb, sig_pq.rgb, lessThan(sig_pq.rgb, vec3(ks))); \n");
+ // Convert back from PQ space to linear light
+ GLSLF("sig *= vec3(sig_pq.a); \n"
+ "sig = pow(sig, vec3(1.0/%f)); \n"
+ "sig = max(sig - vec3(%f), 0.0) / \n"
+ " (vec3(%f) - vec3(%f) * sig); \n"
+ "sig = pow(sig, vec3(1.0/%f)); \n"
+ "sig *= vec3(%f); \n",
+ PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1, 10000.0 / MP_REF_WHITE);
+ break;
+
default:
abort();
}
@@ -754,11 +803,11 @@ static void pass_tone_map(struct gl_shader_cache *sc,
// Mix between the per-channel tone mapped and the linear tone mapped
// signal based on the desaturation strength
if (opts->desat > 0) {
- float base = 0.18 * dst_peak;
+ float base = 0.18 * dst_scale;
GLSLF("float coeff = max(sig[sig_idx] - %f, 1e-6) / "
" max(sig[sig_idx], 1.0);\n", base);
GLSLF("coeff = %f * pow(coeff, %f);\n", opts->desat, opts->desat_exp);
- GLSLF("color.rgb = mix(sig_lin, %f * sig, coeff);\n", dst_peak);
+ GLSLF("color.rgb = mix(sig_lin, %f * sig, coeff);\n", dst_scale);
} else {
GLSL(color.rgb = sig_lin;)
}