mac/vulkan: add support for frame timing via presentation feedback

This commit is contained in:
der richter 2024-04-06 23:31:05 +02:00
parent 6df07ce90c
commit ee6794225d
10 changed files with 109 additions and 7 deletions

View File

@ -6358,6 +6358,7 @@ them.
:precise: Syncs to the time of the next vertical display refresh reported by the :precise: Syncs to the time of the next vertical display refresh reported by the
CVDisplayLink callback provided information CVDisplayLink callback provided information
:system: No manual syncing, depend on the layer mechanic and the next drawable :system: No manual syncing, depend on the layer mechanic and the next drawable
:feedback: Same as precise but uses the presentation feedback core mechanism
``--android-surface-size=<WxH>`` ``--android-surface-size=<WxH>``
Set dimensions of the rendering surface used by the Android gpu context. Set dimensions of the rendering surface used by the Android gpu context.

View File

@ -1541,6 +1541,7 @@ if features['cocoa'] and features['swift']
'osdep/mac/menu_bar.swift', 'osdep/mac/menu_bar.swift',
'osdep/mac/option_helper.swift', 'osdep/mac/option_helper.swift',
'osdep/mac/precise_timer.swift', 'osdep/mac/precise_timer.swift',
'osdep/mac/presentation.swift',
'osdep/mac/swift_compat.swift', 'osdep/mac/swift_compat.swift',
'osdep/mac/swift_extensions.swift', 'osdep/mac/swift_extensions.swift',
'osdep/mac/type_helper.swift', 'osdep/mac/type_helper.swift',

View File

