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