func windowDrawLoop(cb Callbacks, w *C.ANativeWindow, queue *C.AInputQueue) { C.createEGLWindow(w) // TODO: is the library or the app responsible for clearing the buffers? gl.ClearColor(0, 0, 0, 1) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) C.eglSwapBuffers(C.display, C.surface) if errv := gl.GetError(); errv != gl.NO_ERROR { log.Printf("GL initialization error: %s", errv) } geom.Width = geom.Pt(float32(C.windowWidth) / geom.PixelsPerPt) geom.Height = geom.Pt(float32(C.windowHeight) / geom.PixelsPerPt) for { processEvents(cb, queue) select { case <-windowDestroyed: return default: if cb.Draw != nil { cb.Draw() } C.eglSwapBuffers(C.display, C.surface) } } }
func (surface *Surface) SwapBuffers() error { success := C.eglSwapBuffers(surface.Display.eglDisplay, surface.eglSurface) if success == C.EGL_FALSE { return getError() } return nil }
func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) (done bool) { // Android can send a windowRedrawNeeded event any time, including // in the middle of a paint cycle. The redraw event may have changed // the size of the screen, so any partial painting is now invalidated. // We must also not return to Android (via sending on windowRedrawDone) // until a complete paint with the new configuration is complete. // // When a windowRedrawNeeded request comes in, we increment redrawGen // (Gen is short for generation number), and do not make a paint cycle // visible on <-endPaint unless paintGen agrees. If possible, // windowRedrawDone is signalled, allowing onNativeWindowRedrawNeeded // to return. var redrawGen, paintGen uint32 for { processEvents(queue) select { case <-donec: return true case cfg := <-windowConfigChange: // TODO save orientation pixelsPerPt = cfg.pixelsPerPt case w := <-windowRedrawNeeded: sendLifecycle(lifecycle.StageFocused) widthPx := int(C.ANativeWindow_getWidth(w)) heightPx := int(C.ANativeWindow_getHeight(w)) eventsIn <- config.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } if paintGen == 0 { paintGen++ C.createEGLWindow(w) eventsIn <- paint.Event{} } redrawGen++ case <-windowDestroyed: sendLifecycle(lifecycle.StageAlive) return false case <-gl.WorkAvailable: gl.DoWork() case <-endPaint: if paintGen == redrawGen { // eglSwapBuffers blocks until vsync. C.eglSwapBuffers(C.display, C.surface) select { case windowRedrawDone <- struct{}{}: default: } } paintGen = redrawGen eventsIn <- paint.Event{} } } }
func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) (done bool) { C.createEGLWindow(w) // TODO: is this needed if we also have the "case <-windowRedrawNeeded:" below?? sendLifecycle(lifecycle.StageFocused) eventsIn <- config.Event{ Width: geom.Pt(float32(C.windowWidth) / pixelsPerPt), Height: geom.Pt(float32(C.windowHeight) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } if firstWindowDraw { firstWindowDraw = false // TODO: be more principled about when to send a paint event. eventsIn <- paint.Event{} } for { processEvents(queue) select { case <-donec: return true case <-windowRedrawNeeded: // Re-query the width and height. C.querySurfaceWidthAndHeight() sendLifecycle(lifecycle.StageFocused) eventsIn <- config.Event{ Width: geom.Pt(float32(C.windowWidth) / pixelsPerPt), Height: geom.Pt(float32(C.windowHeight) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } case <-windowDestroyed: sendLifecycle(lifecycle.StageAlive) return false case <-gl.WorkAvailable: gl.DoWork() case <-endDraw: // eglSwapBuffers blocks until vsync. C.eglSwapBuffers(C.display, C.surface) eventsIn <- paint.Event{} } } }
func mainUI(vm, jniEnv, ctx uintptr) error { env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer donec := make(chan struct{}) go func() { mainUserFn(app{}) close(donec) }() var q *C.AInputQueue var pixelsPerPt float32 var orientation size.Orientation // Android can send a windowRedrawNeeded event any time, including // in the middle of a paint cycle. The redraw event may have changed // the size of the screen, so any partial painting is now invalidated. // We must also not return to Android (via sending on windowRedrawDone) // until a complete paint with the new configuration is complete. // // When a windowRedrawNeeded request comes in, we increment redrawGen // (Gen is short for generation number), and do not make a paint cycle // visible on <-endPaint unless Generation agrees. If possible, // windowRedrawDone is signalled, allowing onNativeWindowRedrawNeeded // to return. var redrawGen uint32 for { if q != nil { processEvents(env, q) } select { case <-windowCreated: case q = <-inputQueue: case <-donec: return nil case cfg := <-windowConfigChange: pixelsPerPt = cfg.pixelsPerPt orientation = cfg.orientation case w := <-windowRedrawNeeded: if C.surface == nil { if errStr := C.createEGLSurface(w); errStr != nil { return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError()) } } sendLifecycle(lifecycle.StageFocused) widthPx := int(C.ANativeWindow_getWidth(w)) heightPx := int(C.ANativeWindow_getHeight(w)) eventsIn <- size.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, Orientation: orientation, } redrawGen++ eventsIn <- paint.Event{redrawGen} case <-windowDestroyed: if C.surface != nil { if errStr := C.destroyEGLSurface(); errStr != nil { return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError()) } } C.surface = nil sendLifecycle(lifecycle.StageAlive) case <-gl.WorkAvailable: gl.DoWork() case p := <-endPaint: if p.Generation != redrawGen { continue } if C.surface != nil { // eglSwapBuffers blocks until vsync. if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE { log.Printf("app: failed to swap buffers (%s)", eglGetError()) } } select { case windowRedrawDone <- struct{}{}: default: } if C.surface != nil { redrawGen++ eventsIn <- paint.Event{redrawGen} } } } }
func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) (done bool) { C.createEGLWindow(w) // TODO: is this needed if we also have the "case <-windowRedrawNeeded:" below?? sendLifecycle(event.LifecycleStageFocused) eventsIn <- event.Config{ Width: geom.Pt(float32(C.windowWidth) / pixelsPerPt), Height: geom.Pt(float32(C.windowHeight) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } if firstWindowDraw { firstWindowDraw = false // TODO: be more principled about when to send a draw event. eventsIn <- event.Draw{} } for { processEvents(queue) select { case <-donec: return true case <-windowRedrawNeeded: // Re-query the width and height. C.querySurfaceWidthAndHeight() sendLifecycle(event.LifecycleStageFocused) eventsIn <- event.Config{ Width: geom.Pt(float32(C.windowWidth) / pixelsPerPt), Height: geom.Pt(float32(C.windowHeight) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } // This gl.Viewport call has to be in a separate goroutine because any gl // call can block until gl.DoWork is called, but this goroutine is the one // responsible for calling gl.DoWork. // TODO: again, should x/mobile/app be responsible for calling GL code, or // should package gl instead call event.RegisterFilter? { c := make(chan struct{}) go func() { gl.Viewport(0, 0, int(C.windowWidth), int(C.windowHeight)) close(c) }() loop1: for { select { case <-gl.WorkAvailable: gl.DoWork() case <-c: break loop1 } } } case <-windowDestroyed: sendLifecycle(event.LifecycleStageAlive) return false case <-gl.WorkAvailable: gl.DoWork() case <-endDraw: // eglSwapBuffers blocks until vsync. C.eglSwapBuffers(C.display, C.surface) eventsIn <- event.Draw{} } } }
func main(f func(App)) { // Preserve this OS thread for the GL context created below. runtime.LockOSThread() donec := make(chan struct{}) go func() { f(app{}) close(donec) }() var q *C.AInputQueue // Android can send a windowRedrawNeeded event any time, including // in the middle of a paint cycle. The redraw event may have changed // the size of the screen, so any partial painting is now invalidated. // We must also not return to Android (via sending on windowRedrawDone) // until a complete paint with the new configuration is complete. // // When a windowRedrawNeeded request comes in, we increment redrawGen // (Gen is short for generation number), and do not make a paint cycle // visible on <-endPaint unless Generation agrees. If possible, // windowRedrawDone is signalled, allowing onNativeWindowRedrawNeeded // to return. var redrawGen uint32 for { if q != nil { processEvents(q) } select { case <-windowCreated: case q = <-inputQueue: case <-donec: return case cfg := <-windowConfigChange: // TODO save orientation pixelsPerPt = cfg.pixelsPerPt case w := <-windowRedrawNeeded: newWindow := C.surface == nil if newWindow { if errStr := C.createEGLSurface(w); errStr != nil { log.Printf("app: %s (%s)", C.GoString(errStr), eglGetError()) return } } sendLifecycle(lifecycle.StageFocused) widthPx := int(C.ANativeWindow_getWidth(w)) heightPx := int(C.ANativeWindow_getHeight(w)) eventsIn <- config.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } redrawGen++ if newWindow { // New window, begin paint loop. eventsIn <- paint.Event{redrawGen} } case <-windowDestroyed: if C.surface != nil { if errStr := C.destroyEGLSurface(); errStr != nil { log.Printf("app: %s (%s)", C.GoString(errStr), eglGetError()) return } } C.surface = nil sendLifecycle(lifecycle.StageAlive) case <-gl.WorkAvailable: gl.DoWork() case p := <-endPaint: if p.Generation != redrawGen { continue } if C.surface != nil { // eglSwapBuffers blocks until vsync. if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE { log.Printf("app: failed to swap buffers (%s)", eglGetError()) } } select { case windowRedrawDone <- struct{}{}: default: } if C.surface != nil { redrawGen++ eventsIn <- paint.Event{redrawGen} } } } }
func mainUI(vm, jniEnv, ctx uintptr) error { workAvailable := theApp.worker.WorkAvailable() donec := make(chan struct{}) go func() { mainUserFn(theApp) close(donec) }() var pixelsPerPt float32 var orientation size.Orientation for { select { case <-donec: return nil case cfg := <-windowConfigChange: pixelsPerPt = cfg.pixelsPerPt orientation = cfg.orientation case w := <-windowRedrawNeeded: if C.surface == nil { if errStr := C.createEGLSurface(w); errStr != nil { return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError()) } } theApp.sendLifecycle(lifecycle.StageFocused) widthPx := int(C.ANativeWindow_getWidth(w)) heightPx := int(C.ANativeWindow_getHeight(w)) theApp.eventsIn <- size.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, Orientation: orientation, } theApp.eventsIn <- paint.Event{External: true} case <-windowDestroyed: if C.surface != nil { if errStr := C.destroyEGLSurface(); errStr != nil { return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError()) } } C.surface = nil theApp.sendLifecycle(lifecycle.StageAlive) case <-workAvailable: theApp.worker.DoWork() case <-theApp.publish: // TODO: compare a generation number to redrawGen for stale paints? if C.surface != nil { // eglSwapBuffers blocks until vsync. if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE { log.Printf("app: failed to swap buffers (%s)", eglGetError()) } } select { case windowRedrawDone <- struct{}{}: default: } theApp.publishResult <- PublishResult{} } } }
func mainUI(vm, jniEnv, ctx uintptr) error { var worker gl.Worker glctx, worker = gl.NewContext() workAvailable := worker.WorkAvailable() donec := make(chan struct{}) go func() { mainUserFn(theApp) close(donec) }() var pixelsPerPt float32 var orientation size.Orientation // Android can send a windowRedrawNeeded event any time, including // in the middle of a paint cycle. The redraw event may have changed // the size of the screen, so any partial painting is now invalidated. // We must also not return to Android (via sending on windowRedrawDone) // until a complete paint with the new configuration is complete. // // When a windowRedrawNeeded request comes in, we increment redrawGen // (Gen is short for generation number), and do not make a paint cycle // visible on <-endPaint unless Generation agrees. If possible, // windowRedrawDone is signalled, allowing onNativeWindowRedrawNeeded // to return. // // TODO: is this still needed? var redrawGen uint32 for { select { case <-donec: return nil case cfg := <-windowConfigChange: pixelsPerPt = cfg.pixelsPerPt orientation = cfg.orientation case w := <-windowRedrawNeeded: if C.surface == nil { if errStr := C.createEGLSurface(w); errStr != nil { return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError()) } } theApp.sendLifecycle(lifecycle.StageFocused) widthPx := int(C.ANativeWindow_getWidth(w)) heightPx := int(C.ANativeWindow_getHeight(w)) theApp.eventsIn <- size.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, Orientation: orientation, } redrawGen++ theApp.eventsIn <- paint.Event{redrawGen} case <-windowDestroyed: if C.surface != nil { if errStr := C.destroyEGLSurface(); errStr != nil { return fmt.Errorf("%s (%s)", C.GoString(errStr), eglGetError()) } } C.surface = nil theApp.sendLifecycle(lifecycle.StageAlive) case <-workAvailable: worker.DoWork() case <-theApp.publish: // TODO: compare a generation number to redrawGen for stale paints? if C.surface != nil { // eglSwapBuffers blocks until vsync. if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE { log.Printf("app: failed to swap buffers (%s)", eglGetError()) } } select { case windowRedrawDone <- struct{}{}: default: } theApp.publishResult <- PublishResult{} } } }
func SwapBuffers(disp Display, surface Surface) bool { return goBoolean(C.eglSwapBuffers(C.EGLDisplay(unsafe.Pointer(disp)), C.EGLSurface(unsafe.Pointer(surface)))) }