I am trying to programmatically create a Cocoa window with an OpenGL context for an OS X application. I have been unable to find an example online which does not use the Interface Builder for creating a window and OpenGL view.
All I want is for glClear to make my window magenta (0xFF00FF). However, the window remains white.
Here is my project:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
NSOpenGLContext *openGLContext;
}
#property (assign) NSWindow *window;
#property (assign) NSOpenGLContext *openGLContext;
- (void)draw;
#end
AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window;
#synthesize openGLContext;
static NSOpenGLPixelFormatAttribute glAttributes[] = {
0
};
- (void)draw {
NSLog(#"Drawing...");
[self.openGLContext makeCurrentContext];
glClearColor(1, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
[self.openGLContext flushBuffer];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSRect frame = NSMakeRect(0, 0, 200, 200);
self.window = [[[NSWindow alloc]
initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO] autorelease];
[self.window makeKeyAndOrderFront:nil];
NSOpenGLPixelFormat *pixelFormat
= [[NSOpenGLPixelFormat alloc] initWithAttributes:glAttributes];
self.openGLContext = [[NSOpenGLContext alloc]
initWithFormat:pixelFormat shareContext:nil];
[self.openGLContext setView:[self.window contentView]];
[NSTimer
scheduledTimerWithTimeInterval:.1
target:self
selector:#selector(draw)
userInfo:nil
repeats:YES];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)_app {
return YES;
}
#end
main.m
#import "AppDelegate.h"
#import <Cocoa/Cocoa.h>
int main(int argc, char **argv) {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
return NSApplicationMain(argc, (const char **) argv);
}
The documentation for -[NSOpenGLContext flushBuffer] says:
Discussion
If the receiver is not a double-buffered context, this call does nothing.
You can cause your context to be double-buffered by including NSOpenGLPFADoubleBuffer in your pixel format attributes. Alternatively, you can call glFlush() instead of -[NSOpenGLContext flushBuffer] and leave your context single-buffered.
Use this code for the pixel format attributes instead.
NSOpenGLPixelFormatAttribute glAttributes[] =
{
NSOpenGLPFAColorSize, 24,
NSOpenGLPFAAlphaSize, 8,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAAccelerated,
0
};
I've tested it and you get the magenta screen you were looking for.
Related
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.
So I got a Window:
Window.xib
I got a WindowController too:
WindowController.h reads:
#import <Cocoa/Cocoa.h>
#interface MainWindowController : NSWindowController
{
IBOutlet NSView *firstView;
IBOutlet NSView *secondView;
IBOutlet NSView *thirdView;
int currentViewTag;
}
-(IBAction)switchView:(id)sender;
#end
And the WindowController.m reads:
#import "MainWindowController.h"
#interface MainWindowController ()
#end
#implementation MainWindowController
-(id)init
{
self = [super initWithWindowNibName:#"MainWindow"];
if (self){
// Initialization code here
}
return self;
}
//- (void)windowDidLoad {
// [super windowDidLoad];
//
// // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
//}
#pragma mark - Custom view drawing
-(NSRect)newFrameForNewContentView:(NSView *)view
{
NSWindow *window = [self window];
NSRect newFrameRect = [window frameRectForContentRect:[view frame]];
NSRect oldFrameRect = [window frame];
NSSize newSize = newFrameRect.size;
NSSize oldSize = oldFrameRect.size;
NSRect frame = [window frame];
frame.size = newSize;
frame.origin.y -= (newSize.height - oldSize.height);
return frame;
}
-(NSView *)viewForTag:(int)tag{
NSView *view = nil;
if (tag == 0) {
view = firstView;
} else if (tag == 1) {
view = secondView;
} else {
view = thirdView;
}
return view;
}
-(BOOL) validateToolbarItem:(NSToolbarItem *)item
{
if ([item tag] == currentViewTag) return NO;
else return YES;
}
-(void)awakeFromNib
{
[[self window] setContentSize:[firstView frame].size];
[[[self window] contentView]addSubview:firstView];
[[[self window] contentView]setWantsLayer:YES];
}
-(IBAction)switchView:(id)sender
{
int tag = [sender tag];
NSView *view = [self viewForTag:tag];
NSView *previousView = [self viewForTag:currentViewTag];
currentViewTag = tag;
NSRect newFrame = [self newFrameForNewContentView:view];
[NSAnimationContext beginGrouping];
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask)
[[NSAnimationContext currentContext] setDuration:1.0];
[[[[self window]contentView]animator]replaceSubview:previousView with:view];
[[[self window]animator]setFrame:newFrame display:YES];
[NSAnimationContext endGrouping];
}
#end
The problem I have is that when i switch tabs in my app, the custom view (and there are three of different sizes) draw differently each time. Look at the screenshots, all of the numbers should be centre aligned but they are sometimes and others not. Can anyone see what my error is please?
I will also add that all of the actions have been correctly configured + the code works perfectly if the custom view size is the same all the time.
The view that works
The view that almost works
Again pointing out that in my .xib all of the numbers are aligned to 0x and 0y axis.
Appdelegate.h
#import <Cocoa/Cocoa.h>
#class MainWindowController;
#interface AppDelegate : NSObject <NSApplicationDelegate> {
MainWindowController *mainWindowController;
}
#end
Appdelegate.m
#interface AppDelegate ()
#property (nonatomic) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
-(void)awakeFromNib
{
if(!mainWindowController){
mainWindowController = [[MainWindowController alloc]init];
}
[mainWindowController showWindow:nil];
}
#end
In Interface Builder, make sure to disable the "autoresizes subviews" checkbox of the default view of your window
When I try calling setColorSpace on my NSWindow object I get no change in color. I was under the impression that I could dynamically change the way color is rendered.
Here is the .h file for my controller
#import <Cocoa/Cocoa.h>
#interface MainWindow : NSWindowController <NSTextFieldDelegate>
{
}
#property (strong) IBOutlet NSWindow *theWindow;
#property (weak) IBOutlet NSTextField *RedField;
#property (weak) IBOutlet NSTextField *GreenField;
#property (weak) IBOutlet NSTextField *BlueField;
#property (weak) IBOutlet NSTextField *PatternField;
#property (weak) IBOutlet NSButton *ICCBox;
- (IBAction)UpdateICC:(id)sender;
#end
Here is the .m file for my controller
#import "MainWindow.h"
#import <AppKit/AppKit.h>
#interface MainWindow ()
#end
#implementation MainWindow
- (id)init
{
self = [super init];
return self;
}
- (void)awakeFromNib
{
[_RedField setDelegate:self];
[_GreenField setDelegate:self];
[_BlueField setDelegate:self];
}
-(void) controlTextDidChange:(NSNotification *) note {
float redByte = [_RedField floatValue];
float redF = redByte/255.0;
float greenByte = [_GreenField floatValue];
float greenF = greenByte/255.0;
float blueByte = [_BlueField floatValue];
float blueF = blueByte/255.0;
_PatternField.backgroundColor = [NSColor colorWithCalibratedRed:redF green:greenF blue:blueF alpha:1];
}
- (IBAction)UpdateICC:(id)sender {
NSColorSpace *acs = [NSColorSpace adobeRGB1998ColorSpace];
NSColorSpace *scs = [NSColorSpace sRGBColorSpace];
NSColorSpace *dcs = [NSColorSpace deviceRGBColorSpace];
if(_ICCBox.state == NSOnState)
{
[_theWindow setColorSpace:scs];
}
else
{
[_theWindow setColorSpace:dcs];
}
}
#end
Any idea why this isn't working?
You should post specified notification to default NotificationCenter to immediately apply changes (cause redraw window with new color profile).
- (IBAction)UpdateICC:(id)sender {
NSColorSpace *acs = [NSColorSpace adobeRGB1998ColorSpace];
NSColorSpace *scs = [NSColorSpace sRGBColorSpace];
NSColorSpace *dcs = [NSColorSpace deviceRGBColorSpace];
if(_ICCBox.state == NSOnState)
{
[_theWindow setColorSpace:scs];
}
else
{
[_theWindow setColorSpace:dcs];
}
[[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification object:_theWindow];
// In some cases additional call needed:
[_theWindow.contentView viewDidChangeBackingProperties];
}
I'm new to the whole Cocoa/Objective-C malarky (but have years of C/C++ on *nix).
I'm trying to create a basic Cocoa application that is filled with an NSOpenGLView. I have:
chink.mm
#import <OpenGL/gl.h>
#import "chink.h"
#implementation chinkView
#synthesize window;
- (void) applicationDidFinishLaunching : (NSNotification *)aNotification {
NSLog(#"Danger, Will Robinson! Danger!");
}
- (void) dealloc
{
[window release];
[super dealloc];
}
#end
and chink.h
#interface chinkView : NSOpenGLView {
NSWindow *window;
}
#property (assign) NSWindow *window;
#end
I have a single .xib set up like
https://skitch.com/cront/ri625/chinkxib
where 'chink View' is set as delegate for Window and File Owner.
I'm getting an 'invalid drawable' error (even before the application finished launching), which to my understanding means that it is trying to instantiate the opengl view before the window (although I'm probably wrong).
What I want is the solution to this problem of creating the window and setting the opengl view inside it. Once this is done I have absolutely no problem writing the OGL (like I said, I know C++) but all this NS calls gunk is alien to me.
I'm not looking for 'why don't you do it in SDL/GLUT' answers please. Just the code to make it create the window properly.
Okay so I sacked off this approach.
Went the long route and changed to this:
chink.mm
#import <Cocoa/Cocoa.h>
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl.h>
#import <OpenGL/glu.h>
#import "chink.h"
#implementation chinkNS
- (id)initWithFrame:(NSRect)frameRect
{
NSOpenGLPixelFormat * pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:(NSOpenGLPixelFormatAttribute[]) {
NSOpenGLPFAWindow,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFADepthSize, 32,
nil
}];
if(pixelFormat == NULL)
NSLog(#"pixelFormat == NULL");
[pixelFormat autorelease];
self = [super initWithFrame:frameRect pixelFormat:pixelFormat];
if(self == NULL) {
NSLog(#"Could not initialise self");
return NULL;
}
[[self openGLContext] makeCurrentContext];
[self initGL];
return self;
}
- (void)awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(windowWillMiniaturize:) name:NSWindowWillMiniaturizeNotification object:[self window]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:[self window]];
animationTimer = [NSTimer scheduledTimerWithTimeInterval:1/60.0 target:self selector:#selector(timerFired) userInfo:NULL repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSEventTrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSModalPanelRunLoopMode];
lastTime = [[NSDate date] timeIntervalSince1970];
}
- (void)drawRect:(NSRect)frame
{
NSLog(#"Chink is up and running. Let's do this thing!");
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
glFlush();
[self drawGL];
[[self openGLContext] flushBuffer];
}
#pragma mark OpenGL
- (void)initGL { }
- (void)reshapeGL { }
- (void)drawGL { }
- (void)animate:(float)dt { }
#pragma mark Event Handling
- (void)timerFired
{
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
[self animate:currentTime-lastTime];
lastTime = currentTime;
}
#pragma mark Loose ends
- (void)setFrame:(NSRect)frame
{
[super setFrame:frame];
[self reshapeGL];
}
// To get key events
- (BOOL)acceptsFirstResponder
{
return YES;
}
#end
and chink.h
#interface chinkNS : NSOpenGLView
{
NSTimer * animationTimer;
NSTimeInterval lastTime;
}
- (void)initGL;
- (void)reshapeGL;
- (void)drawGL;
- (void)animate:(float)dt;
#end
The same settings in the .xib except that it is a standard NSView that subclasses my opengl view.
I was hoping for a simpler, more elegant set up as above but you can't win 'em all.
Closed.
EDIT 1: changed the code to:
delegate.h:
#import <UIKit/UIKit.h>
#class ViewController;
#interface AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
ViewController *viewController;
UIImageView *splashView;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet ViewController *viewController;
#property (nonatomic, retain) IBOutlet UIImageView *splashView;
- (void)removeSplash;
#end
delegate.m:
#import "AppDelegate.h"
#import "ViewController.h"
#implementation AppDelegate
#synthesize window;
#synthesize viewController;
#synthesize splashView;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
splashView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
splashView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Splash" ofType:#"png"]];
[window addSubview:splashView];
[window bringSubviewToFront:splashView];
[window makeKeyAndVisible];
[self performSelector:#selector(removeSplash) withObject:nil afterDelay:5.0];
[window addSubview:viewController.view];
return YES;
}
- (void)removeSplash {
[splashView removeFromSuperview];
[splashView release];
}
- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}
#end
EDIT 2:
when I use:
splashView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
splashView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Splash" ofType:#"png"]];
if (splashView.image == nil) {
NSLog(#"splashView is nil");
}
it logs "splashView is nil"
My Viewcontroller is empty, just for debugging purposes.
As you probably already know, splash screens are discouraged. Since your image is Default.png, isn't it already being shown on app launch automatically?
In any case, the sleep() call is probably blocking the UI. Remove the sleep() and move the statements after it (the removeFromSuperview, etc) to another method in the app delegate. Call this method using performSelector:withObject:afterDelay:. Put the performSelector call where you currently have the sleep call.
Also, you should use the didFinishLaunchingWithOptions method instead of the old applicationDidFinishLaunching method.