diff --git a/app/darwin_amd64.go b/app/darwin_amd64.go index 8ef44bb..07ca59e 100644 --- a/app/darwin_amd64.go +++ b/app/darwin_amd64.go @@ -14,17 +14,12 @@ package app #cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Cocoa -framework OpenGL -framework QuartzCore #import -#import #include -void glGenVertexArrays(GLsizei n, GLuint* array); -void glBindVertexArray(GLuint array); - void runApp(void); +void stopApp(void); void makeCurrentContext(GLintptr); -double backingScaleFactor(); uint64 threadID(); - */ import "C" import ( @@ -41,8 +36,8 @@ var initThreadID uint64 func init() { // Lock the goroutine responsible for initialization to an OS thread. - // This means the goroutine running main (and calling the run function - // below) is locked to the OS thread that started the program. This is + // This means the goroutine running main (and calling runApp below) + // is locked to the OS thread that started the program. This is // necessary for the correct delivery of Cocoa events to the process. // // A discussion on this topic: @@ -58,12 +53,59 @@ func main(f func(App)) { go func() { f(app{}) + C.stopApp() // TODO(crawshaw): trigger runApp to return }() C.runApp() } +// loop is the primary drawing loop. +// +// After Cocoa has captured the initial OS thread for processing Cocoa +// events in runApp, it starts loop on another goroutine. It is locked +// to an OS thread for its OpenGL context. +// +// Two Cocoa threads deliver draw signals to loop. The primary source of +// draw events is the CVDisplayLink timer, which is tied to the display +// vsync. Secondary draw events come from [NSView drawRect:] when the +// window is resized. +func loop(ctx C.GLintptr) { + runtime.LockOSThread() + C.makeCurrentContext(ctx) + + for range draw { + eventsIn <- event.Draw{} + loop1: + for { + select { + case <-gl.WorkAvailable: + gl.DoWork() + case <-endDraw: + C.CGLFlushDrawable(C.CGLGetCurrentContext()) + break loop1 + } + } + drawDone <- struct{}{} + } +} + +var ( + draw = make(chan struct{}) + drawDone = make(chan struct{}) +) + +//export drawgl +func drawgl() { + draw <- struct{}{} + <-drawDone +} + +//export startloop +func startloop(ctx C.GLintptr) { + go loop(ctx) +} + var windowHeight geom.Pt //export setGeom @@ -102,32 +144,14 @@ func eventMouseDragged(x, y float32) { sendTouch(event.ChangeNone, x, y) } //export eventMouseEnd func eventMouseEnd(x, y float32) { sendTouch(event.ChangeOff, x, y) } -var startedgl = false +//export lifecycleDead +func lifecycleDead() { sendLifecycle(event.LifecycleStageDead) } -//export drawgl -func drawgl(ctx C.GLintptr) { - if !startedgl { - startedgl = true - C.makeCurrentContext(ctx) +//export lifecycleAlive +func lifecycleAlive() { sendLifecycle(event.LifecycleStageAlive) } - // Using attribute arrays in OpenGL 3.3 requires the use of a VBA. - // But VBAs don't exist in ES 2. So we bind a default one. - var id C.GLuint - C.glGenVertexArrays(1, &id) - C.glBindVertexArray(id) +//export lifecycleVisible +func lifecycleVisible() { sendLifecycle(event.LifecycleStageVisible) } - sendLifecycle(event.LifecycleStageFocused) - } - - eventsIn <- event.Draw{} - - for { - select { - case <-gl.WorkAvailable: - gl.DoWork() - case <-endDraw: - C.CGLFlushDrawable(C.CGLGetCurrentContext()) - return - } - } -} +//export lifecycleFocused +func lifecycleFocused() { sendLifecycle(event.LifecycleStageFocused) } diff --git a/app/darwin_amd64.m b/app/darwin_amd64.m index ceea093..c8e3731 100644 --- a/app/darwin_amd64.m +++ b/app/darwin_amd64.m @@ -10,15 +10,13 @@ #import #import -#import +#import #import #import static CVReturn displayLinkDraw(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) { - NSOpenGLView* view = displayLinkContext; - NSOpenGLContext *currentContext = [view openGLContext]; - drawgl((GLintptr)currentContext); + drawgl(); return kCVReturnSuccess; } @@ -36,7 +34,7 @@ uint64 threadID() { } -@interface MobileGLView : NSOpenGLView +@interface MobileGLView : NSOpenGLView { CVDisplayLinkRef displayLink; } @@ -54,10 +52,19 @@ uint64 threadID() { CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat); - CVDisplayLinkStart(displayLink); + + // Using attribute arrays in OpenGL 3.3 requires the use of a VBA. + // But VBAs don't exist in ES 2. So we bind a default one. + GLuint vba; + glGenVertexArrays(1, &vba); + glBindVertexArray(vba); + + startloop((GLintptr)[self openGLContext]); } - (void)reshape { + [super reshape]; + // Calculate screen PPI. // // Note that the backingScaleFactor converts from logical @@ -88,6 +95,14 @@ uint64 threadID() { setGeom(pixelsPerPt, w, h); } +- (void)drawRect:(NSRect)theRect { + // Called during resize. Do an extra draw if we are visible. + // This gets rid of flicker when resizing. + if (CVDisplayLinkIsRunning(displayLink)) { + drawgl(); + } +} + - (void)mouseDown:(NSEvent *)theEvent { double scale = [[NSScreen mainScreen] backingScaleFactor]; NSPoint p = [theEvent locationInWindow]; @@ -105,10 +120,46 @@ uint64 threadID() { NSPoint p = [theEvent locationInWindow]; eventMouseDragged(p.x * scale, p.y * scale); } + +- (void)windowDidBecomeKey:(NSNotification *)notification { + lifecycleFocused(); +} + +- (void)windowDidResignKey:(NSNotification *)notification { + if (![NSApp isHidden]) { + lifecycleVisible(); + } +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + lifecycleAlive(); + [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; + [self.window makeKeyAndOrderFront:self]; + lifecycleVisible(); + CVDisplayLinkStart(displayLink); +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + lifecycleDead(); +} + +- (void)applicationDidHide:(NSNotification *)aNotification { + CVDisplayLinkStop(displayLink); + lifecycleAlive(); +} + +- (void)applicationWillUnhide:(NSNotification *)notification { + lifecycleVisible(); + CVDisplayLinkStart(displayLink); +} + +- (void)windowWillClose:(NSNotification *)notification { + CVDisplayLinkStop(displayLink); + lifecycleAlive(); +} @end -void -runApp(void) { +void runApp(void) { [NSAutoreleasePool new]; [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; @@ -120,6 +171,12 @@ runApp(void) { id menu = [[NSMenu new] autorelease]; id name = [[NSProcessInfo processInfo] processName]; + + id hideMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Hide" + action:@selector(hide:) keyEquivalent:@"h"] + autorelease]; + [menu addItem:hideMenuItem]; + id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"] autorelease]; @@ -128,15 +185,16 @@ runApp(void) { NSRect rect = NSMakeRect(0, 0, 400, 400); - id window = [[[NSWindow alloc] initWithContentRect:rect + NSWindow* window = [[[NSWindow alloc] initWithContentRect:rect styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO] autorelease]; - [window setStyleMask:[window styleMask] | NSResizableWindowMask]; + window.styleMask |= NSResizableWindowMask; + window.styleMask |= NSMiniaturizableWindowMask ; + window.styleMask |= NSClosableWindowMask; + window.title = name; [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; - [window makeKeyAndOrderFront:nil]; - [window setTitle:name]; NSOpenGLPixelFormatAttribute attr[] = { NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, @@ -145,12 +203,17 @@ runApp(void) { NSOpenGLPFADepthSize, 16, NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, + NSOpenGLPFAAllowOfflineRenderers, 0 }; id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; - id view = [[MobileGLView alloc] initWithFrame:rect pixelFormat:pixFormat]; + MobileGLView* view = [[MobileGLView alloc] initWithFrame:rect pixelFormat:pixFormat]; [window setContentView:view]; - - [NSApp activateIgnoringOtherApps:YES]; + [window setDelegate:view]; + [NSApp setDelegate:view]; [NSApp run]; } + +void stopApp(void) { + [NSApp terminate:nil]; +}