github.com/utopiagio/gio@v0.0.8/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 "github.com/utopiagio/gio/gpu/internal/driver" 22 "github.com/utopiagio/gio/internal/byteslice" 23 "github.com/utopiagio/gio/internal/f32" 24 "github.com/utopiagio/gio/internal/f32color" 25 "github.com/utopiagio/gio/internal/ops" 26 "github.com/utopiagio/gio/internal/scene" 27 "github.com/utopiagio/gio/internal/stroke" 28 "github.com/utopiagio/gio/layout" 29 "github.com/utopiagio/gio/op" 30 "gioui.org/shader" 31 "gioui.org/shader/gio" 32 33 // Register backends. 34 _ "github.com/utopiagio/gio/gpu/internal/d3d11" 35 _ "github.com/utopiagio/gio/gpu/internal/metal" 36 _ "github.com/utopiagio/gio/gpu/internal/opengl" 37 _ "github.com/utopiagio/gio/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 [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 p.Release() 564 } 565 } 566 567 func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, uniforms [3]interface{}) ([3]*pipeline, error) { 568 var pipelines [3]*pipeline 569 blend := driver.BlendDesc{ 570 Enable: true, 571 SrcFactor: driver.BlendFactorOne, 572 DstFactor: driver.BlendFactorOneMinusSrcAlpha, 573 } 574 layout := driver.VertexLayout{ 575 Inputs: []driver.InputDesc{ 576 {Type: shader.DataTypeFloat, Size: 2, Offset: 0}, 577 {Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2}, 578 }, 579 Stride: 4 * 4, 580 } 581 vsh, err := b.NewVertexShader(vsSrc) 582 if err != nil { 583 return pipelines, err 584 } 585 defer vsh.Release() 586 { 587 fsh, err := b.NewFragmentShader(fsSrc[materialTexture]) 588 if err != nil { 589 return pipelines, err 590 } 591 defer fsh.Release() 592 pipe, err := b.NewPipeline(driver.PipelineDesc{ 593 VertexShader: vsh, 594 FragmentShader: fsh, 595 BlendDesc: blend, 596 VertexLayout: layout, 597 PixelFormat: driver.TextureFormatOutput, 598 Topology: driver.TopologyTriangleStrip, 599 }) 600 if err != nil { 601 return pipelines, err 602 } 603 var vertBuffer *uniformBuffer 604 if u := uniforms[materialTexture]; u != nil { 605 vertBuffer = newUniformBuffer(b, u) 606 } 607 pipelines[materialTexture] = &pipeline{pipe, vertBuffer} 608 } 609 { 610 var vertBuffer *uniformBuffer 611 fsh, err := b.NewFragmentShader(fsSrc[materialColor]) 612 if err != nil { 613 pipelines[materialTexture].Release() 614 return pipelines, err 615 } 616 defer fsh.Release() 617 pipe, err := b.NewPipeline(driver.PipelineDesc{ 618 VertexShader: vsh, 619 FragmentShader: fsh, 620 BlendDesc: blend, 621 VertexLayout: layout, 622 PixelFormat: driver.TextureFormatOutput, 623 Topology: driver.TopologyTriangleStrip, 624 }) 625 if err != nil { 626 pipelines[materialTexture].Release() 627 return pipelines, err 628 } 629 if u := uniforms[materialColor]; u != nil { 630 vertBuffer = newUniformBuffer(b, u) 631 } 632 pipelines[materialColor] = &pipeline{pipe, vertBuffer} 633 } 634 { 635 var vertBuffer *uniformBuffer 636 fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient]) 637 if err != nil { 638 pipelines[materialTexture].Release() 639 pipelines[materialColor].Release() 640 return pipelines, err 641 } 642 defer fsh.Release() 643 pipe, err := b.NewPipeline(driver.PipelineDesc{ 644 VertexShader: vsh, 645 FragmentShader: fsh, 646 BlendDesc: blend, 647 VertexLayout: layout, 648 PixelFormat: driver.TextureFormatOutput, 649 Topology: driver.TopologyTriangleStrip, 650 }) 651 if err != nil { 652 pipelines[materialTexture].Release() 653 pipelines[materialColor].Release() 654 return pipelines, err 655 } 656 if u := uniforms[materialLinearGradient]; u != nil { 657 vertBuffer = newUniformBuffer(b, u) 658 } 659 pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer} 660 } 661 if err != nil { 662 for _, p := range pipelines { 663 p.Release() 664 } 665 return pipelines, err 666 } 667 return pipelines, nil 668 } 669 670 func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) { 671 if len(r.packer.sizes) == 0 { 672 return 673 } 674 fbo := -1 675 r.pather.begin(r.packer.sizes) 676 for _, p := range ops { 677 if fbo != p.place.Idx { 678 if fbo != -1 { 679 r.ctx.EndRenderPass() 680 } 681 fbo = p.place.Idx 682 f := r.pather.stenciler.cover(fbo) 683 r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear}) 684 r.ctx.BindPipeline(r.pather.stenciler.pipeline.pipeline.pipeline) 685 r.ctx.BindIndexBuffer(r.pather.stenciler.indexBuf) 686 } 687 v, _ := pathCache.get(p.pathKey) 688 r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data) 689 } 690 if fbo != -1 { 691 r.ctx.EndRenderPass() 692 } 693 } 694 695 func (r *renderer) prepareIntersections(ops []imageOp) { 696 for _, img := range ops { 697 if img.clipType != clipTypeIntersection { 698 continue 699 } 700 fbo := r.pather.stenciler.cover(img.path.place.Idx) 701 r.ctx.PrepareTexture(fbo.tex) 702 } 703 } 704 705 func (r *renderer) intersect(ops []imageOp) { 706 if len(r.intersections.sizes) == 0 { 707 return 708 } 709 fbo := -1 710 r.pather.stenciler.beginIntersect(r.intersections.sizes) 711 for _, img := range ops { 712 if img.clipType != clipTypeIntersection { 713 continue 714 } 715 if fbo != img.place.Idx { 716 if fbo != -1 { 717 r.ctx.EndRenderPass() 718 } 719 fbo = img.place.Idx 720 f := r.pather.stenciler.intersections.fbos[fbo] 721 d := driver.LoadDesc{Action: driver.LoadActionClear} 722 d.ClearColor.R = 1.0 723 r.ctx.BeginRenderPass(f.tex, d) 724 r.ctx.BindPipeline(r.pather.stenciler.ipipeline.pipeline.pipeline) 725 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) 726 } 727 r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy()) 728 r.intersectPath(img.path, img.clip) 729 } 730 if fbo != -1 { 731 r.ctx.EndRenderPass() 732 } 733 } 734 735 func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) { 736 if p.parent != nil { 737 r.intersectPath(p.parent, clip) 738 } 739 if !p.path { 740 return 741 } 742 uv := image.Rectangle{ 743 Min: p.place.Pos, 744 Max: p.place.Pos.Add(p.clip.Size()), 745 } 746 o := clip.Min.Sub(p.clip.Min) 747 sub := image.Rectangle{ 748 Min: o, 749 Max: o.Add(clip.Size()), 750 } 751 fbo := r.pather.stenciler.cover(p.place.Idx) 752 r.ctx.BindTexture(0, fbo.tex) 753 coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size) 754 subScale, subOff := texSpaceTransform(f32.FRect(sub), p.clip.Size()) 755 r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y} 756 r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y} 757 r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx) 758 r.ctx.DrawArrays(0, 4) 759 } 760 761 func (r *renderer) packIntersections(ops []imageOp) { 762 r.intersections.clear() 763 for i, img := range ops { 764 var npaths int 765 var onePath *pathOp 766 for p := img.path; p != nil; p = p.parent { 767 if p.path { 768 onePath = p 769 npaths++ 770 } 771 } 772 switch npaths { 773 case 0: 774 case 1: 775 place := onePath.place 776 place.Pos = place.Pos.Sub(onePath.clip.Min).Add(img.clip.Min) 777 ops[i].place = place 778 ops[i].clipType = clipTypePath 779 default: 780 sz := image.Point{X: img.clip.Dx(), Y: img.clip.Dy()} 781 place, ok := r.intersections.add(sz) 782 if !ok { 783 panic("internal error: if the intersection fit, the intersection should fit as well") 784 } 785 ops[i].clipType = clipTypeIntersection 786 ops[i].place = place 787 } 788 } 789 } 790 791 func (r *renderer) packStencils(pops *[]*pathOp) { 792 r.packer.clear() 793 ops := *pops 794 // Allocate atlas space for cover textures. 795 var i int 796 for i < len(ops) { 797 p := ops[i] 798 if p.clip.Empty() { 799 ops[i] = ops[len(ops)-1] 800 ops = ops[:len(ops)-1] 801 continue 802 } 803 place, ok := r.packer.add(p.clip.Size()) 804 if !ok { 805 // The clip area is at most the entire screen. Hopefully no 806 // screen is larger than GL_MAX_TEXTURE_SIZE. 807 panic(fmt.Errorf("clip area %v is larger than maximum texture size %v", p.clip, r.packer.maxDims)) 808 } 809 p.place = place 810 i++ 811 } 812 *pops = ops 813 } 814 815 func (r *renderer) packLayers(layers []opacityLayer) []opacityLayer { 816 // Make every layer bounds contain nested layers; cull empty layers. 817 for i := len(layers) - 1; i >= 0; i-- { 818 l := layers[i] 819 if l.parent != -1 { 820 b := layers[l.parent].clip 821 layers[l.parent].clip = b.Union(l.clip) 822 } 823 if l.clip.Empty() { 824 layers = append(layers[:i], layers[i+1:]...) 825 } 826 } 827 // Pack layers. 828 r.layers.clear() 829 depth := 0 830 for i := range layers { 831 l := &layers[i] 832 // Only layers of the same depth may be packed together. 833 if l.depth != depth { 834 r.layers.newPage() 835 } 836 place, ok := r.layers.add(l.clip.Size()) 837 if !ok { 838 // The layer area is at most the entire screen. Hopefully no 839 // screen is larger than GL_MAX_TEXTURE_SIZE. 840 panic(fmt.Errorf("layer size %v is larger than maximum texture size %v", l.clip.Size(), r.layers.maxDims)) 841 } 842 l.place = place 843 } 844 return layers 845 } 846 847 func (r *renderer) drawLayers(layers []opacityLayer, ops []imageOp) { 848 if len(r.layers.sizes) == 0 { 849 return 850 } 851 fbo := -1 852 r.layerFBOs.resize(r.ctx, driver.TextureFormatSRGBA, r.layers.sizes) 853 for i := len(layers) - 1; i >= 0; i-- { 854 l := layers[i] 855 if fbo != l.place.Idx { 856 if fbo != -1 { 857 r.ctx.EndRenderPass() 858 r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex) 859 } 860 fbo = l.place.Idx 861 f := r.layerFBOs.fbos[fbo] 862 r.ctx.BeginRenderPass(f.tex, driver.LoadDesc{Action: driver.LoadActionClear}) 863 } 864 v := image.Rectangle{ 865 Min: l.place.Pos, 866 Max: l.place.Pos.Add(l.clip.Size()), 867 } 868 r.ctx.Viewport(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y) 869 f := r.layerFBOs.fbos[fbo] 870 r.drawOps(true, l.clip.Min.Mul(-1), l.clip.Size(), ops[l.opStart:l.opEnd]) 871 sr := f32.FRect(v) 872 uvScale, uvOffset := texSpaceTransform(sr, f.size) 873 uvTrans := f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset) 874 // Replace layer ops with one textured op. 875 ops[l.opStart] = imageOp{ 876 clip: l.clip, 877 material: material{ 878 material: materialTexture, 879 tex: f.tex, 880 uvTrans: uvTrans, 881 opacity: l.opacity, 882 }, 883 layerOps: l.opEnd - l.opStart - 1, 884 } 885 } 886 if fbo != -1 { 887 r.ctx.EndRenderPass() 888 r.ctx.PrepareTexture(r.layerFBOs.fbos[fbo].tex) 889 } 890 } 891 892 func (d *drawOps) reset(viewport image.Point) { 893 d.viewport = viewport 894 d.imageOps = d.imageOps[:0] 895 d.pathOps = d.pathOps[:0] 896 d.pathOpCache = d.pathOpCache[:0] 897 d.vertCache = d.vertCache[:0] 898 d.transStack = d.transStack[:0] 899 d.layers = d.layers[:0] 900 d.opacityStack = d.opacityStack[:0] 901 } 902 903 func (d *drawOps) collect(root *op.Ops, viewport image.Point) { 904 viewf := f32.Rectangle{ 905 Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)}, 906 } 907 var ops *ops.Ops 908 if root != nil { 909 ops = &root.Internal 910 } 911 d.reader.Reset(ops) 912 d.collectOps(&d.reader, viewf) 913 } 914 915 func (d *drawOps) buildPaths(ctx driver.Device) { 916 for _, p := range d.pathOps { 917 if v, exists := d.pathCache.get(p.pathKey); !exists || v.data.data == nil { 918 data := buildPath(ctx, p.pathVerts) 919 d.pathCache.put(p.pathKey, opCacheValue{ 920 data: data, 921 bounds: p.bounds, 922 }) 923 } 924 p.pathVerts = nil 925 } 926 } 927 928 func (d *drawOps) newPathOp() *pathOp { 929 d.pathOpCache = append(d.pathOpCache, pathOp{}) 930 return &d.pathOpCache[len(d.pathOpCache)-1] 931 } 932 933 func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey opKey, bounds f32.Rectangle, off f32.Point, push bool) { 934 npath := d.newPathOp() 935 *npath = pathOp{ 936 parent: state.cpath, 937 bounds: bounds, 938 off: off, 939 intersect: bounds.Add(off), 940 rect: true, 941 } 942 if npath.parent != nil { 943 npath.rect = npath.parent.rect 944 npath.intersect = npath.parent.intersect.Intersect(npath.intersect) 945 } 946 if len(aux) > 0 { 947 npath.rect = false 948 npath.pathKey = auxKey 949 npath.path = true 950 npath.pathVerts = aux 951 d.pathOps = append(d.pathOps, npath) 952 } 953 state.cpath = npath 954 } 955 956 func (d *drawOps) save(id int, state f32.Affine2D) { 957 if extra := id - len(d.states) + 1; extra > 0 { 958 d.states = append(d.states, make([]f32.Affine2D, extra)...) 959 } 960 d.states[id] = state 961 } 962 963 func (k opKey) SetTransform(t f32.Affine2D) opKey { 964 sx, hx, _, hy, sy, _ := t.Elems() 965 k.sx = sx 966 k.hx = hx 967 k.hy = hy 968 k.sy = sy 969 return k 970 } 971 972 func (d *drawOps) collectOps(r *ops.Reader, viewport f32.Rectangle) { 973 var ( 974 quads quadsOp 975 state drawState 976 ) 977 reset := func() { 978 state = drawState{ 979 color: color.NRGBA{A: 0xff}, 980 } 981 } 982 reset() 983 loop: 984 for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { 985 switch ops.OpType(encOp.Data[0]) { 986 case ops.TypeTransform: 987 dop, push := ops.DecodeTransform(encOp.Data) 988 if push { 989 d.transStack = append(d.transStack, state.t) 990 } 991 state.t = state.t.Mul(dop) 992 case ops.TypePopTransform: 993 n := len(d.transStack) 994 state.t = d.transStack[n-1] 995 d.transStack = d.transStack[:n-1] 996 997 case ops.TypePushOpacity: 998 opacity := ops.DecodeOpacity(encOp.Data) 999 parent := -1 1000 depth := len(d.opacityStack) 1001 if depth > 0 { 1002 parent = d.opacityStack[depth-1] 1003 } 1004 lidx := len(d.layers) 1005 d.layers = append(d.layers, opacityLayer{ 1006 opacity: opacity, 1007 parent: parent, 1008 depth: depth, 1009 opStart: len(d.imageOps), 1010 }) 1011 d.opacityStack = append(d.opacityStack, lidx) 1012 case ops.TypePopOpacity: 1013 n := len(d.opacityStack) 1014 idx := d.opacityStack[n-1] 1015 d.layers[idx].opEnd = len(d.imageOps) 1016 d.opacityStack = d.opacityStack[:n-1] 1017 1018 case ops.TypeStroke: 1019 quads.key.strokeWidth = decodeStrokeOp(encOp.Data) 1020 1021 case ops.TypePath: 1022 encOp, ok = r.Decode() 1023 if !ok { 1024 break loop 1025 } 1026 quads.aux = encOp.Data[ops.TypeAuxLen:] 1027 quads.key.Key = encOp.Key 1028 1029 case ops.TypeClip: 1030 var op ops.ClipOp 1031 op.Decode(encOp.Data) 1032 quads.key.outline = op.Outline 1033 bounds := f32.FRect(op.Bounds) 1034 trans, off := state.t.Split() 1035 if len(quads.aux) > 0 { 1036 // There is a clipping path, build the gpu data and update the 1037 // cache key such that it will be equal only if the transform is the 1038 // same also. Use cached data if we have it. 1039 quads.key = quads.key.SetTransform(trans) 1040 if v, ok := d.pathCache.get(quads.key); ok { 1041 // Since the GPU data exists in the cache aux will not be used. 1042 // Why is this not used for the offset shapes? 1043 bounds = v.bounds 1044 } else { 1045 var pathData []byte 1046 pathData, bounds = d.buildVerts( 1047 quads.aux, trans, quads.key.outline, quads.key.strokeWidth, 1048 ) 1049 quads.aux = pathData 1050 // add it to the cache, without GPU data, so the transform can be 1051 // reused. 1052 d.pathCache.put(quads.key, opCacheValue{bounds: bounds}) 1053 } 1054 } else { 1055 quads.aux, bounds, _ = d.boundsForTransformedRect(bounds, trans) 1056 quads.key = opKey{Key: encOp.Key} 1057 } 1058 d.addClipPath(&state, quads.aux, quads.key, bounds, off, true) 1059 quads = quadsOp{} 1060 case ops.TypePopClip: 1061 state.cpath = state.cpath.parent 1062 1063 case ops.TypeColor: 1064 state.matType = materialColor 1065 state.color = decodeColorOp(encOp.Data) 1066 case ops.TypeLinearGradient: 1067 state.matType = materialLinearGradient 1068 op := decodeLinearGradientOp(encOp.Data) 1069 state.stop1 = op.stop1 1070 state.stop2 = op.stop2 1071 state.color1 = op.color1 1072 state.color2 = op.color2 1073 case ops.TypeImage: 1074 state.matType = materialTexture 1075 state.image = decodeImageOp(encOp.Data, encOp.Refs) 1076 case ops.TypePaint: 1077 // Transform (if needed) the painting rectangle and if so generate a clip path, 1078 // for those cases also compute a partialTrans that maps texture coordinates between 1079 // the new bounding rectangle and the transformed original paint rectangle. 1080 t, off := state.t.Split() 1081 // Fill the clip area, unless the material is a (bounded) image. 1082 // TODO: Find a tighter bound. 1083 inf := float32(1e6) 1084 dst := f32.Rect(-inf, -inf, inf, inf) 1085 if state.matType == materialTexture { 1086 sz := state.image.src.Rect.Size() 1087 dst = f32.Rectangle{Max: layout.FPt(sz)} 1088 } 1089 clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, t) 1090 cl := viewport.Intersect(bnd.Add(off)) 1091 if state.cpath != nil { 1092 cl = state.cpath.intersect.Intersect(cl) 1093 } 1094 if cl.Empty() { 1095 continue 1096 } 1097 1098 if clipData != nil { 1099 // The paint operation is sheared or rotated, add a clip path representing 1100 // this transformed rectangle. 1101 k := opKey{Key: encOp.Key} 1102 k.SetTransform(t) // TODO: This call has no effect. 1103 d.addClipPath(&state, clipData, k, bnd, off, false) 1104 } 1105 1106 bounds := cl.Round() 1107 mat := state.materialFor(bnd, off, partialTrans, bounds) 1108 1109 rect := state.cpath == nil || state.cpath.rect 1110 if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && rect && mat.opaque && (mat.material == materialColor) && len(d.opacityStack) == 0 { 1111 // The image is a uniform opaque color and takes up the whole screen. 1112 // Scrap images up to and including this image and set clear color. 1113 d.imageOps = d.imageOps[:0] 1114 d.clearColor = mat.color.Opaque() 1115 d.clear = true 1116 continue 1117 } 1118 img := imageOp{ 1119 path: state.cpath, 1120 clip: bounds, 1121 material: mat, 1122 } 1123 if n := len(d.opacityStack); n > 0 { 1124 idx := d.opacityStack[n-1] 1125 lb := d.layers[idx].clip 1126 if lb.Empty() { 1127 d.layers[idx].clip = img.clip 1128 } else { 1129 d.layers[idx].clip = lb.Union(img.clip) 1130 } 1131 } 1132 1133 d.imageOps = append(d.imageOps, img) 1134 if clipData != nil { 1135 // we added a clip path that should not remain 1136 state.cpath = state.cpath.parent 1137 } 1138 case ops.TypeSave: 1139 id := ops.DecodeSave(encOp.Data) 1140 d.save(id, state.t) 1141 case ops.TypeLoad: 1142 reset() 1143 id := ops.DecodeLoad(encOp.Data) 1144 state.t = d.states[id] 1145 } 1146 } 1147 } 1148 1149 func expandPathOp(p *pathOp, clip image.Rectangle) { 1150 for p != nil { 1151 pclip := p.clip 1152 if !pclip.Empty() { 1153 clip = clip.Union(pclip) 1154 } 1155 p.clip = clip 1156 p = p.parent 1157 } 1158 } 1159 1160 func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32.Affine2D, clip image.Rectangle) material { 1161 m := material{ 1162 opacity: 1., 1163 } 1164 switch d.matType { 1165 case materialColor: 1166 m.material = materialColor 1167 m.color = f32color.LinearFromSRGB(d.color) 1168 m.opaque = m.color.A == 1.0 1169 case materialLinearGradient: 1170 m.material = materialLinearGradient 1171 1172 m.color1 = f32color.LinearFromSRGB(d.color1) 1173 m.color2 = f32color.LinearFromSRGB(d.color2) 1174 m.opaque = m.color1.A == 1.0 && m.color2.A == 1.0 1175 1176 m.uvTrans = partTrans.Mul(gradientSpaceTransform(clip, off, d.stop1, d.stop2)) 1177 case materialTexture: 1178 m.material = materialTexture 1179 dr := rect.Add(off).Round() 1180 sz := d.image.src.Bounds().Size() 1181 sr := f32.Rectangle{ 1182 Max: f32.Point{ 1183 X: float32(sz.X), 1184 Y: float32(sz.Y), 1185 }, 1186 } 1187 dx := float32(dr.Dx()) 1188 sdx := sr.Dx() 1189 sr.Min.X += float32(clip.Min.X-dr.Min.X) * sdx / dx 1190 sr.Max.X -= float32(dr.Max.X-clip.Max.X) * sdx / dx 1191 dy := float32(dr.Dy()) 1192 sdy := sr.Dy() 1193 sr.Min.Y += float32(clip.Min.Y-dr.Min.Y) * sdy / dy 1194 sr.Max.Y -= float32(dr.Max.Y-clip.Max.Y) * sdy / dy 1195 uvScale, uvOffset := texSpaceTransform(sr, sz) 1196 m.uvTrans = partTrans.Mul(f32.Affine2D{}.Scale(f32.Point{}, uvScale).Offset(uvOffset)) 1197 m.data = d.image 1198 } 1199 return m 1200 } 1201 1202 func (r *renderer) uploadImages(cache *textureCache, ops []imageOp) { 1203 for i := range ops { 1204 img := &ops[i] 1205 m := img.material 1206 if m.material == materialTexture { 1207 img.material.tex = r.texHandle(cache, m.data) 1208 } 1209 } 1210 } 1211 1212 func (r *renderer) prepareDrawOps(ops []imageOp) { 1213 for _, img := range ops { 1214 m := img.material 1215 switch m.material { 1216 case materialTexture: 1217 r.ctx.PrepareTexture(m.tex) 1218 } 1219 1220 var fbo FBO 1221 switch img.clipType { 1222 case clipTypeNone: 1223 continue 1224 case clipTypePath: 1225 fbo = r.pather.stenciler.cover(img.place.Idx) 1226 case clipTypeIntersection: 1227 fbo = r.pather.stenciler.intersections.fbos[img.place.Idx] 1228 } 1229 r.ctx.PrepareTexture(fbo.tex) 1230 } 1231 } 1232 1233 func (r *renderer) drawOps(isFBO bool, opOff image.Point, viewport image.Point, ops []imageOp) { 1234 var coverTex driver.Texture 1235 for i := 0; i < len(ops); i++ { 1236 img := ops[i] 1237 i += img.layerOps 1238 m := img.material 1239 switch m.material { 1240 case materialTexture: 1241 r.ctx.BindTexture(0, m.tex) 1242 } 1243 drc := img.clip.Add(opOff) 1244 1245 scale, off := clipSpaceTransform(drc, viewport) 1246 var fbo FBO 1247 switch img.clipType { 1248 case clipTypeNone: 1249 p := r.blitter.pipelines[m.material] 1250 r.ctx.BindPipeline(p.pipeline) 1251 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) 1252 r.blitter.blit(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.opacity, m.uvTrans) 1253 continue 1254 case clipTypePath: 1255 fbo = r.pather.stenciler.cover(img.place.Idx) 1256 case clipTypeIntersection: 1257 fbo = r.pather.stenciler.intersections.fbos[img.place.Idx] 1258 } 1259 if coverTex != fbo.tex { 1260 coverTex = fbo.tex 1261 r.ctx.BindTexture(1, coverTex) 1262 } 1263 uv := image.Rectangle{ 1264 Min: img.place.Pos, 1265 Max: img.place.Pos.Add(drc.Size()), 1266 } 1267 coverScale, coverOff := texSpaceTransform(f32.FRect(uv), fbo.size) 1268 p := r.pather.coverer.pipelines[m.material] 1269 r.ctx.BindPipeline(p.pipeline) 1270 r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0) 1271 r.pather.cover(m.material, isFBO, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff) 1272 } 1273 } 1274 1275 func (b *blitter) blit(mat materialType, fbo bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, opacity float32, uvTrans f32.Affine2D) { 1276 p := b.pipelines[mat] 1277 b.ctx.BindPipeline(p.pipeline) 1278 var uniforms *blitUniforms 1279 switch mat { 1280 case materialColor: 1281 b.colUniforms.color = col 1282 uniforms = &b.colUniforms.blitUniforms 1283 case materialTexture: 1284 t1, t2, t3, t4, t5, t6 := uvTrans.Elems() 1285 uniforms = &b.texUniforms.blitUniforms 1286 uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0} 1287 uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0} 1288 case materialLinearGradient: 1289 b.linearGradientUniforms.color1 = col1 1290 b.linearGradientUniforms.color2 = col2 1291 1292 t1, t2, t3, t4, t5, t6 := uvTrans.Elems() 1293 uniforms = &b.linearGradientUniforms.blitUniforms 1294 uniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0} 1295 uniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0} 1296 } 1297 uniforms.fbo = 0 1298 if fbo { 1299 uniforms.fbo = 1 1300 } 1301 uniforms.opacity = opacity 1302 uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y} 1303 p.UploadUniforms(b.ctx) 1304 b.ctx.DrawArrays(0, 4) 1305 } 1306 1307 // newUniformBuffer creates a new GPU uniform buffer backed by the 1308 // structure uniformBlock points to. 1309 func newUniformBuffer(b driver.Device, uniformBlock interface{}) *uniformBuffer { 1310 ref := reflect.ValueOf(uniformBlock) 1311 // Determine the size of the uniforms structure, *uniforms. 1312 size := ref.Elem().Type().Size() 1313 // Map the uniforms structure as a byte slice. 1314 ptr := unsafe.Slice((*byte)(unsafe.Pointer(ref.Pointer())), size) 1315 ubuf, err := b.NewBuffer(driver.BufferBindingUniforms, len(ptr)) 1316 if err != nil { 1317 panic(err) 1318 } 1319 return &uniformBuffer{buf: ubuf, ptr: ptr} 1320 } 1321 1322 func (u *uniformBuffer) Upload() { 1323 u.buf.Upload(u.ptr) 1324 } 1325 1326 func (u *uniformBuffer) Release() { 1327 u.buf.Release() 1328 u.buf = nil 1329 } 1330 1331 func (p *pipeline) UploadUniforms(ctx driver.Device) { 1332 if p.uniforms != nil { 1333 p.uniforms.Upload() 1334 ctx.BindUniforms(p.uniforms.buf) 1335 } 1336 } 1337 1338 func (p *pipeline) Release() { 1339 p.pipeline.Release() 1340 if p.uniforms != nil { 1341 p.uniforms.Release() 1342 } 1343 *p = pipeline{} 1344 } 1345 1346 // texSpaceTransform return the scale and offset that transforms the given subimage 1347 // into quad texture coordinates. 1348 func texSpaceTransform(r f32.Rectangle, bounds image.Point) (f32.Point, f32.Point) { 1349 size := f32.Point{X: float32(bounds.X), Y: float32(bounds.Y)} 1350 scale := f32.Point{X: r.Dx() / size.X, Y: r.Dy() / size.Y} 1351 offset := f32.Point{X: r.Min.X / size.X, Y: r.Min.Y / size.Y} 1352 return scale, offset 1353 } 1354 1355 // gradientSpaceTransform transforms stop1 and stop2 to [(0,0), (1,1)]. 1356 func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f32.Point) f32.Affine2D { 1357 d := stop2.Sub(stop1) 1358 l := float32(math.Sqrt(float64(d.X*d.X + d.Y*d.Y))) 1359 a := float32(math.Atan2(float64(-d.Y), float64(d.X))) 1360 1361 // TODO: optimize 1362 zp := f32.Point{} 1363 return f32.Affine2D{}. 1364 Scale(zp, layout.FPt(clip.Size())). // scale to pixel space 1365 Offset(zp.Sub(off).Add(layout.FPt(clip.Min))). // offset to clip space 1366 Offset(zp.Sub(stop1)). // offset to first stop point 1367 Rotate(zp, a). // rotate to align gradient 1368 Scale(zp, f32.Pt(1/l, 1/l)) // scale gradient to right size 1369 } 1370 1371 // clipSpaceTransform returns the scale and offset that transforms the given 1372 // rectangle from a viewport into GPU driver device coordinates. 1373 func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) { 1374 // First, transform UI coordinates to device coordinates: 1375 // 1376 // [(-1, -1) (+1, -1)] 1377 // [(-1, +1) (+1, +1)] 1378 // 1379 x, y := float32(r.Min.X), float32(r.Min.Y) 1380 w, h := float32(r.Dx()), float32(r.Dy()) 1381 vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y) 1382 x = x*vx - 1 1383 y = y*vy - 1 1384 w *= vx 1385 h *= vy 1386 1387 // Then, compute the transformation from the fullscreen quad to 1388 // the rectangle at (x, y) and dimensions (w, h). 1389 scale := f32.Point{X: w * .5, Y: h * .5} 1390 offset := f32.Point{X: x + w*.5, Y: y + h*.5} 1391 1392 return scale, offset 1393 } 1394 1395 // Fill in maximal Y coordinates of the NW and NE corners. 1396 func fillMaxY(verts []byte) { 1397 contour := 0 1398 bo := binary.LittleEndian 1399 for len(verts) > 0 { 1400 maxy := float32(math.Inf(-1)) 1401 i := 0 1402 for ; i+vertStride*4 <= len(verts); i += vertStride * 4 { 1403 vert := verts[i : i+vertStride] 1404 // MaxY contains the integer contour index. 1405 pathContour := int(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).MaxY)):])) 1406 if contour != pathContour { 1407 contour = pathContour 1408 break 1409 } 1410 fromy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).FromY)):])) 1411 ctrly := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).CtrlY)):])) 1412 toy := math.Float32frombits(bo.Uint32(vert[int(unsafe.Offsetof(((*vertex)(nil)).ToY)):])) 1413 if fromy > maxy { 1414 maxy = fromy 1415 } 1416 if ctrly > maxy { 1417 maxy = ctrly 1418 } 1419 if toy > maxy { 1420 maxy = toy 1421 } 1422 } 1423 fillContourMaxY(maxy, verts[:i]) 1424 verts = verts[i:] 1425 } 1426 } 1427 1428 func fillContourMaxY(maxy float32, verts []byte) { 1429 bo := binary.LittleEndian 1430 for i := 0; i < len(verts); i += vertStride { 1431 off := int(unsafe.Offsetof(((*vertex)(nil)).MaxY)) 1432 bo.PutUint32(verts[i+off:], math.Float32bits(maxy)) 1433 } 1434 } 1435 1436 func (d *drawOps) writeVertCache(n int) []byte { 1437 d.vertCache = append(d.vertCache, make([]byte, n)...) 1438 return d.vertCache[len(d.vertCache)-n:] 1439 } 1440 1441 // transform, split paths as needed, calculate maxY, bounds and create GPU vertices. 1442 func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, strWidth float32) (verts []byte, bounds f32.Rectangle) { 1443 inf := float32(math.Inf(+1)) 1444 d.qs.bounds = f32.Rectangle{ 1445 Min: f32.Point{X: inf, Y: inf}, 1446 Max: f32.Point{X: -inf, Y: -inf}, 1447 } 1448 d.qs.d = d 1449 startLength := len(d.vertCache) 1450 1451 switch { 1452 case strWidth > 0: 1453 // Stroke path. 1454 ss := stroke.StrokeStyle{ 1455 Width: strWidth, 1456 } 1457 quads := stroke.StrokePathCommands(ss, pathData) 1458 for _, quad := range quads { 1459 d.qs.contour = quad.Contour 1460 quad.Quad = quad.Quad.Transform(tr) 1461 1462 d.qs.splitAndEncode(quad.Quad) 1463 } 1464 1465 case outline: 1466 decodeToOutlineQuads(&d.qs, tr, pathData) 1467 } 1468 1469 fillMaxY(d.vertCache[startLength:]) 1470 return d.vertCache[startLength:], d.qs.bounds 1471 } 1472 1473 // decodeOutlineQuads decodes scene commands, splits them into quadratic béziers 1474 // as needed and feeds them to the supplied splitter. 1475 func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) { 1476 for len(pathData) >= scene.CommandSize+4 { 1477 qs.contour = bo.Uint32(pathData) 1478 cmd := ops.DecodeCommand(pathData[4:]) 1479 switch cmd.Op() { 1480 case scene.OpLine: 1481 var q stroke.QuadSegment 1482 q.From, q.To = scene.DecodeLine(cmd) 1483 q.Ctrl = q.From.Add(q.To).Mul(.5) 1484 q = q.Transform(tr) 1485 qs.splitAndEncode(q) 1486 case scene.OpGap: 1487 var q stroke.QuadSegment 1488 q.From, q.To = scene.DecodeGap(cmd) 1489 q.Ctrl = q.From.Add(q.To).Mul(.5) 1490 q = q.Transform(tr) 1491 qs.splitAndEncode(q) 1492 case scene.OpQuad: 1493 var q stroke.QuadSegment 1494 q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd) 1495 q = q.Transform(tr) 1496 qs.splitAndEncode(q) 1497 case scene.OpCubic: 1498 from, ctrl0, ctrl1, to := scene.DecodeCubic(cmd) 1499 qs.scratch = stroke.SplitCubic(from, ctrl0, ctrl1, to, qs.scratch[:0]) 1500 for _, q := range qs.scratch { 1501 q = q.Transform(tr) 1502 qs.splitAndEncode(q) 1503 } 1504 default: 1505 panic("unsupported scene command") 1506 } 1507 pathData = pathData[scene.CommandSize+4:] 1508 } 1509 } 1510 1511 // create GPU vertices for transformed r, find the bounds and establish texture transform. 1512 func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) { 1513 if isPureOffset(tr) { 1514 // fast-path to allow blitting of pure rectangles 1515 _, _, ox, _, _, oy := tr.Elems() 1516 off := f32.Pt(ox, oy) 1517 bnd.Min = r.Min.Add(off) 1518 bnd.Max = r.Max.Add(off) 1519 return 1520 } 1521 1522 // transform all corners, find new bounds 1523 corners := [4]f32.Point{ 1524 tr.Transform(r.Min), tr.Transform(f32.Pt(r.Max.X, r.Min.Y)), 1525 tr.Transform(r.Max), tr.Transform(f32.Pt(r.Min.X, r.Max.Y)), 1526 } 1527 bnd.Min = f32.Pt(math.MaxFloat32, math.MaxFloat32) 1528 bnd.Max = f32.Pt(-math.MaxFloat32, -math.MaxFloat32) 1529 for _, c := range corners { 1530 if c.X < bnd.Min.X { 1531 bnd.Min.X = c.X 1532 } 1533 if c.Y < bnd.Min.Y { 1534 bnd.Min.Y = c.Y 1535 } 1536 if c.X > bnd.Max.X { 1537 bnd.Max.X = c.X 1538 } 1539 if c.Y > bnd.Max.Y { 1540 bnd.Max.Y = c.Y 1541 } 1542 } 1543 1544 // build the GPU vertices 1545 l := len(d.vertCache) 1546 d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...) 1547 aux = d.vertCache[l:] 1548 encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1]) 1549 encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2]) 1550 encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3]) 1551 encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0]) 1552 fillMaxY(aux) 1553 1554 // establish the transform mapping from bounds rectangle to transformed corners 1555 var P1, P2, P3 f32.Point 1556 P1.X = (corners[1].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X) 1557 P1.Y = (corners[1].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y) 1558 P2.X = (corners[2].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X) 1559 P2.Y = (corners[2].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y) 1560 P3.X = (corners[3].X - bnd.Min.X) / (bnd.Max.X - bnd.Min.X) 1561 P3.Y = (corners[3].Y - bnd.Min.Y) / (bnd.Max.Y - bnd.Min.Y) 1562 sx, sy := P2.X-P3.X, P2.Y-P3.Y 1563 ptr = f32.NewAffine2D(sx, P2.X-P1.X, P1.X-sx, sy, P2.Y-P1.Y, P1.Y-sy).Invert() 1564 1565 return 1566 } 1567 1568 func isPureOffset(t f32.Affine2D) bool { 1569 a, b, _, d, e, _ := t.Elems() 1570 return a == 1 && b == 0 && d == 0 && e == 1 1571 }