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