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