github.com/c-darwin/mobile@v0.0.0-20160313183840-ff625c46f7c9/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 6 7 package glutil 8 9 import ( 10 "encoding/binary" 11 "fmt" 12 "image" 13 "runtime" 14 "sync" 15 16 "github.com/c-darwin/mobile/app" 17 "github.com/c-darwin/mobile/event/lifecycle" 18 "github.com/c-darwin/mobile/event/size" 19 "github.com/c-darwin/mobile/exp/f32" 20 "github.com/c-darwin/mobile/geom" 21 "github.com/c-darwin/mobile/gl" 22 ) 23 24 var glimage struct { 25 quadXY gl.Buffer 26 quadUV gl.Buffer 27 program gl.Program 28 pos gl.Attrib 29 mvp gl.Uniform 30 uvp gl.Uniform 31 inUV gl.Attrib 32 textureSample gl.Uniform 33 } 34 35 func init() { 36 app.RegisterFilter(func(e interface{}) interface{} { 37 if e, ok := e.(lifecycle.Event); ok { 38 switch e.Crosses(lifecycle.StageVisible) { 39 case lifecycle.CrossOn: 40 start() 41 case lifecycle.CrossOff: 42 stop() 43 } 44 } 45 return e 46 }) 47 } 48 49 func start() { 50 var err error 51 glimage.program, err = CreateProgram(vertexShader, fragmentShader) 52 if err != nil { 53 panic(err) 54 } 55 56 glimage.quadXY = gl.CreateBuffer() 57 glimage.quadUV = gl.CreateBuffer() 58 59 gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY) 60 gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW) 61 gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV) 62 gl.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW) 63 64 glimage.pos = gl.GetAttribLocation(glimage.program, "pos") 65 glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp") 66 glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp") 67 glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV") 68 glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample") 69 70 texmap.Lock() 71 defer texmap.Unlock() 72 for key, tex := range texmap.texs { 73 texmap.init(key) 74 tex.needsUpload = true 75 } 76 } 77 78 func stop() { 79 gl.DeleteProgram(glimage.program) 80 gl.DeleteBuffer(glimage.quadXY) 81 gl.DeleteBuffer(glimage.quadUV) 82 83 texmap.Lock() 84 for _, t := range texmap.texs { 85 if t.gltex.Value != 0 { 86 gl.DeleteTexture(t.gltex) 87 } 88 t.gltex = gl.Texture{} 89 } 90 texmap.Unlock() 91 } 92 93 type texture struct { 94 gltex gl.Texture 95 width int 96 height int 97 needsUpload bool 98 } 99 100 var texmap = &texmapCache{ 101 texs: make(map[texmapKey]*texture), 102 next: 1, // avoid using 0 to aid debugging 103 } 104 105 type texmapKey int 106 107 type texmapCache struct { 108 sync.Mutex 109 texs map[texmapKey]*texture 110 next texmapKey 111 112 // TODO(crawshaw): This is a workaround for having nowhere better to clean up deleted textures. 113 // Better: app.UI(func() { gl.DeleteTexture(t) } in texmap.delete 114 // Best: Redesign the gl package to do away with this painful notion of a UI thread. 115 toDelete []gl.Texture 116 } 117 118 func (tm *texmapCache) create(dx, dy int) *texmapKey { 119 tm.Lock() 120 defer tm.Unlock() 121 key := tm.next 122 tm.next++ 123 tm.texs[key] = &texture{ 124 width: dx, 125 height: dy, 126 } 127 tm.init(key) 128 return &key 129 } 130 131 // init creates an underlying GL texture for a key. 132 // Must be called with a valid GL context. 133 // Must hold tm.Mutex before calling. 134 func (tm *texmapCache) init(key texmapKey) { 135 tex := tm.texs[key] 136 if tex.gltex.Value != 0 { 137 panic(fmt.Sprintf("attempting to init key (%v) with valid texture", key)) 138 } 139 tex.gltex = gl.CreateTexture() 140 141 gl.BindTexture(gl.TEXTURE_2D, tex.gltex) 142 gl.TexImage2D(gl.TEXTURE_2D, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, nil) 143 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 144 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 145 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 146 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 147 148 for _, t := range tm.toDelete { 149 gl.DeleteTexture(t) 150 } 151 tm.toDelete = nil 152 } 153 154 func (tm *texmapCache) delete(key texmapKey) { 155 tm.Lock() 156 defer tm.Unlock() 157 tex := tm.texs[key] 158 delete(tm.texs, key) 159 if tex == nil { 160 return 161 } 162 tm.toDelete = append(tm.toDelete, tex.gltex) 163 } 164 165 func (tm *texmapCache) get(key texmapKey) *texture { 166 tm.Lock() 167 defer tm.Unlock() 168 return tm.texs[key] 169 } 170 171 // Image bridges between an *image.RGBA and an OpenGL texture. 172 // 173 // The contents of the *image.RGBA can be uploaded as a texture and drawn as a 174 // 2D quad. 175 // 176 // The number of active Images must fit in the system's OpenGL texture limit. 177 // The typical use of an Image is as a texture atlas. 178 type Image struct { 179 RGBA *image.RGBA 180 key *texmapKey 181 } 182 183 // NewImage creates an Image of the given size. 184 // 185 // Both a host-memory *image.RGBA and a GL texture are created. 186 func NewImage(w, h int) *Image { 187 dx := roundToPower2(w) 188 dy := roundToPower2(h) 189 190 // TODO(crawshaw): Using VertexAttribPointer we can pass texture 191 // data with a stride, which would let us use the exact number of 192 // pixels on the host instead of the rounded up power 2 size. 193 m := image.NewRGBA(image.Rect(0, 0, dx, dy)) 194 195 img := &Image{ 196 RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA), 197 key: texmap.create(dx, dy), 198 } 199 runtime.SetFinalizer(img.key, func(key *texmapKey) { 200 texmap.delete(*key) 201 }) 202 return img 203 } 204 205 func roundToPower2(x int) int { 206 x2 := 1 207 for x2 < x { 208 x2 *= 2 209 } 210 return x2 211 } 212 213 // Upload copies the host image data to the GL device. 214 func (img *Image) Upload() { 215 tex := texmap.get(*img.key) 216 gl.BindTexture(gl.TEXTURE_2D, tex.gltex) 217 gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, tex.width, tex.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix) 218 } 219 220 // Delete invalidates the Image and removes any underlying data structures. 221 // The Image cannot be used after being deleted. 222 func (img *Image) Delete() { 223 texmap.delete(*img.key) 224 } 225 226 // Draw draws the srcBounds part of the image onto a parallelogram, defined by 227 // three of its corners, in the current GL framebuffer. 228 func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) { 229 // TODO(crawshaw): Adjust viewport for the top bar on android? 230 gl.UseProgram(glimage.program) 231 tex := texmap.get(*img.key) 232 if tex.needsUpload { 233 img.Upload() 234 tex.needsUpload = false 235 } 236 237 { 238 // We are drawing a parallelogram PQRS, defined by three of its 239 // corners, onto the entire GL framebuffer ABCD. The two quads may 240 // actually be equal, but in the general case, PQRS can be smaller, 241 // and PQRS is not necessarily axis-aligned. 242 // 243 // A +---------------+ B 244 // | P +-----+ Q | 245 // | | | | 246 // | S +-----+ R | 247 // D +---------------+ C 248 // 249 // There are two co-ordinate spaces: geom space and framebuffer space. 250 // In geom space, the ABCD rectangle is: 251 // 252 // (0, 0) (geom.Width, 0) 253 // (0, geom.Height) (geom.Width, geom.Height) 254 // 255 // and the PQRS quad is: 256 // 257 // (topLeft.X, topLeft.Y) (topRight.X, topRight.Y) 258 // (bottomLeft.X, bottomLeft.Y) (implicit, implicit) 259 // 260 // In framebuffer space, the ABCD rectangle is: 261 // 262 // (-1, +1) (+1, +1) 263 // (-1, -1) (+1, -1) 264 // 265 // First of all, convert from geom space to framebuffer space. For 266 // later convenience, we divide everything by 2 here: px2 is half of 267 // the P.X co-ordinate (in framebuffer space). 268 px2 := -0.5 + float32(topLeft.X/sz.WidthPt) 269 py2 := +0.5 - float32(topLeft.Y/sz.HeightPt) 270 qx2 := -0.5 + float32(topRight.X/sz.WidthPt) 271 qy2 := +0.5 - float32(topRight.Y/sz.HeightPt) 272 sx2 := -0.5 + float32(bottomLeft.X/sz.WidthPt) 273 sy2 := +0.5 - float32(bottomLeft.Y/sz.HeightPt) 274 // Next, solve for the affine transformation matrix 275 // [ a00 a01 a02 ] 276 // a = [ a10 a11 a12 ] 277 // [ 0 0 1 ] 278 // that maps A to P: 279 // a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]' 280 // and likewise maps B to Q and D to S. Solving those three constraints 281 // implies that C maps to R, since affine transformations keep parallel 282 // lines parallel. This gives 6 equations in 6 unknowns: 283 // -a00 + a01 + a02 = 2*px2 284 // -a10 + a11 + a12 = 2*py2 285 // +a00 + a01 + a02 = 2*qx2 286 // +a10 + a11 + a12 = 2*qy2 287 // -a00 - a01 + a02 = 2*sx2 288 // -a10 - a11 + a12 = 2*sy2 289 // which gives: 290 // a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2 291 // and similarly for the other elements of a. 292 writeAffine(glimage.mvp, &f32.Affine{{ 293 qx2 - px2, 294 px2 - sx2, 295 qx2 + sx2, 296 }, { 297 qy2 - py2, 298 py2 - sy2, 299 qy2 + sy2, 300 }}) 301 } 302 303 { 304 // Mapping texture co-ordinates is similar, except that in texture 305 // space, the ABCD rectangle is: 306 // 307 // (0,0) (1,0) 308 // (0,1) (1,1) 309 // 310 // and the PQRS quad is always axis-aligned. First of all, convert 311 // from pixel space to texture space. 312 w := float32(tex.width) 313 h := float32(tex.height) 314 px := float32(srcBounds.Min.X-img.RGBA.Rect.Min.X) / w 315 py := float32(srcBounds.Min.Y-img.RGBA.Rect.Min.Y) / h 316 qx := float32(srcBounds.Max.X-img.RGBA.Rect.Min.X) / w 317 sy := float32(srcBounds.Max.Y-img.RGBA.Rect.Min.Y) / h 318 // Due to axis alignment, qy = py and sx = px. 319 // 320 // The simultaneous equations are: 321 // 0 + 0 + a02 = px 322 // 0 + 0 + a12 = py 323 // a00 + 0 + a02 = qx 324 // a10 + 0 + a12 = qy = py 325 // 0 + a01 + a02 = sx = px 326 // 0 + a11 + a12 = sy 327 writeAffine(glimage.uvp, &f32.Affine{{ 328 qx - px, 329 0, 330 px, 331 }, { 332 0, 333 sy - py, 334 py, 335 }}) 336 } 337 338 gl.ActiveTexture(gl.TEXTURE0) 339 gl.BindTexture(gl.TEXTURE_2D, tex.gltex) 340 gl.Uniform1i(glimage.textureSample, 0) 341 342 gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY) 343 gl.EnableVertexAttribArray(glimage.pos) 344 gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0) 345 346 gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV) 347 gl.EnableVertexAttribArray(glimage.inUV) 348 gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0) 349 350 gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) 351 352 gl.DisableVertexAttribArray(glimage.pos) 353 gl.DisableVertexAttribArray(glimage.inUV) 354 } 355 356 var quadXYCoords = f32.Bytes(binary.LittleEndian, 357 -1, +1, // top left 358 +1, +1, // top right 359 -1, -1, // bottom left 360 +1, -1, // bottom right 361 ) 362 363 var quadUVCoords = f32.Bytes(binary.LittleEndian, 364 0, 0, // top left 365 1, 0, // top right 366 0, 1, // bottom left 367 1, 1, // bottom right 368 ) 369 370 const vertexShader = `#version 100 371 uniform mat3 mvp; 372 uniform mat3 uvp; 373 attribute vec3 pos; 374 attribute vec2 inUV; 375 varying vec2 UV; 376 void main() { 377 vec3 p = pos; 378 p.z = 1.0; 379 gl_Position = vec4(mvp * p, 1); 380 UV = (uvp * vec3(inUV, 1)).xy; 381 } 382 ` 383 384 const fragmentShader = `#version 100 385 precision mediump float; 386 varying vec2 UV; 387 uniform sampler2D textureSample; 388 void main(){ 389 gl_FragColor = texture2D(textureSample, UV); 390 } 391 `