github.com/utopiagio/gio@v0.0.8/gpu/gpu.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  /*
     4  Package gpu implements the rendering of Gio drawing operations. It
     5  is used by package app and package app/headless and is otherwise not
     6  useful except for integrating with external window implementations.
     7  */
     8  package gpu
     9  
    10  import (
    11  	"encoding/binary"
    12  	"fmt"
    13  	"image"
    14  	"image/color"
    15  	"math"
    16  	"os"
    17  	"reflect"
    18  	"time"
    19  	"unsafe"
    20  
    21  	"github.com/utopiagio/gio/gpu/internal/driver"
    22  	"github.com/utopiagio/gio/internal/byteslice"
    23  	"github.com/utopiagio/gio/internal/f32"
    24  	"github.com/utopiagio/gio/internal/f32color"
    25  	"github.com/utopiagio/gio/internal/ops"
    26  	"github.com/utopiagio/gio/internal/scene"
    27  	"github.com/utopiagio/gio/internal/stroke"
    28  	"github.com/utopiagio/gio/layout"
    29  	"github.com/utopiagio/gio/op"
    30  	"gioui.org/shader"
    31  	"gioui.org/shader/gio"
    32  
    33  	// Register backends.
    34  	_ "github.com/utopiagio/gio/gpu/internal/d3d11"
    35  	_ "github.com/utopiagio/gio/gpu/internal/metal"
    36  	_ "github.com/utopiagio/gio/gpu/internal/opengl"
    37  	_ "github.com/utopiagio/gio/gpu/internal/vulkan"
    38  )
    39  
    40  type GPU interface {
    41  	// Release non-Go resources. The GPU is no longer valid after Release.
    42  	Release()
    43  	// Clear sets the clear color for the next Frame.
    44  	Clear(color color.NRGBA)
    45  	// Frame draws the graphics operations from op into a viewport of target.
    46  	Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error
    47  }
    48  
    49  type gpu struct {
    50  	cache *textureCache
    51  
    52  	profile                                string
    53  	timers                                 *timers
    54  	frameStart                             time.Time
    55  	stencilTimer, coverTimer, cleanupTimer *timer
    56  	drawOps                                drawOps
    57  	ctx                                    driver.Device
    58  	renderer                               *renderer
    59  }
    60  
    61  type renderer struct {
    62  	ctx           driver.Device
    63  	blitter       *blitter
    64  	pather        *pather
    65  	packer        packer
    66  	intersections packer
    67  	layers        packer
    68  	layerFBOs     fboSet
    69  }
    70  
    71  type drawOps struct {
    72  	reader       ops.Reader
    73  	states       []f32.Affine2D
    74  	transStack   []f32.Affine2D
    75  	layers       []opacityLayer
    76  	opacityStack []int
    77  	vertCache    []byte
    78  	viewport     image.Point
    79  	clear        bool
    80  	clearColor   f32color.RGBA
    81  	imageOps     []imageOp
    82  	pathOps      []*pathOp
    83  	pathOpCache  []pathOp
    84  	qs           quadSplitter
    85  	pathCache    *opCache
    86  }
    87  
    88  type opacityLayer struct {
    89  	opacity float32
    90  	parent  int
    91  	// depth of the opacity stack. Layers of equal depth are
    92  	// independent and may be packed into one atlas.
    93  	depth int
    94  	// opStart and opEnd denote the range of drawOps.imageOps
    95  	// that belong to the layer.
    96  	opStart, opEnd int
    97  	// clip of the layer operations.
    98  	clip  image.Rectangle
    99  	place placement
   100  }
   101  
   102  type drawState struct {
   103  	t     f32.Affine2D
   104  	cpath *pathOp
   105  
   106  	matType materialType
   107  	// Current paint.ImageOp
   108  	image imageOpData
   109  	// Current paint.ColorOp, if any.
   110  	color color.NRGBA
   111  
   112  	// Current paint.LinearGradientOp.
   113  	stop1  f32.Point
   114  	stop2  f32.Point
   115  	color1 color.NRGBA
   116  	color2 color.NRGBA
   117  }
   118  
   119  type pathOp struct {
   120  	off f32.Point
   121  	// rect tracks whether the clip stack can be represented by a
   122  	// pixel-aligned rectangle.
   123  	rect bool
   124  	// clip is the union of all
   125  	// later clip rectangles.
   126  	clip   image.Rectangle
   127  	bounds f32.Rectangle
   128  	// intersect is the intersection of bounds and all
   129  	// previous clip bounds.
   130  	intersect f32.Rectangle
   131  	pathKey   opKey
   132  	path      bool
   133  	pathVerts []byte
   134  	parent    *pathOp
   135  	place     placement
   136  }
   137  
   138  type imageOp struct {
   139  	path     *pathOp
   140  	clip     image.Rectangle
   141  	material material
   142  	clipType clipType
   143  	// place is either a placement in the path fbos or intersection fbos,
   144  	// depending on clipType.
   145  	place placement
   146  	// layerOps is the number of operations this
   147  	// operation replaces.
   148  	layerOps int
   149  }
   150  
   151  func decodeStrokeOp(data []byte) float32 {
   152  	_ = data[4]
   153  	bo := binary.LittleEndian
   154  	return math.Float32frombits(bo.Uint32(data[1:]))
   155  }
   156  
   157  type quadsOp struct {
   158  	key opKey
   159  	aux []byte
   160  }
   161  
   162  type opKey struct {
   163  	outline        bool
   164  	strokeWidth    float32
   165  	sx, hx, sy, hy float32
   166  	ops.Key
   167  }
   168  
   169  type material struct {
   170  	material materialType
   171  	opaque   bool
   172  	// For materialTypeColor.
   173  	color f32color.RGBA
   174  	// For materialTypeLinearGradient.
   175  	color1  f32color.RGBA
   176  	color2  f32color.RGBA
   177  	opacity float32
   178  	// For materialTypeTexture.
   179  	data    imageOpData
   180  	tex     driver.Texture
   181  	uvTrans f32.Affine2D
   182  }
   183  
   184  const (
   185  	filterLinear  = 0
   186  	filterNearest = 1
   187  )
   188  
   189  // imageOpData is the shadow of paint.ImageOp.
   190  type imageOpData struct {
   191  	src    *image.RGBA
   192  	handle interface{}
   193  	filter byte
   194  }
   195  
   196  type linearGradientOpData struct {
   197  	stop1  f32.Point
   198  	color1 color.NRGBA
   199  	stop2  f32.Point
   200  	color2 color.NRGBA
   201  }
   202  
   203  func decodeImageOp(data []byte, refs []interface{}) imageOpData {
   204  	handle := refs[1]
   205  	if handle == nil {
   206  		return imageOpData{}
   207  	}
   208  	return imageOpData{
   209  		src:    refs[0].(*image.RGBA),
   210  		handle: handle,
   211  		filter: data[1],
   212  	}
   213  }
   214  
   215  func decodeColorOp(data []byte) color.NRGBA {
   216  	data = data[:ops.TypeColorLen]
   217  	return color.NRGBA{
   218  		R: data[1],
   219  		G: data[2],
   220  		B: data[3],
   221  		A: data[4],
   222  	}
   223  }
   224  
   225  func decodeLinearGradientOp(data []byte) linearGradientOpData {
   226  	data = data[:ops.TypeLinearGradientLen]
   227  	bo := binary.LittleEndian
   228  	return linearGradientOpData{
   229  		stop1: f32.Point{
   230  			X: math.Float32frombits(bo.Uint32(data[1:])),
   231  			Y: math.Float32frombits(bo.Uint32(data[5:])),
   232  		},
   233  		stop2: f32.Point{
   234  			X: math.Float32frombits(bo.Uint32(data[9:])),
   235  			Y: math.Float32frombits(bo.Uint32(data[13:])),
   236  		},
   237  		color1: color.NRGBA{
   238  			R: data[17+0],
   239  			G: data[17+1],
   240  			B: data[17+2],
   241  			A: data[17+3],
   242  		},
   243  		color2: color.NRGBA{
   244  			R: data[21+0],
   245  			G: data[21+1],
   246  			B: data[21+2],
   247  			A: data[21+3],
   248  		},
   249  	}
   250  }
   251  
   252  type resource interface {
   253  	release()
   254  }
   255  
   256  type texture struct {
   257  	src *image.RGBA
   258  	tex driver.Texture
   259  }
   260  
   261  type blitter struct {
   262  	ctx                    driver.Device
   263  	viewport               image.Point
   264  	pipelines              [3]*pipeline
   265  	colUniforms            *blitColUniforms
   266  	texUniforms            *blitTexUniforms
   267  	linearGradientUniforms *blitLinearGradientUniforms
   268  	quadVerts              driver.Buffer
   269  }
   270  
   271  type blitColUniforms struct {
   272  	blitUniforms
   273  	_ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
   274  	colorUniforms
   275  }
   276  
   277  type blitTexUniforms struct {
   278  	blitUniforms
   279  }
   280  
   281  type blitLinearGradientUniforms struct {
   282  	blitUniforms
   283  	_ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128 bytes.
   284  	gradientUniforms
   285  }
   286  
   287  type uniformBuffer struct {
   288  	buf driver.Buffer
   289  	ptr []byte
   290  }
   291  
   292  type pipeline struct {
   293  	pipeline driver.Pipeline
   294  	uniforms *uniformBuffer
   295  }
   296  
   297  type blitUniforms struct {
   298  	transform     [4]float32
   299  	uvTransformR1 [4]float32
   300  	uvTransformR2 [4]float32
   301  	opacity       float32
   302  	fbo           float32
   303  	_             [2]float32
   304  }
   305  
   306  type colorUniforms struct {
   307  	color f32color.RGBA
   308  }
   309  
   310  type gradientUniforms struct {
   311  	color1 f32color.RGBA
   312  	color2 f32color.RGBA
   313  }
   314  
   315  type clipType uint8
   316  
   317  const (
   318  	clipTypeNone clipType = iota
   319  	clipTypePath
   320  	clipTypeIntersection
   321  )
   322  
   323  type materialType uint8
   324  
   325  const (
   326  	materialColor materialType = iota
   327  	materialLinearGradient
   328  	materialTexture
   329  )
   330  
   331  // New creates a GPU for the given API.
   332  func New(api API) (GPU, error) {
   333  	d, err := driver.NewDevice(api)
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  	return NewWithDevice(d)
   338  }
   339  
   340  // NewWithDevice creates a GPU with a pre-existing device.
   341  //
   342  // Note: for internal use only.
   343  func NewWithDevice(d driver.Device) (GPU, error) {
   344  	d.BeginFrame(nil, false, image.Point{})
   345  	defer d.EndFrame()
   346  	forceCompute := os.Getenv("GIORENDERER") == "forcecompute"
   347  	feats := d.Caps().Features
   348  	switch {
   349  	case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB):
   350  		return newGPU(d)
   351  	}
   352  	return newCompute(d)
   353  }
   354  
   355  func newGPU(ctx driver.Device) (*gpu, error) {
   356  	g := &gpu{
   357  		cache: newTextureCache(),
   358  	}
   359  	g.drawOps.pathCache = newOpCache()
   360  	if err := g.init(ctx); err != nil {
   361  		return nil, err
   362  	}
   363  	return g, nil
   364  }
   365  
   366  func (g *gpu) init(ctx driver.Device) error {
   367  	g.ctx = ctx
   368  	g.renderer = newRenderer(ctx)
   369  	return nil
   370  }
   371  
   372  func (g *gpu) Clear(col color.NRGBA) {
   373  	g.drawOps.clear = true
   374  	g.drawOps.clearColor = f32color.LinearFromSRGB(col)
   375  }
   376  
   377  func (g *gpu) Release() {
   378  	g.renderer.release()
   379  	g.drawOps.pathCache.release()
   380  	g.cache.release()
   381  	if g.timers != nil {
   382  		g.timers.Release()
   383  	}
   384  	g.ctx.Release()
   385  }
   386  
   387  func (g *gpu) Frame(frameOps *op.Ops, target RenderTarget, viewport image.Point) error {
   388  	g.collect(viewport, frameOps)
   389  	return g.frame(target)
   390  }
   391  
   392  func (g *gpu) collect(viewport image.Point, frameOps *op.Ops) {
   393  	g.renderer.blitter.viewport = viewport
   394  	g.renderer.pather.viewport = viewport
   395  	g.drawOps.reset(viewport)
   396  	g.drawOps.collect(frameOps, viewport)
   397  	if false && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
   398  		g.frameStart = time.Now()
   399  		g.timers = newTimers(g.ctx)
   400  		g.stencilTimer = g.timers.newTimer()
   401  		g.coverTimer = g.timers.newTimer()
   402  		g.cleanupTimer = g.timers.newTimer()
   403  	}
   404  }
   405  
   406  func (g *gpu) frame(target RenderTarget) error {
   407  	viewport := g.renderer.blitter.viewport
   408  	defFBO := g.ctx.BeginFrame(target, g.drawOps.clear, viewport)
   409  	defer g.ctx.EndFrame()
   410  	g.drawOps.buildPaths(g.ctx)
   411  	for _, img := range g.drawOps.imageOps {
   412  		expandPathOp(img.path, img.clip)
   413  	}
   414  	g.stencilTimer.begin()
   415  	g.renderer.packStencils(&g.drawOps.pathOps)
   416  	g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps)
   417  	g.renderer.packIntersections(g.drawOps.imageOps)
   418  	g.renderer.prepareIntersections(g.drawOps.imageOps)
   419  	g.renderer.intersect(g.drawOps.imageOps)
   420  	g.stencilTimer.end()
   421  	g.coverTimer.begin()
   422  	g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
   423  	g.renderer.prepareDrawOps(g.drawOps.imageOps)
   424  	g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
   425  	g.renderer.drawLayers(g.drawOps.layers, g.drawOps.imageOps)
   426  	d := driver.LoadDesc{
   427  		ClearColor: g.drawOps.clearColor,
   428  	}
   429  	if g.drawOps.clear {
   430  		g.drawOps.clear = false
   431  		d.Action = driver.LoadActionClear
   432  	}
   433  	g.ctx.BeginRenderPass(defFBO, d)
   434  	g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
   435  	g.renderer.drawOps(false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
   436  	g.coverTimer.end()
   437  	g.ctx.EndRenderPass()
   438  	g.cleanupTimer.begin()
   439  	g.cache.frame()
   440  	g.drawOps.pathCache.frame()
   441  	g.cleanupTimer.end()
   442  	if false && g.timers.ready() {
   443  		st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
   444  		ft := st + covt + cleant
   445  		q := 100 * time.Microsecond
   446  		st, covt = st.Round(q), covt.Round(q)
   447  		frameDur := time.Since(g.frameStart).Round(q)
   448  		ft = ft.Round(q)
   449  		g.profile = fmt.Sprintf("draw:%7s gpu:%7s st:%7s cov:%7s", frameDur, ft, st, covt)
   450  	}
   451  	return nil
   452  }
   453  
   454  func (g *gpu) Profile() string {
   455  	return g.profile
   456  }
   457  
   458  func (r *renderer) texHandle(cache *textureCache, data imageOpData) driver.Texture {
   459  	key := textureCacheKey{
   460  		filter: data.filter,
   461  		handle: data.handle,
   462  	}
   463  
   464  	var tex *texture
   465  	t, exists := cache.get(key)
   466  	if !exists {
   467  		t = &texture{
   468  			src: data.src,
   469  		}
   470  		cache.put(key, t)
   471  	}
   472  	tex = t.(*texture)
   473  	if tex.tex != nil {
   474  		return tex.tex
   475  	}
   476  
   477  	var minFilter, magFilter driver.TextureFilter
   478  	switch data.filter {
   479  	case filterLinear:
   480  		minFilter, magFilter = driver.FilterLinearMipmapLinear, driver.FilterLinear
   481  	case filterNearest:
   482  		minFilter, magFilter = driver.FilterNearest, driver.FilterNearest
   483  	}
   484  
   485  	handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA,
   486  		data.src.Bounds().Dx(), data.src.Bounds().Dy(),
   487  		minFilter, magFilter,
   488  		driver.BufferBindingTexture,
   489  	)
   490  	if err != nil {
   491  		panic(err)
   492  	}
   493  	driver.UploadImage(handle, image.Pt(0, 0), data.src)
   494  	tex.tex = handle
   495  	return tex.tex
   496  }
   497  
   498  func (t *texture) release() {
   499  	if t.tex != nil {
   500  		t.tex.Release()
   501  	}
   502  }
   503  
   504  func newRenderer(ctx driver.Device) *renderer {
   505  	r := &renderer{
   506  		ctx:     ctx,
   507  		blitter: newBlitter(ctx),
   508  		pather:  newPather(ctx),
   509  	}
   510  
   511  	maxDim := ctx.Caps().MaxTextureSize
   512  	// Large atlas textures cause artifacts due to precision loss in
   513  	// shaders.
   514  	if cap := 8192; maxDim > cap {
   515  		maxDim = cap
   516  	}
   517  	d := image.Pt(maxDim, maxDim)
   518  
   519  	r.packer.maxDims = d
   520  	r.intersections.maxDims = d
   521  	r.layers.maxDims = d
   522  	return r
   523  }
   524  
   525  func (r *renderer) release() {
   526  	r.pather.release()
   527  	r.blitter.release()
   528  	r.layerFBOs.delete(r.ctx, 0)
   529  }
   530  
   531  func newBlitter(ctx driver.Device) *blitter {
   532  	quadVerts, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices,
   533  		byteslice.Slice([]float32{
   534  			-1, -1, 0, 0,
   535  			+1, -1, 1, 0,
   536  			-1, +1, 0, 1,
   537  			+1, +1, 1, 1,
   538  		}),
   539  	)
   540  	if err != nil {
   541  		panic(err)
   542  	}
   543  	b := &blitter{
   544  		ctx:       ctx,
   545  		quadVerts: quadVerts,
   546  	}
   547  	b.colUniforms = new(blitColUniforms)
   548  	b.texUniforms = new(blitTexUniforms)
   549  	b.linearGradientUniforms = new(blitLinearGradientUniforms)
   550  	pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
   551  		[3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms},
   552  	)
   553  	if err != nil {
   554  		panic(err)
   555  	}
   556  	b.pipelines = pipelines
   557  	return b
   558  }
   559  
   560  func (b *blitter) release() {
   561  	b.quadVerts.Release()
   562  	for _, p := range b.pipelines {
   563  		p.Release()
   564  	}
   565  }
   566  
   567  func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) {
   568  	var pipelines [3]*pipeline
   569  	blend := driver.BlendDesc{
   570  		Enable:    true,
   571  		SrcFactor: driver.BlendFactorOne,
   572  		DstFactor: driver.BlendFactorOneMinusSrcAlpha,
   573  	}
   574  	layout := driver.VertexLayout{
   575  		Inputs: []driver.InputDesc{
   576  			{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
   577  			{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
   578  		},
   579  		Stride: 4 * 4,
   580  	}
   581  	vsh, err := b.NewVertexShader(vsSrc)
   582  	if err != nil {
   583  		return pipelines, err
   584  	}
   585  	defer vsh.Release()
   586  	{
   587  		fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
   588  		if err != nil {
   589  			return pipelines, err
   590  		}
   591  		defer fsh.Release()
   592  		pipe, err := b.NewPipeline(driver.PipelineDesc{
   593  			VertexShader:   vsh,
   594  			FragmentShader: fsh,
   595  			BlendDesc:      blend,
   596  			VertexLayout:   layout,
   597  			PixelFormat:    driver.TextureFormatOutput,
   598  			Topology:       driver.TopologyTriangleStrip,
   599  		})
   600  		if err != nil {
   601  			return pipelines, err
   602  		}
   603  		var vertBuffer *uniformBuffer
   604  		if u := uniforms[materialTexture]; u != nil {
   605  			vertBuffer = newUniformBuffer(b, u)
   606  		}
   607  		pipelines[materialTexture] = &pipeline{pipe, vertBuffer}
   608  	}
   609  	{
   610  		var vertBuffer *uniformBuffer
   611  		fsh, err := b.NewFragmentShader(fsSrc[materialColor])
   612  		if err != nil {
   613  			pipelines[materialTexture].Release()
   614  			return pipelines, err
   615  		}
   616  		defer fsh.Release()
   617  		pipe, err := b.NewPipeline(driver.PipelineDesc{
   618  			VertexShader:   vsh,
   619  			FragmentShader: fsh,
   620  			BlendDesc:      blend,
   621  			VertexLayout:   layout,
   622  			PixelFormat:    driver.TextureFormatOutput,
   623  			Topology:       driver.TopologyTriangleStrip,
   624  		})
   625  		if err != nil {
   626  			pipelines[materialTexture].Release()
   627  			return pipelines, err
   628  		}
   629  		if u := uniforms[materialColor]; u != nil {
   630  			vertBuffer = newUniformBuffer(b, u)
   631  		}
   632  		pipelines[materialColor] = &pipeline{pipe, vertBuffer}
   633  	}
   634  	{
   635  		var vertBuffer *uniformBuffer
   636  		fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
   637  		if err != nil {
   638  			pipelines[materialTexture].Release()
   639  			pipelines[materialColor].Release()
   640  			return pipelines, err
   641  		}
   642  		defer fsh.Release()
   643  		pipe, err := b.NewPipeline(driver.PipelineDesc{
   644  			VertexShader:   vsh,
   645  			FragmentShader: fsh,
   646  			BlendDesc:      blend,
   647  			VertexLayout:   layout,
   648  			PixelFormat:    driver.TextureFormatOutput,
   649  			Topology:       driver.TopologyTriangleStrip,
   650  		})
   651  		if err != nil {
   652  			pipelines[materialTexture].Release()
   653  			pipelines[materialColor].Release()
   654  			return pipelines, err
   655  		}
   656  		if u := uniforms[materialLinearGradient]; u != nil {
   657  			vertBuffer = newUniformBuffer(b, u)
   658  		}
   659  		pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer}
   660  	}
   661  	if err != nil {
   662  		for _, p := range pipelines {
   663  			p.Release()
   664  		}
   665  		return pipelines, err
   666  	}
   667  	return pipelines, nil
   668  }
   669  
   670  func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
   671  	if len(r.packer.sizes) == 0 {
   672  		return
   673  	}
   674  	fbo := -1
   675  	r.pather.begin(r.packer.sizes)
   676  	for _, p := range ops {
   677  		if fbo != p.place.Idx {
   678  			if fbo != -1 {
   679  				r.ctx.EndRenderPass()
   680  			}
   681  			fbo = p.place.Idx
   682  			f := r.pather.stenciler.cover(fbo)
   683  			r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
   684  			r.ctx.BindPipeline(r.pather.stenciler.pipeline.pipeline.pipeline)
   685  			r.ctx.BindIndexBuffer(r.pather.stenciler.indexBuf)
   686  		}
   687  		v, _ := pathCache.get(p.pathKey)
   688  		r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data)
   689  	}
   690  	if fbo != -1 {
   691  		r.ctx.EndRenderPass()
   692  	}
   693  }
   694  
   695  func (r *renderer) prepareIntersections(ops []imageOp) {
   696  	for _, img := range ops {
   697  		if img.clipType != clipTypeIntersection {
   698  			continue
   699  		}
   700  		fbo := r.pather.stenciler.cover(img.path.place.Idx)
   701  		r.ctx.PrepareTexture(fbo.tex)
   702  	}
   703  }
   704  
   705  func (r *renderer) intersect(ops []imageOp) {
   706  	if len(r.intersections.sizes) == 0 {
   707  		return
   708  	}
   709  	fbo := -1
   710  	r.pather.stenciler.beginIntersect(r.intersections.sizes)
   711  	for _, img := range ops {
   712  		if img.clipType != clipTypeIntersection {
   713  			continue
   714  		}
   715  		if fbo != img.place.Idx {
   716  			if fbo != -1 {
   717  				r.ctx.EndRenderPass()
   718  			}
   719  			fbo = img.place.Idx
   720  			f := r.pather.stenciler.intersections.fbos[fbo]
   721  			d := driver.LoadDesc{Action: driver.LoadActionClear}
   722  			d.ClearColor.R = 1.0
   723  			r.ctx.BeginRenderPass(f.tex, d)
   724  			r.ctx.BindPipeline(r.pather.stenciler.ipipeline.pipeline.pipeline)
   725  			r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
   726  		}
   727  		r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
   728  		r.intersectPath(img.path, img.clip)
   729  	}
   730  	if fbo != -1 {
   731  		r.ctx.EndRenderPass()
   732  	}
   733  }
   734  
   735  func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
   736  	if p.parent != nil {
   737  		r.intersectPath(p.parent, clip)
   738  	}
   739  	if !p.path {
   740  		return
   741  	}
   742  	uv := image.Rectangle{
   743  		Min: p.place.Pos,
   744  		Max: p.place.Pos.Add(p.clip.Size()),
   745  	}
   746  	o := clip.Min.Sub(p.clip.Min)
   747  	sub := image.Rectangle{
   748  		Min: o,
   749  		Max: o.Add(clip.Size()),
   750  	}
   751  	fbo := r.pather.stenciler.cover(p.place.Idx)
   752  	r.ctx.BindTexture(0, fbo.tex)
   753  	coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
   754  	subScale, subOff := texSpaceTransform(f32.FRect(sub), p.clip.Size())
   755  	r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
   756  	r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
   757  	r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx)
   758  	r.ctx.DrawArrays(0, 4)
   759  }
   760  
   761  func (r *renderer) packIntersections(ops []imageOp) {
   762  	r.intersections.clear()
   763  	for i, img := range ops {
   764  		var npaths int
   765  		var onePath *pathOp
   766  		for p := img.path; p != nil; p = p.parent {
   767  			if p.path {
   768  				onePath = p
   769  				npaths++
   770  			}
   771  		}
   772  		switch npaths {
   773  		case 0:
   774  		case 1:
   775  			place := onePath.place
   776  			place.Pos = place.Pos.Sub(onePath.clip.Min).Add(img.clip.Min)
   777  			ops[i].place = place
   778  			ops[i].clipType = clipTypePath
   779  		default:
   780  			sz := image.Point{X: img.clip.Dx(), Y: img.clip.Dy()}
   781  			place, ok := r.intersections.add(sz)
   782  			if !ok {
   783  				panic("internal error: if the intersection fit, the intersection should fit as well")
   784  			}
   785  			ops[i].clipType = clipTypeIntersection
   786  			ops[i].place = place
   787  		}
   788  	}
   789  }
   790  
   791  func (r *renderer) packStencils(pops *[]*pathOp) {
   792  	r.packer.clear()
   793  	ops := *pops
   794  	// Allocate atlas space for cover textures.
   795  	var i int
   796  	for i < len(ops) {
   797  		p := ops[i]
   798  		if p.clip.Empty() {
   799  			ops[i] = ops[len(ops)-1]
   800  			ops = ops[:len(ops)-1]
   801  			continue
   802  		}
   803  		place, ok := r.packer.add(p.clip.Size())
   804  		if !ok {
   805  			// The clip area is at most the entire screen. Hopefully no
   806  			// screen is larger than GL_MAX_TEXTURE_SIZE.
   807  			panic(fmt.Errorf("clip area %v is larger than maximum texture size %v", p.clip, r.packer.maxDims))
   808  		}
   809  		p.place = place
   810  		i++
   811  	}
   812  	*pops = ops
   813  }
   814  
   815  func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
   816  	// Make every layer bounds contain nested layers; cull empty layers.
   817  	for i := len(layers) - 1; i >= 0; i-- {
   818  		l := layers[i]
   819  		if l.parent != -1 {
   820  			b := layers[l.parent].clip
   821  			layers[l.parent].clip = b.Union(l.clip)
   822  		}
   823  		if l.clip.Empty() {
   824  			layers = append(layers[:i], layers[i+1:]...)
   825  		}
   826  	}
   827  	// Pack layers.
   828  	r.layers.clear()
   829  	depth := 0
   830  	for i := range layers {
   831  		l := &layers[i]
   832  		// Only layers of the same depth may be packed together.
   833  		if l.depth != depth {
   834  			r.layers.newPage()
   835  		}
   836  		place, ok := r.layers.add(l.clip.Size())
   837  		if !ok {
   838  			// The layer area is at most the entire screen. Hopefully no
   839  			// screen is larger than GL_MAX_TEXTURE_SIZE.
   840  			panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims))
   841  		}
   842  		l.place = place
   843  	}
   844  	return layers
   845  }
   846  
   847  func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
   848  	if len(r.layers.sizes) == 0 {
   849  		return
   850  	}
   851  	fbo := -1
   852  	r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes)
   853  	for i := len(layers) - 1; i >= 0; i-- {
   854  		l := layers[i]
   855  		if fbo != l.place.Idx {
   856  			if fbo != -1 {
   857  				r.ctx.EndRenderPass()
   858  				r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
   859  			}
   860  			fbo = l.place.Idx
   861  			f := r.layerFBOs.fbos[fbo]
   862  			r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear})
   863  		}
   864  		v := image.Rectangle{
   865  			Min: l.place.Pos,
   866  			Max: l.place.Pos.Add(l.clip.Size()),
   867  		}
   868  		r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y)
   869  		f := r.layerFBOs.fbos[fbo]
   870  		r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
   871  		sr := f32.FRect(v)
   872  		uvScale, uvOffset := texSpaceTransform(sr, f.size)
   873  		uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)
   874  		// Replace layer ops with one textured op.
   875  		ops[l.opStart] = imageOp{
   876  			clip: l.clip,
   877  			material: material{
   878  				material: materialTexture,
   879  				tex:      f.tex,
   880  				uvTrans:  uvTrans,
   881  				opacity:  l.opacity,
   882  			},
   883  			layerOps: l.opEnd - l.opStart - 1,
   884  		}
   885  	}
   886  	if fbo != -1 {
   887  		r.ctx.EndRenderPass()
   888  		r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
   889  	}
   890  }
   891  
   892  func (d *drawOps) reset(viewport image.Point) {
   893  	d.viewport = viewport
   894  	d.imageOps = d.imageOps[:0]
   895  	d.pathOps = d.pathOps[:0]
   896  	d.pathOpCache = d.pathOpCache[:0]
   897  	d.vertCache = d.vertCache[:0]
   898  	d.transStack = d.transStack[:0]
   899  	d.layers = d.layers[:0]
   900  	d.opacityStack = d.opacityStack[:0]
   901  }
   902  
   903  func (d *drawOps) collect(root *op.Ops, viewport image.Point) {
   904  	viewf := f32.Rectangle{
   905  		Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
   906  	}
   907  	var ops *ops.Ops
   908  	if root != nil {
   909  		ops = &root.Internal
   910  	}
   911  	d.reader.Reset(ops)
   912  	d.collectOps(&d.reader, viewf)
   913  }
   914  
   915  func (d *drawOps) buildPaths(ctx driver.Device) {
   916  	for _, p := range d.pathOps {
   917  		if v, exists := d.pathCache.get(p.pathKey); !exists || v.data.data == nil {
   918  			data := buildPath(ctx, p.pathVerts)
   919  			d.pathCache.put(p.pathKey, opCacheValue{
   920  				data:   data,
   921  				bounds: p.bounds,
   922  			})
   923  		}
   924  		p.pathVerts = nil
   925  	}
   926  }
   927  
   928  func (d *drawOps) newPathOp() *pathOp {
   929  	d.pathOpCache = append(d.pathOpCache, pathOp{})
   930  	return &d.pathOpCache[len(d.pathOpCache)-1]
   931  }
   932  
   933  func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) {
   934  	npath := d.newPathOp()
   935  	*npath = pathOp{
   936  		parent:    state.cpath,
   937  		bounds:    bounds,
   938  		off:       off,
   939  		intersect: bounds.Add(off),
   940  		rect:      true,
   941  	}
   942  	if npath.parent != nil {
   943  		npath.rect = npath.parent.rect
   944  		npath.intersect = npath.parent.intersect.Intersect(npath.intersect)
   945  	}
   946  	if len(aux) > 0 {
   947  		npath.rect = false
   948  		npath.pathKey = auxKey
   949  		npath.path = true
   950  		npath.pathVerts = aux
   951  		d.pathOps = append(d.pathOps, npath)
   952  	}
   953  	state.cpath = npath
   954  }
   955  
   956  func (d *drawOps) save(id int, state f32.Affine2D) {
   957  	if extra := id - len(d.states) + 1; extra > 0 {
   958  		d.states = append(d.states, make([]f32.Affine2D, extra)...)
   959  	}
   960  	d.states[id] = state
   961  }
   962  
   963  func (k opKey) SetTransform(t f32.Affine2D) opKey {
   964  	sx, hx, _, hy, sy, _ := t.Elems()
   965  	k.sx = sx
   966  	k.hx = hx
   967  	k.hy = hy
   968  	k.sy = sy
   969  	return k
   970  }
   971  
   972  func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) {
   973  	var (
   974  		quads quadsOp
   975  		state drawState
   976  	)
   977  	reset := func() {
   978  		state = drawState{
   979  			color: color.NRGBA{A: 0xff},
   980  		}
   981  	}
   982  	reset()
   983  loop:
   984  	for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
   985  		switch ops.OpType(encOp.Data[0]) {
   986  		case ops.TypeTransform:
   987  			dop, push := ops.DecodeTransform(encOp.Data)
   988  			if push {
   989  				d.transStack = append(d.transStack, state.t)
   990  			}
   991  			state.t = state.t.Mul(dop)
   992  		case ops.TypePopTransform:
   993  			n := len(d.transStack)
   994  			state.t = d.transStack[n-1]
   995  			d.transStack = d.transStack[:n-1]
   996  
   997  		case ops.TypePushOpacity:
   998  			opacity := ops.DecodeOpacity(encOp.Data)
   999  			parent := -1
  1000  			depth := len(d.opacityStack)
  1001  			if depth > 0 {
  1002  				parent = d.opacityStack[depth-1]
  1003  			}
  1004  			lidx := len(d.layers)
  1005  			d.layers = append(d.layers, opacityLayer{
  1006  				opacity: opacity,
  1007  				parent:  parent,
  1008  				depth:   depth,
  1009  				opStart: len(d.imageOps),
  1010  			})
  1011  			d.opacityStack = append(d.opacityStack, lidx)
  1012  		case ops.TypePopOpacity:
  1013  			n := len(d.opacityStack)
  1014  			idx := d.opacityStack[n-1]
  1015  			d.layers[idx].opEnd = len(d.imageOps)
  1016  			d.opacityStack = d.opacityStack[:n-1]
  1017  
  1018  		case ops.TypeStroke:
  1019  			quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
  1020  
  1021  		case ops.TypePath:
  1022  			encOp, ok = r.Decode()
  1023  			if !ok {
  1024  				break loop
  1025  			}
  1026  			quads.aux = encOp.Data[ops.TypeAuxLen:]
  1027  			quads.key.Key = encOp.Key
  1028  
  1029  		case ops.TypeClip:
  1030  			var op ops.ClipOp
  1031  			op.Decode(encOp.Data)
  1032  			quads.key.outline = op.Outline
  1033  			bounds := f32.FRect(op.Bounds)
  1034  			trans, off := state.t.Split()
  1035  			if len(quads.aux) > 0 {
  1036  				// There is a clipping path, build the gpu data and update the
  1037  				// cache key such that it will be equal only if the transform is the
  1038  				// same also. Use cached data if we have it.
  1039  				quads.key = quads.key.SetTransform(trans)
  1040  				if v, ok := d.pathCache.get(quads.key); ok {
  1041  					// Since the GPU data exists in the cache aux will not be used.
  1042  					// Why is this not used for the offset shapes?
  1043  					bounds = v.bounds
  1044  				} else {
  1045  					var pathData []byte
  1046  					pathData, bounds = d.buildVerts(
  1047  						quads.aux, trans, quads.key.outline, quads.key.strokeWidth,
  1048  					)
  1049  					quads.aux = pathData
  1050  					// add it to the cache, without GPU data, so the transform can be
  1051  					// reused.
  1052  					d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
  1053  				}
  1054  			} else {
  1055  				quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
  1056  				quads.key = opKey{Key: encOp.Key}
  1057  			}
  1058  			d.addClipPath(&state, quads.aux, quads.key, bounds, off, true)
  1059  			quads = quadsOp{}
  1060  		case ops.TypePopClip:
  1061  			state.cpath = state.cpath.parent
  1062  
  1063  		case ops.TypeColor:
  1064  			state.matType = materialColor
  1065  			state.color = decodeColorOp(encOp.Data)
  1066  		case ops.TypeLinearGradient:
  1067  			state.matType = materialLinearGradient
  1068  			op := decodeLinearGradientOp(encOp.Data)
  1069  			state.stop1 = op.stop1
  1070  			state.stop2 = op.stop2
  1071  			state.color1 = op.color1
  1072  			state.color2 = op.color2
  1073  		case ops.TypeImage:
  1074  			state.matType = materialTexture
  1075  			state.image = decodeImageOp(encOp.Data, encOp.Refs)
  1076  		case ops.TypePaint:
  1077  			// Transform (if needed) the painting rectangle and if so generate a clip path,
  1078  			// for those cases also compute a partialTrans that maps texture coordinates between
  1079  			// the new bounding rectangle and the transformed original paint rectangle.
  1080  			t, off := state.t.Split()
  1081  			// Fill the clip area, unless the material is a (bounded) image.
  1082  			// TODO: Find a tighter bound.
  1083  			inf := float32(1e6)
  1084  			dst := f32.Rect(-inf, -inf, inf, inf)
  1085  			if state.matType == materialTexture {
  1086  				sz := state.image.src.Rect.Size()
  1087  				dst = f32.Rectangle{Max: layout.FPt(sz)}
  1088  			}
  1089  			clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
  1090  			cl := viewport.Intersect(bnd.Add(off))
  1091  			if state.cpath != nil {
  1092  				cl = state.cpath.intersect.Intersect(cl)
  1093  			}
  1094  			if cl.Empty() {
  1095  				continue
  1096  			}
  1097  
  1098  			if clipData != nil {
  1099  				// The paint operation is sheared or rotated, add a clip path representing
  1100  				// this transformed rectangle.
  1101  				k := opKey{Key: encOp.Key}
  1102  				k.SetTransform(t) // TODO: This call has no effect.
  1103  				d.addClipPath(&state, clipData, k, bnd, off, false)
  1104  			}
  1105  
  1106  			bounds := cl.Round()
  1107  			mat := state.materialFor(bnd, off, partialTrans, bounds)
  1108  
  1109  			rect := state.cpath == nil || state.cpath.rect
  1110  			if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 {
  1111  				// The image is a uniform opaque color and takes up the whole screen.
  1112  				// Scrap images up to and including this image and set clear color.
  1113  				d.imageOps = d.imageOps[:0]
  1114  				d.clearColor = mat.color.Opaque()
  1115  				d.clear = true
  1116  				continue
  1117  			}
  1118  			img := imageOp{
  1119  				path:     state.cpath,
  1120  				clip:     bounds,
  1121  				material: mat,
  1122  			}
  1123  			if n := len(d.opacityStack); n > 0 {
  1124  				idx := d.opacityStack[n-1]
  1125  				lb := d.layers[idx].clip
  1126  				if lb.Empty() {
  1127  					d.layers[idx].clip = img.clip
  1128  				} else {
  1129  					d.layers[idx].clip = lb.Union(img.clip)
  1130  				}
  1131  			}
  1132  
  1133  			d.imageOps = append(d.imageOps, img)
  1134  			if clipData != nil {
  1135  				// we added a clip path that should not remain
  1136  				state.cpath = state.cpath.parent
  1137  			}
  1138  		case ops.TypeSave:
  1139  			id := ops.DecodeSave(encOp.Data)
  1140  			d.save(id, state.t)
  1141  		case ops.TypeLoad:
  1142  			reset()
  1143  			id := ops.DecodeLoad(encOp.Data)
  1144  			state.t = d.states[id]
  1145  		}
  1146  	}
  1147  }
  1148  
  1149  func expandPathOp(p *pathOp, clip image.Rectangle) {
  1150  	for p != nil {
  1151  		pclip := p.clip
  1152  		if !pclip.Empty() {
  1153  			clip = clip.Union(pclip)
  1154  		}
  1155  		p.clip = clip
  1156  		p = p.parent
  1157  	}
  1158  }
  1159  
  1160  func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material {
  1161  	m := material{
  1162  		opacity: 1.,
  1163  	}
  1164  	switch d.matType {
  1165  	case materialColor:
  1166  		m.material = materialColor
  1167  		m.color = f32color.LinearFromSRGB(d.color)
  1168  		m.opaque = m.color.A == 1.0
  1169  	case materialLinearGradient:
  1170  		m.material = materialLinearGradient
  1171  
  1172  		m.color1 = f32color.LinearFromSRGB(d.color1)
  1173  		m.color2 = f32color.LinearFromSRGB(d.color2)
  1174  		m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0
  1175  
  1176  		m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
  1177  	case materialTexture:
  1178  		m.material = materialTexture
  1179  		dr := rect.Add(off).Round()
  1180  		sz := d.image.src.Bounds().Size()
  1181  		sr := f32.Rectangle{
  1182  			Max: f32.Point{
  1183  				X: float32(sz.X),
  1184  				Y: float32(sz.Y),
  1185  			},
  1186  		}
  1187  		dx := float32(dr.Dx())
  1188  		sdx := sr.Dx()
  1189  		sr.Min.X += float32(clip.Min.X-dr.Min.X) * sdx / dx
  1190  		sr.Max.X -= float32(dr.Max.X-clip.Max.X) * sdx / dx
  1191  		dy := float32(dr.Dy())
  1192  		sdy := sr.Dy()
  1193  		sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
  1194  		sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
  1195  		uvScale, uvOffset := texSpaceTransform(sr, sz)
  1196  		m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset))
  1197  		m.data = d.image
  1198  	}
  1199  	return m
  1200  }
  1201  
  1202  func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) {
  1203  	for i := range ops {
  1204  		img := &ops[i]
  1205  		m := img.material
  1206  		if m.material == materialTexture {
  1207  			img.material.tex = r.texHandle(cache, m.data)
  1208  		}
  1209  	}
  1210  }
  1211  
  1212  func (r *renderer) prepareDrawOps(ops []imageOp) {
  1213  	for _, img := range ops {
  1214  		m := img.material
  1215  		switch m.material {
  1216  		case materialTexture:
  1217  			r.ctx.PrepareTexture(m.tex)
  1218  		}
  1219  
  1220  		var fbo FBO
  1221  		switch img.clipType {
  1222  		case clipTypeNone:
  1223  			continue
  1224  		case clipTypePath:
  1225  			fbo = r.pather.stenciler.cover(img.place.Idx)
  1226  		case clipTypeIntersection:
  1227  			fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
  1228  		}
  1229  		r.ctx.PrepareTexture(fbo.tex)
  1230  	}
  1231  }
  1232  
  1233  func (r *renderer) drawOps(isFBO bool, opOff image.Point, viewport image.Point, ops []imageOp) {
  1234  	var coverTex driver.Texture
  1235  	for i := 0; i < len(ops); i++ {
  1236  		img := ops[i]
  1237  		i += img.layerOps
  1238  		m := img.material
  1239  		switch m.material {
  1240  		case materialTexture:
  1241  			r.ctx.BindTexture(0, m.tex)
  1242  		}
  1243  		drc := img.clip.Add(opOff)
  1244  
  1245  		scale, off := clipSpaceTransform(drc, viewport)
  1246  		var fbo FBO
  1247  		switch img.clipType {
  1248  		case clipTypeNone:
  1249  			p := r.blitter.pipelines[m.material]
  1250  			r.ctx.BindPipeline(p.pipeline)
  1251  			r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
  1252  			r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
  1253  			continue
  1254  		case clipTypePath:
  1255  			fbo = r.pather.stenciler.cover(img.place.Idx)
  1256  		case clipTypeIntersection:
  1257  			fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
  1258  		}
  1259  		if coverTex != fbo.tex {
  1260  			coverTex = fbo.tex
  1261  			r.ctx.BindTexture(1, coverTex)
  1262  		}
  1263  		uv := image.Rectangle{
  1264  			Min: img.place.Pos,
  1265  			Max: img.place.Pos.Add(drc.Size()),
  1266  		}
  1267  		coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size)
  1268  		p := r.pather.coverer.pipelines[m.material]
  1269  		r.ctx.BindPipeline(p.pipeline)
  1270  		r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
  1271  		r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
  1272  	}
  1273  }
  1274  
  1275  func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) {
  1276  	p := b.pipelines[mat]
  1277  	b.ctx.BindPipeline(p.pipeline)
  1278  	var uniforms *blitUniforms
  1279  	switch mat {
  1280  	case materialColor:
  1281  		b.colUniforms.color = col
  1282  		uniforms = &b.colUniforms.blitUniforms
  1283  	case materialTexture:
  1284  		t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
  1285  		uniforms = &b.texUniforms.blitUniforms
  1286  		uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
  1287  		uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
  1288  	case materialLinearGradient:
  1289  		b.linearGradientUniforms.color1 = col1
  1290  		b.linearGradientUniforms.color2 = col2
  1291  
  1292  		t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
  1293  		uniforms = &b.linearGradientUniforms.blitUniforms
  1294  		uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
  1295  		uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
  1296  	}
  1297  	uniforms.fbo = 0
  1298  	if fbo {
  1299  		uniforms.fbo = 1
  1300  	}
  1301  	uniforms.opacity = opacity
  1302  	uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
  1303  	p.UploadUniforms(b.ctx)
  1304  	b.ctx.DrawArrays(0, 4)
  1305  }
  1306  
  1307  // newUniformBuffer creates a new GPU uniform buffer backed by the
  1308  // structure uniformBlock points to.
  1309  func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer {
  1310  	ref := reflect.ValueOf(uniformBlock)
  1311  	// Determine the size of the uniforms structure, *uniforms.
  1312  	size := ref.Elem().Type().Size()
  1313  	// Map the uniforms structure as a byte slice.
  1314  	ptr := unsafe.Slice((*byte)(unsafe.Pointer(ref.Pointer())), size)
  1315  	ubuf, err := b.NewBuffer(driver.BufferBindingUniforms, len(ptr))
  1316  	if err != nil {
  1317  		panic(err)
  1318  	}
  1319  	return &uniformBuffer{buf: ubuf, ptr: ptr}
  1320  }
  1321  
  1322  func (u *uniformBuffer) Upload() {
  1323  	u.buf.Upload(u.ptr)
  1324  }
  1325  
  1326  func (u *uniformBuffer) Release() {
  1327  	u.buf.Release()
  1328  	u.buf = nil
  1329  }
  1330  
  1331  func (p *pipeline) UploadUniforms(ctx driver.Device) {
  1332  	if p.uniforms != nil {
  1333  		p.uniforms.Upload()
  1334  		ctx.BindUniforms(p.uniforms.buf)
  1335  	}
  1336  }
  1337  
  1338  func (p *pipeline) Release() {
  1339  	p.pipeline.Release()
  1340  	if p.uniforms != nil {
  1341  		p.uniforms.Release()
  1342  	}
  1343  	*p = pipeline{}
  1344  }
  1345  
  1346  // texSpaceTransform return the scale and offset that transforms the given subimage
  1347  // into quad texture coordinates.
  1348  func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Point) {
  1349  	size := f32.Point{X: float32(bounds.X), Y: float32(bounds.Y)}
  1350  	scale := f32.Point{X: r.Dx() / size.X, Y: r.Dy() / size.Y}
  1351  	offset := f32.Point{X: r.Min.X / size.X, Y: r.Min.Y / size.Y}
  1352  	return scale, offset
  1353  }
  1354  
  1355  // gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)].
  1356  func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D {
  1357  	d := stop2.Sub(stop1)
  1358  	l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
  1359  	a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
  1360  
  1361  	// TODO: optimize
  1362  	zp := f32.Point{}
  1363  	return f32.Affine2D{}.
  1364  		Scale(zp, layout.FPt(clip.Size())).            // scale to pixel space
  1365  		Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space
  1366  		Offset(zp.Sub(stop1)).                         // offset to first stop point
  1367  		Rotate(zp, a).                                 // rotate to align gradient
  1368  		Scale(zp, f32.Pt(1/l, 1/l))                    // scale gradient to right size
  1369  }
  1370  
  1371  // clipSpaceTransform returns the scale and offset that transforms the given
  1372  // rectangle from a viewport into GPU driver device coordinates.
  1373  func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) {
  1374  	// First, transform UI coordinates to device coordinates:
  1375  	//
  1376  	//	[(-1, -1) (+1, -1)]
  1377  	//	[(-1, +1) (+1, +1)]
  1378  	//
  1379  	x, y := float32(r.Min.X), float32(r.Min.Y)
  1380  	w, h := float32(r.Dx()), float32(r.Dy())
  1381  	vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y)
  1382  	x = x*vx - 1
  1383  	y = y*vy - 1
  1384  	w *= vx
  1385  	h *= vy
  1386  
  1387  	// Then, compute the transformation from the fullscreen quad to
  1388  	// the rectangle at (x, y) and dimensions (w, h).
  1389  	scale := f32.Point{X: w * .5, Y: h * .5}
  1390  	offset := f32.Point{X: x + w*.5, Y: y + h*.5}
  1391  
  1392  	return scale, offset
  1393  }
  1394  
  1395  // Fill in maximal Y coordinates of the NW and NE corners.
  1396  func fillMaxY(verts []byte) {
  1397  	contour := 0
  1398  	bo := binary.LittleEndian
  1399  	for len(verts) > 0 {
  1400  		maxy := float32(math.Inf(-1))
  1401  		i := 0
  1402  		for ; i+vertStride*4 <= len(verts); i += vertStride * 4 {
  1403  			vert := verts[i : i+vertStride]
  1404  			// MaxY contains the integer contour index.
  1405  			pathContour := int(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).MaxY)):]))
  1406  			if contour != pathContour {
  1407  				contour = pathContour
  1408  				break
  1409  			}
  1410  			fromy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).FromY)):]))
  1411  			ctrly := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).CtrlY)):]))
  1412  			toy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).ToY)):]))
  1413  			if fromy > maxy {
  1414  				maxy = fromy
  1415  			}
  1416  			if ctrly > maxy {
  1417  				maxy = ctrly
  1418  			}
  1419  			if toy > maxy {
  1420  				maxy = toy
  1421  			}
  1422  		}
  1423  		fillContourMaxY(maxy, verts[:i])
  1424  		verts = verts[i:]
  1425  	}
  1426  }
  1427  
  1428  func fillContourMaxY(maxy float32, verts []byte) {
  1429  	bo := binary.LittleEndian
  1430  	for i := 0; i < len(verts); i += vertStride {
  1431  		off := int(unsafe.Offsetof(((*vertex)(nil)).MaxY))
  1432  		bo.PutUint32(verts[i+off:], math.Float32bits(maxy))
  1433  	}
  1434  }
  1435  
  1436  func (d *drawOps) writeVertCache(n int) []byte {
  1437  	d.vertCache = append(d.vertCache, make([]byte, n)...)
  1438  	return d.vertCache[len(d.vertCache)-n:]
  1439  }
  1440  
  1441  // transform, split paths as needed, calculate maxY, bounds and create GPU vertices.
  1442  func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, strWidth float32) (verts []byte, bounds f32.Rectangle) {
  1443  	inf := float32(math.Inf(+1))
  1444  	d.qs.bounds = f32.Rectangle{
  1445  		Min: f32.Point{X: inf, Y: inf},
  1446  		Max: f32.Point{X: -inf, Y: -inf},
  1447  	}
  1448  	d.qs.d = d
  1449  	startLength := len(d.vertCache)
  1450  
  1451  	switch {
  1452  	case strWidth > 0:
  1453  		// Stroke path.
  1454  		ss := stroke.StrokeStyle{
  1455  			Width: strWidth,
  1456  		}
  1457  		quads := stroke.StrokePathCommands(ss, pathData)
  1458  		for _, quad := range quads {
  1459  			d.qs.contour = quad.Contour
  1460  			quad.Quad = quad.Quad.Transform(tr)
  1461  
  1462  			d.qs.splitAndEncode(quad.Quad)
  1463  		}
  1464  
  1465  	case outline:
  1466  		decodeToOutlineQuads(&d.qs, tr, pathData)
  1467  	}
  1468  
  1469  	fillMaxY(d.vertCache[startLength:])
  1470  	return d.vertCache[startLength:], d.qs.bounds
  1471  }
  1472  
  1473  // decodeOutlineQuads decodes scene commands, splits them into quadratic béziers
  1474  // as needed and feeds them to the supplied splitter.
  1475  func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) {
  1476  	for len(pathData) >= scene.CommandSize+4 {
  1477  		qs.contour = bo.Uint32(pathData)
  1478  		cmd := ops.DecodeCommand(pathData[4:])
  1479  		switch cmd.Op() {
  1480  		case scene.OpLine:
  1481  			var q stroke.QuadSegment
  1482  			q.From, q.To = scene.DecodeLine(cmd)
  1483  			q.Ctrl = q.From.Add(q.To).Mul(.5)
  1484  			q = q.Transform(tr)
  1485  			qs.splitAndEncode(q)
  1486  		case scene.OpGap:
  1487  			var q stroke.QuadSegment
  1488  			q.From, q.To = scene.DecodeGap(cmd)
  1489  			q.Ctrl = q.From.Add(q.To).Mul(.5)
  1490  			q = q.Transform(tr)
  1491  			qs.splitAndEncode(q)
  1492  		case scene.OpQuad:
  1493  			var q stroke.QuadSegment
  1494  			q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd)
  1495  			q = q.Transform(tr)
  1496  			qs.splitAndEncode(q)
  1497  		case scene.OpCubic:
  1498  			from, ctrl0, ctrl1, to := scene.DecodeCubic(cmd)
  1499  			qs.scratch = stroke.SplitCubic(from, ctrl0, ctrl1, to, qs.scratch[:0])
  1500  			for _, q := range qs.scratch {
  1501  				q = q.Transform(tr)
  1502  				qs.splitAndEncode(q)
  1503  			}
  1504  		default:
  1505  			panic("unsupported scene command")
  1506  		}
  1507  		pathData = pathData[scene.CommandSize+4:]
  1508  	}
  1509  }
  1510  
  1511  // create GPU vertices for transformed r, find the bounds and establish texture transform.
  1512  func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) {
  1513  	if isPureOffset(tr) {
  1514  		// fast-path to allow blitting of pure rectangles
  1515  		_, _, ox, _, _, oy := tr.Elems()
  1516  		off := f32.Pt(ox, oy)
  1517  		bnd.Min = r.Min.Add(off)
  1518  		bnd.Max = r.Max.Add(off)
  1519  		return
  1520  	}
  1521  
  1522  	// transform all corners, find new bounds
  1523  	corners := [4]f32.Point{
  1524  		tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)),
  1525  		tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)),
  1526  	}
  1527  	bnd.Min = f32.Pt(math.MaxFloat32, math.MaxFloat32)
  1528  	bnd.Max = f32.Pt(-math.MaxFloat32, -math.MaxFloat32)
  1529  	for _, c := range corners {
  1530  		if c.X < bnd.Min.X {
  1531  			bnd.Min.X = c.X
  1532  		}
  1533  		if c.Y < bnd.Min.Y {
  1534  			bnd.Min.Y = c.Y
  1535  		}
  1536  		if c.X > bnd.Max.X {
  1537  			bnd.Max.X = c.X
  1538  		}
  1539  		if c.Y > bnd.Max.Y {
  1540  			bnd.Max.Y = c.Y
  1541  		}
  1542  	}
  1543  
  1544  	// build the GPU vertices
  1545  	l := len(d.vertCache)
  1546  	d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...)
  1547  	aux = d.vertCache[l:]
  1548  	encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1])
  1549  	encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2])
  1550  	encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3])
  1551  	encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0])
  1552  	fillMaxY(aux)
  1553  
  1554  	// establish the transform mapping from bounds rectangle to transformed corners
  1555  	var P1, P2, P3 f32.Point
  1556  	P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
  1557  	P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
  1558  	P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
  1559  	P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
  1560  	P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
  1561  	P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
  1562  	sx, sy := P2.X-P3.X, P2.Y-P3.Y
  1563  	ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
  1564  
  1565  	return
  1566  }
  1567  
  1568  func isPureOffset(t f32.Affine2D) bool {
  1569  	a, b, _, d, e, _ := t.Elems()
  1570  	return a == 1 && b == 0 && d == 0 && e == 1
  1571  }