github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/app/internal/gpu/gpu.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package gpu
     4  
     5  import (
     6  	"encoding/binary"
     7  	"fmt"
     8  	"image"
     9  	"image/color"
    10  	"math"
    11  	"strings"
    12  	"time"
    13  	"unsafe"
    14  
    15  	"github.com/gop9/olt/gio/app/internal/gl"
    16  	"github.com/gop9/olt/gio/f32"
    17  	"github.com/gop9/olt/gio/internal/opconst"
    18  	"github.com/gop9/olt/gio/internal/ops"
    19  	"github.com/gop9/olt/gio/internal/path"
    20  	"github.com/gop9/olt/gio/op"
    21  	"github.com/gop9/olt/gio/op/paint"
    22  )
    23  
    24  type GPU struct {
    25  	pathCache *opCache
    26  	cache     *resourceCache
    27  
    28  	timers                                            *timers
    29  	frameStart                                        time.Time
    30  	zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer
    31  	drawOps                                           drawOps
    32  	ctx                                               *context
    33  	renderer                                          *renderer
    34  }
    35  
    36  type renderer struct {
    37  	ctx           *context
    38  	blitter       *blitter
    39  	pather        *pather
    40  	packer        packer
    41  	intersections packer
    42  }
    43  
    44  type drawOps struct {
    45  	reader     ops.Reader
    46  	cache      *resourceCache
    47  	viewport   image.Point
    48  	clearColor [3]float32
    49  	imageOps   []imageOp
    50  	// zimageOps are the rectangle clipped opaque images
    51  	// that can use fast front-to-back rendering with z-test
    52  	// and no blending.
    53  	zimageOps   []imageOp
    54  	pathOps     []*pathOp
    55  	pathOpCache []pathOp
    56  }
    57  
    58  type drawState struct {
    59  	clip  f32.Rectangle
    60  	t     op.TransformOp
    61  	cpath *pathOp
    62  	rect  bool
    63  	z     int
    64  
    65  	matType materialType
    66  	// Current paint.ImageOp
    67  	image imageOpData
    68  	// Current paint.ColorOp, if any.
    69  	color color.RGBA
    70  }
    71  
    72  type pathOp struct {
    73  	off f32.Point
    74  	// clip is the union of all
    75  	// later clip rectangles.
    76  	clip      image.Rectangle
    77  	pathKey   ops.Key
    78  	path      bool
    79  	pathVerts []byte
    80  	parent    *pathOp
    81  	place     placement
    82  }
    83  
    84  type imageOp struct {
    85  	z        float32
    86  	path     *pathOp
    87  	off      f32.Point
    88  	clip     image.Rectangle
    89  	material material
    90  	clipType clipType
    91  	place    placement
    92  }
    93  
    94  type material struct {
    95  	material materialType
    96  	opaque   bool
    97  	// For materialTypeColor.
    98  	color [4]float32
    99  	// For materialTypeTexture.
   100  	texture  *texture
   101  	uvScale  f32.Point
   102  	uvOffset f32.Point
   103  }
   104  
   105  // clipOp is the shadow of clip.Op.
   106  type clipOp struct {
   107  	bounds f32.Rectangle
   108  }
   109  
   110  // imageOpData is the shadow of paint.ImageOp.
   111  type imageOpData struct {
   112  	src    *image.RGBA
   113  	handle interface{}
   114  }
   115  
   116  func (op *clipOp) decode(data []byte) {
   117  	if opconst.OpType(data[0]) != opconst.TypeClip {
   118  		panic("invalid op")
   119  	}
   120  	bo := binary.LittleEndian
   121  	r := f32.Rectangle{
   122  		Min: f32.Point{
   123  			X: math.Float32frombits(bo.Uint32(data[1:])),
   124  			Y: math.Float32frombits(bo.Uint32(data[5:])),
   125  		},
   126  		Max: f32.Point{
   127  			X: math.Float32frombits(bo.Uint32(data[9:])),
   128  			Y: math.Float32frombits(bo.Uint32(data[13:])),
   129  		},
   130  	}
   131  	*op = clipOp{
   132  		bounds: r,
   133  	}
   134  }
   135  
   136  func decodeImageOp(data []byte, refs []interface{}) imageOpData {
   137  	if opconst.OpType(data[0]) != opconst.TypeImage {
   138  		panic("invalid op")
   139  	}
   140  	handle := refs[1]
   141  	if handle == nil {
   142  		panic("nil handle")
   143  	}
   144  	return imageOpData{
   145  		src:    refs[0].(*image.RGBA),
   146  		handle: handle,
   147  	}
   148  }
   149  
   150  func decodeColorOp(data []byte) color.RGBA {
   151  	if opconst.OpType(data[0]) != opconst.TypeColor {
   152  		panic("invalid op")
   153  	}
   154  	return color.RGBA{
   155  		R: data[1],
   156  		G: data[2],
   157  		B: data[3],
   158  		A: data[4],
   159  	}
   160  }
   161  
   162  func decodePaintOp(data []byte) paint.PaintOp {
   163  	bo := binary.LittleEndian
   164  	if opconst.OpType(data[0]) != opconst.TypePaint {
   165  		panic("invalid op")
   166  	}
   167  	r := f32.Rectangle{
   168  		Min: f32.Point{
   169  			X: math.Float32frombits(bo.Uint32(data[1:])),
   170  			Y: math.Float32frombits(bo.Uint32(data[5:])),
   171  		},
   172  		Max: f32.Point{
   173  			X: math.Float32frombits(bo.Uint32(data[9:])),
   174  			Y: math.Float32frombits(bo.Uint32(data[13:])),
   175  		},
   176  	}
   177  	return paint.PaintOp{
   178  		Rect: r,
   179  	}
   180  }
   181  
   182  type clipType uint8
   183  
   184  type resource interface {
   185  	release(ctx *context)
   186  }
   187  
   188  type texture struct {
   189  	src *image.RGBA
   190  	id  gl.Texture
   191  }
   192  
   193  type blitter struct {
   194  	ctx      *context
   195  	viewport image.Point
   196  	prog     [2]gl.Program
   197  	vars     [2]struct {
   198  		z                   gl.Uniform
   199  		uScale, uOffset     gl.Uniform
   200  		uUVScale, uUVOffset gl.Uniform
   201  		uColor              gl.Uniform
   202  	}
   203  	quadVerts gl.Buffer
   204  }
   205  
   206  type materialType uint8
   207  
   208  const (
   209  	clipTypeNone clipType = iota
   210  	clipTypePath
   211  	clipTypeIntersection
   212  )
   213  
   214  const (
   215  	materialColor materialType = iota
   216  	materialTexture
   217  )
   218  
   219  var (
   220  	blitAttribs           = []string{"pos", "uv"}
   221  	attribPos   gl.Attrib = 0
   222  	attribUV    gl.Attrib = 1
   223  )
   224  
   225  func New(ctx *gl.Functions) (*GPU, error) {
   226  	g := &GPU{
   227  		pathCache: newOpCache(),
   228  		cache:     newResourceCache(),
   229  	}
   230  	if err := g.init(ctx); err != nil {
   231  		return nil, err
   232  	}
   233  	return g, nil
   234  }
   235  
   236  func (g *GPU) init(glctx *gl.Functions) error {
   237  	ctx, err := newContext(glctx)
   238  	if err != nil {
   239  		return err
   240  	}
   241  	g.ctx = ctx
   242  	g.renderer = newRenderer(ctx)
   243  	return nil
   244  }
   245  
   246  func (g *GPU) Release() {
   247  	g.renderer.release()
   248  	g.pathCache.release(g.ctx)
   249  	g.cache.release(g.ctx)
   250  	if g.timers != nil {
   251  		g.timers.release()
   252  	}
   253  }
   254  
   255  func (g *GPU) Collect(profile bool, viewport image.Point, frameOps *op.Ops) {
   256  	g.drawOps.reset(g.cache, viewport)
   257  	g.drawOps.collect(g.cache, frameOps, viewport)
   258  	g.frameStart = time.Now()
   259  	if profile && g.timers == nil && g.ctx.caps.EXT_disjoint_timer_query {
   260  		g.timers = newTimers(g.ctx)
   261  		g.zopsTimer = g.timers.newTimer()
   262  		g.stencilTimer = g.timers.newTimer()
   263  		g.coverTimer = g.timers.newTimer()
   264  		g.cleanupTimer = g.timers.newTimer()
   265  	}
   266  	for _, p := range g.drawOps.pathOps {
   267  		if _, exists := g.pathCache.get(p.pathKey); !exists {
   268  			data := buildPath(g.ctx, p.pathVerts)
   269  			g.pathCache.put(p.pathKey, data)
   270  		}
   271  		p.pathVerts = nil
   272  	}
   273  }
   274  
   275  func (g *GPU) Frame(profile bool, viewport image.Point) {
   276  	g.renderer.blitter.viewport = viewport
   277  	g.renderer.pather.viewport = viewport
   278  	for _, img := range g.drawOps.imageOps {
   279  		expandPathOp(img.path, img.clip)
   280  	}
   281  	if profile {
   282  		g.zopsTimer.begin()
   283  	}
   284  	g.ctx.DepthFunc(gl.GREATER)
   285  	g.ctx.ClearColor(g.drawOps.clearColor[0], g.drawOps.clearColor[1], g.drawOps.clearColor[2], 1.0)
   286  	g.ctx.ClearDepthf(0.0)
   287  	g.ctx.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
   288  	g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
   289  	g.renderer.drawZOps(g.drawOps.zimageOps)
   290  	g.zopsTimer.end()
   291  	g.stencilTimer.begin()
   292  	g.ctx.Enable(gl.BLEND)
   293  	g.renderer.packStencils(&g.drawOps.pathOps)
   294  	g.renderer.stencilClips(g.pathCache, g.drawOps.pathOps)
   295  	g.renderer.packIntersections(g.drawOps.imageOps)
   296  	g.renderer.intersect(g.drawOps.imageOps)
   297  	g.stencilTimer.end()
   298  	g.coverTimer.begin()
   299  	g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
   300  	g.renderer.drawOps(g.drawOps.imageOps)
   301  	g.ctx.Disable(gl.BLEND)
   302  	g.renderer.pather.stenciler.invalidateFBO()
   303  	g.coverTimer.end()
   304  }
   305  
   306  func (g *GPU) EndFrame(profile bool) string {
   307  	g.cleanupTimer.begin()
   308  	g.cache.frame(g.ctx)
   309  	g.pathCache.frame(g.ctx)
   310  	g.cleanupTimer.end()
   311  	var summary string
   312  	if profile && g.timers.ready() {
   313  		zt, st, covt, cleant := g.zopsTimer.Elapsed, g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
   314  		ft := zt + st + covt + cleant
   315  		q := 100 * time.Microsecond
   316  		zt, st, covt = zt.Round(q), st.Round(q), covt.Round(q)
   317  		frameDur := time.Since(g.frameStart).Round(q)
   318  		ft = ft.Round(q)
   319  		summary = fmt.Sprintf("draw:%7s gpu:%7s zt:%7s st:%7s cov:%7s", frameDur, ft, zt, st, covt)
   320  	}
   321  	return summary
   322  }
   323  
   324  func (r *renderer) texHandle(t *texture) gl.Texture {
   325  	if t.id.Valid() {
   326  		return t.id
   327  	}
   328  	t.id = createTexture(r.ctx)
   329  	r.ctx.BindTexture(gl.TEXTURE_2D, t.id)
   330  	r.uploadTexture(t.src)
   331  	return t.id
   332  }
   333  
   334  func (t *texture) release(ctx *context) {
   335  	if t.id.Valid() {
   336  		ctx.DeleteTexture(t.id)
   337  	}
   338  }
   339  
   340  func newRenderer(ctx *context) *renderer {
   341  	r := &renderer{
   342  		ctx:     ctx,
   343  		blitter: newBlitter(ctx),
   344  		pather:  newPather(ctx),
   345  	}
   346  	r.packer.maxDim = ctx.GetInteger(gl.MAX_TEXTURE_SIZE)
   347  	r.intersections.maxDim = r.packer.maxDim
   348  	return r
   349  }
   350  
   351  func (r *renderer) release() {
   352  	r.pather.release()
   353  	r.blitter.release()
   354  }
   355  
   356  func newBlitter(ctx *context) *blitter {
   357  	prog, err := createColorPrograms(ctx, blitVSrc, blitFSrc)
   358  	if err != nil {
   359  		panic(err)
   360  	}
   361  	quadVerts := ctx.CreateBuffer()
   362  	ctx.BindBuffer(gl.ARRAY_BUFFER, quadVerts)
   363  	ctx.BufferData(gl.ARRAY_BUFFER,
   364  		gl.BytesView([]float32{
   365  			-1, +1, 0, 0,
   366  			+1, +1, 1, 0,
   367  			-1, -1, 0, 1,
   368  			+1, -1, 1, 1,
   369  		}),
   370  		gl.STATIC_DRAW)
   371  	b := &blitter{
   372  		ctx:       ctx,
   373  		prog:      prog,
   374  		quadVerts: quadVerts,
   375  	}
   376  	for i, prog := range prog {
   377  		ctx.UseProgram(prog)
   378  		switch materialType(i) {
   379  		case materialTexture:
   380  			uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex")
   381  			ctx.Uniform1i(uTex, 0)
   382  			b.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale")
   383  			b.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset")
   384  		case materialColor:
   385  			b.vars[i].uColor = gl.GetUniformLocation(ctx.Functions, prog, "color")
   386  		}
   387  		b.vars[i].z = gl.GetUniformLocation(ctx.Functions, prog, "z")
   388  		b.vars[i].uScale = gl.GetUniformLocation(ctx.Functions, prog, "scale")
   389  		b.vars[i].uOffset = gl.GetUniformLocation(ctx.Functions, prog, "offset")
   390  	}
   391  	return b
   392  }
   393  
   394  func (b *blitter) release() {
   395  	b.ctx.DeleteBuffer(b.quadVerts)
   396  	for _, p := range b.prog {
   397  		b.ctx.DeleteProgram(p)
   398  	}
   399  }
   400  
   401  func createColorPrograms(ctx *context, vsSrc, fsSrc string) ([2]gl.Program, error) {
   402  	var prog [2]gl.Program
   403  	frep := strings.NewReplacer(
   404  		"HEADER", `
   405  uniform sampler2D tex;
   406  `,
   407  		"GET_COLOR", `texture2D(tex, vUV)`,
   408  	)
   409  	fsSrcTex := frep.Replace(fsSrc)
   410  	var err error
   411  	prog[materialTexture], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcTex, blitAttribs)
   412  	if err != nil {
   413  		return prog, err
   414  	}
   415  	frep = strings.NewReplacer(
   416  		"HEADER", `
   417  uniform vec4 color;
   418  `,
   419  		"GET_COLOR", `color`,
   420  	)
   421  	fsSrcCol := frep.Replace(fsSrc)
   422  	prog[materialColor], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcCol, blitAttribs)
   423  	if err != nil {
   424  		ctx.DeleteProgram(prog[materialTexture])
   425  		return prog, err
   426  	}
   427  	return prog, nil
   428  }
   429  
   430  func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
   431  	if len(r.packer.sizes) == 0 {
   432  		return
   433  	}
   434  	fbo := -1
   435  	r.pather.begin(r.packer.sizes)
   436  	for _, p := range ops {
   437  		if fbo != p.place.Idx {
   438  			fbo = p.place.Idx
   439  			f := r.pather.stenciler.cover(fbo)
   440  			bindFramebuffer(r.ctx, f.fbo)
   441  			r.ctx.Clear(gl.COLOR_BUFFER_BIT)
   442  		}
   443  		data, _ := pathCache.get(p.pathKey)
   444  		r.pather.stencilPath(p.clip, p.off, p.place.Pos, data.(*pathData))
   445  	}
   446  	r.pather.end()
   447  }
   448  
   449  func (r *renderer) intersect(ops []imageOp) {
   450  	if len(r.intersections.sizes) == 0 {
   451  		return
   452  	}
   453  	fbo := -1
   454  	r.pather.stenciler.beginIntersect(r.intersections.sizes)
   455  	r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts)
   456  	r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0)
   457  	r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2)
   458  	r.ctx.EnableVertexAttribArray(attribPos)
   459  	r.ctx.EnableVertexAttribArray(attribUV)
   460  	for _, img := range ops {
   461  		if img.clipType != clipTypeIntersection {
   462  			continue
   463  		}
   464  		if fbo != img.place.Idx {
   465  			fbo = img.place.Idx
   466  			f := r.pather.stenciler.intersections.fbos[fbo]
   467  			bindFramebuffer(r.ctx, f.fbo)
   468  			r.ctx.Clear(gl.COLOR_BUFFER_BIT)
   469  		}
   470  		r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
   471  		r.intersectPath(img.path, img.clip)
   472  	}
   473  	r.ctx.DisableVertexAttribArray(attribPos)
   474  	r.ctx.DisableVertexAttribArray(attribUV)
   475  	r.pather.stenciler.endIntersect()
   476  }
   477  
   478  func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
   479  	if p.parent != nil {
   480  		r.intersectPath(p.parent, clip)
   481  	}
   482  	if !p.path {
   483  		return
   484  	}
   485  	o := p.place.Pos.Add(clip.Min).Sub(p.clip.Min)
   486  	uv := image.Rectangle{
   487  		Min: o,
   488  		Max: o.Add(clip.Size()),
   489  	}
   490  	fbo := r.pather.stenciler.cover(p.place.Idx)
   491  	r.ctx.BindTexture(gl.TEXTURE_2D, fbo.tex)
   492  	coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size)
   493  	r.ctx.Uniform2f(r.pather.stenciler.uIntersectUVScale, coverScale.X, coverScale.Y)
   494  	r.ctx.Uniform2f(r.pather.stenciler.uIntersectUVOffset, coverOff.X, coverOff.Y)
   495  	r.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
   496  }
   497  
   498  func (r *renderer) packIntersections(ops []imageOp) {
   499  	r.intersections.clear()
   500  	for i, img := range ops {
   501  		var npaths int
   502  		var onePath *pathOp
   503  		for p := img.path; p != nil; p = p.parent {
   504  			if p.path {
   505  				onePath = p
   506  				npaths++
   507  			}
   508  		}
   509  		switch npaths {
   510  		case 0:
   511  		case 1:
   512  			place := onePath.place
   513  			place.Pos = place.Pos.Sub(onePath.clip.Min).Add(img.clip.Min)
   514  			ops[i].place = place
   515  			ops[i].clipType = clipTypePath
   516  		default:
   517  			sz := image.Point{X: img.clip.Dx(), Y: img.clip.Dy()}
   518  			place, ok := r.intersections.add(sz)
   519  			if !ok {
   520  				panic("internal error: if the intersection fit, the intersection should fit as well")
   521  			}
   522  			ops[i].clipType = clipTypeIntersection
   523  			ops[i].place = place
   524  		}
   525  	}
   526  }
   527  
   528  func (r *renderer) packStencils(pops *[]*pathOp) {
   529  	r.packer.clear()
   530  	ops := *pops
   531  	// Allocate atlas space for cover textures.
   532  	var i int
   533  	for i < len(ops) {
   534  		p := ops[i]
   535  		if p.clip.Empty() {
   536  			ops[i] = ops[len(ops)-1]
   537  			ops = ops[:len(ops)-1]
   538  			continue
   539  		}
   540  		sz := image.Point{X: p.clip.Dx(), Y: p.clip.Dy()}
   541  		place, ok := r.packer.add(sz)
   542  		if !ok {
   543  			// The clip area is at most the entire screen. Hopefully no
   544  			// screen is larger than GL_MAX_TEXTURE_SIZE.
   545  			panic(fmt.Errorf("clip area %v is larger than maximum texture size %dx%d", p.clip, r.packer.maxDim, r.packer.maxDim))
   546  		}
   547  		p.place = place
   548  		i++
   549  	}
   550  	*pops = ops
   551  }
   552  
   553  // intersects intersects clip and b where b is offset by off.
   554  // ceilRect returns a bounding image.Rectangle for a f32.Rectangle.
   555  func boundRectF(r f32.Rectangle) image.Rectangle {
   556  	return image.Rectangle{
   557  		Min: image.Point{
   558  			X: int(floor(r.Min.X)),
   559  			Y: int(floor(r.Min.Y)),
   560  		},
   561  		Max: image.Point{
   562  			X: int(ceil(r.Max.X)),
   563  			Y: int(ceil(r.Max.Y)),
   564  		},
   565  	}
   566  }
   567  
   568  func toRectF(r image.Rectangle) f32.Rectangle {
   569  	return f32.Rectangle{
   570  		Min: f32.Point{
   571  			X: float32(r.Min.X),
   572  			Y: float32(r.Min.Y),
   573  		},
   574  		Max: f32.Point{
   575  			X: float32(r.Max.X),
   576  			Y: float32(r.Max.Y),
   577  		},
   578  	}
   579  }
   580  
   581  func ceil(v float32) int {
   582  	return int(math.Ceil(float64(v)))
   583  }
   584  
   585  func floor(v float32) int {
   586  	return int(math.Floor(float64(v)))
   587  }
   588  
   589  func (d *drawOps) reset(cache *resourceCache, viewport image.Point) {
   590  	d.clearColor = [3]float32{1.0, 1.0, 1.0}
   591  	d.cache = cache
   592  	d.viewport = viewport
   593  	d.imageOps = d.imageOps[:0]
   594  	d.zimageOps = d.zimageOps[:0]
   595  	d.pathOps = d.pathOps[:0]
   596  	d.pathOpCache = d.pathOpCache[:0]
   597  }
   598  
   599  func (d *drawOps) collect(cache *resourceCache, root *op.Ops, viewport image.Point) {
   600  	d.reset(cache, viewport)
   601  	clip := f32.Rectangle{
   602  		Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
   603  	}
   604  	d.reader.Reset(root)
   605  	state := drawState{
   606  		clip:  clip,
   607  		rect:  true,
   608  		color: color.RGBA{A: 0xff},
   609  	}
   610  	d.collectOps(&d.reader, state)
   611  }
   612  
   613  func (d *drawOps) newPathOp() *pathOp {
   614  	d.pathOpCache = append(d.pathOpCache, pathOp{})
   615  	return &d.pathOpCache[len(d.pathOpCache)-1]
   616  }
   617  
   618  func (d *drawOps) collectOps(r *ops.Reader, state drawState) int {
   619  	var aux []byte
   620  	var auxKey ops.Key
   621  loop:
   622  	for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
   623  		switch opconst.OpType(encOp.Data[0]) {
   624  		case opconst.TypeTransform:
   625  			dop := ops.DecodeTransformOp(encOp.Data)
   626  			state.t = state.t.Multiply(op.TransformOp(dop))
   627  		case opconst.TypeAux:
   628  			aux = encOp.Data[opconst.TypeAuxLen:]
   629  			// The first data byte stores whether the MaxY
   630  			// fields have been initialized.
   631  			maxyFilled := aux[0] == 1
   632  			aux[0] = 1
   633  			aux = aux[1:]
   634  			if !maxyFilled {
   635  				fillMaxY(aux)
   636  			}
   637  			auxKey = encOp.Key
   638  		case opconst.TypeClip:
   639  			var op clipOp
   640  			op.decode(encOp.Data)
   641  			off := state.t.Transform(f32.Point{})
   642  			state.clip = state.clip.Intersect(op.bounds.Add(off))
   643  			if state.clip.Empty() {
   644  				continue
   645  			}
   646  			npath := d.newPathOp()
   647  			*npath = pathOp{
   648  				parent: state.cpath,
   649  				off:    off,
   650  			}
   651  			state.cpath = npath
   652  			if len(aux) > 0 {
   653  				state.rect = false
   654  				state.cpath.pathKey = auxKey
   655  				state.cpath.path = true
   656  				state.cpath.pathVerts = aux
   657  				d.pathOps = append(d.pathOps, state.cpath)
   658  			}
   659  			aux = nil
   660  			auxKey = ops.Key{}
   661  		case opconst.TypeColor:
   662  			state.matType = materialColor
   663  			state.color = decodeColorOp(encOp.Data)
   664  		case opconst.TypeImage:
   665  			state.matType = materialTexture
   666  			state.image = decodeImageOp(encOp.Data, encOp.Refs)
   667  		case opconst.TypePaint:
   668  			op := decodePaintOp(encOp.Data)
   669  			off := state.t.Transform(f32.Point{})
   670  			clip := state.clip.Intersect(op.Rect.Add(off))
   671  			if clip.Empty() {
   672  				continue
   673  			}
   674  			bounds := boundRectF(clip)
   675  			mat := state.materialFor(d.cache, op.Rect, off, bounds)
   676  			if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && mat.material == materialColor {
   677  				// The image is a uniform opaque color and takes up the whole screen.
   678  				// Scrap images up to and including this image and set clear color.
   679  				d.zimageOps = d.zimageOps[:0]
   680  				d.imageOps = d.imageOps[:0]
   681  				state.z = 0
   682  				copy(d.clearColor[:], mat.color[:3])
   683  				continue
   684  			}
   685  			state.z++
   686  			// Assume 16-bit depth buffer.
   687  			const zdepth = 1 << 16
   688  			// Convert z to window-space, assuming depth range [0;1].
   689  			zf := float32(state.z)*2/zdepth - 1.0
   690  			img := imageOp{
   691  				z:        zf,
   692  				path:     state.cpath,
   693  				off:      off,
   694  				clip:     bounds,
   695  				material: mat,
   696  			}
   697  			if state.rect && img.material.opaque {
   698  				d.zimageOps = append(d.zimageOps, img)
   699  			} else {
   700  				d.imageOps = append(d.imageOps, img)
   701  			}
   702  		case opconst.TypePush:
   703  			state.z = d.collectOps(r, state)
   704  		case opconst.TypePop:
   705  			break loop
   706  		}
   707  	}
   708  	return state.z
   709  }
   710  
   711  func expandPathOp(p *pathOp, clip image.Rectangle) {
   712  	for p != nil {
   713  		pclip := p.clip
   714  		if !pclip.Empty() {
   715  			clip = clip.Union(pclip)
   716  		}
   717  		p.clip = clip
   718  		p = p.parent
   719  	}
   720  }
   721  
   722  func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f32.Point, clip image.Rectangle) material {
   723  	var m material
   724  	switch d.matType {
   725  	case materialColor:
   726  		m.material = materialColor
   727  		m.color = gamma(d.color.RGBA())
   728  		m.opaque = m.color[3] == 1.0
   729  	case materialTexture:
   730  		m.material = materialTexture
   731  		dr := boundRectF(rect.Add(off))
   732  		sz := d.image.src.Bounds().Size()
   733  		sr := f32.Rectangle{
   734  			Max: f32.Point{
   735  				X: float32(sz.X),
   736  				Y: float32(sz.Y),
   737  			},
   738  		}
   739  		if dx := float32(dr.Dx()); dx != 0 {
   740  			// Don't clip 1 px width sources.
   741  			if sdx := sr.Dx(); sdx > 1 {
   742  				sr.Min.X += (float32(clip.Min.X-dr.Min.X)*sdx + dx/2) / dx
   743  				sr.Max.X -= (float32(dr.Max.X-clip.Max.X)*sdx + dx/2) / dx
   744  			}
   745  		}
   746  		if dy := float32(dr.Dy()); dy != 0 {
   747  			// Don't clip 1 px height sources.
   748  			if sdy := sr.Dy(); sdy > 1 {
   749  				sr.Min.Y += (float32(clip.Min.Y-dr.Min.Y)*sdy + dy/2) / dy
   750  				sr.Max.Y -= (float32(dr.Max.Y-clip.Max.Y)*sdy + dy/2) / dy
   751  			}
   752  		}
   753  		tex, exists := cache.get(d.image.handle)
   754  		if !exists {
   755  			t := &texture{
   756  				src: d.image.src,
   757  			}
   758  			cache.put(d.image.handle, t)
   759  			tex = t
   760  		}
   761  		m.texture = tex.(*texture)
   762  		m.uvScale, m.uvOffset = texSpaceTransform(sr, sz)
   763  	}
   764  	return m
   765  }
   766  
   767  func (r *renderer) drawZOps(ops []imageOp) {
   768  	r.ctx.Enable(gl.DEPTH_TEST)
   769  	r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts)
   770  	r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0)
   771  	r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2)
   772  	r.ctx.EnableVertexAttribArray(attribPos)
   773  	r.ctx.EnableVertexAttribArray(attribUV)
   774  	// Render front to back.
   775  	for i := len(ops) - 1; i >= 0; i-- {
   776  		img := ops[i]
   777  		m := img.material
   778  		switch m.material {
   779  		case materialTexture:
   780  			r.ctx.BindTexture(gl.TEXTURE_2D, r.texHandle(m.texture))
   781  		}
   782  		drc := img.clip
   783  		scale, off := clipSpaceTransform(drc, r.blitter.viewport)
   784  		r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset)
   785  	}
   786  	r.ctx.DisableVertexAttribArray(attribPos)
   787  	r.ctx.DisableVertexAttribArray(attribUV)
   788  	r.ctx.Disable(gl.DEPTH_TEST)
   789  }
   790  
   791  func (r *renderer) drawOps(ops []imageOp) {
   792  	r.ctx.Enable(gl.DEPTH_TEST)
   793  	r.ctx.DepthMask(false)
   794  	r.ctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
   795  	r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts)
   796  	r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0)
   797  	r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2)
   798  	r.ctx.EnableVertexAttribArray(attribPos)
   799  	r.ctx.EnableVertexAttribArray(attribUV)
   800  	var coverTex gl.Texture
   801  	for _, img := range ops {
   802  		m := img.material
   803  		switch m.material {
   804  		case materialTexture:
   805  			r.ctx.BindTexture(gl.TEXTURE_2D, r.texHandle(m.texture))
   806  		}
   807  		drc := img.clip
   808  		scale, off := clipSpaceTransform(drc, r.blitter.viewport)
   809  		var fbo stencilFBO
   810  		switch img.clipType {
   811  		case clipTypeNone:
   812  			r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset)
   813  			continue
   814  		case clipTypePath:
   815  			fbo = r.pather.stenciler.cover(img.place.Idx)
   816  		case clipTypeIntersection:
   817  			fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
   818  		}
   819  		if !coverTex.Equal(fbo.tex) {
   820  			coverTex = fbo.tex
   821  			r.ctx.ActiveTexture(gl.TEXTURE1)
   822  			r.ctx.BindTexture(gl.TEXTURE_2D, coverTex)
   823  			r.ctx.ActiveTexture(gl.TEXTURE0)
   824  		}
   825  		uv := image.Rectangle{
   826  			Min: img.place.Pos,
   827  			Max: img.place.Pos.Add(drc.Size()),
   828  		}
   829  		coverScale, coverOff := texSpaceTransform(toRectF(uv), fbo.size)
   830  		r.pather.cover(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset, coverScale, coverOff)
   831  	}
   832  	r.ctx.DisableVertexAttribArray(attribPos)
   833  	r.ctx.DisableVertexAttribArray(attribUV)
   834  	r.ctx.DepthMask(true)
   835  	r.ctx.Disable(gl.DEPTH_TEST)
   836  }
   837  
   838  func (r *renderer) uploadTexture(img *image.RGBA) {
   839  	var pixels []byte
   840  	b := img.Bounds()
   841  	w, h := b.Dx(), b.Dy()
   842  	if img.Stride != w*4 {
   843  		panic("unsupported stride")
   844  	}
   845  	start := (b.Min.X + b.Min.Y*w) * 4
   846  	end := (b.Max.X + (b.Max.Y-1)*w) * 4
   847  	pixels = img.Pix[start:end]
   848  	tt := r.ctx.caps.srgbaTriple
   849  	r.ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, w, h, tt.format, tt.typ, pixels)
   850  }
   851  
   852  func gamma(r, g, b, a uint32) [4]float32 {
   853  	color := [4]float32{float32(r) / 0xffff, float32(g) / 0xffff, float32(b) / 0xffff, float32(a) / 0xffff}
   854  	// Assume that image.Uniform colors are in sRGB space. Linearize.
   855  	for i := 0; i <= 2; i++ {
   856  		c := color[i]
   857  		// Use the formula from EXT_sRGB.
   858  		if c <= 0.04045 {
   859  			c = c / 12.92
   860  		} else {
   861  			c = float32(math.Pow(float64((c+0.055)/1.055), 2.4))
   862  		}
   863  		color[i] = c
   864  	}
   865  	return color
   866  }
   867  
   868  func (b *blitter) blit(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff f32.Point) {
   869  	b.ctx.UseProgram(b.prog[mat])
   870  	switch mat {
   871  	case materialColor:
   872  		b.ctx.Uniform4f(b.vars[mat].uColor, col[0], col[1], col[2], col[3])
   873  	case materialTexture:
   874  		b.ctx.Uniform2f(b.vars[mat].uUVScale, uvScale.X, uvScale.Y)
   875  		b.ctx.Uniform2f(b.vars[mat].uUVOffset, uvOff.X, uvOff.Y)
   876  	}
   877  	b.ctx.Uniform1f(b.vars[mat].z, z)
   878  	b.ctx.Uniform2f(b.vars[mat].uScale, scale.X, scale.Y)
   879  	b.ctx.Uniform2f(b.vars[mat].uOffset, off.X, off.Y)
   880  	b.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
   881  }
   882  
   883  // texSpaceTransform return the scale and offset that transforms the given subimage
   884  // into quad texture coordinates.
   885  func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Point) {
   886  	size := f32.Point{X: float32(bounds.X), Y: float32(bounds.Y)}
   887  	scale := f32.Point{X: r.Dx() / size.X, Y: r.Dy() / size.Y}
   888  	offset := f32.Point{X: r.Min.X / size.X, Y: r.Min.Y / size.Y}
   889  	return scale, offset
   890  }
   891  
   892  // clipSpaceTransform returns the scale and offset that transforms the given
   893  // rectangle from a viewport into OpenGL clip space.
   894  func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) {
   895  	// First, transform UI coordinates to OpenGL coordinates:
   896  	//
   897  	//	[(-1, +1) (+1, +1)]
   898  	//	[(-1, -1) (+1, -1)]
   899  	//
   900  	x, y := float32(r.Min.X), float32(r.Min.Y)
   901  	w, h := float32(r.Dx()), float32(r.Dy())
   902  	vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y)
   903  	x = x*vx - 1
   904  	y = 1 - y*vy
   905  	w *= vx
   906  	h *= vy
   907  
   908  	// Then, compute the transformation from the fullscreen quad to
   909  	// the rectangle at (x, y) and dimensions (w, h).
   910  	scale := f32.Point{X: w * .5, Y: h * .5}
   911  	offset := f32.Point{X: x + w*.5, Y: y - h*.5}
   912  	return scale, offset
   913  }
   914  
   915  func bindFramebuffer(ctx *context, fbo gl.Framebuffer) {
   916  	ctx.BindFramebuffer(gl.FRAMEBUFFER, fbo)
   917  	if st := ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
   918  		panic(fmt.Errorf("AA FBO not complete; status = 0x%x, err = %d", st, ctx.GetError()))
   919  	}
   920  }
   921  
   922  func createTexture(ctx *context) gl.Texture {
   923  	tex := ctx.CreateTexture()
   924  	ctx.BindTexture(gl.TEXTURE_2D, tex)
   925  	ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
   926  	ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
   927  	ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
   928  	ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
   929  	return tex
   930  }
   931  
   932  // Fill in maximal Y coordinates of the NW and NE corners.
   933  func fillMaxY(verts []byte) {
   934  	contour := 0
   935  	bo := binary.LittleEndian
   936  	for len(verts) > 0 {
   937  		maxy := float32(math.Inf(-1))
   938  		i := 0
   939  		for ; i+path.VertStride*4 <= len(verts); i += path.VertStride * 4 {
   940  			vert := verts[i : i+path.VertStride]
   941  			// MaxY contains the integer contour index.
   942  			pathContour := int(bo.Uint32(vert[int(unsafe.Offsetof(((*path.Vertex)(nil)).MaxY)):]))
   943  			if contour != pathContour {
   944  				contour = pathContour
   945  				break
   946  			}
   947  			fromy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*path.Vertex)(nil)).FromY)):]))
   948  			ctrly := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*path.Vertex)(nil)).CtrlY)):]))
   949  			toy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*path.Vertex)(nil)).ToY)):]))
   950  			if fromy > maxy {
   951  				maxy = fromy
   952  			}
   953  			if ctrly > maxy {
   954  				maxy = ctrly
   955  			}
   956  			if toy > maxy {
   957  				maxy = toy
   958  			}
   959  		}
   960  		fillContourMaxY(maxy, verts[:i])
   961  		verts = verts[i:]
   962  	}
   963  }
   964  
   965  func fillContourMaxY(maxy float32, verts []byte) {
   966  	bo := binary.LittleEndian
   967  	for i := 0; i < len(verts); i += path.VertStride {
   968  		off := int(unsafe.Offsetof(((*path.Vertex)(nil)).MaxY))
   969  		bo.PutUint32(verts[i+off:], math.Float32bits(maxy))
   970  	}
   971  }
   972  
   973  const blitVSrc = `
   974  #version 100
   975  
   976  precision highp float;
   977  
   978  uniform float z;
   979  uniform vec2 scale;
   980  uniform vec2 offset;
   981  
   982  attribute vec2 pos;
   983  
   984  attribute vec2 uv;
   985  uniform vec2 uvScale;
   986  uniform vec2 uvOffset;
   987  
   988  varying vec2 vUV;
   989  
   990  void main() {
   991  	vec2 p = pos;
   992  	p *= scale;
   993  	p += offset;
   994  	gl_Position = vec4(p, z, 1);
   995  	vUV = uv*uvScale + uvOffset;
   996  }
   997  `
   998  
   999  const blitFSrc = `
  1000  #version 100
  1001  
  1002  precision mediump float;
  1003  
  1004  varying vec2 vUV;
  1005  
  1006  HEADER
  1007  
  1008  void main() {
  1009  	gl_FragColor = GET_COLOR;
  1010  }
  1011  `