我正在尝试为Swift OS X OpenGL应用程序创建一个主渲染循环,但我在Web上找不到任何示例,并且无法找出与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
}
我想了解如何做到这一点。
如果我应该使用其他一些主渲染循环而不是基于计时器,我想我可以这样做。
答案 0 :(得分:21)
我没有删除我以前的答案,我认为这对那些想要一起使用Obj-C和Swift的人有用,我将提供另一个使用纯Swift代码的替代答案。现在已经发布了Swift 2.0,我们可以利用CFunctionPointer将Swift函数和闭包作为C API参数传递。这是带注释的代码。
//
// 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中的指针进行了一些更改,打破了此答案的先前版本。为了使信息保持最新,我将在下面提供更新版本。
//
// 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
答案 1 :(得分:4)
您并不是唯一一个努力使CVDisplayLink在Swift Code中工作的人。我一直试图通过我的研究来完成这项工作并实现了一些功能,Swift没有能力将参数发送到C API参数以使CVDisplayLink工作。我也把答案发给了Apple开发者论坛,如果你不介意的话,我会和你分享。
最终编辑
我已将完全实施的Swift和Obj-C文件发布到GitHub
存储库包含:
这些文件包含许多注释,以帮助读者更好地理解代码的工作原理。 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-桥接-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成为可行的&#34;而在Swift中完全编码。也许在今年的WWDC上,他们宣布你可以在swift中使用所有的C API,而不必使用Obj-C方面。更好的是,如果可以在Swift中重写C API,那将是非常棒的。就个人而言,我认为这就是Apple打算采用Swift的地方 - 一个跨平台的扫描来重新定义Swift中的框架。我不是工程师,所以也许这不是他们计划做的事情。但是,如果它不是......为什么要首先制作Swift?
希望在不久的将来,CVDisplayLink可以在Swift中进行编码。
答案 2 :(得分:4)
通过使用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-only openGL环境的功效。毕竟,斯威夫特被称为“没有C的目标C”。 OpenGL是一个C API,因此我们应该期待一场挣扎。我自己的偏好是通过Objective-C桥接的UI调用将我的OpenGL代码保存在C ++中。 openGL方面保留了最大的可移植性,并且我在使用这种方法针对iOS,OSX和Windows的项目上取得了一些成功。
如果你愿意让Swift浮动在Objective-C之上的一层上,this project展示了如何让Swift谈论openGL视图。 swift代码作为Objective-C NSOpenGLView
子类的类扩展提供,Objective-C端设置了CVDisplayLink。
答案 3 :(得分:1)
好的,所以有一种纯粹的Swift方式来使用CVDisplayLink,但你必须跳过一些 unsafeBitCast 箍。 (根据我找到的代码here)
获取Swift块/闭包,将其转换为正确的 @objc_block 类型,然后将其强制转换为 CVDisplayLinkCallback
您可以将其粘贴到Playground中以查看其是否有效。重要的代码位于 DisplayLinkSetOutputCallback
中$strXML .= "<set name = '".$row['Day']."' value = '".$row['TotOutput']."' link='" . urlencode("Detailed.php?Day=" . $row['Day']) . "'/>";
<?php
//We have included ../Includes/FusionCharts.php and ../Includes/DBConn.php, which contains
//functions to help us easily embed the charts and connect to a database.
include("Includes/FusionCharts.php");
include("Includes/DBConn.php");
?>
<HTML>
<HEAD>
<TITLE> FusionCharts XT - Database and Drill-Down Example </TITLE>
<SCRIPT LANGUAGE="Javascript" SRC="../../FusionCharts/FusionCharts.js"></SCRIPT>
</HEAD>
<BODY>
<?php
//This page is invoked from Default.php. When the user clicks on a pie
//slice in Default.php, the factory Id is passed to this page. We need
//to get that factory id, get information from database and then show
//a detailed chart.
//First, get the factory Id
//Request the factory Id from Querystring
$day = $_GET['Day'];
//Connect to database
$link = connectToDB();
//$strXML will be used to store the entire XML document generated
//Generate the chart element string
$strXML = "<graph caption='Peak Electricity Generated ---- for month April 2015' xAxisName='Day' yAxisName='MegaWatts' decimalPrecision='0' formatNumberScale='0' yaxismaxvalue='1000' showNames='1' rotateNames='1'>";
//Now, we get the data for that plant
$strQuery = "select plant_id, peak_generation from daily_report where pdate=" . $day;
$result = mysql_query($strQuery) or die(mysql_error());
//Iterate through each factory
if ($result) {
while($ors = mysql_fetch_array($result)) {
$strQuery = "plant_name from power_plant where plant_id=" . $ors['plant_id'];
$result2 = mysql_query($strQuery) or die(mysql_error());
$ors2 = mysql_fetch_array($result2);
//Here, we convert date into a more readable form for set label.
$strXML .= "<set name = '".$ors2['plant_name']."' value = '".$ors['peak_generation']."'/>";
}
}
mysql_close($link);
//Close <chart> element
$strXML .="</graph>";
//Create the chart - Column 2D Chart with data from $strXML
echo renderChart("charts/FCF_Line.swf", "", $strXML, "Daily Output", 1300, 500);
?>
</CENTER>
</BODY>
</HTML>