mirror of
https://github.com/mpv-player/mpv.git
synced 2025-12-28 05:33:14 +00:00
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.
This commit is contained in:
@@ -1057,9 +1057,38 @@ Available video output drivers are:
|
|||||||
|
|
||||||
``target-brightness=<1..100000>``
|
``target-brightness=<1..100000>``
|
||||||
Specifies the display's approximate brightness in cd/m^2. When playing
|
Specifies the display's approximate brightness in cd/m^2. When playing
|
||||||
HDR content, video colors will be scaled and clipped to this
|
HDR content, video colors will be tone mapped to this target brightness
|
||||||
brightness. The default of 250 cd/m^2 corresponds to a typical consumer
|
using the algorithm specified by ``hdr-tone-mapping``. The default of
|
||||||
display.
|
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>``
|
``icc-profile=<file>``
|
||||||
Load an ICC profile and use it to transform video RGB to screen output.
|
Load an ICC profile and use it to transform video RGB to screen output.
|
||||||
|
|||||||
@@ -321,6 +321,7 @@ const struct gl_video_opts gl_video_opts_def = {
|
|||||||
.prescale_passes = 1,
|
.prescale_passes = 1,
|
||||||
.prescale_downscaling_threshold = 2.0f,
|
.prescale_downscaling_threshold = 2.0f,
|
||||||
.target_brightness = 250,
|
.target_brightness = 250,
|
||||||
|
.tone_mapping_param = NAN,
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct gl_video_opts gl_video_opts_hq_def = {
|
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_passes = 1,
|
||||||
.prescale_downscaling_threshold = 2.0f,
|
.prescale_downscaling_threshold = 2.0f,
|
||||||
.target_brightness = 250,
|
.target_brightness = 250,
|
||||||
|
.tone_mapping_param = NAN,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
|
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-prim", target_prim, 0, mp_csp_prim_names),
|
||||||
OPT_CHOICE_C("target-trc", target_trc, 0, mp_csp_trc_names),
|
OPT_CHOICE_C("target-trc", target_trc, 0, mp_csp_trc_names),
|
||||||
OPT_INTRANGE("target-brightness", target_brightness, 0, 1, 100000),
|
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),
|
OPT_FLAG("pbo", pbo, 0),
|
||||||
SCALER_OPTS("scale", SCALER_SCALE),
|
SCALER_OPTS("scale", SCALER_SCALE),
|
||||||
SCALER_OPTS("dscale", SCALER_DSCALE),
|
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);
|
pass_linearize(p->sc, trc_src);
|
||||||
|
|
||||||
// For HDR, the assumption of reference brightness = display brightness
|
// For HDR, the assumption of reference brightness = display brightness
|
||||||
// is discontinued. Instead, we have to rescale the brightness to match
|
// is discontinued. Instead, we have to tone map the brightness to
|
||||||
// the display (and clip out-of-range values)
|
// the display using some algorithm.
|
||||||
if (p->image_params.gamma == MP_CSP_TRC_SMPTE_ST2084 && !display_scaled) {
|
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
|
int reference_brightness = 10000; // As per SMPTE ST.2084
|
||||||
GLSLF("color.rgb = clamp(%f * color.rgb, 0.0, 1.0);\n",
|
pass_tone_map(p->sc, reference_brightness, p->opts.target_brightness,
|
||||||
(float)reference_brightness / p->opts.target_brightness);
|
p->opts.hdr_tone_mapping, p->opts.tone_mapping_param);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapt to the right colorspace if necessary
|
// Adapt to the right colorspace if necessary
|
||||||
|
|||||||
@@ -103,6 +103,13 @@ enum prescalers {
|
|||||||
PRESCALE_NNEDI3,
|
PRESCALE_NNEDI3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum tone_mapping {
|
||||||
|
TONE_MAPPING_CLIP,
|
||||||
|
TONE_MAPPING_SIMPLE,
|
||||||
|
TONE_MAPPING_GAMMA,
|
||||||
|
TONE_MAPPING_LINEAR,
|
||||||
|
};
|
||||||
|
|
||||||
struct gl_video_opts {
|
struct gl_video_opts {
|
||||||
int dumb_mode;
|
int dumb_mode;
|
||||||
struct scaler_config scaler[4];
|
struct scaler_config scaler[4];
|
||||||
@@ -112,6 +119,8 @@ struct gl_video_opts {
|
|||||||
int target_prim;
|
int target_prim;
|
||||||
int target_trc;
|
int target_trc;
|
||||||
int target_brightness;
|
int target_brightness;
|
||||||
|
int hdr_tone_mapping;
|
||||||
|
float tone_mapping_param;
|
||||||
int linear_scaling;
|
int linear_scaling;
|
||||||
int correct_downscaling;
|
int correct_downscaling;
|
||||||
int sigmoid_upscaling;
|
int sigmoid_upscaling;
|
||||||
|
|||||||
@@ -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.
|
// 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
|
// Obtain random numbers by calling rand(h), followed by h = permute(h) to
|
||||||
// update the state. Assumes the texture was hooked.
|
// update the state. Assumes the texture was hooked.
|
||||||
|
|||||||
@@ -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_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_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,
|
void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
|
||||||
AVLFG *lfg);
|
AVLFG *lfg);
|
||||||
|
|||||||
Reference in New Issue
Block a user