Skip to content

Commit 970a0a0

Browse files
committed
go.mobile/gl/glutil: let Image.Draw draw non-axis-aligned quads.
LGTM=crawshaw R=crawshaw CC=golang-codereviews https://golang.org/cl/160710043
1 parent 0ac70a3 commit 970a0a0

File tree

4 files changed

+136
-133
lines changed

4 files changed

+136
-133
lines changed

app/debug/fps.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ func DrawFPS() {
8383

8484
fps.Upload()
8585
fps.Draw(
86-
geom.Rectangle{geom.Point{0, geom.Height - 12}, geom.Point{50, geom.Height}},
86+
geom.Point{0, geom.Height - 12},
87+
geom.Point{50, geom.Height - 12},
88+
geom.Point{0, geom.Height},
8789
fps.Bounds(),
8890
)
8991

gl/glutil/glimage.go

+130-103
Original file line numberDiff line numberDiff line change
@@ -109,108 +109,112 @@ func (img *Image) Upload() {
109109
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.texWidth, img.texHeight, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
110110
}
111111

112-
// Draw draws the image onto the current GL framebuffer.
113-
func (img *Image) Draw(dstBounds geom.Rectangle, srcBounds image.Rectangle) {
112+
// Draw draws the srcBounds part of the image onto a parallelogram, defined by
113+
// three of its corners, in the current GL framebuffer.
114+
func (img *Image) Draw(topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {
114115
// TODO(crawshaw): Adjust viewport for the top bar on android?
115116
gl.UseProgram(glimage.program)
116117

117-
// We are drawing a sub-image of dst, defined by dstBounds. Let ABCD
118-
// be the image, and PQRS be the sub-image. The two images may actually
119-
// be equal, but in the general case, PQRS can be smaller:
120-
//
121-
// M
122-
// A +----+----------+ B
123-
// | |
124-
// N + P +-----+ Q |
125-
// | | | |
126-
// | S +-----+ R |
127-
// | |
128-
// D +---------------+ C
129-
//
130-
// There are two co-ordinate spaces: geom space and framebuffer space.
131-
// In geom space, the ABCD rectangle is:
132-
//
133-
// (0, 0) (geom.Width, 0)
134-
// (0, geom.Height) (geom.Width, geom.Height)
135-
//
136-
// and the PQRS rectangle is:
137-
//
138-
// (dstBounds.Min.X, dstBounds.Min.Y) (dstBounds.Max.X, dstBounds.Min.Y)
139-
// (dstBounds.Min.X, dstBounds.Max.Y) (dstBounds.Max.X, dstBounds.Max.Y)
140-
//
141-
// In framebuffer space, the ABCD rectangle is:
142-
//
143-
// (-1, +1) (+1, +1)
144-
// (-1, -1) (+1, -1)
145-
//
146-
// We need to solve for PQRS' co-ordinates in framebuffer space, and
147-
// calculate the MVP matrix that transforms the -1/+1 ABCD co-ordinates
148-
// to PQRS co-ordinates.
149-
//
150-
// To solve for PQRS, note that PQ / AB must match in both spaces. Call
151-
// this ratio fracX, and likewise for fracY.
152-
//
153-
// [EQ1] fracX = (dstBounds.Max.X - dstBounds.Min.X) / geom.Width
154-
//
155-
// Similarly, the AM / AB ratio must match:
156-
//
157-
// (P.x - -1) / (1 - -1) = dstBounds.Min.X / geom.Width
158-
//
159-
// where the LHS is in framebuffer space and the RHS in geom space. This
160-
// equation is equivalent to:
161-
//
162-
// [EQ2] P.x = -1 + 2 * dstBounds.Min.X / geom.Width
163-
//
164-
// This MVP matrix is a scale followed by a translate. The scale is by
165-
// (fracX, fracY). After this, our corners have been transformed to:
166-
//
167-
// (-fracX, +fracY) (+fracX, +fracY)
168-
// (-fracX, -fracY) (+fracX, -fracY)
169-
//
170-
// so the translate is by (P.x + fracX) in the X direction, and
171-
// likewise for Y. Combining equations EQ1 and EQ2 simplifies the
172-
// translate to be:
173-
//
174-
// -1 + (dstBounds.Max.X + dstBounds.Min.X) / geom.Width
175-
// +1 - (dstBounds.Max.Y + dstBounds.Min.Y) / geom.Height
176-
var a f32.Affine
177-
a.Identity()
178-
a.Translate(
179-
&a,
180-
-1+float32((dstBounds.Max.X+dstBounds.Min.X)/geom.Width),
181-
+1-float32((dstBounds.Max.Y+dstBounds.Min.Y)/geom.Height),
182-
)
183-
a.Scale(
184-
&a,
185-
float32((dstBounds.Max.X-dstBounds.Min.X)/geom.Width),
186-
float32((dstBounds.Max.Y-dstBounds.Min.Y)/geom.Height),
187-
)
188-
glimage.mvp.WriteAffine(&a)
189-
190-
// Texture UV co-ordinates start out as:
191-
//
192-
// (0,0) (1,0)
193-
// (0,1) (1,1)
194-
//
195-
// These co-ordinates need to be scaled to texWidth/Height,
196-
// which may be less than 1 as the source image may not have
197-
// power-of-2 dimensions. Then it is scaled and translated
198-
// to represent the srcBounds rectangle of the source texture.
199-
//
200-
// The math is simpler here because in both co-ordinate spaces,
201-
// the top-left corner is (0, 0).
202-
a.Identity()
203-
a.Translate(
204-
&a,
205-
float32(srcBounds.Min.X)/float32(img.texWidth),
206-
float32(srcBounds.Min.Y)/float32(img.texHeight),
207-
)
208-
a.Scale(
209-
&a,
210-
float32(srcBounds.Dx())/float32(img.texWidth),
211-
float32(srcBounds.Dy())/float32(img.texHeight),
212-
)
213-
glimage.uvp.WriteAffine(&a)
118+
{
119+
// We are drawing a parallelogram PQRS, defined by three of its
120+
// corners, onto the entire GL framebuffer ABCD. The two quads may
121+
// actually be equal, but in the general case, PQRS can be smaller,
122+
// and PQRS is not necessarily axis-aligned.
123+
//
124+
// A +---------------+ B
125+
// | P +-----+ Q |
126+
// | | | |
127+
// | S +-----+ R |
128+
// D +---------------+ C
129+
//
130+
// There are two co-ordinate spaces: geom space and framebuffer space.
131+
// In geom space, the ABCD rectangle is:
132+
//
133+
// (0, 0) (geom.Width, 0)
134+
// (0, geom.Height) (geom.Width, geom.Height)
135+
//
136+
// and the PQRS quad is:
137+
//
138+
// (topLeft.X, topLeft.Y) (topRight.X, topRight.Y)
139+
// (bottomLeft.X, bottomLeft.Y) (implicit, implicit)
140+
//
141+
// In framebuffer space, the ABCD rectangle is:
142+
//
143+
// (-1, +1) (+1, +1)
144+
// (-1, -1) (+1, -1)
145+
//
146+
// First of all, convert from geom space to framebuffer space. For
147+
// later convenience, we divide everything by 2 here: px2 is half of
148+
// the P.X co-ordinate (in framebuffer space).
149+
px2 := -0.5 + float32(topLeft.X/geom.Width)
150+
py2 := +0.5 - float32(topLeft.Y/geom.Height)
151+
qx2 := -0.5 + float32(topRight.X/geom.Width)
152+
qy2 := +0.5 - float32(topRight.Y/geom.Height)
153+
sx2 := -0.5 + float32(bottomLeft.X/geom.Width)
154+
sy2 := +0.5 - float32(bottomLeft.Y/geom.Height)
155+
// Next, solve for the affine transformation matrix
156+
// [ a00 a01 a02 ]
157+
// a = [ a10 a11 a12 ]
158+
// [ 0 0 1 ]
159+
// that maps A to P:
160+
// a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
161+
// and likewise maps B to Q and D to S. Solving those three constraints
162+
// implies that C maps to R, since affine transformations keep parallel
163+
// lines parallel. This gives 6 equations in 6 unknowns:
164+
// -a00 + a01 + a02 = 2*px2
165+
// -a10 + a11 + a12 = 2*py2
166+
// +a00 + a01 + a02 = 2*qx2
167+
// +a10 + a11 + a12 = 2*qy2
168+
// -a00 - a01 + a02 = 2*sx2
169+
// -a10 - a11 + a12 = 2*sy2
170+
// which gives:
171+
// a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
172+
// and similarly for the other elements of a.
173+
glimage.mvp.WriteAffine(&f32.Affine{{
174+
qx2 - px2,
175+
px2 - sx2,
176+
qx2 + sx2,
177+
}, {
178+
qy2 - py2,
179+
py2 - sy2,
180+
qy2 + sy2,
181+
}})
182+
}
183+
184+
{
185+
// Mapping texture co-ordinates is similar, except that in texture
186+
// space, the ABCD rectangle is:
187+
//
188+
// (0,0) (1,0)
189+
// (0,1) (1,1)
190+
//
191+
// and the PQRS quad is always axis-aligned. First of all, convert
192+
// from pixel space to texture space.
193+
w := float32(img.texWidth)
194+
h := float32(img.texHeight)
195+
px := float32(srcBounds.Min.X-img.Rect.Min.X) / w
196+
py := float32(srcBounds.Min.Y-img.Rect.Min.Y) / h
197+
qx := float32(srcBounds.Max.X-img.Rect.Min.X) / w
198+
sy := float32(srcBounds.Max.Y-img.Rect.Min.Y) / h
199+
// Due to axis alignment, qy = py and sx = px.
200+
//
201+
// The simultaneous equations are:
202+
// 0 + 0 + a02 = px
203+
// 0 + 0 + a12 = py
204+
// a00 + 0 + a02 = qx
205+
// a10 + 0 + a12 = qy = py
206+
// 0 + a01 + a02 = sx = px
207+
// 0 + a11 + a12 = sy
208+
glimage.uvp.WriteAffine(&f32.Affine{{
209+
qx - px,
210+
0,
211+
px,
212+
}, {
213+
0,
214+
sy - py,
215+
py,
216+
}})
217+
}
214218

215219
gl.ActiveTexture(gl.TEXTURE0)
216220
gl.BindTexture(gl.TEXTURE_2D, img.Texture)
@@ -231,15 +235,38 @@ func (img *Image) Draw(dstBounds geom.Rectangle, srcBounds image.Rectangle) {
231235
}
232236

233237
var quadXYCoords = f32.Bytes(binary.LittleEndian,
234-
-1, -1, // bottom left
235-
+1, -1, // bottom right
236238
-1, +1, // top left
237239
+1, +1, // top right
240+
-1, -1, // bottom left
241+
+1, -1, // bottom right
238242
)
239243

240244
var quadUVCoords = f32.Bytes(binary.LittleEndian,
241-
0, 1, // bottom left
242-
1, 1, // bottom right
243245
0, 0, // top left
244246
1, 0, // top right
247+
0, 1, // bottom left
248+
1, 1, // bottom right
245249
)
250+
251+
const vertexShader = `#version 100
252+
uniform mat3 mvp;
253+
uniform mat3 uvp;
254+
attribute vec3 pos;
255+
attribute vec2 inUV;
256+
varying vec2 UV;
257+
void main() {
258+
vec3 p = pos;
259+
p.z = 1.0;
260+
gl_Position = vec4(mvp * p, 1);
261+
UV = (uvp * vec3(inUV, 1)).xy;
262+
}
263+
`
264+
265+
const fragmentShader = `#version 100
266+
precision mediump float;
267+
varying vec2 UV;
268+
uniform sampler2D textureSample;
269+
void main(){
270+
gl_FragColor = texture2D(textureSample, UV);
271+
}
272+
`

gl/glutil/glimage_es.go

-28
This file was deleted.

gl/glutil/glimage_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ func TestImage(t *testing.T) {
7575
b.Max.Y /= 2
7676

7777
ptTopLeft := geom.Point{3, 15}
78+
ptTopRight := geom.Point{48, 15}
79+
ptBottomLeft := geom.Point{3, 46}
7880
ptBottomRight := geom.Point{48, 46}
79-
m.Draw(geom.Rectangle{ptTopLeft, ptBottomRight}, b)
81+
m.Draw(ptTopLeft, ptTopRight, ptBottomLeft, b)
8082

8183
// For unknown reasons, a windowless OpenGL context on darwin
8284
// renders upside down. That is, a quad covering the initial

0 commit comments

Comments
 (0)