@@ -13,6 +13,7 @@ import (
13
13
// MarchingCubesOctree renders using marching cubes with octree space sampling.
14
14
type octree struct {
15
15
dc dc3
16
+ mu sync.Mutex
16
17
todo []cube
17
18
unwritten triangle3Buffer
18
19
// concurrent goroutine processing.
@@ -56,6 +57,7 @@ func NewOctreeRenderer(s sdf.SDF3, meshCells int) *octree {
56
57
dc : * newDc3 (s , bb .Min , resolution , levels ),
57
58
unwritten : triangle3Buffer {buf : make ([]Triangle3 , 0 , 1024 )},
58
59
todo : cubes , //[]cube{{sdf.V3i{0, 0, 0}, levels - 1}}, // process the octree, start at the top level
60
+ // concurrent: 2,
59
61
}
60
62
}
61
63
@@ -75,50 +77,100 @@ func (oc *octree) ReadTriangles(dst []Triangle3) (n int, err error) {
75
77
// Done rendering model.
76
78
return n , io .EOF
77
79
}
80
+ // Number of additional triangles proccessed.
78
81
var nt int
79
- if oc .concurrent <= 1 {
80
- nt = oc .readTriangles (dst [n :])
82
+ if oc .concurrent <= 1 || len (oc .todo ) < oc .concurrent || n < oc .concurrent {
83
+ tproc , nc , newCubes := oc .readTriangles (dst [n :], oc .todo )
84
+ oc .todo = append (oc .todo , newCubes ... )
85
+ oc .todo = oc .todo [nc :] // this leaks, luckily this is a short lived function?
86
+ // oc.todo = append(newCubes, oc.todo[cubesProcessed:]...) // Non leaking slow implementation
87
+ nt = tproc
81
88
} else {
82
- // multi core processing
83
- panic ("no concurrency yet" )
89
+ nt = oc .readTrianglesThreaded (dst [n :])
84
90
}
85
91
n += nt
86
92
return n , err
87
93
}
88
94
89
- // readTriangles is single threaded implementation of ReadTriangles and only returns
90
- // number of triangles written.
91
- func (oc * octree ) readTriangles (dst []Triangle3 ) (n int ) {
92
- cubesProcessed := 0
93
- var newCubes []cube
94
- for _ , cube := range oc .todo {
95
+ // readTriangles is single threaded implementation of ReadTriangles.
96
+ // todo is the slice of cubes that shall be proccessed. n is the number of triangles written to dst.
97
+ // Returned cubesProcessed is the number of cubes of todo that were completely processed.
98
+ // Returned newCubes are non-empty cubes that should be processed in future calls to readTriangles.
99
+ // Triangles that were not succesfully written to dst are stored in octree unwritten buffer.
100
+ // This function is safe to call concurrently.
101
+ func (oc * octree ) readTriangles (dst []Triangle3 , todo []cube ) (n , cubesProcessed int , newCubes []cube ) {
102
+ for _ , cube := range todo {
95
103
if n == len (dst ) {
96
104
// Finished writing all the buffer
97
105
break
98
106
}
99
107
if n + marchingCubesMaxTriangles > len (dst ) {
100
108
// Not enough room in buffer to write all triangles that could be found by marching cubes.
101
- tmp := make ([]Triangle3 , 5 )
109
+ tmp := make ([]Triangle3 , marchingCubesMaxTriangles )
102
110
tri , cubes := oc .processCube (tmp , cube )
111
+ oc .mu .Lock ()
103
112
oc .unwritten .Write (tmp [:tri ])
113
+ oc .mu .Unlock ()
104
114
newCubes = append (newCubes , cubes ... )
105
115
cubesProcessed ++
106
116
break
107
117
}
108
118
tri , cubes := oc .processCube (dst [n :], cube )
109
-
110
119
newCubes = append (newCubes , cubes ... )
111
-
112
120
cubesProcessed ++
113
121
n += tri
114
122
}
115
- oc .todo = append (oc .todo , newCubes ... )
116
- oc .todo = oc .todo [cubesProcessed :] // this leaks, luckily this is a short lived function?
117
- // oc.todo = append(newCubes, oc.todo[cubesProcessed:]...) // Non leaking slow implementation
118
- return n
123
+ return n , cubesProcessed , newCubes
124
+ }
125
+
126
+ // readTrianglesThreaded is a multithreaded triangle reader implementation for octree.
127
+ func (oc * octree ) readTrianglesThreaded (dst []Triangle3 ) (nt int ) {
128
+ var wg sync.WaitGroup
129
+ div := len (dst ) / oc .concurrent
130
+ work := make ([][]Triangle3 , oc .concurrent )
131
+ cubeWork := make ([][]cube , oc .concurrent )
132
+ newCubesC := make ([][]cube , oc .concurrent )
133
+ divC := len (oc .todo ) / oc .concurrent
134
+ for i := 0 ; i < oc .concurrent ; i ++ {
135
+ i := i // Escape loop variable.
136
+ wg .Add (1 )
137
+ go func () {
138
+ start := div * i
139
+ work [i ] = dst [start : start + div ]
140
+ cubeWork [i ] = oc .todo [i * divC : (i + 1 )* divC ]
141
+ ntc , nc , newC := oc .readTriangles (work [i ], cubeWork [i ])
142
+ newCubesC [i ] = newC
143
+ work [i ] = work [i ][:ntc ]
144
+ cubeWork [i ] = cubeWork [i ][nc :]
145
+ wg .Done ()
146
+ }()
147
+ }
148
+ wg .Wait ()
149
+ // Consolidate work done.
150
+ offset := 0
151
+ oc .todo = oc .todo [len (oc .todo ):]
152
+ for i := 0 ; i < oc .concurrent ; i ++ {
153
+ // Triangles written.
154
+ start := div * i - offset
155
+ if i != oc .concurrent - 1 && len (work [i ]) != div {
156
+ offset += div - len (work [i ])
157
+ copy (dst [start + len (work [i ]):], dst [start + div :])
158
+ }
159
+ nt += len (work [i ])
160
+ // Cubes unprocessed.
161
+ if len (cubeWork [i ]) != 0 {
162
+ oc .todo = append (oc .todo , cubeWork [i ]... )
163
+ }
164
+ // New Cubes
165
+ if len (newCubesC [i ]) != 0 {
166
+ oc .todo = append (oc .todo , newCubesC [i ]... )
167
+ }
168
+ }
169
+ return nt
119
170
}
120
171
121
172
// Process a cube. Generate triangles, or more cubes.
173
+ // Safe to call concurrently.
122
174
func (oc * octree ) processCube (dst []Triangle3 , c cube ) (writtenTriangles int , newCubes []cube ) {
123
175
if c .n == 1 {
124
176
// this cube is at the required resolution
0 commit comments