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:
parent
8631054b13
commit
ddd9618c5b
|
@ -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) }
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue