LGTM=nigeltao R=nigeltao, bryanturley, crawshaw CC=adg, davidday, golang-codereviews https://golang.org/cl/136550043
185 lines
4.6 KiB
Go
185 lines
4.6 KiB
Go
// 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()
|
|
}
|