diff options
author | Jan Ekström <jeebjp@gmail.com> | 2019-03-11 01:00:27 +0200 |
---|---|---|
committer | Jan Ekström <jeebjp@gmail.com> | 2019-03-11 01:00:27 +0200 |
commit | 199aabddcc0105b504cc8e0cb240bc3c89288c1a (patch) | |
tree | fa7eb297a8e11b5e0d9339196f23918d6a4d0b85 /video | |
parent | 7c565547b80fdc2a1bfdf31f9725129bcb16aa9d (diff) | |
parent | 1d0349d3b5d9a263251fcb3b0d7e135d4731bfd0 (diff) | |
download | mpv-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.c | 4 | ||||
-rw-r--r-- | video/out/cocoa-cb/events_view.swift | 3 | ||||
-rw-r--r-- | video/out/cocoa-cb/window.swift | 5 | ||||
-rw-r--r-- | video/out/cocoa_cb_common.swift | 25 | ||||
-rw-r--r-- | video/out/gpu/lcms.c | 2 | ||||
-rw-r--r-- | video/out/gpu/user_shaders.c | 5 | ||||
-rw-r--r-- | video/out/gpu/user_shaders.h | 2 | ||||
-rw-r--r-- | video/out/gpu/video.c | 130 | ||||
-rw-r--r-- | video/out/gpu/video.h | 20 | ||||
-rw-r--r-- | video/out/gpu/video_shaders.c | 216 | ||||
-rw-r--r-- | video/out/gpu/video_shaders.h | 6 | ||||
-rw-r--r-- | video/out/opengl/common.c | 1 | ||||
-rw-r--r-- | video/out/opengl/context_drm_egl.c | 209 | ||||
-rw-r--r-- | video/out/wayland_common.c | 20 | ||||
-rw-r--r-- | video/out/wayland_common.h | 2 |
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, ¶ms); 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, gam |