From b599a6d1c53f1448a473c5340e2a952be25310c9 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 16 Sep 2021 14:32:07 +0200 Subject: [PATCH] gpu: introduce driver.Device.PrepareTexture and use it in renderers Vulkan textures (VkImage) are always in a particular layout, where each layout is optimized for a particular use (transfer, sampling, compute storage). Vulkan allows layout transitions everywhere except inside render passes. This change adds driver.Device.PrepareTexture for instructing the driver to switch a texture to a layout for sampling in preparation for using it in a render pass. Signed-off-by: Elias Naur --- go.mod | 2 +- go.sum | 4 +-- gpu/compute.go | 12 +++---- gpu/gpu.go | 53 +++++++++++++++++++++++------ gpu/headless/driver_test.go | 6 ++-- gpu/internal/d3d11/d3d11_windows.go | 2 ++ gpu/internal/driver/driver.go | 1 + gpu/internal/metal/metal_darwin.go | 2 ++ gpu/internal/opengl/opengl.go | 2 ++ 9 files changed, 60 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 870317fa..038311c3 100644 --- a/go.mod +++ b/go.mod @@ -10,5 +10,5 @@ require ( require ( gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 - gioui.org/shader v1.0.3 + gioui.org/shader v1.0.4 ) diff --git a/go.sum b/go.sum index 9edf89dd..fcdb554a 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7 gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= -gioui.org/shader v1.0.3 h1:xllhh4w2XWej9lfkHMtOtI3ES05T8xZzxrSSWbMZgTw= -gioui.org/shader v1.0.3/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= +gioui.org/shader v1.0.4 h1:taS5l4MYdnKOQ5cRh1FPUaOp0YAs0qkRrhwbZqHSYrQ= +gioui.org/shader v1.0.4/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= diff --git a/gpu/compute.go b/gpu/compute.go index 7adc71ee..7a2fa162 100644 --- a/gpu/compute.go +++ b/gpu/compute.go @@ -862,6 +862,7 @@ func (g *compute) blitLayers(d driver.LoadDesc, fbo driver.Framebuffer, viewport {posX: float32(r.Min.X), posY: float32(r.Max.Y), u: placef.X, v: placef.Y + sizef.Y}, } g.output.layerVertices = append(g.output.layerVertices, quad[0], quad[1], quad[3], quad[3], quad[2], quad[1]) + g.ctx.PrepareTexture(l.alloc.atlas.image) } if len(g.output.layerVertices) > 0 { vertexData := byteslice.Slice(g.output.layerVertices) @@ -893,9 +894,6 @@ func (g *compute) blitLayers(d driver.LoadDesc, fbo driver.Framebuffer, viewport // Transform positions to clip space: [-1, -1] - [1, 1], and texture // coordinates to texture space: [0, 0] - [1, 1]. clip := f32.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(2/float32(viewport.X), 2/float32(viewport.Y))).Offset(f32.Pt(-1, -1)) - // Flip y-axis to match framebuffer output space. - flipY := f32.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(1, -1)).Offset(f32.Pt(0, float32(viewport.Y))) - clip = clip.Mul(flipY) sx, _, ox, _, sy, oy := clip.Elems() g.output.uniforms.scale = [2]float32{sx, sy} g.output.uniforms.pos = [2]float32{ox, oy} @@ -992,11 +990,10 @@ func (g *compute) renderMaterials() error { if err := g.realizeAtlas(atlas, g.useCPU, atlas.packer.sizes[0]); err != nil { return err } - // Transform to clip space: [-1, -1] - [1, 1] and flip Y-axis to cancel the implied transformation - // between framebuffer and texture space. + // Transform to clip space: [-1, -1] - [1, 1]. *m.uniforms.u = materialUniforms{ - scale: [2]float32{2, -2}, - pos: [2]float32{-1, +1}, + scale: [2]float32{2, 2}, + pos: [2]float32{-1, -1}, } if !g.srgb { m.uniforms.u.emulatesRGB = 1.0 @@ -1010,6 +1007,7 @@ func (g *compute) renderMaterials() error { if !realized { d.Action = driver.LoadActionClear } + g.ctx.PrepareTexture(imgAtlas.image) g.ctx.BeginRenderPass(atlas.fbo, d) g.ctx.BindTexture(0, imgAtlas.image) g.ctx.BindPipeline(m.pipeline) diff --git a/gpu/gpu.go b/gpu/gpu.go index 597feabc..cd3c0e0c 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -289,7 +289,6 @@ type blitColUniforms struct { type blitTexUniforms struct { blitUniforms - _ [12]byte // Padding to 16 bytes. } type blitLinearGradientUniforms struct { @@ -312,7 +311,6 @@ type blitUniforms struct { transform [4]float32 uvTransformR1 [4]float32 uvTransformR2 [4]float32 - z float32 } type colorUniforms struct { @@ -417,10 +415,12 @@ func (g *gpu) frame(target RenderTarget) error { g.renderer.packStencils(&g.drawOps.pathOps) g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps) g.renderer.packIntersections(g.drawOps.imageOps) + g.renderer.prepareIntersections(g.drawOps.imageOps) g.renderer.intersect(g.drawOps.imageOps) g.stencilTimer.end() g.coverTimer.begin() g.renderer.uploadImages(g.cache, g.drawOps.imageOps) + g.renderer.prepareDrawOps(g.cache, g.drawOps.imageOps) d := driver.LoadDesc{ ClearColor: g.drawOps.clearColor, } @@ -508,10 +508,10 @@ func (r *renderer) release() { func newBlitter(ctx driver.Device) *blitter { quadVerts, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, byteslice.Slice([]float32{ - -1, +1, 0, 0, - +1, +1, 1, 0, - -1, -1, 0, 1, - +1, -1, 1, 1, + -1, -1, 0, 0, + +1, -1, 1, 0, + -1, +1, 0, 1, + +1, +1, 1, 1, }), ) if err != nil { @@ -669,6 +669,16 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) { } } +func (r *renderer) prepareIntersections(ops []imageOp) { + for _, img := range ops { + if img.clipType != clipTypeIntersection { + continue + } + fbo := r.pather.stenciler.cover(img.path.place.Idx) + r.ctx.PrepareTexture(fbo.tex) + } +} + func (r *renderer) intersect(ops []imageOp) { if len(r.intersections.sizes) == 0 { return @@ -1084,6 +1094,27 @@ func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) { } } +func (r *renderer) prepareDrawOps(cache *resourceCache, ops []imageOp) { + for _, img := range ops { + m := img.material + switch m.material { + case materialTexture: + r.ctx.PrepareTexture(r.texHandle(cache, m.data)) + } + + var fbo stencilFBO + switch img.clipType { + case clipTypeNone: + continue + case clipTypePath: + fbo = r.pather.stenciler.cover(img.place.Idx) + case clipTypeIntersection: + fbo = r.pather.stenciler.intersections.fbos[img.place.Idx] + } + r.ctx.PrepareTexture(fbo.tex) + } +} + func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) { var coverTex driver.Texture for _, img := range ops { @@ -1216,25 +1247,25 @@ func gradientSpaceTransform(clip image.Rectangle, off f32.Point, stop1, stop2 f3 } // clipSpaceTransform returns the scale and offset that transforms the given -// rectangle from a viewport into OpenGL clip space. +// rectangle from a viewport into GPU driver device coordinates. func clipSpaceTransform(r image.Rectangle, viewport image.Point) (f32.Point, f32.Point) { - // First, transform UI coordinates to OpenGL coordinates: + // First, transform UI coordinates to device coordinates: // - // [(-1, +1) (+1, +1)] // [(-1, -1) (+1, -1)] + // [(-1, +1) (+1, +1)] // x, y := float32(r.Min.X), float32(r.Min.Y) w, h := float32(r.Dx()), float32(r.Dy()) vx, vy := 2/float32(viewport.X), 2/float32(viewport.Y) x = x*vx - 1 - y = 1 - y*vy + y = y*vy - 1 w *= vx h *= vy // Then, compute the transformation from the fullscreen quad to // the rectangle at (x, y) and dimensions (w, h). scale := f32.Point{X: w * .5, Y: h * .5} - offset := f32.Point{X: x + w*.5, Y: y - h*.5} + offset := f32.Point{X: x + w*.5, Y: y + h*.5} return scale, offset } diff --git a/gpu/headless/driver_test.go b/gpu/headless/driver_test.go index 5e21fbd3..de18f8ad 100644 --- a/gpu/headless/driver_test.go +++ b/gpu/headless/driver_test.go @@ -75,9 +75,9 @@ func TestInputShader(t *testing.T) { defer pipe.Release() buf, err := b.NewImmutableBuffer(driver.BufferBindingVertices, byteslice.Slice([]float32{ - 0, .5, .5, 1, - -.5, -.5, .5, 1, - .5, -.5, .5, 1, + 0, -.5, .5, 1, + -.5, +.5, .5, 1, + .5, +.5, .5, 1, }), ) if err != nil { diff --git a/gpu/internal/d3d11/d3d11_windows.go b/gpu/internal/d3d11/d3d11_windows.go index aada3ca2..dd836483 100644 --- a/gpu/internal/d3d11/d3d11_windows.go +++ b/gpu/internal/d3d11/d3d11_windows.go @@ -633,6 +633,8 @@ func (t *Texture) Release() { *t = Texture{} } +func (b *Backend) PrepareTexture(tex driver.Texture) {} + func (b *Backend) BindTexture(unit int, tex driver.Texture) { t := tex.(*Texture) b.ctx.PSSetSamplers(uint32(unit), t.sampler) diff --git a/gpu/internal/driver/driver.go b/gpu/internal/driver/driver.go index 670ca5f0..9a1ba3a2 100644 --- a/gpu/internal/driver/driver.go +++ b/gpu/internal/driver/driver.go @@ -37,6 +37,7 @@ type Device interface { BeginRenderPass(f Framebuffer, desc LoadDesc) EndRenderPass() + PrepareTexture(t Texture) BindProgram(p Program) BindPipeline(p Pipeline) BindTexture(unit int, t Texture) diff --git a/gpu/internal/metal/metal_darwin.go b/gpu/internal/metal/metal_darwin.go index b4f9ef7a..ac9a6959 100644 --- a/gpu/internal/metal/metal_darwin.go +++ b/gpu/internal/metal/metal_darwin.go @@ -931,6 +931,8 @@ func (p *Pipeline) Release() { *p = Pipeline{} } +func (b *Backend) PrepareTexture(tex driver.Texture) {} + func (b *Backend) BindTexture(unit int, tex driver.Texture) { t := tex.(*Texture) if enc := b.renderEnc; enc != 0 { diff --git a/gpu/internal/opengl/opengl.go b/gpu/internal/opengl/opengl.go index 37f7c0c6..309d0a09 100644 --- a/gpu/internal/opengl/opengl.go +++ b/gpu/internal/opengl/opengl.go @@ -1202,6 +1202,8 @@ func toTexFilter(f driver.TextureFilter) int { } } +func (b *Backend) PrepareTexture(tex driver.Texture) {} + func (b *Backend) BindTexture(unit int, t driver.Texture) { b.glstate.bindTexture(b.funcs, unit, t.(*texture).obj) }