blob: 241a42a13f792e7f223c720a0df1771ea064eccc [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
// +build 386 amd64
// +build !ios
package gldriver
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework OpenGL -framework QuartzCore
#import <Cocoa/Cocoa.h>
#include <pthread.h>
#include "cocoa.h"
*/
import "C"
import (
"log"
"runtime"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/config"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/geom"
"golang.org/x/mobile/gl"
)
var initThreadID C.uint64_t
func init() {
// Lock the goroutine responsible for initialization to an OS thread.
// This means the goroutine running main (and calling startDriver 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:
// https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ
runtime.LockOSThread()
initThreadID = C.threadID()
}
var (
theScreen = &screenImpl{
windows: make(map[uintptr]*windowImpl),
}
mainCallback func(screen.Screen)
)
func main(f func(screen.Screen)) error {
if tid := C.threadID(); tid != initThreadID {
log.Fatalf("gldriver.Main called on thread %d, but gldriver.init ran on %d", tid, initThreadID)
}
mainCallback = f
C.startDriver()
return nil
}
//export driverStarted
func driverStarted() {
go func() {
mainCallback(theScreen)
C.stopDriver()
}()
}
//export drawgl
func drawgl(id uintptr) {
theScreen.mu.Lock()
w := theScreen.windows[id]
theScreen.mu.Unlock()
w.draw <- struct{}{}
<-w.drawDone
}
// drawLoop is the primary drawing loop.
//
// After Cocoa has created an NSWindow on the initial OS thread for
// processing Cocoa events in newWindow, it starts drawLoop on another
// goroutine. It is locked to an OS thread for its OpenGL context.
//
// Two Cocoa threads deliver draw signals to drawLoop. 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 (w *windowImpl) drawLoop(ctx uintptr) {
runtime.LockOSThread()
// TODO(crawshaw): confirm that different windows have different GL contexts.
C.makeCurrentContext(C.uintptr_t(ctx))
// TODO(crawshaw): exit this goroutine on Release.
for range w.draw {
w.eventsIn <- paint.Event{}
loop:
for {
select {
case <-gl.WorkAvailable:
gl.DoWork()
case <-w.endPaint:
C.CGLFlushDrawable(C.CGLGetCurrentContext())
break loop
}
}
w.drawDone <- struct{}{}
}
}
//export setGeom
func setGeom(id uintptr, ppp float32, widthPx, heightPx int) {
theScreen.mu.Lock()
w := theScreen.windows[id]
theScreen.mu.Unlock()
w.eventsIn <- config.Event{
WidthPx: widthPx,
HeightPx: heightPx,
WidthPt: geom.Pt(float32(widthPx) / ppp),
HeightPt: geom.Pt(float32(heightPx) / ppp),
PixelsPerPt: ppp,
}
}
//export eventMouseDown
func eventMouseDown(id uintptr, x, y float32) { log.Printf("eventMouseDown") } // TODO
//export eventMouseDragged
func eventMouseDragged(id uintptr, x, y float32) { log.Printf("eventMouseDragged") } // TODO
//export eventMouseEnd
func eventMouseEnd(id uintptr, x, y float32) { log.Printf("eventMouseEnd") } // TODO
func sendLifecycle(to lifecycle.Stage) {
log.Printf("sendLifecycle: %v", to) // TODO
}
//export lifecycleDead
func lifecycleDead() { sendLifecycle(lifecycle.StageDead) }
//export lifecycleAlive
func lifecycleAlive() { sendLifecycle(lifecycle.StageAlive) }
//export lifecycleVisible
func lifecycleVisible() { sendLifecycle(lifecycle.StageVisible) }
//export lifecycleFocused
func lifecycleFocused() { sendLifecycle(lifecycle.StageFocused) }