github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/mobile/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, 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  	// TODO(crawshaw): Adjust viewport for the top bar on android?
   173  	glctx.UseProgram(glimage.program)
   174  	{
   175  		// We are drawing a parallelogram PQRS, defined by three of its
   176  		// corners, onto the entire GL framebuffer ABCD. The two quads may
   177  		// actually be equal, but in the general case, PQRS can be smaller,
   178  		// and PQRS is not necessarily axis-aligned.
   179  		//
   180  		//	A +---------------+ B
   181  		//	  |  P +-----+ Q  |
   182  		//	  |    |     |    |
   183  		//	  |  S +-----+ R  |
   184  		//	D +---------------+ C
   185  		//
   186  		// There are two co-ordinate spaces: geom space and framebuffer space.
   187  		// In geom space, the ABCD rectangle is:
   188  		//
   189  		//	(0, 0)           (geom.Width, 0)
   190  		//	(0, geom.Height) (geom.Width, geom.Height)
   191  		//
   192  		// and the PQRS quad is:
   193  		//
   194  		//	(topLeft.X,    topLeft.Y)    (topRight.X, topRight.Y)
   195  		//	(bottomLeft.X, bottomLeft.Y) (implicit,   implicit)
   196  		//
   197  		// In framebuffer space, the ABCD rectangle is:
   198  		//
   199  		//	(-1, +1) (+1, +1)
   200  		//	(-1, -1) (+1, -1)
   201  		//
   202  		// First of all, convert from geom space to framebuffer space. For
   203  		// later convenience, we divide everything by 2 here: px2 is half of
   204  		// the P.X co-ordinate (in framebuffer space).
   205  		px2 := -0.5 + float32(topLeft.X/sz.WidthPt)
   206  		py2 := +0.5 - float32(topLeft.Y/sz.HeightPt)
   207  		qx2 := -0.5 + float32(topRight.X/sz.WidthPt)
   208  		qy2 := +0.5 - float32(topRight.Y/sz.HeightPt)
   209  		sx2 := -0.5 + float32(bottomLeft.X/sz.WidthPt)
   210  		sy2 := +0.5 - float32(bottomLeft.Y/sz.HeightPt)
   211  		// Next, solve for the affine transformation matrix
   212  		//	    [ a00 a01 a02 ]
   213  		//	a = [ a10 a11 a12 ]
   214  		//	    [   0   0   1 ]
   215  		// that maps A to P:
   216  		//	a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
   217  		// and likewise maps B to Q and D to S. Solving those three constraints
   218  		// implies that C maps to R, since affine transformations keep parallel
   219  		// lines parallel. This gives 6 equations in 6 unknowns:
   220  		//	-a00 + a01 + a02 = 2*px2
   221  		//	-a10 + a11 + a12 = 2*py2
   222  		//	+a00 + a01 + a02 = 2*qx2
   223  		//	+a10 + a11 + a12 = 2*qy2
   224  		//	-a00 - a01 + a02 = 2*sx2
   225  		//	-a10 - a11 + a12 = 2*sy2
   226  		// which gives:
   227  		//	a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
   228  		// and similarly for the other elements of a.
   229  		writeAffine(glctx, glimage.mvp, &f32.Affine{{
   230  			qx2 - px2,
   231  			px2 - sx2,
   232  			qx2 + sx2,
   233  		}, {
   234  			qy2 - py2,
   235  			py2 - sy2,
   236  			qy2 + sy2,
   237  		}})
   238  	}
   239  
   240  	{
   241  		// Mapping texture co-ordinates is similar, except that in texture
   242  		// space, the ABCD rectangle is:
   243  		//
   244  		//	(0,0) (1,0)
   245  		//	(0,1) (1,1)
   246  		//
   247  		// and the PQRS quad is always axis-aligned. First of all, convert
   248  		// from pixel space to texture space.
   249  		w := float32(img.width)
   250  		h := float32(img.height)
   251  		px := float32(srcBounds.Min.X-img.RGBA.Rect.Min.X) / w
   252  		py := float32(srcBounds.Min.Y-img.RGBA.Rect.Min.Y) / h
   253  		qx := float32(srcBounds.Max.X-img.RGBA.Rect.Min.X) / w
   254  		sy := float32(srcBounds.Max.Y-img.RGBA.Rect.Min.Y) / h
   255  		// Due to axis alignment, qy = py and sx = px.
   256  		//
   257  		// The simultaneous equations are:
   258  		//	  0 +   0 + a02 = px
   259  		//	  0 +   0 + a12 = py
   260  		//	a00 +   0 + a02 = qx
   261  		//	a10 +   0 + a12 = qy = py
   262  		//	  0 + a01 + a02 = sx = px
   263  		//	  0 + a11 + a12 = sy
   264  		writeAffine(glctx, glimage.uvp, &f32.Affine{{
   265  			qx - px,
   266  			0,
   267  			px,
   268  		}, {
   269  			0,
   270  			sy - py,
   271  			py,
   272  		}})
   273  	}
   274  
   275  	glctx.ActiveTexture(gl.TEXTURE0)
   276  	glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
   277  	glctx.Uniform1i(glimage.textureSample, 0)
   278  
   279  	glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
   280  	glctx.EnableVertexAttribArray(glimage.pos)
   281  	glctx.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)
   282  
   283  	glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
   284  	glctx.EnableVertexAttribArray(glimage.inUV)
   285  	glctx.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)
   286  
   287  	glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
   288  
   289  	glctx.DisableVertexAttribArray(glimage.pos)
   290  	glctx.DisableVertexAttribArray(glimage.inUV)
   291  }
   292  
   293  var quadXYCoords = f32.Bytes(binary.LittleEndian,
   294  	-1, +1, // top left
   295  	+1, +1, // top right
   296  	-1, -1, // bottom left
   297  	+1, -1, // bottom right
   298  )
   299  
   300  var quadUVCoords = f32.Bytes(binary.LittleEndian,
   301  	0, 0, // top left
   302  	1, 0, // top right
   303  	0, 1, // bottom left
   304  	1, 1, // bottom right
   305  )
   306  
   307  const vertexShader = `#version 100
   308  uniform mat3 mvp;
   309  uniform mat3 uvp;
   310  attribute vec3 pos;
   311  attribute vec2 inUV;
   312  varying vec2 UV;
   313  void main() {
   314  	vec3 p = pos;
   315  	p.z = 1.0;
   316  	gl_Position = vec4(mvp * p, 1);
   317  	UV = (uvp * vec3(inUV, 1)).xy;
   318  }
   319  `
   320  
   321  const fragmentShader = `#version 100
   322  precision mediump float;
   323  varying vec2 UV;
   324  uniform sampler2D textureSample;
   325  void main(){
   326  	gl_FragColor = texture2D(textureSample, UV);
   327  }
   328  `