2
0
mirror of synced 2025-02-22 14:28:14 +00:00

event/paint: mark paint events sent by the system

A paint.Event now has an External field. Whenever a paint event is
sent by the x/mobile/app package, it is marked as external so users
with an active paint loop can ignore them.

Implemented on OS X and Android, with examples updated.

Change-Id: Ibee8d65625c8818ff954936be48257ad30daa147
Reviewed-on: https://go-review.googlesource.com/15480
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
David Crawshaw 2015-10-06 15:57:53 -04:00
parent 0d45604e75
commit 7fe809f8e0
7 changed files with 46 additions and 62 deletions

View File

@ -278,21 +278,6 @@ func mainUI(vm, jniEnv, ctx uintptr) error {
var pixelsPerPt float32 var pixelsPerPt float32
var orientation size.Orientation 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 { for {
select { select {
case <-donec: case <-donec:
@ -317,8 +302,7 @@ func mainUI(vm, jniEnv, ctx uintptr) error {
PixelsPerPt: pixelsPerPt, PixelsPerPt: pixelsPerPt,
Orientation: orientation, Orientation: orientation,
} }
redrawGen++ theApp.eventsIn <- paint.Event{External: true}
theApp.eventsIn <- paint.Event{redrawGen}
case <-windowDestroyed: case <-windowDestroyed:
if C.surface != nil { if C.surface != nil {
if errStr := C.destroyEGLSurface(); errStr != nil { if errStr := C.destroyEGLSurface(); errStr != nil {

View File

@ -113,7 +113,9 @@ var drawDone = make(chan struct{})
func drawgl() { func drawgl() {
switch theApp.lifecycleStage { switch theApp.lifecycleStage {
case lifecycle.StageFocused, lifecycle.StageVisible: case lifecycle.StageFocused, lifecycle.StageVisible:
theApp.Send(paint.Event{}) theApp.Send(paint.Event{
External: true,
})
<-drawDone <-drawDone
} }
} }
@ -218,7 +220,6 @@ func lifecycleAlive() { theApp.sendLifecycle(lifecycle.StageAlive) }
//export lifecycleVisible //export lifecycleVisible
func lifecycleVisible() { func lifecycleVisible() {
theApp.sendLifecycle(lifecycle.StageVisible) theApp.sendLifecycle(lifecycle.StageVisible)
theApp.eventsIn <- paint.Event{}
} }
//export lifecycleFocused //export lifecycleFocused

View File

@ -7,11 +7,18 @@
// See the golang.org/x/mobile/app package for details on the event model. // See the golang.org/x/mobile/app package for details on the event model.
package paint // import "golang.org/x/mobile/event/paint" package paint // import "golang.org/x/mobile/event/paint"
// Event indicates that the app is ready to paint the next frame of the GUI. A // Event indicates that the app is ready to paint the next frame of the GUI.
// frame is completed by calling the App's EndPaint method. //
//A frame is completed by calling the App's Publish method.
type Event struct { type Event struct {
// Generation is a monotonically increasing generation number. // External is true for paint events sent by the screen driver.
// //
// TODO: is a generation number the right model for stale paints? // An external event may be sent at any time in response to an
Generation uint32 // operating system event, for example the window opened, was
// resized, or the screen memory was lost.
//
// Programs actively drawing to the screen as fast as vsync allows
// should ignore external paint events to avoid a backlog of paint
// events building up.
External bool
} }

View File

@ -73,28 +73,27 @@ var (
func main() { func main() {
app.Main(func(a app.App) { app.Main(func(a app.App) {
var glctx gl.Context var glctx gl.Context
visible := false
for e := range a.Events() { for e := range a.Events() {
switch e := a.Filter(e).(type) { switch e := a.Filter(e).(type) {
case lifecycle.Event: case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) { switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn: case lifecycle.CrossOn:
visible = true
glctx, _ = e.DrawContext.(gl.Context) glctx, _ = e.DrawContext.(gl.Context)
onStart(glctx) onStart(glctx)
a.Send(paint.Event{})
case lifecycle.CrossOff: case lifecycle.CrossOff:
visible = false
onStop() onStop()
glctx = nil
} }
case size.Event: case size.Event:
sz = e sz = e
case paint.Event: case paint.Event:
if glctx == nil || e.External {
continue
}
onPaint(glctx) onPaint(glctx)
a.Publish() a.Publish()
if visible { a.Send(paint.Event{}) // keep animating
// Keep animating.
a.Send(paint.Event{})
}
} }
} }
}) })

View File

@ -60,35 +60,36 @@ var (
func main() { func main() {
app.Main(func(a app.App) { app.Main(func(a app.App) {
var glctx gl.Context var glctx gl.Context
visible, sz := false, size.Event{} var sz size.Event
for e := range a.Events() { for e := range a.Events() {
switch e := a.Filter(e).(type) { switch e := a.Filter(e).(type) {
case lifecycle.Event: case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) { switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn: case lifecycle.CrossOn:
visible = true
glctx, _ = e.DrawContext.(gl.Context) glctx, _ = e.DrawContext.(gl.Context)
onStart(glctx) onStart(glctx)
a.Send(paint.Event{})
case lifecycle.CrossOff: case lifecycle.CrossOff:
visible = false
onStop(glctx) onStop(glctx)
glctx = nil
} }
case size.Event: case size.Event:
sz = e sz = e
touchX = float32(sz.WidthPx / 2) touchX = float32(sz.WidthPx / 2)
touchY = float32(sz.HeightPx / 2) touchY = float32(sz.HeightPx / 2)
case paint.Event: case paint.Event:
if glctx == nil || e.External {
// As we are actively painting as fast as
// we can (usually 60 FPS), skip any paint
// events sent by the system.
continue
}
onPaint(glctx, sz) onPaint(glctx, sz)
a.Publish() a.Publish()
if visible { // Drive the animation by preparing to paint the next frame
// Drive the animation by preparing to paint the next frame // after this one is shown.
// after this one is shown. a.Send(paint.Event{})
//
// TODO: is paint.Event the right thing to send? Should we
// have a dedicated publish.Event type? Should App.Publish
// take an optional event sender and send a publish.Event?
a.Send(paint.Event{})
}
case touch.Event: case touch.Event:
touchX = e.X touchX = e.X
touchY = e.Y touchY = e.Y

View File

@ -46,8 +46,6 @@ import (
"golang.org/x/mobile/event/lifecycle" "golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint" "golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size" "golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/app/debug"
"golang.org/x/mobile/exp/gl/glutil"
"golang.org/x/mobile/gl" "golang.org/x/mobile/gl"
) )
@ -68,14 +66,12 @@ func main() {
switch e := a.Filter(e).(type) { switch e := a.Filter(e).(type) {
case lifecycle.Event: case lifecycle.Event:
glctx, _ = e.DrawContext.(gl.Context) glctx, _ = e.DrawContext.(gl.Context)
if glctx != nil {
glctx = e.DrawContext.(gl.Context)
images = glutil.NewImages(glctx)
fps = debug.NewFPS(images)
}
case size.Event: case size.Event:
sz = e sz = e
case paint.Event: case paint.Event:
if glctx == nil {
continue
}
onDraw(glctx, sz) onDraw(glctx, sz)
a.Publish() a.Publish()
} }
@ -85,8 +81,6 @@ func main() {
} }
var ( var (
images *glutil.Images
fps *debug.FPS
determined = make(chan struct{}) determined = make(chan struct{})
ok = false ok = false
) )
@ -113,6 +107,4 @@ func onDraw(glctx gl.Context, sz size.Event) {
glctx.ClearColor(0, 0, 0, 1) glctx.ClearColor(0, 0, 0, 1)
} }
glctx.Clear(gl.COLOR_BUFFER_BIT) glctx.Clear(gl.COLOR_BUFFER_BIT)
fps.Draw(sz)
} }

View File

@ -61,28 +61,28 @@ var (
func main() { func main() {
app.Main(func(a app.App) { app.Main(func(a app.App) {
var glctx gl.Context var glctx gl.Context
visible, sz := false, size.Event{} var sz size.Event
for e := range a.Events() { for e := range a.Events() {
switch e := a.Filter(e).(type) { switch e := a.Filter(e).(type) {
case lifecycle.Event: case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) { switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn: case lifecycle.CrossOn:
visible = true
glctx, _ = e.DrawContext.(gl.Context) glctx, _ = e.DrawContext.(gl.Context)
onStart(glctx) onStart(glctx)
a.Send(paint.Event{})
case lifecycle.CrossOff: case lifecycle.CrossOff:
visible = false
onStop() onStop()
glctx = nil
} }
case size.Event: case size.Event:
sz = e sz = e
case paint.Event: case paint.Event:
if visible { if glctx == nil || e.External {
onPaint(glctx, sz) continue
a.Publish()
// Keep animating.
a.Send(paint.Event{})
} }
onPaint(glctx, sz)
a.Publish()
a.Send(paint.Event{}) // keep animating
} }
} }
}) })