vo_gpu/vo_gpu_next: add vulkan support for macOS

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.
This commit is contained in:
der richter 2023-09-30 16:01:04 +02:00
parent bc66de2834
commit 78d43740f5
11 changed files with 538 additions and 6 deletions

View File

@ -6273,6 +6273,18 @@ them.
macOS only. 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>`` ``--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.
Needs to be set by the embedding application if the dimensions change during Needs to be set by the embedding application if the dimensions change during
@ -6324,6 +6336,8 @@ them.
X11/EGL X11/EGL
android android
Android/EGL. Requires ``--wid`` be set to an ``android.view.Surface``. 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>`` ``--gpu-api=<type>``
Controls which type of graphics APIs will be accepted: Controls which type of graphics APIs will be accepted:

View File

@ -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'), 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!', error_message: 'A suitable macos sdk version or swift version could not be found!',
) )
features += {'swift': swift.allowed()}
swift_sources = [] swift_sources = []
if cocoa.found() and swift.allowed() if features['cocoa'] and features['swift']
swift_sources += files('osdep/macos/libmpv_helper.swift', swift_sources += files('osdep/macos/libmpv_helper.swift',
'osdep/macos/log_helper.swift', 'osdep/macos/log_helper.swift',
'osdep/macos/mpv_helper.swift', 'osdep/macos/mpv_helper.swift',
'osdep/macos/precise_timer.swift',
'osdep/macos/swift_compat.swift', 'osdep/macos/swift_compat.swift',
'osdep/macos/swift_extensions.swift', 'osdep/macos/swift_extensions.swift',
'video/out/mac/common.swift', 'video/out/mac/common.swift',
@ -1561,7 +1563,7 @@ if cocoa.found() and swift.allowed()
endif endif
macos_cocoa_cb = get_option('macos-cocoa-cb').require( 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!', error_message: 'Either cocoa or swift could not be found!',
) )
features += {'macos-cocoa-cb': macos_cocoa_cb.allowed()} features += {'macos-cocoa-cb': macos_cocoa_cb.allowed()}
@ -1569,9 +1571,14 @@ if features['macos-cocoa-cb']
swift_sources += files('video/out/cocoa_cb_common.swift', swift_sources += files('video/out/cocoa_cb_common.swift',
'video/out/mac/gl_layer.swift') 'video/out/mac/gl_layer.swift')
endif 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_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!', 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()} features += {'macos-media-player': macos_media_player.allowed()}
@ -1579,7 +1586,7 @@ if features['macos-media-player']
swift_sources += files('osdep/macos/remote_command_center.swift') swift_sources += files('osdep/macos/remote_command_center.swift')
endif endif
if swift.allowed() and swift_sources.length() > 0 if features['swift'] and swift_sources.length() > 0
subdir('osdep') subdir('osdep')
endif endif

View File

@ -15,9 +15,10 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>. * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/ */
// including IOKit here again doesn't make sense, but otherwise the swift // including frameworks here again doesn't make sense, but otherwise the swift
// compiler doesn't include the needed header in our generated header file // compiler doesn't include the needed headers in our generated header file
#import <IOKit/pwr_mgt/IOPMLib.h> #import <IOKit/pwr_mgt/IOPMLib.h>
#import <QuartzCore/QuartzCore.h>
#include "player/client.h" #include "player/client.h"
#include "video/out/libmpv.h" #include "video/out/libmpv.h"

View File

@ -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()
}
}
}

View File

