go.mobile/gl/glutil: let Image.Draw draw non-axis-aligned quads.
LGTM=crawshaw R=crawshaw CC=golang-codereviews https://golang.org/cl/160710043
This commit is contained in:
parent
0ac70a3835
commit
970a0a0162
|
@ -83,7 +83,9 @@ func DrawFPS() {
|
||||||
|
|
||||||
fps.Upload()
|
fps.Upload()
|
||||||
fps.Draw(
|
fps.Draw(
|
||||||
geom.Rectangle{geom.Point{0, geom.Height - 12}, geom.Point{50, geom.Height}},
|
geom.Point{0, geom.Height - 12},
|
||||||
|
geom.Point{50, geom.Height - 12},
|
||||||
|
geom.Point{0, geom.Height},
|
||||||
fps.Bounds(),
|
fps.Bounds(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -109,108 +109,112 @@ func (img *Image) Upload() {
|
||||||
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.texWidth, img.texHeight, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
|
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.texWidth, img.texHeight, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw draws the image onto the current GL framebuffer.
|
// Draw draws the srcBounds part of the image onto a parallelogram, defined by
|
||||||
func (img *Image) Draw(dstBounds geom.Rectangle, srcBounds image.Rectangle) {
|
// 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?
|
// TODO(crawshaw): Adjust viewport for the top bar on android?
|
||||||
gl.UseProgram(glimage.program)
|
gl.UseProgram(glimage.program)
|
||||||
|
|
||||||
// We are drawing a sub-image of dst, defined by dstBounds. Let ABCD
|
{
|
||||||
// be the image, and PQRS be the sub-image. The two images may actually
|
// We are drawing a parallelogram PQRS, defined by three of its
|
||||||
// be equal, but in the general case, PQRS can be smaller:
|
// corners, onto the entire GL framebuffer ABCD. The two quads may
|
||||||
//
|
// actually be equal, but in the general case, PQRS can be smaller,
|
||||||
// M
|
// and PQRS is not necessarily axis-aligned.
|
||||||
// A +----+----------+ B
|
//
|
||||||
// | |
|
// A +---------------+ B
|
||||||
// N + P +-----+ Q |
|
// | P +-----+ Q |
|
||||||
// | | | |
|
// | | | |
|
||||||
// | S +-----+ R |
|
// | S +-----+ R |
|
||||||
// | |
|
// D +---------------+ C
|
||||||
// D +---------------+ C
|
//
|
||||||
//
|
// There are two co-ordinate spaces: geom space and framebuffer space.
|
||||||
// There are two co-ordinate spaces: geom space and framebuffer space.
|
// In geom space, the ABCD rectangle is:
|
||||||
// In geom space, the ABCD rectangle is:
|
//
|
||||||
//
|
// (0, 0) (geom.Width, 0)
|
||||||
// (0, 0) (geom.Width, 0)
|
// (0, geom.Height) (geom.Width, geom.Height)
|
||||||
// (0, geom.Height) (geom.Width, geom.Height)
|
//
|
||||||
//
|
// and the PQRS quad is:
|
||||||
// and the PQRS rectangle is:
|
//
|
||||||
//
|
// (topLeft.X, topLeft.Y) (topRight.X, topRight.Y)
|
||||||
// (dstBounds.Min.X, dstBounds.Min.Y) (dstBounds.Max.X, dstBounds.Min.Y)
|
// (bottomLeft.X, bottomLeft.Y) (implicit, implicit)
|
||||||
// (dstBounds.Min.X, dstBounds.Max.Y) (dstBounds.Max.X, dstBounds.Max.Y)
|
//
|
||||||
//
|
// In framebuffer space, the ABCD rectangle is:
|
||||||
// In framebuffer space, the ABCD rectangle is:
|
//
|
||||||
//
|
// (-1, +1) (+1, +1)
|
||||||
// (-1, +1) (+1, +1)
|
// (-1, -1) (+1, -1)
|
||||||
// (-1, -1) (+1, -1)
|
//
|
||||||
//
|
// First of all, convert from geom space to framebuffer space. For
|
||||||
// We need to solve for PQRS' co-ordinates in framebuffer space, and
|
// later convenience, we divide everything by 2 here: px2 is half of
|
||||||
// calculate the MVP matrix that transforms the -1/+1 ABCD co-ordinates
|
// the P.X co-ordinate (in framebuffer space).
|
||||||
// to PQRS co-ordinates.
|
px2 := -0.5 + float32(topLeft.X/geom.Width)
|
||||||
//
|
py2 := +0.5 - float32(topLeft.Y/geom.Height)
|
||||||
// To solve for PQRS, note that PQ / AB must match in both spaces. Call
|
qx2 := -0.5 + float32(topRight.X/geom.Width)
|
||||||
// this ratio fracX, and likewise for fracY.
|
qy2 := +0.5 - float32(topRight.Y/geom.Height)
|
||||||
//
|
sx2 := -0.5 + float32(bottomLeft.X/geom.Width)
|
||||||
// [EQ1] fracX = (dstBounds.Max.X - dstBounds.Min.X) / geom.Width
|
sy2 := +0.5 - float32(bottomLeft.Y/geom.Height)
|
||||||
//
|
// Next, solve for the affine transformation matrix
|
||||||
// Similarly, the AM / AB ratio must match:
|
// [ a00 a01 a02 ]
|
||||||
//
|
// a = [ a10 a11 a12 ]
|
||||||
// (P.x - -1) / (1 - -1) = dstBounds.Min.X / geom.Width
|
// [ 0 0 1 ]
|
||||||
//
|
// that maps A to P:
|
||||||
// where the LHS is in framebuffer space and the RHS in geom space. This
|
// a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
|
||||||
// equation is equivalent to:
|
// 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
|
||||||
// [EQ2] P.x = -1 + 2 * dstBounds.Min.X / geom.Width
|
// lines parallel. This gives 6 equations in 6 unknowns:
|
||||||
//
|
// -a00 + a01 + a02 = 2*px2
|
||||||
// This MVP matrix is a scale followed by a translate. The scale is by
|
// -a10 + a11 + a12 = 2*py2
|
||||||
// (fracX, fracY). After this, our corners have been transformed to:
|
// +a00 + a01 + a02 = 2*qx2
|
||||||
//
|
// +a10 + a11 + a12 = 2*qy2
|
||||||
// (-fracX, +fracY) (+fracX, +fracY)
|
// -a00 - a01 + a02 = 2*sx2
|
||||||
// (-fracX, -fracY) (+fracX, -fracY)
|
// -a10 - a11 + a12 = 2*sy2
|
||||||
//
|
// which gives:
|
||||||
// so the translate is by (P.x + fracX) in the X direction, and
|
// a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
|
||||||
// likewise for Y. Combining equations EQ1 and EQ2 simplifies the
|
// and similarly for the other elements of a.
|
||||||
// translate to be:
|
glimage.mvp.WriteAffine(&f32.Affine{{
|
||||||
//
|
qx2 - px2,
|
||||||
// -1 + (dstBounds.Max.X + dstBounds.Min.X) / geom.Width
|
px2 - sx2,
|
||||||
// +1 - (dstBounds.Max.Y + dstBounds.Min.Y) / geom.Height
|
qx2 + sx2,
|
||||||
var a f32.Affine
|
}, {
|
||||||
a.Identity()
|
qy2 - py2,
|
||||||
a.Translate(
|
py2 - sy2,
|
||||||
&a,
|
qy2 + sy2,
|
||||||
-1+float32((dstBounds.Max.X+dstBounds.Min.X)/geom.Width),
|
}})
|
||||||
+1-float32((dstBounds.Max.Y+dstBounds.Min.Y)/geom.Height),
|
}
|
||||||
)
|
|
||||||
a.Scale(
|
|
||||||
&a,
|
|
||||||
float32((dstBounds.Max.X-dstBounds.Min.X)/geom.Width),
|
|
||||||
float32((dstBounds.Max.Y-dstBounds.Min.Y)/geom.Height),
|
|
||||||
)
|
|
||||||
glimage.mvp.WriteAffine(&a)
|
|
||||||
|
|
||||||
// Texture UV co-ordinates start out as:
|
{
|
||||||
//
|
// Mapping texture co-ordinates is similar, except that in texture
|
||||||
// (0,0) (1,0)
|
// space, the ABCD rectangle is:
|
||||||
// (0,1) (1,1)
|
//
|
||||||
//
|
// (0,0) (1,0)
|
||||||
// These co-ordinates need to be scaled to texWidth/Height,
|
// (0,1) (1,1)
|
||||||
// which may be less than 1 as the source image may not have
|
//
|
||||||
// power-of-2 dimensions. Then it is scaled and translated
|
// and the PQRS quad is always axis-aligned. First of all, convert
|
||||||
// to represent the srcBounds rectangle of the source texture.
|
// from pixel space to texture space.
|
||||||
//
|
w := float32(img.texWidth)
|
||||||
// The math is simpler here because in both co-ordinate spaces,
|
h := float32(img.texHeight)
|
||||||
// the top-left corner is (0, 0).
|
px := float32(srcBounds.Min.X-img.Rect.Min.X) / w
|
||||||
a.Identity()
|
py := float32(srcBounds.Min.Y-img.Rect.Min.Y) / h
|
||||||
a.Translate(
|
qx := float32(srcBounds.Max.X-img.Rect.Min.X) / w
|
||||||
&a,
|
sy := float32(srcBounds.Max.Y-img.Rect.Min.Y) / h
|
||||||
float32(srcBounds.Min.X)/float32(img.texWidth),
|
// Due to axis alignment, qy = py and sx = px.
|
||||||
float32(srcBounds.Min.Y)/float32(img.texHeight),
|
//
|
||||||
)
|
// The simultaneous equations are:
|
||||||
a.Scale(
|
// 0 + 0 + a02 = px
|
||||||
&a,
|
// 0 + 0 + a12 = py
|
||||||
float32(srcBounds.Dx())/float32(img.texWidth),
|
// a00 + 0 + a02 = qx
|
||||||
float32(srcBounds.Dy())/float32(img.texHeight),
|
// a10 + 0 + a12 = qy = py
|
||||||
)
|
// 0 + a01 + a02 = sx = px
|
||||||
glimage.uvp.WriteAffine(&a)
|
// 0 + a11 + a12 = sy
|
||||||
|
glimage.uvp.WriteAffine(&f32.Affine{{
|
||||||
|
qx - px,
|
||||||
|
0,
|
||||||
|
px,
|
||||||
|
}, {
|
||||||
|
0,
|
||||||
|
sy - py,
|
||||||
|
py,
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
gl.ActiveTexture(gl.TEXTURE0)
|
gl.ActiveTexture(gl.TEXTURE0)
|
||||||
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
|
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
|
||||||
|
@ -231,15 +235,38 @@ func (img *Image) Draw(dstBounds geom.Rectangle, srcBounds image.Rectangle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var quadXYCoords = f32.Bytes(binary.LittleEndian,
|
var quadXYCoords = f32.Bytes(binary.LittleEndian,
|
||||||
-1, -1, // bottom left
|
|
||||||
+1, -1, // bottom right
|
|
||||||
-1, +1, // top left
|
-1, +1, // top left
|
||||||
+1, +1, // top right
|
+1, +1, // top right
|
||||||
|
-1, -1, // bottom left
|
||||||
|
+1, -1, // bottom right
|
||||||
)
|
)
|
||||||
|
|
||||||
var quadUVCoords = f32.Bytes(binary.LittleEndian,
|
var quadUVCoords = f32.Bytes(binary.LittleEndian,
|
||||||
0, 1, // bottom left
|
|
||||||
1, 1, // bottom right
|
|
||||||
0, 0, // top left
|
0, 0, // top left
|
||||||
1, 0, // top right
|
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);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
// 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 glutil
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -75,8 +75,10 @@ func TestImage(t *testing.T) {
|
||||||
b.Max.Y /= 2
|
b.Max.Y /= 2
|
||||||
|
|
||||||
ptTopLeft := geom.Point{3, 15}
|
ptTopLeft := geom.Point{3, 15}
|
||||||
|
ptTopRight := geom.Point{48, 15}
|
||||||
|
ptBottomLeft := geom.Point{3, 46}
|
||||||
ptBottomRight := geom.Point{48, 46}
|
ptBottomRight := geom.Point{48, 46}
|
||||||
m.Draw(geom.Rectangle{ptTopLeft, ptBottomRight}, b)
|
m.Draw(ptTopLeft, ptTopRight, ptBottomLeft, b)
|
||||||
|
|
||||||
// For unknown reasons, a windowless OpenGL context on darwin
|
// For unknown reasons, a windowless OpenGL context on darwin
|
||||||
// renders upside down. That is, a quad covering the initial
|
// renders upside down. That is, a quad covering the initial
|
||||||
|
|
Loading…
Reference in New Issue