app: remove iOS system draw loop
Much like the recent change for OS X, this puts the Go paint loop in
control of drawing onto the screen.
Change-Id: I37321e4bb58869d4c7cafc51951ea64e540d536b
Reviewed-on: https://go-review.googlesource.com/15611
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/app/darwin_armx.go b/app/darwin_armx.go
index 5344e27..0138a42 100644
--- a/app/darwin_armx.go
+++ b/app/darwin_armx.go
@@ -14,11 +14,13 @@
#include <stdint.h>
#include <pthread.h>
#include <UIKit/UIDevice.h>
+#import <GLKit/GLKit.h>
extern struct utsname sysInfo;
void runApp(void);
-void setContext(void* context);
+void makeCurrentContext(GLintptr ctx);
+void swapBuffers(GLintptr ctx);
uint64_t threadID();
*/
import "C"
@@ -27,7 +29,6 @@
"runtime"
"strings"
"sync"
- "unsafe"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
@@ -116,6 +117,7 @@
PixelsPerPt: pixelsPerPt,
Orientation: o,
}
+ theApp.eventsIn <- paint.Event{External: true}
}
// touchIDs is the current active touches. The position in the array
@@ -165,28 +167,50 @@
}
}
-var workAvailable <-chan struct{}
+//export lifecycleDead
+func lifecycleDead() { theApp.sendLifecycle(lifecycle.StageDead) }
-//export drawgl
-func drawgl(ctx uintptr) {
- if workAvailable == nil {
- C.setContext(unsafe.Pointer(ctx))
- workAvailable = theApp.worker.WorkAvailable()
- // TODO(crawshaw): not just on process start.
- theApp.sendLifecycle(lifecycle.StageFocused)
- }
+//export lifecycleAlive
+func lifecycleAlive() { theApp.sendLifecycle(lifecycle.StageAlive) }
- // TODO(crawshaw): don't send a paint.Event unconditionally. Only send one
- // if the window actually needs redrawing.
- theApp.eventsIn <- paint.Event{}
+//export lifecycleVisible
+func lifecycleVisible() { theApp.sendLifecycle(lifecycle.StageVisible) }
+
+//export lifecycleFocused
+func lifecycleFocused() { theApp.sendLifecycle(lifecycle.StageFocused) }
+
+//export startloop
+func startloop(ctx C.GLintptr) {
+ go theApp.loop(ctx)
+}
+
+// loop is the primary drawing loop.
+//
+// After UIKit has captured the initial OS thread for processing UIKit
+// events in runApp, it starts loop on another goroutine. It is locked
+// to an OS thread for its OpenGL context.
+func (a *app) loop(ctx C.GLintptr) {
+ runtime.LockOSThread()
+ C.makeCurrentContext(ctx)
+
+ workAvailable := a.worker.WorkAvailable()
for {
select {
case <-workAvailable:
- theApp.worker.DoWork()
+ a.worker.DoWork()
case <-theApp.publish:
+ loop1:
+ for {
+ select {
+ case <-workAvailable:
+ a.worker.DoWork()
+ default:
+ break loop1
+ }
+ }
+ C.swapBuffers(ctx)
theApp.publishResult <- PublishResult{}
- return
}
}
}
diff --git a/app/darwin_armx.m b/app/darwin_armx.m
index 9c96986..c86e778 100644
--- a/app/darwin_armx.m
+++ b/app/darwin_armx.m
@@ -15,7 +15,7 @@
struct utsname sysInfo;
-@interface GoAppAppController : GLKViewController<UIContentContainer>
+@interface GoAppAppController : GLKViewController<UIContentContainer, GLKViewDelegate>
@end
@interface GoAppAppDelegate : UIResponder<UIApplicationDelegate>
@@ -25,27 +25,58 @@
@implementation GoAppAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ lifecycleAlive();
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.controller = [[GoAppAppController alloc] initWithNibName:nil bundle:nil];
self.window.rootViewController = self.controller;
[self.window makeKeyAndVisible];
return YES;
}
+
+- (void)applicationDidBecomeActive:(UIApplication * )application {
+ lifecycleFocused();
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ lifecycleVisible();
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ lifecycleAlive();
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ lifecycleDead();
+}
@end
@interface GoAppAppController ()
@property (strong, nonatomic) EAGLContext *context;
+@property (strong, nonatomic) GLKView *glview;
@end
@implementation GoAppAppController
+- (void)viewWillAppear:(BOOL)animated
+{
+ // TODO: replace by swapping out GLKViewController for a UIVIewController.
+ [super viewWillAppear:animated];
+ self.paused = YES;
+}
+
- (void)viewDidLoad {
[super viewDidLoad];
- self.preferredFramesPerSecond = 60;
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
- GLKView *view = (GLKView *)self.view;
- view.context = self.context;
- view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
- view.multipleTouchEnabled = true; // TODO expose setting to user.
+ self.glview = (GLKView*)self.view;
+ self.glview.drawableDepthFormat = GLKViewDrawableDepthFormat24;
+ self.glview.multipleTouchEnabled = true; // TODO expose setting to user.
+ self.glview.context = self.context;
+ self.glview.userInteractionEnabled = YES;
+ self.glview.enableSetNeedsDisplay = YES; // only invoked once
+
+ // Do not use the GLKViewController draw loop.
+ self.paused = YES;
+ self.resumeOnDidBecomeActive = NO;
+ self.preferredFramesPerSecond = 0;
int scale = 1;
if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)]) {
@@ -67,8 +98,11 @@
}];
}
-- (void)update {
- drawgl((GoUintptr)self.context);
+- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
+ // Now that we have been asked to do the first draw, disable any
+ // future draw and hand control over to the Go paint.Event cycle.
+ self.glview.enableSetNeedsDisplay = NO;
+ startloop((GLintptr)self.context);
}
#define TOUCH_TYPE_BEGIN 0 // touch.TypeBegin
@@ -106,7 +140,7 @@
}
}
-void setContext(void* context) {
+void makeCurrentContext(GLintptr context) {
EAGLContext* ctx = (EAGLContext*)context;
if (![EAGLContext setCurrentContext:ctx]) {
// TODO(crawshaw): determine how terrible this is. Exit?
@@ -114,6 +148,14 @@
}
}
+void swapBuffers(GLintptr context) {
+ __block EAGLContext* ctx = (EAGLContext*)context;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [EAGLContext setCurrentContext:ctx];
+ [ctx presentRenderbuffer:GL_RENDERBUFFER];
+ });
+}
+
uint64_t threadID() {
uint64_t id;
if (pthread_threadid_np(pthread_self(), &id)) {