diff --git a/render/internal_test.go b/render/internal_test.go index 11808b4..69f1fd0 100644 --- a/render/internal_test.go +++ b/render/internal_test.go @@ -4,7 +4,7 @@ import ( "bytes" "errors" "io" - "os" + "runtime" "testing" "github.com/soypat/sdf/form3/must3" @@ -76,20 +76,44 @@ func TestSTLWriteReadback(t *testing.T) { } func TestOctreeMultithread(t *testing.T) { - oct := NewOctreeRenderer(must3.Sphere(1), 8) + oct := NewOctreeRenderer(must3.Sphere(20), 100) oct.concurrent = 2 - buf := make([]Triangle3, oct.concurrent*10) + buf := make([]Triangle3, oct.concurrent*100) var err error var nt int var model []Triangle3 for err == nil { nt, err = oct.ReadTriangles(buf) - model = append(model, buf[nt:]...) + model = append(model, buf[:nt]...) } if err != io.EOF { t.Fatal(err) } - fp, _ := os.Create("mt.stl") - defer fp.Close() - WriteSTL(fp, model) + if len(model) != oct.triangles { + t.Errorf("triangles lost. got %d. octree read %d", len(model), oct.triangles) + } + if oct.cubes != oct.cubesP { + t.Errorf("number of non empty cubes found %d must match number of cubes processed %d", oct.cubes, oct.cubesP) + } + t.Log(oct.triangles) + +} + +func BenchmarkBoltThreaded(b *testing.B) { + const output = "threaded_bolt.stl" + npt := thread.NPT{} + npt.SetFromNominal(1.0 / 2.0) + object, _ := thread.Bolt(thread.BoltParms{ + Thread: npt, // M16x2 + Style: thread.NutHex, + Tolerance: 0.1, + TotalLength: 20, + ShankLength: 10, + }) + + for i := 0; i < b.N; i++ { + oct := NewOctreeRenderer(object, 300) + oct.concurrent = runtime.NumCPU() + CreateSTL(output, oct) + } } diff --git a/render/octree_renderer.go b/render/octree_renderer.go index 8eb5454..c136c17 100644 --- a/render/octree_renderer.go +++ b/render/octree_renderer.go @@ -18,6 +18,12 @@ type octree struct { unwritten triangle3Buffer // concurrent goroutine processing. concurrent int + // Number of triangles generated. + triangles int + // number of non empty cubes found. + cubes int + // number of cubes processed. + cubesP int } type cube struct { @@ -56,8 +62,8 @@ func NewOctreeRenderer(s sdf.SDF3, meshCells int) *octree { return &octree{ dc: *newDc3(s, bb.Min, resolution, levels), unwritten: triangle3Buffer{buf: make([]Triangle3, 0, 1024)}, - todo: cubes, //[]cube{{sdf.V3i{0, 0, 0}, levels - 1}}, // process the octree, start at the top level - // concurrent: 2, + todo: cubes, + cubes: 1, } } @@ -79,7 +85,7 @@ func (oc *octree) ReadTriangles(dst []Triangle3) (n int, err error) { } // Number of additional triangles proccessed. var nt int - if oc.concurrent <= 1 || len(oc.todo) < oc.concurrent || n < oc.concurrent { + if oc.concurrent < 1 || len(oc.todo) < oc.concurrent || len(dst) < oc.concurrent { tproc, nc, newCubes := oc.readTriangles(dst[n:], oc.todo) oc.todo = append(oc.todo, newCubes...) oc.todo = oc.todo[nc:] // this leaks, luckily this is a short lived function? @@ -124,6 +130,7 @@ func (oc *octree) readTriangles(dst []Triangle3, todo []cube) (n, cubesProcessed } // readTrianglesThreaded is a multithreaded triangle reader implementation for octree. +// It writes nt triangles into dst. func (oc *octree) readTrianglesThreaded(dst []Triangle3) (nt int) { var wg sync.WaitGroup div := len(dst) / oc.concurrent @@ -136,8 +143,13 @@ func (oc *octree) readTrianglesThreaded(dst []Triangle3) (nt int) { wg.Add(1) go func() { start := div * i - work[i] = dst[start : start+div] - cubeWork[i] = oc.todo[i*divC : (i+1)*divC] + if i == oc.concurrent-1 { + work[i] = dst[start:] + cubeWork[i] = oc.todo[i*divC:] + } else { + work[i] = dst[start : start+div] + cubeWork[i] = oc.todo[i*divC : (i+1)*divC] + } ntc, nc, newC := oc.readTriangles(work[i], cubeWork[i]) newCubesC[i] = newC work[i] = work[i][:ntc] @@ -153,8 +165,8 @@ func (oc *octree) readTrianglesThreaded(dst []Triangle3) (nt int) { // Triangles written. start := div*i - offset if i != oc.concurrent-1 && len(work[i]) != div { - offset += div - len(work[i]) copy(dst[start+len(work[i]):], dst[start+div:]) + offset += div - len(work[i]) } nt += len(work[i]) // Cubes unprocessed. @@ -207,6 +219,11 @@ func (oc *octree) processCube(dst []Triangle3, c cube) (writtenTriangles int, ne } } } + oc.mu.Lock() + oc.triangles += writtenTriangles + oc.cubes += len(newCubes) + oc.cubesP++ + oc.mu.Unlock() return writtenTriangles, newCubes } diff --git a/render/stl.go b/render/stl.go index 50f90d9..da4eefb 100644 --- a/render/stl.go +++ b/render/stl.go @@ -76,7 +76,7 @@ func (h stlHeader) put(b []byte) { binary.LittleEndian.PutUint32(b[80:], h.Count) } -const trianglesInBuffer = 1 << 10 +const trianglesInBuffer = 1 << 14 type stlReader struct { r Renderer