Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exploration of even lazier unions (bubbling through modules & transforms) for large speed ups #3637

Draft
wants to merge 20 commits into
base: master
Choose a base branch
from

Conversation

ochafik
Copy link
Contributor

@ochafik ochafik commented Jan 29, 2021

Caveat: hackish code not meant to be merged as is, more of a conversation starter. It’s functional tho, so feel free to patch it!

I’m a big fan of lazy unions (@kintel @thehans 👏)! However, some nodes (e.g. translate, color, module calls) still force the union of their children, which means lots of lost opportunities to avoid unions (I love the idea of letting them bubble up to the root of the tree and let others do the work, e.g. slicer).

So… here are some proposals to take lazy-unions to the next level:

  • --enable=lazy-module lets modules’ outputs flow through lazily (this should probably just be fenced behind lazy-union, methinks)

    module A() { sphere(1); cube(1, center=true); }
    A(); // Will be a lazy union too now!
    
  • --enable=push-transforms-down-unions pushes transforms down on their children (they will become lazy unions of transformed children when --enable=lazy-union, otherwise just a group / normal union). Useful in non-lazy cases thanks to the next feature...

    // Before: real union
    translate([1, 0, 0]) {
      sphere(1);
      cube(1, center=true);
    }
    →
    // After: lazy union!
    translate([1, 0, 0]) sphere(1);
    translate([1, 0, 0]) cube(1, center=true);
    
  • --enable=flatten-children flattens nested ListNode, GroupNode and (compatible) CsgOpNode’s children lists. It helps boost the arity of top-level unions/intersections (which that other PR and follow ups can crunch for breakfast), and handles cases of inlining modules lazy output (also, much easier to parallelize).

    union() { union() { A(); B() } C(); } → union() { A(); B(); C(); }
    

These three features skip nodes w/ special tags (!, %, etc).

Lots of failed tests, because trees are modified a lot and I’ve added a bunch of logs.

Context: I didn’t do this just to extend the reach of lazy unions, although this does help lots of models I tested: I was trying to parallelize rendering (didn’t realize others were busy at it in #2399 & #3193 🤦‍♂️ ) and I figured parallelism could be easier to implement with very flat trees (also ofc, with less unions to compute overall).

Ideally I’d like to try and turn much of the tree into disjunctive normal forms (union of intersections, probably leaving cachable nodes untouched), transform differences to more parallelizable (commutative) intersections (A - B - C -… → A ⋂ ¬B ⋂ ¬C ⋂…, “just” need to wire CGAL’s Nef complement operation for that?) and focus parallelization efforts on high-arity unions and intersections, which this PR provides in large amounts (I might clean up my low-footprint concurrency approach to discuss it in yet another PR, which will also make use of my union optimization in #3636)

I’d love to get some feedback on this approach, happy to send separate PRs with some cleaner visiter/transformer pattern and lots of tests if that's a path that sounds sensible.

…ildren lists

Code is a bit horrible for now, but this works great with lazy-module
…ns or not)

This could have two adverse effects:
- Create more work down the line, e.g. transforming two heavy solids which intersection results in a very small one (that would have been cheaper to intersect). The benefits of potentially saving unions can be huge, though, esp. for complex patterns.
- It could potentially defeat some of the caching logic? (TBC)
Better reflects what it does (not: down intersections or other nodes, but might be worth doing that in the future for normal form?)
Copied goldens:

for f in tests/regression/lazyunion-* ; do mkdir ${f/lazy/lazier} ; done

for f in tests/output/lazierunion-cgalpng/lazierunion-* ; do cp $f `echo ${f/actual/expected} | sed 's/output/regression/'` ; done
@thehans
Copy link
Member

thehans commented Jan 30, 2021

... transform differences to more parallelizable (commutative) intersections (A - B - C -… → A ⋂ ¬B ⋂ ¬C ⋂…

Another option would be to do a fast-union over all the negative objects, then just one big difference at the end:
A - B - C - ... = A - (B + C + ...)
Maybe not as optimal as your fully commutative solution though.

@ochafik
Copy link
Contributor Author

ochafik commented Jan 30, 2021

Another option would be to do a fast-union over all the negative objects, then just one big difference at the end:
A - B - C - ... = A - (B + C + ...) Maybe not as optimal as your fully commutative solution though.

Hah! I explored that AST rewrite before fast-union (in that branch), would be the next easy win for fast-union I think.

Maybe only unioning the fast-unionable subtracted operands? I worry about the cost of unioning highly intersecting bodies vs. cheaper incremental differences.

@lf94
Copy link

lf94 commented May 16, 2021

@ochafik please for the love of programmable CSG modeling, come back and complete this ultra useful work 😆

@ochafik
Copy link
Contributor Author

ochafik commented Feb 3, 2022

@lf94 thanks for your enthusiasm! Despite a very large hiatus in my OSS involvement, I haven't given up on this one, hope to get back to it after #3641 goes through (figured faster unions are more useful than lazier unions in more models, or at least in my models :-D).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants