【问题标题】:CVDisplayLink with SwiftCVDisplayLink 与 Swift
【发布时间】:2014-09-22 19:16:00
【问题描述】:

我正在尝试为 Swift OS X OpenGL 应用程序创建一个主渲染循环,但我在网络上找不到任何示例,也无法弄清楚与 Objective C API 的交互。

这是我的NSOpenGLView子类初始化期间的代码:

  var udl : Unmanaged<CVDisplayLink>?
  CVDisplayLinkCreateWithActiveCGDisplays(&udl)
  var displayLink: CVDisplayLink = udl!.takeRetainedValue()  // I guess

  // The two following lines give errors that the type isn't convertible 
  // to the declared type:
  let cb: CVDisplayLinkOutputCallback = dlCallback  // ERROR: type not convertible
  let sp: UnsafeMutablePointer<Void> = &self        // ERROR: type not convertible
  CVDisplayLinkSetOutputCallback(displayLink, cb, sp)

  let cglContext = openGLContext.CGLContextObj
  let cglPixelFormat = pixelFormat.CGLPixelFormatObj
  CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat)

  CVDisplayLinkStart(displayLink)

这是我的回调函数。我不知道如何从传递给CVDisplayLinkSetOutputCallback(或尝试传递)的不透明指针中提取指向我的视图类的指针。

func dlCallback(displayLink: CVDisplayLink!,
            inNow: UnsafePointer<CVTimeStamp>,
            inOutputTime: UnsafePointer<CVTimeStamp>,
            flagsIn: CVOptionFlags,
            flagsOut: UnsafeMutablePointer<CVOptionFlags>,
            context: UnsafeMutablePointer<Void>) -> CVReturn {
  let that = UnsafeMutablePointer<MyView>(context)  // Just a guess, but no
  that.render()  // ERROR: no such method
}

我想了解如何执行此操作。

如果我应该使用其他基于计时器的主渲染循环,我想我可以这样做。

