gpu: introduce pipeline abstraction

Modern API such as Metal and Vulkan want clients to compile expensive
state changes into pipeline objects. Change our GPU driver abstraction
to match, thereby paving the way for future drivers.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-08-10 16:18:47 +02:00
parent 13b93b27d8
commit afaa31eca8
9 changed files with 548 additions and 400 deletions
+87 -65
View File
@@ -281,8 +281,7 @@ type texture struct {
type blitter struct {
ctx driver.Device
viewport image.Point
prog [3]*program
layout driver.InputLayout
pipelines [3]*pipeline
colUniforms *blitColUniforms
texUniforms *blitTexUniforms
linearGradientUniforms *blitLinearGradientUniforms
@@ -321,8 +320,8 @@ type uniformBuffer struct {
ptr []byte
}
type program struct {
prog driver.Program
type pipeline struct {
pipeline driver.Pipeline
vertUniforms *uniformBuffer
fragUniforms *uniformBuffer
}
@@ -453,7 +452,6 @@ func (g *gpu) Frame(target RenderTarget) error {
}
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.stencilTimer.begin()
g.ctx.SetBlend(true)
g.renderer.packStencils(&g.drawOps.pathOps)
g.renderer.stencilClips(g.drawOps.pathCache, g.drawOps.pathOps)
g.renderer.packIntersections(g.drawOps.imageOps)
@@ -463,7 +461,6 @@ func (g *gpu) Frame(target RenderTarget) error {
g.ctx.BindFramebuffer(defFBO)
g.ctx.Viewport(0, 0, viewport.X, viewport.Y)
g.renderer.drawOps(g.cache, g.drawOps.imageOps)
g.ctx.SetBlend(false)
g.renderer.pather.stenciler.invalidateFBO()
g.coverTimer.end()
g.ctx.BindFramebuffer(defFBO)
@@ -558,90 +555,128 @@ func newBlitter(ctx driver.Device) *blitter {
b.colUniforms = new(blitColUniforms)
b.texUniforms = new(blitTexUniforms)
b.linearGradientUniforms = new(blitLinearGradientUniforms)
prog, layout, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
pipelines, err := createColorPrograms(ctx, gio.Shader_blit_vert, gio.Shader_blit_frag,
[3]interface{}{&b.colUniforms.vert, &b.linearGradientUniforms.vert, &b.texUniforms.vert},
[3]interface{}{&b.colUniforms.frag, &b.linearGradientUniforms.frag, nil},
)
if err != nil {
panic(err)
}
b.prog = prog
b.layout = layout
b.pipelines = pipelines
return b
}
func (b *blitter) release() {
b.quadVerts.Release()
for _, p := range b.prog {
for _, p := range b.pipelines {
p.Release()
}
b.layout.Release()
}
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, vertUniforms, fragUniforms [3]interface{}) ([3]*program, driver.InputLayout, error) {
var progs [3]*program
func createColorPrograms(b driver.Device, vsSrc shader.Sources, fsSrc [3]shader.Sources, vertUniforms, fragUniforms [3]interface{}) ([3]*pipeline, error) {
var pipelines [3]*pipeline
blend := driver.BlendDesc{
Enable: true,
SrcFactor: driver.BlendFactorOne,
DstFactor: driver.BlendFactorOneMinusSrcAlpha,
}
layout := []shader.InputDesc{
{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
}
vsh, err := b.NewVertexShader(vsSrc)
if err != nil {
return pipelines, err
}
defer vsh.Release()
{
prog, err := b.NewProgram(vsSrc, fsSrc[materialTexture])
fsh, err := b.NewFragmentShader(fsSrc[materialTexture])
if err != nil {
return progs, nil, err
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: driver.TextureFormatOutput,
})
if err != nil {
return pipelines, err
}
var vertBuffer, fragBuffer *uniformBuffer
if u := vertUniforms[materialTexture]; u != nil {
vertBuffer = newUniformBuffer(b, u)
prog.SetVertexUniforms(vertBuffer.buf)
}
if u := fragUniforms[materialTexture]; u != nil {
fragBuffer = newUniformBuffer(b, u)
prog.SetFragmentUniforms(fragBuffer.buf)
}
progs[materialTexture] = newProgram(prog, vertBuffer, fragBuffer)
pipelines[materialTexture] = &pipeline{pipe, vertBuffer, fragBuffer}
}
{
var vertBuffer, fragBuffer *uniformBuffer
prog, err := b.NewProgram(vsSrc, fsSrc[materialColor])
fsh, err := b.NewFragmentShader(fsSrc[materialColor])
if err != nil {
progs[materialTexture].Release()
return progs, nil, err
pipelines[materialTexture].Release()
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: driver.TextureFormatOutput,
})
if err != nil {
pipelines[materialTexture].Release()
return pipelines, err
}
if u := vertUniforms[materialColor]; u != nil {
vertBuffer = newUniformBuffer(b, u)
prog.SetVertexUniforms(vertBuffer.buf)
}
if u := fragUniforms[materialColor]; u != nil {
fragBuffer = newUniformBuffer(b, u)
prog.SetFragmentUniforms(fragBuffer.buf)
}
progs[materialColor] = newProgram(prog, vertBuffer, fragBuffer)
pipelines[materialColor] = &pipeline{pipe, vertBuffer, fragBuffer}
}
{
var vertBuffer, fragBuffer *uniformBuffer
prog, err := b.NewProgram(vsSrc, fsSrc[materialLinearGradient])
fsh, err := b.NewFragmentShader(fsSrc[materialLinearGradient])
if err != nil {
progs[materialTexture].Release()
progs[materialColor].Release()
return progs, nil, err
pipelines[materialTexture].Release()
pipelines[materialColor].Release()
return pipelines, err
}
defer fsh.Release()
pipe, err := b.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
BlendDesc: blend,
VertexLayout: layout,
PixelFormat: driver.TextureFormatOutput,
})
if err != nil {
pipelines[materialTexture].Release()
pipelines[materialColor].Release()
return pipelines, err
}
if u := vertUniforms[materialLinearGradient]; u != nil {
vertBuffer = newUniformBuffer(b, u)
prog.SetVertexUniforms(vertBuffer.buf)
}
if u := fragUniforms[materialLinearGradient]; u != nil {
fragBuffer = newUniformBuffer(b, u)
prog.SetFragmentUniforms(fragBuffer.buf)
}
progs[materialLinearGradient] = newProgram(prog, vertBuffer, fragBuffer)
pipelines[materialLinearGradient] = &pipeline{pipe, vertBuffer, fragBuffer}
}
layout, err := b.NewInputLayout(vsSrc, []shader.InputDesc{
{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
})
if err != nil {
progs[materialTexture].Release()
progs[materialColor].Release()
progs[materialLinearGradient].Release()
return progs, nil, err
for _, p := range pipelines {
p.Release()
}
return pipelines, err
}
return progs, layout, nil
return pipelines, nil
}
func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) {
@@ -669,7 +704,6 @@ func (r *renderer) intersect(ops []imageOp) {
fbo := -1
r.pather.stenciler.beginIntersect(r.intersections.sizes)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 4*4, 0)
r.ctx.BindInputLayout(r.pather.stenciler.iprog.layout)
for _, img := range ops {
if img.clipType != clipTypeIntersection {
continue
@@ -705,9 +739,9 @@ func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) {
r.ctx.BindTexture(0, fbo.tex)
coverScale, coverOff := texSpaceTransform(layout.FRect(uv), fbo.size)
subScale, subOff := texSpaceTransform(layout.FRect(sub), p.clip.Size())
r.pather.stenciler.iprog.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
r.pather.stenciler.iprog.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
r.pather.stenciler.iprog.prog.UploadUniforms()
r.pather.stenciler.ipipeline.uniforms.vert.uvTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
r.pather.stenciler.ipipeline.uniforms.vert.subUVTransform = [4]float32{subScale.X, subScale.Y, subOff.X, subOff.Y}
r.pather.stenciler.ipipeline.pipeline.UploadUniforms(r.ctx)
r.ctx.DrawArrays(driver.DrawModeTriangleStrip, 0, 4)
}
@@ -1059,9 +1093,7 @@ func (d *drawState) materialFor(rect f32.Rectangle, off f32.Point, partTrans f32
}
func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
r.ctx.BlendFunc(driver.BlendFactorOne, driver.BlendFactorOneMinusSrcAlpha)
r.ctx.BindVertexBuffer(r.blitter.quadVerts, 4*4, 0)
r.ctx.BindInputLayout(r.pather.coverer.layout)
var coverTex driver.Texture
for _, img := range ops {
m := img.material
@@ -1096,8 +1128,8 @@ func (r *renderer) drawOps(cache *resourceCache, ops []imageOp) {
}
func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D) {
p := b.prog[mat]
b.ctx.BindProgram(p.prog)
p := b.pipelines[mat]
b.ctx.BindPipeline(p.pipeline)
var uniforms *blitUniforms
switch mat {
case materialColor:
@@ -1118,7 +1150,7 @@ func (b *blitter) blit(mat materialType, col f32color.RGBA, col1, col2 f32color.
uniforms = &b.linearGradientUniforms.vert.blitUniforms
}
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
p.UploadUniforms()
p.UploadUniforms(b.ctx)
b.ctx.DrawArrays(driver.DrawModeTriangleStrip, 0, 4)
}
@@ -1146,36 +1178,26 @@ func (u *uniformBuffer) Release() {
u.buf = nil
}
func newProgram(prog driver.Program, vertUniforms, fragUniforms *uniformBuffer) *program {
if vertUniforms != nil {
prog.SetVertexUniforms(vertUniforms.buf)
}
if fragUniforms != nil {
prog.SetFragmentUniforms(fragUniforms.buf)
}
return &program{prog: prog, vertUniforms: vertUniforms, fragUniforms: fragUniforms}
}
func (p *program) UploadUniforms() {
func (p *pipeline) UploadUniforms(ctx driver.Device) {
if p.vertUniforms != nil {
ctx.BindVertexUniforms(p.vertUniforms.buf)
p.vertUniforms.Upload()
}
if p.fragUniforms != nil {
ctx.BindFragmentUniforms(p.fragUniforms.buf)
p.fragUniforms.Upload()
}
}
func (p *program) Release() {
p.prog.Release()
p.prog = nil
func (p *pipeline) Release() {
p.pipeline.Release()
if p.vertUniforms != nil {
p.vertUniforms.Release()
p.vertUniforms = nil
}
if p.fragUniforms != nil {
p.fragUniforms.Release()
p.fragUniforms = nil
}
*p = pipeline{}
}
// texSpaceTransform return the scale and offset that transforms the given subimage