Files
gio/gpu/path.go
T
Elias Naur 9cdc8e6182 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>
2020-02-27 20:34:22 +01:00

349 lines
9.1 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package gpu
// GPU accelerated path drawing using the algorithms from
// Pathfinder (https://github.com/servo/pathfinder).
import (
"image"
"unsafe"
"gioui.org/f32"
"gioui.org/internal/path"
gunsafe "gioui.org/internal/unsafe"
)
type pather struct {
ctx Backend
viewport image.Point
stenciler *stenciler
coverer *coverer
}
type coverer struct {
ctx Backend
prog [2]Program
layout InputLayout
vars [2]struct {
z Uniform
uScale, uOffset Uniform
uUVScale, uUVOffset Uniform
uCoverUVScale, uCoverUVOffset Uniform
uColor Uniform
}
}
type stenciler struct {
ctx Backend
defFBO Framebuffer
prog Program
progLayout InputLayout
iprog Program
iprogLayout InputLayout
fbos fboSet
intersections fboSet
uScale, uOffset Uniform
uPathOffset Uniform
uIntersectUVOffset Uniform
uIntersectUVScale Uniform
indexBuf Buffer
}
type fboSet struct {
fbos []stencilFBO
}
type stencilFBO struct {
size image.Point
fbo Framebuffer
tex Texture
}
type pathData struct {
ncurves int
data Buffer
}
const (
// Number of path quads per draw batch.
pathBatchSize = 10000
)
const (
attribPathCorner = 0
attribPathMaxY = 1
attribPathFrom = 2
attribPathCtrl = 3
attribPathTo = 4
)
func newPather(ctx Backend) *pather {
return &pather{
ctx: ctx,
stenciler: newStenciler(ctx),
coverer: newCoverer(ctx),
}
}
func newCoverer(ctx Backend) *coverer {
prog, layout, err := createColorPrograms(ctx, shader_cover_vert, shader_cover_frag)
if err != nil {
panic(err)
}
c := &coverer{
ctx: ctx,
prog: prog,
layout: layout,
}
for i, prog := range prog {
switch materialType(i) {
case materialTexture:
uTex := prog.UniformFor("tex")
prog.Uniform1i(uTex, 0)
c.vars[i].uUVScale = prog.UniformFor("uniforms.uvScale")
c.vars[i].uUVOffset = prog.UniformFor("uniforms.uvOffset")
case materialColor:
c.vars[i].uColor = prog.UniformFor("color.color")
}
uCover := prog.UniformFor("cover")
prog.Uniform1i(uCover, 1)
c.vars[i].z = prog.UniformFor("uniforms.z")
c.vars[i].uScale = prog.UniformFor("uniforms.scale")
c.vars[i].uOffset = prog.UniformFor("uniforms.offset")
c.vars[i].uCoverUVScale = prog.UniformFor("uniforms.uvCoverScale")
c.vars[i].uCoverUVOffset = prog.UniformFor("uniforms.uvCoverOffset")
}
return c
}
func newStenciler(ctx Backend) *stenciler {
defFBO := ctx.DefaultFramebuffer()
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)
if err != nil {
panic(err)
}
coverLoc := iprog.UniformFor("cover")
iprog.Uniform1i(coverLoc, 0)
// Allocate a suitably large index buffer for drawing paths.
indices := make([]uint16, pathBatchSize*6)
for i := 0; i < pathBatchSize; i++ {
i := uint16(i)
indices[i*6+0] = i*4 + 0
indices[i*6+1] = i*4 + 1
indices[i*6+2] = i*4 + 2
indices[i*6+3] = i*4 + 2
indices[i*6+4] = i*4 + 1
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"),
uIntersectUVScale: iprog.UniformFor("uvparams.scale"),
uIntersectUVOffset: iprog.UniformFor("uvparams.offset"),
indexBuf: indexBuf,
}
}
func (s *fboSet) resize(ctx Backend, sizes []image.Point) {
// Add fbos.
for i := len(s.fbos); i < len(sizes); i++ {
s.fbos = append(s.fbos, stencilFBO{
fbo: ctx.NewFramebuffer(),
tex: ctx.NewTexture(FilterNearest, FilterNearest),
})
}
// Resize fbos.
for i, sz := range sizes {
f := &s.fbos[i]
// Resizing or recreating FBOs can introduce rendering stalls.
// Avoid if the space waste is not too high.
resize := sz.X > f.size.X || sz.Y > f.size.Y
waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
resize = resize || waste > 1.2
if resize {
f.size = sz
f.tex.Resize(TextureFormatFloat, sz.X, sz.Y)
f.fbo.BindTexture(f.tex)
}
}
// Delete extra fbos.
s.delete(ctx, len(sizes))
}
func (s *fboSet) invalidate(ctx Backend) {
for _, f := range s.fbos {
f.fbo.Invalidate()
}
}
func (s *fboSet) delete(ctx Backend, idx int) {
for i := idx; i < len(s.fbos); i++ {
f := s.fbos[i]
f.fbo.Release()
f.tex.Release()
}
s.fbos = s.fbos[:idx]
}
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()
}
func (p *pather) release() {
p.stenciler.release()
p.coverer.release()
}
func (c *coverer) release() {
for _, p := range c.prog {
p.Release()
}
c.layout.Release()
}
func buildPath(ctx Backend, p []byte) *pathData {
buf := ctx.NewBuffer(BufferTypeData, p)
return &pathData{
ncurves: len(p) / path.VertStride,
data: buf,
}
}
func (p *pathData) release() {
p.data.Release()
}
func (p *pather) begin(sizes []image.Point) {
p.stenciler.begin(sizes)
}
func (p *pather) end() {
p.stenciler.end()
}
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data *pathData) {
p.stenciler.stencilPath(bounds, offset, uv, data)
}
func (s *stenciler) beginIntersect(sizes []image.Point) {
s.ctx.NilTexture().Bind(1)
s.ctx.BlendFunc(BlendFactorDstColor, BlendFactorZero)
// 8 bit coverage is enough, but OpenGL ES only supports single channel
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
// no floating point support is available.
s.intersections.resize(s.ctx, sizes)
s.ctx.ClearColor(1.0, 0.0, 0.0, 0.0)
s.iprog.Bind()
}
func (s *stenciler) endIntersect() {
s.defFBO.Bind()
}
func (s *stenciler) invalidateFBO() {
s.intersections.invalidate(s.ctx)
s.fbos.invalidate(s.ctx)
s.defFBO.Bind()
}
func (s *stenciler) cover(idx int) stencilFBO {
return s.fbos.fbos[idx]
}
func (s *stenciler) begin(sizes []image.Point) {
s.ctx.NilTexture().Bind(1)
s.ctx.BlendFunc(BlendFactorOne, BlendFactorOne)
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) {
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())}
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
s.prog.Uniform2f(s.uScale, scale.X, scale.Y)
s.prog.Uniform2f(s.uOffset, orig.X, orig.Y)
s.prog.Uniform2f(s.uPathOffset, offset.X, offset.Y)
// Draw in batches that fit in uint16 indices.
start := 0
nquads := data.ncurves / 4
for start < nquads {
batch := nquads - start
if max := pathBatchSize; batch > max {
batch = max
}
off := path.VertStride * start * 4
data.data.BindVertex(path.VertStride, off)
s.ctx.DrawElements(DrawModeTriangles, 0, batch*6)
start += batch
}
}
func (s *stenciler) end() {
s.defFBO.Bind()
}
func (p *pather) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
p.coverer.cover(z, mat, col, scale, off, uvScale, uvOff, coverScale, coverOff)
}
func (c *coverer) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) {
p := c.prog[mat]
p.Bind()
switch mat {
case materialColor:
p.Uniform4f(c.vars[mat].uColor, col[0], col[1], col[2], col[3])
case materialTexture:
p.Uniform2f(c.vars[mat].uUVScale, uvScale.X, uvScale.Y)
p.Uniform2f(c.vars[mat].uUVOffset, uvOff.X, uvOff.Y)
}
p.Uniform1f(c.vars[mat].z, z)
p.Uniform2f(c.vars[mat].uScale, scale.X, scale.Y)
p.Uniform2f(c.vars[mat].uOffset, off.X, off.Y)
p.Uniform2f(c.vars[mat].uCoverUVScale, coverScale.X, coverScale.Y)
p.Uniform2f(c.vars[mat].uCoverUVOffset, coverOff.X, coverOff.Y)
c.ctx.DrawArrays(DrawModeTriangleStrip, 0, 4)
}