gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/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 "gioui.org/gpu/internal/driver" 15 "gioui.org/internal/byteslice" 16 "gioui.org/internal/f32" 17 "gioui.org/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 [2][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 for _, p := range p { 313 p.Release() 314 } 315 } 316 } 317 318 func buildPath(ctx driver.Device, p []byte) pathData { 319 buf, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, p) 320 if err != nil { 321 panic(err) 322 } 323 return pathData{ 324 ncurves: len(p) / vertStride, 325 data: buf, 326 } 327 } 328 329 func (p pathData) release() { 330 p.data.Release() 331 } 332 333 func (p *pather) begin(sizes []image.Point) { 334 p.stenciler.begin(sizes) 335 } 336 337 func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) { 338 p.stenciler.stencilPath(bounds, offset, uv, data) 339 } 340 341 func (s *stenciler) beginIntersect(sizes []image.Point) { 342 // 8 bit coverage is enough, but OpenGL ES only supports single channel 343 // floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if 344 // no floating point support is available. 345 s.intersections.resize(s.ctx, driver.TextureFormatFloat, sizes) 346 } 347 348 func (s *stenciler) cover(idx int) FBO { 349 return s.fbos.fbos[idx] 350 } 351 352 func (s *stenciler) begin(sizes []image.Point) { 353 s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes) 354 } 355 356 func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) { 357 s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy()) 358 // Transform UI coordinates to OpenGL coordinates. 359 texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())} 360 scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y} 361 orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y} 362 s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y} 363 s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y} 364 s.pipeline.pipeline.UploadUniforms(s.ctx) 365 // Draw in batches that fit in uint16 indices. 366 start := 0 367 nquads := data.ncurves / 4 368 for start < nquads { 369 batch := nquads - start 370 if max := pathBatchSize; batch > max { 371 batch = max 372 } 373 off := vertStride * start * 4 374 s.ctx.BindVertexBuffer(data.data, off) 375 s.ctx.DrawElements(0, batch*6) 376 start += batch 377 } 378 } 379 380 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) { 381 p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff) 382 } 383 384 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) { 385 var uniforms *coverUniforms 386 switch mat { 387 case materialColor: 388 c.colUniforms.color = col 389 uniforms = &c.colUniforms.coverUniforms 390 case materialLinearGradient: 391 c.linearGradientUniforms.color1 = col1 392 c.linearGradientUniforms.color2 = col2 393 394 t1, t2, t3, t4, t5, t6 := uvTrans.Elems() 395 c.linearGradientUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0} 396 c.linearGradientUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0} 397 uniforms = &c.linearGradientUniforms.coverUniforms 398 case materialTexture: 399 t1, t2, t3, t4, t5, t6 := uvTrans.Elems() 400 c.texUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0} 401 c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0} 402 uniforms = &c.texUniforms.coverUniforms 403 } 404 uniforms.fbo = 0 405 if isFBO { 406 uniforms.fbo = 1 407 } 408 uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y} 409 uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y} 410 fboIdx := 0 411 if isFBO { 412 fboIdx = 1 413 } 414 c.pipelines[fboIdx][mat].UploadUniforms(c.ctx) 415 c.ctx.DrawArrays(0, 4) 416 } 417 418 func init() { 419 // Check that struct vertex has the expected size and 420 // that it contains no padding. 421 if unsafe.Sizeof(*(*vertex)(nil)) != vertStride { 422 panic("unexpected struct size") 423 } 424 }