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-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
@ -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:

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'),
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',
@ -1561,7 +1563,7 @@ 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()}
@ -1569,9 +1571,14 @@ 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()}
@ -1579,7 +1586,7 @@ 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

View File

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

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,
};
enum {
RENDER_TIMER_CALLBACK = 0,
RENDER_TIMER_PRECISE,
RENDER_TIMER_SYSTEM,
};
struct macos_opts {
int macos_title_bar_style;
int macos_title_bar_appearance;
@ -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;
};

View File

@ -67,6 +67,9 @@ const struct m_sub_options macos_conf = {
{"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)},

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_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;
@ -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: */

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
#define VK_USE_PLATFORM_WIN32_KHR
#endif
#if HAVE_COCOA
#define VK_USE_PLATFORM_MACOS_MVK
#define VK_USE_PLATFORM_METAL_EXT
#endif
#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,
};