Skip to content

Instantly share code, notes, and snippets.

@gszauer
Last active August 5, 2021 18:03
Show Gist options
  • Select an option

  • Save gszauer/147b6a71302cdbb825cd to your computer and use it in GitHub Desktop.

Select an option

Save gszauer/147b6a71302cdbb825cd to your computer and use it in GitHub Desktop.
Basic Cocoa Window
// gcc Cocoa.m -o OSXWindow -framework Cocoa -framework Quartz -framework OpenGL
#import <Cocoa/Cocoa.h>
#import <QuartzCore/CVDisplayLink.h>
#import <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>
@class View;
static CVReturn GlobalDisplayLinkCallback(CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void*);
@interface View : NSOpenGLView <NSWindowDelegate> {
@public
CVDisplayLinkRef displayLink;
bool running;
NSRect windowRect;
NSRecursiveLock* appLock;
}
@end
@implementation View
// Initialize
- (id) initWithFrame: (NSRect) frame {
running = true;
// No multisampling
int samples = 0;
// Keep multisampling attributes at the start of the attribute lists since code below assumes they are array elements 0 through 4.
NSOpenGLPixelFormatAttribute windowedAttrs[] =
{
NSOpenGLPFAMultisample,
NSOpenGLPFASampleBuffers, samples ? 1 : 0,
NSOpenGLPFASamples, samples,
NSOpenGLPFAAccelerated,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAColorSize, 32,
NSOpenGLPFADepthSize, 24,
NSOpenGLPFAAlphaSize, 8,
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy,
0
};
// Try to choose a supported pixel format
NSOpenGLPixelFormat* pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:windowedAttrs];
if (!pf) {
bool valid = false;
while (!pf && samples > 0) {
samples /= 2;
windowedAttrs[2] = samples ? 1 : 0;
windowedAttrs[4] = samples;
pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:windowedAttrs];
if (pf) {
valid = true;
break;
}
}
if (!valid) {
NSLog(@"OpenGL pixel format not supported.");
return nil;
}
}
self = [super initWithFrame:frame pixelFormat:[pf autorelease]];
appLock = [[NSRecursiveLock alloc] init];
return self;
}
- (void) prepareOpenGL {
[super prepareOpenGL];
[[self window] setLevel: NSNormalWindowLevel];
[[self window] makeKeyAndOrderFront: self];
// Make all the OpenGL calls to setup rendering and build the necessary rendering objects
[[self openGLContext] makeCurrentContext];
// Synchronize buffer swaps with vertical refresh rate
GLint swapInt = 1; // Vsynch on!
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
// Create a display link capable of being used with all active displays
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
// Set the renderer output callback function
CVDisplayLinkSetOutputCallback(displayLink, &GlobalDisplayLinkCallback, self);
CGLContextObj cglContext = (CGLContextObj)[[self openGLContext] CGLContextObj];
CGLPixelFormatObj cglPixelFormat = (CGLPixelFormatObj)[[self pixelFormat] CGLPixelFormatObj];
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat);
GLint dim[2] = {windowRect.size.width, windowRect.size.height};
CGLSetParameter(cglContext, kCGLCPSurfaceBackingSize, dim);
CGLEnable(cglContext, kCGLCESurfaceBackingSize);
[appLock lock];
[[self openGLContext] makeCurrentContext];
CGLLockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
NSLog(@"Initialize");
NSLog(@"GL version: %s", glGetString(GL_VERSION));
NSLog(@"GLSL version: %s", glGetString(GL_SHADING_LANGUAGE_VERSION));
// Temp
glClearColor(0.5f, 0.6f, 0.7f, 1.0f);
glViewport(0, 0, windowRect.size.width, windowRect.size.height);
glEnable(GL_DEPTH_TEST);
// End temp
CGLUnlockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
[appLock unlock];
// Activate the display link
CVDisplayLinkStart(displayLink);
}
// Tell the window to accept input events
- (BOOL)acceptsFirstResponder {
return YES;
}
- (void)mouseMoved:(NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Mouse pos: %lf, %lf", point.x, point.y);
[appLock unlock];
}
- (void) mouseDragged: (NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Mouse pos: %lf, %lf", point.x, point.y);
[appLock unlock];
}
- (void)scrollWheel: (NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Mouse wheel at: %lf, %lf. Delta: %lf", point.x, point.y, [event deltaY]);
[appLock unlock];
}
- (void) mouseDown: (NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Left mouse down: %lf, %lf", point.x, point.y);
[appLock unlock];
}
- (void) mouseUp: (NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Left mouse up: %lf, %lf", point.x, point.y);
[appLock unlock];
}
- (void) rightMouseDown: (NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Right mouse down: %lf, %lf", point.x, point.y);
[appLock unlock];
}
- (void) rightMouseUp: (NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Right mouse up: %lf, %lf", point.x, point.y);
[appLock unlock];
}
- (void)otherMouseDown: (NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Middle mouse down: %lf, %lf", point.x, point.y);
[appLock unlock];
}
- (void)otherMouseUp: (NSEvent*) event {
[appLock lock];
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSLog(@"Middle mouse up: %lf, %lf", point.x, point.y);
[appLock unlock];
}
- (void) mouseEntered: (NSEvent*)event {
[appLock lock];
NSLog(@"Mouse entered");
[appLock unlock];
}
- (void) mouseExited: (NSEvent*)event {
[appLock lock];
NSLog(@"Mouse left");
[appLock unlock];
}
- (void) keyDown: (NSEvent*) event {
[appLock lock];
if ([event isARepeat] == NO) {
NSLog(@"Key down: %d", [event keyCode]);
}
[appLock unlock];
}
- (void) keyUp: (NSEvent*) event {
[appLock lock];
NSLog(@"Key up: %d", [event keyCode]);
[appLock unlock];
}
// Update
- (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime {
[appLock lock];
[[self openGLContext] makeCurrentContext];
CGLLockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
NSLog(@"Update");
// Temp
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// EndTemp
CGLFlushDrawable((CGLContextObj)[[self openGLContext] CGLContextObj]);
CGLUnlockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
if (false) { // Update loop returns false
[NSApp terminate:self];
}
[appLock unlock];
return kCVReturnSuccess;
}
// Resize
- (void)windowDidResize:(NSNotification*)notification {
NSSize size = [ [ _window contentView ] frame ].size;
[appLock lock];
[[self openGLContext] makeCurrentContext];
CGLLockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
NSLog(@"Window resize: %lf, %lf", size.width, size.height);
// Temp
windowRect.size.width = size.width;
windowRect.size.height = size.height;
glViewport(0, 0, windowRect.size.width, windowRect.size.height);
// End temp
CGLUnlockContext((CGLContextObj)[[self openGLContext] CGLContextObj]);
[appLock unlock];
}
- (void)resumeDisplayRenderer {
[appLock lock];
CVDisplayLinkStop(displayLink);
[appLock unlock];
}
- (void)haltDisplayRenderer {
[appLock lock];
CVDisplayLinkStop(displayLink);
[appLock unlock];
}
// Terminate window when the red X is pressed
-(void)windowWillClose:(NSNotification *)notification {
if (running) {
running = false;
[appLock lock];
NSLog(@"Cleanup");
CVDisplayLinkStop(displayLink);
CVDisplayLinkRelease(displayLink);
[appLock unlock];
}
[NSApp terminate:self];
}
// Cleanup
- (void) dealloc {
[super dealloc];
}
@end
static CVReturn GlobalDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) {
CVReturn result = [(View*)displayLinkContext getFrameForTime:outputTime];
return result;
}
int main(int argc, const char * argv[]) {
// Autorelease Pool:
// Objects declared in this scope will be automatically
// released at the end of it, when the pool is "drained".
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Create a shared app instance.
// This will initialize the global variable
// 'NSApp' with the application instance.
[NSApplication sharedApplication];
// Create a window:
// Style flags
NSUInteger windowStyle = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask;
// Window bounds (x, y, width, height)
NSRect screenRect = [[NSScreen mainScreen] frame];
NSRect viewRect = NSMakeRect(0, 0, 800, 600);
NSRect windowRect = NSMakeRect(NSMidX(screenRect) - NSMidX(viewRect),
NSMidY(screenRect) - NSMidY(viewRect),
viewRect.size.width,
viewRect.size.height);
NSWindow * window = [[NSWindow alloc] initWithContentRect:windowRect
styleMask:windowStyle
backing:NSBackingStoreBuffered
defer:NO];
[window autorelease];
// Window controller
NSWindowController * windowController = [[NSWindowController alloc] initWithWindow:window];
[windowController autorelease];
// Since Snow Leopard, programs without application bundles and Info.plist files don't get a menubar
// and can't be brought to the front unless the presentation option is changed
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
// Next, we need to create the menu bar. You don't need to give the first item in the menubar a name
// (it will get the application's name automatically)
id menubar = [[NSMenu new] autorelease];
id appMenuItem = [[NSMenuItem new] autorelease];
[menubar addItem:appMenuItem];
[NSApp setMainMenu:menubar];
// Then we add the quit item to the menu. Fortunately the action is simple since terminate: is
// already implemented in NSApplication and the NSApplication is always in the responder chain.
id appMenu = [[NSMenu new] autorelease];
id appName = [[NSProcessInfo processInfo] processName];
id quitTitle = [@"Quit " stringByAppendingString:appName];
id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
[appMenu addItem:quitMenuItem];
[appMenuItem setSubmenu:appMenu];
// Create app delegate to handle system events
View* view = [[[View alloc] initWithFrame:windowRect] autorelease];
view->windowRect = windowRect;
[window setAcceptsMouseMovedEvents:YES];
[window setContentView:view];
[window setDelegate:view];
// Set app title
[window setTitle:appName];
// Add fullscreen button
[window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
// Show window and run event loop
[window orderFrontRegardless];
[NSApp run];
[pool drain];
return (0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment