Skip to content

Commit

Permalink
vo_gpu/vo_gpu_next: add vulkan support for macOS
Browse files Browse the repository at this point in the history
add support for vulkan through metal and a translation layer like
MoltenVK. also add the possibility to use different render timing modes
for testing.

i still consider this experimental atm.
  • Loading branch information
Akemi committed Oct 14, 2023
1 parent bc66de2 commit 78d4374
Show file tree
Hide file tree
Showing 11 changed files with 538 additions and 6 deletions.
14 changes: 14 additions & 0 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6273,6 +6273,18 @@ them.

macOS only.

``--macos-render-timer=<timer>``
Sets the mode (default: callback) for syncing the rendering of frames to the display's
vertical refresh rate.
macOS and Vulkan (macvk) only.

``<timer>`` can be one of the following:

:callback: Syncs to the CVDisplayLink callback
:precise: Syncs to the time of the next vertical display refresh reported by the
CVDisplayLink callback provided information
:system: No manual syncing, depend on the layer mechanic and the next drawable

``--android-surface-size=<WxH>``
Set dimensions of the rendering surface used by the Android gpu context.
Needs to be set by the embedding application if the dimensions change during
Expand Down Expand Up @@ -6324,6 +6336,8 @@ them.
X11/EGL
android
Android/EGL. Requires ``--wid`` be set to an ``android.view.Surface``.
macvk
Vulkan on macOS with a metal surface through a translation layer (experimental)

``--gpu-api=<type>``
Controls which type of graphics APIs will be accepted:
Expand Down
15 changes: 11 additions & 4 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -1546,12 +1546,14 @@ swift = get_option('swift-build').require(
darwin and macos_sdk_version.version_compare('>=10.10') and swift_ver.version_compare('>=4.1'),
error_message: 'A suitable macos sdk version or swift version could not be found!',
)
features += {'swift': swift.allowed()}

swift_sources = []
if cocoa.found() and swift.allowed()
if features['cocoa'] and features['swift']
swift_sources += files('osdep/macos/libmpv_helper.swift',
'osdep/macos/log_helper.swift',
'osdep/macos/mpv_helper.swift',
'osdep/macos/precise_timer.swift',
'osdep/macos/swift_compat.swift',
'osdep/macos/swift_extensions.swift',
'video/out/mac/common.swift',
Expand All @@ -1561,25 +1563,30 @@ if cocoa.found() and swift.allowed()
endif

macos_cocoa_cb = get_option('macos-cocoa-cb').require(
features['cocoa'] and swift.allowed(),
features['cocoa'] and features['swift'],
error_message: 'Either cocoa or swift could not be found!',
)
features += {'macos-cocoa-cb': macos_cocoa_cb.allowed()}
if features['macos-cocoa-cb']
swift_sources += files('video/out/cocoa_cb_common.swift',
'video/out/mac/gl_layer.swift')
endif
if features['cocoa'] and features['vulkan'] and features['swift']
swift_sources += files('video/out/mac_common.swift',
'video/out/mac/metal_layer.swift')
sources += files('video/out/vulkan/context_mac.m')
endif

macos_media_player = get_option('macos-media-player').require(
macos_10_12_2_features.allowed() and swift.allowed(),
macos_10_12_2_features.allowed() and features['swift'],
error_message: 'Either the macos sdk version is not at least 10.12.2 or swift was not found!',
)
features += {'macos-media-player': macos_media_player.allowed()}
if features['macos-media-player']
swift_sources += files('osdep/macos/remote_command_center.swift')
endif

if swift.allowed() and swift_sources.length() > 0
if features['swift'] and swift_sources.length() > 0
subdir('osdep')
endif

Expand Down
5 changes: 3 additions & 2 deletions osdep/macOS_swift_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/

// including IOKit here again doesn't make sense, but otherwise the swift
// compiler doesn't include the needed header in our generated header file
// including frameworks here again doesn't make sense, but otherwise the swift
// compiler doesn't include the needed headers in our generated header file
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <QuartzCore/QuartzCore.h>

#include "player/client.h"
#include "video/out/libmpv.h"
Expand Down
153 changes: 153 additions & 0 deletions osdep/macos/precise_timer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* 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/>.
*/

import Cocoa

struct Timing {
let time: UInt64
let closure: () -> ()
}

class PreciseTimer {
unowned var common: Common
var mpv: MPVHelper? { get { return common.mpv } }

let nanoPerSecond: Double = 1e+9
let machToNano: Double = {
var timebase: mach_timebase_info = mach_timebase_info()
mach_timebase_info(&timebase)
return Double(timebase.numer) / Double(timebase.denom)
}()

let condition = NSCondition()
var events: [Timing] = []
var isRunning: Bool = true
var isHighPrecision: Bool = false

var thread: pthread_t!
var threadPort: thread_port_t = thread_port_t()
let policyFlavor = thread_policy_flavor_t(THREAD_TIME_CONSTRAINT_POLICY)
let policyCount = MemoryLayout<thread_time_constraint_policy>.size /
MemoryLayout<integer_t>.size
var typeNumber: mach_msg_type_number_t {
return mach_msg_type_number_t(policyCount)
}
var threadAttr: pthread_attr_t = {
var attr = pthread_attr_t()
var param = sched_param()
pthread_attr_init(&attr)
param.sched_priority = sched_get_priority_max(SCHED_FIFO)
pthread_attr_setschedparam(&attr, &param)
pthread_attr_setschedpolicy(&attr, SCHED_FIFO)
return attr
}()

init?(common com: Common) {
common = com

pthread_create(&thread, &threadAttr, entryC, MPVHelper.bridge(obj: self))
if thread == nil {
common.log.sendWarning("Couldn't create pthread for high precision timer")
return nil
}

threadPort = pthread_mach_thread_np(thread)
}

func updatePolicy(periodSeconds: Double = 1 / 60.0) {
let period = periodSeconds * nanoPerSecond / machToNano
var policy = thread_time_constraint_policy(
period: UInt32(period),
computation: UInt32(0.75 * period),
constraint: UInt32(0.85 * period),
preemptible: 1
)

let success = withUnsafeMutablePointer(to: &policy) {
$0.withMemoryRebound(to: integer_t.self, capacity: policyCount) {
thread_policy_set(threadPort, policyFlavor, $0, typeNumber)
}
}

isHighPrecision = success == KERN_SUCCESS
if !isHighPrecision {
common.log.sendWarning("Couldn't create a high precision timer")
}
}

func terminate() {
condition.lock()
isRunning = false
condition.signal()
condition.unlock()
pthread_kill(thread, SIGALRM)
pthread_join(thread, nil)
}

func scheduleAt(time: UInt64, closure: @escaping () -> ()) {
condition.lock()
let firstEventTime = events.first?.time ?? 0
let lastEventTime = events.last?.time ?? 0
events.append(Timing(time: time, closure: closure))

if lastEventTime > time {
events.sort{ $0.time < $1.time }
}

condition.signal()
condition.unlock()

if firstEventTime > time {
pthread_kill(thread, SIGALRM)
}
}

let threadSignal: @convention(c) (Int32) -> () = { (sig: Int32) in }

let entryC: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in
let ptimer: PreciseTimer = MPVHelper.bridge(ptr: ptr)
ptimer.entry()
return nil
}

func entry() {
signal(SIGALRM, threadSignal)

while isRunning {
condition.lock()
while events.count == 0 && isRunning {
condition.wait()
}

if !isRunning { break }

guard let event = events.first else {
continue
}
condition.unlock()

mach_wait_until(event.time)

condition.lock()
if events.first?.time == event.time && isRunning {
event.closure()
events.removeFirst()
}
condition.unlock()
}
}
}
7 changes: 7 additions & 0 deletions osdep/macosx_application.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ enum {
FRAME_WHOLE,
};