@ -26,6 +26,12 @@ enum {
FRAME_WHOLE, FRAME_WHOLE,
}; };
enum {
RENDER_TIMER_CALLBACK = 0,
RENDER_TIMER_PRECISE,
RENDER_TIMER_SYSTEM,
};
struct macos_opts { struct macos_opts {
int macos_title_bar_style; int macos_title_bar_style;
int macos_title_bar_appearance; int macos_title_bar_appearance;
@ -35,6 +41,7 @@ struct macos_opts {
bool macos_force_dedicated_gpu; bool macos_force_dedicated_gpu;
int macos_app_activation_policy; int macos_app_activation_policy;
int macos_geometry_calculation; int macos_geometry_calculation;
int macos_render_timer;
int cocoa_cb_sw_renderer; int cocoa_cb_sw_renderer;
bool cocoa_cb_10bit_context; bool cocoa_cb_10bit_context;
}; };

View File

@ -67,6 +67,9 @@ const struct m_sub_options macos_conf = {
{"regular", 0}, {"accessory", 1}, {"prohibited", 2})}, {"regular", 0}, {"accessory", 1}, {"prohibited", 2})},
{"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation, {"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation,
{"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})}, {"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, {"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)},

View File

@ -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_xlib;
extern const struct ra_ctx_fns ra_ctx_vulkan_android; 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_display;
extern const struct ra_ctx_fns ra_ctx_vulkan_mac;
/* Direct3D 11 */ /* Direct3D 11 */
extern const struct ra_ctx_fns ra_ctx_d3d11; extern const struct ra_ctx_fns ra_ctx_d3d11;
@ -113,6 +114,9 @@ static const struct ra_ctx_fns *contexts[] = {
#if HAVE_VK_KHR_DISPLAY #if HAVE_VK_KHR_DISPLAY
&ra_ctx_vulkan_display, &ra_ctx_vulkan_display,
#endif #endif
#if HAVE_COCOA && HAVE_SWIFT
&ra_ctx_vulkan_mac,
#endif
#endif #endif
/* No API contexts: */ /* No API contexts: */

View File

@ -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")
}
}

177
video/out/mac_common.swift Normal file
View File

@ -0,0 +1,177 @@
/*
* 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 MacCommon: Common {
@objc var layer: MetalLayer?
var timer: PreciseTimer?
var swapTime: UInt64 = 0
let swapLock: NSCondition = NSCondition()
var needsICCUpdate: Bool = false
@objc init(_ vo: UnsafeMutablePointer<vo>) {
let newlog = mp_log_new(vo, vo.pointee.log, "mac")
super.init(newlog)
mpv = MPVHelper(vo, log)
timer = PreciseTimer(common: self)
DispatchQueue.main.sync {
layer = MetalLayer(common: self)
initMisc(vo)
}
}
@objc func config(_ vo: UnsafeMutablePointer<vo>) -> Bool {
mpv?.vo = vo
DispatchQueue.main.sync {
let previousActiveApp = getActiveApp()
initApp()
let (_, _, wr) = getInitProperties(vo)
guard let layer = self.layer else {
log.sendError("Something went wrong, no MetalLayer was initialized")
exit(1)
}
if window == nil {
initView(vo, layer)
initWindow(vo, previousActiveApp)
initWindowState()
}
if !NSEqualSizes(window?.unfsContentFramePixel.size ?? NSZeroSize, wr.size) {
window?.updateSize(wr.size)
}
windowDidResize()
needsICCUpdate = true
}
return true
}
@objc func uninit(_ vo: UnsafeMutablePointer<vo>) {
window?.waitForAnimation()
timer?.terminate()
DispatchQueue.main.sync {
window?.delegate = nil
window?.close()
uninitCommon()
}
}
@objc func swapBuffer() {
if mpv?.macOpts.macos_render_timer ?? Int32(RENDER_TIMER_CALLBACK) != RENDER_TIMER_SYSTEM {
swapLock.lock()
while(swapTime < 1) {
swapLock.wait()
}
swapTime = 0
swapLock.unlock()
}
if needsICCUpdate {
needsICCUpdate = false
updateICCProfile()
}
}
func updateRenderSize(_ size: NSSize) {
mpv?.vo.pointee.dwidth = Int32(size.width)
mpv?.vo.pointee.dheight = Int32(size.height)
flagEvents(VO_EVENT_RESIZE | VO_EVENT_EXPOSE)
}
override func displayLinkCallback(_ displayLink: CVDisplayLink,
_ inNow: UnsafePointer<CVTimeStamp>,
_ inOutputTime: UnsafePointer<CVTimeStamp>,
_ flagsIn: CVOptionFlags,
_ flagsOut: UnsafeMutablePointer<CVOptionFlags>) -> CVReturn
{
let frameTimer = mpv?.macOpts.macos_render_timer ?? Int32(RENDER_TIMER_CALLBACK)
let signalSwap = { [self] in
swapLock.lock()
swapTime += 1
swapLock.signal()
swapLock.unlock()
}
if frameTimer != RENDER_TIMER_SYSTEM {
if let timer = self.timer, frameTimer == RENDER_TIMER_PRECISE {
timer.scheduleAt(time: inOutputTime.pointee.hostTime, closure: signalSwap)
return kCVReturnSuccess
}
signalSwap()
}
return kCVReturnSuccess
}
override func startDisplayLink(_ vo: UnsafeMutablePointer<vo>) {
super.startDisplayLink(vo)
timer?.updatePolicy(periodSeconds: 1 / currentFps())
}
override func updateDisplaylink() {
super.updateDisplaylink()
timer?.updatePolicy(periodSeconds: 1 / currentFps())
}
override func lightSensorUpdate() {
flagEvents(VO_EVENT_AMBIENT_LIGHTING_CHANGED)
}
@objc override func updateICCProfile() {
guard let colorSpace = window?.screen?.colorSpace else {
log.sendWarning("Couldn't update ICC Profile, no color space available")
return
}
if #available(macOS 10.11, *) {
layer?.colorspace = colorSpace.cgColorSpace
}
flagEvents(VO_EVENT_ICC_PROFILE_CHANGED)
}
override func windowDidResize() {
guard let window = window else {
log.sendWarning("No window available on window resize event")
return
}
updateRenderSize(window.framePixel.size)
}
override func windowDidChangeScreenProfile() {
needsICCUpdate = true
}
override func windowDidChangeBackingProperties() {
layer?.contentsScale = window?.backingScaleFactor ?? 1
windowDidResize()
}
}

View File

@ -22,6 +22,10 @@
#if HAVE_WIN32_DESKTOP #if HAVE_WIN32_DESKTOP
#define VK_USE_PLATFORM_WIN32_KHR #define VK_USE_PLATFORM_WIN32_KHR
#endif #endif
#if HAVE_COCOA
#define VK_USE_PLATFORM_MACOS_MVK
#define VK_USE_PLATFORM_METAL_EXT
#endif
#include <libplacebo/vulkan.h> #include <libplacebo/vulkan.h>

View File

@ -0,0 +1,119 @@
/*
* 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/>.
*/
#include "video/out/gpu/context.h"
#include "osdep/macOS_swift.h"
#include "common.h"
#include "context.h"
#include "utils.h"
struct priv {
struct mpvk_ctx vk;
MacCommon *vo_mac;
};
static void mac_vk_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
ra_vk_ctx_uninit(ctx);
mpvk_uninit(&p->vk);
[p->vo_mac uninit:ctx->vo];
}
static void mac_vk_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
[p->vo_mac swapBuffer];
}
static bool mac_vk_init(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
struct mpvk_ctx *vk = &p->vk;
int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
if (!mpvk_init(vk, ctx, VK_EXT_METAL_SURFACE_EXTENSION_NAME))
goto error;
p->vo_mac = [[MacCommon alloc] init:ctx->vo];
if (!p->vo_mac)
goto error;
VkMetalSurfaceCreateInfoEXT mac_info = {
.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK,
.pNext = NULL,
.flags = 0,
.pLayer = p->vo_mac.layer,
};
struct ra_vk_ctx_params params = {
.swap_buffers = mac_vk_swap_buffers,
};
VkInstance inst = vk->vkinst->instance;
VkResult res = vkCreateMetalSurfaceEXT(inst, &mac_info, NULL, &vk->surface);
if (res != VK_SUCCESS) {
MP_MSG(ctx, msgl, "Failed creating metal surface\n");
goto error;
}
if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_FIFO_KHR))
goto error;
return true;
error:
if (p->vo_mac)
[p->vo_mac uninit:ctx->vo];
return false;
}
static bool resize(struct ra_ctx *ctx)
{
return ra_vk_ctx_resize(ctx, ctx->vo->dwidth, ctx->vo->dheight);
}
static bool mac_vk_reconfig(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (![p->vo_mac config:ctx->vo])
return false;
return true;
}
static int mac_vk_control(struct ra_ctx *ctx, int *events, int request, void *arg)
{
struct priv *p = ctx->priv;
int ret = [p->vo_mac control:ctx->vo events:events request:request data:arg];
if (*events & VO_EVENT_RESIZE) {
if (!resize(ctx))
return VO_ERROR;
}
return ret;
}
const struct ra_ctx_fns ra_ctx_vulkan_mac = {
.type = "vulkan",
.name = "macvk",
.reconfig = mac_vk_reconfig,
.control = mac_vk_control,
.init = mac_vk_init,
.uninit = mac_vk_uninit,
};