blob: 64c13f201fe9092ae389736ab20fc7968020acd4 [file] [log] [blame]
// 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 linux darwin
package glutil
import (
"encoding/binary"
"image"
"sync"
"golang.org/x/mobile/f32"
"golang.org/x/mobile/geom"
"golang.org/x/mobile/gl"
)
var glimage struct {
sync.Once
quadXY gl.Buffer
quadUV gl.Buffer
program gl.Program
pos gl.Attrib
mvp gl.Uniform
uvp gl.Uniform
inUV gl.Attrib
textureSample gl.Uniform
}
func glInit() {
var err error
glimage.program, err = CreateProgram(vertexShader, fragmentShader)
if err != nil {
panic(err)
}
glimage.quadXY = gl.GenBuffer()
glimage.quadUV = gl.GenBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
gl.BufferData(gl.ARRAY_BUFFER, gl.STATIC_DRAW, quadXYCoords)
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
gl.BufferData(gl.ARRAY_BUFFER, gl.STATIC_DRAW, quadUVCoords)
glimage.pos = gl.GetAttribLocation(glimage.program, "pos")
glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp")
glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp")
glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV")
glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample")
}
// Image bridges between an *image.RGBA and an OpenGL texture.
//
// The contents of the embedded *image.RGBA can be uploaded as a
// texture and drawn as a 2D quad.
//
// The number of active Images must fit in the system's OpenGL texture
// limit. The typical use of an Image is as a texture atlas.
type Image struct {
*image.RGBA
Texture gl.Texture
texWidth int
texHeight int
}
// NewImage creates an Image of the given size.
//
// Both a host-memory *image.RGBA and a GL texture are created.
func NewImage(w, h int) *Image {
dx := roundToPower2(w)
dy := roundToPower2(h)
// TODO(crawshaw): Using VertexAttribPointer we can pass texture
// data with a stride, which would let us use the exact number of
// pixels on the host instead of the rounded up power 2 size.
m := image.NewRGBA(image.Rect(0, 0, dx, dy))
glimage.Do(glInit)
img := &Image{
RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
Texture: gl.GenTexture(),
texWidth: dx,
texHeight: dy,
}
// TODO(crawshaw): We don't have the context on a finalizer. Find a way.
// runtime.SetFinalizer(img, func(img *Image) { gl.DeleteTexture(img.Texture) })
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
gl.TexImage2D(gl.TEXTURE_2D, 0, dx, dy, 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)
return img
}
func roundToPower2(x int) int {
x2 := 1
for x2 < x {
x2 *= 2
}
return x2
}
// Upload copies the host image data to the GL device.
func (img *Image) Upload() {
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.texWidth, img.texHeight, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
}
// Draw draws the srcBounds part of the image onto a parallelogram, defined by
// three of its corners, in the current GL framebuffer.
func (img *Image) Draw(topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {
// TODO(crawshaw): Adjust viewport for the top bar on android?
gl.UseProgram(glimage.program)
{
// We are drawing a parallelogram PQRS, defined by three of its
// corners, onto the entire GL framebuffer ABCD. The two quads may
// actually be equal, but in the general case, PQRS can be smaller,
// and PQRS is not necessarily axis-aligned.
//
// A +---------------+ B
// | P +-----+ Q |
// | | | |
// | S +-----+ R |
// D +---------------+ C
//
// There are two co-ordinate spaces: geom space and framebuffer space.
// In geom space, the ABCD rectangle is:
//
// (0, 0) (geom.Width, 0)
// (0, geom.Height) (geom.Width, geom.Height)
//
// and the PQRS quad is:
//
// (topLeft.X, topLeft.Y) (topRight.X, topRight.Y)
// (bottomLeft.X, bottomLeft.Y) (implicit, implicit)
//
// In framebuffer space, the ABCD rectangle is:
//
// (-1, +1) (+1, +1)
// (-1, -1) (+1, -1)
//
// First of all, convert from geom space to framebuffer space. For
// later convenience, we divide everything by 2 here: px2 is half of
// the P.X co-ordinate (in framebuffer space).
px2 := -0.5 + float32(topLeft.X/geom.Width)
py2 := +0.5 - float32(topLeft.Y/geom.Height)
qx2 := -0.5 + float32(topRight.X/geom.Width)
qy2 := +0.5 - float32(topRight.Y/geom.Height)
sx2 := -0.5 + float32(bottomLeft.X/geom.Width)
sy2 := +0.5 - float32(bottomLeft.Y/geom.Height)
// Next, solve for the affine transformation matrix
// [ a00 a01 a02 ]
// a = [ a10 a11 a12 ]
// [ 0 0 1 ]
// that maps A to P:
// a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
// and likewise maps B to Q and D to S. Solving those three constraints
// implies that C maps to R, since affine transformations keep parallel
// lines parallel. This gives 6 equations in 6 unknowns:
// -a00 + a01 + a02 = 2*px2
// -a10 + a11 + a12 = 2*py2
// +a00 + a01 + a02 = 2*qx2
// +a10 + a11 + a12 = 2*qy2
// -a00 - a01 + a02 = 2*sx2
// -a10 - a11 + a12 = 2*sy2
// which gives:
// a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
// and similarly for the other elements of a.
glimage.mvp.WriteAffine(&f32.Affine{{
qx2 - px2,
px2 - sx2,
qx2 + sx2,
}, {
qy2 - py2,
py2 - sy2,
qy2 + sy2,
}})
}
{
// Mapping texture co-ordinates is similar, except that in texture
// space, the ABCD rectangle is:
//
// (0,0) (1,0)
// (0,1) (1,1)
//
// and the PQRS quad is always axis-aligned. First of all, convert
// from pixel space to texture space.
w := float32(img.texWidth)
h := float32(img.texHeight)
px := float32(srcBounds.Min.X-img.Rect.Min.X) / w
py := float32(srcBounds.Min.Y-img.Rect.Min.Y) / h
qx := float32(srcBounds.Max.X-img.Rect.Min.X) / w
sy := float32(srcBounds.Max.Y-img.Rect.Min.Y) / h
// Due to axis alignment, qy = py and sx = px.
//
// The simultaneous equations are:
// 0 + 0 + a02 = px
// 0 + 0 + a12 = py
// a00 + 0 + a02 = qx
// a10 + 0 + a12 = qy = py
// 0 + a01 + a02 = sx = px
// 0 + a11 + a12 = sy
glimage.uvp.WriteAffine(&f32.Affine{{
qx - px,
0,
px,
}, {
0,
sy - py,
py,
}})
}
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
gl.Uniform1i(glimage.textureSample, 0)
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
gl.EnableVertexAttribArray(glimage.pos)
gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)
gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
gl.EnableVertexAttribArray(glimage.inUV)
gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
gl.DisableVertexAttribArray(glimage.pos)
gl.DisableVertexAttribArray(glimage.inUV)
}
var quadXYCoords = f32.Bytes(binary.LittleEndian,
-1, +1, // top left
+1, +1, // top right
-1, -1, // bottom left
+1, -1, // bottom right
)
var quadUVCoords = f32.Bytes(binary.LittleEndian,
0, 0, // top left
1, 0, // top right
0, 1, // bottom left
1, 1, // bottom right
)
const vertexShader = `#version 100
uniform mat3 mvp;
uniform mat3 uvp;
attribute vec3 pos;
attribute vec2 inUV;
varying vec2 UV;
void main() {
vec3 p = pos;
p.z = 1.0;
gl_Position = vec4(mvp * p, 1);
UV = (uvp * vec3(inUV, 1)).xy;
}
`
const fragmentShader = `#version 100
precision mediump float;
varying vec2 UV;
uniform sampler2D textureSample;
void main(){
gl_FragColor = texture2D(textureSample, UV);
}
`