Skip to content

Commit

Permalink
add ShaderBuffer support to glbuild.Programmer
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Oct 6, 2024
1 parent 903fd88 commit 1d0e94b
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 38 deletions.
12 changes: 9 additions & 3 deletions examples/test/glsdf3test.go
Original file line number Diff line number Diff line change
Expand Up @@ -731,9 +731,11 @@ func visualize(sdf glbuild.Shader3D, filename string) error {
const desiredScale = 2.0
diag := ms3.Norm(bb.Size())
sdf = gsdf.Scale(sdf, desiredScale/diag)
written, err := programmer.WriteFragVisualizerSDF3(fp, sdf)
written, ssbos, err := programmer.WriteFragVisualizerSDF3(fp, sdf)
if err != nil {
return err
} else if len(ssbos) > 0 {
return errors.New("ssbos unsupported in frag visualizer")
}
stat, err := fp.Stat()
if err != nil {
Expand Down Expand Up @@ -895,11 +897,13 @@ func makeGPUSDF3(s glbuild.Shader3D) *gleval.SDF3Compute {
panic("nil Shader3D")
}
var source bytes.Buffer
n, err := programmer.WriteComputeSDF3(&source, s)
n, ssbos, err := programmer.WriteComputeSDF3(&source, s)
if err != nil {
panic(err)
} else if n != source.Len() {
panic("bytes written mismatch")
} else if len(ssbos) > 0 {
panic("ssbos unsupported")
}
invocX, _, _ := programmer.ComputeInvocations()
sdfgpu, err := gleval.NewComputeGPUSDF3(&source, s.Bounds(), gleval.ComputeConfig{InvocX: invocX})
Expand All @@ -915,11 +919,13 @@ func makeGPUSDF2(s glbuild.Shader2D) gleval.SDF2 {
}
var source bytes.Buffer

n, err := programmer.WriteComputeSDF2(&source, s)
n, ssbos, err := programmer.WriteComputeSDF2(&source, s)
if err != nil {
panic(err)
} else if n != source.Len() {
panic("bytes written mismatch")
} else if len(ssbos) > 0 {
panic("ssbos unsupported")
}
invocX, _, _ := programmer.ComputeInvocations()
sdfgpu, err := gleval.NewComputeGPUSDF2(&source, s.Bounds(), gleval.ComputeConfig{InvocX: invocX})
Expand Down
134 changes: 107 additions & 27 deletions glbuild/glbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type Programmer struct {
scratchNodes []Shader
scratch []byte
computeHeader []byte
ssbosScratch []ShaderBuffer
// names maps shader names to body hashes for checking duplicates.
names map[uint64]uint64
// Invocations size in X (local group size) to give each compute work group.
Expand Down Expand Up @@ -116,20 +117,20 @@ func (p *Programmer) ComputeInvocations() (int, int, int) {

// WriteDistanceIO creates the bare bones I/O compute program for calculating SDF
// and writes it to the writer.
func (p *Programmer) WriteComputeSDF3(w io.Writer, obj Shader3D) (int, error) {
func (p *Programmer) WriteComputeSDF3(w io.Writer, obj Shader3D) (int, []ShaderBuffer, error) {
baseName, nodes, err := ParseAppendNodes(p.scratchNodes[:0], obj)
if err != nil {
return 0, err
return 0, nil, err
}
// Begin writing shader source code.
n, err := w.Write(p.computeHeader)
if err != nil {
return n, err
return n, nil, err
}
ngot, err := p.writeShaders(w, nodes)
ngot, ssbos, err := p.writeShaders(w, nodes)
n += ngot
if err != nil {
return n, err
return n, nil, err
}
ngot, err = fmt.Fprintf(w, `
Expand All @@ -154,25 +155,25 @@ void main() {
`, p.invocX, baseName)

n += ngot
return n, err
return n, ssbos, err
}

// WriteDistanceIO creates the bare bones I/O compute program for calculating SDF
// and writes it to the writer.
func (p *Programmer) WriteComputeSDF2(w io.Writer, obj Shader2D) (int, error) {
func (p *Programmer) WriteComputeSDF2(w io.Writer, obj Shader2D) (int, []ShaderBuffer, error) {
baseName, nodes, err := ParseAppendNodes(p.scratchNodes[:0], obj)
if err != nil {
return 0, err
return 0, nil, err
}
// Begin writing shader source code.
n, err := w.Write(p.computeHeader)
if err != nil {
return n, err
return n, nil, err
}
ngot, err := p.writeShaders(w, nodes)
ngot, ssbos, err := p.writeShaders(w, nodes)
n += ngot
if err != nil {
return n, err
return n, ssbos, err
}
ngot, err = fmt.Fprintf(w, `
Expand All @@ -197,43 +198,73 @@ void main() {
`, p.invocX, baseName)

n += ngot
return n, err
return n, ssbos, err
}

// WriteFragVisualizerSDF3 generates a OpenGL program that can be visualized in most shader visualizers such as ShaderToy.
func (p *Programmer) WriteFragVisualizerSDF3(w io.Writer, obj Shader3D) (n int, err error) {
baseName, n, err := p.WriteSDFDecl(w, obj)
func (p *Programmer) WriteFragVisualizerSDF3(w io.Writer, obj Shader3D) (n int, ssbos []ShaderBuffer, err error) {
baseName, n, ssbos, err := p.WriteSDFDecl(w, obj)
if err != nil {
return 0, err
return 0, ssbos, err
}
ngot, err := w.Write([]byte("\nfloat sdf(vec3 p) { return " + baseName + "(p); }\n\n"))
n += ngot
if err != nil {
return n, err
return n, ssbos, err
}
ngot, err = w.Write(visualizerFooter)
n += ngot
if err != nil {
return n, err
return n, ssbos, err
}
return n, nil
return n, ssbos, nil
}

// WriteShaderDecl writes the SDF shader function declarations and returns the top-level SDF function name.
func (p *Programmer) WriteSDFDecl(w io.Writer, obj Shader) (baseName string, n int, err error) {
func (p *Programmer) WriteSDFDecl(w io.Writer, obj Shader) (baseName string, n int, ssbos []ShaderBuffer, err error) {
baseName, nodes, err := ParseAppendNodes(p.scratchNodes[:0], obj)
if err != nil {
return "", 0, err
return "", 0, nil, err
}
n, err = p.writeShaders(w, nodes)
n, ssbos, err = p.writeShaders(w, nodes)
if err != nil {
return "", n, err
return "", n, ssbos, err
}
return baseName, n, nil
return baseName, n, ssbos, nil
}

func (p *Programmer) writeShaders(w io.Writer, nodes []Shader) (n int, err error) {
func (p *Programmer) writeShaders(w io.Writer, nodes []Shader) (n int, ssbos []ShaderBuffer, err error) {
clear(p.names)
p.ssbosScratch = p.ssbosScratch[:0]
currentBase := 2
p.scratch = p.scratch[:0]
for i := len(nodes) - 1; i >= 0; i-- {
node := nodes[i]
prevIdx := len(p.ssbosScratch)
p.ssbosScratch = node.AppendShaderBuffers(p.ssbosScratch)
for _, ssbo := range p.ssbosScratch[prevIdx:] {
nameHash := hash(ssbo.NamePtr, 0)
_, nameConflict := p.names[nameHash]
if nameConflict {
return n, nil, fmt.Errorf("shader buffer object name conflict resolution not implemented: %T has buffer conflicting name %q of type %s", node, ssbo.NamePtr, ssbo.Element.String())
}
p.names[nameHash] = nameHash
p.scratch, err = AppendShaderBufferDecl(p.scratch, "", "", currentBase, ssbo)
currentBase++
if err != nil {
return n, nil, err
}
}
}
if len(p.scratch) > 0 {
// Write shader buffer declarations if any.
ngot, err := w.Write(p.scratch)
n += ngot
if err != nil {
return n, nil, err
}
}

for i := len(nodes) - 1; i >= 0; i-- {
node := nodes[i]
var name, body []byte
Expand All @@ -246,17 +277,18 @@ func (p *Programmer) writeShaders(w io.Writer, nodes []Shader) (n int, err error
if bodyHash == gotBodyHash {
continue // Shader already written and is identical, skip.
}
return n, fmt.Errorf("duplicate %T shader name %q w/ body:\n%s", node, name, body)
return n, nil, fmt.Errorf("duplicate %T shader name %q w/ body:\n%s", node, name, body)
} else {
p.names[nameHash] = bodyHash // Not found, add it.
}
ngot, err := w.Write(p.scratch)
n += ngot
if err != nil {
return n, err
return n, nil, err
}
}
return n, err
ssbos = append(ssbos, p.ssbosScratch...) // Clone slice and return it.
return n, ssbos, err
}

const shorteningBufsize = 1024
Expand Down Expand Up @@ -381,6 +413,54 @@ func WriteShader(w io.Writer, s Shader, scratch []byte) (int, []byte, error) {
return n, scratch, err
}

// AppendShaderBufferDecl appends the ShaderBuffer
//
// layout(<ssbo.std>, binding = <base>) buffer <BlockName> {
// <ssbo.Element> <ssbo.NamePtr>[];
// } <instanceName>;
func AppendShaderBufferDecl(dst []byte, BlockName, instanceName string, base int, ssbo ShaderBuffer) ([]byte, error) {
if len(ssbo.NamePtr) == 0 {
return dst, errors.New("empty ShaderBuffer name")
}
const std = "std140" // Subject to change, would be provided by ShaderBuffer.
var typename string
switch ssbo.Element {
case reflect.TypeOf(float32(0)):
typename = "float"

case reflect.TypeOf(ms2.Vec{}):
typename = "vec2"

case reflect.TypeOf(ms3.Vec{}):
typename = "vec3"

case nil:
return nil, fmt.Errorf("nil shader buffer type for %q", ssbo.NamePtr)
default:
return nil, fmt.Errorf("arbitrary shader buffer type not implemented, got %T from %q", ssbo.Element, ssbo.NamePtr)
}
dst = append(dst, "layout("...)
dst = append(dst, std...)
dst = append(dst, "base="...)
dst = strconv.AppendInt(dst, int64(base), 10)
dst = append(dst, ") buffer"...)
if len(BlockName) > 0 {
dst = append(dst, ' ')
dst = append(dst, BlockName...)
}
dst = append(dst, "{\n\t"...)
dst = append(dst, typename...)
dst = append(dst, ' ')
dst = append(dst, ssbo.NamePtr...)
dst = append(dst, "[];\n}"...)
if len(instanceName) > 0 {
dst = append(dst, ' ')
dst = append(dst, instanceName...)
}
dst = append(dst, ";\n"...)
return dst, nil
}

// AppendShaderSource appends the GL code of a single shader to the dst byte buffer. If dst's
// capacity is grown during the writing the buffer with augmented capacity is returned. If not the same input dst is returned.
// name and body byte slices pointing to the result buffer are also returned for convenience.
Expand Down
8 changes: 6 additions & 2 deletions glbuild/glbuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ func TestShaderNameDeduplication(t *testing.T) {
for _, obj := range []glbuild.Shader3D{s1s1, s1s2} {
programmer := glbuild.NewDefaultProgrammer()
source := new(bytes.Buffer)
n, err := programmer.WriteFragVisualizerSDF3(source, obj)
n, ssbos, err := programmer.WriteFragVisualizerSDF3(source, obj)
if n != source.Len() {
t.Fatal("written length mismatch", err)
} else if len(ssbos) > 0 {
t.Fatal("unexpected ssbo")
}
if err != nil {
t.Error(err)
Expand All @@ -37,9 +39,11 @@ func TestShaderNameDeduplication(t *testing.T) {
t.Errorf("\n%s\nVisualizer: want one declaration, got %d", src, declCount)
}
source.Reset()
n, err = programmer.WriteComputeSDF3(source, obj)
n, ssbos, err = programmer.WriteComputeSDF3(source, obj)
if n != source.Len() {
t.Fatal("written length mismatch", err)
} else if len(ssbos) > 0 {
t.Fatal("unexpected ssbo")
}
if err != nil {
t.Error(err)
Expand Down
4 changes: 3 additions & 1 deletion gleval/gpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,13 @@ func (disp *DisplaceMulti2D) Configure(programmer *glbuild.Programmer, element g
return errZeroInvoc
}
var buf bytes.Buffer
basename, n, err := programmer.WriteSDFDecl(&buf, element)
basename, n, ssbos, err := programmer.WriteSDFDecl(&buf, element)
if err != nil {
return err
} else if n != buf.Len() {
return errors.New("length written mismatch")
} else if len(ssbos) > 0 {
return errors.New("ssbos unsupported")
}
disp.elemBB = element.Bounds()
disp.invocX = cfg.InvocX
Expand Down
5 changes: 4 additions & 1 deletion glrender/glrender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,11 @@ func levelsVisual(filename string, startCube icube, targetLvl int, origin ms3.Ve
if err != nil {
panic(err)
}
_, err = prog.WriteFragVisualizerSDF3(fp, s)
var ssbo []glbuild.ShaderBuffer
_, ssbo, err = prog.WriteFragVisualizerSDF3(fp, s)
if err != nil {
panic(err)
} else if len(ssbo) > 0 {
panic("unexpected ssbo")
}
}
4 changes: 3 additions & 1 deletion glrender/octree.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,9 +414,11 @@ func (oc *Octree) debugVisual(filename string, lvlDescent int, merge glbuild.Sha
if err != nil {
return err
}
_, err = prog.WriteFragVisualizerSDF3(fp, s)
_, ssbos, err := prog.WriteFragVisualizerSDF3(fp, s)
if err != nil {
return err
} else if len(ssbos) > 0 {
return errors.New("ssbos unsupported")
}
return nil
}
Expand Down
15 changes: 12 additions & 3 deletions gsdfaux/gsdfaux.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,12 @@ func RenderShader3D(s glbuild.Shader3D, cfg RenderConfig) (err error) {
return errors.New("zero or negative GPU invocation size")
}
prog.SetComputeInvocations(invoc, 1, 1)
_, err = prog.WriteComputeSDF3(source, s)
var ssbos []glbuild.ShaderBuffer
_, ssbos, err = prog.WriteComputeSDF3(source, s)
if err != nil {
return err
} else if len(ssbos) > 0 {
return errors.New("ssbos unsupported")
}
sdf, err = gleval.NewComputeGPUSDF3(source, bb, gleval.ComputeConfig{InvocX: invoc})
} else {
Expand Down Expand Up @@ -132,9 +135,12 @@ func RenderShader3D(s glbuild.Shader3D, cfg RenderConfig) (err error) {
sz := bb.Size()
visual = gsdf.Translate(visual, center.X, center.Y, center.Z-sz.Z)
visual = gsdf.Scale(visual, sceneSize/bb.Diagonal())
_, err = glbuild.NewDefaultProgrammer().WriteFragVisualizerSDF3(cfg.VisualOutput, visual)
var ssbos []glbuild.ShaderBuffer
_, ssbos, err = glbuild.NewDefaultProgrammer().WriteFragVisualizerSDF3(cfg.VisualOutput, visual)
if err != nil {
return fmt.Errorf("writing visual GLSL: %s", err)
} else if len(ssbos) > 0 {
return errors.New("ssbos unsupported")
}
filename := "GLSL visualization"
if fp, ok := cfg.VisualOutput.(*os.File); ok {
Expand Down Expand Up @@ -226,11 +232,14 @@ func MakeGPUSDF2(s glbuild.Shader2D) (sdf gleval.SDF2, err error) {
prog := glbuild.NewDefaultProgrammer()
prog.SetComputeInvocations(invoc, 1, 1)

n, err = prog.WriteComputeSDF2(&buf, s)
var ssbos []glbuild.ShaderBuffer
n, ssbos, err = prog.WriteComputeSDF2(&buf, s)
if err != nil {
return nil, err
} else if n != buf.Len() {
return nil, fmt.Errorf("wrote %d bytes but WriteComputeSDF2 counted %d", buf.Len(), n)
} else if len(ssbos) > 0 {
return nil, errors.New("ssbos unsupported")
}

return gleval.NewComputeGPUSDF2(&buf, s.Bounds(), gleval.ComputeConfig{InvocX: invoc})
Expand Down

0 comments on commit 1d0e94b

Please sign in to comment.