You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Voxels.RenderImplicit samples the signed-distance callback serially, one voxel at a time, so voxelizing an expensive implicit pins a single core while the rest of the machine sits idle. For cheap SDFs (a sphere, a gear cross-section) this is invisible, but for a costly field it dominates wall-clock time. It would be a big, broadly-useful win if implicit rendering used OpenVDB/TBB parallel iteration the way the lattice and mesh paths effectively do.
Where it comes from
It's not the C#↔native marshaling — it's the native loop itself. In PicoGKRuntime/Source/PicoGKVdbVoxels.h:
voidRenderImplicit(const BBox3& oBBox, PKPFnfSdf pfn)
{
auto oAccess = m_roGrid->getAccessor();
for (int32_t x = ...; ...; x++)
for (int32_t y = ...; ...; y++)
for (int32_t z = ...; ...; z++)
{
Vector3 vecSample = oVoxelSize().vecToMM(Coord(x,y,z));
floatfValue = std::min((*pfn)(&vecSample), oAccess.getValue(xyz));
SetSdValue(&oAccess, xyz, fBackgroundMM(), fValue); // serial, single accessor
}
RebuildGrid();
}
A plain triple loop through one accessor, calling the callback per voxel — no tbb / tools::foreach. By contrast RenderLattice builds geometry through OpenVDB's optimized primitives and uses every core.
Measured impact
A swept-tube cooling channel (240-segment helix SDF), bounding box ~Ø140 × 50 mm at 0.50 mm voxels, on a Ryzen 5 1600 (6c/12t):
Path
Wall-clock
Cores
new Voxels(impl, bbox) — RenderImplicit
~57,000 ms
1
Same shape rebuilt as a Lattice of beams — RenderLattice
~500 ms
12
The ~100× gap is purely the parallelism: a round-capped beam chain is the same geometry as the circular tube SDF, so the only thing that changed was which render path did the work. (We adopted the lattice rebuild as a workaround, but it only applies to tube-like shapes — arbitrary implicits have no such escape hatch.)
Proposal
Parallelize RenderImplicit over the sampling region — e.g. partition the bbox into blocks and run them with tbb::parallel_for (or OpenVDB's threaded leaf-node iteration), each thread writing to its own grid/accessor and merging, instead of a single shared getAccessor().
One caveat worth documenting either way: the user-supplied fSignedDistance would then be called concurrently, so it must be thread-safe / re-entrant. Most SDFs already are (pure functions of the sample point), but a note in the docs would help anyone caching mutable state in their implicit.
Happy to share a minimal repro (the expensive SDF + the lattice equivalent + the timing harness) if useful. Thanks for PicoGK!
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
Voxels.RenderImplicitsamples the signed-distance callback serially, one voxel at a time, so voxelizing an expensive implicit pins a single core while the rest of the machine sits idle. For cheap SDFs (a sphere, a gear cross-section) this is invisible, but for a costly field it dominates wall-clock time. It would be a big, broadly-useful win if implicit rendering used OpenVDB/TBB parallel iteration the way the lattice and mesh paths effectively do.Where it comes from
It's not the C#↔native marshaling — it's the native loop itself. In
PicoGKRuntime/Source/PicoGKVdbVoxels.h:A plain triple loop through one accessor, calling the callback per voxel — no
tbb/tools::foreach. By contrastRenderLatticebuilds geometry through OpenVDB's optimized primitives and uses every core.Measured impact
A swept-tube cooling channel (240-segment helix SDF), bounding box ~Ø140 × 50 mm at 0.50 mm voxels, on a Ryzen 5 1600 (6c/12t):
new Voxels(impl, bbox)—RenderImplicitLatticeof beams —RenderLatticeThe ~100× gap is purely the parallelism: a round-capped beam chain is the same geometry as the circular tube SDF, so the only thing that changed was which render path did the work. (We adopted the lattice rebuild as a workaround, but it only applies to tube-like shapes — arbitrary implicits have no such escape hatch.)
Proposal
Parallelize
RenderImplicitover the sampling region — e.g. partition the bbox into blocks and run them withtbb::parallel_for(or OpenVDB's threaded leaf-node iteration), each thread writing to its own grid/accessor and merging, instead of a single sharedgetAccessor().One caveat worth documenting either way: the user-supplied
fSignedDistancewould then be called concurrently, so it must be thread-safe / re-entrant. Most SDFs already are (pure functions of the sample point), but a note in the docs would help anyone caching mutable state in their implicit.Happy to share a minimal repro (the expensive SDF + the lattice equivalent + the timing harness) if useful. Thanks for PicoGK!
All reactions