开发者

Why is my CAOpenGLLayer updating slower than my previous NSOpenGLView?

开发者 https://www.devze.com 2023-03-08 13:31 出处:网络
I have an application which renders OpenGL content on Mac OS X. Originally it was rendering to an NSOpenGLView, then I changed it to render to a CAOpenGLLayer subclass.

I have an application which renders OpenGL content on Mac OS X. Originally it was rendering to an NSOpenGLView, then I changed it to render to a CAOpenGLLayer subclass.

When I did so I saw a huge performance loss: halved framerate, lower mouse responsivity, stuttering (stops from time to time, up to a second, during which profiler activity reports waiting on mutex for data to load on GPU ram), and doubled CPU usage.

I'm investigating this issue and had a few questions:

  • Has a similar performance hit been seen by someone else?
  • Am I doing something wrong with my CAOpenGLLayer setup?
  • How is CAOpenGLLayer and the Core Animation framework implemented, i.e. what path does my OpenGL content do from my glDrawElements calls up to my screen, and how should I do things on my side to optimize performance with such setup?

Here's my code for CAOpenGLLayer setup:

// my application's entry point (can't be easily changed):
void AppUpdateLogic(); //update application logic. Will load textures
void AppRender(); //executes drawing
void AppEventSink(NSEvent* ev); //handle mouse and keyboard events.
                                //Will do pick renderings

@interface MyCAOpenGLLayer: CAOpenGLLayer
{
    CGLPixelFormatObj   pixelFormat;
    CGLContextObj       glContext;
}
@end

@implementation MyCAOpenGLLayer

- (id)init {
    self = [super init];

    CGLPixelFormatAttribute attributes[] =
    {
        kCGLPFAAccelerated,
        kCGLPFAColorSize, (CGLPixelFormatAttribute)24,
        kCGLPFAAlphaSize, (CGLPixelFormatAttribute)8,
        kCGLPFADepthSize, (CGLPixelFormatAttribute)16,
        (CGLPixelFormatAttribute)0
    };

    GLint numPixelFormats = 0;
    CGLChoosePixelFormat(attributes, &pixelFormat, &numPixelFormats);

    glContext = [super copyCGLContextForPixelFormat:mPixelFormat];

    return self;
}

- (void)drawInCGLContext:(CGLContextObj)inGlContext
             pixelFormat:(CGLPixelFormatObj)inPixelFormat
            forLayerTime:(CFTimeInterval)timeInterval
             displayTime:(const CVTimeStamp *)timeStamp 
{
    AppRender();
    [super drawInCGLContext:inGlContext
                pixelFormat:inPixelFormat
               forLayerTime:timeInterval
                displayTime:timeStamp ]
}

- (void)releaseCGLPixelFormat:(CGLPixelFormatObj)pixelFormat {
    [self release];
}

- (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
    [self retain];
    return pixelFormat;
}

- (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
    [self retain];
    return glContext;
}

- (void)releaseCGLContext:(CGLContextObj)glContext {
    [self release];
}

@end

@interface MyMainViewController: NSViewController {
    CGLContextObj   glContext;
    CALayer*        myOpenGLLayer;
}

-(void)timerTriggered:(NSTimer*)timer;
@end


@implementation MyMainViewController

-(void)viewDidLoad:(NSView*)view {
    myOpenGLLayer = [[MyCAOpenGLLayer alloc] init];
    [view setLayer:myOpenGLLayer];
    [view setWantsLayer:YES];

    glContext = [myOpenGLLayer copyCGLContextForPixelFormat:nil];

    [NSTimer scheduledTimerWithTimeInterval:1/30.0
                                     target:self
                                   selector:@selector(timerTriggered:)
                                   userInfo:nil
                                    repeats:YES ];
}

- (void)timerTriggered:(NSTimer*)timer {
    CGLContextObj oldContext = CGLContextGetCurrent();
    CGLContextSetCurrent(glContext);
    CGLContextLock(glContext);

    AppUpdateLogic();
    [myOpenGLLayer  setNeedsDisplay:YES];

    CGLContextUnlock(glContext);
    CGLContextSetCurrent(oldContext);
}

- (void)mouseDown:(NSEvent*)event {
    CGLContextObj oldContext = CGLContextGetCurrent();
    CGLContextSetCurrent(glContext);
    CGLContextLock(glContext);

    AppEventSink(event);

    CGLContextUnlock(glConte开发者_Go百科xt);
    CGLContextSetCurrent(oldContext);
}
@end

It may be useful to know my video card isn't very powerful (Intel GMA with 64 MB of shared memory).


In one of my applications, I switched from NSOpenGLView to a CAOpenGLLayer, then ended up going back because of a few issues with the update mechanism on the latter. However, that's different from the performance issues you're reporting here.

In your case, I believe that the way you're performing the update of your layer contents may be to blame. First, using NSTimer to trigger a redraw does not guarantee that the update events will align well with the refresh rate of your display. Instead, I'd suggest setting the CAOpenGLLayer's asynchronous property to YES and using the –canDrawInCGLContext:pixelFormat:forLayerTime:displayTime: to manage the update frequency. This will cause the OpenGL layer to update in sync with the display, and it will avoid the context locking that you're doing.

The downside to this (which is also a problem with your NSTimer approach) is that the CAOpenGLLayer delegate callbacks are triggered on the main thread. If you have something that blocks the main thread, your display will freeze. Likewise, if your OpenGL frame updates take a while, they may cause your UI to be less responsive.

This is what caused me to use a CVDisplayLink to produce a triggered update of my OpenGL content on a background thread. Unfortunately, I saw some rendering artifacts when updating my CAOpenGLLayer with this, so I ended up switching back to an NSOpenGLView. Since then, I've encountered a way to potentially avoid these artifacts, but the NSOpenGLView has been fine for our needs so I haven't switched back once again.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号