summaryrefslogtreecommitdiffstats
path: root/video
diff options
context:
space:
mode:
authorJan Ekström <jeebjp@gmail.com>2019-03-11 01:00:27 +0200
committerJan Ekström <jeebjp@gmail.com>2019-03-11 01:00:27 +0200
commit199aabddcc0105b504cc8e0cb240bc3c89288c1a (patch)
treefa7eb297a8e11b5e0d9339196f23918d6a4d0b85 /video
parent7c565547b80fdc2a1bfdf31f9725129bcb16aa9d (diff)
parent1d0349d3b5d9a263251fcb3b0d7e135d4731bfd0 (diff)
downloadmpv-199aabddcc0105b504cc8e0cb240bc3c89288c1a.tar.bz2
mpv-199aabddcc0105b504cc8e0cb240bc3c89288c1a.tar.xz
Merge branch 'master' into pr6360
Manual changes done: * Merged the interface-changes under the already master'd changes. * Moved the hwdec-related option changes to video/decode/vd_lavc.c.
Diffstat (limited to 'video')
-rw-r--r--video/decode/vd_lavc.c4
-rw-r--r--video/out/cocoa-cb/events_view.swift3
-rw-r--r--video/out/cocoa-cb/window.swift5
-rw-r--r--video/out/cocoa_cb_common.swift25
-rw-r--r--video/out/gpu/lcms.c2
-rw-r--r--video/out/gpu/user_shaders.c5
-rw-r--r--video/out/gpu/user_shaders.h2
-rw-r--r--video/out/gpu/video.c130
-rw-r--r--video/out/gpu/video.h20
-rw-r--r--video/out/gpu/video_shaders.c216
-rw-r--r--video/out/gpu/video_shaders.h6
-rw-r--r--video/out/opengl/common.c1
-rw-r--r--video/out/opengl/context_drm_egl.c209
-rw-r--r--video/out/wayland_common.c20
-rw-r--r--video/out/wayland_common.h2
15 files changed, 389 insertions, 261 deletions
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index 17c719b710..97b3fa8b7d 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -140,13 +140,13 @@ const struct m_sub_options vd_lavc_conf = {
.framedrop = AVDISCARD_NONREF,
.dr = 1,
.hwdec_api = HAVE_RPI ? "mmal" : "no",
- .hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video,vp9",
+ .hwdec_codecs = "h264,vc1,hevc,vp9",
},
};
struct hwdec_info {
char name[64];
- char method_name[16]; // non-unique name describing the hwdec method
+ char method_name[24]; // non-unique name describing the hwdec method
const AVCodec *codec; // implemented by this codec
enum AVHWDeviceType lavc_device; // if not NONE, get a hwdevice
bool copying; // if true, outputs sw frames, or copy to sw ourselves
diff --git a/video/out/cocoa-cb/events_view.swift b/video/out/cocoa-cb/events_view.swift
index 5a84d27b2b..667366285e 100644
--- a/video/out/cocoa-cb/events_view.swift
+++ b/video/out/cocoa-cb/events_view.swift
@@ -75,7 +75,8 @@ class EventsView: NSView {
return true
}
} else if types.contains(NSURLPboardType) {
- if let url = pb.propertyList(forType: NSURLPboardType) as? [Any] {
+ if var url = pb.propertyList(forType: NSURLPboardType) as? [String] {
+ url = url.filter{ !$0.isEmpty }
EventsResponder.sharedInstance().handleFilesArray(url)
return true
}
diff --git a/video/out/cocoa-cb/window.swift b/video/out/cocoa-cb/window.swift
index d11706f38b..ad93a9cebd 100644
--- a/video/out/cocoa-cb/window.swift
+++ b/video/out/cocoa-cb/window.swift
@@ -300,6 +300,7 @@ class Window: NSWindow, NSWindowDelegate {
let intermediateFrame = aspectFit(rect: newFrame, in: screen!.frame)
cocoaCB.view.layerContentsPlacement = .scaleProportionallyToFill
hideTitleBar()
+ styleMask.remove(.fullScreen)
setFrame(intermediateFrame, display: true)
NSAnimationContext.runAnimationGroup({ (context) -> Void in
@@ -435,9 +436,7 @@ class Window: NSWindow, NSWindowDelegate {
}
override func setFrame(_ frameRect: NSRect, display flag: Bool) {
- let newFrame = !isAnimating && isInFullscreen ? targetScreen!.frame :
- frameRect
- super.setFrame(newFrame, display: flag)
+ super.setFrame(frameRect, display: flag)
if keepAspect {
contentAspectRatio = unfsContentFrame!.size
diff --git a/video/out/cocoa_cb_common.swift b/video/out/cocoa_cb_common.swift
index 355fa537e1..ae79144d97 100644
--- a/video/out/cocoa_cb_common.swift
+++ b/video/out/cocoa_cb_common.swift
@@ -148,10 +148,9 @@ class CocoaCB: NSObject {
func startDisplayLink(_ vo: UnsafeMutablePointer<vo>) {
let opts: mp_vo_opts = vo.pointee.opts.pointee
let screen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main()
- let displayId = screen!.deviceDescription["NSScreenNumber"] as! UInt32
CVDisplayLinkCreateWithActiveCGDisplays(&link)
- CVDisplayLinkSetCurrentCGDisplay(link!, displayId)
+ CVDisplayLinkSetCurrentCGDisplay(link!, screen!.displayID)
if #available(macOS 10.12, *) {
CVDisplayLinkSetOutputHandler(link!) { link, now, out, inFlags, outFlags -> CVReturn in
self.mpv.reportRenderFlip()
@@ -170,8 +169,7 @@ class CocoaCB: NSObject {
}
func updateDisplaylink() {
- let displayId = UInt32(window.screen!.deviceDescription["NSScreenNumber"] as! Int)
- CVDisplayLinkSetCurrentCGDisplay(link!, displayId)
+ CVDisplayLinkSetCurrentCGDisplay(link!, window.screen!.displayID)
queue.asyncAfter(deadline: DispatchTime.now() + 0.1) {
self.flagEvents(VO_EVENT_WIN_STATE)
@@ -302,9 +300,8 @@ class CocoaCB: NSObject {
var reconfigureCallback: CGDisplayReconfigurationCallBack = { (display, flags, userInfo) in
if flags.contains(.setModeFlag) {
let ccb: CocoaCB = MPVHelper.bridge(ptr: userInfo!)
- let displayID = (ccb.window.screen!.deviceDescription["NSScreenNumber"] as! NSNumber).intValue
- if UInt32(displayID) == display {
- ccb.mpv.sendVerbose("Detected display mode change, updating screen refresh rate\n");
+ if ccb.window.screen!.displayID == display {
+ ccb.mpv.sendVerbose("Detected display mode change, updating screen refresh rate");
ccb.flagEvents(VO_EVENT_WIN_STATE)
}
}
@@ -423,6 +420,20 @@ class CocoaCB: NSObject {
let minimized = data!.assumingMemoryBound(to: Int32.self)
minimized.pointee = ccb.window.isMiniaturized ? VO_WIN_STATE_MINIMIZED : Int32(0)
return VO_TRUE
+ case VOCTRL_GET_DISPLAY_NAMES:
+ let opts: mp_vo_opts = vo!.pointee.opts!.pointee
+ let dnames = data!.assumingMemoryBound(to: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?.self)
+ var array: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? = nil
+ var count: Int32 = 0
+ let screen = ccb.window != nil ? ccb.window.screen :
+ ccb.getScreenBy(id: Int(opts.screen_id)) ??
+ NSScreen.main()
+ let displayName = screen?.displayName ?? "Unknown"
+
+ SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, ta_xstrdup(nil, displayName))
+ SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, nil)
+ dnames.pointee = array
+ return VO_TRUE
case VOCTRL_UPDATE_WINDOW_TITLE:
let titleData = data!.assumingMemoryBound(to: Int8.self)
let title = String(cString: titleData)
diff --git a/video/out/gpu/lcms.c b/video/out/gpu/lcms.c
index bc76db965f..a8f277d3f0 100644
--- a/video/out/gpu/lcms.c
+++ b/video/out/gpu/lcms.c
@@ -83,7 +83,7 @@ const struct m_sub_options mp_icc_conf = {
OPT_FLAG("icc-profile-auto", profile_auto, 0),
OPT_STRING("icc-cache-dir", cache_dir, M_OPT_FILE),
OPT_INT("icc-intent", intent, 0),
- OPT_INTRANGE("icc-contrast", contrast, 0, 0, 1000000),
+ OPT_CHOICE_OR_INT("icc-contrast", contrast, 0, 0, 1000000, ({"inf", -1})),
OPT_STRING_VALIDATE("icc-3dlut-size", size_str, 0, validate_3dlut_size_opt),
OPT_REPLACED("3dlut-size", "icc-3dlut-size"),
diff --git a/video/out/gpu/user_shaders.c b/video/out/gpu/user_shaders.c
index 446941b03f..0613eb93f6 100644
--- a/video/out/gpu/user_shaders.c
+++ b/video/out/gpu/user_shaders.c
@@ -16,6 +16,7 @@
*/
#include <assert.h>
+#include <math.h>
#include "common/msg.h"
#include "misc/ctype.h"
@@ -52,9 +53,11 @@ static bool parse_rpn_szexpr(struct bstr line, struct szexp out[MAX_SZEXP_SIZE])
case '-': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_SUB; continue;
case '*': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_MUL; continue;
case '/': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_DIV; continue;
+ case '%': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_MOD; continue;
case '!': exp->tag = SZEXP_OP1; exp->val.op = SZEXP_OP_NOT; continue;
case '>': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_GT; continue;
case '<': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_LT; continue;
+ case '=': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_EQ; continue;
}
if (mp_isdigit(word.start[0])) {
@@ -118,8 +121,10 @@ bool eval_szexpr(struct mp_log *log, void *priv,
case SZEXP_OP_SUB: res = op1 - op2; break;
case SZEXP_OP_MUL: res = op1 * op2; break;
case SZEXP_OP_DIV: res = op1 / op2; break;
+ case SZEXP_OP_MOD: res = fmodf(op1, op2); break;
case SZEXP_OP_GT: res = op1 > op2; break;
case SZEXP_OP_LT: res = op1 < op2; break;
+ case SZEXP_OP_EQ: res = op1 == op2; break;
default: abort();
}
diff --git a/video/out/gpu/user_shaders.h b/video/out/gpu/user_shaders.h
index 8d8cc6bde0..a477e3ce3d 100644
--- a/video/out/gpu/user_shaders.h
+++ b/video/out/gpu/user_shaders.h
@@ -30,9 +30,11 @@ enum szexp_op {
SZEXP_OP_SUB,
SZEXP_OP_MUL,
SZEXP_OP_DIV,
+ SZEXP_OP_MOD,
SZEXP_OP_NOT,
SZEXP_OP_GT,
SZEXP_OP_LT,
+ SZEXP_OP_EQ,
};
enum szexp_tag {
diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c
index 13e5b06918..6004a0ab60 100644
--- a/video/out/gpu/video.c
+++ b/video/out/gpu/video.c
@@ -313,9 +313,16 @@ static const struct gl_video_opts gl_video_opts_def = {
.alpha_mode = ALPHA_BLEND_TILES,
.background = {0, 0, 0, 255},
.gamma = 1.0f,
- .tone_mapping = TONE_MAPPING_HABLE,
- .tone_mapping_param = NAN,
- .tone_mapping_desat = 0.5,
+ .tone_map = {
+ .curve = TONE_MAPPING_HABLE,
+ .curve_param = NAN,
+ .max_boost = 1.0,
+ .decay_rate = 100.0,
+ .scene_threshold_low = 5.5,
+ .scene_threshold_high = 10.0,
+ .desat = 0.75,
+ .desat_exp = 1.5,
+ },
.early_flush = -1,
.hwdec_interop = "auto",
};
@@ -351,21 +358,30 @@ const struct m_sub_options gl_video_conf = {
OPT_FLAG("gamma-auto", gamma_auto, 0),
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-peak", target_peak, 0, 10, 10000),
- OPT_CHOICE("tone-mapping", tone_mapping, 0,
+ OPT_CHOICE_OR_INT("target-peak", target_peak, 0, 10, 10000,
+ ({"auto", 0})),
+ OPT_CHOICE("tone-mapping", tone_map.curve, 0,
({"clip", TONE_MAPPING_CLIP},
{"mobius", TONE_MAPPING_MOBIUS},
{"reinhard", TONE_MAPPING_REINHARD},
{"hable", TONE_MAPPING_HABLE},
{"gamma", TONE_MAPPING_GAMMA},
{"linear", TONE_MAPPING_LINEAR})),
- OPT_CHOICE("hdr-compute-peak", compute_hdr_peak, 0,
+ OPT_CHOICE("hdr-compute-peak", tone_map.compute_peak, 0,
({"auto", 0},
{"yes", 1},
{"no", -1})),
- OPT_FLOAT("tone-mapping-param", tone_mapping_param, 0),
- OPT_FLOAT("tone-mapping-desaturate", tone_mapping_desat, 0),
- OPT_FLAG("gamut-warning", gamut_warning, 0),
+ OPT_FLOATRANGE("hdr-peak-decay-rate", tone_map.decay_rate, 0, 1.0, 1000.0),
+ OPT_FLOATRANGE("hdr-scene-threshold-low",
+ tone_map.scene_threshold_low, 0, 0, 20.0),
+ OPT_FLOATRANGE("hdr-scene-threshold-high",
+ tone_map.scene_threshold_high, 0, 0, 20.0),
+ OPT_FLOAT("tone-mapping-param", tone_map.curve_param, 0),
+ OPT_FLOATRANGE("tone-mapping-max-boost", tone_map.max_boost, 0, 1.0, 10.0),
+ OPT_FLOAT("tone-mapping-desaturate", tone_map.desat, 0),
+ OPT_FLOATRANGE("tone-mapping-desaturate-exponent",
+ tone_map.desat_exp, 0, 0.0, 20.0),
+ OPT_FLAG("gamut-warning", tone_map.gamut_warning, 0),
OPT_FLAG("opengl-pbo", pbo, 0),
SCALER_OPTS("scale", SCALER_SCALE),
SCALER_OPTS("dscale", SCALER_DSCALE),
@@ -2056,6 +2072,23 @@ static void pass_read_video(struct gl_video *p)
}
}
+ // The basic idea is we assume the rgb/luma texture is the "reference" and
+ // scale everything else to match, after all planes are finalized.
+ // We find the reference texture first, in order to maintain texture offset
+ // between hooks on different type of planes.
+ int reference_tex_num = 0;
+ for (int n = 0; n < 4; n++) {
+ switch (img[n].type) {
+ case PLANE_RGB:
+ case PLANE_XYZ:
+ case PLANE_LUMA: break;
+ default: continue;
+ }
+
+ reference_tex_num = n;
+ break;
+ }
+
// Dispatch the hooks for all of these textures, saving and perhaps
// modifying them in the process
for (int n = 0; n < 4; n++) {
@@ -2070,26 +2103,18 @@ static void pass_read_video(struct gl_video *p)
}
img[n] = pass_hook(p, name, img[n], &offsets[n]);
+
+ if (reference_tex_num == n) {
+ // The reference texture is finalized now.
+ p->texture_w = img[n].w;
+ p->texture_h = img[n].h;
+ p->texture_offset = offsets[n];
+ }
}
// At this point all planes are finalized but they may not be at the
// required size yet. Furthermore, they may have texture offsets that
- // require realignment. For lack of something better to do, we assume
- // the rgb/luma texture is the "reference" and scale everything else
- // to match.
- for (int n = 0; n < 4; n++) {
- switch (img[n].type) {
- case PLANE_RGB:
- case PLANE_XYZ:
- case PLANE_LUMA: break;
- default: continue;
- }
-
- p->texture_w = img[n].w;
- p->texture_h = img[n].h;
- p->texture_offset = offsets[n];
- break;
- }
+ // require realignment.
// Compute the reference rect
struct mp_rect_f src = {0.0, 0.0, p->image_params.w, p->image_params.h};
@@ -2365,6 +2390,7 @@ static void pass_scale_main(struct gl_video *p)
// values at 1 and 0, and then scale/shift them, respectively.
sig_offset = 1.0/(1+expf(sig_slope * sig_center));
sig_scale = 1.0/(1+expf(sig_slope * (sig_center-1))) - sig_offset;
+ GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
GLSLF("color.rgb = %f - log(1.0/(color.rgb * %f + %f) - 1.0) * 1.0/%f;\n",
sig_center, sig_scale, sig_offset, sig_slope);
pass_opt_hook_point(p, "SIGMOID", NULL);
@@ -2392,6 +2418,7 @@ static void pass_scale_main(struct gl_video *p)
GLSLF("// scaler post-conversion\n");
if (use_sigmoid) {
// Inverse of the transformation above
+ GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
GLSLF("color.rgb = (1.0/(1.0 + exp(%f * (%f - color.rgb))) - %f) * 1.0/%f;\n",
sig_slope, sig_center, sig_offset, sig_scale);
}
@@ -2471,16 +2498,16 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
if (!dst.sig_peak)
dst.sig_peak = mp_trc_nom_peak(dst.gamma);
- bool detect_peak = p->opts.compute_hdr_peak >= 0 && mp_trc_is_hdr(src.gamma);
+ struct gl_tone_map_opts tone_map = p->opts.tone_map;
+ bool detect_peak = tone_map.compute_peak >= 0 && mp_trc_is_hdr(src.gamma)
+ && src.sig_peak > dst.sig_peak;
+
if (detect_peak && !p->hdr_peak_ssbo) {
struct {
+ float average[2];
+ int32_t frame_sum;
+ uint32_t frame_max;
uint32_t counter;
- uint32_t frame_idx;
- uint32_t frame_num;
- uint32_t frame_max[PEAK_DETECT_FRAMES+1];
- uint32_t frame_sum[PEAK_DETECT_FRAMES+1];
- uint32_t total_max;
- uint32_t total_sum;
} peak_ssbo = {0};
struct ra_buf_params params = {
@@ -2492,8 +2519,8 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
p->hdr_peak_ssbo = ra_buf_create(ra, &params);
if (!p->hdr_peak_ssbo) {
MP_WARN(p, "Failed to create HDR peak detection SSBO, disabling.\n");
+ tone_map.compute_peak = p->opts.tone_map.compute_peak = -1;
detect_peak = false;
- p->opts.compute_hdr_peak = -1;
}
}
@@ -2501,22 +2528,15 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
pass_describe(p, "detect HDR peak");
pass_is_compute(p, 8, 8, true); // 8x8 is good for performance
gl_sc_ssbo(p->sc, "PeakDetect", p->hdr_peak_ssbo,
+ "vec2 average;"
+ "int frame_sum;"
+ "uint frame_max;"
"uint counter;"
- "uint frame_idx;"
- "uint frame_num;"
- "uint frame_max[%d];"
- "uint frame_avg[%d];"
- "uint total_max;"
- "uint total_avg;",
- PEAK_DETECT_FRAMES + 1,
- PEAK_DETECT_FRAMES + 1
);
}
// Adapt from src to dst as necessary
- pass_color_map(p->sc, src, dst, p->opts.tone_mapping,
- p->opts.tone_mapping_param, p->opts.tone_mapping_desat,
- detect_peak, p->opts.gamut_warning, p->use_linear && !osd);
+ pass_color_map(p->sc, p->use_linear && !osd, src, dst, &tone_map);
if (p->use_lut_3d) {
gl_sc_uniform_texture(p->sc, "lut_3d", p->lut_3d_texture);
@@ -3502,9 +3522,9 @@ static bool check_dumb_mode(struct gl_video *p)
return false;
// otherwise, use auto-detection
- if (o->target_prim || o->target_trc || o->correct_downscaling ||
- o->linear_downscaling || o->linear_upscaling || o->sigmoid_upscaling ||
- o->interpolation || o->blend_subs || o->deband || o->unsharp)
+ if (o->correct_downscaling || o->linear_downscaling ||
+ o->linear_upscaling || o->sigmoid_upscaling || o->interpolation ||
+ o->blend_subs || o->deband || o->unsharp)
return false;
// check remaining scalers (tscale is already implicitly excluded above)
for (int i = 0; i < SCALER_COUNT; i++) {
@@ -3516,8 +3536,6 @@ static bool check_dumb_mode(struct gl_video *p)
}
if (o->user_shaders && o->user_shaders[0])
return false;
- if (p->use_lut_3d)
- return false;
return true;
}
@@ -3582,12 +3600,12 @@ static void check_gl_features(struct gl_video *p)
}
bool have_compute_peak = have_compute && have_ssbo;
- if (!have_compute_peak && p->opts.compute_hdr_peak >= 0) {
- int msgl = p->opts.compute_hdr_peak == 1 ? MSGL_WARN : MSGL_V;
+ if (!have_compute_peak && p->opts.tone_map.compute_peak >= 0) {
+ int msgl = p->opts.tone_map.compute_peak == 1 ? MSGL_WARN : MSGL_V;
MP_MSG(p, msgl, "Disabling HDR peak computation (one or more of the "
"following is not supported: compute shaders=%d, "
"SSBO=%d).\n", have_compute, have_ssbo);
- p->opts.compute_hdr_peak = -1;
+ p->opts.tone_map.compute_peak = -1;
}
p->forced_dumb_mode = p->opts.dumb_mode > 0 || !have_fbo || !have_texrg;
@@ -3609,7 +3627,6 @@ static void check_gl_features(struct gl_video *p)
.alpha_mode = p->opts.alpha_mode,
.use_rectangle = p->opts.use_rectangle,
.background = p->opts.background,
- .compute_hdr_peak = p->opts.compute_hdr_peak,
.dither_algo = p->opts.dither_algo,
.dither_depth = p->opts.dither_depth,
.dither_size = p->opts.dither_size,
@@ -3617,12 +3634,13 @@ static void check_gl_features(struct gl_video *p)
.temporal_dither_period = p->opts.temporal_dither_period,
.tex_pad_x = p->opts.tex_pad_x,
.tex_pad_y = p->opts.tex_pad_y,
- .tone_mapping = p->opts.tone_mapping,
- .tone_mapping_param = p->opts.tone_mapping_param,
- .tone_mapping_desat = p->opts.tone_mapping_desat,
+ .tone_map = p->opts.tone_map,
.early_flush = p->opts.early_flush,
.icc_opts = p->opts.icc_opts,
.hwdec_interop = p->opts.hwdec_interop,
+ .target_trc = p->opts.target_trc,
+ .target_prim = p->opts.target_prim,
+ .target_peak = p->opts.target_peak,
};
for (int n = 0; n < SCALER_COUNT; n++)
p->opts.scaler[n] = gl_video_opts_def.scaler[n];
diff --git a/video/out/gpu/video.h b/video/out/gpu/video.h
index ca8b6f65d4..1b0994ac78 100644
--- a/video/out/gpu/video.h
+++ b/video/out/gpu/video.h
@@ -95,8 +95,18 @@ enum tone_mapping {
TONE_MAPPING_LINEAR,
};
-// How many frames to average over for HDR peak detection
-#define PEAK_DETECT_FRAMES 63
+struct gl_tone_map_opts {
+ int curve;
+ float curve_param;
+ float max_boost;
+ int compute_peak;
+ float decay_rate;
+ float scene_threshold_low;
+ float scene_threshold_high;
+ float desat;
+ float desat_exp;
+ int gamut_warning; // bool
+};
struct gl_video_opts {
int dumb_mode;
@@ -107,11 +117,7 @@ struct gl_video_opts {
int target_prim;
int target_trc;
int target_peak;
- int tone_mapping;
- int compute_hdr_peak;
- float tone_mapping_param;
- float tone_mapping_desat;
- int gamut_warning;
+ struct gl_tone_map_opts tone_map;
int correct_downscaling;
int linear_downscaling;
int linear_upscaling;
diff --git a/video/out/gpu/video_shaders.c b/video/out/gpu/video_shaders.c
index 342fb39ded..b34aa90bfa 100644
--- a/video/out/gpu/video_shaders.c
+++ b/video/out/gpu/video_shaders.c
@@ -380,7 +380,7 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
GLSLF("color.rgb = max(color.rgb - vec3(%f), vec3(0.0)) \n"
" / (vec3(%f) - vec3(%f) * color.rgb);\n",
PQ_C1, PQ_C2, PQ_C3);
- GLSLF("color.rgb = pow(color.rgb, vec3(1.0/%f));\n", PQ_M1);
+ GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", 1.0 / PQ_M1);
// PQ's output range is 0-10000, but we need it to be relative to to
// MP_REF_WHITE instead, so rescale
GLSLF("color.rgb *= vec3(%f);\n", 10000 / MP_REF_WHITE);
@@ -567,123 +567,106 @@ static void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light ligh
// under a typical presentation gamma of about 2.0.
static const float sdr_avg = 0.25;
-// The threshold for which to consider an average luminance difference to be
-// a sign of a scene change.
-static const int scene_threshold = 0.2 * MP_REF_WHITE;
-
-static void hdr_update_peak(struct gl_shader_cache *sc)
+static void hdr_update_peak(struct gl_shader_cache *sc,
+ const struct gl_tone_map_opts *opts)
{
- // For performance, we want to do as few atomic operations on global
- // memory as possible, so use an atomic in shmem for the work group.
- GLSLH(shared uint wg_sum;);
- GLSL(wg_sum = 0;)
+ // Update the sig_peak/sig_avg from the old SSBO state
+ GLSL(if (average.y > 0.0) {)
+ GLSL( sig_avg = max(1e-3, average.x);)
+ GLSL( sig_peak = max(1.00, average.y);)
+ GLSL(})
- // Have each thread update the work group sum with the local value
+ // Chosen to avoid overflowing on an 8K buffer
+ const float log_min = 1e-3, log_scale = 400.0, sig_scale = 10000.0;
+
+ // For performance, and to avoid overflows, we tally up the sub-results per
+ // pixel using shared memory first
+ GLSLH(shared int wg_sum;)
+ GLSLH(shared uint wg_max;)
+ GLSL(wg_sum = 0; wg_max = 0;)
GLSL(barrier();)
- GLSLF("atomicAdd(wg_sum, uint(sig * %f));\n", MP_REF_WHITE);
+ GLSLF("float sig_log = log(max(sig_max, %f));\n", log_min);
+ GLSLF("atomicAdd(wg_sum, int(sig_log * %f));\n", log_scale);
+ GLSLF("atomicMax(wg_max, uint(sig_max * %f));\n", sig_scale);
- // Have one thread per work group update the global atomics. We use the
- // work group average even for the global sum, to make the values slightly
- // more stable and smooth out tiny super-highlights.
+ // Have one thread per work group update the global atomics
GLSL(memoryBarrierShared();)
GLSL(barrier();)
GLSL(if (gl_LocalInvocationIndex == 0) {)
- GLSL( uint wg_avg = wg_sum / (gl_WorkGroupSize.x * gl_WorkGroupSize.y);)
- GLSL( atomicMax(frame_max[frame_idx], wg_avg);)
- GLSL( atomicAdd(frame_avg[frame_idx], wg_avg);)
+ GLSL( int wg_avg = wg_sum / int(gl_WorkGroupSize.x * gl_WorkGroupSize.y);)
+ GLSL( atomicAdd(frame_sum, wg_avg);)
+ GLSL( atomicMax(frame_max, wg_max);)
+ GLSL( memoryBarrierBuffer();)
GLSL(})
-
- const float refi = 1.0 / MP_REF_WHITE;
-
- // Update the sig_peak/sig_avg from the old SSBO state
- GLSL(uint num_wg = gl_NumWorkGroups.x * gl_NumWorkGroups.y;)
- GLSL(if (frame_num > 0) {)
- GLSLF(" float peak = %f * float(total_max) / float(frame_num);\n", refi);
- GLSLF(" float avg = %f * float(total_avg) / float(frame_num);\n", refi);
- GLSLF(" sig_peak = max(1.0, peak);\n");
- GLSLF(" sig_avg = max(%f, avg);\n", sdr_avg);
- GLSL(});
+ GLSL(barrier();)
// Finally, to update the global state, we increment a counter per dispatch
- GLSL(memoryBarrierBuffer();)
- GLSL(barrier();)
+ GLSL(uint num_wg = gl_NumWorkGroups.x * gl_NumWorkGroups.y;)
GLSL(if (gl_LocalInvocationIndex == 0 && atomicAdd(counter, 1) == num_wg - 1) {)
-
- // Since we sum up all the workgroups, we also still need to divide the
- // average by the number of work groups
GLSL( counter = 0;)
- GLSL( frame_avg[frame_idx] /= num_wg;)
- GLSL( uint cur_max = frame_max[frame_idx];)
- GLSL( uint cur_avg = frame_avg[frame_idx];)
-
- // Scene change detection
- GLSL( int diff = int(frame_num * cur_avg) - int(total_avg);)
- GLSLF(" if (abs(diff) > frame_num * %d) {\n", scene_threshold);
- GLSL( frame_num = 0;)
- GLSL( total_max = total_avg = 0;)
- GLSLF(" for (uint i = 0; i < %d; i++)\n", PEAK_DETECT_FRAMES+1);
- GLSL( frame_max[i] = frame_avg[i] = 0;)
- GLSL( frame_max[frame_idx] = cur_max;)
- GLSL( frame_avg[frame_idx] = cur_avg;)
- GLSL( })
-
- // Add the current frame, then subtract and reset the next frame
- GLSLF(" uint next = (frame_idx + 1) %% %d;\n", PEAK_DETECT_FRAMES+1);
- GLSL( total_max += cur_max - frame_max[next];)
- GLSL( total_avg += cur_avg - frame_avg[next];)
- GLSL( frame_max[next] = frame_avg[next] = 0;)
-
- // Update the index and count
- GLSL( frame_idx = next;)
- GLSLF(" frame_num = min(frame_num + 1, %d);\n", PEAK_DETECT_FRAMES);
+ GLSL( vec2 cur = vec2(float(frame_sum) / float(num_wg), frame_max);)
+ GLSLF(" cur *= vec2(1.0/%f, 1.0/%f);\n", log_scale, sig_scale);
+ GLSL( cur.x = exp(cur.x);)
+ GLSL( if (average.y == 0.0))
+ GLSL( average = cur;)
+
+ // Use an IIR low-pass filter to smooth out the detected values, with a
+ // configurable decay rate based on the desired time constant (tau)
+ float a = 1.0 - cos(1.0 / opts->decay_rate);
+ float decay = sqrt(a*a + 2*a) - a;
+ GLSLF(" average += %f * (cur - average);\n", decay);
+
+ // Scene change hysteresis
+ float log_db = 10.0 / log(10.0);
+ GLSLF(" float weight = smoothstep(%f, %f, abs(log(cur.x / average.x)));\n",
+ opts->scene_threshold_low / log_db,
+ opts->scene_threshold_high / log_db);
+ GLSL( average = mix(average, cur, weight);)
+
+ // Reset SSBO state for the next frame
+ GLSL( frame_sum = 0; frame_max = 0;)
GLSL( memoryBarrierBuffer();)
GLSL(})
}
// 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, bool detect_peak,
+static void pass_tone_map(struct gl_shader_cache *sc,
float src_peak, float dst_peak,
- enum tone_mapping algo, float param, float desat)
+ const struct gl_tone_map_opts *opts)
{
GLSLF("// HDR tone mapping\n");
// To prevent discoloration due to out-of-bounds clipping, we need to make
// sure to reduce the value range as far as necessary to keep the entire
// signal in range, so tone map based on the brightest component.
- GLSL(float sig = max(max(color.r, color.g), color.b);)
+ GLSL(int sig_idx = 0;)
+ GLSL(if (color[1] > color[sig_idx]) sig_idx = 1;)
+ GLSL(if (color[2] > color[sig_idx]) sig_idx = 2;)
+ GLSL(float sig_max = color[sig_idx];)
GLSLF("float sig_peak = %f;\n", src_peak);
GLSLF("float sig_avg = %f;\n", sdr_avg);
- if (detect_peak)
- hdr_update_peak(sc);
+ if (opts->compute_peak >= 0)
+ hdr_update_peak(sc, opts);
+
+ GLSLF("vec3 sig = color.rgb;\n");
// 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 *= %f;\n", 1.0 / dst_peak);
- GLSLF("sig_peak *= %f;\n", 1.0 / dst_peak);
+ GLSLF("sig *= 1.0/%f;\n", dst_peak);
+ GLSLF("sig_peak *= 1.0/%f;\n", dst_peak);
}
- GLSL(float sig_orig = sig;)
- GLSLF("float slope = min(1.0, %f / sig_avg);\n", sdr_avg);
+ GLSL(float sig_orig = sig[sig_idx];)
+ GLSLF("float slope = min(%f, %f / sig_avg);\n", opts->max_boost, sdr_avg);
GLSL(sig *= slope;)
GLSL(sig_peak *= slope;)
- // Desaturate the color using a coefficient dependent on the signal.
- // Do this after peak detection in order to prevent over-desaturating
- // overly bright souces
- if (desat > 0) {
- float base = 0.18 * dst_peak;
- GLSL(float luma = dot(dst_luma, color.rgb);)
- GLSLF("float coeff = max(sig - %f, 1e-6) / max(sig, 1e-6);\n", base);
- GLSLF("coeff = pow(coeff, %f);\n", 10.0 / desat);
- GLSL(color.rgb = mix(color.rgb, vec3(luma), coeff);)
- GLSL(sig = mix(sig, luma * slope, coeff);) // also make sure to update `sig`
- }
-
- switch (algo) {
+ float param = opts->curve_param;
+ switch (opts->curve) {
case TONE_MAPPING_CLIP:
GLSLF("sig = %f * sig;\n", isnan(param) ? 1.0 : param);
break;
@@ -697,14 +680,15 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
GLSLF("float b = (j*j - 2.0*j*sig_peak + sig_peak) / "
"max(1e-6, sig_peak - 1.0);\n");
GLSLF("float scale = (b*b + 2.0*b*j + j*j) / (b-a);\n");
- GLSL(sig = sig > j ? scale * (sig + a) / (sig + b) : sig;)
+ GLSLF("sig = mix(sig, scale * (sig + vec3(a)) / (sig + vec3(b)),"
+ " greaterThan(sig, vec3(j)));\n");
GLSLF("}\n");
break;
case TONE_MAPPING_REINHARD: {
float contrast = isnan(param) ? 0.5 : param,
offset = (1.0 - contrast) / contrast;
- GLSLF("sig = sig / (sig + %f);\n", offset);
+ GLSLF("sig = sig / (sig + vec3(%f));\n", offset);
GLSLF("float scale = (sig_peak + %f) / sig_peak;\n", offset);
GLSL(sig *= scale;)
break;
@@ -712,19 +696,25 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
case TONE_MAPPING_HABLE: {
float A = 0.15, B = 0.50, C = 0.10, D = 0.20, E = 0.02, F = 0.30;
- GLSLHF("float hable(float x) {\n");
- GLSLHF("return ((x * (%f*x + %f)+%f)/(x * (%f*x + %f) + %f)) - %f;\n",
- A, C*B, D*E, A, B, D*F, E/F);
+ GLSLHF("vec3 hable(vec3 x) {\n");
+ GLSLHF("return (x * (%f*x + vec3(%f)) + vec3(%f)) / "
+ " (x * (%f*x + vec3(%f)) + vec3(%f)) "
+ " - vec3(%f);\n",
+ A, C*B, D*E,
+ A, B, D*F,
+ E/F);
GLSLHF("}\n");
- GLSL(sig = hable(sig) / hable(sig_peak);)
+ GLSLF("sig = hable(max(vec3(0.0), sig)) / hable(vec3(sig_peak)).x;\n");
break;
}
case TONE_MAPPING_GAMMA: {
float gamma = isnan(param) ? 1.8 : param;
- GLSLF("const float cutoff = 0.05, gamma = %f;\n", 1.0/gamma);
- GLSL(float scale = pow(cutoff / sig_peak, gamma) / cutoff;)
- GLSL(sig = sig > cutoff ? pow(sig / sig_peak, gamma) : scale * sig;)
+ GLSLF("const float cutoff = 0.05, gamma = 1.0/%f;\n", gamma);
+ GLSL(float scale = pow(cutoff / sig_peak, gamma.x) / cutoff;)
+ GLSLF("sig = mix(scale * sig,"
+ " pow(sig / sig_peak, vec3(gamma)),"
+ " greaterThan(sig, vec3(cutoff)));\n");
break;
}
@@ -738,24 +728,32 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
abort();
}
- // Apply the computed scale factor to the color, linearly to prevent
- // discoloration
- GLSL(sig = min(sig, 1.0);)
- GLSL(color.rgb *= vec3(sig / sig_orig);)
+ GLSL(sig = min(sig, vec3(1.0));)
+ GLSL(vec3 sig_lin = color.rgb * (sig[sig_idx] / sig_orig);)
+
+ // Mix between the per-channel tone mapped and the linear tone mapped
+ // signal based on the desaturation strength
+ if (opts->d