gpu.mx raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 // GPU rendering pipeline. Ported from gioui.org/gpu.
4 // Merges gpu.go, path.go, clip.go, caches.go, pack.go.
5 // Timer support dropped. Compute shader path dropped (stencil-and-cover only).
6
7 package gio
8
9 import (
10 "encoding/binary"
11 "fmt"
12 "image"
13 "image/color"
14 "math"
15 "slices"
16 "unsafe"
17 )
18
19 // GPU is the interface for rendering Gio operations.
20 type GPU interface {
21 Release()
22 Clear(color color.NRGBA)
23 Frame(frame *OpList, target RenderTarget, viewport image.Point) error
24 }
25
26 type gpu struct {
27 cache *textureCache
28 drawOps drawOps
29 ctx Device
30 renderer *renderer
31 tess tessGenCache
32 generation uint32
33 // Async tess channels (non-nil when spawn worker is running).
34 // In sync mode (WASM / moxie_sync_tess build tag), both are nil.
35 tessReqCh chan TessRequest
36 tessResCh chan TessResult
37 }
38
39 type renderer struct {
40 ctx Device
41 blitter *blitter
42 pather *pather
43 packer packer
44 intersections packer
45 layers packer
46 layerFBOs fboSet
47 }
48
49 type drawOps struct {
50 reader Reader
51 states []Affine2D
52 transStack []Affine2D
53 layers []opacityLayer
54 opacityStack []int
55 vertCache []byte
56 viewport image.Point
57 clear bool
58 clearColor RGBA
59 imageOps []imageOp
60 pathOps []*pathOp
61 pathOpCache []pathOp
62 tessCache *tessGenCache // set by gpu each frame; nil disables caching
63 generation uint32 // current frame generation (for tessCache lastGen)
64 qs quadSplitter
65 pathCache *opCache
66 }
67
68 type opacityLayer struct {
69 opacity float32
70 parent int
71 depth int
72 opStart int
73 opEnd int
74 clip image.Rectangle
75 place placement
76 }
77
78 type drawState struct {
79 t Affine2D
80 cpath *pathOp
81
82 matType materialType
83 image imageOpData
84 color color.NRGBA
85
86 stop1 Point
87 stop2 Point
88 color1 color.NRGBA
89 color2 color.NRGBA
90 }
91
92 type pathOp struct {
93 off Point
94 rect bool
95 clip image.Rectangle
96 bounds Rectangle
97 intersect Rectangle
98 pathKey opKey
99 pathHash uint64 // geometry hash for tessGenCache; 0 = not set
100 path bool
101 pathVerts []byte
102 parent *pathOp
103 place placement
104 }
105
106 type imageOp struct {
107 path *pathOp
108 clip image.Rectangle
109 material material
110 clipType clipType
111 place placement
112 layerOps int
113 }
114
115 func decodeStrokeOp(data []byte) float32 {
116 _ = data[4]
117 bo := binary.LittleEndian()
118 return math.Float32frombits(bo.Uint32(data[1:]))
119 }
120
121 type quadsOp struct {
122 key opKey
123 aux []byte
124 }
125
126 type opKey struct {
127 outline bool
128 strokeWidth float32
129 sx, hx, sy, hy float32
130 Key
131 }
132
133 type material struct {
134 material materialType
135 opaque bool
136 color RGBA
137 color1 RGBA
138 color2 RGBA
139 opacity float32
140 data imageOpData
141 tex Texture
142 uvTrans Affine2D
143 }
144
145 const (
146 filterLinear = 0
147 filterNearest = 1
148 )
149
150 type imageOpData struct {
151 src *image.RGBA
152 handle any
153 filter byte
154 }
155
156 type linearGradientOpData struct {
157 stop1 Point
158 color1 color.NRGBA
159 stop2 Point
160 color2 color.NRGBA
161 }
162
163 func decodeImageOp(data []byte, refs []any) imageOpData {
164 handle := refs[1]
165 if handle == nil {
166 return imageOpData{}
167 }
168 return imageOpData{
169 src: refs[0].(*image.RGBA),
170 handle: handle,
171 filter: data[1],
172 }
173 }
174
175 func decodeColorOp(data []byte) color.NRGBA {
176 data = data[:TypeColorLen]
177 return color.NRGBA{
178 R: data[1],
179 G: data[2],
180 B: data[3],
181 A: data[4],
182 }
183 }
184
185 func decodeLinearGradientOp(data []byte) linearGradientOpData {
186 data = data[:TypeLinearGradientLen]
187 bo := binary.LittleEndian()
188 return linearGradientOpData{
189 stop1: Point{
190 X: math.Float32frombits(bo.Uint32(data[1:])),
191 Y: math.Float32frombits(bo.Uint32(data[5:])),
192 },
193 stop2: Point{
194 X: math.Float32frombits(bo.Uint32(data[9:])),
195 Y: math.Float32frombits(bo.Uint32(data[13:])),
196 },
197 color1: color.NRGBA{
198 R: data[17+0], G: data[17+1], B: data[17+2], A: data[17+3],
199 },
200 color2: color.NRGBA{
201 R: data[21+0], G: data[21+1], B: data[21+2], A: data[21+3],
202 },
203 }
204 }
205
206 type gpuResource interface {
207 release()
208 }
209
210 type gpuTexture struct {
211 src *image.RGBA
212 tex Texture
213 }
214
215 type blitter struct {
216 ctx Device
217 viewport image.Point
218 pipelines [2][3]*blitPipeline
219 colUniforms *blitColUniforms
220 texUniforms *blitTexUniforms
221 linearGradientUniforms *blitLinearGradientUniforms
222 quadVerts Buffer
223 }
224
225 type blitColUniforms struct {
226 blitUniforms
227 _ [48]byte // 128 - 64 (blitUniforms) - 16 (colorUniforms)
228 colorUniforms
229 }
230
231 type blitTexUniforms struct {
232 blitUniforms
233 }
234
235 type blitLinearGradientUniforms struct {
236 blitUniforms
237 _ [32]byte // 128 - 64 (blitUniforms) - 32 (gradientUniforms)
238 gradientUniforms
239 }
240
241 type gpuUniformBuffer struct {
242 buf Buffer
243 ptr []byte
244 }
245
246 type blitPipeline struct {
247 pipeline Pipeline
248 uniforms *gpuUniformBuffer
249 }
250
251 type blitUniforms struct {
252 transform [4]float32
253 uvTransformR1 [4]float32
254 uvTransformR2 [4]float32
255 opacity float32
256 fbo float32
257 _ [2]float32
258 }
259
260 type colorUniforms struct {
261 color RGBA
262 }
263
264 type gradientUniforms struct {
265 color1 RGBA
266 color2 RGBA
267 }
268
269 type clipType uint8
270
271 const (
272 clipTypeNone clipType = iota
273 clipTypePath
274 clipTypeIntersection
275 )
276
277 type materialType uint8
278
279 const (
280 materialColor materialType = iota
281 materialLinearGradient
282 materialTexture
283 )
284
285 // NewGPU creates a GPU for the given API.
286 func NewGPU(api API) (GPU, error) {
287 d, err := NewDevice(api)
288 if err != nil {
289 return nil, err
290 }
291 return NewGPUWithDevice(d)
292 }
293
294 // NewGPUWithDevice creates a GPU with a pre-existing device.
295 func NewGPUWithDevice(d Device) (GPU, error) {
296 d.BeginFrame(nil, false, image.Point{})
297 defer d.EndFrame()
298 feats := d.Caps().Features
299 switch {
300 case feats.Has(FeatureFloatRenderTargets) && feats.Has(FeatureSRGB):
301 return newGPU(d)
302 }
303 return nil, errorf("gio: no available GPU driver")
304 }
305
306 func newGPU(ctx Device) (*gpu, error) {
307 g := &gpu{
308 cache: newTextureCache(),
309 tess: newTessGenCache(),
310 }
311 g.drawOps.pathCache = newOpCache()
312 // Pre-size slices that grow during frame collection to avoid per-frame
313 // allocations with the leaking GC. Size for typical UI frame (16 ops).
314 g.drawOps.imageOps = []imageOp{:0:16}
315 g.drawOps.pathOpCache = []pathOp{:0:16}
316 g.drawOps.transStack = []Affine2D{:0:8}
317 g.drawOps.states = []Affine2D{:0:8}
318 g.drawOps.layers = []opacityLayer{:0:4}
319 g.drawOps.opacityStack = []int{:0:4}
320 g.ctx = ctx
321 g.renderer = newRenderer(ctx)
322 return g, nil
323 }
324
325 func (g *gpu) Clear(col color.NRGBA) {
326 g.drawOps.clear = true
327 g.drawOps.clearColor = LinearFromSRGB(col)
328 }
329
330 func (g *gpu) Release() {
331 g.renderer.release()
332 g.drawOps.pathCache.release()
333 g.cache.release()
334 g.ctx.Release()
335 // tessGenCache has no GPU resources; just drop the map.
336 g.tess = tessGenCache{}
337 }
338
339 func (g *gpu) Frame(frameOps *OpList, target RenderTarget, viewport image.Point) error {
340 g.collect(viewport, frameOps)
341 return g.frame(target)
342 }
343
344 func (g *gpu) collect(viewport image.Point, frameOps *OpList) {
345 g.renderer.blitter.viewport = viewport
346 g.renderer.pather.viewport = viewport
347 g.generation++
348 g.drawOps.tessCache = &g.tess
349 g.drawOps.generation = g.generation
350 g.drawOps.reset(viewport)
351 g.drawOps.collect(frameOps, viewport)
352 }
353
354 func (g *gpu) frame(target RenderTarget) error {
355 viewport := g.renderer.blitter.viewport
356 defFBO := g.ctx.BeginFrame(target, g.drawOps.clear, viewport)
357 defer g.ctx.EndFrame()
358 g.buildPathsTess()
359 for _, img := range g.drawOps.imageOps {
360 expandPathOp(img.path, img.clip)
361 }
362 g.renderer.packStencils(&g.drawOps.pathOps)
363 g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps)
364 g.renderer.packIntersections(g.drawOps.imageOps)
365 g.renderer.prepareIntersections(g.drawOps.imageOps)
366 g.renderer.intersect(g.drawOps.imageOps)
367 g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
368 g.renderer.prepareDrawOps(g.drawOps.imageOps)
369 g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers)
370 g.renderer.drawLayers(g.drawOps.layers, g.drawOps.imageOps)
371 d := LoadDesc{
372 ClearColor: g.drawOps.clearColor,
373 }
374 if g.drawOps.clear {
375 g.drawOps.clear = false
376 d.Action = LoadActionClear
377 }
378 g.ctx.BeginRenderPass(defFBO, d)
379 g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
380 g.renderer.drawOps(false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps)
381 g.ctx.EndRenderPass()
382 g.cache.frame()
383 g.drawOps.pathCache.frame()
384 return nil
385 }
386
387 func (r *renderer) texHandle(cache *textureCache, data imageOpData) Texture {
388 key := textureCacheKey{filter: data.filter, handle: data.handle}
389 var tex *gpuTexture
390 t, exists := cache.get(key)
391 if !exists {
392 t = &gpuTexture{src: data.src}
393 cache.put(key, t)
394 }
395 tex = t.(*gpuTexture)
396 if tex.tex != nil {
397 return tex.tex
398 }
399 var minFilter, magFilter TextureFilter
400 switch data.filter {
401 case filterLinear:
402 minFilter, magFilter = FilterLinearMipmapLinear, FilterLinear
403 case filterNearest:
404 minFilter, magFilter = FilterNearest, FilterNearest
405 }
406 handle, err := r.ctx.NewTexture(TextureFormatSRGBA,
407 data.src.Bounds().Dx(), data.src.Bounds().Dy(),
408 minFilter, magFilter,
409 BufferBindingTexture,
410 )
411 if err != nil {
412 panic(err)
413 }
414 UploadImage(handle, image.Pt(0, 0), data.src)
415 tex.tex = handle
416 return tex.tex
417 }
418
419 func (t *gpuTexture) release() {
420 if t.tex != nil {
421 t.tex.Release()
422 }
423 }
424
425 func newRenderer(ctx Device) *renderer {
426 r := &renderer{
427 ctx: ctx,
428 blitter: newBlitter(ctx),
429 pather: newPather(ctx),
430 }
431 maxDim := ctx.Caps().MaxTextureSize
432 if cap := 8192; maxDim > cap {
433 maxDim = cap
434 }
435 d := image.Pt(maxDim, maxDim)
436 r.packer.maxDims = d
437 r.intersections.maxDims = d
438 r.layers.maxDims = d
439 return r
440 }
441
442 func (r *renderer) release() {
443 r.pather.release()
444 r.blitter.release()
445 r.layerFBOs.delete(r.ctx, 0)
446 }
447
448 func newGPUUniformBuffer(b Device, ptr unsafe.Pointer, size int) *gpuUniformBuffer {
449 ubuf, err := b.NewBuffer(BufferBindingUniforms, size)
450 if err != nil {
451 panic(err)
452 }
453 data := unsafe.Slice((*byte)(ptr), size)
454 return &gpuUniformBuffer{buf: ubuf, ptr: data}
455 }
456
457 func (u *gpuUniformBuffer) Upload() {
458 u.buf.Upload(u.ptr)
459 }
460
461 func (u *gpuUniformBuffer) Release() {
462 u.buf.Release()
463 u.buf = nil
464 }
465
466 func (p *blitPipeline) UploadUniforms(ctx Device) {
467 if p.uniforms != nil {
468 p.uniforms.Upload()
469 ctx.BindUniforms(p.uniforms.buf)
470 }
471 }
472
473 func (p *blitPipeline) Release() {
474 p.pipeline.Release()
475 if p.uniforms != nil {
476 p.uniforms.Release()
477 }
478 *p = blitPipeline{}
479 }
480
481 func newBlitter(ctx Device) *blitter {
482 quadVerts, err := ctx.NewImmutableBuffer(BufferBindingVertices,
483 Float32sToBytes([]float32{
484 -1, -1, 0, 0,
485 +1, -1, 1, 0,
486 -1, +1, 0, 1,
487 +1, +1, 1, 1,
488 }),
489 )
490 if err != nil {
491 panic(err)
492 }
493 b := &blitter{
494 ctx: ctx,
495 quadVerts: quadVerts,
496 }
497 b.colUniforms = &blitColUniforms{}
498 b.texUniforms = &blitTexUniforms{}
499 b.linearGradientUniforms = &blitLinearGradientUniforms{}
500 pipelines, err := createColorPrograms(ctx, ShaderBlitVert(), ShaderBlitFrag(),
501 [3]unsafe.Pointer{
502 unsafe.Pointer(b.colUniforms),
503 unsafe.Pointer(b.linearGradientUniforms),
504 unsafe.Pointer(b.texUniforms),
505 },
506 [3]int{
507 int(unsafe.Sizeof(*b.colUniforms)),
508 int(unsafe.Sizeof(*b.linearGradientUniforms)),
509 int(unsafe.Sizeof(*b.texUniforms)),
510 },
511 )
512 if err != nil {
513 panic(err)
514 }
515 b.pipelines = pipelines
516 return b
517 }
518
519 func (b *blitter) release() {
520 b.quadVerts.Release()
521 for _, p := range b.pipelines {
522 for _, p := range p {
523 p.Release()
524 }
525 }
526 }
527
528 func createColorPrograms(b Device, vsSrc ShaderSources, fsSrc [3]ShaderSources, uniformPtrs [3]unsafe.Pointer, uniformSizes [3]int) (pipelines [2][3]*blitPipeline, err error) {
529 defer func() {
530 if err != nil {
531 for _, p := range pipelines {
532 for _, p := range p {
533 if p != nil {
534 p.Release()
535 }
536 }
537 }
538 }
539 }()
540 blend := BlendDesc{
541 Enable: true,
542 SrcFactor: BlendFactorOne,
543 DstFactor: BlendFactorOneMinusSrcAlpha,
544 }
545 layout := VertexLayout{
546 Inputs: []InputDesc{
547 {Type: ShaderDataTypeFloat, Size: 2, Offset: 0},
548 {Type: ShaderDataTypeFloat, Size: 2, Offset: 4 * 2},
549 },
550 Stride: 4 * 4,
551 }
552 vsh, err := b.NewVertexShader(vsSrc)
553 if err != nil {
554 return pipelines, err
555 }
556 defer vsh.Release()
557 for i, format := range []TextureFormat{TextureFormatOutput, TextureFormatSRGBA} {
558 for mat := 0; mat < 3; mat++ {
559 fsh, err := b.NewFragmentShader(fsSrc[mat])
560 if err != nil {
561 return pipelines, err
562 }
563 defer fsh.Release()
564 pipe, err := b.NewPipeline(PipelineDesc{
565 VertexShader: vsh,
566 FragmentShader: fsh,
567 BlendDesc: blend,
568 VertexLayout: layout,
569 PixelFormat: format,
570 Topology: TopologyTriangleStrip,
571 })
572 if err != nil {
573 return pipelines, err
574 }
575 var vertBuffer *gpuUniformBuffer
576 if uniformPtrs[mat] != nil {
577 vertBuffer = newGPUUniformBuffer(b, uniformPtrs[mat], uniformSizes[mat])
578 }
579 pipelines[i][mat] = &blitPipeline{pipe, vertBuffer}
580 }
581 }
582 return pipelines, nil
583 }
584
585 func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
586 if len(r.packer.sizes) == 0 {
587 return
588 }
589 fbo := -1
590 r.pather.begin(r.packer.sizes)
591 for _, p := range ops {
592 if fbo != p.place.Idx {
593 if fbo != -1 {
594 r.ctx.EndRenderPass()
595 }
596 fbo = p.place.Idx
597 f := r.pather.stenciler.cover(fbo)
598 r.ctx.BeginRenderPass(f.tex, LoadDesc{Action: LoadActionClear})
599 r.ctx.BindPipeline(r.pather.stenciler.pipeline.pipeline.pipeline)
600 r.ctx.BindIndexBuffer(r.pather.stenciler.indexBuf)
601 }
602 v, _ := pathCache.get(p.pathKey)
603 r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data)
604 }
605 if fbo != -1 {
606 r.ctx.EndRenderPass()
607 }
608 }
609
610 func (r *renderer) prepareIntersections(ops []imageOp) {
611 for _, img := range ops {
612 if img.clipType != clipTypeIntersection {
613 continue
614 }
615 fbo := r.pather.stenciler.cover(img.path.place.Idx)
616 r.ctx.PrepareTexture(fbo.tex)
617 }
618 }
619
620 func (r *renderer) intersect(ops []imageOp) {
621 if len(r.intersections.sizes) == 0 {
622 return
623 }
624 fbo := -1
625 r.pather.stenciler.beginIntersect(r.intersections.sizes)
626 for _, img := range ops {
627 if img.clipType != clipTypeIntersection {
628 continue
629 }
630 if fbo != img.place.Idx {
631 if fbo != -1 {
632 r.ctx.EndRenderPass()
633 }
634 fbo = img.place.Idx
635 f := r.pather.stenciler.intersections.fbos[fbo]
636 d := LoadDesc{Action: LoadActionClear}
637 d.ClearColor.R = 1.0
638 r.ctx.BeginRenderPass(f.tex, d)
639 r.ctx.BindPipeline(r.pather.stenciler.ipipeline.pipeline.pipeline)
640 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
641 }
642 r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
643 r.intersectPath(img.path, img.clip)
644 }
645 if fbo != -1 {
646 r.ctx.EndRenderPass()
647 }
648 }
649
650 func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
651 if p.parent != nil {
652 r.intersectPath(p.parent, clip)
653 }
654 if !p.path {
655 return
656 }
657 uv := image.Rectangle{
658 Min: p.place.Pos,
659 Max: p.place.Pos.Add(p.clip.Size()),
660 }
661 o := clip.Min.Sub(p.clip.Min)
662 sub := image.Rectangle{
663 Min: o,
664 Max: o.Add(clip.Size()),
665 }
666 fbo := r.pather.stenciler.cover(p.place.Idx)
667 r.ctx.BindTexture(0, fbo.tex)
668 coverScale, coverOff := texSpaceTransform(FRect(uv), fbo.size)
669 subScale, subOff := texSpaceTransform(FRect(sub), p.clip.Size())
670 r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
671 r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
672 r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx)
673 r.ctx.DrawArrays(0, 4)
674 }
675
676 func (r *renderer) packIntersections(ops []imageOp) {
677 r.intersections.clear()
678 for i, img := range ops {
679 var npaths int
680 var onePath *pathOp
681 for p := img.path; p != nil; p = p.parent {
682 if p.path {
683 onePath = p
684 npaths++
685 }
686 }
687 switch npaths {
688 case 0:
689 case 1:
690 place := onePath.place
691 place.Pos = place.Pos.Sub(onePath.clip.Min).Add(img.clip.Min)
692 ops[i].place = place
693 ops[i].clipType = clipTypePath
694 default:
695 sz := image.Point{X: img.clip.Dx(), Y: img.clip.Dy()}
696 place, ok := r.intersections.add(sz)
697 if !ok {
698 panic("internal error: intersection should fit")
699 }
700 ops[i].clipType = clipTypeIntersection
701 ops[i].place = place
702 }
703 }
704 }
705
706 func (r *renderer) packStencils(pops *[]*pathOp) {
707 r.packer.clear()
708 ops := *pops
709 var i int
710 for i < len(ops) {
711 p := ops[i]
712 if p.clip.Empty() {
713 ops[i] = ops[len(ops)-1]
714 ops = ops[:len(ops)-1]
715 continue
716 }
717 place, ok := r.packer.add(p.clip.Size())
718 if !ok {
719 panic(errorf("clip area %v is larger than maximum texture size %v", p.clip, r.packer.maxDims))
720 }
721 p.place = place
722 i++
723 }
724 *pops = ops
725 }
726
727 func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer {
728 for i := len(layers) - 1; i >= 0; i-- {
729 l := layers[i]
730 if l.parent != -1 {
731 b := layers[l.parent].clip
732 layers[l.parent].clip = b.Union(l.clip)
733 }
734 if l.clip.Empty() {
735 layers = slices.Delete(layers, i, i+1)
736 }
737 }
738 r.layers.clear()
739 depth := 0
740 for i := range layers {
741 l := &layers[i]
742 if l.depth != depth {
743 r.layers.newPage()
744 }
745 place, ok := r.layers.add(l.clip.Size())
746 if !ok {
747 panic(errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims))
748 }
749 l.place = place
750 }
751 return layers
752 }
753
754 func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) {
755 if len(r.layers.sizes) == 0 {
756 return
757 }
758 fbo := -1
759 r.layerFBOs.resize(r.ctx, TextureFormatSRGBA, r.layers.sizes)
760 for i := len(layers) - 1; i >= 0; i-- {
761 l := layers[i]
762 if fbo != l.place.Idx {
763 if fbo != -1 {
764 r.ctx.EndRenderPass()
765 r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
766 }
767 fbo = l.place.Idx
768 f := r.layerFBOs.fbos[fbo]
769 r.ctx.BeginRenderPass(f.tex, LoadDesc{Action: LoadActionClear})
770 }
771 v := image.Rectangle{
772 Min: l.place.Pos,
773 Max: l.place.Pos.Add(l.clip.Size()),
774 }
775 r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy())
776 f := r.layerFBOs.fbos[fbo]
777 r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd])
778 sr := FRect(v)
779 uvScale, uvOffset := texSpaceTransform(sr, f.size)
780 uvTrans := AffineId().Scale(Point{}, uvScale).Offset(uvOffset)
781 ops[l.opStart] = imageOp{
782 clip: l.clip,
783 material: material{
784 material: materialTexture,
785 tex: f.tex,
786 uvTrans: uvTrans,
787 opacity: l.opacity,
788 },
789 layerOps: l.opEnd - l.opStart - 1,
790 }
791 }
792 if fbo != -1 {
793 r.ctx.EndRenderPass()
794 r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex)
795 }
796 }
797
798 func (d *drawOps) reset(viewport image.Point) {
799 d.viewport = viewport
800 d.imageOps = d.imageOps[:0]
801 d.pathOps = d.pathOps[:0]
802 d.pathOpCache = d.pathOpCache[:0]
803 d.vertCache = d.vertCache[:0]
804 d.transStack = d.transStack[:0]
805 d.layers = d.layers[:0]
806 d.opacityStack = d.opacityStack[:0]
807 d.states = d.states[:0] // must reset; TypeLoad uses index from current frame's TypeSave
808 }
809
810 func (d *drawOps) collect(root *OpList, viewport image.Point) {
811 viewf := Rectangle{
812 Max: Point{X: float32(viewport.X), Y: float32(viewport.Y)},
813 }
814 var ops *Ops
815 if root != nil {
816 ops = &root.Internal
817 }
818 d.reader.ResetReader(ops)
819 d.collectOps(&d.reader, viewf)
820 }
821
822 func (d *drawOps) buildPaths(ctx Device) {
823 for _, p := range d.pathOps {
824 if v, exists := d.pathCache.get(p.pathKey); !exists || v.data.data == nil {
825 data := buildPath(ctx, p.pathVerts)
826 d.pathCache.put(p.pathKey, opCacheValue{
827 data: data,
828 bounds: p.bounds,
829 })
830 }
831 p.pathVerts = nil
832 }
833 }
834
835 // buildPathsTess uploads GPU vertex buffers for all path ops that need them.
836 // Tessellation itself already happened in collectOps (via buildVerts), with
837 // results cached in tessGenCache. This function just handles the GPU upload
838 // step and drains any async tessellation results from the worker channel.
839 func (g *gpu) buildPathsTess() {
840 // Drain async results if worker is running.
841 if g.tessResCh != nil {
842 drainLoop:
843 for {
844 select {
845 case res := <-g.tessResCh:
846 entry := &tessEntry{
847 key: res.Key,
848 verts: []byte(res.Verts),
849 bounds: Rectangle{
850 Min: Point{X: res.BoundsMinX, Y: res.BoundsMinY},
851 Max: Point{X: res.BoundsMaxX, Y: res.BoundsMaxY},
852 },
853 lastGen: res.Gen,
854 }
855 g.tess.put(entry)
856 default:
857 break drainLoop
858 }
859 }
860 }
861
862 d := &g.drawOps
863 for _, p := range d.pathOps {
864 // If opCache already has a GPU buffer, no upload needed.
865 if v, exists := d.pathCache.get(p.pathKey); exists && v.data.data != nil {
866 p.pathVerts = nil
867 continue
868 }
869 // Upload tessellated vertex bytes to GPU.
870 if len(p.pathVerts) > 0 {
871 data := buildPath(g.ctx, p.pathVerts)
872 d.pathCache.put(p.pathKey, opCacheValue{data: data, bounds: p.bounds})
873 }
874 p.pathVerts = nil
875 }
876
877 // Evict tessGenCache entries older than tessEvictAge frames.
878 if g.generation >= tessEvictAge {
879 g.tess.evict(g.generation - tessEvictAge)
880 }
881 }
882
883 func (d *drawOps) newPathOp() *pathOp {
884 d.pathOpCache = append(d.pathOpCache, pathOp{})
885 return &d.pathOpCache[len(d.pathOpCache)-1]
886 }
887
888 func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds Rectangle, off Point) {
889 npath := d.newPathOp()
890 *npath = pathOp{
891 parent: state.cpath,
892 bounds: bounds,
893 off: off,
894 intersect: bounds.Add(off),
895 rect: true,
896 }
897 if npath.parent != nil {
898 npath.rect = npath.parent.rect
899 npath.intersect = npath.parent.intersect.Intersect(npath.intersect)
900 }
901 if len(aux) > 0 {
902 npath.rect = false
903 npath.pathKey = auxKey
904 npath.path = true
905 npath.pathVerts = aux
906 d.pathOps = append(d.pathOps, npath)
907 }
908 state.cpath = npath
909 }
910
911 func (d *drawOps) save(id int, state Affine2D) {
912 for extra := id - len(d.states) + 1; extra > 0; extra-- {
913 d.states = append(d.states, AffineId())
914 }
915 d.states[id] = state
916 }
917
918 func (k opKey) SetTransform(t Affine2D) opKey {
919 sx, hx, _, hy, sy, _ := t.Elems()
920 k.sx = sx
921 k.hx = hx
922 k.hy = hy
923 k.sy = sy
924 return k
925 }
926
927 // toAffine reconstructs an Affine2D from the transform components stored in
928 // opKey. The translation (ox, oy) is not stored in opKey (it was split off as
929 // a separate integer offset), so this returns a transform with zero offset.
930 func (k opKey) toAffine() Affine2D {
931 return NewAffine2D(k.sx, k.hx, 0, k.hy, k.sy, 0)
932 }
933
934 func (d *drawOps) collectOps(r *Reader, viewport Rectangle) {
935 var quads quadsOp
936 state := drawState{t: AffineId()}
937 reset := func() {
938 state = drawState{
939 t: AffineId(),
940 color: color.NRGBA{A: 0xff},
941 }
942 }
943 reset()
944 loop:
945 for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
946 switch OpType(encOp.Data[0]) {
947 case TypeTransform:
948 dop, push := DecodeTransform(encOp.Data)
949 if push {
950 d.transStack = append(d.transStack, state.t)
951 }
952 state.t = state.t.Mul(dop)
953 case TypePopTransform:
954 n := len(d.transStack)
955 state.t = d.transStack[n-1]
956 d.transStack = d.transStack[:n-1]
957
958 case TypePushOpacity:
959 opacity := DecodeOpacity(encOp.Data)
960 parent := -1
961 depth := len(d.opacityStack)
962 if depth > 0 {
963 parent = d.opacityStack[depth-1]
964 }
965 lidx := len(d.layers)
966 d.layers = append(d.layers, opacityLayer{
967 opacity: opacity,
968 parent: parent,
969 depth: depth,
970 opStart: len(d.imageOps),
971 })
972 d.opacityStack = append(d.opacityStack, lidx)
973 case TypePopOpacity:
974 n := len(d.opacityStack)
975 idx := d.opacityStack[n-1]
976 d.layers[idx].opEnd = len(d.imageOps)
977 d.opacityStack = d.opacityStack[:n-1]
978
979 case TypeStroke:
980 quads.key.strokeWidth = decodeStrokeOp(encOp.Data)
981
982 case TypePath:
983 encOp, ok = r.Decode()
984 if !ok {
985 break loop
986 }
987 quads.aux = encOp.Data[TypeAuxLen:]
988 quads.key.Key = encOp.Key
989
990 case TypeClip:
991 var op DecodedClipOp
992 op.Decode(encOp.Data)
993 quads.key.outline = op.Outline
994 bounds := FRect(op.Bounds)
995 trans, off := transformOffset(state.t)
996 if len(quads.aux) > 0 {
997 quads.key = quads.key.SetTransform(trans)
998 if v, ok := d.pathCache.get(quads.key); ok {
999 bounds = v.bounds
1000 } else {
1001 var pathData []byte
1002 // Check tessGenCache before tessellating.
1003 var tessHash uint64
1004 if d.tessCache != nil {
1005 tessHash = hashPathData(quads.aux, trans, quads.key.outline, quads.key.strokeWidth)
1006 pk := PathKey{Hash: tessHash, Outline: quads.key.outline,
1007 Stroke: quads.key.strokeWidth > 0, Width: quads.key.strokeWidth}
1008 if entry, ok := d.tessCache.get(pk, d.generation); ok {
1009 pathData = entry.verts
1010 bounds = entry.bounds
1011 }
1012 }
1013 if pathData == nil {
1014 pathData, bounds = d.buildVerts(quads.aux, trans, quads.key.outline, quads.key.strokeWidth)
1015 // Store in tessGenCache (copy: pathData points into vertCache).
1016 if d.tessCache != nil && tessHash != 0 {
1017 pk := PathKey{Hash: tessHash, Outline: quads.key.outline,
1018 Stroke: quads.key.strokeWidth > 0, Width: quads.key.strokeWidth}
1019 copyVerts := []byte{:len(pathData)}
1020 copy(copyVerts, pathData)
1021 d.tessCache.put(&tessEntry{key: pk, verts: copyVerts, bounds: bounds, lastGen: d.generation})
1022 }
1023 }
1024 quads.aux = pathData
1025 d.pathCache.put(quads.key, opCacheValue{bounds: bounds})
1026 }
1027 } else {
1028 quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans)
1029 quads.key = opKey{Key: encOp.Key}
1030 quads.key = quads.key.SetTransform(trans)
1031 }
1032 d.addClipPath(&state, quads.aux, quads.key, bounds, off)
1033 quads = quadsOp{}
1034 case TypePopClip:
1035 state.cpath = state.cpath.parent
1036
1037 case TypeColor:
1038 state.matType = materialColor
1039 state.color = decodeColorOp(encOp.Data)
1040 case TypeLinearGradient:
1041 state.matType = materialLinearGradient
1042 op := decodeLinearGradientOp(encOp.Data)
1043 state.stop1 = op.stop1
1044 state.stop2 = op.stop2
1045 state.color1 = op.color1
1046 state.color2 = op.color2
1047 case TypeImage:
1048 state.matType = materialTexture
1049 state.image = decodeImageOp(encOp.Data, encOp.Refs)
1050 case TypePaint:
1051 t, off := transformOffset(state.t)
1052 inf := float32(1e6)
1053 dst := Rect(-inf, -inf, inf, inf)
1054 if state.matType == materialTexture {
1055 sz := state.image.src.Rect.Size()
1056 dst = Rectangle{Max: FPt(sz)}
1057 }
1058 clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t)
1059 cl := viewport.Intersect(bnd.Add(off))
1060 if state.cpath != nil {
1061 cl = state.cpath.intersect.Intersect(cl)
1062 }
1063 if cl.Empty() {
1064 continue
1065 }
1066 if clipData != nil {
1067 k := opKey{Key: encOp.Key}
1068 k = k.SetTransform(t)
1069 d.addClipPath(&state, clipData, k, bnd, off)
1070 }
1071 bounds := cl.Round()
1072 mat := state.materialFor(bnd, off, partialTrans, bounds)
1073 rect := state.cpath == nil || state.cpath.rect
1074 if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 {
1075 d.imageOps = d.imageOps[:0]
1076 d.clearColor = mat.color.Opaque()
1077 d.clear = true
1078 continue
1079 }
1080 img := imageOp{
1081 path: state.cpath,
1082 clip: bounds,
1083 material: mat,
1084 }
1085 if n := len(d.opacityStack); n > 0 {
1086 idx := d.opacityStack[n-1]
1087 lb := d.layers[idx].clip
1088 if lb.Empty() {
1089 d.layers[idx].clip = img.clip
1090 } else {
1091 d.layers[idx].clip = lb.Union(img.clip)
1092 }
1093 }
1094 d.imageOps = append(d.imageOps, img)
1095 if clipData != nil {
1096 state.cpath = state.cpath.parent
1097 }
1098 case TypeSave:
1099 id := DecodeSave(encOp.Data)
1100 d.save(id, state.t)
1101 case TypeLoad:
1102 reset()
1103 id := DecodeLoad(encOp.Data)
1104 state.t = d.states[id]
1105 }
1106 }
1107 }
1108
1109 func expandPathOp(p *pathOp, clip image.Rectangle) {
1110 for p != nil {
1111 pclip := p.clip
1112 if !pclip.Empty() {
1113 clip = clip.Union(pclip)
1114 }
1115 p.clip = clip
1116 p = p.parent
1117 }
1118 }
1119
1120 func (d *drawState) materialFor(rect Rectangle, off Point, partTrans Affine2D, clip image.Rectangle) material {
1121 m := material{
1122 opacity: 1.,
1123 uvTrans: AffineId(),
1124 }
1125 switch d.matType {
1126 case materialColor:
1127 m.material = materialColor
1128 m.color = LinearFromSRGB(d.color)
1129 m.opaque = m.color.A == 1.0
1130 case materialLinearGradient:
1131 m.material = materialLinearGradient
1132 m.color1 = LinearFromSRGB(d.color1)
1133 m.color2 = LinearFromSRGB(d.color2)
1134 m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0
1135 m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2))
1136 case materialTexture:
1137 m.material = materialTexture
1138 dr := rect.Add(off).Round()
1139 sz := d.image.src.Bounds().Size()
1140 sr := Rectangle{Max: Point{X: float32(sz.X), Y: float32(sz.Y)}}
1141 dx := float32(dr.Dx())
1142 sdx := sr.Dx()
1143 sr.Min.X += float32(clip.Min.X-dr.Min.X) * sdx / dx
1144 sr.Max.X -= float32(dr.Max.X-clip.Max.X) * sdx / dx
1145 dy := float32(dr.Dy())
1146 sdy := sr.Dy()
1147 sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy
1148 sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy
1149 uvScale, uvOffset := texSpaceTransform(sr, sz)
1150 m.uvTrans = partTrans.Mul(AffineId().Scale(Point{}, uvScale).Offset(uvOffset))
1151 m.data = d.image
1152 }
1153 return m
1154 }
1155
1156 func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) {
1157 for i := range ops {
1158 img := &ops[i]
1159 m := img.material
1160 if m.material == materialTexture {
1161 img.material.tex = r.texHandle(cache, m.data)
1162 }
1163 }
1164 }
1165
1166 func (r *renderer) prepareDrawOps(ops []imageOp) {
1167 for _, img := range ops {
1168 m := img.material
1169 switch m.material {
1170 case materialTexture:
1171 r.ctx.PrepareTexture(m.tex)
1172 }
1173 var fbo FBO
1174 switch img.clipType {
1175 case clipTypeNone:
1176 continue
1177 case clipTypePath:
1178 fbo = r.pather.stenciler.cover(img.place.Idx)
1179 case clipTypeIntersection:
1180 fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
1181 }
1182 r.ctx.PrepareTexture(fbo.tex)
1183 }
1184 }
1185
1186 func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageOp) {
1187 var coverTex Texture
1188 for i := 0; i < len(ops); i++ {
1189 img := ops[i]
1190 i += img.layerOps
1191 m := img.material
1192 switch m.material {
1193 case materialTexture:
1194 r.ctx.BindTexture(0, m.tex)
1195 }
1196 drc := img.clip.Add(opOff)
1197 scale, off := clipSpaceTransform(drc, viewport)
1198 var fbo FBO
1199 fboIdx := 0
1200 if isFBO {
1201 fboIdx = 1
1202 }
1203 switch img.clipType {
1204 case clipTypeNone:
1205 p := r.blitter.pipelines[fboIdx][m.material]
1206 r.ctx.BindPipeline(p.pipeline)
1207 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
1208 r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans)
1209 continue
1210 case clipTypePath:
1211 fbo = r.pather.stenciler.cover(img.place.Idx)
1212 case clipTypeIntersection:
1213 fbo = r.pather.stenciler.intersections.fbos[img.place.Idx]
1214 }
1215 if coverTex != fbo.tex {
1216 coverTex = fbo.tex
1217 r.ctx.BindTexture(1, coverTex)
1218 }
1219 uv := image.Rectangle{
1220 Min: img.place.Pos,
1221 Max: img.place.Pos.Add(drc.Size()),
1222 }
1223 coverScale, coverOff := texSpaceTransform(FRect(uv), fbo.size)
1224 p := r.pather.coverer.pipelines[fboIdx][m.material]
1225 r.ctx.BindPipeline(p.pipeline)
1226 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
1227 r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
1228 }
1229 }
1230
1231 func (b *blitter) blit(mat materialType, fbo bool, col RGBA, col1, col2 RGBA, scale, off Point, opacity float32, uvTrans Affine2D) {
1232 fboIdx := 0
1233 if fbo {
1234 fboIdx = 1
1235 }
1236 p := b.pipelines[fboIdx][mat]
1237 b.ctx.BindPipeline(p.pipeline)
1238 var uniforms *blitUniforms
1239 switch mat {
1240 case materialColor:
1241 b.colUniforms.color = col
1242 uniforms = &b.colUniforms.blitUniforms
1243 case materialTexture:
1244 t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
1245 uniforms = &b.texUniforms.blitUniforms
1246 uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
1247 uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
1248 case materialLinearGradient:
1249 b.linearGradientUniforms.color1 = col1
1250 b.linearGradientUniforms.color2 = col2
1251 t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
1252 uniforms = &b.linearGradientUniforms.blitUniforms
1253 uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
1254 uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
1255 }
1256 uniforms.fbo = 0
1257 if fbo {
1258 uniforms.fbo = 1
1259 }
1260 uniforms.opacity = opacity
1261 uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
1262 p.UploadUniforms(b.ctx)
1263 b.ctx.DrawArrays(0, 4)
1264 }
1265
1266 func texSpaceTransform(r Rectangle, bounds image.Point) (Point, Point) {
1267 size := Point{X: float32(bounds.X), Y: float32(bounds.Y)}
1268 scale := Point{X: r.Dx() / size.X, Y: r.Dy() / size.Y}
1269 offset := Point{X: r.Min.X / size.X, Y: r.Min.Y / size.Y}
1270 return scale, offset
1271 }
1272
1273 func gradientSpaceTransform(clip image.Rectangle, off Point, stop1, stop2 Point) Affine2D {
1274 d := stop2.Sub(stop1)
1275 l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y)))
1276 a := float32(math.Atan2(float64(-d.Y), float64(d.X)))
1277 zp := Point{}
1278 return AffineId().
1279 Scale(zp, FPt(clip.Size())).
1280 Offset(zp.Sub(off).Add(FPt(clip.Min))).
1281 Offset(zp.Sub(stop1)).
1282 Rotate(zp, a).
1283 Scale(zp, Pt(1/l, 1/l))
1284 }
1285
1286 func clipSpaceTransform(r image.Rectangle, viewport image.Point) (Point, Point) {
1287 x, y := float32(r.Min.X), float32(r.Min.Y)
1288 w, h := float32(r.Dx()), float32(r.Dy())
1289 vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y)
1290 x = x*vx - 1
1291 y = y*vy - 1
1292 w *= vx
1293 h *= vy
1294 scale := Point{X: w * .5, Y: h * .5}
1295 offset := Point{X: x + w*.5, Y: y + h*.5}
1296 return scale, offset
1297 }
1298
1299 func fillMaxY(verts []byte) {
1300 contour := 0
1301 bo := binary.LittleEndian()
1302 for len(verts) > 0 {
1303 maxy := float32(math.Inf(-1))
1304 i := 0
1305 for ; i+vertStride*4 <= len(verts); i += vertStride * 4 {
1306 vert := verts[i : i+vertStride]
1307 pathContour := int(bo.Uint32(vert[int(unsafe.Offsetof((*vertex)(nil).MaxY)):]))
1308 if contour != pathContour {
1309 contour = pathContour
1310 break
1311 }
1312 fromy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof((*vertex)(nil).FromY)):]))
1313 ctrly := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof((*vertex)(nil).CtrlY)):]))
1314 toy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof((*vertex)(nil).ToY)):]))
1315 if fromy > maxy {
1316 maxy = fromy
1317 }
1318 if ctrly > maxy {
1319 maxy = ctrly
1320 }
1321 if toy > maxy {
1322 maxy = toy
1323 }
1324 }
1325 fillContourMaxY(maxy, verts[:i])
1326 verts = verts[i:]
1327 }
1328 }
1329
1330 func fillContourMaxY(maxy float32, verts []byte) {
1331 bo := binary.LittleEndian()
1332 for i := 0; i < len(verts); i += vertStride {
1333 off := int(unsafe.Offsetof((*vertex)(nil).MaxY))
1334 bo.PutUint32(verts[i+off:], math.Float32bits(maxy))
1335 }
1336 }
1337
1338 func (d *drawOps) writeVertCache(n int) []byte {
1339 d.vertCache = append(d.vertCache, []byte{:n}...)
1340 return d.vertCache[len(d.vertCache)-n:]
1341 }
1342
1343 func (d *drawOps) buildVerts(pathData []byte, tr Affine2D, outline bool, strWidth float32) (verts []byte, bounds Rectangle) {
1344 inf := float32(math.Inf(+1))
1345 d.qs.bounds = Rectangle{
1346 Min: Point{X: inf, Y: inf},
1347 Max: Point{X: -inf, Y: -inf},
1348 }
1349 d.qs.d = d
1350 startLength := len(d.vertCache)
1351 switch {
1352 case strWidth > 0:
1353 ss := StrokeStyle{Width: strWidth}
1354 quads := StrokePathCommands(ss, pathData)
1355 for _, quad := range quads {
1356 d.qs.contour = quad.Contour
1357 quad.Quad = quad.Quad.Transform(tr)
1358 d.qs.splitAndEncode(quad.Quad)
1359 }
1360 case outline:
1361 decodeToOutlineQuads(&d.qs, tr, pathData)
1362 }
1363 fillMaxY(d.vertCache[startLength:])
1364 return d.vertCache[startLength:], d.qs.bounds
1365 }
1366
1367 func decodeToOutlineQuads(qs *quadSplitter, tr Affine2D, pathData []byte) {
1368 for len(pathData) >= sceneElemSize+4 {
1369 qs.contour = binary.LittleEndian().Uint32(pathData)
1370 cmd := DecodeCommand(pathData[4:])
1371 switch cmd.Op() {
1372 case OpLine:
1373 var q QuadSegment
1374 q.From, q.To = DecodeLine(cmd)
1375 q.Ctrl = q.From.Add(q.To).Mul(.5)
1376 q = q.Transform(tr)
1377 qs.splitAndEncode(q)
1378 case OpGap:
1379 var q QuadSegment
1380 q.From, q.To = DecodeGap(cmd)
1381 q.Ctrl = q.From.Add(q.To).Mul(.5)
1382 q = q.Transform(tr)
1383 qs.splitAndEncode(q)
1384 case OpQuad:
1385 var q QuadSegment
1386 q.From, q.Ctrl, q.To = DecodeQuad(cmd)
1387 q = q.Transform(tr)
1388 qs.splitAndEncode(q)
1389 case OpCubic:
1390 from, ctrl0, ctrl1, to := DecodeCubic(cmd)
1391 qs.scratch = SplitCubic(from, ctrl0, ctrl1, to, qs.scratch[:0])
1392 for _, q := range qs.scratch {
1393 q = q.Transform(tr)
1394 qs.splitAndEncode(q)
1395 }
1396 default:
1397 panic("unsupported scene command")
1398 }
1399 pathData = pathData[sceneElemSize+4:]
1400 }
1401 }
1402
1403 func (d *drawOps) boundsForTransformedRect(r Rectangle, tr Affine2D) (aux []byte, bnd Rectangle, ptr Affine2D) {
1404 ptr = AffineId()
1405 if tr == AffineId() {
1406 bnd = r
1407 return
1408 }
1409 corners := [4]Point{
1410 tr.Transform(r.Min), tr.Transform(Pt(r.Max.X, r.Min.Y)),
1411 tr.Transform(r.Max), tr.Transform(Pt(r.Min.X, r.Max.Y)),
1412 }
1413 bnd.Min = Pt(math.MaxFloat32, math.MaxFloat32)
1414 bnd.Max = Pt(-math.MaxFloat32, -math.MaxFloat32)
1415 for _, c := range corners {
1416 if c.X < bnd.Min.X {
1417 bnd.Min.X = c.X
1418 }
1419 if c.Y < bnd.Min.Y {
1420 bnd.Min.Y = c.Y
1421 }
1422 if c.X > bnd.Max.X {
1423 bnd.Max.X = c.X
1424 }
1425 if c.Y > bnd.Max.Y {
1426 bnd.Max.Y = c.Y
1427 }
1428 }
1429 l := len(d.vertCache)
1430 d.vertCache = append(d.vertCache, []byte{:vertStride * 4 * 4}...)
1431 aux = d.vertCache[l:]
1432 encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1])
1433 encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2])
1434 encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3])
1435 encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0])
1436 fillMaxY(aux)
1437 var P1, P2, P3 Point
1438 P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
1439 P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
1440 P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
1441 P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
1442 P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X)
1443 P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y)
1444 sx, sy := P2.X-P3.X, P2.Y-P3.Y
1445 ptr = NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert()
1446 return aux, bnd, ptr
1447 }
1448
1449 func transformOffset(t Affine2D) (Affine2D, Point) {
1450 sx, hx, ox, hy, sy, oy := t.Elems()
1451 iox, fox := math.Modf(float64(ox))
1452 ioy, foy := math.Modf(float64(oy))
1453 ft := NewAffine2D(sx, hx, float32(fox), hy, sy, float32(foy))
1454 ip := Pt(float32(iox), float32(ioy))
1455 return ft, ip
1456 }
1457
1458 func newShaders(ctx Device, vsrc, fsrc ShaderSources) (VertexShader, FragmentShader, error) {
1459 vert, err := ctx.NewVertexShader(vsrc)
1460 if err != nil {
1461 return nil, nil, err
1462 }
1463 frag, err := ctx.NewFragmentShader(fsrc)
1464 if err != nil {
1465 vert.Release()
1466 return nil, nil, err
1467 }
1468 return vert, frag, nil
1469 }
1470
1471 // --- Path rendering (from path.go) ---
1472
1473 type pather struct {
1474 ctx Device
1475 viewport image.Point
1476 stenciler *stenciler
1477 coverer *coverer
1478 }
1479
1480 type coverer struct {
1481 ctx Device
1482 pipelines [2][3]*blitPipeline
1483 texUniforms *coverTexUniforms
1484 colUniforms *coverColUniforms
1485 linearGradientUniforms *coverLinearGradientUniforms
1486 }
1487
1488 type coverTexUniforms struct {
1489 coverUniforms
1490 _ [12]byte
1491 }
1492
1493 type coverColUniforms struct {
1494 coverUniforms
1495 _ [44]byte // 128 - 68 (coverUniforms) - 16 (colorUniforms)
1496 colorUniforms
1497 }
1498
1499 type coverLinearGradientUniforms struct {
1500 coverUniforms
1501 _ [28]byte // 128 - 68 (coverUniforms) - 32 (gradientUniforms)
1502 gradientUniforms
1503 }
1504
1505 type coverUniforms struct {
1506 transform [4]float32
1507 uvCoverTransform [4]float32
1508 uvTransformR1 [4]float32
1509 uvTransformR2 [4]float32
1510 fbo float32
1511 }
1512
1513 type stenciler struct {
1514 ctx Device
1515 pipeline struct {
1516 pipeline *blitPipeline
1517 uniforms *stencilUniforms
1518 }
1519 ipipeline struct {
1520 pipeline *blitPipeline
1521 uniforms *intersectUniforms
1522 }
1523 fbos fboSet
1524 intersections fboSet
1525 indexBuf Buffer
1526 }
1527
1528 type stencilUniforms struct {
1529 transform [4]float32
1530 pathOffset [2]float32
1531 _ [8]byte
1532 }
1533
1534 type intersectUniforms struct {
1535 vert struct {
1536 uvTransform [4]float32
1537 subUVTransform [4]float32
1538 }
1539 }
1540
1541 type fboSet struct {
1542 fbos []FBO
1543 }
1544
1545 type FBO struct {
1546 size image.Point
1547 tex Texture
1548 }
1549
1550 type pathData struct {
1551 ncurves int
1552 data Buffer
1553 }
1554
1555 type vertex struct {
1556 Corner float32
1557 MaxY float32
1558 FromX, FromY float32
1559 CtrlX, CtrlY float32
1560 ToX, ToY float32
1561 }
1562
1563 func (v vertex) encode(d []byte, maxy uint32) {
1564 d = d[0:32]
1565 bo := binary.LittleEndian()
1566 bo.PutUint32(d[0:4], math.Float32bits(v.Corner))
1567 bo.PutUint32(d[4:8], maxy)
1568 bo.PutUint32(d[8:12], math.Float32bits(v.FromX))
1569 bo.PutUint32(d[12:16], math.Float32bits(v.FromY))
1570 bo.PutUint32(d[16:20], math.Float32bits(v.CtrlX))
1571 bo.PutUint32(d[20:24], math.Float32bits(v.CtrlY))
1572 bo.PutUint32(d[24:28], math.Float32bits(v.ToX))
1573 bo.PutUint32(d[28:32], math.Float32bits(v.ToY))
1574 }
1575
1576 const (
1577 // 10000 costs 120KB WASM heap (60000 uint16 = 120000 bytes); 1000 costs 12KB.
1578 pathBatchSize = 1000
1579 vertStride = 8 * 4
1580 )
1581
1582 func newPather(ctx Device) *pather {
1583 return &pather{
1584 ctx: ctx,
1585 stenciler: newStenciler(ctx),
1586 coverer: newCoverer(ctx),
1587 }
1588 }
1589
1590 func newCoverer(ctx Device) *coverer {
1591 c := &coverer{ctx: ctx}
1592 c.colUniforms = &coverColUniforms{}
1593 c.texUniforms = &coverTexUniforms{}
1594 c.linearGradientUniforms = &coverLinearGradientUniforms{}
1595 pipelines, err := createColorPrograms(ctx, ShaderCoverVert(), ShaderCoverFrag(),
1596 [3]unsafe.Pointer{
1597 unsafe.Pointer(c.colUniforms),
1598 unsafe.Pointer(c.linearGradientUniforms),
1599 unsafe.Pointer(c.texUniforms),
1600 },
1601 [3]int{
1602 int(unsafe.Sizeof(*c.colUniforms)),
1603 int(unsafe.Sizeof(*c.linearGradientUniforms)),
1604 int(unsafe.Sizeof(*c.texUniforms)),
1605 },
1606 )
1607 if err != nil {
1608 panic(err)
1609 }
1610 c.pipelines = pipelines
1611 return c
1612 }
1613
1614 func newStenciler(ctx Device) *stenciler {
1615 indices := []uint16{:pathBatchSize * 6}
1616 for i := int32(0); i < pathBatchSize; i++ {
1617 idx := uint16(i)
1618 indices[idx*6+0] = idx*4 + 0
1619 indices[idx*6+1] = idx*4 + 1
1620 indices[idx*6+2] = idx*4 + 2
1621 indices[idx*6+3] = idx*4 + 2
1622 indices[idx*6+4] = idx*4 + 1
1623 indices[idx*6+5] = idx*4 + 3
1624 }
1625 indexBuf, err := ctx.NewImmutableBuffer(BufferBindingIndices, Uint16sToBytes(indices))
1626 if err != nil {
1627 panic(err)
1628 }
1629 progLayout := VertexLayout{
1630 Inputs: []InputDesc{
1631 {Type: ShaderDataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*vertex)(nil).Corner))},
1632 {Type: ShaderDataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*vertex)(nil).MaxY))},
1633 {Type: ShaderDataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*vertex)(nil).FromX))},
1634 {Type: ShaderDataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*vertex)(nil).CtrlX))},
1635 {Type: ShaderDataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*vertex)(nil).ToX))},
1636 },
1637 Stride: vertStride,
1638 }
1639 iprogLayout := VertexLayout{
1640 Inputs: []InputDesc{
1641 {Type: ShaderDataTypeFloat, Size: 2, Offset: 0},
1642 {Type: ShaderDataTypeFloat, Size: 2, Offset: 4 * 2},
1643 },
1644 Stride: 4 * 4,
1645 }
1646 st := &stenciler{ctx: ctx, indexBuf: indexBuf}
1647 vsh, fsh, err := newShaders(ctx, ShaderStencilVert(), ShaderStencilFrag())
1648 if err != nil {
1649 panic(err)
1650 }
1651 defer vsh.Release()
1652 defer fsh.Release()
1653 st.pipeline.uniforms = &stencilUniforms{}
1654 vertUniforms := newGPUUniformBuffer(ctx, unsafe.Pointer(st.pipeline.uniforms), int(unsafe.Sizeof(*st.pipeline.uniforms)))
1655 pipe, err := st.ctx.NewPipeline(PipelineDesc{
1656 VertexShader: vsh,
1657 FragmentShader: fsh,
1658 VertexLayout: progLayout,
1659 BlendDesc: BlendDesc{
1660 Enable: true,
1661 SrcFactor: BlendFactorOne,
1662 DstFactor: BlendFactorOne,
1663 },
1664 PixelFormat: TextureFormatFloat,
1665 Topology: TopologyTriangles,
1666 })
1667 if err != nil {
1668 panic(err)
1669 }
1670 st.pipeline.pipeline = &blitPipeline{pipe, vertUniforms}
1671 vsh2, fsh2, err := newShaders(ctx, ShaderIntersectVert(), ShaderIntersectFrag())
1672 if err != nil {
1673 panic(err)
1674 }
1675 defer vsh2.Release()
1676 defer fsh2.Release()
1677 st.ipipeline.uniforms = &intersectUniforms{}
1678 vertUniforms2 := newGPUUniformBuffer(ctx, unsafe.Pointer(&st.ipipeline.uniforms.vert), int(unsafe.Sizeof(st.ipipeline.uniforms.vert)))
1679 ipipe, err := st.ctx.NewPipeline(PipelineDesc{
1680 VertexShader: vsh2,
1681 FragmentShader: fsh2,
1682 VertexLayout: iprogLayout,
1683 BlendDesc: BlendDesc{
1684 Enable: true,
1685 SrcFactor: BlendFactorDstColor,
1686 DstFactor: BlendFactorZero,
1687 },
1688 PixelFormat: TextureFormatFloat,
1689 Topology: TopologyTriangleStrip,
1690 })
1691 if err != nil {
1692 panic(err)
1693 }
1694 st.ipipeline.pipeline = &blitPipeline{ipipe, vertUniforms2}
1695 return st
1696 }
1697
1698 func (s *fboSet) resize(ctx Device, format TextureFormat, sizes []image.Point) {
1699 for i := len(s.fbos); i < len(sizes); i++ {
1700 s.fbos = append(s.fbos, FBO{})
1701 }
1702 for i, sz := range sizes {
1703 f := &s.fbos[i]
1704 resize := sz.X > f.size.X || sz.Y > f.size.Y
1705 if f.size.X > 0 && f.size.Y > 0 {
1706 waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
1707 resize = resize || waste > 1.2
1708 }
1709 if resize {
1710 if f.tex != nil {
1711 f.tex.Release()
1712 }
1713 sz = sz.Mul(105).Div(100)
1714 max := ctx.Caps().MaxTextureSize
1715 if sz.Y > max {
1716 sz.Y = max
1717 }
1718 if sz.X > max {
1719 sz.X = max
1720 }
1721 tex, err := ctx.NewTexture(format, sz.X, sz.Y, FilterNearest, FilterNearest,
1722 BufferBindingTexture|BufferBindingFramebuffer)
1723 if err != nil {
1724 panic(err)
1725 }
1726 f.size = sz
1727 f.tex = tex
1728 }
1729 }
1730 s.delete(ctx, len(sizes))
1731 }
1732
1733 func (s *fboSet) delete(ctx Device, idx int) {
1734 for i := idx; i < len(s.fbos); i++ {
1735 f := s.fbos[i]
1736 f.tex.Release()
1737 }
1738 s.fbos = s.fbos[:idx]
1739 }
1740
1741 func (s *stenciler) release() {
1742 s.fbos.delete(s.ctx, 0)
1743 s.intersections.delete(s.ctx, 0)
1744 s.pipeline.pipeline.Release()
1745 s.ipipeline.pipeline.Release()
1746 s.indexBuf.Release()
1747 }
1748
1749 func (p *pather) release() {
1750 p.stenciler.release()
1751 p.coverer.release()
1752 }
1753
1754 func (c *coverer) release() {
1755 for _, p := range c.pipelines {
1756 for _, p := range p {
1757 p.Release()
1758 }
1759 }
1760 }
1761
1762 func buildPath(ctx Device, p []byte) pathData {
1763 buf, err := ctx.NewImmutableBuffer(BufferBindingVertices, p)
1764 if err != nil {
1765 panic(err)
1766 }
1767 return pathData{ncurves: len(p) / vertStride, data: buf}
1768 }
1769
1770 func (p pathData) release() {
1771 p.data.Release()
1772 }
1773
1774 func (p *pather) begin(sizes []image.Point) {
1775 p.stenciler.begin(sizes)
1776 }
1777
1778 func (p *pather) stencilPath(bounds image.Rectangle, offset Point, uv image.Point, data pathData) {
1779 p.stenciler.stencilPath(bounds, offset, uv, data)
1780 }
1781
1782 func (s *stenciler) beginIntersect(sizes []image.Point) {
1783 s.intersections.resize(s.ctx, TextureFormatFloat, sizes)
1784 }
1785
1786 func (s *stenciler) cover(idx int) FBO {
1787 return s.fbos.fbos[idx]
1788 }
1789
1790 func (s *stenciler) begin(sizes []image.Point) {
1791 s.fbos.resize(s.ctx, TextureFormatFloat, sizes)
1792 }
1793
1794 func (s *stenciler) stencilPath(bounds image.Rectangle, offset Point, uv image.Point, data pathData) {
1795 s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
1796 texSize := Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
1797 scale := Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
1798 orig := Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
1799 s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
1800 s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
1801 s.pipeline.pipeline.UploadUniforms(s.ctx)
1802 start := 0
1803 nquads := data.ncurves / 4
1804 for start < nquads {
1805 batch := nquads - start
1806 if batch > pathBatchSize {
1807 batch = pathBatchSize
1808 }
1809 off := vertStride * start * 4
1810 s.ctx.BindVertexBuffer(data.data, off)
1811 s.ctx.DrawElements(0, batch*6)
1812 start += batch
1813 }
1814 }
1815
1816 func (p *pather) cover(mat materialType, isFBO bool, col RGBA, col1, col2 RGBA, scale, off Point, uvTrans Affine2D, coverScale, coverOff Point) {
1817 p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
1818 }
1819
1820 func (c *coverer) cover(mat materialType, isFBO bool, col RGBA, col1, col2 RGBA, scale, off Point, uvTrans Affine2D, coverScale, coverOff Point) {
1821 var uniforms *coverUniforms
1822 switch mat {
1823 case materialColor:
1824 c.colUniforms.color = col
1825 uniforms = &c.colUniforms.coverUniforms
1826 case materialLinearGradient:
1827 c.linearGradientUniforms.color1 = col1
1828 c.linearGradientUniforms.color2 = col2
1829 t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
1830 c.linearGradientUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
1831 c.linearGradientUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
1832 uniforms = &c.linearGradientUniforms.coverUniforms
1833 case materialTexture:
1834 t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
1835 c.texUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
1836 c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
1837 uniforms = &c.texUniforms.coverUniforms
1838 }
1839 uniforms.fbo = 0
1840 if isFBO {
1841 uniforms.fbo = 1
1842 }
1843 uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
1844 uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
1845 fboIdx := 0
1846 if isFBO {
1847 fboIdx = 1
1848 }
1849 c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
1850 c.ctx.DrawArrays(0, 4)
1851 }
1852
1853 // vertex is 8 x float32 = 32 bytes = vertStride. Keep in sync.
1854
1855 // --- Clip encoding (from clip.go) ---
1856
1857 func encodeQuadTo(data []byte, meta uint32, from, ctrl, to Point) {
1858 bo := binary.LittleEndian()
1859 data = data[:vertStride*4]
1860 bo.PutUint32(data[4:8], meta)
1861 bo.PutUint32(data[8:12], math.Float32bits(from.X))
1862 bo.PutUint32(data[12:16], math.Float32bits(from.Y))
1863 bo.PutUint32(data[16:20], math.Float32bits(ctrl.X))
1864 bo.PutUint32(data[20:24], math.Float32bits(ctrl.Y))
1865 bo.PutUint32(data[24:28], math.Float32bits(to.X))
1866 bo.PutUint32(data[28:32], math.Float32bits(to.Y))
1867 copy(data[vertStride*1:vertStride*2], data[vertStride*0:vertStride*1])
1868 copy(data[vertStride*2:vertStride*3], data[vertStride*0:vertStride*1])
1869 copy(data[vertStride*3:vertStride*4], data[vertStride*0:vertStride*1])
1870 bo.PutUint32(data[vertStride*0:vertStride*0+4], math.Float32bits(nwCorner))
1871 bo.PutUint32(data[vertStride*1:vertStride*1+4], math.Float32bits(neCorner))
1872 bo.PutUint32(data[vertStride*2:vertStride*2+4], math.Float32bits(swCorner))
1873 bo.PutUint32(data[vertStride*3:vertStride*3+4], math.Float32bits(seCorner))
1874 }
1875
1876 const (
1877 nwCorner = float32(1*0.5 + 0*0.25)
1878 neCorner = float32(1*0.5 + 1*0.25)
1879 swCorner = float32(0*0.5 + 0*0.25)
1880 seCorner = float32(0*0.5 + 1*0.25)
1881 )
1882
1883 type quadSplitter struct {
1884 bounds Rectangle
1885 contour uint32
1886 d *drawOps
1887 scratch []QuadSegment
1888 }
1889
1890 func (qs *quadSplitter) encodeQuadTo(from, ctrl, to Point) {
1891 data := qs.d.writeVertCache(vertStride * 4)
1892 encodeQuadTo(data, qs.contour, from, ctrl, to)
1893 }
1894
1895 func (qs *quadSplitter) splitAndEncode(quad QuadSegment) {
1896 cbnd := Rectangle{Min: quad.From, Max: quad.To}.Canon()
1897 from, ctrl, to := quad.From, quad.Ctrl, quad.To
1898 v0 := ctrl.Sub(from)
1899 v1 := to.Sub(ctrl)
1900 d := v0.X - v1.X
1901 if v0.X > 0 && d > v0.X || v0.X < 0 && d < v0.X {
1902 t := v0.X / d
1903 ctrl0 := from.Mul(1 - t).Add(ctrl.Mul(t))
1904 ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t))
1905 mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t))
1906 qs.encodeQuadTo(from, ctrl0, mid)
1907 qs.encodeQuadTo(mid, ctrl1, to)
1908 if mid.X > cbnd.Max.X {
1909 cbnd.Max.X = mid.X
1910 }
1911 if mid.X < cbnd.Min.X {
1912 cbnd.Min.X = mid.X
1913 }
1914 } else {
1915 qs.encodeQuadTo(from, ctrl, to)
1916 }
1917 d = v0.Y - v1.Y
1918 if v0.Y > 0 && d > v0.Y || v0.Y < 0 && d < v0.Y {
1919 t := v0.Y / d
1920 y := (1-t)*(1-t)*from.Y + 2*(1-t)*t*ctrl.Y + t*t*to.Y
1921 if y > cbnd.Max.Y {
1922 cbnd.Max.Y = y
1923 }
1924 if y < cbnd.Min.Y {
1925 cbnd.Min.Y = y
1926 }
1927 }
1928 qs.bounds = unionRect(qs.bounds, cbnd)
1929 }
1930
1931 func unionRect(r, s Rectangle) Rectangle {
1932 if r.Min.X > s.Min.X {
1933 r.Min.X = s.Min.X
1934 }
1935 if r.Min.Y > s.Min.Y {
1936 r.Min.Y = s.Min.Y
1937 }
1938 if r.Max.X < s.Max.X {
1939 r.Max.X = s.Max.X
1940 }
1941 if r.Max.Y < s.Max.Y {
1942 r.Max.Y = s.Max.Y
1943 }
1944 return r
1945 }
1946
1947 // --- Caches (from caches.go) ---
1948
1949 type textureCacheKey struct {
1950 filter byte
1951 handle any
1952 }
1953
1954 type textureCache struct {
1955 res map[textureCacheKey]resourceCacheValue
1956 }
1957
1958 type resourceCacheValue struct {
1959 used bool
1960 resource gpuResource
1961 }
1962
1963 type opCache struct {
1964 index map[opKey]int
1965 freelist []int
1966 cache []opCacheValue
1967 }
1968
1969 type opCacheValue struct {
1970 data pathData
1971 bounds Rectangle
1972 key opKey
1973 keep bool
1974 }
1975
1976 func newTextureCache() *textureCache {
1977 return &textureCache{res: map[textureCacheKey]resourceCacheValue{}}
1978 }
1979
1980 func (r *textureCache) get(key textureCacheKey) (gpuResource, bool) {
1981 v, exists := r.res[key]
1982 if !exists {
1983 return nil, false
1984 }
1985 if !v.used {
1986 v.used = true
1987 r.res[key] = v
1988 }
1989 return v.resource, exists
1990 }
1991
1992 func (r *textureCache) put(key textureCacheKey, val gpuResource) {
1993 v, exists := r.res[key]
1994 if exists && v.used {
1995 panic(errorf("key exists, %v", key))
1996 }
1997 v.used = true
1998 v.resource = val
1999 r.res[key] = v
2000 }
2001
2002 func (r *textureCache) frame() {
2003 for k, v := range r.res {
2004 if v.used {
2005 v.used = false
2006 r.res[k] = v
2007 } else {
2008 delete(r.res, k)
2009 v.resource.release()
2010 }
2011 }
2012 }
2013
2014 func (r *textureCache) release() {
2015 for _, v := range r.res {
2016 v.resource.release()
2017 }
2018 r.res = nil
2019 }
2020
2021 func newOpCache() *opCache {
2022 return &opCache{
2023 index: map[opKey]int{},
2024 freelist: []int{},
2025 cache: []opCacheValue{},
2026 }
2027 }
2028
2029 func (r *opCache) get(key opKey) (o opCacheValue, exist bool) {
2030 v := r.index[key]
2031 if v == 0 {
2032 return
2033 }
2034 r.cache[v-1].keep = true
2035 return r.cache[v-1], true
2036 }
2037
2038 func (r *opCache) put(key opKey, val opCacheValue) {
2039 v := r.index[key]
2040 val.keep = true
2041 val.key = key
2042 if v == 0 {
2043 i := len(r.cache)
2044 if len(r.freelist) > 0 {
2045 i = r.freelist[len(r.freelist)-1]
2046 r.freelist = r.freelist[:len(r.freelist)-1]
2047 r.cache[i] = val
2048 } else {
2049 r.cache = append(r.cache, val)
2050 }
2051 r.index[key] = i + 1
2052 } else {
2053 r.cache[v-1] = val
2054 }
2055 }
2056
2057 func (r *opCache) frame() {
2058 r.freelist = r.freelist[:0]
2059 for i, v := range r.cache {
2060 r.cache[i].keep = false
2061 if v.keep {
2062 continue
2063 }
2064 if v.data.data != nil {
2065 v.data.release()
2066 r.cache[i].data.data = nil
2067 }
2068 delete(r.index, v.key)
2069 r.freelist = append(r.freelist, i)
2070 }
2071 }
2072
2073 func (r *opCache) release() {
2074 for i := range r.cache {
2075 r.cache[i].keep = false
2076 }
2077 r.frame()
2078 r.index = nil
2079 r.freelist = nil
2080 r.cache = nil
2081 }
2082
2083 // --- Packer (from pack.go) ---
2084
2085 type packer struct {
2086 maxDims image.Point
2087 spaces []image.Rectangle
2088 sizes []image.Point
2089 pos image.Point
2090 }
2091
2092 type placement struct {
2093 Idx int
2094 Pos image.Point
2095 }
2096
2097 func (p *packer) add(s image.Point) (placement, bool) {
2098 if place, ok := p.tryAdd(s); ok {
2099 return place, true
2100 }
2101 p.newPage()
2102 return p.tryAdd(s)
2103 }
2104
2105 func (p *packer) clear() {
2106 p.sizes = p.sizes[:0]
2107 p.spaces = p.spaces[:0]
2108 }
2109
2110 func (p *packer) newPage() {
2111 p.pos = image.Point{}
2112 p.sizes = append(p.sizes, image.Point{})
2113 p.spaces = p.spaces[:0]
2114 p.spaces = append(p.spaces, image.Rectangle{
2115 Max: image.Point{X: 1e6, Y: 1e6},
2116 })
2117 }
2118
2119 func (p *packer) tryAdd(s image.Point) (placement, bool) {
2120 if len(p.spaces) == 0 || len(p.sizes) == 0 {
2121 return placement{}, false
2122 }
2123 var (
2124 bestIdx *image.Rectangle
2125 bestSize = p.maxDims
2126 lastSize = p.sizes[len(p.sizes)-1]
2127 )
2128 for i := range p.spaces {
2129 space := &p.spaces[i]
2130 rightSpace := space.Dx() - s.X
2131 bottomSpace := space.Dy() - s.Y
2132 if rightSpace < 0 || bottomSpace < 0 {
2133 continue
2134 }
2135 size := lastSize
2136 if x := space.Min.X + s.X; x > size.X {
2137 if x > p.maxDims.X {
2138 continue
2139 }
2140 size.X = x
2141 }
2142 if y := space.Min.Y + s.Y; y > size.Y {
2143 if y > p.maxDims.Y {
2144 continue
2145 }
2146 size.Y = y
2147 }
2148 if size.X*size.Y < bestSize.X*bestSize.Y {
2149 bestIdx = space
2150 bestSize = size
2151 }
2152 }
2153 if bestIdx == nil {
2154 return placement{}, false
2155 }
2156 bestSpace := *bestIdx
2157 *bestIdx = p.spaces[len(p.spaces)-1]
2158 p.spaces = p.spaces[:len(p.spaces)-1]
2159 pos := bestSpace.Min
2160 if rem := bestSpace.Dy() - s.Y; rem > 0 {
2161 p.spaces = append(p.spaces, image.Rectangle{
2162 Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
2163 Max: image.Point{X: bestSpace.Max.X, Y: bestSpace.Max.Y},
2164 })
2165 }
2166 if rem := bestSpace.Dx() - s.X; rem > 0 {
2167 p.spaces = append(p.spaces, image.Rectangle{
2168 Min: image.Point{X: pos.X + s.X, Y: pos.Y},
2169 Max: image.Point{X: bestSpace.Max.X, Y: pos.Y + s.Y},
2170 })
2171 }
2172 idx := len(p.sizes) - 1
2173 p.sizes[idx] = bestSize
2174 return placement{Idx: idx, Pos: pos}, true
2175 }
2176
2177 // errorf creates a formatted error without importing fmt (it's already imported).
2178 func errorf(format string, args ...any) error {
2179 return fmt.Errorf(format, args...)
2180 }
2181