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

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package gpu
     4  
     5  // GPU accelerated path drawing using the algorithms from
     6  // Pathfinder (https://github.com/servo/pathfinder).
     7  
     8  import (
     9  	"encoding/binary"
    10  	"image"
    11  	"math"
    12  	"unsafe"
    13  
    14  	"github.com/utopiagio/gio/gpu/internal/driver"
    15  	"github.com/utopiagio/gio/internal/byteslice"
    16  	"github.com/utopiagio/gio/internal/f32"
    17  	"github.com/utopiagio/gio/internal/f32color"
    18  	"gioui.org/shader"
    19  	"gioui.org/shader/gio"
    20  )
    21  
    22  type pather struct {
    23  	ctx driver.Device
    24  
    25  	viewport image.Point
    26  
    27  	stenciler *stenciler
    28  	coverer   *coverer
    29  }
    30  
    31  type coverer struct {
    32  	ctx                    driver.Device
    33  	pipelines              [3]*pipeline
    34  	texUniforms            *coverTexUniforms
    35  	colUniforms            *coverColUniforms
    36  	linearGradientUniforms *coverLinearGradientUniforms
    37  }
    38  
    39  type coverTexUniforms struct {
    40  	coverUniforms
    41  	_ [12]byte // Padding to multiple of 16.
    42  }
    43  
    44  type coverColUniforms struct {
    45  	coverUniforms
    46  	_ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
    47  	colorUniforms
    48  }
    49  
    50  type coverLinearGradientUniforms struct {
    51  	coverUniforms
    52  	_ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128.
    53  	gradientUniforms
    54  }
    55  
    56  type coverUniforms struct {
    57  	transform        [4]float32
    58  	uvCoverTransform [4]float32
    59  	uvTransformR1    [4]float32
    60  	uvTransformR2    [4]float32
    61  	fbo              float32
    62  }
    63  
    64  type stenciler struct {
    65  	ctx      driver.Device
    66  	pipeline struct {
    67  		pipeline *pipeline
    68  		uniforms *stencilUniforms
    69  	}
    70  	ipipeline struct {
    71  		pipeline *pipeline
    72  		uniforms *intersectUniforms
    73  	}
    74  	fbos          fboSet
    75  	intersections fboSet
    76  	indexBuf      driver.Buffer
    77  }
    78  
    79  type stencilUniforms struct {
    80  	transform  [4]float32
    81  	pathOffset [2]float32
    82  	_          [8]byte // Padding to multiple of 16.
    83  }
    84  
    85  type intersectUniforms struct {
    86  	vert struct {
    87  		uvTransform    [4]float32
    88  		subUVTransform [4]float32
    89  	}
    90  }
    91  
    92  type fboSet struct {
    93  	fbos []FBO
    94  }
    95  
    96  type FBO struct {
    97  	size image.Point
    98  	tex  driver.Texture
    99  }
   100  
   101  type pathData struct {
   102  	ncurves int
   103  	data    driver.Buffer
   104  }
   105  
   106  // vertex data suitable for passing to vertex programs.
   107  type vertex struct {
   108  	// Corner encodes the corner: +0.5 for south, +.25 for east.
   109  	Corner       float32
   110  	MaxY         float32
   111  	FromX, FromY float32
   112  	CtrlX, CtrlY float32
   113  	ToX, ToY     float32
   114  }
   115  
   116  // encode needs to stay in-sync with the code in clip.go encodeQuadTo.
   117  func (v vertex) encode(d []byte, maxy uint32) {
   118  	d = d[0:32]
   119  	bo := binary.LittleEndian
   120  	bo.PutUint32(d[0:4], math.Float32bits(v.Corner))
   121  	bo.PutUint32(d[4:8], maxy)
   122  	bo.PutUint32(d[8:12], math.Float32bits(v.FromX))
   123  	bo.PutUint32(d[12:16], math.Float32bits(v.FromY))
   124  	bo.PutUint32(d[16:20], math.Float32bits(v.CtrlX))
   125  	bo.PutUint32(d[20:24], math.Float32bits(v.CtrlY))
   126  	bo.PutUint32(d[24:28], math.Float32bits(v.ToX))
   127  	bo.PutUint32(d[28:32], math.Float32bits(v.ToY))
   128  }
   129  
   130  const (
   131  	// Number of path quads per draw batch.
   132  	pathBatchSize = 10000
   133  	// Size of a vertex as sent to gpu
   134  	vertStride = 8 * 4
   135  )
   136  
   137  func newPather(ctx driver.Device) *pather {
   138  	return &pather{
   139  		ctx:       ctx,
   140  		stenciler: newStenciler(ctx),
   141  		coverer:   newCoverer(ctx),
   142  	}
   143  }
   144  
   145  func newCoverer(ctx driver.Device) *coverer {
   146  	c := &coverer{
   147  		ctx: ctx,
   148  	}
   149  	c.colUniforms = new(coverColUniforms)
   150  	c.texUniforms = new(coverTexUniforms)
   151  	c.linearGradientUniforms = new(coverLinearGradientUniforms)
   152  	pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
   153  		[3]interface{}{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
   154  	)
   155  	if err != nil {
   156  		panic(err)
   157  	}
   158  	c.pipelines = pipelines
   159  	return c
   160  }
   161  
   162  func newStenciler(ctx driver.Device) *stenciler {
   163  	// Allocate a suitably large index buffer for drawing paths.
   164  	indices := make([]uint16, pathBatchSize*6)
   165  	for i := 0; i < pathBatchSize; i++ {
   166  		i := uint16(i)
   167  		indices[i*6+0] = i*4 + 0
   168  		indices[i*6+1] = i*4 + 1
   169  		indices[i*6+2] = i*4 + 2
   170  		indices[i*6+3] = i*4 + 2
   171  		indices[i*6+4] = i*4 + 1
   172  		indices[i*6+5] = i*4 + 3
   173  	}
   174  	indexBuf, err := ctx.NewImmutableBuffer(driver.BufferBindingIndices, byteslice.Slice(indices))
   175  	if err != nil {
   176  		panic(err)
   177  	}
   178  	progLayout := driver.VertexLayout{
   179  		Inputs: []driver.InputDesc{
   180  			{Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).Corner))},
   181  			{Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).MaxY))},
   182  			{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).FromX))},
   183  			{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).CtrlX))},
   184  			{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).ToX))},
   185  		},
   186  		Stride: vertStride,
   187  	}
   188  	iprogLayout := driver.VertexLayout{
   189  		Inputs: []driver.InputDesc{
   190  			{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
   191  			{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
   192  		},
   193  		Stride: 4 * 4,
   194  	}
   195  	st := &stenciler{
   196  		ctx:      ctx,
   197  		indexBuf: indexBuf,
   198  	}
   199  	vsh, fsh, err := newShaders(ctx, gio.Shader_stencil_vert, gio.Shader_stencil_frag)
   200  	if err != nil {
   201  		panic(err)
   202  	}
   203  	defer vsh.Release()
   204  	defer fsh.Release()
   205  	st.pipeline.uniforms = new(stencilUniforms)
   206  	vertUniforms := newUniformBuffer(ctx, st.pipeline.uniforms)
   207  	pipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
   208  		VertexShader:   vsh,
   209  		FragmentShader: fsh,
   210  		VertexLayout:   progLayout,
   211  		BlendDesc: driver.BlendDesc{
   212  			Enable:    true,
   213  			SrcFactor: driver.BlendFactorOne,
   214  			DstFactor: driver.BlendFactorOne,
   215  		},
   216  		PixelFormat: driver.TextureFormatFloat,
   217  		Topology:    driver.TopologyTriangles,
   218  	})
   219  	st.pipeline.pipeline = &pipeline{pipe, vertUniforms}
   220  	if err != nil {
   221  		panic(err)
   222  	}
   223  	vsh, fsh, err = newShaders(ctx, gio.Shader_intersect_vert, gio.Shader_intersect_frag)
   224  	if err != nil {
   225  		panic(err)
   226  	}
   227  	defer vsh.Release()
   228  	defer fsh.Release()
   229  	st.ipipeline.uniforms = new(intersectUniforms)
   230  	vertUniforms = newUniformBuffer(ctx, &st.ipipeline.uniforms.vert)
   231  	ipipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
   232  		VertexShader:   vsh,
   233  		FragmentShader: fsh,
   234  		VertexLayout:   iprogLayout,
   235  		BlendDesc: driver.BlendDesc{
   236  			Enable:    true,
   237  			SrcFactor: driver.BlendFactorDstColor,
   238  			DstFactor: driver.BlendFactorZero,
   239  		},
   240  		PixelFormat: driver.TextureFormatFloat,
   241  		Topology:    driver.TopologyTriangleStrip,
   242  	})
   243  	st.ipipeline.pipeline = &pipeline{ipipe, vertUniforms}
   244  	if err != nil {
   245  		panic(err)
   246  	}
   247  	return st
   248  }
   249  
   250  func (s *fboSet) resize(ctx driver.Device, format driver.TextureFormat, sizes []image.Point) {
   251  	// Add fbos.
   252  	for i := len(s.fbos); i < len(sizes); i++ {
   253  		s.fbos = append(s.fbos, FBO{})
   254  	}
   255  	// Resize fbos.
   256  	for i, sz := range sizes {
   257  		f := &s.fbos[i]
   258  		// Resizing or recreating FBOs can introduce rendering stalls.
   259  		// Avoid if the space waste is not too high.
   260  		resize := sz.X > f.size.X || sz.Y > f.size.Y
   261  		waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
   262  		resize = resize || waste > 1.2
   263  		if resize {
   264  			if f.tex != nil {
   265  				f.tex.Release()
   266  			}
   267  			// Add 5% extra space in each dimension to minimize resizing.
   268  			sz = sz.Mul(105).Div(100)
   269  			max := ctx.Caps().MaxTextureSize
   270  			if sz.Y > max {
   271  				sz.Y = max
   272  			}
   273  			if sz.X > max {
   274  				sz.X = max
   275  			}
   276  			tex, err := ctx.NewTexture(format, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
   277  				driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
   278  			if err != nil {
   279  				panic(err)
   280  			}
   281  			f.size = sz
   282  			f.tex = tex
   283  		}
   284  	}
   285  	// Delete extra fbos.
   286  	s.delete(ctx, len(sizes))
   287  }
   288  
   289  func (s *fboSet) delete(ctx driver.Device, idx int) {
   290  	for i := idx; i < len(s.fbos); i++ {
   291  		f := s.fbos[i]
   292  		f.tex.Release()
   293  	}
   294  	s.fbos = s.fbos[:idx]
   295  }
   296  
   297  func (s *stenciler) release() {
   298  	s.fbos.delete(s.ctx, 0)
   299  	s.intersections.delete(s.ctx, 0)
   300  	s.pipeline.pipeline.Release()
   301  	s.ipipeline.pipeline.Release()
   302  	s.indexBuf.Release()
   303  }
   304  
   305  func (p *pather) release() {
   306  	p.stenciler.release()
   307  	p.coverer.release()
   308  }
   309  
   310  func (c *coverer) release() {
   311  	for _, p := range c.pipelines {
   312  		p.Release()
   313  	}
   314  }
   315  
   316  func buildPath(ctx driver.Device, p []byte) pathData {
   317  	buf, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, p)
   318  	if err != nil {
   319  		panic(err)
   320  	}
   321  	return pathData{
   322  		ncurves: len(p) / vertStride,
   323  		data:    buf,
   324  	}
   325  }
   326  
   327  func (p pathData) release() {
   328  	p.data.Release()
   329  }
   330  
   331  func (p *pather) begin(sizes []image.Point) {
   332  	p.stenciler.begin(sizes)
   333  }
   334  
   335  func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
   336  	p.stenciler.stencilPath(bounds, offset, uv, data)
   337  }
   338  
   339  func (s *stenciler) beginIntersect(sizes []image.Point) {
   340  	// 8 bit coverage is enough, but OpenGL ES only supports single channel
   341  	// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
   342  	// no floating point support is available.
   343  	s.intersections.resize(s.ctx, driver.TextureFormatFloat, sizes)
   344  }
   345  
   346  func (s *stenciler) cover(idx int) FBO {
   347  	return s.fbos.fbos[idx]
   348  }
   349  
   350  func (s *stenciler) begin(sizes []image.Point) {
   351  	s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
   352  }
   353  
   354  func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
   355  	s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
   356  	// Transform UI coordinates to OpenGL coordinates.
   357  	texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
   358  	scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
   359  	orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
   360  	s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
   361  	s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
   362  	s.pipeline.pipeline.UploadUniforms(s.ctx)
   363  	// Draw in batches that fit in uint16 indices.
   364  	start := 0
   365  	nquads := data.ncurves / 4
   366  	for start < nquads {
   367  		batch := nquads - start
   368  		if max := pathBatchSize; batch > max {
   369  			batch = max
   370  		}
   371  		off := vertStride * start * 4
   372  		s.ctx.BindVertexBuffer(data.data, off)
   373  		s.ctx.DrawElements(0, batch*6)
   374  		start += batch
   375  	}
   376  }
   377  
   378  func (p *pather) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
   379  	p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
   380  }
   381  
   382  func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
   383  	var uniforms *coverUniforms
   384  	switch mat {
   385  	case materialColor:
   386  		c.colUniforms.color = col
   387  		uniforms = &c.colUniforms.coverUniforms
   388  	case materialLinearGradient:
   389  		c.linearGradientUniforms.color1 = col1
   390  		c.linearGradientUniforms.color2 = col2
   391  
   392  		t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
   393  		c.linearGradientUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
   394  		c.linearGradientUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
   395  		uniforms = &c.linearGradientUniforms.coverUniforms
   396  	case materialTexture:
   397  		t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
   398  		c.texUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
   399  		c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
   400  		uniforms = &c.texUniforms.coverUniforms
   401  	}
   402  	uniforms.fbo = 0
   403  	if isFBO {
   404  		uniforms.fbo = 1
   405  	}
   406  	uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
   407  	uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
   408  	c.pipelines[mat].UploadUniforms(c.ctx)
   409  	c.ctx.DrawArrays(0, 4)
   410  }
   411  
   412  func init() {
   413  	// Check that struct vertex has the expected size and
   414  	// that it contains no padding.
   415  	if unsafe.Sizeof(*(*vertex)(nil)) != vertStride {
   416  		panic("unexpected struct size")
   417  	}
   418  }