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  `