summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiklas Haas <git@nand.wakku.to>2016-05-16 02:44:30 +0200
committerNiklas Haas <git@nand.wakku.to>2016-05-16 02:49:49 +0200
commite047cc0931a22d277d7ccd14588f905d7852f7e0 (patch)
tree6ad6d88295bf3b7ab045ce6befadd063bec84c81
parent3cfe98c6848d888e06e5d402f9100d55ab09d755 (diff)
downloadmpv-e047cc0931a22d277d7ccd14588f905d7852f7e0.tar.bz2
mpv-e047cc0931a22d277d7ccd14588f905d7852f7e0.tar.xz
vo_opengl: implement more HDR tonemapping algorithms
This is now a configurable option, with tunable parameters. I got inspiration for these algorithms off wikipedia. "simple" seems to work pretty well, but not well enough to make it a reasonable default. Some other notable candidates: - Local functions (e.g. based on local contrast or gradient) - Clamp with soft knee (linear up to a point) - Mapping in CIE L*Ch. Map L smoothly, clamp C and h. - Color appearance models These will have to be implemented some other time. Note that the parameter "peak_src" to pass_tone_map should, in principle, be auto-detected from the SEI information of the source file where available. This will also have to be implemented in a later commit.
-rw-r--r--DOCS/man/vo.rst35
-rw-r--r--video/out/opengl/video.c17
-rw-r--r--video/out/opengl/video.h9
-rw-r--r--video/out/opengl/video_shaders.c40
-rw-r--r--video/out/opengl/video_shaders.h2
5 files changed, 96 insertions, 7 deletions
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index 046fa7e0e6..7792831dd5 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -1057,9 +1057,38 @@ Available video output drivers are:
``target-brightness=<1..100000>``
Specifies the display's approximate brightness in cd/m^2. When playing
- HDR content, video colors will be scaled and clipped to this
- brightness. The default of 250 cd/m^2 corresponds to a typical consumer
- display.
+ HDR content, video colors will be tone mapped to this target brightness
+ using the algorithm specified by ``hdr-tone-mapping``. The default of
+ 250 cd/m^2 corresponds to a typical consumer display.
+
+ ``hdr-tone-mapping=<value>``
+ Specifies the algorithm used for tone-mapping HDR images onto the
+ target display. Valid values are:
+
+ clip
+ Hard-clip any out-of-range values (default)
+ simple
+ Very simple continuous curve. Preserves dynamic range and peak but
+ uses nonlinear contrast.
+ gamma
+ Fits a logarithmic transfer between the tone curves.
+ linear
+ Linearly stretches the entire reference gamut to (a linear multiple
+ of) the display.
+
+ ``tone-mapping-param=<value>``
+ Set tone mapping parameters. Ignored if the tone mapping algorithm is
+ not tunable. This affects the following tone mapping algorithms:
+
+ simple
+ Specifies the local contrast coefficient at the display peak.
+ Defaults to 0.5, which means that in-gamut values will be about
+ half as bright as when clipping.
+ gamma
+ Specifies the exponent of the function. Defaults to 1.8.
+ linear
+ Specifies the scale factor to use while stretching. Defaults to
+ 1.0.
``icc-profile=<file>``
Load an ICC profile and use it to transform video RGB to screen output.
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index 7470f9668d..7224e1af22 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -321,6 +321,7 @@ const struct gl_video_opts gl_video_opts_def = {
.prescale_passes = 1,
.prescale_downscaling_threshold = 2.0f,
.target_brightness = 250,
+ .tone_mapping_param = NAN,
};
const struct gl_video_opts gl_video_opts_hq_def = {
@@ -350,6 +351,7 @@ const struct gl_video_opts gl_video_opts_hq_def = {
.prescale_passes = 1,
.prescale_downscaling_threshold = 2.0f,
.target_brightness = 250,
+ .tone_mapping_param = NAN,
};
static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
@@ -379,6 +381,12 @@ const struct m_sub_options gl_video_conf = {
OPT_CHOICE_C("target-prim", target_prim, 0, mp_csp_prim_names),
OPT_CHOICE_C("target-trc", target_trc, 0, mp_csp_trc_names),
OPT_INTRANGE("target-brightness", target_brightness, 0, 1, 100000),
+ OPT_CHOICE("hdr-tone-mapping", hdr_tone_mapping, 0,
+ ({"clip", TONE_MAPPING_CLIP},
+ {"simple", TONE_MAPPING_SIMPLE},
+ {"gamma", TONE_MAPPING_GAMMA},
+ {"linear", TONE_MAPPING_LINEAR})),
+ OPT_FLOAT("tone-mapping-param", tone_mapping_param, 0),
OPT_FLAG("pbo", pbo, 0),
SCALER_OPTS("scale", SCALER_SCALE),
SCALER_OPTS("dscale", SCALER_DSCALE),
@@ -2249,12 +2257,13 @@ static void pass_colormanage(struct gl_video *p, bool display_scaled,
pass_linearize(p->sc, trc_src);
// For HDR, the assumption of reference brightness = display brightness
- // is discontinued. Instead, we have to rescale the brightness to match
- // the display (and clip out-of-range values)
+ // is discontinued. Instead, we have to tone map the brightness to
+ // the display using some algorithm.
if (p->image_params.gamma == MP_CSP_TRC_SMPTE_ST2084 && !display_scaled) {
+ GLSLF("// HDR tone mapping\n");
int reference_brightness = 10000; // As per SMPTE ST.2084
- GLSLF("color.rgb = clamp(%f * color.rgb, 0.0, 1.0);\n",
- (float)reference_brightness / p->opts.target_brightness);
+ pass_tone_map(p->sc, reference_brightness, p->opts.target_brightness,
+ p->opts.hdr_tone_mapping, p->opts.tone_mapping_param);
}
// Adapt to the right colorspace if necessary
diff --git a/video/out/opengl/video.h b/video/out/opengl/video.h
index 5d8c938535..01c0cca78b 100644
--- a/video/out/opengl/video.h
+++ b/video/out/opengl/video.h
@@ -103,6 +103,13 @@ enum prescalers {
PRESCALE_NNEDI3,
};
+enum tone_mapping {
+ TONE_MAPPING_CLIP,
+ TONE_MAPPING_SIMPLE,
+ TONE_MAPPING_GAMMA,
+ TONE_MAPPING_LINEAR,
+};
+
struct gl_video_opts {
int dumb_mode;
struct scaler_config scaler[4];
@@ -112,6 +119,8 @@ struct gl_video_opts {
int target_prim;
int target_trc;
int target_brightness;
+ int hdr_tone_mapping;
+ float tone_mapping_param;
int linear_scaling;
int correct_downscaling;
int sigmoid_upscaling;
diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c
index c63f32ca7d..237e1f39d2 100644
--- a/video/out/opengl/video_shaders.c
+++ b/video/out/opengl/video_shaders.c
@@ -313,6 +313,46 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
}
}
+// Tone map from one brightness to another
+void pass_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst,
+ enum tone_mapping algo, float param)
+{
+ // First we renormalize to the output range
+ float scale = peak_src / peak_dst;
+ GLSLF("color.rgb *= vec3(%f);\n", scale);
+
+ // Then we use some algorithm to map back to [0,1]
+ switch (algo) {
+ case TONE_MAPPING_CLIP:
+ GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
+ break;
+
+ case TONE_MAPPING_SIMPLE: {
+ float contrast = isnan(param) ? 0.5 : param,
+ offset = (1.0 - contrast) / contrast;
+ GLSLF("color.rgb = color.rgb / (color.rgb + vec3(%f));\n", offset);
+ GLSLF("color.rgb *= vec3(%f);\n", (scale + offset) / scale);
+ break;
+ }
+
+ case TONE_MAPPING_GAMMA: {
+ float gamma = isnan(param) ? 1.8 : param;
+ GLSLF("color.rgb = pow(color.rgb / vec3(%f), vec3(%f));\n",
+ scale, 1.0/gamma);
+ break;
+ }
+
+ case TONE_MAPPING_LINEAR: {
+ float coeff = isnan(param) ? 1.0 : param;
+ GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / scale);
+ break;
+ }
+
+ default:
+ abort();
+ }
+}
+
// Wide usage friendly PRNG, shamelessly stolen from a GLSL tricks forum post.
// Obtain random numbers by calling rand(h), followed by h = permute(h) to
// update the state. Assumes the texture was hooked.
diff --git a/video/out/opengl/video_shaders.h b/video/out/opengl/video_shaders.h
index 1f4496fbab..e43efadeb4 100644
--- a/video/out/opengl/video_shaders.h
+++ b/video/out/opengl/video_shaders.h
@@ -38,6 +38,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_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst,
+ enum tone_mapping algo, float param);
void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
AVLFG *lfg);