Skip to content

Instantly share code, notes, and snippets.

@db47h
Last active November 13, 2015 23:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save db47h/0bb10754d2993c0526e1 to your computer and use it in GitHub Desktop.
Save db47h/0bb10754d2993c0526e1 to your computer and use it in GitHub Desktop.
go x/mobile basic example for fast drawing app
// 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