github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/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/cybriq/giocore/f32"
    15  	"github.com/cybriq/giocore/gpu/internal/driver"
    16  	"github.com/cybriq/giocore/internal/byteslice"
    17  	"github.com/cybriq/giocore/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  }