Skip to content

Commit

Permalink
add gleval.NewComputeSDF2
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Aug 17, 2024
1 parent c2dc60a commit 4d45dd6
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 49 deletions.
32 changes: 14 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Offshoot from [this project](https://github.com/soypat/sdf/pull/13). Is WIP.
This was converted from the [original example](https://github.com/soypat/sdf/blob/main/examples/npt-flange/flange.go). See [README](https://github.com/soypat/sdf/tree/main/examples) for images.

```go
const (
tlen = 18. / 25.4 // thread length
const (
tlen = 18. / 25.4
internalDiameter = 1.5 / 2.
flangeH = 7. / 25.4
flangeD = 60. / 25.4
Expand All @@ -37,24 +37,20 @@ This was converted from the [original example](https://github.com/soypat/sdf/blo
if err != nil {
return nil, err
}
pipe, err := threads.Nut(threads.NutParms{

pipe, _ := threads.Nut(threads.NutParms{
Thread: npt,
Style: threads.NutCircular,
})
if err != nil {
return nil, err
}
flange, err = gsdf.NewCylinder(flangeD/2, flangeH, flangeH/8)
if err != nil {
return nil, err
}
// Base plate which goes bolted to joint.
flange, _ = gsdf.NewCylinder(flangeD/2, flangeH, flangeH/8)
// Make through-hole in flange bottom.
hole, _ := gsdf.NewCylinder(internalDiameter/2, flangeH, 0)
flange = gsdf.Difference(flange, hole)

// Join threaded section with flange.
flange = gsdf.Translate(flange, 0, 0, -tlen/2)
flange = gsdf.SmoothUnion(pipe, flange, 0.2)
hole, err := gsdf.NewCylinder(internalDiameter/2, 4*flangeH, 0)
if err != nil {
return nil, err
}
flange = gsdf.Difference(flange, hole) // Make through-hole in flange bottom
flange = gsdf.Scale(flange, 25.4) // convert to millimeters
renderSDF(flange)
union := gsdf.SmoothUnion(pipe, flange, 0.2)
union = gsdf.Scale(union, 1)
renderSDF(union)
```
50 changes: 22 additions & 28 deletions examples/npt-flange/flange.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func main() {
os.Exit(1)
}
const resDiv = 200
const evaluationBufferSize = 1024
const evaluationBufferSize = 1024 * 8
resolution := sdf.Bounds().Size().Max() / resDiv
renderer, err := glrender.NewOctreeRenderer(sdf, resolution, evaluationBufferSize)
if err != nil {
Expand All @@ -75,10 +75,6 @@ func main() {

func scene() (gleval.SDF3, error) {
const (
// visualization is the name of the file with a GLSL
// generated visualization of the SDF which can be visualized in https://www.shadertoy.com/
// or using VSCode's ShaderToy extension. If visualization=="" then no file is generated.
// thread length
tlen = 18. / 25.4
internalDiameter = 1.5 / 2.
flangeH = 7. / 25.4
Expand All @@ -94,30 +90,28 @@ func scene() (gleval.SDF3, error) {
return nil, err
}

pipe, err := threads.Nut(threads.NutParms{
Thread: threads.ISO{D: npt.D, P: 1.0 / npt.TPI},
Style: threads.NutHex,
pipe, _ := threads.Nut(threads.NutParms{
Thread: npt,
Style: threads.NutCircular,
})
if err != nil {
return nil, err
}
flange, err = gsdf.NewCylinder(flangeD/2, flangeH, flangeH/8)
if err != nil {
return nil, err
}
// Base plate which goes bolted to joint.
flange, _ = gsdf.NewCylinder(flangeD/2, flangeH, flangeH/8)
// Make through-hole in flange bottom.
hole, _ := gsdf.NewCylinder(internalDiameter/2, 4*flangeH, 0)
flange = gsdf.Difference(flange, hole)

// Join threaded section with flange.
flange = gsdf.Translate(flange, 0, 0, -tlen/2)
flange = gsdf.SmoothUnion(pipe, flange, 0.2)
hole, err := gsdf.NewCylinder(internalDiameter/2, 4*flangeH, 0)
if err != nil {
return nil, err
}
// return makeSDF(hole)
flange = gsdf.Difference(flange, hole) // Make through-hole in flange bottom
flange = gsdf.Scale(flange, 25.4) // convert to millimeters
return makeSDF(flange)
union := gsdf.SmoothUnion(pipe, flange, 0.2)
union = gsdf.Scale(union, 25.4)
return makeSDF(union)
}

func makeSDF(s glbuild.Shader3D) (gleval.SDF3, error) {
err := glbuild.RewriteNames3D(&s, 32) // Shorten names to not crash GL tokenizer.
if err != nil {
return nil, err
}
if visualization != "" {
const sceneSize = 0.5
// We include the bounding box in the visualization.
Expand All @@ -126,13 +120,13 @@ func makeSDF(s glbuild.Shader3D) (gleval.SDF3, error) {
if err != nil {
return nil, err
}
s = gsdf.Union(s, envelope)
visual := gsdf.Union(s, envelope)
// Scale size and translate to center so visualization is in camera range.
center := bb.Center()
s = gsdf.Translate(s, center.X, center.Y, center.Z)
s = gsdf.Scale(s, sceneSize/bb.Size().Max())
visual = gsdf.Translate(visual, center.X, center.Y, center.Z)
visual = gsdf.Scale(visual, sceneSize/bb.Size().Max())
source := new(bytes.Buffer)
_, err = glbuild.NewDefaultProgrammer().WriteFragVisualizerSDF3(source, s)
_, err = glbuild.NewDefaultProgrammer().WriteFragVisualizerSDF3(source, visual)
if err != nil {
return nil, err
}
Expand Down
69 changes: 68 additions & 1 deletion examples/test/glsdf3test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,16 @@ var PremadePrimitives = []glbuild.Shader3D{
Ext: true,
})),
}
var npt threads.NPT
var _ = npt.SetFromNominal(1.0 / 2.0)

var PremadePrimitives2D = []glbuild.Shader2D{
mustShader2D(gsdf.NewCircle(1)),
mustShader2D(gsdf.NewHexagon(1)),
mustShader2D(gsdf.NewPolygon([]ms2.Vec{
{X: -1, Y: -1}, {X: -1, Y: 0}, {X: 0.5, Y: 2},
})),
mustShader2D(npt.Thread()),
// mustShader2D(gsdf.NewEllipse(1, 2)), // Ellipse seems to be very sensitive to position.
}
var BinaryOps = []func(a, b glbuild.Shader3D) glbuild.Shader3D{
Expand Down Expand Up @@ -126,6 +129,7 @@ func test_sdf_gpu_cpu() error {
scratchDistGPU := make([]float32, maxBuf)
scratchDist := make([]float32, maxBuf)
scratchPos := make([]ms3.Vec, maxBuf)
scratchPos2 := make([]ms2.Vec, maxBuf)
for _, primitive := range PremadePrimitives {
log.Printf("begin evaluating %s\n", getBaseTypename(primitive))
bounds := primitive.Bounds()
Expand Down Expand Up @@ -157,6 +161,37 @@ func test_sdf_gpu_cpu() error {
}
}

for _, primitive := range PremadePrimitives2D {
log.Printf("evaluate 2D %s\n", getBaseTypename(primitive))
bounds := primitive.Bounds()
pos := appendMeshgrid2D(scratchPos2[:0], bounds, nx, ny, nz)
distCPU := scratchDistCPU[:len(pos)]
distGPU := scratchDistGPU[:len(pos)]
sdfcpu, err := gleval.AssertSDF2(primitive)
if err != nil {
return err
}
err = sdfcpu.Evaluate(pos, distCPU, vp)
if err != nil {
return err
}
sdfgpu := makeGPUSDF2(primitive)
err = sdfgpu.Evaluate(pos, distGPU, nil)
if err != nil {
return err
}
err = cmpDist(pos, distCPU, distGPU)
if err != nil {
description := sprintOpPrimitive(nil, primitive)
return fmt.Errorf("%s: %s", description, err)
}
// err = test_bounds(sdfcpu, scratchDist, vp)
// if err != nil {
// description := sprintOpPrimitive(nil, primitive)
// return fmt.Errorf("%s: %s", description, err)
// }
}

for _, op := range BinaryOps {
log.Printf("begin evaluating %s\n", getFnName(op))
p1 := PremadePrimitives[0]
Expand Down Expand Up @@ -536,6 +571,20 @@ func appendMeshgrid(dst []ms3.Vec, bounds ms3.Box, nx, ny, nz int) []ms3.Vec {
return dst
}

func appendMeshgrid2D(dst []ms2.Vec, bounds ms2.Box, nx, ny, nz int) []ms2.Vec {
nxyz := ms2.Vec{X: float32(nx), Y: float32(ny)}
dxyz := ms2.DivElem(bounds.Size(), nxyz)
var xy ms2.Vec
for j := 0; j < nx; j++ {
xy.Y = bounds.Min.Y + dxyz.Y*float32(j)
for i := 0; i < nx; i++ {
xy.X = bounds.Min.X + dxyz.X*float32(i)
dst = append(dst, xy)
}
}
return dst
}

func makeGPUSDF3(s glbuild.Shader3D) gleval.SDF3 {
if s == nil {
panic("nil Shader3D")
Expand All @@ -554,6 +603,24 @@ func makeGPUSDF3(s glbuild.Shader3D) gleval.SDF3 {
return sdfgpu
}

func makeGPUSDF2(s glbuild.Shader2D) gleval.SDF2 {
if s == nil {
panic("nil Shader3D")
}
var source bytes.Buffer
n, err := programmer.WriteComputeSDF2(&source, s)
if err != nil {
panic(err)
} else if n != source.Len() {
panic("bytes written mismatch")
}
sdfgpu, err := gleval.NewComputeGPUSDF2(&source, s.Bounds())
if err != nil {
panic(err)
}
return sdfgpu
}

func mustShader(s glbuild.Shader3D, err error) glbuild.Shader3D {
if err != nil || s == nil {
panic(err.Error())
Expand All @@ -568,7 +635,7 @@ func mustShader2D(s glbuild.Shader2D, err error) glbuild.Shader2D {
return s
}

func cmpDist(pos []ms3.Vec, dcpu, dgpu []float32) error {
func cmpDist[T any](pos []T, dcpu, dgpu []float32) error {
mismatches := 0
const tol = 5e-3
var mismatchErr error
Expand Down
41 changes: 40 additions & 1 deletion glbuild/glbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func NewDefaultProgrammer() *Programmer {

// 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 Shader) (int, error) {
func (p *Programmer) WriteComputeSDF3(w io.Writer, obj Shader3D) (int, error) {
baseName, nodes, err := ParseAppendNodes(p.scratchNodes[:0], obj)
if err != nil {
return 0, err
Expand Down Expand Up @@ -117,6 +117,45 @@ void main() {
return n, 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) {
baseName, nodes, err := ParseAppendNodes(p.scratchNodes[:0], obj)
if err != nil {
return 0, err
}
// Begin writing shader source code.
n, err := w.Write(p.computeHeader)
if err != nil {
return n, err
}
ngot, err := p.writeShaders(w, nodes)
n += ngot
if err != nil {
return n, err
}
ngot, err = fmt.Fprintf(w, `
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(rg32f, binding = 0) uniform image2D in_tex;
// The binding argument refers to the textures Unit.
layout(r32f, binding = 1) uniform image2D out_tex;
void main() {
// get position to read/write data from.
ivec2 pos = ivec2( gl_GlobalInvocationID.xy );
// Get SDF position value.
vec2 p = imageLoad( in_tex, pos ).rg;
float distance = %s(p);
// store new value in image
imageStore( out_tex, pos, vec4( distance, 0.0, 0.0, 0.0 ) );
}
`, baseName)

n += ngot
return n, 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, nodes, err := ParseAppendNodes(p.scratchNodes[:0], obj)
Expand Down
76 changes: 75 additions & 1 deletion gleval/gpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
"io"

"github.com/go-gl/gl/all-core/gl"
"github.com/soypat/glgl/math/ms2"
"github.com/soypat/glgl/math/ms3"
"github.com/soypat/glgl/v4.6-core/glgl"
)

// NewComputeGPUSDF3 instantiates a SDF3 that runs on the GPU.
// NewComputeGPUSDF3 instantiates a [SDF3] that runs on the GPU.
func NewComputeGPUSDF3(glglSourceCode io.Reader, bb ms3.Box) (*SDF3Compute, error) {
combinedSource, err := glgl.ParseCombined(glglSourceCode)
if err != nil {
Expand Down Expand Up @@ -83,3 +84,76 @@ func (sdf *SDF3Compute) Evaluate(pos []ms3.Vec, dist []float32, userData any) er
}
return nil
}

// NewComputeGPUSDF2 instantiates a [SDF2] that runs on the GPU.
func NewComputeGPUSDF2(glglSourceCode io.Reader, bb ms2.Box) (*SDF2Compute, error) {
combinedSource, err := glgl.ParseCombined(glglSourceCode)
if err != nil {
return nil, err
}
glprog, err := glgl.CompileProgram(combinedSource)
if err != nil {
return nil, errors.New(string(combinedSource.Compute) + "\n" + err.Error())
}
sdf := SDF2Compute{
prog: glprog,
bb: bb,
}
return &sdf, nil
}

type SDF2Compute struct {
prog glgl.Program
bb ms2.Box
}

func (sdf *SDF2Compute) Bounds() ms2.Box {
return sdf.bb
}

func (sdf *SDF2Compute) Evaluate(pos []ms2.Vec, dist []float32, userData any) error {
sdf.prog.Bind()
defer sdf.prog.Unbind()
posCfg := glgl.TextureImgConfig{
Type: glgl.Texture2D,
Width: len(pos),
Height: 1,
Access: glgl.ReadOnly,
Format: gl.RG,
MinFilter: gl.NEAREST,
MagFilter: gl.NEAREST,
Xtype: gl.FLOAT,
InternalFormat: gl.RG32F,
ImageUnit: 0,
}
_, err := glgl.NewTextureFromImage(posCfg, pos)
if err != nil {
return err
}
distCfg := glgl.TextureImgConfig{
Type: glgl.Texture2D,
Width: len(dist),
Height: 1,
Access: glgl.WriteOnly,
Format: gl.RED,
MinFilter: gl.NEAREST,
MagFilter: gl.NEAREST,
Xtype: gl.FLOAT,
InternalFormat: gl.R32F,
ImageUnit: 1,
}

distTex, err := glgl.NewTextureFromImage(distCfg, dist)
if err != nil {
return err
}
err = sdf.prog.RunCompute(len(dist), 1, 1)
if err != nil {
return err
}
err = glgl.GetImage(dist, distTex, distCfg)
if err != nil {
return err
}
return nil
}

0 comments on commit 4d45dd6

Please sign in to comment.