为什么我的 CAOpenGLLayer 更新速度比之前的 NSOpenGLView 慢?
我有一个在 Mac OS X 上渲染 OpenGL 内容的应用程序。最初它渲染到 NSOpenGLView,然后我将其更改为渲染到 CAOpenGLLayer 子类。
当我这样做时,我看到了巨大的性能损失:帧速率减半,鼠标响应能力降低,卡顿(时不时停止,最多一秒钟,在此期间分析器活动报告等待互斥体以将数据加载到 GPU 内存上),并且加倍CPU 使用率。
我正在调查这个问题,并有几个问题:
- 其他人是否也看到过类似的性能下降?
- 我的 CAOpenGLLayer 设置有问题吗?
- CAOpenGLLayer 和核心动画框架是如何实现的,即我的 OpenGL 内容从 glDrawElements 调用到我的屏幕的路径是什么,以及我应该如何在我这边做一些事情来优化这样的设置的性能?
这是我的 CAOpenGLLayer 设置代码:
// 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(glContext);
CGLContextSetCurrent(oldContext);
}
@end
知道我的显卡不是很强大(具有 64 MB 共享内存的 Intel GMA)可能会很有用。
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(glContext);
CGLContextSetCurrent(oldContext);
}
@end
It may be useful to know my video card isn't very powerful (Intel GMA with 64 MB of shared memory).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在我的一个应用程序中,我从 NSOpenGLView 切换到 CAOpenGLLayer,然后由于后者更新机制的一些问题而最终返回。但是,这与您在此处报告的性能问题不同。
就您而言,我认为您执行图层内容更新的方式可能是罪魁祸首。首先,使用 NSTimer 触发重绘并不能保证更新事件与显示器的刷新率保持一致。相反,我建议将 CAOpenGLLayer 的
asynchronous
属性设置为 YES 并使用–canDrawInCGLContext:pixelFormat:forLayerTime:displayTime:
来管理更新频率。这将导致 OpenGL 层与显示同步更新,并且将避免您正在执行的上下文锁定。这样做的缺点(这也是 NSTimer 方法的问题)是 CAOpenGLLayer 委托回调在主线程上触发。如果你有什么东西阻塞了主线程,你的显示将会冻结。同样,如果您的 OpenGL 帧更新需要一段时间,则可能会导致您的 UI 响应速度降低。
这就是我使用 CVDisplayLink 在后台线程上触发 OpenGL 内容更新的原因。不幸的是,我在用它更新我的 CAOpenGLLayer 时看到了一些渲染伪影,所以我最终切换回 NSOpenGLView。从那时起,我遇到了一种可能避免这些瑕疵的方法,但 NSOpenGLView 已经很好地满足了我们的需求,所以我没有再次切换回来。
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.