gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/gpu/gpu.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 /* 4 Package gpu implements the rendering of Gio drawing operations. It 5 is used by package app and package app/headless and is otherwise not 6 useful except for integrating with external window implementations. 7 */ 8 package gpu 9 10 import ( 11 "encoding/binary" 12 "fmt" 13 "image" 14 "image/color" 15 "math" 16 "os" 17 "reflect" 18 "time" 19 "unsafe" 20 21 "gioui.org/gpu/internal/driver" 22 "gioui.org/internal/byteslice" 23 "gioui.org/internal/f32" 24 "gioui.org/internal/f32color" 25 "gioui.org/internal/ops" 26 "gioui.org/internal/scene" 27 "gioui.org/internal/stroke" 28 "gioui.org/layout" 29 "gioui.org/op" 30 "gioui.org/shader" 31 "gioui.org/shader/gio" 32 33 // Register backends. 34 _ "gioui.org/gpu/internal/d3d11" 35 _ "gioui.org/gpu/internal/metal" 36 _ "gioui.org/gpu/internal/opengl" 37 _ "gioui.org/gpu/internal/vulkan" 38 ) 39 40 type GPU interface { 41 // Release non-Go resources. The GPU is no longer valid after Release. 42 Release() 43 // Clear sets the clear color for the next Frame. 44 Clear(color color.NRGBA) 45 // Frame draws the graphics operations from op into a viewport of target. 46 Frame(frame *op.Ops, target RenderTarget, viewport image.Point) error 47 } 48 49 type gpu struct { 50 cache *textureCache 51 52 profile string 53 timers *timers 54 frameStart time.Time 55 stencilTimer, coverTimer, cleanupTimer *timer 56 drawOps drawOps 57 ctx driver.Device 58 renderer *renderer 59 } 60 61 type renderer struct { 62 ctx driver.Device 63 blitter *blitter 64 pather *pather 65 packer packer 66 intersections packer 67 layers packer 68 layerFBOs fboSet 69 } 70 71 type drawOps struct { 72 reader ops.Reader 73 states []f32.Affine2D 74 transStack []f32.Affine2D 75 layers []opacityLayer 76 opacityStack []int 77 vertCache []byte 78 viewport image.Point 79 clear bool 80 clearColor f32color.RGBA 81 imageOps []imageOp 82 pathOps []*pathOp 83 pathOpCache []pathOp 84 qs quadSplitter 85 pathCache *opCache 86 } 87 88 type opacityLayer struct { 89 opacity float32 90 parent int 91 // depth of the opacity stack. Layers of equal depth are 92 // independent and may be packed into one atlas. 93 depth int 94 // opStart and opEnd denote the range of drawOps.imageOps 95 // that belong to the layer. 96 opStart, opEnd int 97 // clip of the layer operations. 98 clip image.Rectangle 99 place placement 100 } 101 102 type drawState struct { 103 t f32.Affine2D 104 cpath *pathOp 105 106 matType materialType 107 // Current paint.ImageOp 108 image imageOpData 109 // Current paint.ColorOp, if any. 110 color color.NRGBA 111 112 // Current paint.LinearGradientOp. 113 stop1 f32.Point 114 stop2 f32.Point 115 color1 color.NRGBA 116 color2 color.NRGBA 117 } 118 119 type pathOp struct { 120 off f32.Point 121 // rect tracks whether the clip stack can be represented by a 122 // pixel-aligned rectangle. 123 rect bool 124 // clip is the union of all 125 // later clip rectangles. 126 clip image.Rectangle 127 bounds f32.Rectangle 128 // intersect is the intersection of bounds and all 129 // previous clip bounds. 130 intersect f32.Rectangle 131 pathKey opKey 132 path bool 133 pathVerts []byte 134 parent *pathOp 135 place placement 136 } 137 138 type imageOp struct { 139 path *pathOp 140 clip image.Rectangle 141 material material 142 clipType clipType 143 // place is either a placement in the path fbos or intersection fbos, 144 // depending on clipType. 145 place placement 146 // layerOps is the number of operations this 147 // operation replaces. 148 layerOps int 149 } 150 151 func decodeStrokeOp(data []byte) float32 { 152 _ = data[4] 153 bo := binary.LittleEndian 154 return math.Float32frombits(bo.Uint32(data[1:])) 155 } 156 157 type quadsOp struct { 158 key opKey 159 aux []byte 160 } 161 162 type opKey struct { 163 outline bool 164 strokeWidth float32 165 sx, hx, sy, hy float32 166 ops.Key 167 } 168 169 type material struct { 170 material materialType 171 opaque bool 172 // For materialTypeColor. 173 color f32color.RGBA 174 // For materialTypeLinearGradient. 175 color1 f32color.RGBA 176 color2 f32color.RGBA 177 opacity float32 178 // For materialTypeTexture. 179 data imageOpData 180 tex driver.Texture 181 uvTrans f32.Affine2D 182 } 183 184 const ( 185 filterLinear = 0 186 filterNearest = 1 187 ) 188 189 // imageOpData is the shadow of paint.ImageOp. 190 type imageOpData struct { 191 src *image.RGBA 192 handle interface{} 193 filter byte 194 } 195 196 type linearGradientOpData struct { 197 stop1 f32.Point 198 color1 color.NRGBA 199 stop2 f32.Point 200 color2 color.NRGBA 201 } 202 203 func decodeImageOp(data []byte, refs []interface{}) imageOpData { 204 handle := refs[1] 205 if handle == nil { 206 return imageOpData{} 207 } 208 return imageOpData{ 209 src: refs[0].(*image.RGBA), 210 handle: handle, 211 filter: data[1], 212 } 213 } 214 215 func decodeColorOp(data []byte) color.NRGBA { 216 data = data[:ops.TypeColorLen] 217 return color.NRGBA{ 218 R: data[1], 219 G: data[2], 220 B: data[3], 221 A: data[4], 222 } 223 } 224 225 func decodeLinearGradientOp(data []byte) linearGradientOpData { 226 data = data[:ops.TypeLinearGradientLen] 227 bo := binary.LittleEndian 228 return linearGradientOpData{ 229 stop1: f32.Point{ 230 X: math.Float32frombits(bo.Uint32(data[1:])), 231 Y: math.Float32frombits(bo.Uint32(data[5:])), 232 }, 233 stop2: f32.Point{ 234 X: math.Float32frombits(bo.Uint32(data[9:])), 235 Y: math.Float32frombits(bo.Uint32(data[13:])), 236 }, 237 color1: color.NRGBA{ 238 R: data[17+0], 239 G: data[17+1], 240 B: data[17+2], 241 A: data[17+3], 242 }, 243 color2: color.NRGBA{ 244 R: data[21+0], 245 G: data[21+1], 246 B: data[21+2], 247 A: data[21+3], 248 }, 249 } 250 } 251 252 type resource interface { 253 release() 254 } 255 256 type texture struct { 257 src *image.RGBA 258 tex driver.Texture 259 } 260 261 type blitter struct { 262 ctx driver.Device 263 viewport image.Point 264 pipelines [2][3]*pipeline 265 colUniforms *blitColUniforms 266 texUniforms *blitTexUniforms 267 linearGradientUniforms *blitLinearGradientUniforms 268 quadVerts driver.Buffer 269 } 270 271 type blitColUniforms struct { 272 blitUniforms 273 _ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes. 274 colorUniforms 275 } 276 277 type blitTexUniforms struct { 278 blitUniforms 279 } 280 281 type blitLinearGradientUniforms struct { 282 blitUniforms 283 _ [128 - unsafe.Sizeof(blitUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128 bytes. 284 gradientUniforms 285 } 286 287 type uniformBuffer struct { 288 buf driver.Buffer 289 ptr []byte 290 } 291 292 type pipeline struct { 293 pipeline driver.Pipeline 294 uniforms *uniformBuffer 295 } 296 297 type blitUniforms struct { 298 transform [4]float32 299 uvTransformR1 [4]float32 300 uvTransformR2 [4]float32 301 opacity float32 302 fbo float32 303 _ [2]float32 304 } 305 306 type colorUniforms struct { 307 color f32color.RGBA 308 } 309 310 type gradientUniforms struct { 311 color1 f32color.RGBA 312 color2 f32color.RGBA 313 } 314 315 type clipType uint8 316 317 const ( 318 clipTypeNone clipType = iota 319 clipTypePath 320 clipTypeIntersection 321 ) 322 323 type materialType uint8 324 325 const ( 326 materialColor materialType = iota 327 materialLinearGradient 328 materialTexture 329 ) 330 331 // New creates a GPU for the given API. 332 func New(api API) (GPU, error) { 333 d, err := driver.NewDevice(api) 334 if err != nil { 335 return nil, err 336 } 337 return NewWithDevice(d) 338 } 339 340 // NewWithDevice creates a GPU with a pre-existing device. 341 // 342 // Note: for internal use only. 343 func NewWithDevice(d driver.Device) (GPU, error) { 344 d.BeginFrame(nil, false, image.Point{}) 345 defer d.EndFrame() 346 forceCompute := os.Getenv("GIORENDERER") == "forcecompute" 347 feats := d.Caps().Features 348 switch { 349 case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets) && feats.Has(driver.FeatureSRGB): 350 return newGPU(d) 351 } 352 return newCompute(d) 353 } 354 355 func newGPU(ctx driver.Device) (*gpu, error) { 356 g := &gpu{ 357 cache: newTextureCache(), 358 } 359 g.drawOps.pathCache = newOpCache() 360 if err := g.init(ctx); err != nil { 361 return nil, err 362 } 363 return g, nil 364 } 365 366 func (g *gpu) init(ctx driver.Device) error { 367 g.ctx = ctx 368 g.renderer = newRenderer(ctx) 369 return nil 370 } 371 372 func (g *gpu) Clear(col color.NRGBA) { 373 g.drawOps.clear = true 374 g.drawOps.clearColor = f32color.LinearFromSRGB(col) 375 } 376 377 func (g *gpu) Release() { 378 g.renderer.release() 379 g.drawOps.pathCache.release() 380 g.cache.release() 381 if g.timers != nil { 382 g.timers.Release() 383 } 384 g.ctx.Release() 385 } 386 387 func (g *gpu) Frame(frameOps *op.Ops, target RenderTarget, viewport image.Point) error { 388 g.collect(viewport, frameOps) 389 return g.frame(target) 390 } 391 392 func (g *gpu) collect(viewport image.Point, frameOps *op.Ops) { 393 g.renderer.blitter.viewport = viewport 394 g.renderer.pather.viewport = viewport 395 g.drawOps.reset(viewport) 396 g.drawOps.collect(frameOps, viewport) 397 if false && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) { 398 g.frameStart = time.Now() 399 g.timers = newTimers(g.ctx) 400 g.stencilTimer = g.timers.newTimer() 401 g.coverTimer = g.timers.newTimer() 402 g.cleanupTimer = g.timers.newTimer() 403 } 404 } 405 406 func (g *gpu) frame(target RenderTarget) error { 407 viewport := g.renderer.blitter.viewport 408 defFBO := g.ctx.BeginFrame(target, g.drawOps.clear, viewport) 409 defer g.ctx.EndFrame() 410 g.drawOps.buildPaths(g.ctx) 411 for _, img := range g.drawOps.imageOps { 412 expandPathOp(img.path, img.clip) 413 } 414 g.stencilTimer.begin() 415 g.renderer.packStencils(&g.drawOps.pathOps) 416 g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps) 417 g.renderer.packIntersections(g.drawOps.imageOps) 418 g.renderer.prepareIntersections(g.drawOps.imageOps) 419 g.renderer.intersect(g.drawOps.imageOps) 420 g.stencilTimer.end() 421 g.coverTimer.begin() 422 g.renderer.uploadImages(g.cache, g.drawOps.imageOps) 423 g.renderer.prepareDrawOps(g.drawOps.imageOps) 424 g.drawOps.layers = g.renderer.packLayers(g.drawOps.layers) 425 g.renderer.drawLayers(g.drawOps.layers, g.drawOps.imageOps) 426 d := driver.LoadDesc{ 427 ClearColor: g.drawOps.clearColor, 428 } 429 if g.drawOps.clear { 430 g.drawOps.clear = false 431 d.Action = driver.LoadActionClear 432 } 433 g.ctx.BeginRenderPass(defFBO, d) 434 g.ctx.Viewport(0, 0, viewport.X, viewport.Y) 435 g.renderer.drawOps(false, image.Point{}, g.renderer.blitter.viewport, g.drawOps.imageOps) 436 g.coverTimer.end() 437 g.ctx.EndRenderPass() 438 g.cleanupTimer.begin() 439 g.cache.frame() 440 g.drawOps.pathCache.frame() 441 g.cleanupTimer.end() 442 if false && g.timers.ready() { 443 st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed 444 ft := st + covt + cleant 445 q := 100 * time.Microsecond 446 st, covt = st.Round(q), covt.Round(q) 447 frameDur := time.Since(g.frameStart).Round(q) 448 ft = ft.Round(q) 449 g.profile = fmt.Sprintf("draw:%7s gpu:%7s st:%7s cov:%7s", frameDur, ft, st, covt) 450 } 451 return nil 452 } 453 454 func (g *gpu) Profile() string { 455 return g.profile 456 } 457 458 func (r *renderer) texHandle(cache *textureCache, data imageOpData) driver.Texture { 459 key := textureCacheKey{ 460 filter: data.filter, 461 handle: data.handle, 462 } 463 464 var tex *texture 465 t, exists := cache.get(key) 466 if !exists { 467 t = &texture{ 468 src: data.src, 469 } 470 cache.put(key, t) 471 } 472 tex = t.(*texture) 473 if tex.tex != nil { 474 return tex.tex 475 } 476 477 var minFilter, magFilter driver.TextureFilter 478 switch data.filter { 479 case filterLinear: 480 minFilter, magFilter = driver.FilterLinearMipmapLinear, driver.FilterLinear 481 case filterNearest: 482 minFilter, magFilter = driver.FilterNearest, driver.FilterNearest 483 } 484 485 handle, err := r.ctx.NewTexture(driver.TextureFormatSRGBA, 486 data.src.Bounds().Dx(), data.src.Bounds().Dy(), 487 minFilter, magFilter, 488 driver.BufferBindingTexture, 489 ) 490 if err != nil { 491 panic(err) 492 } 493 driver.UploadImage(handle, image.Pt(0, 0), data.src) 494 tex.tex = handle 495 return tex.tex 496 } 497 498 func (t *texture) release() { 499 if t.tex != nil { 500 t.tex.Release() 501 } 502 } 503 504 func newRenderer(ctx driver.Device) *renderer { 505 r := &renderer{ 506 ctx: ctx, 507 blitter: newBlitter(ctx), 508 pather: newPather(ctx), 509 } 510 511 maxDim := ctx.Caps().MaxTextureSize 512 // Large atlas textures cause artifacts due to precision loss in 513 // shaders. 514 if cap := 8192; maxDim > cap { 515 maxDim = cap 516 } 517 d := image.Pt(maxDim, maxDim) 518 519 r.packer.maxDims = d 520 r.intersections.maxDims = d 521 r.layers.maxDims = d 522 return r 523 } 524 525 func (r *renderer) release() { 526 r.pather.release() 527 r.blitter.release() 528 r.layerFBOs.delete(r.ctx, 0) 529 } 530 531 func newBlitter(ctx driver.Device) *blitter { 532 quadVerts, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, 533 byteslice.Slice([]float32{ 534 -1, -1, 0, 0, 535 +1, -1, 1, 0, 536 -1, +1, 0, 1, 537 +1, +1, 1, 1, 538 }), 539 ) 540 if err != nil { 541 panic(err) 542 } 543 b := &blitter{ 544 ctx: ctx, 545 quadVerts: quadVerts, 546 } 547 b.colUniforms = new(blitColUniforms) 548 b.texUniforms = new(blitTexUniforms) 549 b.linearGradientUniforms = new(blitLinearGradientUniforms) 550 pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag, 551 [3]interface{}{b.colUniforms, b.linearGradientUniforms, b.texUniforms}, 552 ) 553 if err != nil { 554 panic(err) 555 } 556 b.pipelines = pipelines 557 return b 558 } 559 560 func (b *blitter) release() { 561 b.quadVerts.Release() 562 for _, p := range b.pipelines { 563 for _, p := range p { 564 p.Release() 565 } 566 } 567 } 568 569 func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) (pipelines [2][3]*pipeline, err error) { 570 defer func() { 571 if err != nil { 572 for _, p := range pipelines { 573 for _, p := range p { 574 if p != nil { 575 p.Release() 576 } 577 } 578 } 579 } 580 }() 581 blend := driver.BlendDesc{ 582 Enable: true, 583 SrcFactor: driver.BlendFactorOne, 584 DstFactor: driver.BlendFactorOneMinusSrcAlpha, 585 } 586 layout := driver.VertexLayout{ 587 Inputs: []driver.InputDesc{ 588 {Type: shader.DataTypeFloat, Size: 2, Offset: 0}, 589 {Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2}, 590 }, 591 Stride: 4 * 4, 592 } 593 vsh, err := b.NewVertexShader(vsSrc) 594 if err != nil { 595 return pipelines, err 596 } 597 defer vsh.Release() 598 for i, format := range []driver.TextureFormat{driver.TextureFormatOutput, driver.TextureFormatSRGBA} { 599 { 600 fsh, err := b.NewFragmentShader(fsSrc[materialTexture]) 601 if err != nil { 602 return pipelines, err 603 } 604 defer fsh.Release() 605 pipe, err := b.NewPipeline(driver.PipelineDesc{ 606 VertexShader: vsh, 607 FragmentShader: fsh, 608 BlendDesc: blend, 609 VertexLayout: layout, 610 PixelFormat: format, 611 Topology: driver.TopologyTriangleStrip, 612 }) 613 if err != nil { 614 return pipelines, err 615 } 616 var vertBuffer *uniformBuffer 617 if u := uniforms[materialTexture]; u != nil { 618 vertBuffer = newUniformBuffer(b, u) 619 } 620 pipelines[i][materialTexture] = &pipeline{pipe, vertBuffer} 621 } 622 { 623 var vertBuffer *uniformBuffer 624 fsh, err := b.NewFragmentShader(fsSrc[materialColor]) 625 if err != nil { 626 return pipelines, err 627 } 628 defer fsh.Release() 629 pipe, err := b.NewPipeline(driver.PipelineDesc{ 630 VertexShader: vsh, 631 FragmentShader: fsh, 632 BlendDesc: blend, 633 VertexLayout: layout, 634 PixelFormat: format, 635 Topology: driver.TopologyTriangleStrip, 636 }) 637 if err != nil { 638 return pipelines, err 639 } 640 if u := uniforms[materialColor]; u != nil { 641 vertBuffer = newUniformBuffer(b, u) 642 } 643 pipelines[i][materialColor] = &pipeline{pipe, vertBuffer} 644 } 645 { 646 var vertBuffer *uniformBuffer 647 fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient]) 648 if err != nil { 649 return pipelines, err 650 } 651 defer fsh.Release() 652 pipe, err := b.NewPipeline(driver.PipelineDesc{ 653 VertexShader: vsh, 654 FragmentShader: fsh, 655 BlendDesc: blend, 656 VertexLayout: layout, 657 PixelFormat: format, 658 Topology: driver.TopologyTriangleStrip, 659 }) 660 if err != nil { 661 return pipelines, err 662 } 663 if u := uniforms[materialLinearGradient]; u != nil { 664 vertBuffer = newUniformBuffer(b, u) 665 } 666 pipelines[i][materialLinearGradient] = &pipeline{pipe, vertBuffer} 667 } 668 } 669 return pipelines, nil 670 } 671 672 func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) { 673 if len(r.packer.sizes) == 0 { 674 return 675 } 676 fbo := -1 677 r.pather.begin(r.packer.sizes) 678 for _, p := range ops { 679 if fbo != p.place.Idx { 680 if fbo != -1 { 681 r.ctx.EndRenderPass() 682 } 683 fbo = p.place.Idx 684 f := r.pather.stenciler.cover(fbo) 685 r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear}) 686 r.ctx.BindPipeline(r.pather.stenciler.pipeline.pipeline.pipeline) 687 r.ctx.BindIndexBuffer(r.pather.stenciler.indexBuf) 688 } 689 v, _ := pathCache.get(p.pathKey) 690 r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data) 691 } 692 if fbo != -1 { 693 r.ctx.EndRenderPass() 694 } 695 } 696 697 func (r *renderer) prepareIntersections(ops []imageOp) { 698 for _, img := range ops { 699 if img.clipType != clipTypeIntersection { 700 continue 701 } 702 fbo := r.pather.stenciler.cover(img.path.place.Idx) 703 r.ctx.PrepareTexture(fbo.tex) 704 } 705 } 706 707 func (r *renderer) intersect(ops []imageOp) { 708 if len(r.intersections.sizes) == 0 { 709 return 710 } 711 fbo := -1 712 r.pather.stenciler.beginIntersect(r.intersections.sizes) 713 for _, img := range ops { 714 if img.clipType != clipTypeIntersection { 715 continue 716 } 717 if fbo != img.place.Idx { 718 if fbo != -1 { 719 r.ctx.EndRenderPass() 720 } 721 fbo = img.place.Idx 722 f := r.pather.stenciler.intersections.fbos[fbo] 723 d := driver.LoadDesc{Action: driver.LoadActionClear} 724 d.ClearColor.R = 1.0 725 r.ctx.BeginRenderPass(f.tex, d) 726 r.ctx.BindPipeline(r.pather.stenciler.ipipeline.pipeline.pipeline) 727 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) 728 } 729 r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy()) 730 r.intersectPath(img.path, img.clip) 731 } 732 if fbo != -1 { 733 r.ctx.EndRenderPass() 734 } 735 } 736 737 func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) { 738 if p.parent != nil { 739 r.intersectPath(p.parent, clip) 740 } 741 if !p.path { 742 return 743 } 744 uv := image.Rectangle{ 745 Min: p.place.Pos, 746 Max: p.place.Pos.Add(p.clip.Size()), 747 } 748 o := clip.Min.Sub(p.clip.Min) 749 sub := image.Rectangle{ 750 Min: o, 751 Max: o.Add(clip.Size()), 752 } 753 fbo := r.pather.stenciler.cover(p.place.Idx) 754 r.ctx.BindTexture(0, fbo.tex) 755 coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size) 756 subScale, subOff := texSpaceTransform(f32.FRect(sub), p.clip.Size()) 757 r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y} 758 r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y} 759 r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx) 760 r.ctx.DrawArrays(0, 4) 761 } 762 763 func (r *renderer) packIntersections(ops []imageOp) { 764 r.intersections.clear() 765 for i, img := range ops { 766 var npaths int 767 var onePath *pathOp 768 for p := img.path; p != nil; p = p.parent { 769 if p.path { 770 onePath = p 771 npaths++ 772 } 773 } 774 switch npaths { 775 case 0: 776 case 1: 777 place := onePath.place 778 place.Pos = place.Pos.Sub(onePath.clip.Min).Add(img.clip.Min) 779 ops[i].place = place 780 ops[i].clipType = clipTypePath 781 default: 782 sz := image.Point{X: img.clip.Dx(), Y: img.clip.Dy()} 783 place, ok := r.intersections.add(sz) 784 if !ok { 785 panic("internal error: if the intersection fit, the intersection should fit as well") 786 } 787 ops[i].clipType = clipTypeIntersection 788 ops[i].place = place 789 } 790 } 791 } 792 793 func (r *renderer) packStencils(pops *[]*pathOp) { 794 r.packer.clear() 795 ops := *pops 796 // Allocate atlas space for cover textures. 797 var i int 798 for i < len(ops) { 799 p := ops[i] 800 if p.clip.Empty() { 801 ops[i] = ops[len(ops)-1] 802 ops = ops[:len(ops)-1] 803 continue 804 } 805 place, ok := r.packer.add(p.clip.Size()) 806 if !ok { 807 // The clip area is at most the entire screen. Hopefully no 808 // screen is larger than GL_MAX_TEXTURE_SIZE. 809 panic(fmt.Errorf("clip area %v is larger than maximum texture size %v", p.clip, r.packer.maxDims)) 810 } 811 p.place = place 812 i++ 813 } 814 *pops = ops 815 } 816 817 func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer { 818 // Make every layer bounds contain nested layers; cull empty layers. 819 for i := len(layers) - 1; i >= 0; i-- { 820 l := layers[i] 821 if l.parent != -1 { 822 b := layers[l.parent].clip 823 layers[l.parent].clip = b.Union(l.clip) 824 } 825 if l.clip.Empty() { 826 layers = append(layers[:i], layers[i+1:]...) 827 } 828 } 829 // Pack layers. 830 r.layers.clear() 831 depth := 0 832 for i := range layers { 833 l := &layers[i] 834 // Only layers of the same depth may be packed together. 835 if l.depth != depth { 836 r.layers.newPage() 837 } 838 place, ok := r.layers.add(l.clip.Size()) 839 if !ok { 840 // The layer area is at most the entire screen. Hopefully no 841 // screen is larger than GL_MAX_TEXTURE_SIZE. 842 panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims)) 843 } 844 l.place = place 845 } 846 return layers 847 } 848 849 func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) { 850 if len(r.layers.sizes) == 0 { 851 return 852 } 853 fbo := -1 854 r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes) 855 for i := len(layers) - 1; i >= 0; i-- { 856 l := layers[i] 857 if fbo != l.place.Idx { 858 if fbo != -1 { 859 r.ctx.EndRenderPass() 860 r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex) 861 } 862 fbo = l.place.Idx 863 f := r.layerFBOs.fbos[fbo] 864 r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear}) 865 } 866 v := image.Rectangle{ 867 Min: l.place.Pos, 868 Max: l.place.Pos.Add(l.clip.Size()), 869 } 870 r.ctx.Viewport(v.Min.X, v.Min.Y, v.Dx(), v.Dy()) 871 f := r.layerFBOs.fbos[fbo] 872 r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd]) 873 sr := f32.FRect(v) 874 uvScale, uvOffset := texSpaceTransform(sr, f.size) 875 uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset) 876 // Replace layer ops with one textured op. 877 ops[l.opStart] = imageOp{ 878 clip: l.clip, 879 material: material{ 880 material: materialTexture, 881 tex: f.tex, 882 uvTrans: uvTrans, 883 opacity: l.opacity, 884 }, 885 layerOps: l.opEnd - l.opStart - 1, 886 } 887 } 888 if fbo != -1 { 889 r.ctx.EndRenderPass() 890 r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex) 891 } 892 } 893 894 func (d *drawOps) reset(viewport image.Point) { 895 d.viewport = viewport 896 d.imageOps = d.imageOps[:0] 897 d.pathOps = d.pathOps[:0] 898 d.pathOpCache = d.pathOpCache[:0] 899 d.vertCache = d.vertCache[:0] 900 d.transStack = d.transStack[:0] 901 d.layers = d.layers[:0] 902 d.opacityStack = d.opacityStack[:0] 903 } 904 905 func (d *drawOps) collect(root *op.Ops, viewport image.Point) { 906 viewf := f32.Rectangle{ 907 Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)}, 908 } 909 var ops *ops.Ops 910 if root != nil { 911 ops = &root.Internal 912 } 913 d.reader.Reset(ops) 914 d.collectOps(&d.reader, viewf) 915 } 916 917 func (d *drawOps) buildPaths(ctx driver.Device) { 918 for _, p := range d.pathOps { 919 if v, exists := d.pathCache.get(p.pathKey); !exists || v.data.data == nil { 920 data := buildPath(ctx, p.pathVerts) 921 d.pathCache.put(p.pathKey, opCacheValue{ 922 data: data, 923 bounds: p.bounds, 924 }) 925 } 926 p.pathVerts = nil 927 } 928 } 929 930 func (d *drawOps) newPathOp() *pathOp { 931 d.pathOpCache = append(d.pathOpCache, pathOp{}) 932 return &d.pathOpCache[len(d.pathOpCache)-1] 933 } 934 935 func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point) { 936 npath := d.newPathOp() 937 *npath = pathOp{ 938 parent: state.cpath, 939 bounds: bounds, 940 off: off, 941 intersect: bounds.Add(off), 942 rect: true, 943 } 944 if npath.parent != nil { 945 npath.rect = npath.parent.rect 946 npath.intersect = npath.parent.intersect.Intersect(npath.intersect) 947 } 948 if len(aux) > 0 { 949 npath.rect = false 950 npath.pathKey = auxKey 951 npath.path = true 952 npath.pathVerts = aux 953 d.pathOps = append(d.pathOps, npath) 954 } 955 state.cpath = npath 956 } 957 958 func (d *drawOps) save(id int, state f32.Affine2D) { 959 if extra := id - len(d.states) + 1; extra > 0 { 960 d.states = append(d.states, make([]f32.Affine2D, extra)...) 961 } 962 d.states[id] = state 963 } 964 965 func (k opKey) SetTransform(t f32.Affine2D) opKey { 966 sx, hx, _, hy, sy, _ := t.Elems() 967 k.sx = sx 968 k.hx = hx 969 k.hy = hy 970 k.sy = sy 971 return k 972 } 973 974 func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) { 975 var ( 976 quads quadsOp 977 state drawState 978 ) 979 reset := func() { 980 state = drawState{ 981 color: color.NRGBA{A: 0xff}, 982 } 983 } 984 reset() 985 loop: 986 for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { 987 switch ops.OpType(encOp.Data[0]) { 988 case ops.TypeTransform: 989 dop, push := ops.DecodeTransform(encOp.Data) 990 if push { 991 d.transStack = append(d.transStack, state.t) 992 } 993 state.t = state.t.Mul(dop) 994 case ops.TypePopTransform: 995 n := len(d.transStack) 996 state.t = d.transStack[n-1] 997 d.transStack = d.transStack[:n-1] 998 999 case ops.TypePushOpacity: 1000 opacity := ops.DecodeOpacity(encOp.Data) 1001 parent := -1 1002 depth := len(d.opacityStack) 1003 if depth > 0 { 1004 parent = d.opacityStack[depth-1] 1005 } 1006 lidx := len(d.layers) 1007 d.layers = append(d.layers, opacityLayer{ 1008 opacity: opacity, 1009 parent: parent, 1010 depth: depth, 1011 opStart: len(d.imageOps), 1012 }) 1013 d.opacityStack = append(d.opacityStack, lidx) 1014 case ops.TypePopOpacity: 1015 n := len(d.opacityStack) 1016 idx := d.opacityStack[n-1] 1017 d.layers[idx].opEnd = len(d.imageOps) 1018 d.opacityStack = d.opacityStack[:n-1] 1019 1020 case ops.TypeStroke: 1021 quads.key.strokeWidth = decodeStrokeOp(encOp.Data) 1022 1023 case ops.TypePath: 1024 encOp, ok = r.Decode() 1025 if !ok { 1026 break loop 1027 } 1028 quads.aux = encOp.Data[ops.TypeAuxLen:] 1029 quads.key.Key = encOp.Key 1030 1031 case ops.TypeClip: 1032 var op ops.ClipOp 1033 op.Decode(encOp.Data) 1034 quads.key.outline = op.Outline 1035 bounds := f32.FRect(op.Bounds) 1036 trans, off := state.t.Split() 1037 if len(quads.aux) > 0 { 1038 // There is a clipping path, build the gpu data and update the 1039 // cache key such that it will be equal only if the transform is the 1040 // same also. Use cached data if we have it. 1041 quads.key = quads.key.SetTransform(trans) 1042 if v, ok := d.pathCache.get(quads.key); ok { 1043 // Since the GPU data exists in the cache aux will not be used. 1044 // Why is this not used for the offset shapes? 1045 bounds = v.bounds 1046 } else { 1047 var pathData []byte 1048 pathData, bounds = d.buildVerts( 1049 quads.aux, trans, quads.key.outline, quads.key.strokeWidth, 1050 ) 1051 quads.aux = pathData 1052 // add it to the cache, without GPU data, so the transform can be 1053 // reused. 1054 d.pathCache.put(quads.key, opCacheValue{bounds: bounds}) 1055 } 1056 } else { 1057 quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans) 1058 quads.key = opKey{Key: encOp.Key} 1059 } 1060 d.addClipPath(&state, quads.aux, quads.key, bounds, off) 1061 quads = quadsOp{} 1062 case ops.TypePopClip: 1063 state.cpath = state.cpath.parent 1064 1065 case ops.TypeColor: 1066 state.matType = materialColor 1067 state.color = decodeColorOp(encOp.Data) 1068 case ops.TypeLinearGradient: 1069 state.matType = materialLinearGradient 1070 op := decodeLinearGradientOp(encOp.Data) 1071 state.stop1 = op.stop1 1072 state.stop2 = op.stop2 1073 state.color1 = op.color1 1074 state.color2 = op.color2 1075 case ops.TypeImage: 1076 state.matType = materialTexture 1077 state.image = decodeImageOp(encOp.Data, encOp.Refs) 1078 case ops.TypePaint: 1079 // Transform (if needed) the painting rectangle and if so generate a clip path, 1080 // for those cases also compute a partialTrans that maps texture coordinates between 1081 // the new bounding rectangle and the transformed original paint rectangle. 1082 t, off := state.t.Split() 1083 // Fill the clip area, unless the material is a (bounded) image. 1084 // TODO: Find a tighter bound. 1085 inf := float32(1e6) 1086 dst := f32.Rect(-inf, -inf, inf, inf) 1087 if state.matType == materialTexture { 1088 sz := state.image.src.Rect.Size() 1089 dst = f32.Rectangle{Max: layout.FPt(sz)} 1090 } 1091 clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t) 1092 cl := viewport.Intersect(bnd.Add(off)) 1093 if state.cpath != nil { 1094 cl = state.cpath.intersect.Intersect(cl) 1095 } 1096 if cl.Empty() { 1097 continue 1098 } 1099 1100 if clipData != nil { 1101 // The paint operation is sheared or rotated, add a clip path representing 1102 // this transformed rectangle. 1103 k := opKey{Key: encOp.Key} 1104 k.SetTransform(t) // TODO: This call has no effect. 1105 d.addClipPath(&state, clipData, k, bnd, off) 1106 } 1107 1108 bounds := cl.Round() 1109 mat := state.materialFor(bnd, off, partialTrans, bounds) 1110 1111 rect := state.cpath == nil || state.cpath.rect 1112 if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 { 1113 // The image is a uniform opaque color and takes up the whole screen. 1114 // Scrap images up to and including this image and set clear color. 1115 d.imageOps = d.imageOps[:0] 1116 d.clearColor = mat.color.Opaque() 1117 d.clear = true 1118 continue 1119 } 1120 img := imageOp{ 1121 path: state.cpath, 1122 clip: bounds, 1123 material: mat, 1124 } 1125 if n := len(d.opacityStack); n > 0 { 1126 idx := d.opacityStack[n-1] 1127 lb := d.layers[idx].clip 1128 if lb.Empty() { 1129 d.layers[idx].clip = img.clip 1130 } else { 1131 d.layers[idx].clip = lb.Union(img.clip) 1132 } 1133 } 1134 1135 d.imageOps = append(d.imageOps, img) 1136 if clipData != nil { 1137 // we added a clip path that should not remain 1138 state.cpath = state.cpath.parent 1139 } 1140 case ops.TypeSave: 1141 id := ops.DecodeSave(encOp.Data) 1142 d.save(id, state.t) 1143 case ops.TypeLoad: 1144 reset() 1145 id := ops.DecodeLoad(encOp.Data) 1146 state.t = d.states[id] 1147 } 1148 } 1149 } 1150 1151 func expandPathOp(p *pathOp, clip image.Rectangle) { 1152 for p != nil { 1153 pclip := p.clip 1154 if !pclip.Empty() { 1155 clip = clip.Union(pclip) 1156 } 1157 p.clip = clip 1158 p = p.parent 1159 } 1160 } 1161 1162 func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material { 1163 m := material{ 1164 opacity: 1., 1165 } 1166 switch d.matType { 1167 case materialColor: 1168 m.material = materialColor 1169 m.color = f32color.LinearFromSRGB(d.color) 1170 m.opaque = m.color.A == 1.0 1171 case materialLinearGradient: 1172 m.material = materialLinearGradient 1173 1174 m.color1 = f32color.LinearFromSRGB(d.color1) 1175 m.color2 = f32color.LinearFromSRGB(d.color2) 1176 m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0 1177 1178 m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2)) 1179 case materialTexture: 1180 m.material = materialTexture 1181 dr := rect.Add(off).Round() 1182 sz := d.image.src.Bounds().Size() 1183 sr := f32.Rectangle{ 1184 Max: f32.Point{ 1185 X: float32(sz.X), 1186 Y: float32(sz.Y), 1187 }, 1188 } 1189 dx := float32(dr.Dx()) 1190 sdx := sr.Dx() 1191 sr.Min.X += float32(clip.Min.X-dr.Min.X) * sdx / dx 1192 sr.Max.X -= float32(dr.Max.X-clip.Max.X) * sdx / dx 1193 dy := float32(dr.Dy()) 1194 sdy := sr.Dy() 1195 sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy 1196 sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy 1197 uvScale, uvOffset := texSpaceTransform(sr, sz) 1198 m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)) 1199 m.data = d.image 1200 } 1201 return m 1202 } 1203 1204 func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) { 1205 for i := range ops { 1206 img := &ops[i] 1207 m := img.material 1208 if m.material == materialTexture { 1209 img.material.tex = r.texHandle(cache, m.data) 1210 } 1211 } 1212 } 1213 1214 func (r *renderer) prepareDrawOps(ops []imageOp) { 1215 for _, img := range ops { 1216 m := img.material 1217 switch m.material { 1218 case materialTexture: 1219 r.ctx.PrepareTexture(m.tex) 1220 } 1221 1222 var fbo FBO 1223 switch img.clipType { 1224 case clipTypeNone: 1225 continue 1226 case clipTypePath: 1227 fbo = r.pather.stenciler.cover(img.place.Idx) 1228 case clipTypeIntersection: 1229 fbo = r.pather.stenciler.intersections.fbos[img.place.Idx] 1230 } 1231 r.ctx.PrepareTexture(fbo.tex) 1232 } 1233 } 1234 1235 func (r *renderer) drawOps(isFBO bool, opOff, viewport image.Point, ops []imageOp) { 1236 var coverTex driver.Texture 1237 for i := 0; i < len(ops); i++ { 1238 img := ops[i] 1239 i += img.layerOps 1240 m := img.material 1241 switch m.material { 1242 case materialTexture: 1243 r.ctx.BindTexture(0, m.tex) 1244 } 1245 drc := img.clip.Add(opOff) 1246 1247 scale, off := clipSpaceTransform(drc, viewport) 1248 var fbo FBO 1249 fboIdx := 0 1250 if isFBO { 1251 fboIdx = 1 1252 } 1253 switch img.clipType { 1254 case clipTypeNone: 1255 p := r.blitter.pipelines[fboIdx][m.material] 1256 r.ctx.BindPipeline(p.pipeline) 1257 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) 1258 r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans) 1259 continue 1260 case clipTypePath: 1261 fbo = r.pather.stenciler.cover(img.place.Idx) 1262 case clipTypeIntersection: 1263 fbo = r.pather.stenciler.intersections.fbos[img.place.Idx] 1264 } 1265 if coverTex != fbo.tex { 1266 coverTex = fbo.tex 1267 r.ctx.BindTexture(1, coverTex) 1268 } 1269 uv := image.Rectangle{ 1270 Min: img.place.Pos, 1271 Max: img.place.Pos.Add(drc.Size()), 1272 } 1273 coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size) 1274 p := r.pather.coverer.pipelines[fboIdx][m.material] 1275 r.ctx.BindPipeline(p.pipeline) 1276 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) 1277 r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff) 1278 } 1279 } 1280 1281 func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) { 1282 fboIdx := 0 1283 if fbo { 1284 fboIdx = 1 1285 } 1286 p := b.pipelines[fboIdx][mat] 1287 b.ctx.BindPipeline(p.pipeline) 1288 var uniforms *blitUniforms 1289 switch mat { 1290 case materialColor: 1291 b.colUniforms.color = col 1292 uniforms = &b.colUniforms.blitUniforms 1293 case materialTexture: 1294 t1, t2, t3, t4, t5, t6 := uvTrans.Elems() 1295 uniforms = &b.texUniforms.blitUniforms 1296 uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0} 1297 uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0} 1298 case materialLinearGradient: 1299 b.linearGradientUniforms.color1 = col1 1300 b.linearGradientUniforms.color2 = col2 1301 1302 t1, t2, t3, t4, t5, t6 := uvTrans.Elems() 1303 uniforms = &b.linearGradientUniforms.blitUniforms 1304 uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0} 1305 uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0} 1306 } 1307 uniforms.fbo = 0 1308 if fbo { 1309 uniforms.fbo = 1 1310 } 1311 uniforms.opacity = opacity 1312 uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y} 1313 p.UploadUniforms(b.ctx) 1314 b.ctx.DrawArrays(0, 4) 1315 } 1316 1317 // newUniformBuffer creates a new GPU uniform buffer backed by the 1318 // structure uniformBlock points to. 1319 func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer { 1320 ref := reflect.ValueOf(uniformBlock) 1321 // Determine the size of the uniforms structure, *uniforms. 1322 size := ref.Elem().Type().Size() 1323 // Map the uniforms structure as a byte slice. 1324 ptr := unsafe.Slice((*byte)(unsafe.Pointer(ref.Pointer())), size) 1325 ubuf, err := b.NewBuffer(driver.BufferBindingUniforms, len(ptr)) 1326 if err != nil { 1327 panic(err) 1328 } 1329 return &uniformBuffer{buf: ubuf, ptr: ptr} 1330 } 1331 1332 func (u *uniformBuffer) Upload() { 1333 u.buf.Upload(u.ptr) 1334 } 1335 1336 func (u *uniformBuffer) Release() { 1337 u.buf.Release() 1338 u.buf = nil 1339 } 1340 1341 func (p *pipeline) UploadUniforms(ctx driver.Device) { 1342 if p.uniforms != nil { 1343 p.uniforms.Upload() 1344 ctx.BindUniforms(p.uniforms.buf) 1345 } 1346 } 1347 1348 func (p *pipeline) Release() { 1349 p.pipeline.Release() 1350 if p.uniforms != nil { 1351 p.uniforms.Release() 1352 } 1353 *p = pipeline{} 1354 } 1355 1356 // texSpaceTransform return the scale and offset that transforms the given subimage 1357 // into quad texture coordinates. 1358 func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Point) { 1359 size := f32.Point{X: float32(bounds.X), Y: float32(bounds.Y)} 1360 scale := f32.Point{X: r.Dx() / size.X, Y: r.Dy() / size.Y} 1361 offset := f32.Point{X: r.Min.X / size.X, Y: r.Min.Y / size.Y} 1362 return scale, offset 1363 } 1364 1365 // gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)]. 1366 func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D { 1367 d := stop2.Sub(stop1) 1368 l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y))) 1369 a := float32(math.Atan2(float64(-d.Y), float64(d.X))) 1370 1371 // TODO: optimize 1372 zp := f32.Point{} 1373 return f32.Affine2D{}. 1374 Scale(zp, layout.FPt(clip.Size())). // scale to pixel space 1375 Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space 1376 Offset(zp.Sub(stop1)). // offset to first stop point 1377 Rotate(zp, a). // rotate to align gradient 1378 Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size 1379 } 1380 1381 // clipSpaceTransform returns the scale and offset that transforms the given 1382 // rectangle from a viewport into GPU driver device coordinates. 1383 func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) { 1384 // First, transform UI coordinates to device coordinates: 1385 // 1386 // [(-1, -1) (+1, -1)] 1387 // [(-1, +1) (+1, +1)] 1388 // 1389 x, y := float32(r.Min.X), float32(r.Min.Y) 1390 w, h := float32(r.Dx()), float32(r.Dy()) 1391 vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y) 1392 x = x*vx - 1 1393 y = y*vy - 1 1394 w *= vx 1395 h *= vy 1396 1397 // Then, compute the transformation from the fullscreen quad to 1398 // the rectangle at (x, y) and dimensions (w, h). 1399 scale := f32.Point{X: w * .5, Y: h * .5} 1400 offset := f32.Point{X: x + w*.5, Y: y + h*.5} 1401 1402 return scale, offset 1403 } 1404 1405 // Fill in maximal Y coordinates of the NW and NE corners. 1406 func fillMaxY(verts []byte) { 1407 contour := 0 1408 bo := binary.LittleEndian 1409 for len(verts) > 0 { 1410 maxy := float32(math.Inf(-1)) 1411 i := 0 1412 for ; i+vertStride*4 <= len(verts); i += vertStride * 4 { 1413 vert := verts[i : i+vertStride] 1414 // MaxY contains the integer contour index. 1415 pathContour := int(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).MaxY)):])) 1416 if contour != pathContour { 1417 contour = pathContour 1418 break 1419 } 1420 fromy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).FromY)):])) 1421 ctrly := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).CtrlY)):])) 1422 toy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).ToY)):])) 1423 if fromy > maxy { 1424 maxy = fromy 1425 } 1426 if ctrly > maxy { 1427 maxy = ctrly 1428 } 1429 if toy > maxy { 1430 maxy = toy 1431 } 1432 } 1433 fillContourMaxY(maxy, verts[:i]) 1434 verts = verts[i:] 1435 } 1436 } 1437 1438 func fillContourMaxY(maxy float32, verts []byte) { 1439 bo := binary.LittleEndian 1440 for i := 0; i < len(verts); i += vertStride { 1441 off := int(unsafe.Offsetof(((*vertex)(nil)).MaxY)) 1442 bo.PutUint32(verts[i+off:], math.Float32bits(maxy)) 1443 } 1444 } 1445 1446 func (d *drawOps) writeVertCache(n int) []byte { 1447 d.vertCache = append(d.vertCache, make([]byte, n)...) 1448 return d.vertCache[len(d.vertCache)-n:] 1449 } 1450 1451 // transform, split paths as needed, calculate maxY, bounds and create GPU vertices. 1452 func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, strWidth float32) (verts []byte, bounds f32.Rectangle) { 1453 inf := float32(math.Inf(+1)) 1454 d.qs.bounds = f32.Rectangle{ 1455 Min: f32.Point{X: inf, Y: inf}, 1456 Max: f32.Point{X: -inf, Y: -inf}, 1457 } 1458 d.qs.d = d 1459 startLength := len(d.vertCache) 1460 1461 switch { 1462 case strWidth > 0: 1463 // Stroke path. 1464 ss := stroke.StrokeStyle{ 1465 Width: strWidth, 1466 } 1467 quads := stroke.StrokePathCommands(ss, pathData) 1468 for _, quad := range quads { 1469 d.qs.contour = quad.Contour 1470 quad.Quad = quad.Quad.Transform(tr) 1471 1472 d.qs.splitAndEncode(quad.Quad) 1473 } 1474 1475 case outline: 1476 decodeToOutlineQuads(&d.qs, tr, pathData) 1477 } 1478 1479 fillMaxY(d.vertCache[startLength:]) 1480 return d.vertCache[startLength:], d.qs.bounds 1481 } 1482 1483 // decodeOutlineQuads decodes scene commands, splits them into quadratic béziers 1484 // as needed and feeds them to the supplied splitter. 1485 func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) { 1486 for len(pathData) >= scene.CommandSize+4 { 1487 qs.contour = bo.Uint32(pathData) 1488 cmd := ops.DecodeCommand(pathData[4:]) 1489 switch cmd.Op() { 1490 case scene.OpLine: 1491 var q stroke.QuadSegment 1492 q.From, q.To = scene.DecodeLine(cmd) 1493 q.Ctrl = q.From.Add(q.To).Mul(.5) 1494 q = q.Transform(tr) 1495 qs.splitAndEncode(q) 1496 case scene.OpGap: 1497 var q stroke.QuadSegment 1498 q.From, q.To = scene.DecodeGap(cmd) 1499 q.Ctrl = q.From.Add(q.To).Mul(.5) 1500 q = q.Transform(tr) 1501 qs.splitAndEncode(q) 1502 case scene.OpQuad: 1503 var q stroke.QuadSegment 1504 q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd) 1505 q = q.Transform(tr) 1506 qs.splitAndEncode(q) 1507 case scene.OpCubic: 1508 from, ctrl0, ctrl1, to := scene.DecodeCubic(cmd) 1509 qs.scratch = stroke.SplitCubic(from, ctrl0, ctrl1, to, qs.scratch[:0]) 1510 for _, q := range qs.scratch { 1511 q = q.Transform(tr) 1512 qs.splitAndEncode(q) 1513 } 1514 default: 1515 panic("unsupported scene command") 1516 } 1517 pathData = pathData[scene.CommandSize+4:] 1518 } 1519 } 1520 1521 // create GPU vertices for transformed r, find the bounds and establish texture transform. 1522 func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) { 1523 if isPureOffset(tr) { 1524 // fast-path to allow blitting of pure rectangles 1525 _, _, ox, _, _, oy := tr.Elems() 1526 off := f32.Pt(ox, oy) 1527 bnd.Min = r.Min.Add(off) 1528 bnd.Max = r.Max.Add(off) 1529 return 1530 } 1531 1532 // transform all corners, find new bounds 1533 corners := [4]f32.Point{ 1534 tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)), 1535 tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)), 1536 } 1537 bnd.Min = f32.Pt(math.MaxFloat32, math.MaxFloat32) 1538 bnd.Max = f32.Pt(-math.MaxFloat32, -math.MaxFloat32) 1539 for _, c := range corners { 1540 if c.X < bnd.Min.X { 1541 bnd.Min.X = c.X 1542 } 1543 if c.Y < bnd.Min.Y { 1544 bnd.Min.Y = c.Y 1545 } 1546 if c.X > bnd.Max.X { 1547 bnd.Max.X = c.X 1548 } 1549 if c.Y > bnd.Max.Y { 1550 bnd.Max.Y = c.Y 1551 } 1552 } 1553 1554 // build the GPU vertices 1555 l := len(d.vertCache) 1556 d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...) 1557 aux = d.vertCache[l:] 1558 encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1]) 1559 encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2]) 1560 encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3]) 1561 encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0]) 1562 fillMaxY(aux) 1563 1564 // establish the transform mapping from bounds rectangle to transformed corners 1565 var P1, P2, P3 f32.Point 1566 P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X) 1567 P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y) 1568 P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X) 1569 P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y) 1570 P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X) 1571 P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y) 1572 sx, sy := P2.X-P3.X, P2.Y-P3.Y 1573 ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert() 1574 1575 return 1576 } 1577 1578 func isPureOffset(t f32.Affine2D) bool { 1579 a, b, _, d, e, _ := t.Elems() 1580 return a == 1 && b == 0 && d == 0 && e == 1 1581 }