gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/internal/gpu/gpu.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package gpu 4 5 import ( 6 "encoding/binary" 7 "fmt" 8 "image" 9 "image/color" 10 "math" 11 "runtime" 12 "strings" 13 "time" 14 15 "gioui.org/ui" 16 "gioui.org/ui/app/internal/gl" 17 "gioui.org/ui/f32" 18 "gioui.org/ui/internal/opconst" 19 "gioui.org/ui/internal/ops" 20 "gioui.org/ui/paint" 21 "golang.org/x/image/draw" 22 ) 23 24 type GPU struct { 25 drawing bool 26 summary string 27 err error 28 29 pathCache *opCache 30 cache *resourceCache 31 32 frames chan frame 33 results chan frameResult 34 refresh chan struct{} 35 refreshErr chan error 36 ack chan struct{} 37 stop chan struct{} 38 stopped chan struct{} 39 ops drawOps 40 } 41 42 type frame struct { 43 collectStats bool 44 viewport image.Point 45 ops drawOps 46 } 47 48 type frameResult struct { 49 summary string 50 err error 51 } 52 53 type renderer struct { 54 ctx *context 55 blitter *blitter 56 pather *pather 57 packer packer 58 intersections packer 59 } 60 61 type drawOps struct { 62 reader ops.Reader 63 cache *resourceCache 64 viewport image.Point 65 clearColor [3]float32 66 imageOps []imageOp 67 // zimageOps are the rectangle clipped opaque images 68 // that can use fast front-to-back rendering with z-test 69 // and no blending. 70 zimageOps []imageOp 71 pathOps []*pathOp 72 pathOpCache []pathOp 73 } 74 75 type drawState struct { 76 clip f32.Rectangle 77 t ui.TransformOp 78 cpath *pathOp 79 rect bool 80 z int 81 82 // Current ImageOp image and rect, if any. 83 img image.Image 84 imgRect image.Rectangle 85 // Current ColorOp, if any. 86 color color.RGBA 87 } 88 89 type pathOp struct { 90 off f32.Point 91 // clip is the union of all 92 // later clip rectangles. 93 clip image.Rectangle 94 pathKey ops.Key 95 path bool 96 pathVerts []byte 97 parent *pathOp 98 place placement 99 } 100 101 type imageOp struct { 102 z float32 103 path *pathOp 104 off f32.Point 105 clip image.Rectangle 106 material material 107 clipType clipType 108 place placement 109 } 110 111 type material struct { 112 material materialType 113 opaque bool 114 // For materialTypeColor. 115 color [4]float32 116 // For materialTypeTexture. 117 texture *texture 118 uvScale f32.Point 119 uvOffset f32.Point 120 } 121 122 // clipOp is the shadow of draw.ClipOp. 123 type clipOp struct { 124 bounds f32.Rectangle 125 } 126 127 func (op *clipOp) decode(data []byte) { 128 if opconst.OpType(data[0]) != opconst.TypeClip { 129 panic("invalid op") 130 } 131 bo := binary.LittleEndian 132 r := f32.Rectangle{ 133 Min: f32.Point{ 134 X: math.Float32frombits(bo.Uint32(data[1:])), 135 Y: math.Float32frombits(bo.Uint32(data[5:])), 136 }, 137 Max: f32.Point{ 138 X: math.Float32frombits(bo.Uint32(data[9:])), 139 Y: math.Float32frombits(bo.Uint32(data[13:])), 140 }, 141 } 142 *op = clipOp{ 143 bounds: r, 144 } 145 } 146 147 func decodeImageOp(data []byte, refs []interface{}) paint.ImageOp { 148 bo := binary.LittleEndian 149 if opconst.OpType(data[0]) != opconst.TypeImage { 150 panic("invalid op") 151 } 152 sr := image.Rectangle{ 153 Min: image.Point{ 154 X: int(int32(bo.Uint32(data[1:]))), 155 Y: int(int32(bo.Uint32(data[5:]))), 156 }, 157 Max: image.Point{ 158 X: int(int32(bo.Uint32(data[9:]))), 159 Y: int(int32(bo.Uint32(data[13:]))), 160 }, 161 } 162 return paint.ImageOp{ 163 Src: refs[0].(image.Image), 164 Rect: sr, 165 } 166 } 167 168 func decodeColorOp(data []byte) paint.ColorOp { 169 if opconst.OpType(data[0]) != opconst.TypeColor { 170 panic("invalid op") 171 } 172 return paint.ColorOp{ 173 Color: color.RGBA{ 174 R: data[1], 175 G: data[2], 176 B: data[3], 177 A: data[4], 178 }, 179 } 180 } 181 182 func decodePaintOp(data []byte) paint.PaintOp { 183 bo := binary.LittleEndian 184 if opconst.OpType(data[0]) != opconst.TypePaint { 185 panic("invalid op") 186 } 187 r := f32.Rectangle{ 188 Min: f32.Point{ 189 X: math.Float32frombits(bo.Uint32(data[1:])), 190 Y: math.Float32frombits(bo.Uint32(data[5:])), 191 }, 192 Max: f32.Point{ 193 X: math.Float32frombits(bo.Uint32(data[9:])), 194 Y: math.Float32frombits(bo.Uint32(data[13:])), 195 }, 196 } 197 return paint.PaintOp{ 198 Rect: r, 199 } 200 } 201 202 type clipType uint8 203 204 type resource interface { 205 release(ctx *context) 206 } 207 208 type texture struct { 209 src image.Image 210 id gl.Texture 211 } 212 213 type blitter struct { 214 ctx *context 215 viewport image.Point 216 prog [2]gl.Program 217 vars [2]struct { 218 z gl.Uniform 219 uScale, uOffset gl.Uniform 220 uUVScale, uUVOffset gl.Uniform 221 uColor gl.Uniform 222 } 223 quadVerts gl.Buffer 224 } 225 226 type materialType uint8 227 228 const ( 229 clipTypeNone clipType = iota 230 clipTypePath 231 clipTypeIntersection 232 ) 233 234 const ( 235 materialTexture materialType = iota 236 materialColor 237 ) 238 239 var ( 240 blitAttribs = []string{"pos", "uv"} 241 attribPos gl.Attrib = 0 242 attribUV gl.Attrib = 1 243 ) 244 245 func NewGPU(ctx gl.Context) (*GPU, error) { 246 g := &GPU{ 247 frames: make(chan frame), 248 results: make(chan frameResult), 249 refresh: make(chan struct{}), 250 refreshErr: make(chan error), 251 ack: make(chan struct{}), 252 stop: make(chan struct{}), 253 stopped: make(chan struct{}), 254 pathCache: newOpCache(), 255 cache: newResourceCache(), 256 } 257 if err := g.renderLoop(ctx); err != nil { 258 return nil, err 259 } 260 return g, nil 261 } 262 263 func (g *GPU) renderLoop(glctx gl.Context) error { 264 // GL Operations must happen on a single OS thread, so 265 // pass initialization result through a channel. 266 initErr := make(chan error) 267 go func() { 268 runtime.LockOSThread() 269 // Don't UnlockOSThread to avoid reuse by the Go runtime. 270 defer close(g.stopped) 271 defer glctx.Release() 272 273 if err := glctx.MakeCurrent(); err != nil { 274 initErr <- err 275 return 276 } 277 ctx, err := newContext(glctx) 278 if err != nil { 279 initErr <- err 280 return 281 } 282 defer g.cache.release(ctx) 283 defer g.pathCache.release(ctx) 284 r := newRenderer(ctx) 285 defer r.release() 286 var timers *timers 287 var zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer 288 initErr <- nil 289 loop: 290 for { 291 select { 292 case <-g.refresh: 293 g.refreshErr <- glctx.MakeCurrent() 294 case frame := <-g.frames: 295 glctx.Lock() 296 if frame.collectStats && timers == nil && ctx.caps.EXT_disjoint_timer_query { 297 timers = newTimers(ctx) 298 zopsTimer = timers.newTimer() 299 stencilTimer = timers.newTimer() 300 coverTimer = timers.newTimer() 301 cleanupTimer = timers.newTimer() 302 defer timers.release() 303 } 304 ops := frame.ops 305 // Upload path data to GPU before ack'ing the frame data for re-use. 306 for _, p := range ops.pathOps { 307 if _, exists := g.pathCache.get(p.pathKey); !exists { 308 data := buildPath(r.ctx, p.pathVerts) 309 g.pathCache.put(p.pathKey, data) 310 } 311 p.pathVerts = nil 312 } 313 g.ack <- struct{}{} 314 r.blitter.viewport = frame.viewport 315 r.pather.viewport = frame.viewport 316 for _, img := range ops.imageOps { 317 expandPathOp(img.path, img.clip) 318 } 319 if frame.collectStats { 320 zopsTimer.begin() 321 } 322 ctx.DepthFunc(gl.GREATER) 323 ctx.ClearColor(ops.clearColor[0], ops.clearColor[1], ops.clearColor[2], 1.0) 324 ctx.ClearDepthf(0.0) 325 ctx.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 326 ctx.Viewport(0, 0, frame.viewport.X, frame.viewport.Y) 327 r.drawZOps(ops.zimageOps) 328 zopsTimer.end() 329 stencilTimer.begin() 330 ctx.Enable(gl.BLEND) 331 r.packStencils(&ops.pathOps) 332 r.stencilClips(g.pathCache, ops.pathOps) 333 r.packIntersections(ops.imageOps) 334 r.intersect(ops.imageOps) 335 stencilTimer.end() 336 coverTimer.begin() 337 ctx.Viewport(0, 0, frame.viewport.X, frame.viewport.Y) 338 r.drawOps(ops.imageOps) 339 ctx.Disable(gl.BLEND) 340 r.pather.stenciler.invalidateFBO() 341 coverTimer.end() 342 err := glctx.Present() 343 cleanupTimer.begin() 344 g.cache.frame(ctx) 345 g.pathCache.frame(ctx) 346 cleanupTimer.end() 347 var res frameResult 348 if frame.collectStats && timers.ready() { 349 zt, st, covt, cleant := zopsTimer.Elapsed, stencilTimer.Elapsed, coverTimer.Elapsed, cleanupTimer.Elapsed 350 ft := zt + st + covt + cleant 351 q := 100 * time.Microsecond 352 zt, st, covt = zt.Round(q), st.Round(q), covt.Round(q) 353 ft = ft.Round(q) 354 res.summary = fmt.Sprintf("f:%7s zt:%7s st:%7s cov:%7s", ft, zt, st, covt) 355 } 356 res.err = err 357 glctx.Unlock() 358 g.results <- res 359 case <-g.stop: 360 break loop 361 } 362 } 363 }() 364 return <-initErr 365 } 366 367 func (g *GPU) Release() { 368 // Flush error. 369 g.Flush() 370 close(g.stop) 371 <-g.stopped 372 g.stop = nil 373 } 374 375 func (g *GPU) Flush() error { 376 if g.drawing { 377 st := <-g.results 378 g.setErr(st.err) 379 if st.summary != "" { 380 g.summary = st.summary 381 } 382 g.drawing = false 383 } 384 return g.err 385 } 386 387 func (g *GPU) Timings() string { 388 return g.summary 389 } 390 391 func (g *GPU) Refresh() { 392 if g.err != nil { 393 return 394 } 395 // Make sure any pending frame is complete. 396 g.Flush() 397 g.refresh <- struct{}{} 398 g.setErr(<-g.refreshErr) 399 } 400 401 func (g *GPU) Draw(profile bool, viewport image.Point, root *ui.Ops) { 402 if g.err != nil { 403 return 404 } 405 g.Flush() 406 g.ops.reset(g.cache, viewport) 407 g.ops.collect(g.cache, root, viewport) 408 g.frames <- frame{profile, viewport, g.ops} 409 <-g.ack 410 g.drawing = true 411 } 412 413 func (g *GPU) setErr(err error) { 414 if g.err == nil { 415 g.err = err 416 } 417 } 418 419 func (r *renderer) texHandle(t *texture) gl.Texture { 420 if t.id != (gl.Texture{}) { 421 return t.id 422 } 423 t.id = createTexture(r.ctx) 424 r.ctx.BindTexture(gl.TEXTURE_2D, t.id) 425 r.uploadTexture(t.src) 426 return t.id 427 } 428 429 func (t *texture) release(ctx *context) { 430 if t.id != (gl.Texture{}) { 431 ctx.DeleteTexture(t.id) 432 } 433 } 434 435 func newRenderer(ctx *context) *renderer { 436 r := &renderer{ 437 ctx: ctx, 438 blitter: newBlitter(ctx), 439 pather: newPather(ctx), 440 } 441 r.packer.maxDim = ctx.GetInteger(gl.MAX_TEXTURE_SIZE) 442 r.intersections.maxDim = r.packer.maxDim 443 return r 444 } 445 446 func (r *renderer) release() { 447 r.pather.release() 448 r.blitter.release() 449 } 450 451 func newBlitter(ctx *context) *blitter { 452 prog, err := createColorPrograms(ctx, blitVSrc, blitFSrc) 453 if err != nil { 454 panic(err) 455 } 456 quadVerts := ctx.CreateBuffer() 457 ctx.BindBuffer(gl.ARRAY_BUFFER, quadVerts) 458 ctx.BufferData(gl.ARRAY_BUFFER, 459 gl.BytesView([]float32{ 460 -1, +1, 0, 0, 461 +1, +1, 1, 0, 462 -1, -1, 0, 1, 463 +1, -1, 1, 1, 464 }), 465 gl.STATIC_DRAW) 466 b := &blitter{ 467 ctx: ctx, 468 prog: prog, 469 quadVerts: quadVerts, 470 } 471 for i, prog := range prog { 472 ctx.UseProgram(prog) 473 switch materialType(i) { 474 case materialTexture: 475 uTex := gl.GetUniformLocation(ctx.Functions, prog, "tex") 476 ctx.Uniform1i(uTex, 0) 477 b.vars[i].uUVScale = gl.GetUniformLocation(ctx.Functions, prog, "uvScale") 478 b.vars[i].uUVOffset = gl.GetUniformLocation(ctx.Functions, prog, "uvOffset") 479 case materialColor: 480 b.vars[i].uColor = gl.GetUniformLocation(ctx.Functions, prog, "color") 481 } 482 b.vars[i].z = gl.GetUniformLocation(ctx.Functions, prog, "z") 483 b.vars[i].uScale = gl.GetUniformLocation(ctx.Functions, prog, "scale") 484 b.vars[i].uOffset = gl.GetUniformLocation(ctx.Functions, prog, "offset") 485 } 486 return b 487 } 488 489 func (b *blitter) release() { 490 b.ctx.DeleteBuffer(b.quadVerts) 491 for _, p := range b.prog { 492 b.ctx.DeleteProgram(p) 493 } 494 } 495 496 func createColorPrograms(ctx *context, vsSrc, fsSrc string) ([2]gl.Program, error) { 497 var prog [2]gl.Program 498 frep := strings.NewReplacer( 499 "HEADER", ` 500 uniform sampler2D tex; 501 `, 502 "GET_COLOR", `texture2D(tex, vUV)`, 503 ) 504 fsSrcTex := frep.Replace(fsSrc) 505 var err error 506 prog[materialTexture], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcTex, blitAttribs) 507 if err != nil { 508 return prog, err 509 } 510 frep = strings.NewReplacer( 511 "HEADER", ` 512 uniform vec4 color; 513 `, 514 "GET_COLOR", `color`, 515 ) 516 fsSrcCol := frep.Replace(fsSrc) 517 prog[materialColor], err = gl.CreateProgram(ctx.Functions, vsSrc, fsSrcCol, blitAttribs) 518 if err != nil { 519 ctx.DeleteProgram(prog[materialTexture]) 520 return prog, err 521 } 522 return prog, nil 523 } 524 525 func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) { 526 if len(r.packer.sizes) == 0 { 527 return 528 } 529 fbo := -1 530 r.pather.begin(r.packer.sizes) 531 for _, p := range ops { 532 if fbo != p.place.Idx { 533 fbo = p.place.Idx 534 f := r.pather.stenciler.cover(fbo) 535 bindFramebuffer(r.ctx, f.fbo) 536 r.ctx.Clear(gl.COLOR_BUFFER_BIT) 537 } 538 data, _ := pathCache.get(p.pathKey) 539 r.pather.stencilPath(p.clip, p.off, p.place.Pos, data.(*pathData)) 540 } 541 r.pather.end() 542 } 543 544 func (r *renderer) intersect(ops []imageOp) { 545 if len(r.intersections.sizes) == 0 { 546 return 547 } 548 fbo := -1 549 r.pather.stenciler.beginIntersect(r.intersections.sizes) 550 r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) 551 r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) 552 r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) 553 r.ctx.EnableVertexAttribArray(attribPos) 554 r.ctx.EnableVertexAttribArray(attribUV) 555 for _, img := range ops { 556 if img.clipType != clipTypeIntersection { 557 continue 558 } 559 if fbo != img.place.Idx { 560 fbo = img.place.Idx 561 f := r.pather.stenciler.intersections.fbos[fbo] 562 bindFramebuffer(r.ctx, f.fbo) 563 r.ctx.Clear(gl.COLOR_BUFFER_BIT) 564 } 565 r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy()) 566 r.intersectPath(img.path, img.clip) 567 } 568 r.ctx.DisableVertexAttribArray(attribPos) 569 r.ctx.DisableVertexAttribArray(attribUV) 570 r.pather.stenciler.endIntersect() 571 } 572 573 func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) { 574 if p.parent != nil { 575 r.intersectPath(p.parent, clip) 576 } 577 if !p.path { 578 return 579 } 580 o := p.place.Pos.Add(clip.Min).Sub(p.clip.Min) 581 uv := image.Rectangle{ 582 Min: o, 583 Max: o.Add(clip.Size()), 584 } 585 fbo := r.pather.stenciler.cover(p.place.Idx) 586 r.ctx.BindTexture(gl.TEXTURE_2D, fbo.tex) 587 coverScale, coverOff := texSpaceTransform(uv, fbo.size) 588 r.ctx.Uniform2f(r.pather.stenciler.uIntersectUVScale, coverScale.X, coverScale.Y) 589 r.ctx.Uniform2f(r.pather.stenciler.uIntersectUVOffset, coverOff.X, coverOff.Y) 590 r.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) 591 } 592 593 func (r *renderer) packIntersections(ops []imageOp) { 594 r.intersections.clear() 595 for i, img := range ops { 596 var npaths int 597 var onePath *pathOp 598 for p := img.path; p != nil; p = p.parent { 599 if p.path { 600 onePath = p 601 npaths++ 602 } 603 } 604 switch npaths { 605 case 0: 606 case 1: 607 place := onePath.place 608 place.Pos = place.Pos.Sub(onePath.clip.Min).Add(img.clip.Min) 609 ops[i].place = place 610 ops[i].clipType = clipTypePath 611 default: 612 sz := image.Point{X: img.clip.Dx(), Y: img.clip.Dy()} 613 place, ok := r.intersections.add(sz) 614 if !ok { 615 panic("internal error: if the intersection fit, the intersection should fit as well") 616 } 617 ops[i].clipType = clipTypeIntersection 618 ops[i].place = place 619 } 620 } 621 } 622 623 func (r *renderer) packStencils(pops *[]*pathOp) { 624 r.packer.clear() 625 ops := *pops 626 // Allocate atlas space for cover textures. 627 var i int 628 for i < len(ops) { 629 p := ops[i] 630 if p.clip.Empty() { 631 ops[i] = ops[len(ops)-1] 632 ops = ops[:len(ops)-1] 633 continue 634 } 635 sz := image.Point{X: p.clip.Dx(), Y: p.clip.Dy()} 636 place, ok := r.packer.add(sz) 637 if !ok { 638 // The clip area is at most the entire screen. Hopefully no 639 // screen is larger than GL_MAX_TEXTURE_SIZE. 640 panic(fmt.Errorf("clip area %v is larger than maximum texture size %dx%d", p.clip, r.packer.maxDim, r.packer.maxDim)) 641 } 642 p.place = place 643 i++ 644 } 645 *pops = ops 646 } 647 648 // intersects intersects clip and b where b is offset by off. 649 // ceilRect returns a bounding image.Rectangle for a f32.Rectangle. 650 func boundRectF(r f32.Rectangle) image.Rectangle { 651 return image.Rectangle{ 652 Min: image.Point{ 653 X: int(floor(r.Min.X)), 654 Y: int(floor(r.Min.Y)), 655 }, 656 Max: image.Point{ 657 X: int(ceil(r.Max.X)), 658 Y: int(ceil(r.Max.Y)), 659 }, 660 } 661 } 662 663 func ceil(v float32) int { 664 return int(math.Ceil(float64(v))) 665 } 666 667 func floor(v float32) int { 668 return int(math.Floor(float64(v))) 669 } 670 671 func (d *drawOps) reset(cache *resourceCache, viewport image.Point) { 672 d.clearColor = [3]float32{1.0, 1.0, 1.0} 673 d.cache = cache 674 d.viewport = viewport 675 d.imageOps = d.imageOps[:0] 676 d.zimageOps = d.zimageOps[:0] 677 d.pathOps = d.pathOps[:0] 678 d.pathOpCache = d.pathOpCache[:0] 679 } 680 681 func (d *drawOps) collect(cache *resourceCache, root *ui.Ops, viewport image.Point) { 682 d.reset(cache, viewport) 683 clip := f32.Rectangle{ 684 Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)}, 685 } 686 d.reader.Reset(root) 687 state := drawState{ 688 clip: clip, 689 rect: true, 690 color: color.RGBA{A: 0xff}, 691 } 692 d.collectOps(&d.reader, state) 693 } 694 695 func (d *drawOps) newPathOp() *pathOp { 696 d.pathOpCache = append(d.pathOpCache, pathOp{}) 697 return &d.pathOpCache[len(d.pathOpCache)-1] 698 } 699 700 func (d *drawOps) collectOps(r *ops.Reader, state drawState) int { 701 var aux []byte 702 var auxKey ops.Key 703 loop: 704 for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { 705 switch opconst.OpType(encOp.Data[0]) { 706 case opconst.TypeTransform: 707 op := ops.DecodeTransformOp(encOp.Data) 708 state.t = state.t.Multiply(ui.TransformOp(op)) 709 case opconst.TypeAux: 710 aux = encOp.Data[opconst.TypeAuxLen:] 711 auxKey = encOp.Key 712 case opconst.TypeClip: 713 var op clipOp 714 op.decode(encOp.Data) 715 off := state.t.Transform(f32.Point{}) 716 state.clip = state.clip.Intersect(op.bounds.Add(off)) 717 if state.clip.Empty() { 718 continue 719 } 720 npath := d.newPathOp() 721 *npath = pathOp{ 722 parent: state.cpath, 723 off: off, 724 } 725 state.cpath = npath 726 if len(aux) > 0 { 727 state.rect = false 728 state.cpath.pathKey = auxKey 729 state.cpath.path = true 730 state.cpath.pathVerts = aux 731 d.pathOps = append(d.pathOps, state.cpath) 732 } 733 aux = nil 734 auxKey = ops.Key{} 735 case opconst.TypeColor: 736 op := decodeColorOp(encOp.Data) 737 state.img = nil 738 state.color = op.Color 739 case opconst.TypeImage: 740 op := decodeImageOp(encOp.Data, encOp.Refs) 741 state.img = op.Src 742 state.imgRect = op.Rect 743 case opconst.TypePaint: 744 op := decodePaintOp(encOp.Data) 745 off := state.t.Transform(f32.Point{}) 746 clip := state.clip.Intersect(op.Rect.Add(off)) 747 if clip.Empty() { 748 continue 749 } 750 bounds := boundRectF(clip) 751 mat := state.materialFor(d.cache, op.Rect, off, bounds) 752 if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && mat.material == materialColor { 753 // The image is a uniform opaque color and takes up the whole screen. 754 // Scrap images up to and including this image and set clear color. 755 d.zimageOps = d.zimageOps[:0] 756 d.imageOps = d.imageOps[:0] 757 state.z = 0 758 copy(d.clearColor[:], mat.color[:3]) 759 continue 760 } 761 state.z++ 762 // Assume 16-bit depth buffer. 763 const zdepth = 1 << 16 764 // Convert z to window-space, assuming depth range [0;1]. 765 zf := float32(state.z)*2/zdepth - 1.0 766 img := imageOp{ 767 z: zf, 768 path: state.cpath, 769 off: off, 770 clip: bounds, 771 material: mat, 772 } 773 if state.rect && img.material.opaque { 774 d.zimageOps = append(d.zimageOps, img) 775 } else { 776 d.imageOps = append(d.imageOps, img) 777 } 778 case opconst.TypePush: 779 state.z = d.collectOps(r, state) 780 case opconst.TypePop: 781 break loop 782 } 783 } 784 return state.z 785 } 786 787 func expandPathOp(p *pathOp, clip image.Rectangle) { 788 for p != nil { 789 pclip := p.clip 790 if !pclip.Empty() { 791 clip = clip.Union(pclip) 792 } 793 p.clip = clip 794 p = p.parent 795 } 796 } 797 798 func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f32.Point, clip image.Rectangle) material { 799 var m material 800 if d.img == nil { 801 m.material = materialColor 802 m.color = gamma(d.color.RGBA()) 803 m.opaque = m.color[3] == 1.0 804 } else if uniform, ok := d.img.(*image.Uniform); ok { 805 m.material = materialColor 806 m.color = gamma(uniform.RGBA()) 807 m.opaque = m.color[3] == 1.0 808 } else { 809 m.material = materialTexture 810 dr := boundRectF(rect.Add(off)) 811 sr := d.imgRect 812 if dx := dr.Dx(); dx != 0 { 813 // Don't clip 1 px width sources. 814 if sdx := sr.Dx(); sdx > 1 { 815 sr.Min.X += ((clip.Min.X-dr.Min.X)*sdx + dx/2) / dx 816 sr.Max.X -= ((dr.Max.X-clip.Max.X)*sdx + dx/2) / dx 817 } 818 } 819 if dy := dr.Dy(); dy != 0 { 820 // Don't clip 1 px height sources. 821 if sdy := sr.Dy(); sdy > 1 { 822 sr.Min.Y += ((clip.Min.Y-dr.Min.Y)*sdy + dy/2) / dy 823 sr.Max.Y -= ((dr.Max.Y-clip.Max.Y)*sdy + dy/2) / dy 824 } 825 } 826 tex, exists := cache.get(d.img) 827 if !exists { 828 t := &texture{ 829 src: d.img, 830 } 831 cache.put(d.img, t) 832 tex = t 833 } 834 m.texture = tex.(*texture) 835 m.uvScale, m.uvOffset = texSpaceTransform(sr, d.img.Bounds().Size()) 836 } 837 return m 838 } 839 840 func (r *renderer) drawZOps(ops []imageOp) { 841 r.ctx.Enable(gl.DEPTH_TEST) 842 r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) 843 r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) 844 r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) 845 r.ctx.EnableVertexAttribArray(attribPos) 846 r.ctx.EnableVertexAttribArray(attribUV) 847 // Render front to back. 848 for i := len(ops) - 1; i >= 0; i-- { 849 img := ops[i] 850 m := img.material 851 switch m.material { 852 case materialTexture: 853 r.ctx.BindTexture(gl.TEXTURE_2D, r.texHandle(m.texture)) 854 } 855 drc := img.clip 856 scale, off := clipSpaceTransform(drc, r.blitter.viewport) 857 r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset) 858 } 859 r.ctx.DisableVertexAttribArray(attribPos) 860 r.ctx.DisableVertexAttribArray(attribUV) 861 r.ctx.Disable(gl.DEPTH_TEST) 862 } 863 864 func (r *renderer) drawOps(ops []imageOp) { 865 r.ctx.Enable(gl.DEPTH_TEST) 866 r.ctx.DepthMask(false) 867 r.ctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) 868 r.ctx.BindBuffer(gl.ARRAY_BUFFER, r.blitter.quadVerts) 869 r.ctx.VertexAttribPointer(attribPos, 2, gl.FLOAT, false, 4*4, 0) 870 r.ctx.VertexAttribPointer(attribUV, 2, gl.FLOAT, false, 4*4, 4*2) 871 r.ctx.EnableVertexAttribArray(attribPos) 872 r.ctx.EnableVertexAttribArray(attribUV) 873 var coverTex gl.Texture 874 for _, img := range ops { 875 m := img.material 876 switch m.material { 877 case materialTexture: 878 r.ctx.BindTexture(gl.TEXTURE_2D, r.texHandle(m.texture)) 879 } 880 drc := img.clip 881 scale, off := clipSpaceTransform(drc, r.blitter.viewport) 882 var fbo stencilFBO 883 switch img.clipType { 884 case clipTypeNone: 885 r.blitter.blit(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset) 886 continue 887 case clipTypePath: 888 fbo = r.pather.stenciler.cover(img.place.Idx) 889 case clipTypeIntersection: 890 fbo = r.pather.stenciler.intersections.fbos[img.place.Idx] 891 } 892 if coverTex != fbo.tex { 893 coverTex = fbo.tex 894 r.ctx.ActiveTexture(gl.TEXTURE1) 895 r.ctx.BindTexture(gl.TEXTURE_2D, coverTex) 896 r.ctx.ActiveTexture(gl.TEXTURE0) 897 } 898 uv := image.Rectangle{ 899 Min: img.place.Pos, 900 Max: img.place.Pos.Add(drc.Size()), 901 } 902 coverScale, coverOff := texSpaceTransform(uv, fbo.size) 903 r.pather.cover(img.z, m.material, m.color, scale, off, m.uvScale, m.uvOffset, coverScale, coverOff) 904 } 905 r.ctx.DisableVertexAttribArray(attribPos) 906 r.ctx.DisableVertexAttribArray(attribUV) 907 r.ctx.DepthMask(true) 908 r.ctx.Disable(gl.DEPTH_TEST) 909 } 910 911 func (r *renderer) uploadTexture(img image.Image) { 912 var pixels []byte 913 b := img.Bounds() 914 w, h := b.Dx(), b.Dy() 915 switch img := img.(type) { 916 case *image.RGBA: 917 if img.Stride == w*4 { 918 start := (b.Min.X + b.Min.Y*w) * 4 919 end := (b.Max.X + (b.Max.Y-1)*w) * 4 920 pixels = img.Pix[start:end] 921 } else { 922 pixels = copyImage(img, b).Pix 923 } 924 default: 925 pixels = copyImage(img, b).Pix 926 } 927 tt := r.ctx.caps.srgbaTriple 928 r.ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, w, h, tt.format, tt.typ, pixels) 929 930 } 931 932 func gamma(r, g, b, a uint32) [4]float32 { 933 color := [4]float32{float32(r) / 0xffff, float32(g) / 0xffff, float32(b) / 0xffff, float32(a) / 0xffff} 934 // Assume that image.Uniform colors are in sRGB space. Linearize. 935 for i, c := range color { 936 // Use the formula from EXT_sRGB. 937 if c <= 0.04045 { 938 c = c / 12.92 939 } else { 940 c = float32(math.Pow(float64((c+0.055)/1.055), 2.4)) 941 } 942 color[i] = c 943 } 944 return color 945 } 946 947 func (b *blitter) blit(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff f32.Point) { 948 b.ctx.UseProgram(b.prog[mat]) 949 switch mat { 950 case materialColor: 951 b.ctx.Uniform4f(b.vars[mat].uColor, col[0], col[1], col[2], col[3]) 952 case materialTexture: 953 b.ctx.Uniform2f(b.vars[mat].uUVScale, uvScale.X, uvScale.Y) 954 b.ctx.Uniform2f(b.vars[mat].uUVOffset, uvOff.X, uvOff.Y) 955 } 956 b.ctx.Uniform1f(b.vars[mat].z, z) 957 b.ctx.Uniform2f(b.vars[mat].uScale, scale.X, scale.Y) 958 b.ctx.Uniform2f(b.vars[mat].uOffset, off.X, off.Y) 959 b.ctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) 960 } 961 962 // texSpaceTransform return the scale and offset that transforms the given subimage 963 // into quad texture coordinates. 964 func texSpaceTransform(r image.Rectangle, bounds image.Point) (f32.Point, f32.Point) { 965 size := f32.Point{X: float32(bounds.X), Y: float32(bounds.Y)} 966 scale := f32.Point{X: float32(r.Dx()) / size.X, Y: float32(r.Dy()) / size.Y} 967 offset := f32.Point{X: float32(r.Min.X) / size.X, Y: float32(r.Min.Y) / size.Y} 968 return scale, offset 969 } 970 971 // clipSpaceTransform returns the scale and offset that transforms the given 972 // rectangle from a viewport into OpenGL clip space. 973 func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) { 974 // First, transform UI coordinates to OpenGL coordinates: 975 // 976 // [(-1, +1) (+1, +1)] 977 // [(-1, -1) (+1, -1)] 978 // 979 x, y := float32(r.Min.X), float32(r.Min.Y) 980 w, h := float32(r.Dx()), float32(r.Dy()) 981 vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y) 982 x = x*vx - 1 983 y = 1 - y*vy 984 w *= vx 985 h *= vy 986 987 // Then, compute the transformation from the fullscreen quad to 988 // the rectangle at (x, y) and dimensions (w, h). 989 scale := f32.Point{X: w * .5, Y: h * .5} 990 offset := f32.Point{X: x + w*.5, Y: y - h*.5} 991 return scale, offset 992 } 993 994 func bindFramebuffer(ctx *context, fbo gl.Framebuffer) { 995 ctx.BindFramebuffer(gl.FRAMEBUFFER, fbo) 996 if st := ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE { 997 panic(fmt.Errorf("AA FBO not complete; status = 0x%x, err = %d", st, ctx.GetError())) 998 } 999 } 1000 1001 func createTexture(ctx *context) gl.Texture { 1002 tex := ctx.CreateTexture() 1003 ctx.BindTexture(gl.TEXTURE_2D, tex) 1004 ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 1005 ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 1006 ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 1007 ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 1008 return tex 1009 } 1010 1011 func copyImage(img image.Image, r image.Rectangle) *image.RGBA { 1012 tmp := image.NewRGBA(r) 1013 draw.Draw(tmp, r, img, r.Min, draw.Src) 1014 return tmp 1015 } 1016 1017 const blitVSrc = ` 1018 #version 100 1019 1020 precision highp float; 1021 1022 uniform float z; 1023 uniform vec2 scale; 1024 uniform vec2 offset; 1025 1026 attribute vec2 pos; 1027 1028 attribute vec2 uv; 1029 uniform vec2 uvScale; 1030 uniform vec2 uvOffset; 1031 1032 varying vec2 vUV; 1033 1034 void main() { 1035 vec2 p = pos; 1036 p *= scale; 1037 p += offset; 1038 gl_Position = vec4(p, z, 1); 1039 vUV = uv*uvScale + uvOffset; 1040 } 1041 ` 1042 1043 const blitFSrc = ` 1044 #version 100 1045 1046 precision mediump float; 1047 1048 varying vec2 vUV; 1049 1050 HEADER 1051 1052 void main() { 1053 gl_FragColor = GET_COLOR; 1054 } 1055 `