gpu: remove opaque rectangle optimization

The default renderer tracks opaque rectangle draw operations and render
them front-to-back with a z-buffer to omit overdraw. However,

- Overlapping opaque rectangles are rare in a GUI, and the most common
instance, a solid window background, is already optimized to a glClear.
- A z-buffer is memory heavy.
- We conservatively assume a 16-bit depth buffer, which limits the
  number of drawing operations to 64k (#127).
- Depth buffer support makes GPU ports more complex, especially for
  upcoming ports (Metal, Vulkan).
- The compute renderer doesn't use z-buffers.

This change removes the optimization; a follow-up removes GPU backend
support.

Fixes gio#127

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-08-06 16:19:22 +02:00
parent 2059862416
commit cea8e21f97
2 changed files with 18 additions and 65 deletions
+15 -61
View File
@@ -53,13 +53,13 @@ type GPU interface {
type gpu struct { type gpu struct {
cache *resourceCache cache *resourceCache
profile string profile string
timers *timers timers *timers
frameStart time.Time frameStart time.Time
zopsTimer, stencilTimer, coverTimer, cleanupTimer *timer stencilTimer, coverTimer, cleanupTimer *timer
drawOps drawOps drawOps drawOps
ctx driver.Device ctx driver.Device
renderer *renderer renderer *renderer
} }
type renderer struct { type renderer struct {
@@ -83,7 +83,6 @@ type drawOps struct {
// zimageOps are the rectangle clipped opaque images // zimageOps are the rectangle clipped opaque images
// that can use fast front-to-back rendering with z-test // that can use fast front-to-back rendering with z-test
// and no blending. // and no blending.
zimageOps []imageOp
pathOps []*pathOp pathOps []*pathOp
pathOpCache []pathOp pathOpCache []pathOp
qs quadSplitter qs quadSplitter
@@ -123,7 +122,6 @@ type pathOp struct {
} }
type imageOp struct { type imageOp struct {
z float32
path *pathOp path *pathOp
clip image.Rectangle clip image.Rectangle
material material material material
@@ -407,7 +405,6 @@ func (g *gpu) Collect(viewport image.Point, frameOps *op.Ops) {
g.frameStart = time.Now() g.frameStart = time.Now()
if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) { if g.drawOps.profile && g.timers == nil && g.ctx.Caps().Features.Has(driver.FeatureTimers) {
g.timers = newTimers(g.ctx) g.timers = newTimers(g.ctx)
g.zopsTimer = g.timers.newTimer()
g.stencilTimer = g.timers.newTimer() g.stencilTimer = g.timers.newTimer()
g.coverTimer = g.timers.newTimer() g.coverTimer = g.timers.newTimer()
g.cleanupTimer = g.timers.newTimer() g.cleanupTimer = g.timers.newTimer()
@@ -421,9 +418,6 @@ func (g *gpu) Frame(target RenderTarget) error {
for _, img := range g.drawOps.imageOps { for _, img := range g.drawOps.imageOps {
expandPathOp(img.path, img.clip) expandPathOp(img.path, img.clip)
} }
if g.drawOps.profile {
g.zopsTimer.begin()
}
g.ctx.BindFramebuffer(defFBO) g.ctx.BindFramebuffer(defFBO)
g.ctx.DepthFunc(driver.DepthFuncGreater) g.ctx.DepthFunc(driver.DepthFuncGreater)
// Note that Clear must be before ClearDepth if nothing else is rendered // Note that Clear must be before ClearDepth if nothing else is rendered
@@ -434,8 +428,6 @@ func (g *gpu) Frame(target RenderTarget) error {
} }
g.ctx.ClearDepth(0.0) g.ctx.ClearDepth(0.0)
g.ctx.Viewport(0, 0, viewport.X, viewport.Y) g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.renderer.drawZOps(g.cache, g.drawOps.zimageOps)
g.zopsTimer.end()
g.stencilTimer.begin() g.stencilTimer.begin()
g.ctx.SetBlend(true) g.ctx.SetBlend(true)
g.renderer.packStencils(&g.drawOps.pathOps) g.renderer.packStencils(&g.drawOps.pathOps)
@@ -456,13 +448,13 @@ func (g *gpu) Frame(target RenderTarget) error {
g.drawOps.pathCache.frame() g.drawOps.pathCache.frame()
g.cleanupTimer.end() g.cleanupTimer.end()
if g.drawOps.profile && g.timers.ready() { if g.drawOps.profile && g.timers.ready() {
zt, st, covt, cleant := g.zopsTimer.Elapsed, g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed st, covt, cleant := g.stencilTimer.Elapsed, g.coverTimer.Elapsed, g.cleanupTimer.Elapsed
ft := zt + st + covt + cleant ft := st + covt + cleant
q := 100 * time.Microsecond q := 100 * time.Microsecond
zt, st, covt = zt.Round(q), st.Round(q), covt.Round(q) st, covt = st.Round(q), covt.Round(q)
frameDur := time.Since(g.frameStart).Round(q) frameDur := time.Since(g.frameStart).Round(q)
ft = ft.Round(q) ft = ft.Round(q)
g.profile = fmt.Sprintf("draw:%7s gpu:%7s zt:%7s st:%7s cov:%7s", frameDur, ft, zt, st, covt) g.profile = fmt.Sprintf("draw:%7s gpu:%7s st:%7s cov:%7s", frameDur, ft, st, covt)
} }
return nil return nil
} }
@@ -777,7 +769,6 @@ func (d *drawOps) reset(cache *resourceCache, viewport image.Point) {
d.cache = cache d.cache = cache
d.viewport = viewport d.viewport = viewport
d.imageOps = d.imageOps[:0] d.imageOps = d.imageOps[:0]
d.zimageOps = d.zimageOps[:0]
d.pathOps = d.pathOps[:0] d.pathOps = d.pathOps[:0]
d.pathOpCache = d.pathOpCache[:0] d.pathOpCache = d.pathOpCache[:0]
d.vertCache = d.vertCache[:0] d.vertCache = d.vertCache[:0]
@@ -857,7 +848,6 @@ func (d *drawOps) collectOps(r *ops.Reader, state drawState) {
var ( var (
quads quadsOp quads quadsOp
str clip.StrokeStyle str clip.StrokeStyle
z int
) )
d.save(opconst.InitialStateID, state) d.save(opconst.InitialStateID, state)
loop: loop:
@@ -960,34 +950,18 @@ loop:
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && (mat.material == materialColor) { if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && (mat.material == materialColor) {
// The image is a uniform opaque color and takes up the whole screen. // The image is a uniform opaque color and takes up the whole screen.
// Scrap images up to and including this image and set clear color. // Scrap images up to and including this image and set clear color.
d.zimageOps = d.zimageOps[:0]
d.imageOps = d.imageOps[:0] d.imageOps = d.imageOps[:0]
z = 0
d.clearColor = mat.color.Opaque() d.clearColor = mat.color.Opaque()
d.clear = true d.clear = true
continue continue
} }
z++
if z != int(uint16(z)) {
// TODO(eliasnaur) gioui.org/issue/127.
panic("more than 65k paint objects not supported")
}
// Assume 16-bit depth buffer.
const zdepth = 1 << 16
// Convert z to window-space, assuming depth range [0;1].
zf := float32(z)*2/zdepth - 1.0
img := imageOp{ img := imageOp{
z: zf,
path: state.cpath, path: state.cpath,
clip: bounds, clip: bounds,
material: mat, material: mat,
} }
if state.rect && img.material.opaque { d.imageOps = append(d.imageOps, img)
d.zimageOps = append(d.zimageOps, img)
} else {
d.imageOps = append(d.imageOps, img)
}
if clipData != nil { if clipData != nil {
// we added a clip path that should not remain // we added a clip path that should not remain
state.cpath = state.cpath.parent state.cpath = state.cpath.parent
@@ -1060,25 +1034,6 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
return m return m
} }
func (r *renderer) drawZOps(cache *resourceCache, ops []imageOp) {
r.ctx.SetDepthTest(true)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 4*4, 0)
r.ctx.BindInputLayout(r.blitter.layout)
// Render front to back.
for i := len(ops) - 1; i >= 0; i-- {
img := ops[i]
m := img.material
switch m.material {
case materialTexture:
r.ctx.BindTexture(0, r.texHandle(cache, m.data))
}
drc := img.clip
scale, off := clipSpaceTransform(drc, r.blitter.viewport)
r.blitter.blit(img.z, m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
}
r.ctx.SetDepthTest(false)
}
func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) { func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
r.ctx.SetDepthTest(true) r.ctx.SetDepthTest(true)
r.ctx.DepthMask(false) r.ctx.DepthMask(false)
@@ -1098,7 +1053,7 @@ func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
var fbo stencilFBO var fbo stencilFBO
switch img.clipType { switch img.clipType {
case clipTypeNone: case clipTypeNone:
r.blitter.blit(img.z, m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans) r.blitter.blit(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans)
continue continue
case clipTypePath: case clipTypePath:
fbo = r.pather.stenciler.cover(img.place.Idx) fbo = r.pather.stenciler.cover(img.place.Idx)
@@ -1114,13 +1069,13 @@ func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
Max: img.place.Pos.Add(drc.Size()), Max: img.place.Pos.Add(drc.Size()),
} }
coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size) coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size)
r.pather.cover(img.z, m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff) r.pather.cover(m.material, m.color, m.color1, m.color2, scale, off, m.uvTrans, coverScale, coverOff)
} }
r.ctx.DepthMask(true) r.ctx.DepthMask(true)
r.ctx.SetDepthTest(false) r.ctx.SetDepthTest(false)
} }
func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) { func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
p := b.prog[mat] p := b.prog[mat]
b.ctx.BindProgram(p.prog) b.ctx.BindProgram(p.prog)
var uniforms *blitUniforms var uniforms *blitUniforms
@@ -1142,7 +1097,6 @@ func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, col1, col
b.linearGradientUniforms.vert.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0} b.linearGradientUniforms.vert.blitUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &b.linearGradientUniforms.vert.blitUniforms uniforms = &b.linearGradientUniforms.vert.blitUniforms
} }
uniforms.z = z
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y} uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
p.UploadUniforms() p.UploadUniforms()
b.ctx.DrawArrays(driver.DrawModeTriangleStrip, 0, 4) b.ctx.DrawArrays(driver.DrawModeTriangleStrip, 0, 4)
+3 -4
View File
@@ -374,11 +374,11 @@ func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv ima
} }
} }
func (p *pather) cover(z float32, mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) { func (p *pather) cover(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
p.coverer.cover(z, mat, col, col1, col2, scale, off, uvTrans, coverScale, coverOff) p.coverer.cover(mat, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
} }
func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) { func (c *coverer) cover(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
p := c.prog[mat] p := c.prog[mat]
c.ctx.BindProgram(p.prog) c.ctx.BindProgram(p.prog)
var uniforms *coverUniforms var uniforms *coverUniforms
@@ -400,7 +400,6 @@ func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, col1, co
c.texUniforms.vert.uvTransformR2 = [4]float32{t4, t5, t6, 0} c.texUniforms.vert.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &c.texUniforms.vert.coverUniforms uniforms = &c.texUniforms.vert.coverUniforms
} }
uniforms.z = z
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y} uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y} uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
p.UploadUniforms() p.UploadUniforms()