Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/LargeIslandSplitter.cpp
9906 views
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe1// SPDX-License-Identifier: MIT23#include <Jolt/Jolt.h>45#include <Jolt/Physics/LargeIslandSplitter.h>6#include <Jolt/Physics/IslandBuilder.h>7#include <Jolt/Physics/Constraints/CalculateSolverSteps.h>8#include <Jolt/Physics/Constraints/Constraint.h>9#include <Jolt/Physics/Constraints/ContactConstraintManager.h>10#include <Jolt/Physics/Body/BodyManager.h>11#include <Jolt/Core/Profiler.h>12#include <Jolt/Core/TempAllocator.h>1314//#define JPH_LARGE_ISLAND_SPLITTER_DEBUG1516JPH_NAMESPACE_BEGIN1718LargeIslandSplitter::EStatus LargeIslandSplitter::Splits::FetchNextBatch(uint32 &outConstraintsBegin, uint32 &outConstraintsEnd, uint32 &outContactsBegin, uint32 &outContactsEnd, bool &outFirstIteration)19{20{21// First check if we can get a new batch (doing a read to avoid hammering an atomic with an atomic subtract)22// Note this also avoids overflowing the status counter if we're done but there's still one thread processing items23uint64 status = mStatus.load(memory_order_acquire);2425// Check for special value that indicates that the splits are still being built26// (note we do not check for this condition again below as we reset all splits before kicking off jobs that fetch batches of work)27if (status == StatusItemMask)28return EStatus::WaitingForBatch;2930// Next check if all items have been processed. Note that we do this after checking if the job can be started31// as mNumIterations is not initialized until the split is started.32if (sGetIteration(status) >= mNumIterations)33return EStatus::AllBatchesDone;3435uint item = sGetItem(status);36uint split_index = sGetSplit(status);37if (split_index == cNonParallelSplitIdx)38{39// Non parallel split needs to be taken as a single batch, only the thread that takes element 0 will do it40if (item != 0)41return EStatus::WaitingForBatch;42}43else44{45// Parallel split is split into batches46JPH_ASSERT(split_index < mNumSplits);47const Split &split = mSplits[split_index];48if (item >= split.GetNumItems())49return EStatus::WaitingForBatch;50}51}5253// Then try to actually get the batch54uint64 status = mStatus.fetch_add(cBatchSize, memory_order_acquire);55int iteration = sGetIteration(status);56if (iteration >= mNumIterations)57return EStatus::AllBatchesDone;5859uint split_index = sGetSplit(status);60JPH_ASSERT(split_index < mNumSplits || split_index == cNonParallelSplitIdx);61const Split &split = mSplits[split_index];62uint item_begin = sGetItem(status);63if (split_index == cNonParallelSplitIdx)64{65if (item_begin == 0)66{67// Non-parallel split always goes as a single batch68outConstraintsBegin = split.mConstraintBufferBegin;69outConstraintsEnd = split.mConstraintBufferEnd;70outContactsBegin = split.mContactBufferBegin;71outContactsEnd = split.mContactBufferEnd;72outFirstIteration = iteration == 0;73return EStatus::BatchRetrieved;74}75else76{77// Otherwise we're done with this split78return EStatus::WaitingForBatch;79}80}8182// Parallel split is split into batches83uint num_constraints = split.GetNumConstraints();84uint num_contacts = split.GetNumContacts();85uint num_items = num_constraints + num_contacts;86if (item_begin >= num_items)87return EStatus::WaitingForBatch;8889uint item_end = min(item_begin + cBatchSize, num_items);90if (item_end >= num_constraints)91{92if (item_begin < num_constraints)93{94// Partially from constraints and partially from contacts95outConstraintsBegin = split.mConstraintBufferBegin + item_begin;96outConstraintsEnd = split.mConstraintBufferEnd;97}98else99{100// Only contacts101outConstraintsBegin = 0;102outConstraintsEnd = 0;103}104105outContactsBegin = split.mContactBufferBegin + (max(item_begin, num_constraints) - num_constraints);106outContactsEnd = split.mContactBufferBegin + (item_end - num_constraints);107}108else109{110// Only constraints111outConstraintsBegin = split.mConstraintBufferBegin + item_begin;112outConstraintsEnd = split.mConstraintBufferBegin + item_end;113114outContactsBegin = 0;115outContactsEnd = 0;116}117118outFirstIteration = iteration == 0;119return EStatus::BatchRetrieved;120}121122void LargeIslandSplitter::Splits::MarkBatchProcessed(uint inNumProcessed, bool &outLastIteration, bool &outFinalBatch)123{124// We fetched this batch, nobody should change the split and or iteration until we mark the last batch as processed so we can safely get the current status125uint64 status = mStatus.load(memory_order_relaxed);126uint split_index = sGetSplit(status);127JPH_ASSERT(split_index < mNumSplits || split_index == cNonParallelSplitIdx);128const Split &split = mSplits[split_index];129uint num_items_in_split = split.GetNumItems();130131// Determine if this is the last iteration before possibly incrementing it132int iteration = sGetIteration(status);133outLastIteration = iteration == mNumIterations - 1;134135// Add the number of items we processed to the total number of items processed136// Note: This needs to happen after we read the status as other threads may update the status after we mark items as processed137JPH_ASSERT(inNumProcessed > 0); // Logic will break if we mark a block of 0 items as processed138uint total_items_processed = mItemsProcessed.fetch_add(inNumProcessed, memory_order_acq_rel) + inNumProcessed;139140// Check if we're at the end of the split141if (total_items_processed >= num_items_in_split)142{143JPH_ASSERT(total_items_processed == num_items_in_split); // Should not overflow, that means we're retiring more items than we should process144145// Set items processed back to 0 for the next split/iteration146mItemsProcessed.store(0, memory_order_release);147148// Determine next split149do150{151if (split_index == cNonParallelSplitIdx)152{153// At start of next iteration154split_index = 0;155++iteration;156}157else158{159// At start of next split160++split_index;161}162163// If we're beyond the end of splits, go to the non-parallel split164if (split_index >= mNumSplits)165split_index = cNonParallelSplitIdx;166}167while (iteration < mNumIterations168&& mSplits[split_index].GetNumItems() == 0); // We don't support processing empty splits, skip to the next split in this case169170mStatus.store((uint64(iteration) << StatusIterationShift) | (uint64(split_index) << StatusSplitShift), memory_order_release);171}172173// Track if this is the final batch174outFinalBatch = iteration >= mNumIterations;175}176177LargeIslandSplitter::~LargeIslandSplitter()178{179JPH_ASSERT(mSplitMasks == nullptr);180JPH_ASSERT(mContactAndConstraintsSplitIdx == nullptr);181JPH_ASSERT(mContactAndConstraintIndices == nullptr);182JPH_ASSERT(mSplitIslands == nullptr);183}184185void LargeIslandSplitter::Prepare(const IslandBuilder &inIslandBuilder, uint32 inNumActiveBodies, TempAllocator *inTempAllocator)186{187JPH_PROFILE_FUNCTION();188189// Count the total number of constraints and contacts that we will be putting in splits190mContactAndConstraintsSize = 0;191for (uint32 island = 0; island < inIslandBuilder.GetNumIslands(); ++island)192{193// Get the contacts in this island194uint32 *contacts_start, *contacts_end;195inIslandBuilder.GetContactsInIsland(island, contacts_start, contacts_end);196uint num_contacts_in_island = uint(contacts_end - contacts_start);197198// Get the constraints in this island199uint32 *constraints_start, *constraints_end;200inIslandBuilder.GetConstraintsInIsland(island, constraints_start, constraints_end);201uint num_constraints_in_island = uint(constraints_end - constraints_start);202203uint island_size = num_contacts_in_island + num_constraints_in_island;204if (island_size >= cLargeIslandTreshold)205{206mNumSplitIslands++;207mContactAndConstraintsSize += island_size;208}209else210break; // If this island doesn't have enough constraints, the next islands won't either since they're sorted from big to small211}212213if (mContactAndConstraintsSize > 0)214{215mNumActiveBodies = inNumActiveBodies;216217// Allocate split mask buffer218mSplitMasks = (SplitMask *)inTempAllocator->Allocate(mNumActiveBodies * sizeof(SplitMask));219220// Allocate contact and constraint buffer221uint contact_and_constraint_indices_size = mContactAndConstraintsSize * sizeof(uint32);222mContactAndConstraintsSplitIdx = (uint32 *)inTempAllocator->Allocate(contact_and_constraint_indices_size);223mContactAndConstraintIndices = (uint32 *)inTempAllocator->Allocate(contact_and_constraint_indices_size);224225// Allocate island split buffer226mSplitIslands = (Splits *)inTempAllocator->Allocate(mNumSplitIslands * sizeof(Splits));227228// Prevent any of the splits from being picked up as work229for (uint i = 0; i < mNumSplitIslands; ++i)230mSplitIslands[i].ResetStatus();231}232}233234uint LargeIslandSplitter::AssignSplit(const Body *inBody1, const Body *inBody2)235{236uint32 idx1 = inBody1->GetIndexInActiveBodiesInternal();237uint32 idx2 = inBody2->GetIndexInActiveBodiesInternal();238239// Test if either index is negative240if (idx1 == Body::cInactiveIndex || !inBody1->IsDynamic())241{242// Body 1 is not active or a kinematic body, so we only need to set 1 body243JPH_ASSERT(idx2 < mNumActiveBodies);244SplitMask &mask = mSplitMasks[idx2];245uint split = min(CountTrailingZeros(~uint32(mask)), cNonParallelSplitIdx);246mask |= SplitMask(1U << split);247return split;248}249else if (idx2 == Body::cInactiveIndex || !inBody2->IsDynamic())250{251// Body 2 is not active or a kinematic body, so we only need to set 1 body252JPH_ASSERT(idx1 < mNumActiveBodies);253SplitMask &mask = mSplitMasks[idx1];254uint split = min(CountTrailingZeros(~uint32(mask)), cNonParallelSplitIdx);255mask |= SplitMask(1U << split);256return split;257}258else259{260// If both bodies are active, we need to set 2 bodies261JPH_ASSERT(idx1 < mNumActiveBodies);262JPH_ASSERT(idx2 < mNumActiveBodies);263SplitMask &mask1 = mSplitMasks[idx1];264SplitMask &mask2 = mSplitMasks[idx2];265uint split = min(CountTrailingZeros((~uint32(mask1)) & (~uint32(mask2))), cNonParallelSplitIdx);266SplitMask mask = SplitMask(1U << split);267mask1 |= mask;268mask2 |= mask;269return split;270}271}272273uint LargeIslandSplitter::AssignToNonParallelSplit(const Body *inBody)274{275uint32 idx = inBody->GetIndexInActiveBodiesInternal();276if (idx != Body::cInactiveIndex)277{278JPH_ASSERT(idx < mNumActiveBodies);279mSplitMasks[idx] |= 1U << cNonParallelSplitIdx;280}281282return cNonParallelSplitIdx;283}284285bool LargeIslandSplitter::SplitIsland(uint32 inIslandIndex, const IslandBuilder &inIslandBuilder, const BodyManager &inBodyManager, const ContactConstraintManager &inContactManager, Constraint **inActiveConstraints, CalculateSolverSteps &ioStepsCalculator)286{287JPH_PROFILE_FUNCTION();288289// Get the contacts in this island290uint32 *contacts_start, *contacts_end;291inIslandBuilder.GetContactsInIsland(inIslandIndex, contacts_start, contacts_end);292uint num_contacts_in_island = uint(contacts_end - contacts_start);293294// Get the constraints in this island295uint32 *constraints_start, *constraints_end;296inIslandBuilder.GetConstraintsInIsland(inIslandIndex, constraints_start, constraints_end);297uint num_constraints_in_island = uint(constraints_end - constraints_start);298299// Check if it exceeds the threshold300uint island_size = num_contacts_in_island + num_constraints_in_island;301if (island_size < cLargeIslandTreshold)302return false;303304// Get bodies in this island305BodyID *bodies_start, *bodies_end;306inIslandBuilder.GetBodiesInIsland(inIslandIndex, bodies_start, bodies_end);307308// Reset the split mask for all bodies in this island309Body const * const *bodies = inBodyManager.GetBodies().data();310for (const BodyID *b = bodies_start; b < bodies_end; ++b)311mSplitMasks[bodies[b->GetIndex()]->GetIndexInActiveBodiesInternal()] = 0;312313// Count the number of contacts and constraints per split314uint num_contacts_in_split[cNumSplits] = { };315uint num_constraints_in_split[cNumSplits] = { };316317// Get space to store split indices318uint offset = mContactAndConstraintsNextFree.fetch_add(island_size, memory_order_relaxed);319uint32 *contact_split_idx = mContactAndConstraintsSplitIdx + offset;320uint32 *constraint_split_idx = contact_split_idx + num_contacts_in_island;321322// Assign the contacts to a split323uint32 *cur_contact_split_idx = contact_split_idx;324for (const uint32 *c = contacts_start; c < contacts_end; ++c)325{326const Body *body1, *body2;327inContactManager.GetAffectedBodies(*c, body1, body2);328uint split = AssignSplit(body1, body2);329num_contacts_in_split[split]++;330*cur_contact_split_idx++ = split;331332if (body1->IsDynamic())333ioStepsCalculator(body1->GetMotionPropertiesUnchecked());334if (body2->IsDynamic())335ioStepsCalculator(body2->GetMotionPropertiesUnchecked());336}337338// Assign the constraints to a split339uint32 *cur_constraint_split_idx = constraint_split_idx;340for (const uint32 *c = constraints_start; c < constraints_end; ++c)341{342const Constraint *constraint = inActiveConstraints[*c];343uint split = constraint->BuildIslandSplits(*this);344num_constraints_in_split[split]++;345*cur_constraint_split_idx++ = split;346347ioStepsCalculator(constraint);348}349350ioStepsCalculator.Finalize();351352// Start with 0 splits353uint split_remap_table[cNumSplits];354uint new_split_idx = mNextSplitIsland.fetch_add(1, memory_order_relaxed);355JPH_ASSERT(new_split_idx < mNumSplitIslands);356Splits &splits = mSplitIslands[new_split_idx];357splits.mIslandIndex = inIslandIndex;358splits.mNumSplits = 0;359splits.mNumIterations = ioStepsCalculator.GetNumVelocitySteps() + 1; // Iteration 0 is used for warm starting360splits.mNumVelocitySteps = ioStepsCalculator.GetNumVelocitySteps();361splits.mNumPositionSteps = ioStepsCalculator.GetNumPositionSteps();362splits.mItemsProcessed.store(0, memory_order_release);363364// Allocate space to store the sorted constraint and contact indices per split365uint32 *constraint_buffer_cur[cNumSplits], *contact_buffer_cur[cNumSplits];366for (uint s = 0; s < cNumSplits; ++s)367{368// If this split doesn't contain enough constraints and contacts, we will combine it with the non parallel split369if (num_constraints_in_split[s] + num_contacts_in_split[s] < cSplitCombineTreshold370&& s < cNonParallelSplitIdx) // The non-parallel split cannot merge into itself371{372// Remap it373split_remap_table[s] = cNonParallelSplitIdx;374375// Add the counts to the non parallel split376num_contacts_in_split[cNonParallelSplitIdx] += num_contacts_in_split[s];377num_constraints_in_split[cNonParallelSplitIdx] += num_constraints_in_split[s];378}379else380{381// This split is valid, map it to the next empty slot382uint target_split;383if (s < cNonParallelSplitIdx)384target_split = splits.mNumSplits++;385else386target_split = cNonParallelSplitIdx;387Split &split = splits.mSplits[target_split];388split_remap_table[s] = target_split;389390// Allocate space for contacts391split.mContactBufferBegin = offset;392split.mContactBufferEnd = split.mContactBufferBegin + num_contacts_in_split[s];393394// Allocate space for constraints395split.mConstraintBufferBegin = split.mContactBufferEnd;396split.mConstraintBufferEnd = split.mConstraintBufferBegin + num_constraints_in_split[s];397398// Store start for each split399contact_buffer_cur[target_split] = mContactAndConstraintIndices + split.mContactBufferBegin;400constraint_buffer_cur[target_split] = mContactAndConstraintIndices + split.mConstraintBufferBegin;401402// Update offset403offset = split.mConstraintBufferEnd;404}405}406407// Split the contacts408for (uint c = 0; c < num_contacts_in_island; ++c)409{410uint split = split_remap_table[contact_split_idx[c]];411*contact_buffer_cur[split]++ = contacts_start[c];412}413414// Split the constraints415for (uint c = 0; c < num_constraints_in_island; ++c)416{417uint split = split_remap_table[constraint_split_idx[c]];418*constraint_buffer_cur[split]++ = constraints_start[c];419}420421#ifdef JPH_LARGE_ISLAND_SPLITTER_DEBUG422// Trace the size of all splits423uint sum = 0;424String stats;425for (uint s = 0; s < cNumSplits; ++s)426{427// If we've processed all splits, jump to the non-parallel split428if (s >= splits.GetNumSplits())429s = cNonParallelSplitIdx;430431const Split &split = splits.mSplits[s];432stats += StringFormat("g:%d:%d:%d, ", s, split.GetNumContacts(), split.GetNumConstraints());433sum += split.GetNumItems();434}435stats += StringFormat("sum: %d", sum);436Trace(stats.c_str());437#endif // JPH_LARGE_ISLAND_SPLITTER_DEBUG438439#ifdef JPH_ENABLE_ASSERTS440for (uint s = 0; s < cNumSplits; ++s)441{442// If there are no more splits, process the non-parallel split443if (s >= splits.mNumSplits)444s = cNonParallelSplitIdx;445446// Check that we wrote all elements447Split &split = splits.mSplits[s];448JPH_ASSERT(contact_buffer_cur[s] == mContactAndConstraintIndices + split.mContactBufferEnd);449JPH_ASSERT(constraint_buffer_cur[s] == mContactAndConstraintIndices + split.mConstraintBufferEnd);450}451452#ifdef JPH_DEBUG453// Validate that the splits are indeed not touching the same body454for (uint s = 0; s < splits.mNumSplits; ++s)455{456Array<bool> body_used(mNumActiveBodies, false);457458// Validate contacts459uint32 split_contacts_begin, split_contacts_end;460splits.GetContactsInSplit(s, split_contacts_begin, split_contacts_end);461for (uint32 *c = mContactAndConstraintIndices + split_contacts_begin; c < mContactAndConstraintIndices + split_contacts_end; ++c)462{463const Body *body1, *body2;464inContactManager.GetAffectedBodies(*c, body1, body2);465466uint32 idx1 = body1->GetIndexInActiveBodiesInternal();467if (idx1 != Body::cInactiveIndex && body1->IsDynamic())468{469JPH_ASSERT(!body_used[idx1]);470body_used[idx1] = true;471}472473uint32 idx2 = body2->GetIndexInActiveBodiesInternal();474if (idx2 != Body::cInactiveIndex && body2->IsDynamic())475{476JPH_ASSERT(!body_used[idx2]);477body_used[idx2] = true;478}479}480}481#endif // JPH_DEBUG482#endif // JPH_ENABLE_ASSERTS483484// Allow other threads to pick up this split island now485splits.StartFirstBatch();486return true;487}488489LargeIslandSplitter::EStatus LargeIslandSplitter::FetchNextBatch(uint &outSplitIslandIndex, uint32 *&outConstraintsBegin, uint32 *&outConstraintsEnd, uint32 *&outContactsBegin, uint32 *&outContactsEnd, bool &outFirstIteration)490{491// We can't be done when all islands haven't been submitted yet492uint num_splits_created = mNextSplitIsland.load(memory_order_acquire);493bool all_done = num_splits_created == mNumSplitIslands;494495// Loop over all split islands to find work496uint32 constraints_begin, constraints_end, contacts_begin, contacts_end;497for (Splits *s = mSplitIslands; s < mSplitIslands + num_splits_created; ++s)498switch (s->FetchNextBatch(constraints_begin, constraints_end, contacts_begin, contacts_end, outFirstIteration))499{500case EStatus::AllBatchesDone:501break;502503case EStatus::WaitingForBatch:504all_done = false;505break;506507case EStatus::BatchRetrieved:508outSplitIslandIndex = uint(s - mSplitIslands);509outConstraintsBegin = mContactAndConstraintIndices + constraints_begin;510outConstraintsEnd = mContactAndConstraintIndices + constraints_end;511outContactsBegin = mContactAndConstraintIndices + contacts_begin;512outContactsEnd = mContactAndConstraintIndices + contacts_end;513return EStatus::BatchRetrieved;514}515516return all_done? EStatus::AllBatchesDone : EStatus::WaitingForBatch;517}518519void LargeIslandSplitter::MarkBatchProcessed(uint inSplitIslandIndex, const uint32 *inConstraintsBegin, const uint32 *inConstraintsEnd, const uint32 *inContactsBegin, const uint32 *inContactsEnd, bool &outLastIteration, bool &outFinalBatch)520{521uint num_items_processed = uint(inConstraintsEnd - inConstraintsBegin) + uint(inContactsEnd - inContactsBegin);522523JPH_ASSERT(inSplitIslandIndex < mNextSplitIsland.load(memory_order_relaxed));524Splits &splits = mSplitIslands[inSplitIslandIndex];525splits.MarkBatchProcessed(num_items_processed, outLastIteration, outFinalBatch);526}527528void LargeIslandSplitter::PrepareForSolvePositions()529{530for (Splits *s = mSplitIslands, *s_end = mSplitIslands + mNumSplitIslands; s < s_end; ++s)531{532// Set the number of iterations to the number of position steps533s->mNumIterations = s->mNumPositionSteps;534535// We can start again from the first batch536s->StartFirstBatch();537}538}539540void LargeIslandSplitter::Reset(TempAllocator *inTempAllocator)541{542JPH_PROFILE_FUNCTION();543544// Everything should have been used545JPH_ASSERT(mContactAndConstraintsNextFree.load(memory_order_relaxed) == mContactAndConstraintsSize);546JPH_ASSERT(mNextSplitIsland.load(memory_order_relaxed) == mNumSplitIslands);547548// Free split islands549if (mNumSplitIslands > 0)550{551inTempAllocator->Free(mSplitIslands, mNumSplitIslands * sizeof(Splits));552mSplitIslands = nullptr;553554mNumSplitIslands = 0;555mNextSplitIsland.store(0, memory_order_relaxed);556}557558// Free contact and constraint buffers559if (mContactAndConstraintsSize > 0)560{561inTempAllocator->Free(mContactAndConstraintIndices, mContactAndConstraintsSize * sizeof(uint32));562mContactAndConstraintIndices = nullptr;563564inTempAllocator->Free(mContactAndConstraintsSplitIdx, mContactAndConstraintsSize * sizeof(uint32));565mContactAndConstraintsSplitIdx = nullptr;566567mContactAndConstraintsSize = 0;568mContactAndConstraintsNextFree.store(0, memory_order_relaxed);569}570571// Free split masks572if (mSplitMasks != nullptr)573{574inTempAllocator->Free(mSplitMasks, mNumActiveBodies * sizeof(SplitMask));575mSplitMasks = nullptr;576577mNumActiveBodies = 0;578}579}580581JPH_NAMESPACE_END582583584