diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 3aa2a541e8..160bc3679c 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -47,6 +47,12 @@ Interface changes - support for `--spirv-compiler=nvidia` has been removed, leaving `shaderc` as the only option. The `--spirv-compiler` option itself has been marked as deprecated, and may be removed in the future. + - split up `--tone-mapping-desaturate`` into strength + exponent, instead of + only using a single value (which previously just controlled the exponent). + The strength now linearly blends between the linear and nonlinear tone + mapped versions of a color. + - add --hdr-peak-decay-rate and --hdr-scene-threshold-low/high + - add --tone-mapping-max-boost - ipc: require that "request_id" fields are integers. Other types are still accepted for compatibility, but this will stop in the future. Also, if no request_id is provided, 0 will be assumed. diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 6bf196931c..b2d676c644 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -1817,7 +1817,9 @@ Property list are the xrandr names (LVDS1, HDMI1, DP1, VGA1, etc.). On Windows, these are the GDI names (\\.\DISPLAY1, \\.\DISPLAY2, etc.) and the first display in the list will be the one that Windows considers associated with the - window (as determined by the MonitorFromWindow API.) + window (as determined by the MonitorFromWindow API.) On macOS these are the + Display Product Names as used in the System Information and only one display + name is returned since a window can only be on one screen. ``display-fps`` (RW) The refresh rate of the current display. Currently, this is the lowest FPS diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index f384123f6d..e3eb46495b 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1092,7 +1092,7 @@ Video You can get the list of allowed codecs with ``mpv --vd=help``. Remove the prefix, e.g. instead of ``lavc:h264`` use ``h264``. - By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video,vp9``. Note that + By default, this is set to ``h264,vc1,hevc,vp9``. Note that the hardware acceleration special codecs like ``h264_vdpau`` are not relevant anymore, and in fact have been removed from Libav in this form. @@ -1326,8 +1326,10 @@ Audio Since mpv 0.18.1, this always controls the internal mixer (aka "softvol"). ``--replaygain=`` - Adjust volume gain according to the track-gain or album-gain replaygain - value stored in the file metadata (default: no replaygain). + Adjust volume gain according to replaygain values stored in the file + metadata. With ``--replaygain=no`` (the default), perform no adjustment. + With ``--replaygain=track``, apply track gain. With ``--replaygain=album``, + apply album gain if present and fall back to track gain otherwise. ``--replaygain-preamp=`` Pre-amplification gain in dB to apply to the selected replaygain gain @@ -3988,8 +3990,8 @@ Network DVB --- -``--dvbin-card=<1-4>`` - Specifies using card number 1-4 (default: 1). +``--dvbin-card=<0-15>`` + Specifies using card number 0-15 (default: 0). ``--dvbin-file=`` Instructs mpv to read the channels list from ````. The default is @@ -5073,7 +5075,7 @@ The following video options are currently all specific to ``--vo=gpu`` and The user should independently guarantee this before using these signal formats for display. -``--target-peak=`` +``--target-peak=`` Specifies the measured peak brightness of the output display, in cd/m^2 (AKA nits). The interpretation of this brightness depends on the configured ``--target-trc``. In all cases, it imposes a limit on the signal values @@ -5085,9 +5087,9 @@ The following video options are currently all specific to ``--vo=gpu`` and above 100 essentially causes the display to be treated as if it were an HDR display in disguise. (See the note below) - By default, the chosen peak defaults to an appropriate value based on the - TRC in use. For SDR curves, it defaults to 100. For HDR curves, it - defaults to 100 * the transfer function's nominal peak. + In ``auto`` mode (the default), the chosen peak is an appropriate value + based on the TRC in use. For SDR curves, it uses 100. For HDR curves, it + uses 100 * the transfer function's nominal peak. .. note:: @@ -5164,6 +5166,14 @@ The following video options are currently all specific to ``--vo=gpu`` and linear Specifies the scale factor to use while stretching. Defaults to 1.0. +``--tone-mapping-max-boost=<1.0..10.0>`` + Upper limit for how much the tone mapping algorithm is allowed to boost + the average brightness by over-exposing the image. The default value of 1.0 + allows no additional brightness boost. A value of 2.0 would allow + over-exposing by a factor of 2, and so on. Raising this setting can help + reveal details that would otherwise be hidden in dark scenes, but raising + it too high will make dark scenes appear unnaturally bright. + ``--hdr-compute-peak=`` Compute the HDR peak and frame average brightness per-frame instead of relying on tagged metadata. These values are averaged over local regions as @@ -5174,17 +5184,50 @@ The following video options are currently all specific to ``--vo=gpu`` and The special value ``auto`` (default) will enable HDR peak computation automatically if compute shaders and SSBOs are supported. -``--tone-mapping-desaturate=`` - Apply desaturation for highlights. The parameter essentially controls the - steepness of the desaturation curve. The higher the parameter, the more - aggressively colors will be desaturated. This setting helps prevent - unnaturally blown-out colors for super-highlights, by (smoothly) turning - into white instead. This makes images feel more natural, at the cost of - reducing information about out-of-range colors. +``--hdr-peak-decay-rate=<1.0..1000.0>`` + The decay rate used for the HDR peak detection algorithm (default: 100.0). + This is only relevant when ``--hdr-compute-peak`` is enabled. Higher values + make the peak decay more slowly, leading to more stable values at the cost + of more "eye adaptation"-like effects (although this is mitigated somewhat + by ``--hdr-scene-threshold``). A value of 1.0 (the lowest possible) disables + all averaging, meaning each frame's value is used directly as measured, + but doing this is not recommended for "noisy" sources since it may lead + to excessive flicker. (In signal theory terms, this controls the time + constant "tau" of an IIR low pass filter) - The default of 0.5 provides a good balance. This value is weaker than the - ACES ODT curves' recommendation, but works better for most content in - practice. A setting of 0.0 disables this option. +``--hdr-scene-threshold-low=<0.0..100.0>``, ``--hdr-scene-threshold-high=<0.0..100.0>`` + The lower and upper thresholds (in dB) for a brightness difference + to be considered a scene change (default: 5.5 low, 10.0 high). This is only + relevant when ``--hdr-compute-peak`` is enabled. Normally, small + fluctuations in the frame brightness are compensated for by the peak + averaging mechanism, but for large jumps in the brightness this can result + in the frame remaining too bright or too dark for up to several seconds, + depending on the value of ``--hdr-peak-decay-rate``. To counteract this, + when the brightness between the running average and the current frame + exceeds the low threshold, mpv will make the averaging filter more + aggressive, up to the limit of the high threshold (at which point the + filter becomes instant). + +``--tone-mapping-desaturate=<0.0..1.0>`` + Apply desaturation for highlights (default: 0.75). The parameter controls + the strength of the desaturation curve. A value of 0.0 completely disables + it, while a value of 1.0 means that overly bright colors will tend towards + white. (This is not always the case, especially not for highlights that are + near primary colors) + + Values in between apply progressively more/less aggressive desaturation. + This setting helps prevent unnaturally oversaturated colors for + super-highlights, by (smoothly) turning them into less saturated (per + channel tone mapped) colors instead. This makes images feel more natural, + at the cost of chromatic distortions for out-of-range colors. The default + value of 0.75 provides a good balance. Setting this to 0.0 preserves the + chromatic accuracy of the tone mapping process. + +``--tone-mapping-desaturate-exponent=<0.0..20.0>`` + This setting controls the exponent of the desaturation curve, which + controls how bright a color needs to be in order to start being + desaturated. The default of 1.5 provides a reasonable balance. Decreasing + this exponent makes the curve more aggressive. ``--gamut-warning`` If enabled, mpv will mark all clipped/out-of-gamut pixels that exceed a @@ -5245,12 +5288,14 @@ The following video options are currently all specific to ``--vo=gpu`` and Size of the 3D LUT generated from the ICC profile in each dimension. Default is 64x64x64. Sizes may range from 2 to 512. -``--icc-contrast=<0-1000000>`` +``--icc-contrast=<0-1000000|inf>`` Specifies an upper limit on the target device's contrast ratio. This is detected automatically from the profile if possible, but for some profiles it might be missing, causing the contrast to be assumed as infinite. As a result, video may appear darker than intended. This only affects BT.1886 - content. The default of 0 means no limit. + content. The default of 0 means no limit if the detected contrast is less + than 100000, and limits to 1000 otherwise. Use ``--icc-contrast=inf`` to + preserve the infinite contrast (most likely when using OLED displays). ``--blend-subtitles=`` Blend subtitles directly onto upscaled video frames, before interpolation diff --git a/demux/demux.c b/demux/demux.c index 4e1fb57c72..45a2ad65b6 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -1715,8 +1715,6 @@ static void prune_old_packets(struct demux_internal *in) } prev = prev->next; } - - update_seek_ranges(range); } bool done = false; @@ -1725,6 +1723,8 @@ static void prune_old_packets(struct demux_internal *in) remove_head_packet(queue); } + update_seek_ranges(range); + if (range != in->current_range && range->seek_start == MP_NOPTS_VALUE) free_empty_cached_ranges(in); } @@ -2100,12 +2100,17 @@ static struct replaygain_data *decode_rgain(struct mp_log *log, { struct replaygain_data rg = {0}; + // Set values in *rg, using track gain as a fallback for album gain if the + // latter is not present. This behavior matches that in demux/demux_lavf.c's + // export_replaygain; if you change this, please make equivalent changes + // there too. if (decode_gain(log, tags, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) >= 0 && decode_peak(log, tags, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) >= 0) { if (decode_gain(log, tags, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) < 0 || decode_peak(log, tags, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak) < 0) { + // Album gain is undefined; fall back to track gain. rg.album_gain = rg.track_gain; rg.album_peak = rg.track_peak; } diff --git a/demux/demux_edl.c b/demux/demux_edl.c index b724ffa592..7f85568eae 100644 --- a/demux/demux_edl.c +++ b/demux/demux_edl.c @@ -80,8 +80,10 @@ static struct tl_parts *parse_edl(bstr str) { struct tl_parts *tl = talloc_zero(NULL, struct tl_parts); while (str.len) { - if (bstr_eatstart0(&str, "#")) + if (bstr_eatstart0(&str, "#")) { bstr_split_tok(str, "\n", &(bstr){0}, &str); + continue; + } if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";")) continue; bool is_header = bstr_eatstart0(&str, "!"); diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index ad71937580..8ec856d0c8 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2004 Michael Niedermayer + * Copyright (C) 2018 Google LLC * * This file is part of mpv. * @@ -588,17 +589,27 @@ static void export_replaygain(demuxer_t *demuxer, struct sh_stream *sh, av_rgain = (AVReplayGain*)src_sd->data; rgain = talloc_ptrtype(demuxer, rgain); - rgain->track_gain = (av_rgain->track_gain != INT32_MIN) ? - av_rgain->track_gain / 100000.0f : 0.0; + // Set values in *rgain, using track gain as a fallback for album gain + // if the latter is not present. This behavior matches that in + // demux/demux.c's decode_rgain; if you change this, please make + // equivalent changes there too. + if (av_rgain->track_gain != INT32_MIN && av_rgain->track_peak != 0.0) { + // Track gain is defined. + rgain->track_gain = av_rgain->track_gain / 100000.0f; + rgain->track_peak = av_rgain->track_peak / 100000.0f; - rgain->track_peak = (av_rgain->track_peak != 0.0) ? - av_rgain->track_peak / 100000.0f : 1.0; - - rgain->album_gain = (av_rgain->album_gain != INT32_MIN) ? - av_rgain->album_gain / 100000.0f : 0.0; - - rgain->album_peak = (av_rgain->album_peak != 0.0) ? - av_rgain->album_peak / 100000.0f : 1.0; + if (av_rgain->album_gain != INT32_MIN && + av_rgain->album_peak != 0.0) + { + // Album gain is also defined. + rgain->album_gain = av_rgain->album_gain / 100000.0f; + rgain->album_peak = av_rgain->album_peak / 100000.0f; + } else { + // Album gain is undefined; fall back to track gain. + rgain->album_gain = rgain->track_gain; + rgain->album_peak = rgain->track_peak; + } + } // This must be run only before the stream was added, otherwise there // will be race conditions with accesses from the user thread. diff --git a/filters/f_swresample.c b/filters/f_swresample.c index 49f69cbbd7..8ee44195a8 100644 --- a/filters/f_swresample.c +++ b/filters/f_swresample.c @@ -399,7 +399,7 @@ static bool reorder_planes(struct mp_aframe *mpa, int *reorder, if (!mp_aframe_set_chmap(mpa, newmap)) return false; - int num_planes = newmap->num; + int num_planes = mp_aframe_get_planes(mpa); uint8_t **planes = mp_aframe_get_data_rw(mpa); uint8_t *old_planes[MP_NUM_CHANNELS]; assert(num_planes <= MP_NUM_CHANNELS); diff --git a/osdep/macOS_swift_bridge.h b/osdep/macOS_swift_bridge.h index 4204b514d1..f0b549ca40 100644 --- a/osdep/macOS_swift_bridge.h +++ b/osdep/macOS_swift_bridge.h @@ -49,3 +49,11 @@ static int SWIFT_KEY_MOUSE_LEAVE = MP_KEY_MOUSE_LEAVE; static int SWIFT_KEY_MOUSE_ENTER = MP_KEY_MOUSE_ENTER; static int SWIFT_KEY_STATE_DOWN = MP_KEY_STATE_DOWN; static int SWIFT_KEY_STATE_UP = MP_KEY_STATE_UP; + +// only used from Swift files and therefore seen as unused by the c compiler +static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s) __attribute__ ((unused)); + +static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s) +{ + MP_TARRAY_APPEND(t, *a, *i, s); +} diff --git a/osdep/macOS_swift_extensions.swift b/osdep/macOS_swift_extensions.swift new file mode 100644 index 0000000000..14d217f589 --- /dev/null +++ b/osdep/macOS_swift_extensions.swift @@ -0,0 +1,58 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +import Cocoa + +extension NSScreen { + + public var displayID: CGDirectDisplayID { + get { + return deviceDescription["NSScreenNumber"] as! CGDirectDisplayID + } + } + + public var displayName: String? { + get { + var name: String? = nil + var object: io_object_t + var iter = io_iterator_t() + let matching = IOServiceMatching("IODisplayConnect") + let result = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iter) + + if result != KERN_SUCCESS || iter == 0 { return nil } + + repeat { + object = IOIteratorNext(iter) + let info = IODisplayCreateInfoDictionary(object, IOOptionBits(kIODisplayOnlyPreferredName)).takeRetainedValue() as! [String:AnyObject] + if (info[kDisplayVendorID] as? UInt32 == CGDisplayVendorNumber(displayID) && + info[kDisplayProductID] as? UInt32 == CGDisplayModelNumber(displayID) && + info[kDisplaySerialNumber] as? UInt32 ?? 0 == CGDisplaySerialNumber(displayID)) + { + if let productNames = info["DisplayProductName"] as? [String:String], + let productName = productNames.first?.value + { + name = productName + break + } + } + } while object != 0 + + IOObjectRelease(iter) + return name + } + } +} diff --git a/osdep/macosx_compat.h b/osdep/macosx_compat.h index 747aa159af..d5f8dab571 100644 --- a/osdep/macosx_compat.h +++ b/osdep/macosx_compat.h @@ -55,7 +55,7 @@ static const NSEventModifierFlags NSEventModifierFlagOption = NSAlternateKeyMask #if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9) typedef NSUInteger NSModalResponse; -static const NSModalResponse NSModalResponseOK = NSFileHandlingPanelOKButton +static const NSModalResponse NSModalResponseOK = NSFileHandlingPanelOKButton; #endif #endif diff --git a/player/command.c b/player/command.c index 4d0fbefc34..0dc9e9a8b9 100644 --- a/player/command.c +++ b/player/command.c @@ -4052,6 +4052,7 @@ static bool is_property_set(int action, void *val) case M_PROPERTY_SWITCH: case M_PROPERTY_SET_STRING: case M_PROPERTY_SET_NODE: + case M_PROPERTY_MULTIPLY: return true; case M_PROPERTY_KEY_ACTION: { struct m_property_action_arg *key = val; diff --git a/player/loadfile.c b/player/loadfile.c index 4dff4c710c..f55dd29f34 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -752,7 +752,7 @@ int mp_add_external_file(struct MPContext *mpctx, char *filename, if (!demuxer) goto err_out; - if (opts->rebase_start_time) + if (filter != STREAM_SUB && opts->rebase_start_time) demux_set_ts_offset(demuxer, -demuxer->start_time); bool has_any = false; diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua index 1da6420182..523b649b8c 100644 --- a/player/lua/defaults.lua +++ b/player/lua/defaults.lua @@ -476,12 +476,9 @@ function mp.dispatch_events(allow_wait) while mp.keep_running do local wait = 0 if not more_events then - wait = process_timers() - if wait == nil then - for _, handler in ipairs(idle_handlers) do - handler() - end - wait = 1e20 -- infinity for all practical purposes + wait = process_timers() or 1e20 -- infinity for all practical purposes + for _, handler in ipairs(idle_handlers) do + handler() end -- Resume playloop - important especially if an error happened while -- suspended, and the error was handled, but no resume was done. diff --git a/player/lua/stats.lua b/player/lua/stats.lua index 6272aab9c2..db3df01c6d 100644 --- a/player/lua/stats.lua +++ b/player/lua/stats.lua @@ -430,7 +430,8 @@ local function add_file(s) append_property(s, "media-title", {prefix="Title:"}) end - append_property(s, "file-format", {prefix="Format/Protocol:"}) + local fs = append_property(s, "file-size", {prefix="Size:"}) + append_property(s, "file-format", {prefix="Format/Protocol:", nl=fs and "" or o.nl}) local ch_index = mp.get_property_number("chapter") if ch_index and ch_index >= 0 then @@ -457,7 +458,6 @@ local function add_file(s) indent=o.prefix_sep, no_prefix_markup=true}) end end - append_property(s, "file-size", {prefix="Size:"}) end @@ -536,9 +536,9 @@ local function add_audio(s) append(s, "", {prefix=o.nl .. o.nl .. "Audio:", nl="", indent=""}) append_property(s, "audio-codec", {prefix_sep="", nl="", indent=""}) - append(s, r["format"], {prefix="Format:"}) + local cc = append(s, r["channel-count"], {prefix="Channels:"}) + append(s, r["format"], {prefix="Format:", nl=cc and "" or o.nl}) append(s, r["samplerate"], {prefix="Sample Rate:", suffix=" Hz"}) - append(s, r["channel-count"], {prefix="Channels:"}) append_property(s, "packet-audio-bitrate", {prefix="Bitrate:", suffix=" kbps"}) append_filters(s, "af", "Filters:") end diff --git a/stream/stream_dvb.c b/stream/stream_dvb.c index fa58f633ec..20ed3337c4 100644 --- a/stream/stream_dvb.c +++ b/stream/stream_dvb.c @@ -73,7 +73,7 @@ static pthread_mutex_t global_dvb_state_lock = PTHREAD_MUTEX_INITIALIZER; const struct m_sub_options stream_dvb_conf = { .opts = (const m_option_t[]) { OPT_STRING("prog", cfg_prog, 0), - OPT_INTRANGE("card", cfg_devno, 0, 1, 4), + OPT_INTRANGE("card", cfg_devno, 0, 0, MAX_ADAPTERS-1), OPT_INTRANGE("timeout", cfg_timeout, 0, 1, 30), OPT_STRING("file", cfg_file, M_OPT_FILE), OPT_FLAG("full-transponder", cfg_full_transponder, 0), @@ -1157,7 +1157,7 @@ dvb_state_t *dvb_get_state(stream_t *stream) if (devno.len) { bstr r; priv->cfg_devno = bstrtoll(devno, &r, 0); - if (r.len || priv->cfg_devno < 0 || priv->cfg_devno > MAX_ADAPTERS) { + if (r.len || priv->cfg_devno < 0 || priv->cfg_devno >= MAX_ADAPTERS) { MP_ERR(stream, "invalid devno: '%.*s'\n", BSTR_P(devno)); return NULL; } diff --git a/sub/filter_sdh.c b/sub/filter_sdh.c index ce8c28ed0a..a04f33a23e 100644 --- a/sub/filter_sdh.c +++ b/sub/filter_sdh.c @@ -406,8 +406,11 @@ char *filter_SDH(struct sd *sd, char *format, int n_ignored, char *data, int len line_with_text = true; } } else if (*rp && rp[0] != '\\') { - if (rp[0] > 32 && rp[0] < 127 && rp[0] != '-') + if ((rp[0] > 32 && rp[0] < 127 && rp[0] != '-') || + (unsigned char)rp[0] >= 0xC0) + { line_with_text = true; + } append(sd, buf, rp[0]); rp++; } else if (rp[0] == '\\' && rp[1] != 'N') { 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) { 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?>?.self) + var array: UnsafeMutablePointer?>? = 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 +#include #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];) + 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;) - // 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( }) + // 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); - // 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;) + // 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);) - // Update the index and count - GLSL( frame_idx = next;) - GLSLF(" frame_num = min(frame_num + 1, %d);\n", PEAK_DETECT_FRAMES); + // 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->desat > 0) { + float base = 0.18 * dst_peak; + GLSLF("float coeff = max(sig[sig_idx] - %f, 1e-6) / " + " max(sig[sig_idx], 1.0);\n", base); + GLSLF("coeff = %f * pow(coeff, %f);\n", opts->desat, opts->desat_exp); + GLSLF("color.rgb = mix(sig_lin, %f * sig, coeff);\n", dst_peak); + } else { + GLSL(color.rgb = sig_lin;) + } } // Map colors from one source space to another. These source spaces must be // known (i.e. not MP_CSP_*_AUTO), as this function won't perform any // auto-guessing. If is_linear is true, we assume the input has already been -// linearized (e.g. for linear-scaling). If `detect_peak` is true, we will -// detect the peak instead of relying on metadata. Note that this requires -// the caller to have already bound the appropriate SSBO and set up the -// compute shader metadata -void pass_color_map(struct gl_shader_cache *sc, +// linearized (e.g. for linear-scaling). If `opts->compute_peak` is true, we +// will detect the peak instead of relying on metadata. Note that this requires +// the caller to have already bound the appropriate SSBO and set up the compute +// shader metadata +void pass_color_map(struct gl_shader_cache *sc, bool is_linear, struct mp_colorspace src, struct mp_colorspace dst, - enum tone_mapping algo, float tone_mapping_param, - float tone_mapping_desat, bool detect_peak, - bool gamut_warning, bool is_linear) + const struct gl_tone_map_opts *opts) { GLSLF("// color mapping\n"); @@ -791,6 +789,10 @@ void pass_color_map(struct gl_shader_cache *sc, if (need_ootf) pass_ootf(sc, src.light, src.sig_peak); + // Tone map to prevent clipping due to excessive brightness + if (src.sig_peak > dst.sig_peak) + pass_tone_map(sc, src.sig_peak, dst.sig_peak, opts); + // Adapt to the right colorspace if necessary if (src.primaries != dst.primaries) { struct mp_csp_primaries csp_src = mp_get_csp_primaries(src.primaries), @@ -801,13 +803,6 @@ void pass_color_map(struct gl_shader_cache *sc, GLSL(color.rgb = cms_matrix * color.rgb;) } - // Tone map to prevent clipping when the source signal peak exceeds the - // encodable range or we've reduced the gamut - if (src.sig_peak > dst.sig_peak) { - pass_tone_map(sc, detect_peak, src.sig_peak, dst.sig_peak, algo, - tone_mapping_param, tone_mapping_desat); - } - if (need_ootf) pass_inverse_ootf(sc, dst.light, dst.sig_peak); @@ -821,8 +816,9 @@ void pass_color_map(struct gl_shader_cache *sc, GLSLF("color.rgb *= vec3(%f);\n", 1.0 / dst_range); // Warn for remaining out-of-gamut colors is enabled - if (gamut_warning) { - GLSL(if (any(greaterThan(color.rgb, vec3(1.01))))) + if (opts->gamut_warning) { + GLSL(if (any(greaterThan(color.rgb, vec3(1.01))) || + any(lessThan(color.rgb, vec3(0.0))))) GLSL(color.rgb = vec3(1.0) - color.rgb;) // invert } diff --git a/video/out/gpu/video_shaders.h b/video/out/gpu/video_shaders.h index cd395d6377..f20d643e99 100644 --- a/video/out/gpu/video_shaders.h +++ b/video/out/gpu/video_shaders.h @@ -40,11 +40,9 @@ 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_color_map(struct gl_shader_cache *sc, +void pass_color_map(struct gl_shader_cache *sc, bool is_linear, struct mp_colorspace src, struct mp_colorspace dst, - enum tone_mapping algo, float tone_mapping_param, - float tone_mapping_desat, bool use_detected_peak, - bool gamut_warning, bool is_linear); + const struct gl_tone_map_opts *opts); void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, AVLFG *lfg, enum mp_csp_trc trc); diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c index 00692f0350..86d0eec404 100644 --- a/video/out/opengl/common.c +++ b/video/out/opengl/common.c @@ -262,6 +262,7 @@ static const struct gl_functions gl_functions[] = { }, { .ver_core = 320, + .ver_es_core = 300, .extension = "GL_ARB_sync", .functions = (const struct gl_function[]) { DEF_FN(FenceSync), diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c index 6aa3d95e79..7e14155d28 100644 --- a/video/out/opengl/context_drm_egl.c +++ b/video/out/opengl/context_drm_egl.c @@ -50,8 +50,8 @@ struct gbm { struct gbm_surface *surface; struct gbm_device *device; - struct gbm_bo *bo; - struct gbm_bo *next_bo; + struct gbm_bo **bo; + unsigned int num_bos; }; struct egl @@ -72,6 +72,9 @@ struct priv { struct gbm gbm; struct framebuffer *fb; + GLsync *vsync_fences; + unsigned int num_vsync_fences; + uint32_t gbm_format; bool active; @@ -80,6 +83,9 @@ struct priv { bool vt_switcher_active; struct vt_switcher vt_switcher; + bool still; + bool paused; + struct mpv_opengl_drm_params drm_params; struct mpv_opengl_drm_draw_surface_size draw_surface_size; }; @@ -355,15 +361,6 @@ static void crtc_release(struct ra_ctx *ctx) return; p->active = false; - // wait for current page flip - while (p->waiting_for_flip) { - int ret = drmHandleEvent(p->kms->fd, &p->ev); - if (ret) { - MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret); - break; - } - } - if (p->kms->atomic_context) { if (p->kms->atomic_context->old_state.saved) { if (!crtc_release_atomic(ctx)) @@ -414,44 +411,19 @@ static void acquire_vt(void *data) crtc_setup(ctx); } -static bool drm_atomic_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo) -{ - struct priv *p = sw->ctx->priv; - if (p->kms->atomic_context) { - if (!p->kms->atomic_context->request) { - p->kms->atomic_context->request = drmModeAtomicAlloc(); - p->drm_params.atomic_request_ptr = &p->kms->atomic_context->request; - } - return ra_gl_ctx_start_frame(sw, out_fbo); - } - return false; -} - -static const struct ra_swapchain_fns drm_atomic_swapchain = { - .start_frame = drm_atomic_egl_start_frame, -}; - -static void drm_egl_swap_buffers(struct ra_ctx *ctx) +static void queue_flip(struct ra_ctx *ctx) { struct priv *p = ctx->priv; struct drm_atomic_context *atomic_ctx = p->kms->atomic_context; int ret; - if (!p->active) - return; - - eglSwapBuffers(p->egl.display, p->egl.surface); - p->gbm.next_bo = gbm_surface_lock_front_buffer(p->gbm.surface); - p->waiting_for_flip = true; - update_framebuffer_from_bo(ctx, p->gbm.next_bo); - if (atomic_ctx) { drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "FB_ID", p->fb->id); drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "CRTC_ID", atomic_ctx->crtc->id); drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "ZPOS", 1); ret = drmModeAtomicCommit(p->kms->fd, atomic_ctx->request, - DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, NULL); + DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, p); if (ret) MP_WARN(ctx->vo, "Failed to commit atomic request (%d)\n", ret); } else { @@ -461,30 +433,129 @@ static void drm_egl_swap_buffers(struct ra_ctx *ctx) MP_WARN(ctx->vo, "Failed to queue page flip: %s\n", mp_strerror(errno)); } } - - // poll page flip finish event - const int timeout_ms = 3000; - struct pollfd fds[1] = { { .events = POLLIN, .fd = p->kms->fd } }; - poll(fds, 1, timeout_ms); - if (fds[0].revents & POLLIN) { - ret = drmHandleEvent(p->kms->fd, &p->ev); - if (ret != 0) { - MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret); - p->waiting_for_flip = false; - return; - } - } - p->waiting_for_flip = false; + p->waiting_for_flip = true; if (atomic_ctx) { drmModeAtomicFree(atomic_ctx->request); atomic_ctx->request = drmModeAtomicAlloc(); } - - gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo); - p->gbm.bo = p->gbm.next_bo; } +static void wait_on_flip(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + + // poll page flip finish event + while (p->waiting_for_flip) { + const int timeout_ms = 3000; + struct pollfd fds[1] = { { .events = POLLIN, .fd = p->kms->fd } }; + poll(fds, 1, timeout_ms); + if (fds[0].revents & POLLIN) { + const int ret = drmHandleEvent(p->kms->fd, &p->ev); + if (ret != 0) + MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret); + } + } +} + +static void swapchain_step(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + + if (p->gbm.num_bos > 0 && p->gbm.bo[0]) + gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo[0]); + MP_TARRAY_REMOVE_AT(p->gbm.bo, p->gbm.num_bos, 0); +} + +static void new_fence(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + + if (p->gl.FenceSync) { + GLsync fence = p->gl.FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + if (fence) + MP_TARRAY_APPEND(p, p->vsync_fences, p->num_vsync_fences, fence); + } +} + +static void wait_fence(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + + while (p->num_vsync_fences && (p->num_vsync_fences >= p->gbm.num_bos)) { + p->gl.ClientWaitSync(p->vsync_fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1e9); + p->gl.DeleteSync(p->vsync_fences[0]); + MP_TARRAY_REMOVE_AT(p->vsync_fences, p->num_vsync_fences, 0); + } +} + +static bool drm_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo) +{ + struct ra_ctx *ctx = sw->ctx; + struct priv *p = ctx->priv; + + if (p->kms->atomic_context && !p->kms->atomic_context->request) { + p->kms->atomic_context->request = drmModeAtomicAlloc(); + p->drm_params.atomic_request_ptr = &p->kms->atomic_context->request; + } + + return ra_gl_ctx_start_frame(sw, out_fbo); +} + +static bool drm_egl_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame) +{ + struct ra_ctx *ctx = sw->ctx; + struct priv *p = ctx->priv; + + p->still = frame->still; + + return ra_gl_ctx_submit_frame(sw, frame); +} + +static void drm_egl_swap_buffers(struct ra_swapchain *sw) +{ + struct ra_ctx *ctx = sw->ctx; + struct priv *p = ctx->priv; + const bool drain = p->paused || p->still; // True when we need to drain the swapchain + + if (!p->active) + return; + + wait_fence(ctx); + + eglSwapBuffers(p->egl.display, p->egl.surface); + + struct gbm_bo *new_bo = gbm_surface_lock_front_buffer(p->gbm.surface); + if (!new_bo) { + MP_ERR(ctx->vo, "Couldn't lock front buffer\n"); + return; + } + MP_TARRAY_APPEND(p, p->gbm.bo, p->gbm.num_bos, new_bo); + new_fence(ctx); + + while (drain || p->gbm.num_bos > ctx->opts.swapchain_depth || !gbm_surface_has_free_buffers(p->gbm.surface)) { + if (p->waiting_for_flip) { + wait_on_flip(ctx); + swapchain_step(ctx); + } + if (p->gbm.num_bos <= 1) + break; + if (!p->gbm.bo[1]) { + MP_ERR(ctx->vo, "Hole in swapchain?\n"); + swapchain_step(ctx); + continue; + } + update_framebuffer_from_bo(ctx, p->gbm.bo[1]); + queue_flip(ctx); + } +} + +static const struct ra_swapchain_fns drm_egl_swapchain = { + .start_frame = drm_egl_start_frame, + .submit_frame = drm_egl_submit_frame, + .swap_buffers = drm_egl_swap_buffers, +}; + static void drm_egl_uninit(struct ra_ctx *ctx) { struct priv *p = ctx->priv; @@ -503,6 +574,12 @@ static void drm_egl_uninit(struct ra_ctx *ctx) if (p->vt_switcher_active) vt_switcher_destroy(&p->vt_switcher); + // According to GBM documentation all BO:s must be released before + // gbm_surface_destroy can be called on the surface. + while (p->gbm.num_bos) { + swapchain_step(ctx); + } + eglMakeCurrent(p->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(p->egl.display, p->egl.context); @@ -565,6 +642,13 @@ static bool probe_gbm_format(struct ra_ctx *ctx, uint32_t argb_format, uint32_t return result; } +static void page_flipped(int fd, unsigned int frame, unsigned int sec, + unsigned int usec, void *data) +{ + struct priv *p = data; + p->waiting_for_flip = false; +} + static bool drm_egl_init(struct ra_ctx *ctx) { if (ctx->opts.probing) { @@ -574,6 +658,7 @@ static bool drm_egl_init(struct ra_ctx *ctx) struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); p->ev.version = DRM_EVENT_CONTEXT_VERSION; + p->ev.page_flip_handler = page_flipped; p->vt_switcher_active = vt_switcher_init(&p->vt_switcher, ctx->vo->log); if (p->vt_switcher_active) { @@ -644,12 +729,13 @@ static bool drm_egl_init(struct ra_ctx *ctx) eglSwapBuffers(p->egl.display, p->egl.surface); MP_VERBOSE(ctx, "Preparing framebuffer\n"); - p->gbm.bo = gbm_surface_lock_front_buffer(p->gbm.surface); - if (!p->gbm.bo) { + struct gbm_bo *new_bo = gbm_surface_lock_front_buffer(p->gbm.surface); + if (!new_bo) { MP_ERR(ctx, "Failed to lock GBM surface.\n"); return false; } - update_framebuffer_from_bo(ctx, p->gbm.bo); + MP_TARRAY_APPEND(p, p->gbm.bo, p->gbm.num_bos, new_bo); + update_framebuffer_from_bo(ctx, new_bo); if (!p->fb || !p->fb->id) { MP_ERR(ctx, "Failed to create framebuffer.\n"); return false; @@ -681,9 +767,7 @@ static bool drm_egl_init(struct ra_ctx *ctx) } struct ra_gl_ctx_params params = { - .swap_buffers = drm_egl_swap_buffers, - .external_swapchain = p->kms->atomic_context ? &drm_atomic_swapchain : - NULL, + .external_swapchain = &drm_egl_swapchain, }; if (!ra_gl_ctx_init(ctx, &p->gl, params)) return false; @@ -715,6 +799,13 @@ static int drm_egl_control(struct ra_ctx *ctx, int *events, int request, *(double*)arg = fps; return VO_TRUE; } + case VOCTRL_PAUSE: + ctx->vo->want_redraw = true; + p->paused = true; + return VO_TRUE; + case VOCTRL_RESUME: + p->paused = false; + return VO_TRUE; } return VO_NOTIMPL; } diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index 45071c8f35..a929c2619e 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -35,13 +35,13 @@ // Generated from xdg-decoration-unstable-v1.xml #include "video/out/wayland/xdg-decoration-v1.h" -static void xdg_shell_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +static void xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) { - xdg_wm_base_pong(shell, serial); + xdg_wm_base_pong(wm_base, serial); } -static const struct xdg_wm_base_listener xdg_shell_listener = { - xdg_shell_ping, +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping, }; static int spawn_cursor(struct vo_wayland_state *wl) @@ -806,8 +806,8 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id if (!strcmp(interface, xdg_wm_base_interface.name) && found++) { ver = MPMIN(ver, 2); /* We can use either 1 or 2 */ - wl->shell = wl_registry_bind(reg, id, &xdg_wm_base_interface, ver); - xdg_wm_base_add_listener(wl->shell, &xdg_shell_listener, wl); + wl->wm_base = wl_registry_bind(reg, id, &xdg_wm_base_interface, ver); + xdg_wm_base_add_listener(wl->wm_base, &xdg_wm_base_listener, wl); } if (!strcmp(interface, wl_seat_interface.name) && found++) { @@ -956,7 +956,7 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = { static int create_xdg_surface(struct vo_wayland_state *wl) { - wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->shell, wl->surface); + wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->wm_base, wl->surface); xdg_surface_add_listener(wl->xdg_surface, &xdg_surface_listener, wl); wl->xdg_toplevel = xdg_surface_get_toplevel(wl->xdg_surface); @@ -1013,7 +1013,7 @@ int vo_wayland_init(struct vo *vo) /* Do a roundtrip to run the registry */ wl_display_roundtrip(wl->display); - if (!wl->shell) { + if (!wl->wm_base) { MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n", xdg_wm_base_interface.name); return false; @@ -1078,8 +1078,8 @@ void vo_wayland_uninit(struct vo *vo) if (wl->idle_inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(wl->idle_inhibit_manager); - if (wl->shell) - xdg_wm_base_destroy(wl->shell); + if (wl->wm_base) + xdg_wm_base_destroy(wl->wm_base); if (wl->shm) wl_shm_destroy(wl->shm); diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index b381d7c46f..0648efa09a 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -70,7 +70,7 @@ struct vo_wayland_state { /* Shell */ struct wl_surface *surface; - struct xdg_wm_base *shell; + struct xdg_wm_base *wm_base; struct xdg_toplevel *xdg_toplevel; struct xdg_surface *xdg_surface; struct zxdg_decoration_manager_v1 *xdg_decoration_manager; diff --git a/wscript_build.py b/wscript_build.py index 091f938275..e08a7fe3f4 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -164,6 +164,7 @@ def build(ctx): if ctx.dependency_satisfied('macos-cocoa-cb'): swift_source = [ ( "osdep/macOS_mpv_helper.swift" ), + ( "osdep/macOS_swift_extensions.swift" ), ( "video/out/cocoa-cb/events_view.swift" ), ( "video/out/cocoa-cb/video_layer.swift" ), ( "video/out/cocoa-cb/window.swift" ), @@ -234,12 +235,10 @@ def build(ctx): ( "audio/out/ao_alsa.c", "alsa" ), ( "audio/out/ao_audiounit.m", "audiounit" ), ( "audio/out/ao_coreaudio.c", "coreaudio" ), - ( "audio/out/ao_coreaudio_chmap.c", "audiounit" ), - ( "audio/out/ao_coreaudio_chmap.c", "coreaudio" ), + ( "audio/out/ao_coreaudio_chmap.c", "coreaudio || audiounit" ), ( "audio/out/ao_coreaudio_exclusive.c", "coreaudio" ), ( "audio/out/ao_coreaudio_properties.c", "coreaudio" ), - ( "audio/out/ao_coreaudio_utils.c", "audiounit" ), - ( "audio/out/ao_coreaudio_utils.c", "coreaudio" ), + ( "audio/out/ao_coreaudio_utils.c", "coreaudio || audiounit" ), ( "audio/out/ao_jack.c", "jack" ), ( "audio/out/ao_lavc.c" ), ( "audio/out/ao_null.c" ),