app: darwin/amd64 rendering improvements

The full set of event.LifecycleStages are now plumbed through.

More user control over the window, in particular it:
- takes focus correctly on start
- can now be closed and minimized
- can be hidden (allowing testing of LifecycleStageAlive)

Flickering during resize has been eliminated by triggering draws from
a second thread. This encouraged the introduction of a dedicated draw
loop in Go, which makes responsibility for GL calls a little more
like android/x11.

Change-Id: I63982f20bd4859601e2a27915f60f6c8083a176f
Reviewed-on: https://go-review.googlesource.com/11733
Reviewed-by: Nigel Tao <nigeltao@golang.org>
This commit is contained in:
David Crawshaw 2015-06-30 12:09:44 -04:00
parent 8631054b13
commit ddd9618c5b
2 changed files with 136 additions and 49 deletions

View File

@ -14,17 +14,12 @@ package app
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework OpenGL -framework QuartzCore
#import <Cocoa/Cocoa.h>
#import <OpenGL/gl.h>
#include <pthread.h>
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) }

View File

@ -10,15 +10,13 @@
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#import <OpenGL/gl.h>
#import <OpenGL/gl3.h>
#import <QuartzCore/CVReturn.h>
#import <QuartzCore/CVBase.h>
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<NSApplicationDelegate, NSWindowDelegate>
{
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];
}