github.com/acrespo/mobile@v0.0.0-20190107162257-dc0771356504/exp/gl/glutil/glimage.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build linux darwin windows 6 7 package glutil 8 9 import ( 10 "encoding/binary" 11 "image" 12 "runtime" 13 "sync" 14 15 "golang.org/x/mobile/event/size" 16 "golang.org/x/mobile/exp/f32" 17 "golang.org/x/mobile/geom" 18 "golang.org/x/mobile/gl" 19 ) 20 21 // Images maintains the shared state used by a set of *Image objects. 22 type Images struct { 23 glctx gl.Context 24 quadXY gl.Buffer 25 quadUV gl.Buffer 26 program gl.Program 27 pos gl.Attrib 28 mvp gl.Uniform 29 uvp gl.Uniform 30 inUV gl.Attrib 31 textureSample gl.Uniform 32 33 mu sync.Mutex 34 activeImages int 35 } 36 37 // NewImages creates an *Images. 38 func NewImages(glctx gl.Context) *Images { 39 program, err := CreateProgram(glctx, vertexShader, fragmentShader) 40 if err != nil { 41 panic(err) 42 } 43 44 p := &Images{ 45 glctx: glctx, 46 quadXY: glctx.CreateBuffer(), 47 quadUV: glctx.CreateBuffer(), 48 program: program, 49 pos: glctx.GetAttribLocation(program, "pos"), 50 mvp: glctx.GetUniformLocation(program, "mvp"), 51 uvp: glctx.GetUniformLocation(program, "uvp"), 52 inUV: glctx.GetAttribLocation(program, "inUV"), 53 textureSample: glctx.GetUniformLocation(program, "textureSample"), 54 } 55 56 glctx.BindBuffer(gl.ARRAY_BUFFER, p.quadXY) 57 glctx.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW) 58 glctx.BindBuffer(gl.ARRAY_BUFFER, p.quadUV) 59 glctx.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW) 60 61 return p 62 } 63 64 // Release releases any held OpenGL resources. 65 // All *Image objects must be released first, or this function panics. 66 func (p *Images) Release() { 67 if p.program == (gl.Program{}) { 68 return 69 } 70 71 p.mu.Lock() 72 rem := p.activeImages 73 p.mu.Unlock() 74 if rem > 0 { 75 panic("glutil.Images.Release called, but active *Image objects remain") 76 } 77 78 p.glctx.DeleteProgram(p.program) 79 p.glctx.DeleteBuffer(p.quadXY) 80 p.glctx.DeleteBuffer(p.quadUV) 81 82 p.program = gl.Program{} 83 } 84 85 // Image bridges between an *image.RGBA and an OpenGL texture. 86 // 87 // The contents of the *image.RGBA can be uploaded as a texture and drawn as a 88 // 2D quad. 89 // 90 // The number of active Images must fit in the system's OpenGL texture limit. 91 // The typical use of an Image is as a texture atlas. 92 type Image struct { 93 RGBA *image.RGBA 94 95 gltex gl.Texture 96 width int 97 height int 98 images *Images 99 } 100 101 // NewImage creates an Image of the given size. 102 // 103 // Both a host-memory *image.RGBA and a GL texture are created. 104 func (p *Images) NewImage(w, h int) *Image { 105 dx := roundToPower2(w) 106 dy := roundToPower2(h) 107 108 // TODO(crawshaw): Using VertexAttribPointer we can pass texture 109 // data with a stride, which would let us use the exact number of 110 // pixels on the host instead of the rounded up power 2 size. 111 m := image.NewRGBA(image.Rect(0, 0, dx, dy)) 112 113 img := &Image{ 114 RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA), 115 images: p, 116 width: dx, 117 height: dy, 118 } 119 120 p.mu.Lock() 121 p.activeImages++ 122 p.mu.Unlock() 123 124 img.gltex = p.glctx.CreateTexture() 125 126 p.glctx.BindTexture(gl.TEXTURE_2D, img.gltex) 127 p.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, nil) 128 p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 129 p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 130 p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 131 p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 132 133 runtime.SetFinalizer(img, (*Image).Release) 134 return img 135 } 136 137 func roundToPower2(x int) int { 138 x2 := 1 139 for x2 < x { 140 x2 *= 2 141 } 142 return x2 143 } 144 145 // Upload copies the host image data to the GL device. 146 func (img *Image) Upload() { 147 img.images.glctx.BindTexture(gl.TEXTURE_2D, img.gltex) 148 img.images.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix) 149 } 150 151 // Release invalidates the Image and removes any underlying data structures. 152 // The Image cannot be used after being deleted. 153 func (img *Image) Release() { 154 if img.gltex == (gl.Texture{}) { 155 return 156 } 157 158 img.images.glctx.DeleteTexture(img.gltex) 159 img.gltex = gl.Texture{} 160 161 img.images.mu.Lock() 162 img.images.activeImages-- 163 img.images.mu.Unlock() 164 } 165 166 // Draw draws the srcBounds part of the image onto a parallelogram, defined by 167 // three of its corners, in the current GL framebuffer. 168 func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) { 169 glimage := img.images 170 glctx := img.images.glctx 171 172 glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) 173 glctx.Enable(gl.BLEND) 174 175 // TODO(crawshaw): Adjust viewport for the top bar on android? 176 glctx.UseProgram(glimage.program) 177 { 178 // We are drawing a parallelogram PQRS, defined by three of its 179 // corners, onto the entire GL framebuffer ABCD. The two quads may 180 // actually be equal, but in the general case, PQRS can be smaller, 181 // and PQRS is not necessarily axis-aligned. 182 // 183 // A +---------------+ B 184 // | P +-----+ Q | 185 // | | | | 186 // | S +-----+ R | 187 // D +---------------+ C 188 // 189 // There are two co-ordinate spaces: geom space and framebuffer space. 190 // In geom space, the ABCD rectangle is: 191 // 192 // (0, 0) (geom.Width, 0) 193 // (0, geom.Height) (geom.Width, geom.Height) 194 // 195 // and the PQRS quad is: 196 // 197 // (topLeft.X, topLeft.Y) (topRight.X, topRight.Y) 198 // (bottomLeft.X, bottomLeft.Y) (implicit, implicit) 199 // 200 // In framebuffer space, the ABCD rectangle is: 201 // 202 // (-1, +1) (+1, +1) 203 // (-1, -1) (+1, -1) 204 // 205 // First of all, convert from geom space to framebuffer space. For 206 // later convenience, we divide everything by 2 here: px2 is half of 207 // the P.X co-ordinate (in framebuffer space). 208 px2 := -0.5 + float32(topLeft.X/sz.WidthPt) 209 py2 := +0.5 - float32(topLeft.Y/sz.HeightPt) 210 qx2 := -0.5 + float32(topRight.X/sz.WidthPt) 211 qy2 := +0.5 - float32(topRight.Y/sz.HeightPt) 212 sx2 := -0.5 + float32(bottomLeft.X/sz.WidthPt) 213 sy2 := +0.5 - float32(bottomLeft.Y/sz.HeightPt) 214 // Next, solve for the affine transformation matrix 215 // [ a00 a01 a02 ] 216 // a = [ a10 a11 a12 ] 217 // [ 0 0 1 ] 218 // that maps A to P: 219 // a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]' 220 // and likewise maps B to Q and D to S. Solving those three constraints 221 // implies that C maps to R, since affine transformations keep parallel 222 // lines parallel. This gives 6 equations in 6 unknowns: 223 // -a00 + a01 + a02 = 2*px2 224 // -a10 + a11 + a12 = 2*py2 225 // +a00 + a01 + a02 = 2*qx2 226 // +a10 + a11 + a12 = 2*qy2 227 // -a00 - a01 + a02 = 2*sx2 228 // -a10 - a11 + a12 = 2*sy2 229 // which gives: 230 // a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2 231 // and similarly for the other elements of a. 232 writeAffine(glctx, glimage.mvp, &f32.Affine{{ 233 qx2 - px2, 234 px2 - sx2, 235 qx2 + sx2, 236 }, { 237 qy2 - py2, 238 py2 - sy2, 239 qy2 + sy2, 240 }}) 241 } 242 243 { 244 // Mapping texture co-ordinates is similar, except that in texture 245 // space, the ABCD rectangle is: 246 // 247 // (0,0) (1,0) 248 // (0,1) (1,1) 249 // 250 // and the PQRS quad is always axis-aligned. First of all, convert 251 // from pixel space to texture space. 252 w := float32(img.width) 253 h := float32(img.height) 254 px := float32(srcBounds.Min.X-img.RGBA.Rect.Min.X) / w 255 py := float32(srcBounds.Min.Y-img.RGBA.Rect.Min.Y) / h 256 qx := float32(srcBounds.Max.X-img.RGBA.Rect.Min.X) / w 257 sy := float32(srcBounds.Max.Y-img.RGBA.Rect.Min.Y) / h 258 // Due to axis alignment, qy = py and sx = px. 259 // 260 // The simultaneous equations are: 261 // 0 + 0 + a02 = px 262 // 0 + 0 + a12 = py 263 // a00 + 0 + a02 = qx 264 // a10 + 0 + a12 = qy = py 265 // 0 + a01 + a02 = sx = px 266 // 0 + a11 + a12 = sy 267 writeAffine(glctx, glimage.uvp, &f32.Affine{{ 268 qx - px, 269 0, 270 px, 271 }, { 272 0, 273 sy - py, 274 py, 275 }}) 276 } 277 278 glctx.ActiveTexture(gl.TEXTURE0) 279 glctx.BindTexture(gl.TEXTURE_2D, img.gltex) 280 glctx.Uniform1i(glimage.textureSample, 0) 281 282 glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY) 283 glctx.EnableVertexAttribArray(glimage.pos) 284 glctx.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0) 285 286 glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV) 287 glctx.EnableVertexAttribArray(glimage.inUV) 288 glctx.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0) 289 290 glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) 291 292 glctx.DisableVertexAttribArray(glimage.pos) 293 glctx.DisableVertexAttribArray(glimage.inUV) 294 295 glctx.Disable(gl.BLEND) 296 } 297 298 var quadXYCoords = f32.Bytes(binary.LittleEndian, 299 -1, +1, // top left 300 +1, +1, // top right 301 -1, -1, // bottom left 302 +1, -1, // bottom right 303 ) 304 305 var quadUVCoords = f32.Bytes(binary.LittleEndian, 306 0, 0, // top left 307 1, 0, // top right 308 0, 1, // bottom left 309 1, 1, // bottom right 310 ) 311 312 const vertexShader = `#version 100 313 uniform mat3 mvp; 314 uniform mat3 uvp; 315 attribute vec3 pos; 316 attribute vec2 inUV; 317 varying vec2 UV; 318 void main() { 319 vec3 p = pos; 320 p.z = 1.0; 321 gl_Position = vec4(mvp * p, 1); 322 UV = (uvp * vec3(inUV, 1)).xy; 323 } 324 ` 325 326 const fragmentShader = `#version 100 327 precision mediump float; 328 varying vec2 UV; 329 uniform sampler2D textureSample; 330 void main(){ 331 gl_FragColor = texture2D(textureSample, UV); 332 } 333 `