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 <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2020-02-19 17:16:55 +01:00
parent ac7029fa24
commit 9cdc8e6182
4 changed files with 158 additions and 61 deletions
+18 -2
View File
@@ -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()
}
+78 -20
View File
@@ -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
+25 -20
View File
@@ -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
+37 -19
View File
@@ -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
}