enum {
RENDER_TIMER_CALLBACK = 0,
RENDER_TIMER_PRECISE,
RENDER_TIMER_SYSTEM,
};

struct macos_opts {
int macos_title_bar_style;
int macos_title_bar_appearance;
Expand All @@ -35,6 +41,7 @@ struct macos_opts {
bool macos_force_dedicated_gpu;
int macos_app_activation_policy;
int macos_geometry_calculation;
int macos_render_timer;
int cocoa_cb_sw_renderer;
bool cocoa_cb_10bit_context;
};
Expand Down
3 changes: 3 additions & 0 deletions osdep/macosx_application.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
{"regular", 0}, {"accessory", 1}, {"prohibited", 2})},
{"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation,
{"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})},
{"macos-render-timer", OPT_CHOICE(macos_render_timer,
{"callback", RENDER_TIMER_CALLBACK}, {"precise", RENDER_TIMER_PRECISE},
{"system", RENDER_TIMER_SYSTEM})},
{"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer,
{"auto", -1}, {"no", 0}, {"yes", 1})},
{"cocoa-cb-10bit-context", OPT_BOOL(cocoa_cb_10bit_context)},
Expand Down
4 changes: 4 additions & 0 deletions video/out/gpu/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ extern const struct ra_ctx_fns ra_ctx_vulkan_win;
extern const struct ra_ctx_fns ra_ctx_vulkan_xlib;
extern const struct ra_ctx_fns ra_ctx_vulkan_android;
extern const struct ra_ctx_fns ra_ctx_vulkan_display;
extern const struct ra_ctx_fns ra_ctx_vulkan_mac;

/* Direct3D 11 */
extern const struct ra_ctx_fns ra_ctx_d3d11;
Expand Down Expand Up @@ -113,6 +114,9 @@ static const struct ra_ctx_fns *contexts[] = {
#if HAVE_VK_KHR_DISPLAY
&ra_ctx_vulkan_display,
#endif
#if HAVE_COCOA && HAVE_SWIFT
&ra_ctx_vulkan_mac,
#endif
#endif

/* No API contexts: */
Expand Down
43 changes: 43 additions & 0 deletions video/out/mac/metal_layer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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/>.
*/

import Cocoa

class MetalLayer: CAMetalLayer {
unowned var common: MacCommon

init(common com: MacCommon) {
common = com
super.init()

pixelFormat = .rgba16Float
backgroundColor = NSColor.black.cgColor
}

// necessary for when the layer containing window changes the screen
override init(layer: Any) {
guard let oldLayer = layer as? MetalLayer else {
fatalError("init(layer: Any) passed an invalid layer")
}
common = oldLayer.common
super.init()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Loading

0 comments on commit 78d4374

Please sign in to comment.