From 9cdc8e6182bdd5027f51f1f1fea76826027f4b57 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 19 Feb 2020 17:16:55 +0100 Subject: [PATCH] gpu,gpu/gl: introduce InputLayout and use shader reflection data InputLayout is the abstraction for the mapping between vertex data and shader inputs. The mapping is implicit in OpenGL but explicit in Direct3D. Infer the attribute name location index using shader reflection data, and get rid of a parameter to NewProgram. Signed-off-by: Elias Naur --- gpu/backend.go | 20 +++++++++- gpu/gl/backend.go | 98 +++++++++++++++++++++++++++++++++++++---------- gpu/gpu.go | 45 ++++++++++++---------- gpu/path.go | 56 ++++++++++++++++++--------- 4 files changed, 158 insertions(+), 61 deletions(-) diff --git a/gpu/backend.go b/gpu/backend.go index bc3a3cb0..d58d2473 100644 --- a/gpu/backend.go +++ b/gpu/backend.go @@ -23,8 +23,8 @@ type Backend interface { NilTexture() Texture NewFramebuffer() Framebuffer NewBuffer(typ BufferType, data []byte) Buffer - NewProgram(vertexShader, fragmentShader ShaderSources, attribMap []string) (Program, error) - SetupVertexArray(slot int, size int, dataType DataType, stride, offset int) + NewProgram(vertexShader, fragmentShader ShaderSources) (Program, error) + NewInputLayout(vertexShader ShaderSources, layout []InputDesc) (InputLayout, error) DepthFunc(f DepthFunc) ClearColor(r, g, b, a float32) @@ -65,6 +65,21 @@ type InputLocation struct { Size int } +// InputDesc describes a vertex attribute as laid out in a Buffer. +type InputDesc struct { + Type DataType + Size int + + Offset int +} + +// InputLayout is the backend specific representation of the mapping +// between Buffers and shader attributes. +type InputLayout interface { + Bind() + Release() +} + type BlendFactor uint8 type DrawMode uint8 @@ -100,6 +115,7 @@ type Program interface { type Uniform interface{} type Buffer interface { + BindVertex(stride, offset int) Bind() Release() } diff --git a/gpu/gl/backend.go b/gpu/gl/backend.go index c1b2d02b..0e689521 100644 --- a/gpu/gl/backend.go +++ b/gpu/gl/backend.go @@ -34,6 +34,14 @@ type glstate struct { nattr int prog *gpuProgram texUnits [2]*gpuTexture + layout *gpuInputLayout + buffer bufferBinding +} + +type bufferBinding struct { + buf *gpuBuffer + offset int + stride int } type gpuTimer struct { @@ -52,9 +60,9 @@ type gpuFramebuffer struct { } type gpuBuffer struct { - funcs Functions - obj Buffer - typ Enum + backend *Backend + obj Buffer + typ Enum } type gpuProgram struct { @@ -63,6 +71,12 @@ type gpuProgram struct { nattr int } +type gpuInputLayout struct { + backend *Backend + inputs []gpu.InputLocation + layout []gpu.InputDesc +} + // textureTriple holds the type settings for // a TexImage2D call. type textureTriple struct { @@ -159,7 +173,7 @@ func (b *Backend) NewBuffer(typ gpu.BufferType, data []byte) gpu.Buffer { default: panic("unsupported buffer type") } - buf := &gpuBuffer{funcs: b.funcs, obj: obj, typ: gltyp} + buf := &gpuBuffer{backend: b, obj: obj, typ: gltyp} buf.Bind() b.funcs.BufferData(gltyp, data, STATIC_DRAW) return buf @@ -232,10 +246,12 @@ func (b *Backend) SetBlend(enable bool) { } func (b *Backend) DrawElements(mode gpu.DrawMode, off, count int) { + b.setupVertexArrays() b.funcs.DrawElements(toGLDrawMode(mode), count, UNSIGNED_SHORT, off) } func (b *Backend) DrawArrays(mode gpu.DrawMode, off, count int) { + b.setupVertexArrays() b.funcs.DrawArrays(toGLDrawMode(mode), off, count) } @@ -284,7 +300,27 @@ func (b *Backend) DepthFunc(f gpu.DepthFunc) { b.funcs.DepthFunc(glfunc) } -func (b *Backend) NewProgram(vssrc, fssrc gpu.ShaderSources, attr []string) (gpu.Program, error) { +func (b *Backend) NewInputLayout(vs gpu.ShaderSources, layout []gpu.InputDesc) (gpu.InputLayout, error) { + if len(vs.Inputs) != len(layout) { + return nil, fmt.Errorf("NewInputLayout: got %d inputs, expected %d", len(layout), len(vs.Inputs)) + } + for i, inp := range vs.Inputs { + if exp, got := inp.Size, layout[i].Size; exp != got { + return nil, fmt.Errorf("NewInputLayout: data size mismatch for %q: got %d expected %d", inp.Name, got, exp) + } + } + return &gpuInputLayout{ + backend: b, + inputs: vs.Inputs, + layout: layout, + }, nil +} + +func (b *Backend) NewProgram(vssrc, fssrc gpu.ShaderSources) (gpu.Program, error) { + attr := make([]string, len(vssrc.Inputs)) + for _, inp := range vssrc.Inputs { + attr[inp.Location] = inp.Name + } p, err := CreateProgram(b.funcs, vssrc.GLES2, fssrc.GLES2, attr) if err != nil { return nil, err @@ -322,29 +358,45 @@ func (p *gpuProgram) UniformFor(uniform string) gpu.Uniform { return GetUniformLocation(f, p.obj, uniform) } -func (b *Backend) SetupVertexArray(slot int, size int, dataType gpu.DataType, stride, offset int) { - var gltyp Enum - switch dataType { - case gpu.DataTypeFloat: - gltyp = FLOAT - case gpu.DataTypeShort: - gltyp = SHORT - default: - panic("unsupported data type") - } - b.funcs.VertexAttribPointer(Attrib(slot), size, gltyp, false, stride, offset) -} - func (p *gpuProgram) Release() { p.backend.funcs.DeleteProgram(p.obj) } func (b *gpuBuffer) Release() { - b.funcs.DeleteBuffer(b.obj) + b.backend.funcs.DeleteBuffer(b.obj) +} + +func (b *gpuBuffer) BindVertex(stride, offset int) { + if b.typ != ARRAY_BUFFER { + panic("not a vertex buffer") + } + b.backend.state.buffer = bufferBinding{buf: b, stride: stride, offset: offset} +} + +func (b *Backend) setupVertexArrays() { + layout := b.state.layout + if layout == nil { + panic("no input layout is current") + } + buf := b.state.buffer + b.funcs.BindBuffer(ARRAY_BUFFER, buf.buf.obj) + for i, inp := range layout.inputs { + l := layout.layout[i] + var gltyp Enum + switch l.Type { + case gpu.DataTypeFloat: + gltyp = FLOAT + case gpu.DataTypeShort: + gltyp = SHORT + default: + panic("unsupported data type") + } + b.funcs.VertexAttribPointer(Attrib(inp.Location), l.Size, gltyp, false, buf.stride, buf.offset+l.Offset) + } } func (b *gpuBuffer) Bind() { - b.funcs.BindBuffer(b.typ, b.obj) + b.backend.funcs.BindBuffer(b.typ, b.obj) } func (f *gpuFramebuffer) IsComplete() error { @@ -437,6 +489,12 @@ func (t *gpuTimer) Duration() (time.Duration, bool) { return time.Duration(nanos), true } +func (l *gpuInputLayout) Bind() { + l.backend.state.layout = l +} + +func (l *gpuInputLayout) Release() {} + // floatTripleFor determines the best texture triple for floating point FBOs. func floatTripleFor(f Functions, ver [2]int, exts []string) (textureTriple, error) { var triples []textureTriple diff --git a/gpu/gpu.go b/gpu/gpu.go index bc1f89af..5eb6678e 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -212,6 +212,7 @@ type blitter struct { ctx Backend viewport image.Point prog [2]Program + layout InputLayout vars [2]struct { z Uniform uScale, uOffset Uniform @@ -234,10 +235,6 @@ const ( materialTexture ) -var ( - blitAttribs = []string{"pos", "uv"} -) - const ( attribPos = 0 attribUV = 1 @@ -375,7 +372,7 @@ func (r *renderer) release() { } func newBlitter(ctx Backend) *blitter { - prog, err := createColorPrograms(ctx, shader_blit_vert, shader_blit_frag) + prog, layout, err := createColorPrograms(ctx, shader_blit_vert, shader_blit_frag) if err != nil { panic(err) } @@ -390,6 +387,7 @@ func newBlitter(ctx Backend) *blitter { b := &blitter{ ctx: ctx, prog: prog, + layout: layout, quadVerts: quadVerts, } for i, prog := range prog { @@ -414,21 +412,31 @@ func (b *blitter) release() { for _, p := range b.prog { p.Release() } + b.layout.Release() } -func createColorPrograms(ctx Backend, vsSrc ShaderSources, fsSrc [2]ShaderSources) ([2]Program, error) { +func createColorPrograms(ctx Backend, vsSrc ShaderSources, fsSrc [2]ShaderSources) ([2]Program, InputLayout, error) { var prog [2]Program var err error - prog[materialTexture], err = ctx.NewProgram(vsSrc, fsSrc[materialTexture], blitAttribs) + prog[materialTexture], err = ctx.NewProgram(vsSrc, fsSrc[materialTexture]) if err != nil { - return prog, err + return prog, nil, err } - prog[materialColor], err = ctx.NewProgram(vsSrc, fsSrc[materialColor], blitAttribs) + prog[materialColor], err = ctx.NewProgram(vsSrc, fsSrc[materialColor]) if err != nil { prog[materialTexture].Release() - return prog, err + return prog, nil, err } - return prog, nil + layout, err := ctx.NewInputLayout(vsSrc, []InputDesc{ + {Type: DataTypeFloat, Size: 2, Offset: 0}, + {Type: DataTypeFloat, Size: 2, Offset: 4 * 2}, + }) + if err != nil { + prog[materialTexture].Release() + prog[materialColor].Release() + return prog, nil, err + } + return prog, layout, nil } func (r *renderer) stencilClips(pathCache *opCache, ops []*pathOp) { @@ -456,9 +464,8 @@ func (r *renderer) intersect(ops []imageOp) { } fbo := -1 r.pather.stenciler.beginIntersect(r.intersections.sizes) - r.blitter.quadVerts.Bind() - r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0) - r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2) + r.blitter.quadVerts.BindVertex(4*4, 0) + r.pather.stenciler.iprogLayout.Bind() for _, img := range ops { if img.clipType != clipTypeIntersection { continue @@ -764,9 +771,8 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3 func (r *renderer) drawZOps(ops []imageOp) { r.ctx.SetDepthTest(true) - r.blitter.quadVerts.Bind() - r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0) - r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2) + r.blitter.quadVerts.BindVertex(4*4, 0) + r.blitter.layout.Bind() // Render front to back. for i := len(ops) - 1; i >= 0; i-- { img := ops[i] @@ -786,9 +792,8 @@ func (r *renderer) drawOps(ops []imageOp) { r.ctx.SetDepthTest(true) r.ctx.DepthMask(false) r.ctx.BlendFunc(BlendFactorOne, BlendFactorOneMinusSrcAlpha) - r.blitter.quadVerts.Bind() - r.ctx.SetupVertexArray(attribPos, 2, DataTypeFloat, 4*4, 0) - r.ctx.SetupVertexArray(attribUV, 2, DataTypeFloat, 4*4, 4*2) + r.blitter.quadVerts.BindVertex(4*4, 0) + r.pather.coverer.layout.Bind() var coverTex Texture for _, img := range ops { m := img.material diff --git a/gpu/path.go b/gpu/path.go index 9bdea64a..ec2f8e0e 100644 --- a/gpu/path.go +++ b/gpu/path.go @@ -24,9 +24,10 @@ type pather struct { } type coverer struct { - ctx Backend - prog [2]Program - vars [2]struct { + ctx Backend + prog [2]Program + layout InputLayout + vars [2]struct { z Uniform uScale, uOffset Uniform uUVScale, uUVOffset Uniform @@ -39,7 +40,9 @@ type stenciler struct { ctx Backend defFBO Framebuffer prog Program + progLayout InputLayout iprog Program + iprogLayout InputLayout fbos fboSet intersections fboSet uScale, uOffset Uniform @@ -64,11 +67,6 @@ type pathData struct { data Buffer } -var ( - pathAttribs = []string{"corner", "maxy", "from", "ctrl", "to"} - intersectAttribs = []string{"pos", "uv"} -) - const ( // Number of path quads per draw batch. pathBatchSize = 10000 @@ -91,13 +89,14 @@ func newPather(ctx Backend) *pather { } func newCoverer(ctx Backend) *coverer { - prog, err := createColorPrograms(ctx, shader_cover_vert, shader_cover_frag) + prog, layout, err := createColorPrograms(ctx, shader_cover_vert, shader_cover_frag) if err != nil { panic(err) } c := &coverer{ - ctx: ctx, - prog: prog, + ctx: ctx, + prog: prog, + layout: layout, } for i, prog := range prog { switch materialType(i) { @@ -122,11 +121,11 @@ func newCoverer(ctx Backend) *coverer { func newStenciler(ctx Backend) *stenciler { defFBO := ctx.DefaultFramebuffer() - prog, err := ctx.NewProgram(shader_stencil_vert, shader_stencil_frag, pathAttribs) + prog, err := ctx.NewProgram(shader_stencil_vert, shader_stencil_frag) if err != nil { panic(err) } - iprog, err := ctx.NewProgram(shader_intersect_vert, shader_intersect_frag, intersectAttribs) + iprog, err := ctx.NewProgram(shader_intersect_vert, shader_intersect_frag) if err != nil { panic(err) } @@ -144,11 +143,30 @@ func newStenciler(ctx Backend) *stenciler { indices[i*6+5] = i*4 + 3 } indexBuf := ctx.NewBuffer(BufferTypeIndices, gunsafe.BytesView(indices)) + progLayout, err := ctx.NewInputLayout(shader_stencil_vert, []InputDesc{ + {Type: DataTypeShort, Size: 2, Offset: int(unsafe.Offsetof((*(*path.Vertex)(nil)).CornerX))}, + {Type: DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*path.Vertex)(nil)).MaxY))}, + {Type: DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*path.Vertex)(nil)).FromX))}, + {Type: DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*path.Vertex)(nil)).CtrlX))}, + {Type: DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*path.Vertex)(nil)).ToX))}, + }) + if err != nil { + panic(err) + } + iprogLayout, err := ctx.NewInputLayout(shader_intersect_vert, []InputDesc{ + {Type: DataTypeFloat, Size: 2, Offset: 0}, + {Type: DataTypeFloat, Size: 2, Offset: 4 * 2}, + }) + if err != nil { + panic(err) + } return &stenciler{ ctx: ctx, defFBO: defFBO, prog: prog, + progLayout: progLayout, iprog: iprog, + iprogLayout: iprogLayout, uScale: prog.UniformFor("uniforms.scale"), uOffset: prog.UniformFor("uniforms.offset"), uPathOffset: prog.UniformFor("uniforms.pathOffset"), @@ -201,7 +219,10 @@ func (s *fboSet) delete(ctx Backend, idx int) { func (s *stenciler) release() { s.fbos.delete(s.ctx, 0) + s.progLayout.Release() s.prog.Release() + s.iprogLayout.Release() + s.iprog.Release() s.indexBuf.Release() } @@ -214,6 +235,7 @@ func (c *coverer) release() { for _, p := range c.prog { p.Release() } + c.layout.Release() } func buildPath(ctx Backend, p []byte) *pathData { @@ -271,11 +293,11 @@ func (s *stenciler) begin(sizes []image.Point) { s.fbos.resize(s.ctx, sizes) s.ctx.ClearColor(0.0, 0.0, 0.0, 0.0) s.prog.Bind() + s.progLayout.Bind() s.indexBuf.Bind() } func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) { - data.data.Bind() s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy()) // Transform UI coordinates to OpenGL coordinates. texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())} @@ -293,11 +315,7 @@ func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv ima batch = max } off := path.VertStride * start * 4 - s.ctx.SetupVertexArray(attribPathCorner, 2, DataTypeShort, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CornerX))) - s.ctx.SetupVertexArray(attribPathMaxY, 1, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).MaxY))) - s.ctx.SetupVertexArray(attribPathFrom, 2, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).FromX))) - s.ctx.SetupVertexArray(attribPathCtrl, 2, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).CtrlX))) - s.ctx.SetupVertexArray(attribPathTo, 2, DataTypeFloat, path.VertStride, off+int(unsafe.Offsetof((*(*path.Vertex)(nil)).ToX))) + data.data.BindVertex(path.VertStride, off) s.ctx.DrawElements(DrawModeTriangles, 0, batch*6) start += batch }