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  `