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 }