-
-
Save db47h/0bb10754d2993c0526e1 to your computer and use it in GitHub Desktop.
go x/mobile basic example for fast drawing app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2014 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 linux | |
// An app that draws a green triangle on a red background. | |
// | |
// Note: This demo is an early preview of Go 1.5. In order to build this | |
// program as an Android APK using the gomobile tool. | |
// | |
// See http://godoc.org/golang.org/x/mobile/cmd/gomobile to install gomobile. | |
// | |
// Get the basic example and use gomobile to build or install it on your device. | |
// | |
// $ go get -d golang.org/x/mobile/example/basic | |
// $ gomobile build golang.org/x/mobile/example/basic # will build an APK | |
// | |
// # plug your Android device to your computer or start an Android emulator. | |
// # if you have adb installed on your machine, use gomobile install to | |
// # build and deploy the APK to an Android target. | |
// $ gomobile install golang.org/x/mobile/example/basic | |
// | |
// Switch to your device or emulator to start the Basic application from | |
// the launcher. | |
// You can also run the application on your desktop by running the command | |
// below. (Note: It currently doesn't work on Windows.) | |
// $ go install golang.org/x/mobile/example/basic && basic | |
package main | |
import ( | |
"encoding/binary" | |
"log" | |
"time" | |
"golang.org/x/mobile/app" | |
"golang.org/x/mobile/event/lifecycle" | |
"golang.org/x/mobile/event/paint" | |
"golang.org/x/mobile/event/size" | |
"golang.org/x/mobile/event/touch" | |
"golang.org/x/mobile/exp/app/debug" | |
"golang.org/x/mobile/exp/f32" | |
"golang.org/x/mobile/exp/gl/glutil" | |
"golang.org/x/mobile/gl" | |
) | |
var ( | |
images *glutil.Images | |
fps *debug.FPS | |
program gl.Program | |
position gl.Attrib | |
offset gl.Uniform | |
color gl.Uniform | |
buf gl.Buffer | |
green float32 | |
touchX float32 | |
touchY float32 | |
) | |
// publishEvent is sent after app.Publish completes | |
type publishEvent struct{} | |
// noEvent is used as a dummy return value for pollEvent when not events were read | |
type noEvent struct{} | |
type clientApp struct { | |
app.App | |
glctx gl.Context | |
sz size.Event // window size | |
dirty bool // needs redraw, useless in a fast drawing app but here for completeness | |
rendering bool | |
pubC chan struct{} // sending on pubC triggers app.Publish | |
stage lifecycle.Stage | |
interval time.Duration // fixed time delta for game world updates | |
} | |
func main() { | |
app.Main(func(gApp app.App) { | |
a := &clientApp{ | |
App: gApp, | |
pubC: make(chan struct{}), | |
stage: lifecycle.StageDead, | |
interval: 8333333 * time.Nanosecond, // set update to 1/120s, we're doing a high fidelity simulation :) | |
} | |
// start async publishing | |
go func() { | |
for _ = range a.pubC { | |
a.Publish() | |
// notify caller | |
a.Send(publishEvent{}) | |
} | |
}() | |
eventC := a.Events() | |
var ( | |
prev = time.Now() | |
lag time.Duration | |
) | |
for { | |
var e interface{} | |
// depending on the app state, we may want to poll or wait on events | |
switch a.stage { | |
case lifecycle.StageDead, lifecycle.StageAlive: | |
// not displaying anything, just wait to be visible again | |
e = waitEvent(eventC) | |
case lifecycle.StageVisible, lifecycle.StageFocused: | |
// we have focus, run as fast as possible | |
e = pollEvent(eventC) | |
} | |
if e == nil { | |
// event channel closed -> quit | |
return | |
} | |
now := time.Now() | |
delta := now.Sub(prev) | |
prev = now | |
lag += delta | |
processEvent(a, e) | |
// update the game world | |
for lag >= a.interval { | |
onUpdate() | |
lag -= a.interval | |
} | |
// render | |
onPaint(a) | |
} | |
}) | |
} | |
// pollEvent polls for events without blocking and returns the first event in the queue, noEvent{} if none, | |
// and nil if the channel is closed. | |
func pollEvent(c <-chan interface{}) interface{} { | |
select { | |
case e := <-c: | |
return e | |
default: | |
return noEvent{} | |
} | |
} | |
// waitEvent blocks until an event is received. Returns nil if the channel is closed. | |
func waitEvent(c <-chan interface{}) interface{} { | |
return <-c | |
} | |
func processEvent(a *clientApp, e interface{}) { | |
switch e := a.Filter(e).(type) { | |
case lifecycle.Event: | |
a.stage = e.To | |
switch e.Crosses(lifecycle.StageVisible) { | |
case lifecycle.CrossOn: | |
a.glctx, _ = e.DrawContext.(gl.Context) | |
onStart(a) | |
a.dirty = true | |
case lifecycle.CrossOff: | |
onStop(a) | |
a.glctx = nil | |
} | |
case size.Event: | |
a.sz = e | |
touchX = float32(e.WidthPx / 2) | |
touchY = float32(e.HeightPx / 2) | |
case paint.Event: | |
if a.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. | |
break | |
} | |
a.dirty = true | |
case touch.Event: | |
touchX = e.X | |
touchY = e.Y | |
a.dirty = true | |
case publishEvent: | |
// Publish has completed, we can draw again | |
a.rendering = false | |
} | |
} | |
func onStart(a *clientApp) { | |
var err error | |
glctx := a.glctx | |
program, err = glutil.CreateProgram(glctx, vertexShader, fragmentShader) | |
if err != nil { | |
log.Printf("error creating GL program: %v", err) | |
return | |
} | |
buf = a.glctx.CreateBuffer() | |
glctx.BindBuffer(gl.ARRAY_BUFFER, buf) | |
glctx.BufferData(gl.ARRAY_BUFFER, triangleData, gl.STATIC_DRAW) | |
position = glctx.GetAttribLocation(program, "position") | |
color = glctx.GetUniformLocation(program, "color") | |
offset = glctx.GetUniformLocation(program, "offset") | |
images = glutil.NewImages(glctx) | |
fps = debug.NewFPS(images) | |
a.dirty = true | |
} | |
func onStop(a *clientApp) { | |
a.glctx.DeleteProgram(program) | |
a.glctx.DeleteBuffer(buf) | |
fps.Release() | |
images.Release() | |
} | |
// onUpdate updates the game world. move things around, etc. It advances time in the game world by a fixed time interval read from clientApp.interval | |
func onUpdate() { | |
green += 0.01 | |
if green > 1 { | |
green = 0 | |
} | |
} | |
func onPaint(a *clientApp) { | |
if a.rendering { | |
return | |
} | |
glctx := a.glctx | |
if glctx == nil { | |
return | |
} | |
a.rendering = true | |
a.dirty = false | |
glctx.ClearColor(1, 0, 0, 1) | |
glctx.Clear(gl.COLOR_BUFFER_BIT) | |
glctx.UseProgram(program) | |
glctx.Uniform4f(color, 0, green, 0, 1) | |
glctx.Uniform2f(offset, touchX/float32(a.sz.WidthPx), touchY/float32(a.sz.HeightPx)) | |
glctx.BindBuffer(gl.ARRAY_BUFFER, buf) | |
glctx.EnableVertexAttribArray(position) | |
glctx.VertexAttribPointer(position, coordsPerVertex, gl.FLOAT, false, 0, 0) | |
glctx.DrawArrays(gl.TRIANGLES, 0, vertexCount) | |
glctx.DisableVertexAttribArray(position) | |
fps.Draw(a.sz) | |
// publish | |
a.pubC <- struct{}{} | |
} | |
var triangleData = f32.Bytes(binary.LittleEndian, | |
0.0, 0.4, 0.0, // top left | |
0.0, 0.0, 0.0, // bottom left | |
0.4, 0.0, 0.0, // bottom right | |
) | |
const ( | |
coordsPerVertex = 3 | |
vertexCount = 3 | |
) | |
const vertexShader = `#version 100 | |
uniform vec2 offset; | |
attribute vec4 position; | |
void main() { | |
// offset comes in with x/y values between 0 and 1. | |
// position bounds are -1 to 1. | |
vec4 offset4 = vec4(2.0*offset.x-1.0, 1.0-2.0*offset.y, 0, 0); | |
gl_Position = position + offset4; | |
}` | |
const fragmentShader = `#version 100 | |
precision mediump float; | |
uniform vec4 color; | |
void main() { | |
gl_FragColor = color; | |
}` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment