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  `