gpu: introduce render passes

Modern GPU API such as Metal and Vulkan use explicit render passes
and command buffers for recording rendering commands. They don't have
global state; each render pass starts with a clean set of bound
textures, pipeline etc.

Change our GPU abstraction to better match newer API and modify our two
renderers to explicitly describe their render passes.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-09-03 10:05:35 +02:00
parent af6770de18
commit cc2edbaa51
9 changed files with 252 additions and 228 deletions
+26 -21
View File
@@ -623,17 +623,18 @@ func (g *compute) frame(target RenderTarget) error {
return err return err
} }
t.render.end() t.render.end()
var d driver.LoadDesc d := driver.LoadDesc{
ClearColor: g.collector.clearColor,
}
if g.collector.clear { if g.collector.clear {
g.collector.clear = false g.collector.clear = false
d.Action = driver.LoadActionClear d.Action = driver.LoadActionClear
c := &d.ClearColor
c.R, c.G, c.B, c.A = g.collector.clearColor.Float32()
} }
g.ctx.BindFramebuffer(defFBO, d) g.ctx.BeginRenderPass(defFBO, d)
t.blit.begin() t.blit.begin()
g.blitLayers(viewport) g.blitLayers(viewport)
t.blit.end() t.blit.end()
g.ctx.EndRenderPass()
t.compact.begin() t.compact.begin()
if err := g.compactAllocs(); err != nil { if err := g.compactAllocs(); err != nil {
return err return err
@@ -993,18 +994,18 @@ func (g *compute) renderMaterials() error {
m.vert.uniforms.scale = [2]float32{2, -2} m.vert.uniforms.scale = [2]float32{2, -2}
m.vert.uniforms.pos = [2]float32{-1, +1} m.vert.uniforms.pos = [2]float32{-1, +1}
m.vert.buf.Upload(byteslice.Struct(m.vert.uniforms)) m.vert.buf.Upload(byteslice.Struct(m.vert.uniforms))
g.ctx.BindVertexUniforms(m.vert.buf)
g.ctx.BindFragmentUniforms(m.frag.buf)
vertexData := byteslice.Slice(m.quads) vertexData := byteslice.Slice(m.quads)
n := pow2Ceil(len(vertexData)) n := pow2Ceil(len(vertexData))
m.buffer.ensureCapacity(false, g.ctx, driver.BufferBindingVertices, n) m.buffer.ensureCapacity(false, g.ctx, driver.BufferBindingVertices, n)
m.buffer.buffer.Upload(vertexData) m.buffer.buffer.Upload(vertexData)
g.ctx.BindTexture(0, imgAtlas.image)
var d driver.LoadDesc var d driver.LoadDesc
if !realized { if !realized {
d.Action = driver.LoadActionClear d.Action = driver.LoadActionClear
} }
g.ctx.BindFramebuffer(atlas.fbo, d) g.ctx.BeginRenderPass(atlas.fbo, d)
g.ctx.BindVertexUniforms(m.vert.buf)
g.ctx.BindFragmentUniforms(m.frag.buf)
g.ctx.BindTexture(0, imgAtlas.image)
g.ctx.BindPipeline(m.pipeline) g.ctx.BindPipeline(m.pipeline)
g.ctx.BindVertexBuffer(m.buffer.buffer, 0) g.ctx.BindVertexBuffer(m.buffer.buffer, 0)
newAllocs := atlas.allocs[allocStart:] newAllocs := atlas.allocs[allocStart:]
@@ -1013,6 +1014,7 @@ func (g *compute) renderMaterials() error {
g.ctx.Viewport(a.rect.Min.X, a.rect.Min.Y, sz.X, sz.Y) g.ctx.Viewport(a.rect.Min.X, a.rect.Min.Y, sz.X, sz.Y)
g.ctx.DrawArrays(driver.DrawModeTriangles, i*6, 6) g.ctx.DrawArrays(driver.DrawModeTriangles, i*6, 6)
} }
g.ctx.EndRenderPass()
if !g.useCPU { if !g.useCPU {
continue continue
} }
@@ -1263,18 +1265,6 @@ func (g *compute) render(images *textureAtlas, dst driver.Texture, cpuDst cpu.Im
} }
} }
if !g.useCPU {
g.ctx.BindImageTexture(kernel4OutputUnit, dst, driver.AccessWrite, driver.TextureFormatRGBA8)
if images != nil {
g.ctx.BindImageTexture(kernel4AtlasUnit, images.image, driver.AccessRead, driver.TextureFormatRGBA8)
}
} else {
*g.output.descriptors.Binding2() = cpuDst
if images != nil {
*g.output.descriptors.Binding3() = images.cpuImage
}
}
for { for {
*g.memHeader = memoryHeader{ *g.memHeader = memoryHeader{
mem_offset: alloc, mem_offset: alloc,
@@ -1282,6 +1272,19 @@ func (g *compute) render(images *textureAtlas, dst driver.Texture, cpuDst cpu.Im
g.buffers.memory.upload(byteslice.Struct(g.memHeader)) g.buffers.memory.upload(byteslice.Struct(g.memHeader))
g.buffers.state.upload(g.zeros(clearSize)) g.buffers.state.upload(g.zeros(clearSize))
if !g.useCPU {
g.ctx.BeginCompute()
g.ctx.BindImageTexture(kernel4OutputUnit, dst, driver.AccessWrite, driver.TextureFormatRGBA8)
if images != nil {
g.ctx.BindImageTexture(kernel4AtlasUnit, images.image, driver.AccessRead, driver.TextureFormatRGBA8)
}
} else {
*g.output.descriptors.Binding2() = cpuDst
if images != nil {
*g.output.descriptors.Binding3() = images.cpuImage
}
}
g.bindBuffers() g.bindBuffers()
g.memoryBarrier() g.memoryBarrier()
g.dispatch(g.programs.elements, numPartitions, 1, 1) g.dispatch(g.programs.elements, numPartitions, 1, 1)
@@ -1298,7 +1301,9 @@ func (g *compute) render(images *textureAtlas, dst driver.Texture, cpuDst cpu.Im
g.memoryBarrier() g.memoryBarrier()
g.dispatch(g.programs.kernel4, tileDims.X, tileDims.Y, 1) g.dispatch(g.programs.kernel4, tileDims.X, tileDims.Y, 1)
g.memoryBarrier() g.memoryBarrier()
if g.useCPU { if !g.useCPU {
g.ctx.EndCompute()
} else {
g.dispatcher.Sync() g.dispatcher.Sync()
} }
+41 -11
View File
@@ -424,7 +424,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)
} }
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.stencilTimer.begin() g.stencilTimer.begin()
g.renderer.packStencils(&g.drawOps.pathOps) g.renderer.packStencils(&g.drawOps.pathOps)
g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps) g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps)
@@ -432,17 +431,19 @@ func (g *gpu) frame(target RenderTarget) error {
g.renderer.intersect(g.drawOps.imageOps) g.renderer.intersect(g.drawOps.imageOps)
g.stencilTimer.end() g.stencilTimer.end()
g.coverTimer.begin() g.coverTimer.begin()
var d driver.LoadDesc g.renderer.uploadImages(g.cache, g.drawOps.imageOps)
d := driver.LoadDesc{
ClearColor: g.drawOps.clearColor,
}
if g.drawOps.clear { if g.drawOps.clear {
g.drawOps.clear = false g.drawOps.clear = false
d.Action = driver.LoadActionClear d.Action = driver.LoadActionClear
c := &d.ClearColor
c.R, c.G, c.B, c.A = g.drawOps.clearColor.Float32()
} }
g.ctx.BindFramebuffer(defFBO, d) g.ctx.BeginRenderPass(defFBO, d)
g.ctx.Viewport(0, 0, viewport.X, viewport.Y) g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.renderer.drawOps(g.cache, g.drawOps.imageOps) g.renderer.drawOps(g.cache, g.drawOps.imageOps)
g.coverTimer.end() g.coverTimer.end()
g.ctx.EndRenderPass()
g.cleanupTimer.begin() g.cleanupTimer.begin()
g.cache.frame() g.cache.frame()
g.drawOps.pathCache.frame() g.drawOps.pathCache.frame()
@@ -669,13 +670,21 @@ func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
r.pather.begin(r.packer.sizes) r.pather.begin(r.packer.sizes)
for _, p := range ops { for _, p := range ops {
if fbo != p.place.Idx { if fbo != p.place.Idx {
if fbo != -1 {
r.ctx.EndRenderPass()
}
fbo = p.place.Idx fbo = p.place.Idx
f := r.pather.stenciler.cover(fbo) f := r.pather.stenciler.cover(fbo)
r.ctx.BindFramebuffer(f.fbo, driver.LoadDesc{Action: driver.LoadActionClear}) r.ctx.BeginRenderPass(f.fbo, driver.LoadDesc{Action: driver.LoadActionClear})
r.ctx.BindPipeline(r.pather.stenciler.pipeline.pipeline.pipeline)
r.ctx.BindIndexBuffer(r.pather.stenciler.indexBuf)
} }
v, _ := pathCache.get(p.pathKey) v, _ := pathCache.get(p.pathKey)
r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data) r.pather.stencilPath(p.clip, p.off, p.place.Pos, v.data)
} }
if fbo != -1 {
r.ctx.EndRenderPass()
}
} }
func (r *renderer) intersect(ops []imageOp) { func (r *renderer) intersect(ops []imageOp) {
@@ -684,21 +693,28 @@ func (r *renderer) intersect(ops []imageOp) {
} }
fbo := -1 fbo := -1
r.pather.stenciler.beginIntersect(r.intersections.sizes) r.pather.stenciler.beginIntersect(r.intersections.sizes)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
for _, img := range ops { for _, img := range ops {
if img.clipType != clipTypeIntersection { if img.clipType != clipTypeIntersection {
continue continue
} }
if fbo != img.place.Idx { if fbo != img.place.Idx {
if fbo != -1 {
r.ctx.EndRenderPass()
}
fbo = img.place.Idx fbo = img.place.Idx
f := r.pather.stenciler.intersections.fbos[fbo] f := r.pather.stenciler.intersections.fbos[fbo]
d := driver.LoadDesc{Action: driver.LoadActionClear} d := driver.LoadDesc{Action: driver.LoadActionClear}
d.ClearColor.R = 1.0 d.ClearColor.R = 1.0
r.ctx.BindFramebuffer(f.fbo, d) r.ctx.BeginRenderPass(f.fbo, d)
r.ctx.BindPipeline(r.pather.stenciler.ipipeline.pipeline.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
} }
r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy()) r.ctx.Viewport(img.place.Pos.X, img.place.Pos.Y, img.clip.Dx(), img.clip.Dy())
r.intersectPath(img.path, img.clip) r.intersectPath(img.path, img.clip)
} }
if fbo != -1 {
r.ctx.EndRenderPass()
}
} }
func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) { func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
@@ -1077,8 +1093,16 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
return m return m
} }
func (r *renderer) uploadImages(cache *resourceCache, ops []imageOp) {
for _, img := range ops {
m := img.material
if m.material == materialTexture {
r.texHandle(cache, m.data)
}
}
}
func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) { func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
var coverTex driver.Texture var coverTex driver.Texture
for _, img := range ops { for _, img := range ops {
m := img.material m := img.material
@@ -1092,6 +1116,9 @@ func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
var fbo stencilFBO var fbo stencilFBO
switch img.clipType { switch img.clipType {
case clipTypeNone: case clipTypeNone:
p := r.blitter.pipelines[m.material]
r.ctx.BindPipeline(p.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
r.blitter.blit(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:
@@ -1108,6 +1135,9 @@ 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)
p := r.pather.coverer.pipelines[m.material]
r.ctx.BindPipeline(p.pipeline)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 0)
r.pather.cover(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)
} }
} }
@@ -1165,12 +1195,12 @@ func (u *uniformBuffer) Release() {
func (p *pipeline) UploadUniforms(ctx driver.Device) { func (p *pipeline) UploadUniforms(ctx driver.Device) {
if p.vertUniforms != nil { if p.vertUniforms != nil {
ctx.BindVertexUniforms(p.vertUniforms.buf)
p.vertUniforms.Upload() p.vertUniforms.Upload()
ctx.BindVertexUniforms(p.vertUniforms.buf)
} }
if p.fragUniforms != nil { if p.fragUniforms != nil {
ctx.BindFragmentUniforms(p.fragUniforms.buf)
p.fragUniforms.Upload() p.fragUniforms.Upload()
ctx.BindFragmentUniforms(p.fragUniforms.buf)
} }
} }
+34 -24
View File
@@ -27,7 +27,15 @@ var clearColExpect = f32color.NRGBAToRGBA(clearCol)
func TestFramebufferClear(t *testing.T) { func TestFramebufferClear(t *testing.T) {
b := newDriver(t) b := newDriver(t)
sz := image.Point{X: 800, Y: 600} sz := image.Point{X: 800, Y: 600}
fbo := setupFBO(t, b, sz) fbo := newFBO(t, b, sz)
d := driver.LoadDesc{
// ClearColor accepts linear RGBA colors, while 8-bit colors
// are in the sRGB color space.
ClearColor: f32color.LinearFromSRGB(clearCol),
Action: driver.LoadActionClear,
}
b.BeginRenderPass(fbo, d)
b.EndRenderPass()
img := screenshot(t, b, fbo, sz) img := screenshot(t, b, fbo, sz)
if got := img.RGBAAt(0, 0); got != clearColExpect { if got := img.RGBAAt(0, 0); got != clearColExpect {
t.Errorf("got color %v, expected %v", got, clearColExpect) t.Errorf("got color %v, expected %v", got, clearColExpect)
@@ -37,13 +45,13 @@ func TestFramebufferClear(t *testing.T) {
func TestSimpleShader(t *testing.T) { func TestSimpleShader(t *testing.T) {
b := newDriver(t) b := newDriver(t)
sz := image.Point{X: 800, Y: 600} sz := image.Point{X: 800, Y: 600}
fbo := setupFBO(t, b, sz)
vsh, fsh, err := newShaders(b, gio.Shader_simple_vert, gio.Shader_simple_frag) vsh, fsh, err := newShaders(b, gio.Shader_simple_vert, gio.Shader_simple_frag)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer vsh.Release() defer vsh.Release()
defer fsh.Release() defer fsh.Release()
fbo := newFBO(t, b, sz)
p, err := b.NewPipeline(driver.PipelineDesc{ p, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh, VertexShader: vsh,
FragmentShader: fsh, FragmentShader: fsh,
@@ -53,8 +61,15 @@ func TestSimpleShader(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer p.Release() defer p.Release()
d := driver.LoadDesc{
ClearColor: f32color.LinearFromSRGB(clearCol),
Action: driver.LoadActionClear,
}
b.BeginRenderPass(fbo, d)
b.Viewport(0, 0, sz.X, sz.Y)
b.BindPipeline(p) b.BindPipeline(p)
b.DrawArrays(driver.DrawModeTriangles, 0, 3) b.DrawArrays(driver.DrawModeTriangles, 0, 3)
b.EndRenderPass()
img := screenshot(t, b, fbo, sz) img := screenshot(t, b, fbo, sz)
if got := img.RGBAAt(0, 0); got != clearColExpect { if got := img.RGBAAt(0, 0); got != clearColExpect {
t.Errorf("got color %v, expected %v", got, clearColExpect) t.Errorf("got color %v, expected %v", got, clearColExpect)
@@ -70,7 +85,6 @@ func TestSimpleShader(t *testing.T) {
func TestInputShader(t *testing.T) { func TestInputShader(t *testing.T) {
b := newDriver(t) b := newDriver(t)
sz := image.Point{X: 800, Y: 600} sz := image.Point{X: 800, Y: 600}
fbo := setupFBO(t, b, sz)
vsh, fsh, err := newShaders(b, gio.Shader_input_vert, gio.Shader_simple_frag) vsh, fsh, err := newShaders(b, gio.Shader_input_vert, gio.Shader_simple_frag)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -87,6 +101,7 @@ func TestInputShader(t *testing.T) {
}, },
Stride: 4 * 4, Stride: 4 * 4,
} }
fbo := newFBO(t, b, sz)
pipe, err := b.NewPipeline(driver.PipelineDesc{ pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh, VertexShader: vsh,
FragmentShader: fsh, FragmentShader: fsh,
@@ -97,7 +112,6 @@ func TestInputShader(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer pipe.Release() defer pipe.Release()
b.BindPipeline(pipe)
buf, err := b.NewImmutableBuffer(driver.BufferBindingVertices, buf, err := b.NewImmutableBuffer(driver.BufferBindingVertices,
byteslice.Slice([]float32{ byteslice.Slice([]float32{
0, .5, .5, 1, 0, .5, .5, 1,
@@ -109,8 +123,16 @@ func TestInputShader(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
defer buf.Release() defer buf.Release()
d := driver.LoadDesc{
ClearColor: f32color.LinearFromSRGB(clearCol),
Action: driver.LoadActionClear,
}
b.BeginRenderPass(fbo, d)
b.Viewport(0, 0, sz.X, sz.Y)
b.BindPipeline(pipe)
b.BindVertexBuffer(buf, 0) b.BindVertexBuffer(buf, 0)
b.DrawArrays(driver.DrawModeTriangles, 0, 3) b.DrawArrays(driver.DrawModeTriangles, 0, 3)
b.EndRenderPass()
img := screenshot(t, b, fbo, sz) img := screenshot(t, b, fbo, sz)
if got := img.RGBAAt(0, 0); got != clearColExpect { if got := img.RGBAAt(0, 0); got != clearColExpect {
t.Errorf("got color %v, expected %v", got, clearColExpect) t.Errorf("got color %v, expected %v", got, clearColExpect)
@@ -137,19 +159,20 @@ func newShaders(ctx driver.Device, vsrc, fsrc shader.Sources) (vert driver.Verte
func TestFramebuffers(t *testing.T) { func TestFramebuffers(t *testing.T) {
b := newDriver(t) b := newDriver(t)
sz := image.Point{X: 800, Y: 600} sz := image.Point{X: 800, Y: 600}
fbo1 := newFBO(t, b, sz)
fbo2 := newFBO(t, b, sz)
var ( var (
col1 = color.NRGBA{R: 0xac, G: 0xbd, B: 0xef, A: 0xde} col1 = color.NRGBA{R: 0xac, G: 0xbd, B: 0xef, A: 0xde}
col2 = color.NRGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca} col2 = color.NRGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca}
) )
fbo1 := newFBO(t, b, sz)
fbo2 := newFBO(t, b, sz)
fcol1, fcol2 := f32color.LinearFromSRGB(col1), f32color.LinearFromSRGB(col2) fcol1, fcol2 := f32color.LinearFromSRGB(col1), f32color.LinearFromSRGB(col2)
d := driver.LoadDesc{Action: driver.LoadActionClear} d := driver.LoadDesc{Action: driver.LoadActionClear}
c := &d.ClearColor d.ClearColor = fcol1
c.R, c.G, c.B, c.A = fcol1.Float32() b.BeginRenderPass(fbo1, d)
b.BindFramebuffer(fbo1, d) b.EndRenderPass()
c.R, c.G, c.B, c.A = fcol2.Float32() d.ClearColor = fcol2
b.BindFramebuffer(fbo2, d) b.BeginRenderPass(fbo2, d)
b.EndRenderPass()
img := screenshot(t, b, fbo1, sz) img := screenshot(t, b, fbo1, sz)
if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) { if got := img.RGBAAt(0, 0); got != f32color.NRGBAToRGBA(col1) {
t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1)) t.Errorf("got color %v, expected %v", got, f32color.NRGBAToRGBA(col1))
@@ -160,19 +183,6 @@ func TestFramebuffers(t *testing.T) {
} }
} }
func setupFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer {
fbo := newFBO(t, b, size)
// ClearColor accepts linear RGBA colors, while 8-bit colors
// are in the sRGB color space.
col := f32color.LinearFromSRGB(clearCol)
d := driver.LoadDesc{Action: driver.LoadActionClear}
c := &d.ClearColor
c.R, c.G, c.B, c.A = col.Float32()
b.BindFramebuffer(fbo, d)
b.Viewport(0, 0, size.X, size.Y)
return fbo
}
func newFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer { func newFBO(t *testing.T, b driver.Device, size image.Point) driver.Framebuffer {
fboTex, err := b.NewTexture( fboTex, err := b.NewTexture(
driver.TextureFormatSRGBA, driver.TextureFormatSRGBA,
-1
View File
@@ -47,7 +47,6 @@ func NewWindow(width, height int) (*Window, error) {
if err != nil { if err != nil {
return err return err
} }
dev.Viewport(0, 0, width, height)
fboTex, err := dev.NewTexture( fboTex, err := dev.NewTexture(
driver.TextureFormatSRGBA, driver.TextureFormatSRGBA,
width, height, width, height,
+10 -1
View File
@@ -789,7 +789,13 @@ func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int)
return nil return nil
} }
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer, d driver.LoadDesc) { func (b *Backend) BeginCompute() {
}
func (b *Backend) EndCompute() {
}
func (b *Backend) BeginRenderPass(fbo driver.Framebuffer, d driver.LoadDesc) {
b.fbo = fbo.(*Framebuffer) b.fbo = fbo.(*Framebuffer)
b.ctx.OMSetRenderTargets(b.fbo.renderTarget, nil) b.ctx.OMSetRenderTargets(b.fbo.renderTarget, nil)
if d.Action == driver.LoadActionClear { if d.Action == driver.LoadActionClear {
@@ -799,6 +805,9 @@ func (b *Backend) BindFramebuffer(fbo driver.Framebuffer, d driver.LoadDesc) {
} }
} }
func (b *Backend) EndRenderPass() {
}
func (f *Framebuffer) Release() { func (f *Framebuffer) Release() {
if f.foreign { if f.foreign {
panic("framebuffer not created by NewFramebuffer") panic("framebuffer not created by NewFramebuffer")
+6 -7
View File
@@ -7,6 +7,7 @@ import (
"image" "image"
"time" "time"
"gioui.org/internal/f32color"
"gioui.org/shader" "gioui.org/shader"
) )
@@ -34,9 +35,10 @@ type Device interface {
DrawArrays(mode DrawMode, off, count int) DrawArrays(mode DrawMode, off, count int)
DrawElements(mode DrawMode, off, count int) DrawElements(mode DrawMode, off, count int)
BeginRenderPass(f Framebuffer, desc LoadDesc)
EndRenderPass()
BindProgram(p Program) BindProgram(p Program)
BindPipeline(p Pipeline) BindPipeline(p Pipeline)
BindFramebuffer(f Framebuffer, a LoadDesc)
BindTexture(unit int, t Texture) BindTexture(unit int, t Texture)
BindVertexBuffer(b Buffer, offset int) BindVertexBuffer(b Buffer, offset int)
BindIndexBuffer(b Buffer) BindIndexBuffer(b Buffer)
@@ -45,6 +47,8 @@ type Device interface {
BindFragmentUniforms(buf Buffer) BindFragmentUniforms(buf Buffer)
BindStorageBuffer(binding int, buf Buffer) BindStorageBuffer(binding int, buf Buffer)
BeginCompute()
EndCompute()
CopyTexture(dst Texture, dstOrigin image.Point, src Framebuffer, srcRect image.Rectangle) CopyTexture(dst Texture, dstOrigin image.Point, src Framebuffer, srcRect image.Rectangle)
MemoryBarrier() MemoryBarrier()
DispatchCompute(x, y, z int) DispatchCompute(x, y, z int)
@@ -54,12 +58,7 @@ type Device interface {
type LoadDesc struct { type LoadDesc struct {
Action LoadAction Action LoadAction
ClearColor struct { ClearColor f32color.RGBA
R float32
G float32
B float32
A float32
}
} }
type Pipeline interface { type Pipeline interface {
+122 -154
View File
@@ -387,12 +387,12 @@ type Backend struct {
computeEnc C.CFTypeRef computeEnc C.CFTypeRef
blitEnc C.CFTypeRef blitEnc C.CFTypeRef
prog *Program
stagingBuf C.CFTypeRef stagingBuf C.CFTypeRef
stagingOff int stagingOff int
indexBuf *Buffer indexBuf *Buffer
state state
newState state
// bufSizes is scratch space for filling out the spvBufferSizeConstants // bufSizes is scratch space for filling out the spvBufferSizeConstants
// that spirv-cross generates for emulating buffer.length expressions in // that spirv-cross generates for emulating buffer.length expressions in
@@ -400,25 +400,6 @@ type Backend struct {
bufSizes []uint32 bufSizes []uint32
} }
type state struct {
renderPass struct {
framebuffer *Framebuffer
loadAction driver.LoadAction
clearColor [4]float32
}
pipeline *Pipeline
program *Program
vertices struct {
buffer *Buffer
offset int
}
buffers [bufferUnits]*Buffer
vertUniforms *Buffer
fragUniforms *Buffer
textures [texUnits]*Texture
viewport C.MTLViewport
}
type Texture struct { type Texture struct {
backend *Backend backend *Backend
texture C.CFTypeRef texture C.CFTypeRef
@@ -517,7 +498,6 @@ func (b *Backend) startBlit() C.CFTypeRef {
if b.blitEnc == 0 { if b.blitEnc == 0 {
panic("metal: [MTLCommandBuffer blitCommandEncoder:] failed") panic("metal: [MTLCommandBuffer blitCommandEncoder:] failed")
} }
b.state = state{}
return b.blitEnc return b.blitEnc
} }
@@ -805,24 +785,34 @@ func (b *Backend) newShader(src shader.Sources) (*Shader, error) {
} }
func (b *Backend) Viewport(x, y, width, height int) { func (b *Backend) Viewport(x, y, width, height int) {
b.newState.viewport = C.MTLViewport{ enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
C.renderEncViewport(enc, C.MTLViewport{
originX: C.double(x), originX: C.double(x),
originY: C.double(y), originY: C.double(y),
width: C.double(width), width: C.double(width),
height: C.double(height), height: C.double(height),
znear: 0.0, znear: 0.0,
zfar: 1.0, zfar: 1.0,
} })
} }
func (b *Backend) DrawArrays(mode driver.DrawMode, off, count int) { func (b *Backend) DrawArrays(mode driver.DrawMode, off, count int) {
enc := b.encodeState() enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
t := primitiveFor(mode) t := primitiveFor(mode)
C.renderEncDrawPrimitives(enc, t, C.NSUInteger(off), C.NSUInteger(count)) C.renderEncDrawPrimitives(enc, t, C.NSUInteger(off), C.NSUInteger(count))
} }
func (b *Backend) DrawElements(mode driver.DrawMode, off, count int) { func (b *Backend) DrawElements(mode driver.DrawMode, off, count int) {
enc := b.encodeState() enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
t := primitiveFor(mode) t := primitiveFor(mode)
C.renderEncDrawIndexedPrimitives(enc, t, b.indexBuf.buffer, C.NSUInteger(off), C.NSUInteger(count)) C.renderEncDrawIndexedPrimitives(enc, t, b.indexBuf.buffer, C.NSUInteger(off), C.NSUInteger(count))
} }
@@ -839,59 +829,46 @@ func primitiveFor(mode driver.DrawMode) C.MTLPrimitiveType {
} }
func (b *Backend) BindImageTexture(unit int, tex driver.Texture, access driver.AccessBits, f driver.TextureFormat) { func (b *Backend) BindImageTexture(unit int, tex driver.Texture, access driver.AccessBits, f driver.TextureFormat) {
b.newState.textures[unit] = tex.(*Texture) b.BindTexture(unit, tex)
} }
func (b *Backend) MemoryBarrier() {} func (b *Backend) MemoryBarrier() {}
func (b *Backend) startCompute() C.CFTypeRef { func (b *Backend) BeginCompute() {
if b.computeEnc != 0 {
return b.computeEnc
}
b.endEncoder() b.endEncoder()
b.ensureCmdBuffer() b.ensureCmdBuffer()
for i := range b.bufSizes {
b.bufSizes[i] = 0
}
b.computeEnc = C.cmdBufferComputeEncoder(b.cmdBuffer) b.computeEnc = C.cmdBufferComputeEncoder(b.cmdBuffer)
if b.computeEnc == 0 { if b.computeEnc == 0 {
panic("metal: [MTLCommandBuffer computeCommandEncoder:] failed") panic("metal: [MTLCommandBuffer computeCommandEncoder:] failed")
} }
b.state = state{} }
return b.computeEnc
func (b *Backend) EndCompute() {
if b.computeEnc == 0 {
panic("no active compute pass")
}
C.computeEncEnd(b.computeEnc)
C.CFRelease(b.computeEnc)
b.computeEnc = 0
} }
func (b *Backend) DispatchCompute(x, y, z int) { func (b *Backend) DispatchCompute(x, y, z int) {
enc := b.startCompute() enc := b.computeEnc
p := b.newState.program if enc == 0 {
if p != b.state.program { panic("no active compute pass")
C.computeEncSetPipeline(enc, p.pipeline)
}
for i, t := range b.newState.textures {
current := b.state.textures[i]
if t != current {
C.computeEncSetTexture(enc, C.NSUInteger(i), t.texture)
}
}
for i, buf := range b.newState.buffers {
b.bufSizes[i] = uint32(buf.size)
current := b.state.buffers[i]
if buf.buffer != 0 {
if buf != current {
C.computeEncSetBuffer(enc, C.NSUInteger(i), buf.buffer)
}
} else if buf.size > 0 {
C.computeEncSetBytes(enc, unsafe.Pointer(&buf.store[0]), C.NSUInteger(buf.size), C.NSUInteger(i))
}
}
if n := len(b.newState.buffers); n > 0 {
C.computeEncSetBytes(enc, unsafe.Pointer(&b.bufSizes[0]), C.NSUInteger(n*4), spvBufferSizeConstantsBinding)
} }
C.computeEncSetBytes(enc, unsafe.Pointer(&b.bufSizes[0]), C.NSUInteger(len(b.bufSizes)*4), spvBufferSizeConstantsBinding)
threadgroupsPerGrid := C.MTLSize{ threadgroupsPerGrid := C.MTLSize{
width: C.NSUInteger(x), height: C.NSUInteger(y), depth: C.NSUInteger(z), width: C.NSUInteger(x), height: C.NSUInteger(y), depth: C.NSUInteger(z),
} }
sz := b.prog.groupSize
threadsPerThreadgroup := C.MTLSize{ threadsPerThreadgroup := C.MTLSize{
width: C.NSUInteger(p.groupSize[0]), height: C.NSUInteger(p.groupSize[1]), depth: C.NSUInteger(p.groupSize[2]), width: C.NSUInteger(sz[0]), height: C.NSUInteger(sz[1]), depth: C.NSUInteger(sz[2]),
} }
C.computeEncDispatch(enc, threadgroupsPerGrid, threadsPerThreadgroup) C.computeEncDispatch(enc, threadgroupsPerGrid, threadsPerThreadgroup)
b.state = b.newState
} }
func (b *Backend) stagingBuffer(size int) (C.CFTypeRef, int) { func (b *Backend) stagingBuffer(size int) (C.CFTypeRef, int) {
@@ -956,34 +933,14 @@ func (p *Pipeline) Release() {
func (b *Backend) BindTexture(unit int, tex driver.Texture) { func (b *Backend) BindTexture(unit int, tex driver.Texture) {
t := tex.(*Texture) t := tex.(*Texture)
b.newState.textures[unit] = t if enc := b.renderEnc; enc != 0 {
} C.renderEncSetFragmentTexture(enc, C.NSUInteger(unit), t.texture)
C.renderEncSetFragmentSamplerState(enc, C.NSUInteger(unit), t.sampler)
func (b *Backend) beginPass() C.CFTypeRef { } else if enc := b.computeEnc; enc != 0 {
r := b.newState.renderPass C.computeEncSetTexture(enc, C.NSUInteger(unit), t.texture)
if r == b.state.renderPass { } else {
return b.renderEnc panic("no active render nor compute pass")
} }
b.endEncoder()
var act C.MTLLoadAction
switch r.loadAction {
case driver.LoadActionKeep:
act = C.MTLLoadActionLoad
case driver.LoadActionClear:
act = C.MTLLoadActionClear
case driver.LoadActionInvalidate:
act = C.MTLLoadActionDontCare
}
b.ensureCmdBuffer()
c := r.clearColor
b.renderEnc = C.cmdBufferRenderEncoder(b.cmdBuffer, r.framebuffer.texture, act, C.float(c[0]), C.float(c[1]), C.float(c[2]), C.float(c[3]))
if b.renderEnc == 0 {
panic("metal: [MTLCommandBuffer renderCommandEncoderWithDescriptor:] failed")
}
r.loadAction = driver.LoadActionKeep
b.newState.renderPass = r
b.state.renderPass = r
return b.renderEnc
} }
func (b *Backend) ensureCmdBuffer() { func (b *Backend) ensureCmdBuffer() {
@@ -996,59 +953,23 @@ func (b *Backend) ensureCmdBuffer() {
} }
} }
func (b *Backend) encodeState() C.CFTypeRef {
enc := b.beginPass()
for i, t := range b.newState.textures {
current := b.state.textures[i]
if t != current {
C.renderEncSetFragmentTexture(enc, C.NSUInteger(i), t.texture)
C.renderEncSetFragmentSamplerState(enc, C.NSUInteger(i), t.sampler)
}
}
if p := b.newState.pipeline; p != b.state.pipeline {
C.renderEncSetRenderPipelineState(enc, p.pipeline)
}
if bf := b.newState.fragUniforms; bf != nil {
if bf.buffer != 0 {
if bf != b.state.fragUniforms {
C.renderEncSetFragmentBuffer(enc, bf.buffer, uniformBufferIndex, 0)
}
} else if bf.size > 0 {
C.renderEncSetFragmentBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex)
}
}
if bf := b.newState.vertUniforms; bf != nil {
if bf.buffer != 0 {
if bf != b.state.vertUniforms {
C.renderEncSetVertexBuffer(enc, bf.buffer, uniformBufferIndex, 0)
}
} else if bf.size > 0 {
C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex)
}
}
if v := b.newState.vertices; v.buffer != nil {
if v.buffer.buffer != 0 {
if v != b.state.vertices {
C.renderEncSetVertexBuffer(enc, v.buffer.buffer, attributeBufferIndex, C.NSUInteger(v.offset))
}
} else if n := v.buffer.size - v.offset; n > 0 {
C.renderEncSetVertexBytes(enc, unsafe.Pointer(&v.buffer.store[v.offset]), C.NSUInteger(n), attributeBufferIndex)
}
}
if vp := b.newState.viewport; vp != b.state.viewport {
C.renderEncViewport(enc, vp)
}
b.state = b.newState
return enc
}
func (b *Backend) BindPipeline(pipe driver.Pipeline) { func (b *Backend) BindPipeline(pipe driver.Pipeline) {
p := pipe.(*Pipeline) p := pipe.(*Pipeline)
b.newState.pipeline = p enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
C.renderEncSetRenderPipelineState(enc, p.pipeline)
} }
func (b *Backend) BindProgram(prog driver.Program) { func (b *Backend) BindProgram(prog driver.Program) {
b.newState.program = prog.(*Program) enc := b.computeEnc
if enc == 0 {
panic("no active compute pass")
}
p := prog.(*Program)
C.computeEncSetPipeline(enc, p.pipeline)
b.prog = p
} }
func (s *Shader) Release() { func (s *Shader) Release() {
@@ -1062,23 +983,56 @@ func (p *Program) Release() {
} }
func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) { func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
b.newState.buffers[binding] = buffer.(*Buffer) buf := buffer.(*Buffer)
b.bufSizes[binding] = uint32(buf.size)
enc := b.computeEnc
if enc == 0 {
panic("no active compute pass")
}
if buf.buffer != 0 {
C.computeEncSetBuffer(enc, C.NSUInteger(binding), buf.buffer)
} else if buf.size > 0 {
C.computeEncSetBytes(enc, unsafe.Pointer(&buf.store[0]), C.NSUInteger(buf.size), C.NSUInteger(binding))
}
} }
func (b *Backend) BindVertexUniforms(buf driver.Buffer) { func (b *Backend) BindVertexUniforms(buf driver.Buffer) {
bf := buf.(*Buffer) bf := buf.(*Buffer)
b.newState.vertUniforms = bf enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
if bf.buffer != 0 {
C.renderEncSetVertexBuffer(enc, bf.buffer, uniformBufferIndex, 0)
} else if bf.size > 0 {
C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex)
}
} }
func (b *Backend) BindFragmentUniforms(buf driver.Buffer) { func (b *Backend) BindFragmentUniforms(buf driver.Buffer) {
bf := buf.(*Buffer) bf := buf.(*Buffer)
b.newState.fragUniforms = bf enc := b.renderEnc
if enc == 0 {
panic("no active render pass")
}
if bf.buffer != 0 {
C.renderEncSetFragmentBuffer(enc, bf.buffer, uniformBufferIndex, 0)
} else if bf.size > 0 {
C.renderEncSetFragmentBytes(enc, unsafe.Pointer(&bf.store[0]), C.NSUInteger(bf.size), uniformBufferIndex)
}
} }
func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) { func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
bf := buf.(*Buffer) bf := buf.(*Buffer)
b.newState.vertices.buffer = bf enc := b.renderEnc
b.newState.vertices.offset = offset if enc == 0 {
panic("no active render pass")
}
if bf.buffer != 0 {
C.renderEncSetVertexBuffer(enc, bf.buffer, attributeBufferIndex, C.NSUInteger(offset))
} else if n := bf.size - offset; n > 0 {
C.renderEncSetVertexBytes(enc, unsafe.Pointer(&bf.store[offset]), C.NSUInteger(n), attributeBufferIndex)
}
} }
func (b *Backend) BindIndexBuffer(buf driver.Buffer) { func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
@@ -1162,32 +1116,46 @@ func (f *Framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int)
return nil return nil
} }
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer, desc driver.LoadDesc) { func (b *Backend) BeginRenderPass(fbo driver.Framebuffer, d driver.LoadDesc) {
b.newState.renderPass.framebuffer = fbo.(*Framebuffer) b.endEncoder()
b.newState.renderPass.loadAction = desc.Action b.ensureCmdBuffer()
c := desc.ClearColor f := fbo.(*Framebuffer)
b.newState.renderPass.clearColor = [4]float32{c.R, c.G, c.B, c.A} col := d.ClearColor
b.beginPass() var act C.MTLLoadAction
switch d.Action {
case driver.LoadActionKeep:
act = C.MTLLoadActionLoad
case driver.LoadActionClear:
act = C.MTLLoadActionClear
case driver.LoadActionInvalidate:
act = C.MTLLoadActionDontCare
}
b.renderEnc = C.cmdBufferRenderEncoder(b.cmdBuffer, f.texture, act, C.float(col.R), C.float(col.G), C.float(col.B), C.float(col.A))
if b.renderEnc == 0 {
panic("metal: [MTLCommandBuffer renderCommandEncoderWithDescriptor:] failed")
}
}
func (b *Backend) EndRenderPass() {
if b.renderEnc == 0 {
panic("no active render pass")
}
C.renderEncEnd(b.renderEnc)
C.CFRelease(b.renderEnc)
b.renderEnc = 0
} }
func (b *Backend) endEncoder() { func (b *Backend) endEncoder() {
if b.renderEnc != 0 { if b.renderEnc != 0 {
C.renderEncEnd(b.renderEnc) panic("active render pass")
C.CFRelease(b.renderEnc)
b.renderEnc = 0
b.state = state{}
} }
if b.computeEnc != 0 { if b.computeEnc != 0 {
C.computeEncEnd(b.computeEnc) panic("active compute pass")
C.CFRelease(b.computeEnc)
b.computeEnc = 0
b.state = state{}
} }
if b.blitEnc != 0 { if b.blitEnc != 0 {
C.blitEncEnd(b.blitEnc) C.blitEncEnd(b.blitEnc)
C.CFRelease(b.blitEnc) C.CFRelease(b.blitEnc)
b.blitEnc = 0 b.blitEnc = 0
b.state = state{}
} }
} }
+12 -3
View File
@@ -660,7 +660,7 @@ func (b *Backend) NewFramebuffer(tex driver.Texture) (driver.Framebuffer, error)
gltex := tex.(*texture) gltex := tex.(*texture)
fb := b.funcs.CreateFramebuffer() fb := b.funcs.CreateFramebuffer()
fbo := &framebuffer{backend: b, obj: fb} fbo := &framebuffer{backend: b, obj: fb}
b.BindFramebuffer(fbo, driver.LoadDesc{}) b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.obj)
if err := glErr(b.funcs); err != nil { if err := glErr(b.funcs); err != nil {
fbo.Release() fbo.Release()
return nil, err return nil, err
@@ -1194,7 +1194,7 @@ func (b *Backend) CopyTexture(dst driver.Texture, dstOrigin image.Point, src dri
func (f *framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) error { func (f *framebuffer) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
glErr(f.backend.funcs) glErr(f.backend.funcs)
f.backend.BindFramebuffer(f, driver.LoadDesc{}) f.backend.glstate.bindFramebuffer(f.backend.funcs, gl.FRAMEBUFFER, f.obj)
if len(pixels) < src.Dx()*src.Dy()*4 { if len(pixels) < src.Dx()*src.Dy()*4 {
return errors.New("unexpected RGBA size") return errors.New("unexpected RGBA size")
} }
@@ -1217,7 +1217,13 @@ func (b *Backend) BindPipeline(pl driver.Pipeline) {
b.BlendFunc(p.blend.SrcFactor, p.blend.DstFactor) b.BlendFunc(p.blend.SrcFactor, p.blend.DstFactor)
} }
func (b *Backend) BindFramebuffer(fbo driver.Framebuffer, desc driver.LoadDesc) { func (b *Backend) BeginCompute() {
}
func (b *Backend) EndCompute() {
}
func (b *Backend) BeginRenderPass(fbo driver.Framebuffer, desc driver.LoadDesc) {
b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.(*framebuffer).obj) b.glstate.bindFramebuffer(b.funcs, gl.FRAMEBUFFER, fbo.(*framebuffer).obj)
switch desc.Action { switch desc.Action {
case driver.LoadActionClear: case driver.LoadActionClear:
@@ -1228,6 +1234,9 @@ func (b *Backend) BindFramebuffer(fbo driver.Framebuffer, desc driver.LoadDesc)
} }
} }
func (b *Backend) EndRenderPass() {
}
func (f *framebuffer) Release() { func (f *framebuffer) Release() {
if f.foreign { if f.foreign {
panic("framebuffer not created by NewFramebuffer") panic("framebuffer not created by NewFramebuffer")
+1 -6
View File
@@ -346,7 +346,6 @@ func (s *stenciler) beginIntersect(sizes []image.Point) {
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if // floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
// no floating point support is available. // no floating point support is available.
s.intersections.resize(s.ctx, sizes) s.intersections.resize(s.ctx, sizes)
s.ctx.BindPipeline(s.ipipeline.pipeline.pipeline)
} }
func (s *stenciler) cover(idx int) stencilFBO { func (s *stenciler) cover(idx int) stencilFBO {
@@ -355,8 +354,6 @@ func (s *stenciler) cover(idx int) stencilFBO {
func (s *stenciler) begin(sizes []image.Point) { func (s *stenciler) begin(sizes []image.Point) {
s.fbos.resize(s.ctx, sizes) s.fbos.resize(s.ctx, sizes)
s.ctx.BindPipeline(s.pipeline.pipeline.pipeline)
s.ctx.BindIndexBuffer(s.indexBuf)
} }
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) { func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
@@ -388,8 +385,6 @@ func (p *pather) cover(mat materialType, col f32color.RGBA, col1, col2 f32color.
} }
func (c *coverer) cover(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.pipelines[mat]
c.ctx.BindPipeline(p.pipeline)
var uniforms *coverUniforms var uniforms *coverUniforms
switch mat { switch mat {
case materialColor: case materialColor:
@@ -411,7 +406,7 @@ func (c *coverer) cover(mat materialType, col f32color.RGBA, col1, col2 f32color
} }
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(c.ctx) c.pipelines[mat].UploadUniforms(c.ctx)
c.ctx.DrawArrays(driver.DrawModeTriangleStrip, 0, 4) c.ctx.DrawArrays(driver.DrawModeTriangleStrip, 0, 4)
} }