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