-
-
Notifications
You must be signed in to change notification settings - Fork 277
/
TwoBodyTypeProcessor.cs
250 lines (224 loc) · 16.5 KB
/
TwoBodyTypeProcessor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
using BepuUtilities;
using BepuUtilities.Collections;
using BepuUtilities.Memory;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace BepuPhysics.Constraints
{
/// <summary>
/// A constraint's body references. Stored separately from the iteration data since it is accessed by both the prestep and solve.
/// Two address streams isn't much of a problem for prefetching.
/// </summary>
public struct TwoBodyReferences
{
public Vector<int> IndexA;
public Vector<int> IndexB;
}
/// <summary>
/// Prestep, warm start and solve iteration functions for a two body constraint type.
/// </summary>
/// <typeparam name="TPrestepData">Type of the prestep data used by the constraint.</typeparam>
/// <typeparam name="TAccumulatedImpulse">Type of the accumulated impulses used by the constraint.</typeparam>
public interface ITwoBodyConstraintFunctions<TPrestepData, TAccumulatedImpulse>
{
static abstract void WarmStart(in Vector3Wide positionA, in QuaternionWide orientationA, in BodyInertiaWide inertiaA, in Vector3Wide positionB, in QuaternionWide orientationB, in BodyInertiaWide inertiaB,
ref TPrestepData prestep, ref TAccumulatedImpulse accumulatedImpulses, ref BodyVelocityWide wsvA, ref BodyVelocityWide wsvB);
static abstract void Solve(in Vector3Wide positionA, in QuaternionWide orientationA, in BodyInertiaWide inertiaA, in Vector3Wide positionB, in QuaternionWide orientationB, in BodyInertiaWide inertiaB, float dt, float inverseDt,
ref TPrestepData prestep, ref TAccumulatedImpulse accumulatedImpulses, ref BodyVelocityWide wsvA, ref BodyVelocityWide wsvB);
/// <summary>
/// Gets whether this constraint type requires incremental updates for each substep taken beyond the first.
/// </summary>
static abstract bool RequiresIncrementalSubstepUpdates { get; }
static abstract void IncrementallyUpdateForSubstep(in Vector<float> dt, in BodyVelocityWide wsvA, in BodyVelocityWide wsvB, ref TPrestepData prestepData);
}
//Not a big fan of complex generic-filled inheritance hierarchies, but this is the shortest evolutionary step to removing duplicates.
//There are some other options if this inheritance hierarchy gets out of control.
/// <summary>
/// Shared implementation across all two body constraints.
/// </summary>
public abstract class TwoBodyTypeProcessor<TPrestepData, TAccumulatedImpulse, TConstraintFunctions,
TWarmStartAccessFilterA, TWarmStartAccessFilterB, TSolveAccessFilterA, TSolveAccessFilterB>
: TypeProcessor<TwoBodyReferences, TPrestepData, TAccumulatedImpulse>
where TPrestepData : unmanaged where TAccumulatedImpulse : unmanaged
where TConstraintFunctions : unmanaged, ITwoBodyConstraintFunctions<TPrestepData, TAccumulatedImpulse>
where TWarmStartAccessFilterA : unmanaged, IBodyAccessFilter
where TWarmStartAccessFilterB : unmanaged, IBodyAccessFilter
where TSolveAccessFilterA : unmanaged, IBodyAccessFilter
where TSolveAccessFilterB : unmanaged, IBodyAccessFilter
{
protected sealed override int InternalBodiesPerConstraint => 2;
struct TwoBodySortKeyGenerator : ISortKeyGenerator<TwoBodyReferences>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetSortKey(int constraintIndex, ref Buffer<TwoBodyReferences> bodyReferences)
{
BundleIndexing.GetBundleIndices(constraintIndex, out var bundleIndex, out var innerIndex);
ref var bundleReferences = ref bodyReferences[bundleIndex];
//We sort based on the body references within the constraint.
//Sort based on the smaller body index in a constraint. Note that it is impossible for there to be two references to the same body within a constraint batch,
//so there's no need to worry about the case where the comparison is equal.
ref var indexA = ref GatherScatter.Get(ref bundleReferences.IndexA, innerIndex);
ref var indexB = ref Unsafe.Add(ref indexA, Vector<int>.Count);
return indexA < indexB ? indexA : indexB;
//TODO: It is conceivable that another sorting key heuristic would beat this one. This completely ignores the second connection and makes it very unlikely
//that it could converge to whatever globally optimal layout exists. It's a little tricky to come up with good heuristics, though- many will end up
//batching constraints which relate to wildly different bodies. Sorting by the minimum at least guarantees that two adjacent constraints will be as close as they can be
//in one way.
//In practice, we approach within about 5-10% of the optimum using the above sorting heuristic and the current incremental body optimizer.
//It's not immediately clear that ANY local comparison based sort will be able to do as well as some form of global optimizer that maximizes
//the 'closeness', which drops to zero once accesses would leave the cache line. This is made more complicated by the AOSOA layout- most likely
//such a heuristic would need to score based on whether the bodies are in the same bundle. So, for example, two constraints get one 'close' point for each
//shared body bundle.
//(Since you would only be optimizing within type batches, the fact that different types have different body counts wouldn't be an issue. They would
//only ever be compared against other constraints of the same type.)
//Even if you can't guarantee the next constraint is going to have bodies that are in cache, if you can generally lift the number of constraints
//that end up used quite a few times in L1/L2, there is probably a nice benefit to be had. That would suggest 'wider' optimizations rather than bundle-specific ones.
//All of these global techniques get into some nasty O complexities, but there may be heuristics which can approach them- sort of like BVH SAH sweep builders.
//Especially incremental ones, like the refinement we use in the dynamic BVH broadphase.
}
}
internal sealed override void GenerateSortKeysAndCopyReferences(
ref TypeBatch typeBatch,
int bundleStart, int localBundleStart, int bundleCount,
int constraintStart, int localConstraintStart, int constraintCount,
ref int firstSortKey, ref int firstSourceIndex, ref Buffer<byte> bodyReferencesCache)
{
GenerateSortKeysAndCopyReferences<TwoBodySortKeyGenerator>(
ref typeBatch,
bundleStart, localBundleStart, bundleCount,
constraintStart, localConstraintStart, constraintCount,
ref firstSortKey, ref firstSourceIndex, ref bodyReferencesCache);
}
internal sealed override void VerifySortRegion(ref TypeBatch typeBatch, int bundleStartIndex, int constraintCount, ref Buffer<int> sortedKeys, ref Buffer<int> sortedSourceIndices)
{
VerifySortRegion<TwoBodySortKeyGenerator>(ref typeBatch, bundleStartIndex, constraintCount, ref sortedKeys, ref sortedSourceIndices);
}
//public const int WarmStartPrefetchDistance = 8;
//public const int SolvePrefetchDistance = 4;
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//static unsafe void Prefetch(void* address)
//{
// if (Sse.IsSupported)
// {
// Sse.Prefetch0(address);
// //Sse.Prefetch0((byte*)address + 64);
// //TODO: prefetch should grab cache line pair anyway, right? not much reason to explicitly do more?
// }
// //TODO: ARM?
//}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//static unsafe void PrefetchBundle(SolverState* stateBase, ref TwoBodyReferences references, int countInBundle)
//{
// var indicesA = (int*)Unsafe.AsPointer(ref references.IndexA);
// var indicesB = (int*)Unsafe.AsPointer(ref references.IndexB);
// for (int i = 0; i < countInBundle; ++i)
// {
// var indexA = indicesA[i];
// var indexB = indicesA[i];
// Prefetch(stateBase + indexA);
// Prefetch(stateBase + indexB);
// }
//}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//[Conditional("PREFETCH")]
//public unsafe static void EarlyPrefetch(int prefetchDistance, ref TypeBatch typeBatch, ref Buffer<TwoBodyReferences> references, ref Buffer<SolverState> states, int startBundleIndex, int exclusiveEndBundleIndex)
//{
// exclusiveEndBundleIndex = Math.Min(exclusiveEndBundleIndex, startBundleIndex + prefetchDistance);
// var lastBundleIndex = exclusiveEndBundleIndex - 1;
// for (int i = startBundleIndex; i < lastBundleIndex; ++i)
// {
// PrefetchBundle(states.Memory, ref references[i], Vector<float>.Count);
// }
// var countInBundle = GetCountInBundle(ref typeBatch, lastBundleIndex);
// PrefetchBundle(states.Memory, ref references[lastBundleIndex], countInBundle);
//}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//[Conditional("PREFETCH")]
//public unsafe static void Prefetch(int prefetchDistance, ref TypeBatch typeBatch, ref Buffer<TwoBodyReferences> references, ref Buffer<SolverState> states, int bundleIndex, int exclusiveEndBundleIndex)
//{
// var targetIndex = bundleIndex + prefetchDistance;
// if (targetIndex < exclusiveEndBundleIndex)
// {
// PrefetchBundle(states.Memory, ref references[targetIndex], GetCountInBundle(ref typeBatch, targetIndex));
// }
//}
//The following covers the common loop logic for all two body constraints. Each iteration invokes the warm start function type.
//This abstraction should, in theory, have zero overhead if the implementation of the interface is in a struct with aggressive inlining.
//By providing the overrides at this level, the concrete implementation (assuming it inherits from one of the prestep-providing variants)
//only has to specify *type* arguments associated with the interface-implementing struct-delegates. It's going to look very strange, but it's low overhead
//and minimizes per-type duplication.
public override void WarmStart<TIntegratorCallbacks, TBatchIntegrationMode, TAllowPoseIntegration>(
ref TypeBatch typeBatch, ref Buffer<IndexSet> integrationFlags, Bodies bodies, ref TIntegratorCallbacks integratorCallbacks,
float dt, float inverseDt, int startBundle, int exclusiveEndBundle, int workerIndex)
{
var prestepBundles = typeBatch.PrestepData.As<TPrestepData>();
var bodyReferencesBundles = typeBatch.BodyReferences.As<TwoBodyReferences>();
var accumulatedImpulsesBundles = typeBatch.AccumulatedImpulses.As<TAccumulatedImpulse>();
//EarlyPrefetch(WarmStartPrefetchDistance, ref typeBatch, ref bodyReferencesBundles, ref states, startBundle, exclusiveEndBundle);
for (int i = startBundle; i < exclusiveEndBundle; ++i)
{
ref var prestep = ref prestepBundles[i];
ref var accumulatedImpulses = ref accumulatedImpulsesBundles[i];
ref var references = ref bodyReferencesBundles[i];
//Prefetch(WarmStartPrefetchDistance, ref typeBatch, ref bodyReferencesBundles, ref states, i, exclusiveEndBundle);
GatherAndIntegrate<TIntegratorCallbacks, TBatchIntegrationMode, TWarmStartAccessFilterA, TAllowPoseIntegration>(bodies, ref integratorCallbacks, ref integrationFlags, 0, dt, workerIndex, i, ref references.IndexA,
out var positionA, out var orientationA, out var wsvA, out var inertiaA);
GatherAndIntegrate<TIntegratorCallbacks, TBatchIntegrationMode, TWarmStartAccessFilterB, TAllowPoseIntegration>(bodies, ref integratorCallbacks, ref integrationFlags, 1, dt, workerIndex, i, ref references.IndexB,
out var positionB, out var orientationB, out var wsvB, out var inertiaB);
TConstraintFunctions.WarmStart(positionA, orientationA, inertiaA, positionB, orientationB, inertiaB, ref prestep, ref accumulatedImpulses, ref wsvA, ref wsvB);
if (typeof(TBatchIntegrationMode) == typeof(BatchShouldNeverIntegrate))
{
bodies.ScatterVelocities<TWarmStartAccessFilterA>(ref wsvA, ref references.IndexA);
bodies.ScatterVelocities<TWarmStartAccessFilterB>(ref wsvB, ref references.IndexB);
}
else
{
//This batch has some integrators, which means that every bundle is going to gather all velocities.
//(We don't make per-bundle determinations about this to avoid an extra branch and instruction complexity, and the difference is very small.)
bodies.ScatterVelocities<AccessAll>(ref wsvA, ref references.IndexA);
bodies.ScatterVelocities<AccessAll>(ref wsvB, ref references.IndexB);
}
}
}
public override void Solve(ref TypeBatch typeBatch, Bodies bodies, float dt, float inverseDt, int startBundle, int exclusiveEndBundle)
{
var prestepBundles = typeBatch.PrestepData.As<TPrestepData>();
var bodyReferencesBundles = typeBatch.BodyReferences.As<TwoBodyReferences>();
var accumulatedImpulsesBundles = typeBatch.AccumulatedImpulses.As<TAccumulatedImpulse>();
//EarlyPrefetch(SolvePrefetchDistance, ref typeBatch, ref bodyReferencesBundles, ref motionStates, startBundle, exclusiveEndBundle);
for (int i = startBundle; i < exclusiveEndBundle; ++i)
{
ref var prestep = ref prestepBundles[i];
ref var accumulatedImpulses = ref accumulatedImpulsesBundles[i];
ref var references = ref bodyReferencesBundles[i];
//Prefetch(SolvePrefetchDistance, ref typeBatch, ref bodyReferencesBundles, ref motionStates, i, exclusiveEndBundle);
bodies.GatherState<TSolveAccessFilterA>(references.IndexA, true, out var positionA, out var orientationA, out var wsvA, out var inertiaA);
bodies.GatherState<TSolveAccessFilterB>(references.IndexB, true, out var positionB, out var orientationB, out var wsvB, out var inertiaB);
TConstraintFunctions.Solve(positionA, orientationA, inertiaA, positionB, orientationB, inertiaB, dt, inverseDt, ref prestep, ref accumulatedImpulses, ref wsvA, ref wsvB);
bodies.ScatterVelocities<TSolveAccessFilterA>(ref wsvA, ref references.IndexA);
bodies.ScatterVelocities<TSolveAccessFilterB>(ref wsvB, ref references.IndexB);
}
}
public override bool RequiresIncrementalSubstepUpdates => TConstraintFunctions.RequiresIncrementalSubstepUpdates;
public override void IncrementallyUpdateForSubstep(ref TypeBatch typeBatch, Bodies bodies, float dt, float inverseDt, int startBundle, int exclusiveEndBundle)
{
var prestepBundles = typeBatch.PrestepData.As<TPrestepData>();
var bodyReferencesBundles = typeBatch.BodyReferences.As<TwoBodyReferences>();
var dtWide = new Vector<float>(dt);
for (int i = startBundle; i < exclusiveEndBundle; ++i)
{
ref var prestep = ref prestepBundles[i];
ref var references = ref bodyReferencesBundles[i];
bodies.GatherState<AccessOnlyVelocity>(references.IndexA, true, out _, out _, out var wsvA, out _);
bodies.GatherState<AccessOnlyVelocity>(references.IndexB, true, out _, out _, out var wsvB, out _);
TConstraintFunctions.IncrementallyUpdateForSubstep(dtWide, wsvA, wsvB, ref prestep);
}
}
}
public abstract class TwoBodyContactTypeProcessor<TPrestepData, TAccumulatedImpulse, TConstraintFunctions>
: TwoBodyTypeProcessor<TPrestepData, TAccumulatedImpulse, TConstraintFunctions, AccessNoPose, AccessNoPose, AccessNoPose, AccessNoPose>
where TPrestepData : unmanaged where TAccumulatedImpulse : unmanaged
where TConstraintFunctions : unmanaged, ITwoBodyConstraintFunctions<TPrestepData, TAccumulatedImpulse>
{
}
}