Skip to content

Commit

Permalink
vo_opengl: implement more HDR tonemapping algorithms
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
haasn committed May 16, 2016
1 parent 3cfe98c commit e047cc0
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 7 deletions.
35 changes: 32 additions & 3 deletions DOCS/man/vo.rst
Expand Up @@ -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.
Expand Down
17 changes: 13 additions & 4 deletions video/out/opengl/video.c
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions video/out/opengl/video.h
Expand Up @@ -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];
Expand All @@ -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;
Expand Down
40 changes: 40 additions & 0 deletions video/out/opengl/video_shaders.c
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions video/out/opengl/video_shaders.h
Expand Up @@ -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);
Expand Down

0 comments on commit e047cc0

Please sign in to comment.