-
-
Notifications
You must be signed in to change notification settings - Fork 374
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
Improve narrow-phase collision physics performance #5789
base: master
Are you sure you want to change the base?
Conversation
Looks good, given it a test and not had any problems so far 👍 |
Perhaps not the first time you're back in Pioneer after hiatus, but I imagine it would be a mix of shock of all the new stuff intertwined with familiarity over the stuff being like they used to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was very interesting to dive into this topic, I hope my nitpicking is useful!
startIdx++; | ||
} else { | ||
endIdx--; | ||
std::swap(keys[startIdx], keys[endIdx]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This swap turns out to be useless if the element at the end is also greater. Generally, if the array is already sorted, it seems to make a lot of unnecessary swaps. Maybe it's possible to check the element in endIdx
before swap? (applies to all places where this algorithm is used)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This broadly breaks down into two categories. Either the array is "unknown" - i.e. we cannot assume the elements are sorted - or the array is already known to be sorted.
- If the array is unknown, we cannot determine whether the element at the end that we are swapping with is greater until we read it. Either we read it before swapping and conditionally swap based on the value, or we always swap and then read it as part of the next iteration (at which point the value can be cached in a register).
- If the array is known to be sorted, this function will perform unnecessary swaps for approximately 1/2 of the array, amortized.
This function is used almost exclusively over unsorted data, as the pivot axis and point change with every successive call to Partition()
. Because this does not perform an in-order ascending/descending sort, but rather a simple partition over the dataset, any change to pivot axis or point invalidates the sorted state of the array.
The algorithm is intentionally designed to be as simple and unconditional as possible. There is a single less-than check - the minimum possible to implement the algorithm - and the algorithm is intended to perform as few operations as possible to make the best use of instruction and data caches.
- Alternate BVH construction algorithm using a bin-based surface area heuristic search to find optimal splitting planes. - Optimized for human-authored models which are highly ordered compared to runtime scene broadphase information which is much more chaotic. - Binning implementation inspired by https://jacco.ompf2.com/2022/04/21/how-to-build-a-bvh-part-3-quick-builds/ - Provides a general 10-50% improvement in tree SAH vs. existing BVHTree, while ensuring each leaf only contains 1 element. - Tree SAH on worst-case available inputs is ~15% higher than existing BVHTree. - Overall memory usage is reduced by 20-25% in worst-case scenes - Tested binning factors of 8, 12, and 16, with 12 producing the best results on existing input data.
- Useful when doing simultaneous traversal of two BVHs and need to limit ray-tracing to a subtree
- The buffer size wasn't properly set when creating a buffer for incoming draw data >1MB
- Moving the geomFlag variable around reduces the size of a CollisionContact to 96 bytes
- Produces generally faster and higher-quality BVHs - Query performance can be significantly faster due to better cache occupancy and lower memory consumption - Implemented side-by-side in this commit for testing purposes
- Traversal over new BVHTree implementation sees significantly better narrow-phase performance (average: 1.5-2x, up to 5x in some cases) due to improved cache occupancy and higher-quality tree layout - With maximum reported contacts, new implementation can take up to 2x time to saturate allowed contact reports, though overall performance is still significantly faster than existing BVHTree worst-case performance with 0-7 contacts - Tested against Cydonia and Mars High to provide equal or faster performance in Debug builds and significant performance gains in Release builds - Implemented side-by-side with old collision algorithm for performance testing
- Fully remove the now-deprecated BVHTree implementation and use new BinnedAreaBVHTree for collision detection code - Geom::Collide now returns a list of contacts rather than taking a callback to execute on each collision - This could provide future potential for multithreading collision detection if required - GeomTree only loads its EdgeTree AABB data from disk - this should be extended in future SGM versions to include the entire triangle/edge BVH structure
- Render debug visualizer for edge and triangle BVH trees for model collision mesh - Compute and display surface area heuristic for edge/triangle collision mesh BVH - Add support for displaying dynamic collision mesh BVHs, even though no current models use dynamic collision meshes
- Variable-length stack-allocated arrays are a GCC extension
- Accidentally used volume of the contained area instead of surface-area of the AABB bounding box
- An off-by-one error resulted in splitting planes ignoring the last object bin
- Resolve mixed-signs warnings where feasible - Introduce new vector3::ReciprocalSafe() method to compute 1.0 / vector without introducing NaNs - Fix initialization order warning in CollisionContact.h
92077eb
to
ceef715
Compare
- Remove ReciprocalSafe as division by zero is an intended feature of computing the inverse of the ray direction
This PR makes the performance of the narrow-phase ("are these objects really colliding?") collision detection pass more betterer, with an average gain for nearly-colliding ship-station pairs on the order of 1.5x-5x. It's slightly slower for interpenetrating objects, as the old code saturated the maximum 8 collision contacts without traversing as many layers of the BVH, but that's not the scenario we should be optimizing for.
I've intentionally structured the commits so that intermediate commits can be built and tested if needed to check performance numbers across multiple systems. This PR strongly needs testing by as many people as possible, as it has a high possibility of completely broken physics if bugs slip through.
Please note when testing that this PR does not improve the quality of the physics - if you were able to fall through the landing pad before, you'll be able to fall through the pad now. It also only affects object-object collision, so ships vs. stations, buildings, and other ships. Collision with terrain is not affected by this PR.
I'm mainly looking for feedback about the performance and any regressions - if you phase entirely through an object that is solid in
master
, that's a problem. Review is welcome if anyone feels like it, but testing is more important, especially if you want this in the upcoming 2024-03-14 release.CC @nozmajner @fluffyfreak @JonBooth78(?) for sanity-testing on Windows.