Mac OS X下的CADisplayLink替代方案

18

iOS中有CADisplayLink,在Mac OS X中有CVDisplayLink,但我找不到使用它的方法,所有的例子都与OpenGL相关。

我创建了这个自定义的UIView并希望将其转换为NSView。

#import "StarView.h"
#import <QuartzCore/QuartzCore.h>

#define MAX_FPS (100.0)
#define MIN_FPS (MAX_FPS/40.0)
#define FRAME_TIME (1.0 / MAX_FPS)
#define MAX_CPF (MAX_FPS / MIN_FPS)
#define aEPS (0.0001f)

@implementation StarView

@synthesize starImage = _starImage;
@synthesize x, y;

- (void)baseInit {
    _starImage = nil;
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self     selector:@selector(runLoop)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
    [self baseInit];
}
return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
    [self baseInit];
}
return self;
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    [self.starImage drawAtPoint:CGPointMake(self.x, self.y)];
}

-(void) cycle {
    self.x += 5;
    if (self.x > 230) {
        self.x = 0;
    }
}

- (void) runLoop {
//NSLog(@"called");
static CFTimeInterval last_time = 0.0f;
static float cycles_left_over = 0.0f;
static float dt2 = 0.0f;

float dropAnimRate = (2.1f/25.0f);

CFTimeInterval current_time = CACurrentMediaTime();
float dt = current_time - last_time + cycles_left_over;
dt2 += dt;

[self setNeedsDisplay];

if (dt > (MAX_CPF * FRAME_TIME)) {
    dt = (MAX_CPF * FRAME_TIME);
}
while (dt > FRAME_TIME) {
    if (dt2 > (dropAnimRate - aEPS)){
        [self cycle];
        dt2 = 0.0f;
    }
    dt -= FRAME_TIME;
}

cycles_left_over = dt;
last_time = current_time;
}

@end

我无法翻译的部分是这一段

- (void)baseInit {
    _starImage = nil;
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self     selector:@selector(runLoop)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

我知道我可以使用NSTimer,但它的精确度不同

2个回答

28
您可以配置CVDisplayLink独立于OpenGL工作。以下是我使用的代码,用于设置CVDisplayLink以从工业相机触发常规捕获和渲染:
CGDirectDisplayID   displayID = CGMainDisplayID();
CVReturn            error = kCVReturnSuccess;
error = CVDisplayLinkCreateWithCGDisplay(displayID, &displayLink);
if (error)
{
    NSLog(@"DisplayLink created with error:%d", error);
    displayLink = NULL;
}
CVDisplayLinkSetOutputCallback(displayLink, renderCallback, (__bridge void *)self);

我的renderCallback函数看起来像这样:

static CVReturn renderCallback(CVDisplayLinkRef displayLink, 
                               const CVTimeStamp *inNow, 
                               const CVTimeStamp *inOutputTime, 
                               CVOptionFlags flagsIn, 
                               CVOptionFlags *flagsOut, 
                               void *displayLinkContext)
{
    return [(__bridge SPVideoView *)displayLinkContext renderTime:inOutputTime];
}

当然,您需要使用自己的类和回调方法来替换上面的内容。代码中的__bridge是为了支持ARC,因此在手动引用计数环境中可能不需要它。

要开始从显示链接捕获事件,您可以使用

CVDisplayLinkStart(displayLink);

停止

CVDisplayLinkStop(displayLink);

完成后,请务必使用CVDisplayLinkRelease()清理您创建的CVDisplayLink。

如果我可以最后发表一点评论,使用CVDisplayLink通常与OpenGL配对的原因是您通常不希望使用Core Graphics在屏幕上进行快速刷新。 如果您需要以30-60 FPS速率动画某些内容,则需要直接使用OpenGL进行绘制或使用Core Animation并让它处理动画定时。 Core Graphics绘图不是流畅地实现动画效果的方法。


“return [(__bridge SPVideoView *)displayLinkContext renderTime:inOutputTime];” 我不理解这一行,我猜想在SPVideoView之外应该使用MyView,但我不明白在renderTime之外应该使用什么。您能否向我解释一下这些函数的目的(renderCallback和renderTime)? - AR89
@AR89 - 因为CVDisplayLink采用回调函数而不是方法,所以你需要在你的类中调用适当的方法。在上面的例子中,我使用的类是SPVideoView,并且我有一个方法可以接收inOutputTime时间戳。你可以使用该时间戳来确定是否要触发显示更新。另外,我还想指出,如果使用Core Graphics,你应该小心使用此功能进行显示更新,因为此回调发生在后台线程上,所以你需要确保绘图发生在主线程上。 - Brad Larson
这是一个很好的答案,但你最终会在不同的线程上触发回调函数,这对于视图的绘制可能非常重要,至少对于OpenGL渲染来说是如此。 - jheriko
@jheriko - 请看我上面的评论。对于OpenGL渲染,只要您正确地保证独占线程访问您的上下文,您就可以安全地在后台线程上更新它。您不必在主线程上更新它。 - Brad Larson

-1
我认为NSTimer可能是这里的最佳选择。你说“它没有同样的准确性”这一点很有趣。也许你误以为是准确性问题,实际上是运行循环模式问题。如果你发现定时器在执行某些操作(如打开菜单或拖动)时未触发,请尝试更改定时器所运行的运行循环模式。
另一个选择是基于实际渲染时间而不是假设的上一次渲染经过的时间来计算动画。后者会夸大任何延迟渲染的视觉效果,而这种延迟可能会被忽略。
除了NSTimer之外,你还可以使用GCD进行定时器操作。可以参考这个例子:http://www.fieryrobot.com/blog/2010/07/10/a-watchdog-timer-in-gcd/ 我们在包括视频播放的OpenGL渲染中使用CVDisplayLink。但我相信对于你想要做的事情来说,这可能有些过度设计了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接