mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
9cdc8e6182
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>
349 lines
9.1 KiB
Go
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)
|
|
}
|