go.mobile/app/debug: basic GL-based debugging tools

LGTM=nigeltao
R=nigeltao, bryanturley, crawshaw
CC=adg, davidday, golang-codereviews
https://golang.org/cl/136550043
This commit is contained in:
David Crawshaw 2014-09-22 11:18:43 -04:00
parent 8439ecbf6b
commit 022508a5a3
3 changed files with 289 additions and 0 deletions

78
app/debug/fps.go Normal file
View File

@ -0,0 +1,78 @@
// 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.
// Package debug provides GL-based debugging tools for apps.
package debug
import (
"fmt"
"image"
"image/draw"
"io/ioutil"
"log"
"runtime"
"sync"
"time"
"code.google.com/p/freetype-go/freetype"
"code.google.com/p/go.mobile/geom"
)
var lastDraw = time.Now()
var monofont = freetype.NewContext()
var fps struct {
sync.Once
*rgba
}
// TODO(crawshaw): It looks like we need a gl.RegisterInit feature.
// TODO(crawshaw): The gldebug mode needs to complain loudly when GL functions
// are called before init, because often they fail silently.
func fpsInit() {
font := "/system/fonts/DroidSansMono.ttf"
if runtime.GOOS == "darwin" {
font = "/Library/Fonts/Andale Mono.ttf"
}
b, err := ioutil.ReadFile(font)
if err != nil {
panic(err)
}
f, err := freetype.ParseFont(b)
if err != nil {
panic(err)
}
monofont.SetFont(f)
monofont.SetSrc(image.Black)
monofont.SetHinting(freetype.FullHinting)
fps.rgba = newRGBA(geom.Point{50, 12})
monofont.SetDst(fps.Image)
monofont.SetClip(fps.Image.Bounds())
monofont.SetDPI(72 * float64(geom.Scale))
monofont.SetFontSize(12)
log.Printf("fps.Image bounds: %s", fps.Image.Bounds())
}
// DrawFPS draws the per second framerate in the bottom-left of the screen.
func DrawFPS() {
fps.Do(fpsInit)
now := time.Now()
diff := now.Sub(lastDraw)
str := fmt.Sprintf("%.0f FPS", float32(time.Second)/float32(diff))
draw.Draw(fps.Image, fps.Image.Rect, image.White, image.Point{}, draw.Src)
ftpt12 := freetype.Pt(0, int(12*geom.Scale))
if _, err := monofont.DrawString(str, ftpt12); err != nil {
log.Printf("DrawFPS: %v", err)
return
}
fps.Upload()
fps.Draw(geom.Point{0, geom.Height - 12})
lastDraw = now
}

184
app/debug/glimage.go Normal file
View File

@ -0,0 +1,184 @@
// 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.
// TODO(crawshaw): A GL texture backed by an *image.RGBA is a common
// concept we will want to export. Find a generally useful interface.
// Think about the tricky things: mipmaps, a varying Rect, just backed
// by texture, not 2D drawer, etc. Maybe skip all of these issues and
// just solve the smaller problem: an unscaled sprite.
package debug
import (
"bytes"
"encoding/binary"
"image"
"log"
"runtime"
"sync"
"code.google.com/p/go.mobile/f32"
"code.google.com/p/go.mobile/geom"
"code.google.com/p/go.mobile/gl"
"code.google.com/p/go.mobile/gl/glutil"
)
var glimage struct {
sync.Once
square gl.Buffer
squareUV gl.Buffer
program gl.Program
pos gl.Attrib
mvp gl.Uniform
inUV gl.Attrib
textureSample gl.Uniform
}
func glInit() {
var err error
glimage.program, err = glutil.CreateProgram(vertexShader, fragmentShader)
if err != nil {
panic(err)
}
glimage.square = gl.GenBuffer()
glimage.squareUV = gl.GenBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.square)
gl.BufferData(gl.ARRAY_BUFFER, gl.STATIC_DRAW, squareCoords)
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.squareUV)
gl.BufferData(gl.ARRAY_BUFFER, gl.STATIC_DRAW, squareUVCoords)
glimage.pos = gl.GetAttribLocation(glimage.program, "pos")
glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp")
glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV")
glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample")
}
type rgba struct {
Image *image.RGBA
Texture gl.Texture
sizeX, sizeY float32
}
func roundToPower2(x int) int {
x2 := 1
for x2 < x {
x2 *= 2
}
return x2
}
func newRGBA(size geom.Point) *rgba {
dx := roundToPower2(int(size.X.Px()))
dy := roundToPower2(int(size.Y.Px()))
imgRGBA := image.NewRGBA(image.Rect(0, 0, dx, dy))
glimage.Do(glInit)
w, h := imgRGBA.Rect.Dx(), imgRGBA.Rect.Dy()
img := &rgba{
Image: imgRGBA, // TODO: embed?
Texture: gl.GenTexture(),
}
runtime.SetFinalizer(img, func(img *rgba) {
gl.DeleteTexture(img.Texture)
})
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
gl.TexImage2D(gl.TEXTURE_2D, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, nil)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
img.sizeX = float32(size.X / geom.Width)
img.sizeY = float32(size.Y / geom.Height)
return img
}
func (img *rgba) Upload() {
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
w, h := img.Image.Rect.Dx(), img.Image.Rect.Dy()
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, img.Image.Pix)
}
// TODO(crawshaw): Clip/Scale options. Introduce geom.Rectangle?
// TODO(crawshaw): Adjust viewport for the top bar on android?
func (img *rgba) Draw(at geom.Point) {
gl.UseProgram(glimage.program)
// Screen plane
// (-1, 1) ( 1, 1)
// (-1,-1) ( 1,-1)
// Unscaled image corners
// (1,0) (1,1)
// (0,0) (0,1)
// Orig size.X / geom.Width is a [0,1] fraction of screen.
// The at position is referenced from the top of the image.
var m f32.Mat4
m.Identity()
m.Translate(m, &f32.Vec3{
-1 + float32(2*at.X/geom.Width),
+1 - 2*img.sizeY - float32(2*at.Y/geom.Height),
0,
})
m.Scale(&m, &f32.Vec3{
2 * img.sizeX,
2 * img.sizeY,
1,
})
glimage.mvp.WriteMat4(&m)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.Uniform1i(glimage.textureSample, 0)
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.square)
gl.EnableVertexAttribArray(glimage.pos)
gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.squareUV)
gl.EnableVertexAttribArray(glimage.inUV)
gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)
gl.DrawArrays(gl.TRIANGLES, 0, 6)
gl.DisableVertexAttribArray(glimage.pos)
gl.DisableVertexAttribArray(glimage.inUV)
}
// Vertices of two triangles.
var squareCoords = toBytes([]float32{
0, 0,
1, 0,
1, 1,
0, 0,
1, 1,
0, 1,
})
var squareUVCoords = toBytes([]float32{
0, 1,
1, 1,
1, 0,
0, 1,
1, 0,
0, 0,
})
func toBytes(v []float32) []byte {
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.LittleEndian, v); err != nil {
log.Fatal(err)
}
return buf.Bytes()
}

27
app/debug/glimage_es.go Normal file
View File

@ -0,0 +1,27 @@
// 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 android
package debug
const vertexShader = `
uniform mat4 mvp;
attribute vec4 pos;
attribute vec2 inUV;
varying vec2 UV;
void main() {
gl_Position = mvp * pos;
UV = inUV;
}
`
const fragmentShader = `
precision mediump float;
varying vec2 UV;
uniform sampler2D textureSample;
void main(){
gl_FragColor = texture2D(textureSample, UV);
}
`