mirror of
https://github.com/yattee/yattee.git
synced 2024-12-23 05:53:41 +00:00
5e85fd294c
- use displays refresh rate - execute needs drawing with higher priority - run create() with higher priority - determine the number of threads used for rendering - enable VSYNC and change video-sync to display-resample - iOS/tvOS: set new display refresh rate on change - run setSize with higher priority - add more options to MPVClient - get refresh rate updates - sync refresh rate to fps - update CADisplayLink to current refresh rate - update refresh rate on macOS - Add experimental feature to sync display with content fps Signed-off-by: Toni Förster <toni.foerster@gmail.com>
141 lines
5.0 KiB
Swift
141 lines
5.0 KiB
Swift
import GLKit
|
|
import Libmpv
|
|
import Logging
|
|
import OpenGLES
|
|
|
|
final class MPVOGLView: GLKView {
|
|
private var logger = Logger(label: "stream.yattee.mpv.oglview")
|
|
private var defaultFBO: GLint?
|
|
private var displayLink: CADisplayLink?
|
|
|
|
var mpvGL: UnsafeMutableRawPointer?
|
|
var queue = DispatchQueue(label: "stream.yattee.opengl", qos: .userInteractive)
|
|
var needsDrawing = true
|
|
|
|
override init(frame: CGRect) {
|
|
guard let context = EAGLContext(api: .openGLES2) else {
|
|
print("Failed to initialize OpenGLES 2.0 context")
|
|
exit(1)
|
|
}
|
|
|
|
logger.info("frame size: \(frame.width) x \(frame.height)")
|
|
|
|
super.init(frame: frame, context: context)
|
|
|
|
self.context = context
|
|
bindDrawable()
|
|
|
|
defaultFBO = -1
|
|
isOpaque = true
|
|
enableSetNeedsDisplay = false
|
|
|
|
fillBlack()
|
|
setupDisplayLink()
|
|
setupNotifications()
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
super.init(coder: aDecoder)
|
|
setupDisplayLink()
|
|
setupNotifications()
|
|
}
|
|
|
|
private func setupDisplayLink() {
|
|
displayLink = CADisplayLink(target: self, selector: #selector(updateFrame))
|
|
displayLink?.add(to: .main, forMode: .common)
|
|
}
|
|
|
|
// Set up observers to detect display changes and custom refresh rate updates.
|
|
private func setupNotifications() {
|
|
NotificationCenter.default.addObserver(self, selector: #selector(updateDisplayLinkFromNotification(_:)), name: .updateDisplayLinkFrameRate, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(screenDidChange), name: UIScreen.didConnectNotification, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(screenDidChange), name: UIScreen.didDisconnectNotification, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(screenDidChange), name: UIScreen.modeDidChangeNotification, object: nil)
|
|
}
|
|
|
|
@objc private func screenDidChange(_: Notification) {
|
|
// Update the display link refresh rate when the screen configuration changes
|
|
updateDisplayLinkFrameRate()
|
|
}
|
|
|
|
// Update the display link frame rate from the notification.
|
|
@objc private func updateDisplayLinkFromNotification(_ notification: Notification) {
|
|
guard let userInfo = notification.userInfo,
|
|
let refreshRate = userInfo["refreshRate"] as? Int else { return }
|
|
displayLink?.preferredFramesPerSecond = refreshRate
|
|
logger.info("Updated CADisplayLink frame rate to: \(refreshRate) from backend notification.")
|
|
}
|
|
|
|
// Update the display link's preferred frame rate based on the current screen refresh rate.
|
|
private func updateDisplayLinkFrameRate() {
|
|
guard let displayLink else { return }
|
|
let refreshRate = getScreenRefreshRate()
|
|
displayLink.preferredFramesPerSecond = refreshRate
|
|
logger.info("Updated CADisplayLink preferred frames per second to: \(refreshRate)")
|
|
}
|
|
|
|
// Retrieve the screen's current refresh rate dynamically.
|
|
private func getScreenRefreshRate() -> Int {
|
|
// Use the main screen's maximumFramesPerSecond property
|
|
let refreshRate = UIScreen.main.maximumFramesPerSecond
|
|
logger.info("Screen refresh rate: \(refreshRate) Hz")
|
|
return refreshRate
|
|
}
|
|
|
|
@objc private func updateFrame() {
|
|
// Trigger the drawing process if needed
|
|
if needsDrawing {
|
|
setNeedsDisplay()
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
// Invalidate the display link and remove observers to avoid memory leaks
|
|
displayLink?.invalidate()
|
|
NotificationCenter.default.removeObserver(self)
|
|
}
|
|
|
|
func fillBlack() {
|
|
glClearColor(0, 0, 0, 0)
|
|
glClear(UInt32(GL_COLOR_BUFFER_BIT))
|
|
}
|
|
|
|
override func draw(_: CGRect) {
|
|
guard needsDrawing, let mpvGL else { return }
|
|
|
|
// Bind the default framebuffer
|
|
glGetIntegerv(UInt32(GL_FRAMEBUFFER_BINDING), &defaultFBO!)
|
|
|
|
// Get the current viewport dimensions
|
|
var dims: [GLint] = [0, 0, 0, 0]
|
|
glGetIntegerv(GLenum(GL_VIEWPORT), &dims)
|
|
|
|
// Set up the OpenGL FBO data
|
|
var data = mpv_opengl_fbo(
|
|
fbo: Int32(defaultFBO!),
|
|
w: Int32(dims[2]),
|
|
h: Int32(dims[3]),
|
|
internal_format: 0
|
|
)
|
|
|
|
// Flip Y coordinate for proper rendering
|
|
var flip: CInt = 1
|
|
|
|
// Render with the provided OpenGL FBO parameters
|
|
withUnsafeMutablePointer(to: &flip) { flipPtr in
|
|
withUnsafeMutablePointer(to: &data) { dataPtr in
|
|
var params = [
|
|
mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: dataPtr),
|
|
mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: flipPtr),
|
|
mpv_render_param()
|
|
]
|
|
mpv_render_context_render(OpaquePointer(mpvGL), ¶ms)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Notification.Name {
|
|
static let updateDisplayLinkFrameRate = Notification.Name("updateDisplayLinkFrameRate")
|
|
}
|