Optimize disjointed unions & differences (+ introduce caching of costly nef<->polyset conversions)#3636
Optimize disjointed unions & differences (+ introduce caching of costly nef<->polyset conversions)#3636ochafik wants to merge 32 commits intoopenscad:masterfrom
Conversation
…f non-overlapping solids
Crucial now that we're concatenating polysets everywhere!
Difference somehow seems marked green on Nef bodies, which is an issue when we now only have PolySets after unions (when --enable=fast-union, which I set for all tests). Updated expectation + added a union-free test that checks colors
… older boost version linux builds run with (should address build error on ubuntu 18, which has Boost 1.65.1 ; ubuntu 20 has Boost 1.71.0 which has has_value)
…oint union Also allow skipping the (relatively) costly pathological case detection with `-DOPTIMISTIC_FAST_UNION` (e.g. `qmake ... QMAKE_CSSFLAGS+=-DOPTIMISTIC_FAST_UNION ...` to build on mac)
|
Seems like a very good ideal overall. I get substantial speedup for the sample script with And just for fun, I wrote a variation of your script which is more cache-friendly (it makes the inner loop / entire columns of spheres cache-able) union() {
for (i=[0:N-1]) translate([i,0,0])
for(j=[0:N-1]) translate([0,j,0])
sphere(d=(overlap ? 1.1 : 0.9), $fn=50);
}That gets my render time down to 0.732s, which is outstanding! ...However, going back to your original script and changing to
Since I can't reproduce any speedup, I'm curious what you mean about caching spheres? I haven't had a chance to look over the whole PR yet, but spheres should have already gone into Geometry Cache, no different from other Polysets. And on the topic of prioritizing order of operations, I don't know if you've seen it, but the discussion in #1234 and my subsequent PR #3121 are maybe worth looking at. As a quick example of why combining least-complex-geometries first, is a reasonable heuristic: If we just union them in order: ((((((A+B)+C)+D)+E)+F)+G)+H If we instead pick the lowest complexity pair between inputs and intermediate results at each step, then we end up looking more like this: ((A+B)+(C+D))+((E+F)+(G+H)) Differences would be even more dramatic if for example the first object A had a cost of 100, while the remaining objects stayed at cost = 1. Anyways, using the "Morton" Z-Order curve you mentioned (i had to look it up) seems promising as well. I had also thought about mapping bounds or centroid to a position on a 3D Hilbert Curve to facilitate sorting by proximity. More or less the same idea, just using a different space filling curve. |
Thanks for trying things out!
Hah, neat trick!
Sorry I was a bit cryptic: the spheres' PolySets are indeed in the geometry cache, but when unioning N of them we convert each one to a Nef polyhedron independently ( The questionably-named LazyGeometry helper provides a transparent on-demand conversion & cache behaviour and I think could potentially be used beyond the union case.
I hadn't, thanks for the great pointers! I hadn't understood the extent of the problem that queue was solving, thanks for elaborating. I'm working on refactoring the code to preserve the queue logic ("almost" done), will ping this thread when it's ready (moving the BoundingBoxes object inside the LazyGeometry, lots of the code should stay the same).
I'd assume the most efficient way to multithread this would be work-stealing from the same queue with a proper lock?
I haven't played with Hilbert's curve yet, could be a better pick (thanks for adding the links ;-)). In any case, overlaps of the 1D intervals corresponding to a bounding box in the curve space may not result in actual overlaps of the 3D bounding boxes, so will still need some tests on the bboxes but it should make it more likely for the heuristic to find good non-overlaps in some bounded time. Can't wait to give it a try :-) |
Oh, and this opens an entirely new can of worms. A rendering tree optimizer might be able to detect repeating subpatterns and reproduce this manual optimization... Something I'd love to explore (see my ramblings in #3637 about tree rewrites). |
Aligns applyUnion3DFast on applyUnion3D's behaviour. Can be disabled w/ -DFAST_UNION_LINEAR_UNION for comparison
Confirmed noticeable speed up on large disjoint grids
|
I've retrofitted the original queue logic and did see sensible improvements, although haven't done thorough benchmarks yet. (Also, I tried caching intermediate results to see if it could somehow approximate your manual optimization: it resulted in lots of cache evictions, so not a free pass just yet; left it out of the last batch of commits) |
|
This pull request introduces 1 alert when merging 8ea55bc into 99cae25 - view on LGTM.com new alerts:
|
Edit: Took me a while from starting this reply to finishing it, and in the meantime I pulled your latest changes and retested.
OK, this sounds a bit similar to the
I don't know precisely, but I'm pretty sure its very close to linear time by total number of faces. IIRC @rcolyer helped show this to me a while ago by writing some scripts and benching them at various complexities, maybe around when I was writing PR 3121.
I don't know much about work stealing, but i guess that implies each thread would have maintain its own separate queue of work to be stolen from? I think it would be better to have a centralized queue so that ordering can be applied across threads. So I would probably start with a basic design where all threads share a single priority queue and just pull from and push to that (with locking), something like:
But yes proper locking will be crucial for any shared resources (and there's a lot of static / global data around the codebase, for various geometry caches, stat cache, module cache, stackchecker, progress bar, console output handlers etc. which will require careful consideration). There has been some talk, about working towards minimizing such global state wherever possible so that its more compatible with multi-threading.
Another idea is maybe to just calculate volume of bbox intersections. I don't think this would be compatible in any way with the priority queue as-is, since that only calculates cost of individual geometries and not pairs of them. Its hard to predict the impact (positive or negative) of various optimization attempts without just implementing and measuring them directly. |
I don't think this type of optimization will be possible, due to the way caching works. Cache keys are basically just the substring / subtree of the CSG Tree which you see from "Design -> Display CSG Tree..." The manual optimization works by splitting the translate into two separate operations. So the inner loop gets repeated verbatim, as a child of the outer translate. Without splitting up the translates, then each one will always be unique, so it would be pretty tough I think to match patterns like that, and even if something was devised it would probably have to be specific to grid-like translations, which is a niche solution. Also, I will probably expand some more in your other PR eventually, but keep in mind that |
Yeah for me it only seems significant with large $fn values (i.e. when nefs are costly to create)
I've seen it, and agree! I've split this logic to a mixed_cache.h file as a starting point (with a meta-templated
Glad we're on the same page on this. I've actually taken a stab at this a few weeks ago: parallel_reduce.h. I wasn't using the priority queue mechanism yet, and the rest of that branch would need rethink or refresh. It does naive parallelism using futures: I essentially swapped My take away from that experiment was that unions were still too slow even with multithreading (and the evaluation tree is too deep, which serializes lots of operations), hence the lazy union extravaganza from #3637 (to flatten the tree) and now this fast union route. FYI here's the real-world model that motivated this month of hacking: it renders 20x faster with all my (monothreaded) features combined. BTW: would you be open to having some benchmark model collection + scripts? (I'm sure there's cool tools to use?) I'd be happy to contribute.
Interesting idea! (even if we don't apply it here, could help with the cost function of some global optimizer... thinking out loud, but imagine if we could build a gross, octree-based approximation of the model with just the leaf #facet & bbox information? we might have wild estimates of the number of vertices in each quadrant after CSG operations, which could help rule out some global tree rewrites)
So... I couldn't resist, I did a crude heuristic that tries to find pair-wise disjoint geometries in the following neigbhours, examining up to K geometries unsuccessfully before calling it a day. But that K allowance is reset if it gathered something in its snowball. Since K is a constant, the search isn't quite quadratic, but I'm too lazy to find out if how much more than linear it actually is. I ran a few tests and it cuts the N=5 overlap=true example by a sizeable amount (full test results + code): the single loop case is 2x faster than baseline, your nested loops trick is 35% faster than baseline. The code is a bit crude so I'll work on it it to ensure we fast-union the solids in the right order before I commit anything, but I'm pleased :-D Btw, added some copyright headers to the new files as per my employer's wishes (I'm doing all this just for fun, though), please let me know if that's an issue.
What I did is to retain a sorted set of the cache keys of the geometries that have been unioned in the LazyGeometry, so that I can create a semantic key ( I haven't looked enough at the cache size issue but maybe with persistent caching it could become okay to store all that extra data?
I saw this too early in my discovery of the codebase, concluded it wasn't used in rendering and completely forgot about it! 🤦♂️ |
|
This pull request introduces 1 alert when merging be8ab70 into 99cae25 - view on LGTM.com new alerts:
|
(oh, I think I now see what you mean, did you suggest also moving the on-demand conversion logic to that centralized cache facade? lots more code could get speed ups with that, e.g. intersections / differences) |
src/bounding_boxes.h
Outdated
| * OpenSCAD (www.openscad.org) | ||
| * Copyright 2021 Google LLC | ||
| * | ||
| * This program is free software; you can redistribute it and/or |
There was a problem hiding this comment.
GPL2-only does not work for OpenSCAD, while the OpenSCAD code itself is GPL2+, CGAL which is a very core part of the application is GPL3+, so we need the GPL2+ (or at your option) any later version definition to build an application that can be distributed.
There was a problem hiding this comment.
Hi Olivier,
* OpenSCAD (www.openscad.org)
* Copyright 2021 Google LLC
I would also note that this part is a little confusing, since under the standard structure for copyright notices, it looks like Google wrote OpenSCAD (which of course was not your intention). Of course, Google via you is a contributor now, and it's perfectly reasonable to note that to satisfy your employer's conditions on your flex time. To prepare ahead for the new files being modified by other contributors after being integrated, and to include the GPL2+ status without being wordy, perhaps a good notice is something like:
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
Do you think this would satisfy it adequately?
There was a problem hiding this comment.
Oops good to know, I should have checked, now fixed.
There was a problem hiding this comment.
@rcolyer sorry, replied before seeing your comment. I'll reword to something less ambiguous indeed!
There was a problem hiding this comment.
Used your suggestion verbatim, hope it wasn't copyrighted ;-)
|
This pull request introduces 1 alert when merging 7b6e2d2 into 99cae25 - view on LGTM.com new alerts:
|
Yeah, I guess I was just meaning that it would be a shame if on demand conversion code ended up not being used beyond the union case, and that it sounds like there's now two places where we have some code choosing between two caches in one way or another. Definitely needs some unified solution, I had at one point looked into making a single GeometryCache which took take a tuple of shared_ptrs for the different types, not sure if that's ideal either, but it was one idea. Just advocating the DRY principle basically, after all I'm quite proud that my sum contributions to OpenSCAD is in the negative LoC :D
I see, that's clever and maybe useful in some cases, but I'm not sure. I still don't see it helping with the original script's * Well its technically a
If there is still some use case for caching intermediate results like that, then maybe instead of appending disjoint geometries to As an aside, if you are interested in a more immediate / informal discussion, I'd invite you to join the IRC channel: #openscad on irc.freenode.net |
|
Ok, so I'm currently reworking the code to be faster, and less complex. My current approach is to do an analysis phase where I partition union operands into clusters of mutually non-overlapping geometries (no LazyGeometry class anymore). Some of these clusters will be of size 1. Then I reduce each of these non-overlapping clusters by appending in-place to a PolySet (avoids many copies, especially in pathological cases with, say, hundreds of thousands of small geometries). At the end of each cluster reduction I do a single check of integrity and abort that cluster if it's not closed or valid ("explode" it to clusters of size 1 for subsequent normal union), to amortize the costs of integrity checks (which should rarely fail anyway?). The resulting clusters of size 1 are slow-unioned as usual. I've got a working proof of concept (using CGAL::spatial_sort and CGAL::Union_find) and I'm working on switching to CGAL::box_intersection_d for faster bbox tests. All this to say, please hold off before reviewing too much of this as I hope the code will become smaller (will also try and merge some of the LazyGeometry methods with existing cache helpers as suggested by @thehans ). |
Added CGALUtils helpers: spatialSort, generalized bbox, getNumberOfFacets.
…ing CGAL packages (*massive* speedup) Uses blazing fast PMP csg operations, with in-place updates as much as possible. Detects non-manifold meshes not handled by PMP and reverts back to Nefs (to avoid costly checks, convex polysets are trusted, but also introduced --enable=trust-manifold to allow speedups on large STL imports, for instance). Also includes "fast-union" trick introduced in openscad#3636 (concatenate disjoint bodies) Speed improvements in the 10x to 100x range for my various grid patterns. More improvements to come when Polyhedron will become a Geometry subclass (should be able to cache a read-only version of them), and chain CSG operations instead of converting to PolySet as currently done.
|
So, that algorithm wasn't such a good idea after all, it did find large patches of fast-unionable polysets and that bit was fast, but if there were any overlaps left it ended up unioning much larger nef polyhedra at the end, making things slower for some large models. I've now pursued a different route in #3641, which is to use the Polygon Mesh Processing library (and to salvage the fast-union idea from this PR, but that's now almost a detail). That PR completely obsoletes this one so I'll close this one :-) |
…ing CGAL packages (*massive* speedup) Uses blazing fast PMP csg operations, with in-place updates as much as possible. Detects non-manifold meshes not handled by PMP and reverts back to Nefs (to avoid costly checks, convex polysets are trusted, but also introduced --enable=trust-manifold to allow speedups on large STL imports, for instance). Also includes "fast-union" trick introduced in openscad#3636 (concatenate disjoint bodies) Speed improvements in the 10x to 100x range for my various grid patterns. More improvements to come when Polyhedron will become a Geometry subclass (should be able to cache a read-only version of them), and chain CSG operations instead of converting to PolySet as currently done. Create scoped_timer.h Some cleanups Update test expectations (5 failing tests left) Conditional import path for PMP::is_non_manifold_vertex (moved files in https://doc.cgal.org/5.1/Polygon_mesh_processing/group__PMP__repairing__grp.html#ga121f588ac324938d9a6b6931a08661e1 Conditional import, take two [fast-csg] Require CGAL 5.0+ for now (compile fast polyhedron away in earlier versions) Most of the functionality still seems possible in 4.x (e.g. is_non_manifold_vertex available from 4.14), but that's too many hoops to go through for this initial version. Require CGAL 5.1.2 :-( include <CGAL/version.h> otherwise the feature isn't built, ever, d'oh [fast-csg] Renaming to FastPolyhedron to avoid naming clashes w/ OGL class [fast-csg] Add helpers to simplify boost::variant code Fix scoped performance timings syntax -DPERFORMANCE_TIMINGS [fast-csg] Drastically reduce manifoldness check costs w/ PMP::duplicate_non_manifold_vertices instead of quadratic calls to PMP::is_non_manifold_vertex [fast-csg] Pass manifoldness through PolySet [fast-csg] Rename: CGALPolyhedron Update test expectation (different triangulation of nonplanar polys) [fast-csg] Don't invert faces in CGALUtils::createPolyhedronFromPolySet [fast-csg] Surface_mesh cgal utils + copy fixes (exact conversions) [fast-csg] Fence cgal hash helpers behind ENABLE_CGAL Exact hull geometry conversion [fast-csg] Optimize differences + add fast-csg-exact Difference sped up by union their subtracted terms. Exact mode converts to Nefs instead of PolySet. It's transitional as the new poly class will soon become a Geometry, so conversion will only happen at the top of the visitation [fast-csg] CGALUtils: transform, getGeometryAs{NefPolyhedron,PolySet} Also, handle CGALPolyhedron in createNefPolyhedronFromGeometry [fast-csg] CGALPolyhedron extends Geometry! Also, a few helpers: - ResultObject.asMutableGeometry - Geometry.{transform, resize} Note that GeometryEvaluator force-converts to PolySet at the root level for now, for higher compatibility w/ exports [fast-csg] Hull for CGALPolyhedron [fast-csg] Fix convexity on editable geom [fast-csg] Fix a few crashes + move resize logic to CGALUtils [fast-csg] Fix a few crashes + move resize logic to CGALUtils [fast-csg] Fix a few crashes + move resize logic to CGALUtils [fast-csg] Download CGAL 5.2 in {uni,macosx}-build-dependencies.sh [fast-csg] Disable failing tests. One last regression to fix. remove files added by mistake [fast-csg] remove unused code in export_stl [fast-csg] Fix stl export regression [fast-csg] Add invert_orientation option to CGALUtils::createPolyhedronFromPolySet defaults to true for backwards compatibility [fast-csg] Attempt to have Travis download CGAL 5.2 on Linux [fast-csg] Take2 of CGAL 5.2 on travis linux [fast-csg] omit fast-csg on more tests [fast-csg] fix finding of boost for uni-build-dependencies cgal [fast-csg] Hunt for cgal in github-ci.sh [fast-csg] (attempt to) fix win build w/o fast-csg support [fast-csg] update test expectations Update lazyunion-difference-for-expected.png [fast-csg] fix build w/ old cgal versions [fast-csg] More/fix #ifdef FAST_CSG_AVAILABLE fences [fast-csg] Fix minkowksi crash + label skipped tests [fast-csg] Give CGAL 5.2 a spin in mxe builds (uses / tests my mxe branch for now, but should be upstreamed to openscad/mxe then mxe/mxe once it works) [fast-csg] Attempt to force CGAL 5.2 onto circleci setup Revert "[fast-csg] Attempt to force CGAL 5.2 onto circleci setup" This reverts commit 8f83001. [fast-csg] Try using images w/ cgal5.2 Built with: docker run -it openscad/mxe-i686-deps:latest /bin/bash git remote set-url origin https://github.com/ochafik/mxe.git git pull git checkout openscad-cgal-5.2 make MXE_PLUGIN_DIRS=plugins/gcc7 cgal MXE_TARGETS=i686-w64-mingw32.static.posix -j 4 JOBS=4 rm -fR .ccache/ tmp-* pkg/ rm -fR usr/bin/x86_64-* usr/x86_64-* usr/*/gcc/x86_64-* exit docker commit `docker ps -a | head -n 2 | tail -n 1 | awk '{ print $1 }'` ochafik/openscad-mxe-i686-deps-fast-csg docker push ochafik/openscad-mxe-i686-deps-fast-csg docker run -it openscad/mxe-x86_64-deps:latest /bin/bash git remote set-url origin https://github.com/ochafik/mxe.git git pull git checkout openscad-cgal-5.2 make MXE_PLUGIN_DIRS=plugins/gcc7 cgal MXE_TARGETS=x86_64-w64-mingw32.static.posix -j 4 JOBS=4 rm -fR .ccache/ tmp-* pkg/ rm -fR usr/bin/i686-* usr/i686-* usr/*/gcc/i686-* exit docker commit `docker ps -a | head -n 2 | tail -n 1 | awk '{ print $1 }'` ochafik/openscad-mxe-x86_64-deps-fast-csg docker push ochafik/openscad-mxe-x86_64-deps-fast-csg docker run -it openscad/appimage-x86_64-base:latest /bin/bash export CGAL_VERSION=5.2 curl -L --insecure https://github.com/CGAL/cgal/releases/download/v${CGAL_VERSION}/CGAL-${CGAL_VERSION}-library.tar.xz --output CGAL-${CGAL_VERSION}.tar.xz xz -f -d CGAL-${CGAL_VERSION}.tar.xz tar -xf CGAL-${CGAL_VERSION}.tar cd CGAL-${CGAL_VERSION} cmake . -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release make install cd .. rm -fR CGAL- exit docker commit `docker ps -a | head -n 2 | tail -n 1 | awk '{ print $1 }'` ochafik/openscad-appimage-x86_64-base-fast-csg docker push ochafik/openscad-appimage-x86_64-base-fast-csg Phony change to restart circleci (docker images hadn't finished pushing) [fast-csg] don't rely on broken custom images fix typo in circleci image names [fast-csg] only build w/ 1 CPU tp avoid OOM [fast-csg] Fix compilation w/ CGAL 5.0.2 circleci: use medium+ resource class https://circleci.com/docs/2.0/configuration-reference/#resource_class Revert "circleci: use medium+ resource class" This reverts commit 8a32b95. [fast-csg] Rename: CGALHybridPolyhedron [fast-csg] reduce object code size by splitting some utils out [fast-csg] More code split + drop unused file [fast-csg] fix build [fast-csg] remove some dead code [fast-csg] Proactively fix face orientation (this will probably break gcc again, will fix tomorrow) Revert "[fast-csg] Proactively fix face orientation" This reverts commit c523fe7. [fast-csg] Fix orientations of polygons automatically Helps with models built with sweep [fast-csg] update polyhedron-tests expectations Update polyhedron-tests-expected.png added a comment to openscad.desktop file Update release list and fix order of entries. Update release notes. Initial install target for cmake. Add SUFFIX option for installation. Add suffix replacement for desktop and appstream files. Ensure template is always replaced. Allow OPENSCAD_VERSION to be set as build parameter. Use GET instead of SUBLIST which is only available in cmake >= 3.12. Don't use VERSION_GREATER_EQUAL which is only available in cmake >= 3.7. Update console text colors for dark theme Sets the default background color to transparent and sets the foreground to black for text that has a set background color. Fix resource path with suffix for cmake build. Convert CircleCI AppImage build to cmake. Init MCAD git submodule. Update command line for gpg signing. [fast-csg] Add missing -DCGAL_USE_GMPXX to cmake [fast-csg] Smaller object files (more cgalutils splitting) + remove now-useless PolySet.manifold [fast-csg] fix build [fast-csg] More code split... cgal-minkowski.o is now largest object file, can't go down? [fast-csg] Reenable fast-csg that was just.. compiled away. [fast-csg] Add a "Compile w/ CGAL 4.x" github workflow + make circleci builds fast again Update polyhedron-tests-expected.png [fast-csg] fix tests (stlcgalpngtest_polyhedron-tests and monotonepngtest_polyhedron-tests share same golden, d'oh) [fast-csg] fix build Delete legacy-cgal.yml [fast-csg] Run tests w/ different expectations separately [fast-csg] fix skipping of fast-csg tests Update travis-ci-before-install-linux.sh [fast-csg] fast-csg with CGAL 4.x on github ci w/ cmake (+ avoid weird lazy-union interaction) [fast-cgal] one build per github workflow file? [fast-csg] update expectations (somehow these tests are only run on windows?) [fast-csg] minkowski3-erosion needs different expectation. Also CSG.scad isn't heavy w/ fast-union Cleanup some comments & dead code [fast-csg] Use corefinement w/o preconditions, check their return value & catch their exceptions. Also, support using CGAL_Kernel3 w/ latest 5.2.x branch [fast-csg] Drop difference "optimization", obsolete remnant from earlier iterations of the code [fast-csg] Rehabilitate vertex tests: found tests where corefinement leaves polys in states that nef chokes on [fast-csg] fix bbox regression (for-tests) [fast-csg] add edge-cases tests (literally) [fast-csg] Bit more logs [fast-csg] fix build w/ CGAL < 5.1 [fast-csg] fix build [fast-csg] mawr... lawgs! (let's be a bit verbose, shall we?) [fast-csg] Drop the grid! [fast-csg] Skip mysterious test (new failure only on CI - not on local mac) [fast-csg] Support CGAL 5.2.x branch from github in uni-build-dependencies.sh [fast-csg] Test more CGAL versions (including 5.2.x branch for latest fixes) [fast-csg] Attempt to fix the cgal versions github workflow matrix Update cgal-versions.yml [fast-csg] Disable shared vertices testing (unless -DFAST_CSG_TEST_SHARED_VERTICES is set) [fast-csg] Don't use corefinement on non-closed polyhedra (e.g. testdata/scad/3D/issues/issue1223.scad) [fast-csg] DIsable fast-csg for mirror-tests, as it triggers a corefinement condition that leaves polyhedra in a state that can't be converted to a nef! Update cgal-versions.yml Update cgal-versions.yml [fast-csg] Don't test w/ 5.2.x branch, it doesn't build yet /openscad/libraries/include/CGAL/boost/graph/copy_face_graph.h:28:10: fatal error: boost/iterator/function_output_iterator.hpp: No such file or directory #include <boost/iterator/function_output_iterator.hpp> [fast-csg] A single manifoldness check is enough in union
…ing CGAL packages (*massive* speedup) Uses blazing fast PMP csg operations, with in-place updates as much as possible. Detects non-manifold meshes not handled by PMP and reverts back to Nefs (to avoid costly checks, convex polysets are trusted, but also introduced --enable=trust-manifold to allow speedups on large STL imports, for instance). Also includes "fast-union" trick introduced in openscad#3636 (concatenate disjoint bodies) Speed improvements in the 10x to 100x range for my various grid patterns. More improvements to come when Polyhedron will become a Geometry subclass (should be able to cache a read-only version of them), and chain CSG operations instead of converting to PolySet as currently done. Create scoped_timer.h Some cleanups Update test expectations (5 failing tests left) Conditional import path for PMP::is_non_manifold_vertex (moved files in https://doc.cgal.org/5.1/Polygon_mesh_processing/group__PMP__repairing__grp.html#ga121f588ac324938d9a6b6931a08661e1 Conditional import, take two [fast-csg] Require CGAL 5.0+ for now (compile fast polyhedron away in earlier versions) Most of the functionality still seems possible in 4.x (e.g. is_non_manifold_vertex available from 4.14), but that's too many hoops to go through for this initial version. Require CGAL 5.1.2 :-( include <CGAL/version.h> otherwise the feature isn't built, ever, d'oh [fast-csg] Renaming to FastPolyhedron to avoid naming clashes w/ OGL class [fast-csg] Add helpers to simplify boost::variant code Fix scoped performance timings syntax -DPERFORMANCE_TIMINGS [fast-csg] Drastically reduce manifoldness check costs w/ PMP::duplicate_non_manifold_vertices instead of quadratic calls to PMP::is_non_manifold_vertex [fast-csg] Pass manifoldness through PolySet [fast-csg] Rename: CGALPolyhedron Update test expectation (different triangulation of nonplanar polys) [fast-csg] Don't invert faces in CGALUtils::createPolyhedronFromPolySet [fast-csg] Surface_mesh cgal utils + copy fixes (exact conversions) [fast-csg] Fence cgal hash helpers behind ENABLE_CGAL Exact hull geometry conversion [fast-csg] Optimize differences + add fast-csg-exact Difference sped up by union their subtracted terms. Exact mode converts to Nefs instead of PolySet. It's transitional as the new poly class will soon become a Geometry, so conversion will only happen at the top of the visitation [fast-csg] CGALUtils: transform, getGeometryAs{NefPolyhedron,PolySet} Also, handle CGALPolyhedron in createNefPolyhedronFromGeometry [fast-csg] CGALPolyhedron extends Geometry! Also, a few helpers: - ResultObject.asMutableGeometry - Geometry.{transform, resize} Note that GeometryEvaluator force-converts to PolySet at the root level for now, for higher compatibility w/ exports [fast-csg] Hull for CGALPolyhedron [fast-csg] Fix convexity on editable geom [fast-csg] Fix a few crashes + move resize logic to CGALUtils [fast-csg] Fix a few crashes + move resize logic to CGALUtils [fast-csg] Fix a few crashes + move resize logic to CGALUtils [fast-csg] Download CGAL 5.2 in {uni,macosx}-build-dependencies.sh [fast-csg] Disable failing tests. One last regression to fix. remove files added by mistake [fast-csg] remove unused code in export_stl [fast-csg] Fix stl export regression [fast-csg] Add invert_orientation option to CGALUtils::createPolyhedronFromPolySet defaults to true for backwards compatibility [fast-csg] Attempt to have Travis download CGAL 5.2 on Linux [fast-csg] Take2 of CGAL 5.2 on travis linux [fast-csg] omit fast-csg on more tests [fast-csg] fix finding of boost for uni-build-dependencies cgal [fast-csg] Hunt for cgal in github-ci.sh [fast-csg] (attempt to) fix win build w/o fast-csg support [fast-csg] update test expectations Update lazyunion-difference-for-expected.png [fast-csg] fix build w/ old cgal versions [fast-csg] More/fix #ifdef FAST_CSG_AVAILABLE fences [fast-csg] Fix minkowksi crash + label skipped tests [fast-csg] Give CGAL 5.2 a spin in mxe builds (uses / tests my mxe branch for now, but should be upstreamed to openscad/mxe then mxe/mxe once it works) [fast-csg] Attempt to force CGAL 5.2 onto circleci setup Revert "[fast-csg] Attempt to force CGAL 5.2 onto circleci setup" This reverts commit 8f83001. [fast-csg] Try using images w/ cgal5.2 Built with: docker run -it openscad/mxe-i686-deps:latest /bin/bash git remote set-url origin https://github.com/ochafik/mxe.git git pull git checkout openscad-cgal-5.2 make MXE_PLUGIN_DIRS=plugins/gcc7 cgal MXE_TARGETS=i686-w64-mingw32.static.posix -j 4 JOBS=4 rm -fR .ccache/ tmp-* pkg/ rm -fR usr/bin/x86_64-* usr/x86_64-* usr/*/gcc/x86_64-* exit docker commit `docker ps -a | head -n 2 | tail -n 1 | awk '{ print $1 }'` ochafik/openscad-mxe-i686-deps-fast-csg docker push ochafik/openscad-mxe-i686-deps-fast-csg docker run -it openscad/mxe-x86_64-deps:latest /bin/bash git remote set-url origin https://github.com/ochafik/mxe.git git pull git checkout openscad-cgal-5.2 make MXE_PLUGIN_DIRS=plugins/gcc7 cgal MXE_TARGETS=x86_64-w64-mingw32.static.posix -j 4 JOBS=4 rm -fR .ccache/ tmp-* pkg/ rm -fR usr/bin/i686-* usr/i686-* usr/*/gcc/i686-* exit docker commit `docker ps -a | head -n 2 | tail -n 1 | awk '{ print $1 }'` ochafik/openscad-mxe-x86_64-deps-fast-csg docker push ochafik/openscad-mxe-x86_64-deps-fast-csg docker run -it openscad/appimage-x86_64-base:latest /bin/bash export CGAL_VERSION=5.2 curl -L --insecure https://github.com/CGAL/cgal/releases/download/v${CGAL_VERSION}/CGAL-${CGAL_VERSION}-library.tar.xz --output CGAL-${CGAL_VERSION}.tar.xz xz -f -d CGAL-${CGAL_VERSION}.tar.xz tar -xf CGAL-${CGAL_VERSION}.tar cd CGAL-${CGAL_VERSION} cmake . -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release make install cd .. rm -fR CGAL- exit docker commit `docker ps -a | head -n 2 | tail -n 1 | awk '{ print $1 }'` ochafik/openscad-appimage-x86_64-base-fast-csg docker push ochafik/openscad-appimage-x86_64-base-fast-csg Phony change to restart circleci (docker images hadn't finished pushing) [fast-csg] don't rely on broken custom images fix typo in circleci image names [fast-csg] only build w/ 1 CPU tp avoid OOM [fast-csg] Fix compilation w/ CGAL 5.0.2 circleci: use medium+ resource class https://circleci.com/docs/2.0/configuration-reference/#resource_class Revert "circleci: use medium+ resource class" This reverts commit 8a32b95. [fast-csg] Rename: CGALHybridPolyhedron [fast-csg] reduce object code size by splitting some utils out [fast-csg] More code split + drop unused file [fast-csg] fix build [fast-csg] remove some dead code [fast-csg] Proactively fix face orientation (this will probably break gcc again, will fix tomorrow) Revert "[fast-csg] Proactively fix face orientation" This reverts commit c523fe7. [fast-csg] Fix orientations of polygons automatically Helps with models built with sweep [fast-csg] update polyhedron-tests expectations Update polyhedron-tests-expected.png added a comment to openscad.desktop file Update release list and fix order of entries. Update release notes. Initial install target for cmake. Add SUFFIX option for installation. Add suffix replacement for desktop and appstream files. Ensure template is always replaced. Allow OPENSCAD_VERSION to be set as build parameter. Use GET instead of SUBLIST which is only available in cmake >= 3.12. Don't use VERSION_GREATER_EQUAL which is only available in cmake >= 3.7. Update console text colors for dark theme Sets the default background color to transparent and sets the foreground to black for text that has a set background color. Fix resource path with suffix for cmake build. Convert CircleCI AppImage build to cmake. Init MCAD git submodule. Update command line for gpg signing. [fast-csg] Add missing -DCGAL_USE_GMPXX to cmake [fast-csg] Smaller object files (more cgalutils splitting) + remove now-useless PolySet.manifold [fast-csg] fix build [fast-csg] More code split... cgal-minkowski.o is now largest object file, can't go down? [fast-csg] Reenable fast-csg that was just.. compiled away. [fast-csg] Add a "Compile w/ CGAL 4.x" github workflow + make circleci builds fast again Update polyhedron-tests-expected.png [fast-csg] fix tests (stlcgalpngtest_polyhedron-tests and monotonepngtest_polyhedron-tests share same golden, d'oh) [fast-csg] fix build Delete legacy-cgal.yml [fast-csg] Run tests w/ different expectations separately [fast-csg] fix skipping of fast-csg tests Update travis-ci-before-install-linux.sh [fast-csg] fast-csg with CGAL 4.x on github ci w/ cmake (+ avoid weird lazy-union interaction) [fast-cgal] one build per github workflow file? [fast-csg] update expectations (somehow these tests are only run on windows?) [fast-csg] minkowski3-erosion needs different expectation. Also CSG.scad isn't heavy w/ fast-union Cleanup some comments & dead code [fast-csg] Use corefinement w/o preconditions, check their return value & catch their exceptions. Also, support using CGAL_Kernel3 w/ latest 5.2.x branch [fast-csg] Drop difference "optimization", obsolete remnant from earlier iterations of the code [fast-csg] Rehabilitate vertex tests: found tests where corefinement leaves polys in states that nef chokes on [fast-csg] fix bbox regression (for-tests) [fast-csg] add edge-cases tests (literally) [fast-csg] Bit more logs [fast-csg] fix build w/ CGAL < 5.1 [fast-csg] fix build [fast-csg] mawr... lawgs! (let's be a bit verbose, shall we?) [fast-csg] Drop the grid! [fast-csg] Skip mysterious test (new failure only on CI - not on local mac) [fast-csg] Support CGAL 5.2.x branch from github in uni-build-dependencies.sh [fast-csg] Test more CGAL versions (including 5.2.x branch for latest fixes) [fast-csg] Attempt to fix the cgal versions github workflow matrix Update cgal-versions.yml [fast-csg] Disable shared vertices testing (unless -DFAST_CSG_TEST_SHARED_VERTICES is set) [fast-csg] Don't use corefinement on non-closed polyhedra (e.g. testdata/scad/3D/issues/issue1223.scad) [fast-csg] DIsable fast-csg for mirror-tests, as it triggers a corefinement condition that leaves polyhedra in a state that can't be converted to a nef! Update cgal-versions.yml Update cgal-versions.yml [fast-csg] Don't test w/ 5.2.x branch, it doesn't build yet /openscad/libraries/include/CGAL/boost/graph/copy_face_graph.h:28:10: fatal error: boost/iterator/function_output_iterator.hpp: No such file or directory #include <boost/iterator/function_output_iterator.hpp> [fast-csg] A single manifoldness check is enough in union
Hi all!
This PR introduces a
fast-unionfeature that avoids using Nef polyhedra for unions of "clearly disjoint" solids (as per their respective bboxes). It simply concatenates PolySets in that case (converting from Nef polyhedra if needed), which is much quicker (Nef joins themselves are very slow, but creating Nef polyhedra can be very slow too at the first place). It plays well with lazy-union (and some upgrades to it I'm proposing in #3637).Edit: As suggested below, now also testing for fast-unionability of subtracted terms in differences (
fast-differencefeature).It also makes full use of the caches for Nef polyhedra <-> polysets conversions in the process.
As it turns out, spheres love caching: the following example now renders in 5 sec (instead of 4min30sec; will add more examples here):
(less dramatic, but this brick wall benchmark's rendering time is almost cut in half)
Some implementation comments: (suggested entry point for review: CGALUtils::applyUnion3DFast):
Added a
BoundingBoxeshelper, which allows testing a new solid's bbox against all the previously unioned solids' bounding boxes in the union (allows optimizing non-overlapping grid patterns). It currently does a crude linear search on those bounding bboxes (could speed up with some smart CGAL structure but this isn't the new bottleneck anyway)Added a
LazyGeometryhelper, which does nef ↔ polyset conversions on demand, provides their bboxes and does disjointed unions or normal ones (using / filling the cache in the process). Because of some pathological cases, even disjointed unions may fall back to Nef polyhedra operations (as a result, need to keep the original polysets, which prevents in-place concatenation: copying and checking increasingly large PolySets over can become the bottleneck in large grids of objects, for instance the example above with N=10). May want to revisit the costly detection of such cases + fallback strategy (edit: added an optional-DOPTIMISTIC_FAST_UNIONdefine to disable these checks: the spheres then render in 700ms).The order of unions matters now even more than ever. Currently I'm only using the order of the children, but I plan to find "easy" groups of disjoint operands that can be unioned as polysets (say, looking at morton invervals overlaps of the operands' bounding boxes), to minimize the number of Nef polyhedra built and operated on. Much potential for parallelism here too.
I had to switch some APIs to return shared_ptr and propagate the changes, hence a bit of diff noise, sorry.
There are other cache-unaware code paths (e.g. intersection / difference / minkowski) that I'd be happy to tackle in a follow up.
Managed to get all tests green locally (macosx), except for differences-tests for which I had to do some compromise.
Welcoming comments & suggestions / happy to refactor.