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