@ -29,9 +29,10 @@ enum {
}; };
enum { enum {
RENDER_TIMER_CALLBACK = 0, RENDER_TIMER_PRESENTATION_FEEDBACK = -1,
RENDER_TIMER_PRECISE,
RENDER_TIMER_SYSTEM, RENDER_TIMER_SYSTEM,
RENDER_TIMER_CALLBACK,
RENDER_TIMER_PRECISE,
}; };
struct macos_opts { struct macos_opts {

View File

@ -51,7 +51,7 @@ const struct m_sub_options macos_conf = {
{"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})}, {"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})},
{"macos-render-timer", OPT_CHOICE(macos_render_timer, {"macos-render-timer", OPT_CHOICE(macos_render_timer,
{"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE}, {"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE},
{"system", RENDER_TIMER_SYSTEM})}, {"system", RENDER_TIMER_SYSTEM}, {"feedback", RENDER_TIMER_PRESENTATION_FEEDBACK})},
{"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer, {"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer,
{"auto", -1}, {"no", 0}, {"yes", 1})}, {"auto", -1}, {"no", 0}, {"yes", 1})},
{"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)}, {"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)},
@ -61,6 +61,7 @@ const struct m_sub_options macos_conf = {
.defaults = &(const struct macos_opts){ .defaults = &(const struct macos_opts){
.macos_title_bar_color = {0, 0, 0, 0}, .macos_title_bar_color = {0, 0, 0, 0},
.macos_fs_animation_duration = -1, .macos_fs_animation_duration = -1,
.macos_render_timer = RENDER_TIMER_CALLBACK,
.cocoa_cb_sw_renderer = -1, .cocoa_cb_sw_renderer = -1,
.cocoa_cb_10bit_context = true .cocoa_cb_10bit_context = true
}, },

View File

@ -0,0 +1,56 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
extension Presentation {
struct Time {
let cvTime: CVTimeStamp
var skipped: Int64 = 0
var time: Int64 { return mp_time_ns_from_raw_time(mp_raw_time_ns_from_mach(cvTime.hostTime)) }
var duration: Int64 {
let durationSeconds = Double(cvTime.videoRefreshPeriod) / Double(cvTime.videoTimeScale)
return Int64(durationSeconds * Presentation.nanoPerSecond * cvTime.rateScalar)
}
init(_ time: CVTimeStamp) {
cvTime = time
}
}
}
class Presentation {
unowned var common: Common
var times: [Time] = []
static let nanoPerSecond: Double = 1e+9
init(common com: Common) {
common = com
}
func add(time: CVTimeStamp) {
times.append(Time(time))
}
func next() -> Time? {
let now = mp_time_ns()
let count = times.count
times.removeAll(where: { $0.time <= now })
var time = times.first
time?.skipped = Int64(max(count - times.count - 1, 0))
return time
}
}

View File

@ -36,7 +36,12 @@ void mp_sleep_ns(int64_t ns)
uint64_t mp_raw_time_ns(void) uint64_t mp_raw_time_ns(void)
{ {
return mach_absolute_time() * timebase_ratio_ns; return mp_raw_time_ns_from_mach(mach_absolute_time());
}
uint64_t mp_raw_time_ns_from_mach(uint64_t mach_time)
{
return mach_time * timebase_ratio_ns;
} }
void mp_raw_time_init(void) void mp_raw_time_init(void)

View File

@ -46,7 +46,12 @@ void mp_time_init(void)
int64_t mp_time_ns(void) int64_t mp_time_ns(void)
{ {
return mp_raw_time_ns() - raw_time_offset; return mp_time_ns_from_raw_time(mp_raw_time_ns());
}
int64_t mp_time_ns_from_raw_time(uint64_t raw_time)
{
return raw_time - raw_time_offset;
} }
double mp_time_sec(void) double mp_time_sec(void)

View File

@ -19,6 +19,7 @@
#define MPLAYER_TIMER_H #define MPLAYER_TIMER_H
#include <inttypes.h> #include <inttypes.h>
#include "config.h"
// Initialize timer, must be called at least once at start. // Initialize timer, must be called at least once at start.
void mp_time_init(void); void mp_time_init(void);
@ -26,6 +27,9 @@ void mp_time_init(void);
// Return time in nanoseconds. Never wraps. Never returns negative values. // Return time in nanoseconds. Never wraps. Never returns negative values.
int64_t mp_time_ns(void); int64_t mp_time_ns(void);
// Return time in nanoseconds. Coverts raw time in nanoseconds to mp time, subtracts init offset.
int64_t mp_time_ns_from_raw_time(uint64_t raw_time);
// Return time in seconds. Can have down to 1 nanosecond resolution, but will // Return time in seconds. Can have down to 1 nanosecond resolution, but will
// be much worse when casted to float. // be much worse when casted to float.
double mp_time_sec(void); double mp_time_sec(void);
@ -38,6 +42,11 @@ uint64_t mp_raw_time_ns(void);
// Sleep in nanoseconds. // Sleep in nanoseconds.
void mp_sleep_ns(int64_t ns); void mp_sleep_ns(int64_t ns);
#if HAVE_DARWIN
// Coverts mach time to raw time in nanoseconds and returns it.
uint64_t mp_raw_time_ns_from_mach(uint64_t mach_time);
#endif
#ifdef _WIN32 #ifdef _WIN32
// returns: timer resolution in ns if needed and started successfully, else 0 // returns: timer resolution in ns if needed and started successfully, else 0
int64_t mp_start_hires_timers(int64_t wait_ns); int64_t mp_start_hires_timers(int64_t wait_ns);

View File

@ -20,6 +20,7 @@ import Cocoa
class MacCommon: Common { class MacCommon: Common {
@objc var layer: MetalLayer? @objc var layer: MetalLayer?
var presentation: Presentation?
var timer: PreciseTimer? var timer: PreciseTimer?
var swapTime: UInt64 = 0 var swapTime: UInt64 = 0
let swapLock: NSCondition = NSCondition() let swapLock: NSCondition = NSCondition()
@ -30,6 +31,7 @@ class MacCommon: Common {
super.init(option, log) super.init(option, log)
self.vo = vo self.vo = vo
input = InputHelper(vo.pointee.input_ctx, option) input = InputHelper(vo.pointee.input_ctx, option)
presentation = Presentation(common: self)
timer = PreciseTimer(common: self) timer = PreciseTimer(common: self)
DispatchQueue.main.sync { DispatchQueue.main.sync {
@ -89,7 +91,7 @@ class MacCommon: Common {
} }
@objc func swapBuffer() { @objc func swapBuffer() {
if option.mac.macos_render_timer != RENDER_TIMER_SYSTEM { if option.mac.macos_render_timer > RENDER_TIMER_SYSTEM {
swapLock.lock() swapLock.lock()
while(swapTime < 1) { while(swapTime < 1) {
swapLock.wait() swapLock.wait()
@ -99,6 +101,15 @@ class MacCommon: Common {
} }
} }
@objc func fillVsync(info: UnsafeMutablePointer<vo_vsync_info>) {
if option.mac.macos_render_timer != RENDER_TIMER_PRESENTATION_FEEDBACK { return }
let next = presentation?.next()
info.pointee.vsync_duration = next?.duration ?? -1
info.pointee.skipped_vsyncs = next?.skipped ?? -1
info.pointee.last_queue_display_time = next?.time ?? -1
}
override func displayLinkCallback(_ displayLink: CVDisplayLink, override func displayLinkCallback(_ displayLink: CVDisplayLink,
_ inNow: UnsafePointer<CVTimeStamp>, _ inNow: UnsafePointer<CVTimeStamp>,
_ inOutputTime: UnsafePointer<CVTimeStamp>, _ inOutputTime: UnsafePointer<CVTimeStamp>,
@ -112,13 +123,18 @@ class MacCommon: Common {
self.swapLock.unlock() self.swapLock.unlock()
} }
if option.mac.macos_render_timer != RENDER_TIMER_SYSTEM { if option.mac.macos_render_timer > RENDER_TIMER_SYSTEM {
if let timer = self.timer, option.mac.macos_render_timer == RENDER_TIMER_PRECISE { if let timer = self.timer, option.mac.macos_render_timer == RENDER_TIMER_PRECISE {
timer.scheduleAt(time: inOutputTime.pointee.hostTime, closure: signalSwap) timer.scheduleAt(time: inOutputTime.pointee.hostTime, closure: signalSwap)
return kCVReturnSuccess return kCVReturnSuccess
} }
signalSwap() signalSwap()
return kCVReturnSuccess
}
if option.mac.macos_render_timer == RENDER_TIMER_PRESENTATION_FEEDBACK {
presentation?.add(time: inOutputTime.pointee)
} }
return kCVReturnSuccess return kCVReturnSuccess

View File

@ -44,6 +44,12 @@ static void mac_vk_swap_buffers(struct ra_ctx *ctx)
[p->vo_mac swapBuffer]; [p->vo_mac swapBuffer];
} }
static void mac_vk_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
{
struct priv *p = ctx->priv;
[p->vo_mac fillVsyncWithInfo:info];
}
static bool mac_vk_init(struct ra_ctx *ctx) static bool mac_vk_init(struct ra_ctx *ctx)
{ {
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
@ -66,6 +72,7 @@ static bool mac_vk_init(struct ra_ctx *ctx)
struct ra_vk_ctx_params params = { struct ra_vk_ctx_params params = {
.swap_buffers = mac_vk_swap_buffers, .swap_buffers = mac_vk_swap_buffers,
.get_vsync = mac_vk_get_vsync,
}; };
VkInstance inst = vk->vkinst->instance; VkInstance inst = vk->vkinst->instance;