path.go raw

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