diff --git a/examples/text/text.go b/examples/text/text.go new file mode 100644 index 0000000..16293cf --- /dev/null +++ b/examples/text/text.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "log" + "runtime" + + "github.com/soypat/glgl/math/ms2" + "github.com/soypat/gsdf" + "github.com/soypat/gsdf/forge/textsdf" + "github.com/soypat/gsdf/glbuild" + "github.com/soypat/gsdf/gleval" + "github.com/soypat/gsdf/gsdfaux" +) + +const dim = 20 +const filename = "text.png" + +func init() { + runtime.LockOSThread() +} + +func scene(bld *gsdf.Builder) (glbuild.Shader2D, error) { + var f textsdf.Font + f.Configure(textsdf.FontConfig{ + RelativeGlyphTolerance: 0.001, + }) + err := f.LoadTTFBytes(textsdf.ISO3098TTF()) + if err != nil { + return nil, err + } + var poly ms2.PolygonBuilder + poly.Nagon(7, 1) + vecs, _ := poly.AppendVecs(nil) + return bld.NewPolygon(vecs), bld.Err() + return f.TextLine("Abc") +} + +func main() { + useGPU := true + if useGPU { + term, err := gleval.Init1x1GLFW() + if err != nil { + log.Fatal(err) + } + defer term() + } + var bld gsdf.Builder + s, err := scene(&bld) + if err != nil { + log.Fatal(err) + } + var sdf2 gleval.SDF2 + if useGPU { + sdf2, err = gsdfaux.MakeGPUSDF2(s) + } else { + sdf2, err = gleval.NewCPUSDF2(s) + } + if err != nil { + log.Fatal(err) + } + err = gsdfaux.RenderPNGFile(filename, sdf2, 300, nil) + if err != nil { + log.Fatal(err) + } + fmt.Println("PNG file rendered to", filename) +} diff --git a/examples/ui-geb/uigeb.go b/examples/ui-geb/uigeb.go index f09a69a..cb82adb 100644 --- a/examples/ui-geb/uigeb.go +++ b/examples/ui-geb/uigeb.go @@ -46,6 +46,10 @@ func scene(bld *gsdf.Builder) (glbuild.Shader3D, error) { G = bld.Translate2D(G, -szG.X/2, -szG.Y/2) E = bld.Translate2D(E, -szE.X/2, -szE.Y/2) B = bld.Translate2D(B, -szB.X/2, -szB.Y/2) + const round1 = 0.01 + G = bld.Offset2D(G, -round1) + E = bld.Offset2D(E, -round1) + B = bld.Offset2D(B, -round1) // GEB size. Scale all letters to match size. szz := ms2.MaxElem(szG, ms2.MaxElem(szE, szB)).Max() @@ -64,16 +68,22 @@ func scene(bld *gsdf.Builder) (glbuild.Shader3D, error) { G3 = bld.Transform(G3, ms3.ScaleMat4(ms3.Vec{X: sclG.X, Y: sclG.Y, Z: 1})) E3 = bld.Transform(E3, ms3.ScaleMat4(ms3.Vec{X: sclE.X, Y: sclE.Y, Z: 1})) B3 = bld.Transform(B3, ms3.ScaleMat4(ms3.Vec{X: sclB.X, Y: sclB.Y, Z: 1})) + const round2 = 0.025 + G3 = bld.Offset(G3, -round2) + E3 = bld.Offset(E3, -round2) + B3 = bld.Offset(B3, -round2) // Orient letters. const deg90 = math.Pi / 2 - E3 = bld.Rotate(E3, deg90, ms3.Vec{Y: 1}) - B3 = bld.Rotate(B3, -deg90, ms3.Vec{X: 1}) + GEB1 := bld.Intersection(G3, bld.Rotate(E3, deg90, ms3.Vec{Y: 1})) + GEB1 = bld.Intersection(GEB1, bld.Rotate(B3, -deg90, ms3.Vec{X: 1})) - GEB := bld.Intersection(G3, E3) - GEB = bld.Intersection(GEB, B3) - // return bld.Union(G3, E3, B3), bld.Err() // For debugging. - return GEB, bld.Err() + GEB2 := bld.Intersection(E3, bld.Rotate(G3, deg90, ms3.Vec{Y: 1})) + GEB2 = bld.Intersection(GEB2, bld.Rotate(B3, -deg90, ms3.Vec{X: 1})) + + GEB2 = bld.Translate(GEB2, 0, GEB2.Bounds().Size().Y*1.5, 0) + + return bld.Union(GEB1, GEB2), bld.Err() } func main() { diff --git a/glbuild/glbuild.go b/glbuild/glbuild.go index cd0e1a0..d255461 100644 --- a/glbuild/glbuild.go +++ b/glbuild/glbuild.go @@ -277,7 +277,7 @@ func (p *Programmer) writeShaders(w io.Writer, nodes []Shader) (n int, objs []Sh newObjects := p.objsScratch[prevIdx:] for i := range newObjects { if newObjects[i].Binding != -1 { - return n, nil, fmt.Errorf("shader buffer object binding should be set to -1 until shader generated for %T, %q", node, newObjects[i].NamePtr) + return n, nil, fmt.Errorf("shader buffer object binding should be set to -1 until shader generated for %T, %q", unwraproot(node), newObjects[i].NamePtr) } newObjects[i].Binding = currentBase currentBase++ @@ -285,7 +285,7 @@ func (p *Programmer) writeShaders(w io.Writer, nodes []Shader) (n int, objs []Sh nameHash := hash(obj.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, obj.NamePtr, obj.Element.String()) + return n, nil, fmt.Errorf("shader buffer object name conflict resolution not implemented: %T has buffer conflicting name %q of type %s", unwraproot(node), obj.NamePtr, obj.Element.String()) } p.names[nameHash] = nameHash blockName := unsafe.String(&obj.NamePtr[0], len(obj.NamePtr)) + "Buffer" @@ -316,7 +316,17 @@ func (p *Programmer) writeShaders(w io.Writer, nodes []Shader) (n int, objs []Sh if bodyHash == gotBodyHash { continue // Shader already written and is identical, skip. } - return n, nil, fmt.Errorf("duplicate %T shader name %q w/ body:\n%s", node, name, body) + // Look for identical shader + var conflictBody []byte + for j := i + 1; j < len(nodes); j++ { + conflictBody = nodes[j].AppendShaderName(conflictBody[:0]) + if bytes.Equal(conflictBody, name) { + conflictBody = nodes[j].AppendShaderBody(conflictBody[:0]) + break + } + conflictBody = conflictBody[:0] + } + return n, nil, fmt.Errorf("duplicate %T shader name %q w/ body:\n%s\n\nconflict with distinct shader with same name:\n%s", unwraproot(node), name, body, conflictBody) } else { p.names[nameHash] = bodyHash // Not found, add it. } @@ -368,6 +378,9 @@ func ShortenNames2D(root *Shader2D, maxRewriteLen int) error { func rewriteName3(s3 *Shader3D, scratch []byte, rewritelen int) []byte { sd3 := *s3 + if _, ok := sd3.(*nameOverloadShader3D); ok { + return scratch // Already overloaded. + } name, scratch := makeShortname(sd3, scratch, rewritelen) if name == nil { return scratch @@ -378,6 +391,9 @@ func rewriteName3(s3 *Shader3D, scratch []byte, rewritelen int) []byte { func rewriteName2(s2 *Shader2D, scratch []byte, rewritelen int) []byte { sd2 := *s2 + if _, ok := sd2.(*nameOverloadShader2D); ok { + return scratch // Already overloaded. + } name, scratch := makeShortname(sd2, scratch, rewritelen) if name == nil { return scratch @@ -960,6 +976,7 @@ func (ob3 *overloadBounds3) Evaluate(pos []ms3.Vec, dist []float32, userData any } return sdf.Evaluate(pos, dist, userData) } +func (ob3 *overloadBounds3) unwrap() Shader { return ob3.Shader3D } // OverloadShader2DBounds overloads a [Shader2D] Bounds method with the argument bounding box. func OverloadShader2DBounds(s Shader2D, bb ms2.Box) Shader2D { @@ -985,6 +1002,8 @@ func (ob3 *overloadBounds2) Evaluate(pos []ms2.Vec, dist []float32, userData any return sdf.Evaluate(pos, dist, userData) } +func (ob2 *overloadBounds2) unwrap() Shader { return ob2.Shader2D } + var _ Shader3D = (*CachedShader3D)(nil) // Interface implementation compile-time check. // CachedShader3D implements the Shader3D interface with results it caches for another Shader3D on a call to RefreshCache. @@ -1045,6 +1064,8 @@ func (c3 *CachedShader3D) Evaluate(pos []ms3.Vec, dist []float32, userData any) return sdf.Evaluate(pos, dist, userData) } +func (c3 *CachedShader3D) unwrap() Shader { return c3.Shader } + var _ Shader2D = (*CachedShader2D)(nil) // Interface implementation compile-time check. // CachedShader2D implements the Shader2D interface with results it caches for another Shader2D on a call to RefreshCache. @@ -1095,6 +1116,8 @@ func (c2 *CachedShader2D) AppendShaderObjects(objs []ShaderObject) []ShaderObjec return c2.Shader.AppendShaderObjects(objs) } +func (c2 *CachedShader2D) unwrap() Shader { return c2.Shader } + type nameOverloadShader3D struct { Shader Shader3D name []byte @@ -1150,6 +1173,8 @@ func (nos3 *nameOverloadShader3D) AppendShaderName(b []byte) []byte { return append(b, nos3.name...) } +func (nos3 *nameOverloadShader3D) unwrap() Shader { return nos3.Shader } + type nameOverloadShader2D struct { Shader Shader2D name []byte @@ -1182,6 +1207,8 @@ func (nos2 *nameOverloadShader2D) AppendShaderObjects(objs []ShaderObject) []Sha return nos2.Shader.AppendShaderObjects(objs) } +func (nos2 *nameOverloadShader2D) unwrap() Shader { return nos2.Shader } + func hash(b []byte, in uint64) uint64 { // Leaving md5 here since we may need to revert to // a more entropic hash to avoid collisions... @@ -1200,9 +1227,33 @@ func hash(b []byte, in uint64) uint64 { x = (x ^ (x >> 27)) * 0x94d049bb133111eb x ^= x >> 31 b = b[8:] + } - for i := range b { - x ^= uint64(b[i]) << i * 8 + if len(b) > 0 { + var buf [8]byte + copy(buf[:], b) + x ^= binary.LittleEndian.Uint64(buf[:]) + x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9 + x = (x ^ (x >> 27)) * 0x94d049bb133111eb + x ^= x >> 31 } return x } + +func unwraproot(s Shader) Shader { + i := 0 + var sbase Shader + for s != nil && i < 6 { + sbase = s + s = unwrap(s) + i++ + } + return sbase +} + +func unwrap(s Shader) Shader { + if unwrapper, ok := s.(interface{ unwrap() Shader }); ok { + return unwrapper.unwrap() + } + return nil +} diff --git a/gleval/gleval.go b/gleval/gleval.go index 05871a0..7c02b87 100644 --- a/gleval/gleval.go +++ b/gleval/gleval.go @@ -111,9 +111,14 @@ type BlockCachedSDF3 struct { evals uint64 } +func (c3 *BlockCachedSDF3) VecPool() *VecPool { + vp, _ := GetVecPool(c3.sdf) + return vp +} + // Reset resets the SDF3 and reuses the underlying buffers for future SDF evaluations. It also resets statistics such as evaluations and cache hits. -func (c3 *BlockCachedSDF3) Reset(sdf SDF3, res ms3.Vec) error { - if res.X <= 0 || res.Y <= 0 || res.Z <= 0 { +func (c3 *BlockCachedSDF3) Reset(sdf SDF3, resX, resY, resZ float32) error { + if resX <= 0 || resY <= 0 || resZ <= 0 { return errors.New("invalid resolution for BlockCachedSDF3") } if c3.m == nil { @@ -125,7 +130,7 @@ func (c3 *BlockCachedSDF3) Reset(sdf SDF3, res ms3.Vec) error { // Ncells := ms3.DivElem(bb.Size(), res) *c3 = BlockCachedSDF3{ sdf: sdf, - mul: ms3.DivElem(ms3.Vec{X: 1, Y: 1, Z: 1}, res), + mul: ms3.DivElem(ms3.Vec{X: 1, Y: 1, Z: 1}, ms3.Vec{X: resX, Y: resY, Z: resZ}), m: c3.m, posbuf: c3.posbuf[:0], distbuf: c3.distbuf[:0], diff --git a/gleval/gpu_cgo.go b/gleval/gpu_cgo.go index 4f31c50..9283fd9 100644 --- a/gleval/gpu_cgo.go +++ b/gleval/gpu_cgo.go @@ -187,6 +187,7 @@ func computeEvaluate[T ms2.Vec | ms3.Vec](pos []T, dist []float32, invocX int, o } err := glgl.Err() if err != nil { + p.Unpin() return fmt.Errorf("binding SSBOs: %w", err) } } diff --git a/glrender/glrender_test.go b/glrender/glrender_test.go index 9777156..4f12626 100644 --- a/glrender/glrender_test.go +++ b/glrender/glrender_test.go @@ -226,7 +226,7 @@ func levelsVisual(filename string, startCube icube, targetLvl int, origin ms3.Ve s = bld.Union(s, bb) } s = bld.Scale(s, 0.5/s.Bounds().Size().Max()) - glbuild.ShortenNames3D(&s, 8) + glbuild.ShortenNames3D(&s, 12) prog := glbuild.NewDefaultProgrammer() fp, err := os.Create(filename) if err != nil { diff --git a/glrender/octreerenderer.go b/glrender/octreerenderer.go index 4cfe3b6..4e907ae 100644 --- a/glrender/octreerenderer.go +++ b/glrender/octreerenderer.go @@ -253,7 +253,7 @@ func (oc *Octree) debugVisual(filename string, lvlDescent int, merge glbuild.Sha s = bld.Union(s, bb) } s = bld.Scale(s, 0.5/s.Bounds().Size().Max()) - glbuild.ShortenNames3D(&s, 8) + glbuild.ShortenNames3D(&s, 12) prog := glbuild.NewDefaultProgrammer() fp, err := os.Create(filename) if err != nil { diff --git a/gsdf.go b/gsdf.go index 99508d7..5b88a88 100644 --- a/gsdf.go +++ b/gsdf.go @@ -2,6 +2,7 @@ package gsdf import ( _ "embed" + "encoding/binary" "errors" "fmt" "unsafe" @@ -9,6 +10,7 @@ import ( "github.com/chewxy/math32" "github.com/soypat/glgl/math/ms2" "github.com/soypat/glgl/math/ms3" + "github.com/soypat/gsdf/glbuild" ) const ( @@ -41,18 +43,14 @@ type Builder struct { } func (bld *Builder) useGPU(n int) bool { - return bld.limVecGPU != 0 && n > bld.limVecGPU || n > 1024 + return bld.limVecGPU != 0 && n > bld.limVecGPU || n > 1 } func makeHashName[T any](dst []byte, name string, vec []T) []byte { var z T data := unsafe.Pointer(&vec[0]) - bdata := unsafe.Slice((*byte)(data), len(vec)*int(unsafe.Sizeof(z))) - _ = bdata - // for _, b := range bdata { - - // } - return fmt.Appendf(dst, "%s%x", name, uintptr(data)) + sz := len(vec) * int(unsafe.Sizeof(z)) + return fmt.Appendf(dst, "%s%x_%x", name, uintptr(data), sz) } func (bld *Builder) Flags() Flags { @@ -186,3 +184,29 @@ func hashAdd(a, b, num float32) (aNew, bNew float32) { func hashfint(f float32) float32 { return float32(int(f*1000000)%1000000) / 1000000 // Keep within [0.0, 1.0) } + +func hash(b []byte, in uint64) uint64 { + x := in + for len(b) >= 8 { + x ^= binary.LittleEndian.Uint64(b) + x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9 + x = (x ^ (x >> 27)) * 0x94d049bb133111eb + x ^= x >> 31 + b = b[8:] + + } + if len(b) > 0 { + var buf [8]byte + copy(buf[:], b) + x ^= binary.LittleEndian.Uint64(buf[:]) + x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9 + x = (x ^ (x >> 27)) * 0x94d049bb133111eb + x ^= x >> 31 + } + return x +} + +func hashshaderptr(s glbuild.Shader) uint64 { + v := *(*[2]uintptr)(unsafe.Pointer(&s)) + return (uint64(v[0]) ^ (uint64(v[1]) << 8)) * 0xbf58476d1ce4e5b9 +} diff --git a/gsdf2d.go b/gsdf2d.go index a5c358c..3cf833e 100644 --- a/gsdf2d.go +++ b/gsdf2d.go @@ -555,11 +555,13 @@ func (bld *Builder) NewPolygon(vertices []ms2.Vec) glbuild.Shader2D { } prevIdx = i } - println("poly") - // if bld.useGPU(len(vertices)) { - // return &polyGPU{poly2D: poly2D{vert: vertices}, bufname: makeHashName(nil, "ssboPoly", vertices)} - // } - return &poly2D{vert: vertices} + + poly := poly2D{vert: vertices} + if bld.useGPU(len(vertices)) { + // println("poly") + // return &polyGPU{poly2D: poly, bufname: makeHashName(nil, "ssboPoly", vertices)} + } + return &poly } func (c *poly2D) Bounds() ms2.Box { @@ -583,9 +585,7 @@ func (c *poly2D) AppendShaderName(b []byte) []byte { return b } -func (c *poly2D) AppendShaderBody(b []byte) []byte { - b = glbuild.AppendVec2SliceDecl(b, "v", c.vert) - b = append(b, `const int num = v.length(); +const polyShader = `const int num = v.length(); float d = dot(p-v[0],p-v[0]); float s = 1.0; for( int i=0, j=num-1; ie.y*w.x ); if( all(cond) || all(not(cond)) ) s=-s; } -return s*sqrt(d);`...) +return s*sqrt(d); +` + +func (c *poly2D) AppendShaderBody(b []byte) []byte { + b = glbuild.AppendVec2SliceDecl(b, "v", c.vert) + b = append(b, polyShader...) return b } @@ -619,26 +624,9 @@ type polyGPU struct { } func (c *polyGPU) AppendShaderBody(b []byte) []byte { - b = glbuild.AppendDefineDecl(b, "ver", string(c.bufname)) - b = append(b, `const int num = v.length(); -float d = dot(p-v[0],p-v[0]); -float s = 1.0; -for( int i=0, j=num-1; i=v[i].y, - p.y e.y*w.x ); - if( all(cond) || all(not(cond)) ) s=-s; -} -return s*sqrt(d); -`...) - b = glbuild.AppendUndefineDecl(b, "ver") + b = glbuild.AppendDefineDecl(b, "v", string(c.bufname)) + b = append(b, polyShader...) + b = glbuild.AppendUndefineDecl(b, "v") return b } diff --git a/gsdf_gpu_test.go b/gsdf_gpu_test.go index 867e00b..1d92942 100644 --- a/gsdf_gpu_test.go +++ b/gsdf_gpu_test.go @@ -27,8 +27,6 @@ import ( "github.com/soypat/gsdf/gleval" ) -var bld Builder - var failedObj glbuild.Shader3D type shaderTestConfig struct { @@ -617,7 +615,7 @@ func ui(s glbuild.Shader3D, width, height int) error { defer term() var sdfDecl bytes.Buffer programmer := glbuild.NewDefaultProgrammer() - err = glbuild.ShortenNames3D(&s, 8) + err = glbuild.ShortenNames3D(&s, 12) if err != nil { return err } diff --git a/gsdf_test.go b/gsdf_test.go new file mode 100644 index 0000000..67d8ce4 --- /dev/null +++ b/gsdf_test.go @@ -0,0 +1,57 @@ +package gsdf + +import ( + "bytes" + "math" + "testing" + + "github.com/soypat/glgl/math/ms3" + "github.com/soypat/gsdf/glbuild" +) + +var bld Builder + +func TestTransformDuplicateBug(t *testing.T) { + G := bld.NewCircle(1) + E := bld.NewCircle(1) + B := bld.NewCircle(1) + + L := float32(1.0) + G3 := bld.Extrude(G, L) + E3 := bld.Extrude(E, L) + B3 := bld.Extrude(B, L) + + // Non-uniform scaling to fill letter intersections. + G3 = bld.Transform(G3, ms3.ScaleMat4(ms3.Vec{X: 1.2, Y: 1.3, Z: 1})) + E3 = bld.Transform(E3, ms3.ScaleMat4(ms3.Vec{X: 1.2, Y: 1.3, Z: 1})) + B3 = bld.Transform(B3, ms3.ScaleMat4(ms3.Vec{X: 1.2, Y: 1.3, Z: 1})) + const round2 = 0.025 + G3 = bld.Offset(G3, -round2) + E3 = bld.Offset(E3, -round2) + B3 = bld.Offset(B3, -round2) + + // Orient letters. + const deg90 = math.Pi / 2 + GEB1 := bld.Intersection(G3, bld.Rotate(E3, deg90, ms3.Vec{Y: 1})) + GEB1 = bld.Intersection(GEB1, bld.Rotate(B3, -deg90, ms3.Vec{X: 1})) + + GEB2 := bld.Intersection(E3, bld.Rotate(G3, deg90, ms3.Vec{Y: 1})) + GEB2 = bld.Intersection(GEB2, bld.Rotate(B3, -deg90, ms3.Vec{X: 1})) + + GEB2 = bld.Translate(GEB2, 0, 0, GEB2.Bounds().Size().Z*1.5) + shape := bld.Union(GEB1, GEB2) + + err := glbuild.ShortenNames3D(&shape, 12) + if err != nil { + t.Fatal(err) + } + prog := glbuild.NewDefaultProgrammer() + prog.SetComputeInvocations(32, 1, 1) + var buf bytes.Buffer + n, _, err := prog.WriteComputeSDF3(&buf, shape) + if err != nil { + t.Fatal(err) + } else if n != buf.Len() { + t.Error("mismatched length") + } +} diff --git a/gsdfaux/gsdfaux.go b/gsdfaux/gsdfaux.go index e3e7c64..3844c01 100644 --- a/gsdfaux/gsdfaux.go +++ b/gsdfaux/gsdfaux.go @@ -67,7 +67,7 @@ func RenderShader3D(s glbuild.Shader3D, cfg RenderConfig) (err error) { fmt.Println(args...) } } - err = glbuild.ShortenNames3D(&s, 6) + err = glbuild.ShortenNames3D(&s, 12) if err != nil { return fmt.Errorf("shortening shader names: %s", err) } @@ -129,7 +129,7 @@ func RenderShader3D(s glbuild.Shader3D, cfg RenderConfig) (err error) { if cfg.EnableCaching { var cache gleval.BlockCachedSDF3 cacheRes := cfg.Resolution / 2 - err = cache.Reset(sdf, ms3.Vec{X: cacheRes, Y: cacheRes, Z: cacheRes}) + err = cache.Reset(sdf, cacheRes, cacheRes, cacheRes) if err != nil { return err } @@ -173,9 +173,14 @@ func RenderShader3D(s glbuild.Shader3D, cfg RenderConfig) (err error) { } if cfg.STLOutput != nil { - maybeVP, _ := gleval.GetVecPool(sdf) + var userData any + maybeVP, err := gleval.GetVecPool(sdf) + if err == nil { + userData = maybeVP + } + watch = stopwatch() - triangles, err := glrender.RenderAll(renderer, maybeVP) + triangles, err := glrender.RenderAll(renderer, userData) if err != nil { return fmt.Errorf("rendering triangles: %s", err) } @@ -183,7 +188,7 @@ func RenderShader3D(s glbuild.Shader3D, cfg RenderConfig) (err error) { e := sdf.(interface{ Evaluations() uint64 }) omitted := 8 * renderer.TotalPruned() percentOmit := percentUint64(omitted, e.Evaluations()+omitted) - log("evaluated SDF", e.Evaluations(), "times and rendered", len(triangles), "triangles in", watch(), "with", percentOmit, "percent evaluations omitted") + log("evaluated SDF", e.Evaluations(), "times and rendered", len(triangles), "triangles in", watch().Round(10*time.Millisecond), "with", percentOmit, "percent evaluations omitted") watch = stopwatch() _, err = glrender.WriteBinarySTL(cfg.STLOutput, triangles) diff --git a/gsdfaux/ui.go b/gsdfaux/ui.go index 1d3adb4..9b4554b 100644 --- a/gsdfaux/ui.go +++ b/gsdfaux/ui.go @@ -26,7 +26,7 @@ func ui(s glbuild.Shader3D, cfg UIConfig) error { defer term() var sdfDecl bytes.Buffer programmer := glbuild.NewDefaultProgrammer() - err = glbuild.ShortenNames3D(&s, 8) + err = glbuild.ShortenNames3D(&s, 12) if err != nil { return err } diff --git a/operations.go b/operations.go index c64099a..6897993 100644 --- a/operations.go +++ b/operations.go @@ -2,6 +2,7 @@ package gsdf import ( "fmt" + "strconv" "github.com/chewxy/math32" "github.com/soypat/glgl/math/ms2" @@ -340,7 +341,9 @@ func (bld *Builder) Transform(s glbuild.Shader3D, m ms3.Mat4) glbuild.Shader3D { if math32.Abs(det) < epstol { bld.shapeErrorf("singular Mat4") } - return &transform{s: s, t: m, tInv: m.Inverse()} + t := &transform{s: s, t: m, tInv: m.Inverse()} + t.hash = hashshaderptr(t) // TODO(soypat): fix TestTransformDuplicateBug so that hashing input shader works. + return t } type transform struct { @@ -352,35 +355,37 @@ type transform struct { // The SDF receives points which we must evaluate in // transformed coordinates, so we must work backwards, thus inverse. tInv ms3.Mat4 + hash uint64 } -func (u *transform) Bounds() ms3.Box { - return u.t.MulBox(u.s.Bounds()) +func (t *transform) Bounds() ms3.Box { + return t.t.MulBox(t.s.Bounds()) } -func (s *transform) ForEachChild(userData any, fn func(userData any, s *glbuild.Shader3D) error) error { - return fn(userData, &s.s) +func (t *transform) ForEachChild(userData any, fn func(userData any, s *glbuild.Shader3D) error) error { + return fn(userData, &t.s) } -func (s *transform) AppendShaderName(b []byte) []byte { +func (t *transform) AppendShaderName(b []byte) []byte { b = append(b, "transform"...) + // Hash floats so that name is not too long. - values := s.t.Array() - b = glbuild.AppendFloat(b, 'p', 'n', hashf(values[:])) + values := t.t.Array() + b = glbuild.AppendFloat(b, 'n', 'p', hashf(values[:])) b = append(b, '_') - b = s.s.AppendShaderName(b) + b = strconv.AppendUint(b, t.hash, 32) return b } -func (r *transform) AppendShaderBody(b []byte) []byte { - b = glbuild.AppendMat4Decl(b, "invT", r.tInv) +func (t *transform) AppendShaderBody(b []byte) []byte { + b = glbuild.AppendMat4Decl(b, "invT", t.tInv) b = append(b, "return "...) - b = r.s.AppendShaderName(b) + b = t.s.AppendShaderName(b) b = append(b, "(((invT) * vec4(p,0.0)).xyz);"...) return b } -func (u *transform) AppendShaderObjects(objects []glbuild.ShaderObject) []glbuild.ShaderObject { +func (t *transform) AppendShaderObjects(objects []glbuild.ShaderObject) []glbuild.ShaderObject { return objects }