Is it possible for a layer-backed NSView
to correctly do OpenGL rendering in drawRect:
, or is it necessary to use NSOpenGLLayer
/CAOpenGLLayer
? Until recently, I haven't needed to worry about that, I just used a non-layer-backed view. But as stated in the AppKit release notes for 10.14:
Windows in apps linked against the macOS 10.14 SDK are displayed using Core Animation when the app is running in macOS 10.14.
To show how this breaks my rendering, I put together a sample app, with code shown below. If the project is run on an OS before Mojave, or is built using an SDK before 10.14, then the view shows solid red that stays that way if the window is resized. But if the project is build using the 10.14 SDK and run under macOS 10.14, then as the window is resized, the view flickers madly, and when you stop resizing, the view may or may not show anything.
Here's the project folder. Also, here is the app, built under High Sierra but with two instances of the view, one marked layer-backed in the nib.
-- AppDelegate.h --
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (strong) NSOpenGLPixelFormat* pixelFormat;
@end
-- AppDelegate.m --
#import "AppDelegate.h"
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@end
@implementation AppDelegate
- (instancetype) init
{
self = [super init];
if (self != nil)
{
NSOpenGLPixelFormatAttribute attribs[] =
{
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAAccelerated,
NSOpenGLPFAColorSize,
32,
0
};
_pixelFormat = [[NSOpenGLPixelFormat alloc]
initWithAttributes: attribs];
NSLog(@"Got pixel format %@", _pixelFormat);
}
return self;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self.window makeKeyAndOrderFront: self];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
}
@end
-- MyGLView.h --
#import <Cocoa/Cocoa.h>
@class AppDelegate;
@interface MyGLView : NSView
@property (weak) IBOutlet AppDelegate* appDelegate;
@end
-- MyGLView.m --
#import "MyGLView.h"
#import "AppDelegate.h"
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl.h>
@interface MyGLView ()
@property (strong) NSOpenGLContext* openGLContext;
@end
@implementation MyGLView
- (void) awakeFromNib
{
self.openGLContext = [[NSOpenGLContext alloc]
initWithFormat: self.appDelegate.pixelFormat shareContext: nil];
self.openGLContext.view = self;
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(resized:)
name: NSViewFrameDidChangeNotification
object: self];
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
- (void) resized: (NSNotification*) note
{
[self.openGLContext update];
}
- (void) drawRect:(NSRect) dirtyRect
{
[self.openGLContext makeCurrentContext];
glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
[self.openGLContext flushBuffer];
}
@end
Update:
It's looking as if what I wanted to do can't be done. So my choices are to use NSOpenGLView
, which uses a private class _NSOpenGLViewBackingLayer
for its layer, or use NSOpenGLLayer
.
I find NSOpenGLLayer
confusing because although it seems to be rendering on screen, it's actually rendering to an FBO. And even if you start with a pixel format that supports double-buffering, you're really doing single-buffered rendering. In contrast, NSOpenGLView
appears to render to the main frame buffer, double-buffered if you like.