Alternative of CADisplayLink for Mac OS X

2019-02-03 06:38发布

问题:

Is iOS there is CADisplayLink, in Mac OS X there is CVDisplayLink, but I can't find a way to use it, all the examples are related to OpenGL.

I created this custom UIView and I want to translate it to a 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

The part that I can't translate is this one

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

I know that I can use a NSTimer, but it doesn't have the same accuracy

回答1:

You can configure a CVDisplayLink to work independently of OpenGL. The following is code that I've used to set up a CVDisplayLink to trigger regular capture and rendering from an industrial camera:

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);

my renderCallback function looks something like this:

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

You'll have to replace the above with your own class and callback method, of course. The __bridge in the code is there for ARC support, so you may not need that in a manually reference counted environment.

To start capturing events from the display link, you'd use

CVDisplayLinkStart(displayLink);

and to stop

CVDisplayLinkStop(displayLink);

When done, be sure to clean up after your created CVDisplayLink using CVDisplayLinkRelease().

If I may make one last comment, the reason why you see CVDisplayLink normally paired with OpenGL is that you usually don't want to be doing rapid refreshes on the screen using Core Graphics. If you need to be animating something at a 30-60 FPS rate, you're going to either want to draw directly using OpenGL or use Core Animation and let it handle the animation timing for you. Core Graphics drawing is not the way to fluidly animate things.



回答2:

I would think that an NSTimer would be the way to go here. Your statement "it doesn't have the same accuracy", is interesting. Perhaps what you are mistaking for an accuracy problem is a run loop mode problem. If you are seeing the timer not fire when you are doing certain things such as opening menus or dragging, you may need to just change the run loop modes that the timer is running in.

Another option is to calculate your animation based on the actual time that it's rendered, not on an assumed difference since the last render pass. Doing the latter will visually exaggerate any delays in rendering that might be otherwise go unnoticed.

Aside from NSTimer, you can also do timers in GCD. Take a look at this example: http://www.fieryrobot.com/blog/2010/07/10/a-watchdog-timer-in-gcd/

We use CVDisplayLink for OpenGL rendering that includes video playback. I am pretty sure it's overkill for what you are trying to do.