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 }