【问题讨论】:

    标签: macos opengl swift


    【解决方案1】:

    为 Swift 3.0 更新 - 见答案结束

    我不会删除我之前的答案,我认为这对那些想要同时使用 Obj-C 和 Swift 的人很有用,我将提供第二个使用纯 Swift 代码的替代答案。现在 Swift 2.0 发布了,我们可以利用 CFunctionPointer 将 Swift 函数和闭包作为 C API 参数传递。这是带有 cmets 的代码。

    //
    //  SwiftOpenGLView.swift
    //  Swift CVDisplayLink
    //
    //  Created by Myles La Verne Schultz on 10/17/15.
    //  Copyright © 2015 MyKo. All rights reserved.
    //
    
    import Cocoa
    import OpenGL.GL3
    
    
    class SwiftOpenGLView: NSOpenGLView {
    
        var displayLink: CVDisplayLink?
    
        required init?(coder: NSCoder) {
    
            //  Call the super before setting the pixelFormat and openGLContext so that the super does not override 
            //  our custom versions of these properties.
            super.init(coder: coder)
    
            //  Create a pixel format and context and set them to the view's pixelFormat and openGLContext properties.
            let attributes: [NSOpenGLPixelFormatAttribute] = [
                UInt32(NSOpenGLPFAAccelerated),
                UInt32(NSOpenGLPFAColorSize), UInt32(32),
                UInt32(NSOpenGLPFADoubleBuffer),
                UInt32(NSOpenGLPFAOpenGLProfile),
                UInt32(NSOpenGLProfileVersion3_2Core),
                UInt32(0)
            ]
            guard let pixelFormat = NSOpenGLPixelFormat(attributes: attributes) else {
                Swift.print("pixel format could not be created")
                return
            }
            self.pixelFormat = pixelFormat
    
            guard let context = NSOpenGLContext(format: pixelFormat, shareContext: nil) else {
                Swift.print("context could not be created")
                return
            }
            self.openGLContext = context
    
            //  Tell the view how often we are swaping the buffers, 1 indicates we are using the 60Hz refresh rate (i.e. 60 fps)
            self.openGLContext?.setValues([1], forParameter: .GLCPSwapInterval)
    
        }
    
        //  prepareOpenGL is where we set OpenGL state calls before the first render, we will set up the CVDisplayLink here.
        override func prepareOpenGL() {
    
            //  The callback function is called everytime CVDisplayLink says its time to get a new frame.
            func displayLinkOutputCallback(displayLink: CVDisplayLink, _ inNow: UnsafePointer<CVTimeStamp>, _ inOutputTime: UnsafePointer<CVTimeStamp>, _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer<CVOptionFlags>, _ displayLinkContext: UnsafeMutablePointer<Void>) -> CVReturn {
    
                /*  The displayLinkContext is CVDisplayLink's parameter definition of the view in which we are working.
                    In order to access the methods of a given view we need to specify what kind of view it is as right
                    now the UnsafeMutablePointer<Void> just means we have a pointer to "something".  To cast the pointer
                    such that the compiler at runtime can access the methods associated with our SwiftOpenGLView, we use
                    an unsafeBitCast.  The definition of which states, "Returns the the bits of x, interpreted as having
                    type U."  We may then call any of that view's methods.  Here we call drawView() which we draw a
                    frame for rendering.  */
                unsafeBitCast(displayLinkContext, SwiftOpenGLView.self).renderFrame()
    
                //  We are going to assume that everything went well for this mock up, and pass success as the CVReturn
                return kCVReturnSuccess
            }
    
            //  Grab the a link to the active displays, set the callback defined above, and start the link.
            /*  An alternative to a nested function is a global function or a closure passed as the argument--a local function 
                (i.e. a function defined within the class) is NOT allowed. */
            //  The UnsafeMutablePointer<Void>(unsafeAddressOf(self)) passes a pointer to the instance of our class.
            CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
            CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, UnsafeMutablePointer<Void>(unsafeAddressOf(self)))
            CVDisplayLinkStart(displayLink!)
    
        }
    
        //  Method called to render a new frame with an OpenGL pipeline
        func renderFrame() {
    
            guard let context = self.openGLContext else {
                Swift.print("oops")
                return
            }
    
            //  Tell OpenGL this is the context we want to draw into and lock the focus.
            context.makeCurrentContext()
            CGLLockContext(context.CGLContextObj)
    
            //  Lock the focus before making state change calls to OpenGL, or the app gives you a EXC_BAD_ACCESS fault
            //  This float is a changing value we can use to create a simple animation.
            let value = Float(sin(1.00 * CACurrentMediaTime()))
            //  Uses the float to set a clear color that is on the gray scale.
            glClearColor(value, value, value, 1.0)
    
            glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
            //  Flushing sends the context to be used for display, then we can unlock the focus.
            CGLFlushDrawable(context.CGLContextObj)
            CGLUnlockContext(context.CGLContextObj)
    
        }
    
        override func drawRect(dirtyRect: NSRect) {
    
            super.drawRect(dirtyRect)
    
            // Should drawRect(_:) get called, we want a new frame to be drawn, so call drawView()
            renderFrame()
    
        }
    
        deinit {
    
            //When the view gets destroyed, we don't want to keep the link going.
            CVDisplayLinkStop(displayLink!)
    
        }
    
    }
    

    Swift 3.0 和 CVDisplayLink

    已对 Swift 中的指针进行了许多更改,这些更改破坏了此答案的先前版本。为了使信息保持最新,我在下面提供了更新版本。

    //
    //  SwiftOpenGLView_Swift_3_0.swift
    //  SwiftOpenGL
    //
    //  Created by Myles La Verne Schultz on 1/11/17.
    //  Copyright © 2017 MyKo. All rights reserved.
    //
    //  This file is an update to the previous SwiftOpenGLView used
    //  to display animated content using the CVDisplayLink.  This
    //  version uses Swift 3.0 without the need for a bridging
    //  header for the CVDisplayLinkCallback function.  An
    //  explanation of the CVTimeStamp is also provided.
    //
    import Cocoa
    import OpenGL.GL3
    
    
    final class SwiftOpenGLView: NSOpenGLView {
    
        //  A CVDisplayLink for animating.
        fileprivate var displayLink: CVDisplayLink?
    
        //  The current time, used to produce varying values to change background color
        fileprivate var currentTime = 0.0
    
        required init?(coder: NSCoder) {
            super.init(coder: coder)
    
            let attrs: [NSOpenGLPixelFormatAttribute] = [
                UInt32(NSOpenGLPFAAccelerated),
                UInt32(NSOpenGLPFADoubleBuffer),
                UInt32(NSOpenGLPFAColorSize), UInt32(32),
                UInt32(NSOpenGLPFAOpenGLProfile), UInt32(NSOpenGLProfileVersion3_2Core),
                UInt32(0)
            ]
            guard let pixelFormat = NSOpenGLPixelFormat(attributes: attrs) else {
                Swift.print("pixelFormat could not be constructed")
                return
            }
            self.pixelFormat = pixelFormat
            guard let context = NSOpenGLContext(format: pixelFormat, share: nil) else {
                Swift.print("context could not be constructed")
                return
            }
            self.openGLContext = context
    
            //  Set the context's swap interval parameter to 60Hz (i.e. 1 frame per swamp)
            self.openGLContext?.setValues([1], for: .swapInterval)
    
        }
    
        override func prepareOpenGL() {
    
            super.prepareOpenGL()
    
            glClearColor(0.0, 0.0, 0.0, 1.0)
    
            // ** ** ** ** ** ** ** ** ** //
            // Setup OpenGL pipline here  //
            // ** ** ** ** ** ** ** ** ** //
    
            /*  Now that the OpenGL pipeline is defined, declare a callback for our CVDisplayLink.
                There are three ways to do this:  declare a function, declare a computed property,
                or declare/pass a closure.  Using each requires subtle changes in the
                CVDisplayLinkSetOutputCallback()'s argument list.  We shall declare a local
                closure of type CVDisplayLinkOutputCallback.
             */
            let displayLinkOutputCallback: CVDisplayLinkOutputCallback = {(displayLink: CVDisplayLink, inNow: UnsafePointer<CVTimeStamp>, inOutputTime: UnsafePointer<CVTimeStamp>, flagsIn: CVOptionFlags, flagsOut: UnsafeMutablePointer<CVOptionFlags>, displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn in
    
                /*  It's prudent to also have a brief discussion about the CVTimeStamp.
                    CVTimeStamp has five properties.  Three of the five are very useful
                    for keeping track of the current time, calculating delta time, the
                    frame number, and the number of frames per second.  The utility of
                    each property is not terribly obvious from just reading the names
                    or the descriptions in the Developer dcumentation and has been a
                    mystery to many a developer.  Thankfully, CaptainRedmuff on
                    StackOverflow asked a question that provided the equation that
                    calculates frames per second.  From that equation, we can
                    extrapolate the value of each field.
    
                    @hostTime = current time in Units of the "root".  Yeah, I don't know.
                      The key to this field is to understand that it is in nanoseconds
                      (e.g. 1/1_000_000_000 of a second) not units.  To convert it to
                      seconds divide by 1_000_000_000.  Dividing by videoRefreshPeriod
                      and videoTimeScale in a calculation for frames per second yields
                      the appropriate number of frames.  This works as a result of
                      proportionality--dividing seconds by seconds.  Note that dividing
                      by videoTimeScale to get the time in seconds does not work like it
                      does for videoTime.
    
                      framesPerSecond:
                        (videoTime / videoRefreshPeriod) / (videoTime / videoTimeScale) = 59
                      and
                        (hostTime / videoRefreshPeriod) / (hostTime / videoTimeScale) = 59
                      but
                        hostTime * videoTimeScale ≠ seconds, but Units = seconds * (Units / seconds) = Units
    
                  @rateScalar = ratio of "rate of device in CVTimeStamp/unitOfTime" to
                    the "Nominal Rate".  I think the "Nominal Rate" is
                    videoRefreshPeriod, but unfortunately, the documentation doesn't
                    just say videoRefreshPeriod is the Nominal rate and then define
                    what that means.  Regardless, because this is a ratio, and the fact
                    that we know the value of one of the parts (e.g. Units/frame), we
                    then know that the "rate of the device" is frame/Units (the units of
                    measure need to cancel out for the ratio to be a ratio).  This
                    makes sense in that rateScalar's definition tells us the rate is
                    "measured by timeStamps".  Since there is a frame for every
                    timeStamp, the rate of the device equals CVTimeStamp/Unit or
                    frame/Unit.  Thus,
    
                      rateScalar = frame/Units : Units/frame
    
                  @videoTime = the time the frame was created since computer started up.
                    If you turn your computer off and then turn it back on, this timer
                    returns to zero.  The timer is paused when you put your computer to
                    sleep.  This value is in Units not seconds.  To get the number of
                    seconds this value represents, you have to apply videoTimeScale.
    
                  @videoRefreshPeriod = the number of Units per frame (i.e. Units/frame)
                    This is useful in calculating the frame number or frames per second.
                    The documentation calls this the "nominal update period" and I am
                    pretty sure that is quivalent to the aforementioned "nominal rate".
                    Unfortunately, the documetation mixes naming conventions and this
                    inconsistency creates confusion.
    
                      frame = videoTime / videoRefreshPeriod
    
                  @videoTimeScale = Units/second, used to convert videoTime into seconds
                    and may also be used with videoRefreshPeriod to calculate the expected
                    framesPerSecond.  I say expected, because videoTimeScale and
                    videoRefreshPeriod don't change while videoTime does change.  Thus,
                    to calculate fps in the case of system slow down, one would need to
                    use videoTime with videoTimeScale to calculate the actual fps value.
    
                      seconds = videoTime / videoTimeScale
    
                      framesPerSecondConstant = videoTimeScale / videoRefreshPeriod (this value does not change if their is system slowdown)
    
                USE CASE 1: Time in DD:HH:mm:ss using hostTime
                  let rootTotalSeconds = inNow.pointee.hostTime
                  let rootDays = inNow.pointee.hostTime / (1_000_000_000 * 60 * 60 * 24) % 365
                  let rootHours = inNow.pointee.hostTime / (1_000_000_000 * 60 * 60) % 24
                  let rootMinutes = inNow.pointee.hostTime / (1_000_000_000 * 60) % 60
                  let rootSeconds = inNow.pointee.hostTime / 1_000_000_000 % 60
                  Swift.print("rootTotalSeconds: \(rootTotalSeconds) rootDays: \(rootDays) rootHours: \(rootHours) rootMinutes: \(rootMinutes) rootSeconds: \(rootSeconds)")
    
                USE CASE 2: Time in DD:HH:mm:ss using videoTime
                  let totalSeconds = inNow.pointee.videoTime / Int64(inNow.pointee.videoTimeScale)
                  let days = (totalSeconds / (60 * 60 * 24)) % 365
                  let hours = (totalSeconds / (60 * 60)) % 24
                  let minutes = (totalSeconds / 60) % 60
                  let seconds = totalSeconds % 60
                  Swift.print("totalSeconds: \(totalSeconds) Days: \(days) Hours: \(hours) Minutes: \(minutes) Seconds: \(seconds)")
    
                  Swift.print("fps: \(Double(inNow.pointee.videoTimeScale) / Double(inNow.pointee.videoRefreshPeriod)) seconds: \(Double(inNow.pointee.videoTime) / Double(inNow.pointee.videoTimeScale))")
                 */
    
                /*  The displayLinkContext in CVDisplayLinkOutputCallback's parameter list is the
                    view being driven by the CVDisplayLink.  In order to use the context as an
                    instance of SwiftOpenGLView (which has our drawView() method) we need to use
                    unsafeBitCast() to cast this context to a SwiftOpenGLView.
                 */
    
                let view = unsafeBitCast(displayLinkContext, to: SwiftOpenGLView.self)
                //  Capture the current time in the currentTime property.
                view.currentTime = inNow.pointee.videoTime / Int64(inNow.pointee.videoTimeScale)
                view.drawView()
    
                //  We are going to assume that everything went well, and success as the CVReturn
                return kCVReturnSuccess
            }
    
            /*  Grab the a link to the active displays, set the callback defined above, and start
                the link.  An alternative to a nested function is a global function or a closure
                passed as the argument--a local function (i.e. a function defined within the
                class) is NOT allowed.  The
                UnsafeMutableRawPointer(unmanaged.passUnretained(self).toOpaque()) passes a
                pointer to an instance of SwiftOpenGLView.  UnsafeMutableRawPointer is a new type
                Swift 3.0 that does not require type definition at its creation.  For greater
                detail place the Swift Evolution notes at https://github.com/apple/swift-evolution/blob/master/proposals/0107-unsaferawpointer.md
            */
            CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
            CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
            CVDisplayLinkStart(displayLink!)
    
            //  Test render
    
        }
    
        override func draw(_ dirtyRect: NSRect) {
            super.draw(dirtyRect)
    
            // Drawing code here.
            // This call is not entirely necessary as the view is already
            // set to draw with every screen refresh.  Were we to have
            // used the view's display() function, then this object's
            // draw(_:) would actually be called and this our drawView()
            // within it.  As it is now, it's not based on our implementation.
            drawView()
    
        }
    
        fileprivate func drawView() {
    
            //  Grab a context, make it the active context for drawing, and then lock the focus
            //  before making OpenGL calls that change state or data within objects.
            guard let context = self.openGLContext else {
                //  Just a filler error
                Swift.print("oops")
                return
            }
    
            context.makeCurrentContext()
            CGLLockContext(context.cglContextObj!)
    
            value = sin(currentTime)
            glClearColor(value, value, value, 1.0)
    
            glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
    
            //  glFlush() is replaced with CGLFlushDrawable() and swaps the buffer being displayed
            CGLFlushDrawable(context.cglContextObj!)
            CGLUnlockContext(context.cglContextObj!)
        }
    
        deinit {
            //  Stop the display link.  A better place to stop the link is in
            //  the viewController or windowController within functions such as
            //  windowWillClose(_:)
            CVDisplayLinkStop(displayLink!)
        }
    
    }
    

    源码位于GitHub

    【讨论】:

    • 这个答案的 swift 3 版本?
    • 我确实有一个工作版本。您可以在我的 GitHub 上的 SwiftOpenGLTutorials 存储库下查看工作代码。每个使用 CVDisplayLink 的教程都是一个有效的 Swift 3 版本。我还没有更新答案或 CVDisplayLink 存储库的 GitHub,现在我将您提到它。应该在今晚晚些时候准备好。
    • 太棒了!我更正了我自己的代码,现在从 swift 2 -> 3 我必须注意 UnsafeMutablePointer&lt;Void&gt;(unsafeAddressOf(self)) 现在是:UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())displayLinkContext: UnsafeMutableRawPointer? 现在是:displayLinkContext: UnsafeMutableRawPointer? 这些在 swift 3 的自动迁移中没有更正。其他所有内容都会为我自动迁移
    • 非常正确。几个月前,当我更新编写的 Swift 3.0 教程时,我自己也不得不这样做。我已经更新了答案以显示 Swift 3.0 版本。还提供了指向源代码的 GitHub 链接。
    • @Buggy 您提供的链接似乎已损坏:opengl-swift-and-cvdisplaylink/SwiftOpenGL/… GitHub 甚至不在 URL 中!
    【解决方案2】:

    您并不是唯一一个努力使 CVDisplayLink 在 Swift 代码中工作的人。我一直在努力完成这项工作,并通过我的研究意识到了一些事情,Swift 没有能力将参数发送到 C API 参数以使 CVDisplayLink 工作。我也在 Apple Developer 论坛上发布了我的答案,如果您不介意,我会与您分享。

    最终编辑

    我已将完全实现的 Swift 和 Obj-C 文件发布到 GitHub

    存储库包含:

    1. SwiftOpenGLView.swift(Swift 中 NSOpenGLView 的子类)
    2. CVDisplayLinkCallbackFunction.h 和 .m(回调函数的 Obj-C 类
    3. SwiftOpenGL-Bridging-Header.h(用于评估 Swift 中的回调函数)
    4. Appdelegate.swift(用于在应用终止时停止 CVDisplayLink

    这些文件包含许多 cmets,以帮助读者更好地理解代码的工作原理。 OpenGL 和 CVDisplayLink 并不是 Mac 上最容易学习的 API。希望这些文件将有助于加快这一进程。欢迎输入,但请记住要友善——我努力处理这些文件。相同的文件如下所示。


    SwiftOpenGLView.swift

    import Cocoa
    import OpenGL.GL3
    import QuartzCore.CVDisplayLink
    
    
    @objc class SwiftOpenGLView: NSOpenGLView {
    
        var displayLink: CVDisplayLink?
    
        required init?(coder: NSCoder) {
    
            //  CVDisplayLinkCreateActiveCGDisplays() says we are enabling all useable 
            //  delays to show our content.  Pass in the displayLink porterty
            //  to capture the link.
    
            CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
    
            super.init(coder: coder)
    
            //  some OpenGL setup
            //  NSOpenGLPixelFormatAttribute is a typealias for UInt32 in Swift, cast each attribute
            //  Set the view's PixelFormat and Context to the custom pixelFormat and context
    
            let attrs: [NSOpenGLPixelFormatAttribute] = [
                UInt32(NSOpenGLPFAAccelerated),
                UInt32(NSOpenGLPFAColorSize), UInt32(32),
                UInt32(NSOpenGLPFADoubleBuffer),
                UInt32(NSOpenGLPFAOpenGLProfile),
                UInt32( NSOpenGLProfileVersion3_2Core),
                UInt32(0)
            ]
            let pixelFormat = NSOpenGLPixelFormat(attributes: attrs)
            self.pixelFormat = pixelFormat
            let context = NSOpenGLContext(format: pixelFormat, shareContext: nil)
            self.openGLContext = context
    
            //  Set the swaping interval parameter on the context, setValues:forParameter: is expecting multiple values--use an array
            //  In Swift, context parameters are accessed though the NSOpenGLContextParameter enum, use dot syntax to access the swap interval
    
            var swapInterval: [GLint] = [1]
            self.openGLContext.setValues(swapInterval, forParameter: .GLCPSwapInterval)
    
            //  CVDLCallbackFunctionPointer() is a C function declared in CVDisplayLinkCallbackFunction.h
            //  It returns a pointer to our callback:  CVDisplayLinkOutputCallback
            //  The third parameter takes an UnsafeMutablePointer<Void> and our argument needs to be our view (ie self)
            //  We have already stated this type of parameter requires the address of operator '&'
            //  We can't use'&' on out object, but we can still access the pointer using unsafeAddressOf()
            //  However, this address/pointer can't be passed as is--you have to cast to UnsafeMutablePointer<T> (where T is our class)
            //  To se the current display from our OpenGL context, we retrieve the pixelFormat and context as CoreGraphicsLayer objects
            //  Start the CVDisplayLink, note that we need to stop the displayLink when we are done --> done in APPDELEGATE.SWIFT!!!
    
            CVDisplayLinkSetOutputCallback(displayLink!, CVDLCallbackFunctionPointer(), UnsafeMutablePointer<SwiftOpenGLView>(unsafeAddressOf(self)))
            let cglPixelFormat = self.pixelFormat?.CGLPixelFormatObj
            let cglContext = self.openGLContext.CGLContextObj
            CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink!, cglContext, cglPixelFormat!)
            CVDisplayLinkStart(displayLink!)
        }
    
        //  Called by the callback function to ask our model to render out a frame for our context
        //  We have to cast from an UnsafePointer<CVTimeStamp> to an UnsafeMutablePointer<CVTimeStamp>
    
        func getFrameForTime(outputTime: UnsafePointer<CVTimeStamp>)->CVReturn {
            CVDisplayLinkGetCurrentTime(displayLink!, UnsafeMutablePointer<CVTimeStamp>(outputTime))
    
            //  For development purpose, calculate the frames per second using the CVTimeStamp passed to the callback function
            //  CVTimeStamp is a C struct with several members that are accessed by going straight to their memory location with .memory
            //  'command' + 'click' on CVTimeStamp to see the struct's definition
    
            let fps = (outputTime.memory.rateScalar * Double(outputTime.memory.videoTimeScale) / Double(outputTime.memory.videoRefreshPeriod))
            println("FPS:\t \(fps)")
    
            //  It's time to draw, request the rendered frame
    
            drawView()
    
            return kCVReturnSuccess.value
        }
    
        override func prepareOpenGL() {
    
            //  Setup OpenGL
    
            glClearColor(0.0, 0.0, 0.0, 1.0)
    
            //  Run a test render
    
            drawView()
    
        }
    
        override func drawRect(dirtyRect: NSRect) {
            super.drawRect(dirtyRect)
    
            // Drawing code here.
    
            drawView()
    
        }
    
        func drawView() {
    
            //  Grab a context from our view and make it current for drawing into
            //  CVDisplayLink uses a separate thread, lock focus or our context for thread safety
    
            let context = self.openGLContext
            context.makeCurrentContext()
            CGLLockContext(context.CGLContextObj)
    
            //  Clear the context, set up the OpenGL shader program(s), call drawing commands
            //  OpenGL targets and such are UInt32's, cast them before sending in the OpenGL function
    
            glClear(UInt32(GL_COLOR_BUFFER_BIT))
    
            //  We're using a double buffer, call CGLFlushDrawable() to swap the buffer
            //  We're done drawing, unlock the context before moving on
    
            CGLFlushDrawable(context.CGLContextObj)
            CGLUnlockContext(context.CGLContextObj)
    
        }
    
    }
    

    CVDisplayLinkCallbackFunction.h

    @import Foundation;
    @import QuartzCore.CVDisplayLink;
    
    
    @interface CVDisplayLinkCallbackFunction : NSObject
    
    CVDisplayLinkOutputCallback CVDLCallbackFunctionPointer();
    
    @end
    

    CVDisplayLinkCallbackFunction.m

    #import "CVDisplayLinkCallbackFunction.h"
    #import "SwiftOpenGL-Swift.h"
    
    
    @implementation CVDisplayLinkCallbackFunction
    
    
    CVDisplayLinkOutputCallback CVDLCallbackFunctionPointer()
    {
        return CVDLCallbackFunction;
    }
    
    
    CVReturn CVDLCallbackFunction( CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext )
    {
        //  Tell CVDisplayLink to call getFrameForTime: (in SwiftOpenGLView) with the provided CVTimeStamp
        //  The function returns a result which can be checked for success
    
        CVReturn result = [(__bridge SwiftOpenGLView*)displayLinkContext getFrameForTime:inOutputTime];
    
        return result;
    }
    
    @end
    

    SwiftOpenGL-Bridging-Header.h

    #import "CVDisplaylinkCallbackFunction.h"
    

    AppDelegate.swift

    import Cocoa
    import QuartzCore.CVDisplayLink
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
    
    
    
        func applicationDidFinishLaunching(aNotification: NSNotification) {
            // Insert code here to initialize your application
        }
    
        func applicationWillTerminate(aNotification: NSNotification) {
            // Insert code here to tear down your application
    
            //  Grab the current window in our app, and from that grab the subviews of the attached viewController
            //  Cycle through that array to get our SwiftOpenGLView instance
    
            let windowController = NSApplication.sharedApplication().mainWindow?.windowController() as? NSWindowController
            let views = windowController?.contentViewController?.view.subviews as [NSView]
            for view in views {
                if let aView = view as? SwiftOpenGLView {
                    println("Checking if CVDisplayLink is running")
                    if let running = CVDisplayLinkIsRunning(aView.displayLink) as Boolean? {
                        println("Stopping CVDisplayLink")
                        let result = CVDisplayLinkStop(aView.displayLink)
                        if result == kCVReturnSuccess.value { println("CVDisplayLink stopped\n\tCode: \(result)") }
                    }
                }
            }
        }
    
    }
    

    我希望随着他们在 Swift 1.2 中取得的进步,他们将使这些 C API “可行”,同时完全使用 Swift 编码。也许在今年的 WWDC 上,他们会宣布您可以在 swift 中使用所有 C API,而无需使用 Obj-C 端。更好的是,如果可以用 Swift 重写 C API,那就太棒了。就个人而言,我认为这就是 Apple 打算采用 Swift 的地方——跨平台扫描以重新定义 Swift 中的框架。不过我不是工程师,所以也许这不是他们打算做的。但是,如果不是……为什么首先要制作 Swift?

    希望 CVDisplayLink 在不久的将来可以在 Swift 中进行编码。

    【讨论】:

    • 这在 Swift 2.0 中似乎不再起作用 - CVDisplayLinkCreateWithActiveCGDisplays(&amp;displayLinkPointer) 出现类型错误 - 知道应该如何更新吗?
    • 是的,实际上,现在让它在 Swift 2.0 中工作的方法是在 Swift 中完成所有事情并使用 CFuntionPointer。我稍后会发布代码。我现在得上班了。
    • 所以我能够解决我自己的问题 - 看起来随着 API 的变化,用CVDisplayLinkCreateWithActiveCGDisplays(&amp;displayLink) 替换该行就足够了,其中displayLinkCGLDisplayLinkRef 的一个实例
    • 我在下面添加了第二个替代答案,它利用 CFunctionPointer 并允许您仅使用 Swift 编写。是的,你是对的,现在你只需将 displayLink 属性直接发送到 CVDisplayLInkCreateWithActiveCGDisplays() 函数中。我也编辑了原始答案,因此未来的访问者不会感到困惑。感谢您发现这一变化!
    【解决方案3】:

    通过使用 NSTimer 而不是 CVDisplayLink,您可以在没有 Objective-C 中介的情况下使其工作。这是 this Apple Technote 中描述的旧的但未被弃用的做事方式。

    CVDisplayLink 为屏幕硬件刷新添加了更好的同步。但这是以多线程问题为代价的,而且正如@Buggy 所解释的那样 - Swift 函数指针问题。而在words of an Apple Engineer

    嗯....即使是最初编写 CVDisplayLink 的人,如果我正在编写类似游戏的应用程序,我可能只是使用连续触发的 NSTimer 并让 VBL 的东西限制实际帧速率。

    这里概述了如何使用 NSTimer...

    (1) 从您的 init 方法调用

    func setupView() {
            var attribs : [NSOpenGLPixelFormatAttribute] = [
                 //set up your NSOpenGLPixelFormat attributes here
            ]
            var pix : NSOpenGLPixelFormat = NSOpenGLPixelFormat(attributes: attribs)
            self.pixelFormat = pix;
            self.renderTimer = NSTimer(timeInterval: 0.001
                , target: self
                , selector: "timerFired"
                , userInfo: nil
                , repeats: true)
    
    
        NSRunLoop.currentRunLoop().addTimer(self.renderTimer!
            , forMode:NSDefaultRunLoopMode)
        NSRunLoop.currentRunLoop().addTimer(self.renderTimer!
            , forMode:NSEventTrackingRunLoopMode)
    
        }
    

    (2) prepareForOpenGL() 覆盖...

        override func prepareOpenGL() {
            super.prepareOpenGL()
            self.openGLContext.makeCurrentContext()
            var swapInterval : GLint = 1 // request vsync
            self.openGLContext.setValues(&swapInterval
               , forParameter: NSOpenGLContextParameter.GLCPSwapInterval)
    

    (3)定时器函数(触发系统调用drawRect):

    func timerFired() {
        self.display()
    }
    
    override func drawRect(dirtyRect: NSRect) {
            super.drawRect(dirtyRect)
            // Drawing code here.
    }
    

    不过,我确实想知道严格仅限 Swift 的 openGL 环境的功效。毕竟,Swift 是作为“没有 C 的目标 C”引入的。 OpenGL 是一个 C API,所以我们应该预料到会发生争执。我自己的偏好是将我的 OpenGL 代码保留在 C++ 中,并通过 Objective-C 桥接 UI 调用。 openGL 方面保留了最大的可移植性,我在使用这种方法针对 iOS、OSX 和 Windows 的项目中取得了一些成功。

    如果你愿意让 Swift 漂浮在 Objective-C 之上的层上,this project 展示了如何让 Swift 对话的 openGL 视图运行。 swift 代码作为 Objective-C NSOpenGLView 子类的类扩展提供,Objective-C 端设置 CVDisplayLink。

    【讨论】:

    • 谢谢,我正在找这个!
    【解决方案4】:

    好的,所以有一种使用 CVDisplayLink 的纯 Swift 方式,但您必须跳过一些 unsafeBitCast 圈。 (基于我找到的代码here

    获取您的 Swift 块/闭包,将其转换为正确的 @objc_block 类型,然后将其转换为类型 CVDisplayLinkCallback

    您可以将其粘贴到 Playground 中以查看其工作情况。重要的代码在 DisplayLinkSetOutputCallback

    typealias DisplayLinkCallback = @objc_block ( CVDisplayLink!, UnsafePointer<CVTimeStamp>, UnsafePointer<CVTimeStamp>, CVOptionFlags, UnsafeMutablePointer<CVOptionFlags>, UnsafeMutablePointer<Void>)->Void
    
    func DisplayLinkSetOutputCallback( displayLink:CVDisplayLink, callback:DisplayLinkCallback )
    {
        let block:DisplayLinkCallback = callback
        let myImp = imp_implementationWithBlock( unsafeBitCast( block, AnyObject.self ) )
        let callback = unsafeBitCast( myImp, CVDisplayLinkOutputCallback.self )
    
        CVDisplayLinkSetOutputCallback( displayLink, callback, UnsafeMutablePointer<Void>() )
    }
    
    
    let dl:CVDisplayLink? = {
        var linkRef:Unmanaged<CVDisplayLink>?
        CVDisplayLinkCreateWithActiveCGDisplays( &linkRef )
    
        return linkRef?.takeUnretainedValue()
    }()
    
    let callback = { (
        _:CVDisplayLink!,
        _:UnsafePointer<CVTimeStamp>,
        _:UnsafePointer<CVTimeStamp>,
        _:CVOptionFlags,
        _:UnsafeMutablePointer<CVOptionFlags>,
        _:UnsafeMutablePointer<Void>)->Void in
    
        println("yep")
    
    }
    
    DisplayLinkSetOutputCallback( dl!, callback )
    CVDisplayLinkStart( dl! )
    
    NSRunLoop.mainRunLoop().run()
    

    【讨论】:

    • 为什么不使用答案中的内容?
    • 我试过了,但不确定在哪里需要一个完整的工作示例。我真的很感激。
    猜你喜欢
    • 2022-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-01
    • 2022-07-18
    • 1970-01-01
    相关资源
    最近更新 更多