Skip to content

Commit b8cf2e2

Browse files
authored
add FloatingOriginPerf patch (by gotmachine) (#266)
* add FloatingOriginPerf patch (by gotmachine) * fixup readme * add missing publicize that I dropped in the reshuffle * merge from dev * check for null particle system
1 parent edefff9 commit b8cf2e2

File tree

5 files changed

+318
-4
lines changed

5 files changed

+318
-4
lines changed

GameData/KSPCommunityFixes/Settings.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,10 @@ KSP_COMMUNITY_FIXES
455455
// framerate in large part count situations.
456456
FlightPerf = true
457457
458+
// General micro-optimization of floating origin shifts. Main benefit is in large particle count situations
459+
// but this helps a bit in other cases as well.
460+
FloatingOriginPerf = true
461+
458462
// ##########################
459463
// Modding
460464
// ##########################

KSPCommunityFixes/KSPCommunityFixes.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
<Publicize Include="UnityEngine.CoreModule:UnityEngine.Object.m_CachedPtr" />
9999
<Publicize Include="UnityEngine.CoreModule:UnityEngine.Object.GetOffsetOfInstanceIDInCPlusPlusObject" />
100100
<Publicize Include="UnityEngine.IMGUIModule" />
101+
<Publicize Include="UnityEngine.CoreModule:Unity.Collections.NativeArray`1.m_Buffer" />
101102
<Publicize Include="mscorlib:System.IO.MonoIO" />
102103
<Publicize Include="mscorlib:System.IO.MonoIOError" />
103104
<Publicize Include="mscorlib:System.IO.MonoIOStat" />
@@ -160,6 +161,7 @@
160161
<Compile Include="Performance\AsteroidAndCometDrillCache.cs" />
161162
<Compile Include="BugFixes\DoubleCurvePreserveTangents.cs" />
162163
<Compile Include="BugFixes\RestoreMaxPhysicsDT.cs" />
164+
<Compile Include="Performance\FloatingOriginPerf.cs" />
163165
<Compile Include="Performance\PartSystemsFastUpdate.cs" />
164166
<Compile Include="Performance\CollisionEnhancerFastUpdate.cs" />
165167
<Compile Include="Performance\CollisionManagerFastUpdate.cs" />

KSPCommunityFixes/Library/Extensions.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
2+
using System.Runtime.CompilerServices;
3+
using Unity.Collections;
4+
using Unity.Collections.LowLevel.Unsafe;
65
using UnityEngine;
76

87
namespace KSPCommunityFixes
@@ -23,4 +22,46 @@ public static bool IsPAWOpen(this Part part)
2322
return part.PartActionWindow.IsNotNullOrDestroyed() && part.PartActionWindow.isActiveAndEnabled;
2423
}
2524
}
25+
26+
static class ParticleBuffer
27+
{
28+
private static NativeArray<ParticleSystem.Particle> particleBuffer = new NativeArray<ParticleSystem.Particle>(1000, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
29+
private static long particleSize = UnsafeUtility.SizeOf<ParticleSystem.Particle>();
30+
31+
/// <summary>
32+
/// Get a native array of active Particle in this ParticleSystem
33+
/// </summary>
34+
/// <param name="particleCount">The amount of particles in the system, usually ParticleSystem.particleCount. After returning, this will be the amount of active particles, which might be lower.</param>
35+
/// <returns></returns>
36+
public static NativeArray<ParticleSystem.Particle> GetParticlesNativeArray(this ParticleSystem particleSystem, ref int particleCount)
37+
{
38+
if (particleBuffer.Length < particleCount)
39+
{
40+
particleBuffer.Dispose();
41+
particleBuffer = new NativeArray<ParticleSystem.Particle>(particleCount * 2, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
42+
}
43+
particleCount = particleSystem.GetParticles(particleBuffer);
44+
return particleBuffer;
45+
}
46+
47+
/// <summary>
48+
/// Get the position of the particle at the specified index, avoiding to have to make copies of the (huge) particle struct
49+
/// </summary>
50+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
51+
public static unsafe Vector3 GetParticlePosition(this NativeArray<ParticleSystem.Particle> buffer, int particleIndex)
52+
{
53+
// note : the position Vector3 is the first field of the struct
54+
return *(Vector3*)((byte*)buffer.m_Buffer + particleIndex * particleSize);
55+
}
56+
57+
/// <summary>
58+
/// Set the position of the particle at the specified index, avoiding to have to make copies of the (huge) particle struct
59+
/// </summary>
60+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
61+
public static unsafe void SetParticlePosition(this NativeArray<ParticleSystem.Particle> buffer, int particleIndex, Vector3 position)
62+
{
63+
// note : the position Vector3 is the first field of the struct
64+
*(Vector3*)((byte*)buffer.m_Buffer + particleIndex * particleSize) = position;
65+
}
66+
}
2667
}
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
using KSPCommunityFixes.Library;
2+
using System;
3+
using System.Collections.Generic;
4+
using Unity.Collections;
5+
using UnityEngine;
6+
7+
namespace KSPCommunityFixes.Performance
8+
{
9+
internal class FloatingOriginPerf : BasePatch
10+
{
11+
protected override Version VersionMin => new Version(1, 12, 3);
12+
13+
protected override void ApplyPatches()
14+
{
15+
AddPatch(PatchType.Override, typeof(FloatingOrigin), nameof(FloatingOrigin.setOffset));
16+
}
17+
18+
private static HashSet<int> activePS = new HashSet<int>(200);
19+
20+
private static void FloatingOrigin_setOffset_Override(FloatingOrigin fo, Vector3d refPos, Vector3d nonFrame)
21+
{
22+
if (refPos.IsInvalid())
23+
return;
24+
25+
if (double.IsInfinity(refPos.sqrMagnitude))
26+
return;
27+
28+
fo.SetOffsetThisFrame = true;
29+
fo.offset = refPos;
30+
fo.reverseoffset = new Vector3d(0.0 - refPos.x, 0.0 - refPos.y, 0.0 - refPos.z);
31+
fo.offsetNonKrakensbane = fo.offset + nonFrame;
32+
33+
Vector3 offsetF = fo.offset;
34+
Vector3 offsetNonKrakensbaneF = fo.offsetNonKrakensbane;
35+
float deltaTime = Time.deltaTime;
36+
Vector3 frameVelocity = Krakensbane.GetFrameVelocity();
37+
38+
activePS.Clear();
39+
40+
List<CelestialBody> bodies = FlightGlobals.Bodies;
41+
for (int i = bodies.Count; i-- > 0;)
42+
bodies[i].position -= fo.offsetNonKrakensbane;
43+
44+
bool needCoMRecalc = fo.offset.sqrMagnitude > fo.CoMRecalcOffsetMaxSqr;
45+
List<Vessel> vessels = FlightGlobals.Vessels;
46+
47+
for (int i = vessels.Count; i-- > 0;)
48+
{
49+
Vessel vessel = vessels[i];
50+
51+
if (vessel.state == Vessel.State.DEAD)
52+
continue;
53+
54+
Vector3d vesselOffset = (!vessel.loaded || vessel.packed || vessel.LandedOrSplashed) ? fo.offsetNonKrakensbane : fo.offset;
55+
vessel.SetPosition((Vector3d)vessel.transform.position - vesselOffset);
56+
57+
if (needCoMRecalc && vessel.packed)
58+
{
59+
vessel.precalc.CalculatePhysicsStats();
60+
}
61+
else
62+
{
63+
vessel.CoMD -= vesselOffset;
64+
vessel.CoM = vessel.CoMD;
65+
}
66+
67+
// Update legacy (?) particle system
68+
for (int j = vessel.parts.Count; j-- > 0;)
69+
{
70+
Part part = vessel.parts[j];
71+
72+
if (part.fxGroups.Count == 0)
73+
continue;
74+
75+
bool partDataComputed = false;
76+
bool hasRigidbody = false;
77+
Vector3 partVelocity = Vector3.zero;
78+
79+
for (int k = part.fxGroups.Count; k-- > 0;)
80+
{
81+
FXGroup fXGroup = part.fxGroups[k];
82+
for (int l = fXGroup.fxEmittersNewSystem.Count; l-- > 0;)
83+
{
84+
ParticleSystem particleSystem = fXGroup.fxEmittersNewSystem[l];
85+
86+
if (particleSystem.IsNullOrDestroyed())
87+
continue;
88+
89+
int particleCount = particleSystem.particleCount;
90+
if (particleCount == 0 || particleSystem.main.simulationSpace != ParticleSystemSimulationSpace.World)
91+
continue;
92+
93+
activePS.Add(particleSystem.GetInstanceIDFast());
94+
95+
if (!partDataComputed)
96+
{
97+
partDataComputed = true;
98+
Rigidbody partRB = part.Rigidbody;
99+
if (partRB.IsNotNullOrDestroyed())
100+
{
101+
hasRigidbody = true;
102+
partVelocity = partRB.velocity + frameVelocity;
103+
}
104+
}
105+
106+
NativeArray<ParticleSystem.Particle> particleBuffer = particleSystem.GetParticlesNativeArray(ref particleCount);
107+
for (int pIdx = particleCount; pIdx-- > 0;)
108+
{
109+
Vector3 particlePos = particleBuffer.GetParticlePosition(pIdx);
110+
111+
if (hasRigidbody)
112+
{
113+
float scalar = UnityEngine.Random.value * deltaTime;
114+
particlePos.Substract(partVelocity.x * scalar, partVelocity.y * scalar, partVelocity.z * scalar);
115+
}
116+
117+
particlePos.Substract(offsetNonKrakensbaneF);
118+
particleBuffer.SetParticlePosition(pIdx, particlePos);
119+
}
120+
particleSystem.SetParticles(particleBuffer, particleCount);
121+
}
122+
}
123+
}
124+
}
125+
126+
// update "new" (but just as shitty) particle system (this replicate a call to EffectBehaviour.OffsetParticles())
127+
Vector3 systemVelocity = Vector3.zero;
128+
129+
List<ParticleSystem> pSystems = EffectBehaviour.emitters;
130+
for (int i = pSystems.Count; i-- > 0;)
131+
{
132+
ParticleSystem particleSystem = pSystems[i];
133+
134+
if (particleSystem.IsNullOrDestroyed())
135+
{
136+
pSystems.RemoveAt(i);
137+
continue;
138+
}
139+
140+
int particleCount = particleSystem.particleCount;
141+
if (particleCount == 0 || particleSystem.main.simulationSpace != ParticleSystemSimulationSpace.World)
142+
continue;
143+
144+
activePS.Add(particleSystem.GetInstanceIDFast());
145+
146+
bool hasRigidbody = false;
147+
Rigidbody rb = particleSystem.GetComponentInParent<Rigidbody>();
148+
if (rb.IsNotNullRef())
149+
{
150+
hasRigidbody = true;
151+
systemVelocity = rb.velocity + frameVelocity;
152+
}
153+
154+
NativeArray<ParticleSystem.Particle> particleBuffer = particleSystem.GetParticlesNativeArray(ref particleCount);
155+
for (int pIdx = particleCount; pIdx-- > 0;)
156+
{
157+
Vector3 particlePos = particleBuffer.GetParticlePosition(pIdx);
158+
159+
if (hasRigidbody)
160+
{
161+
float scalar = UnityEngine.Random.value * deltaTime;
162+
particlePos.Substract(systemVelocity.x * scalar, systemVelocity.y * scalar, systemVelocity.z * scalar);
163+
}
164+
165+
particlePos.Substract(offsetNonKrakensbaneF);
166+
particleBuffer.SetParticlePosition(pIdx, particlePos);
167+
}
168+
particleSystem.SetParticles(particleBuffer, particleCount);
169+
}
170+
171+
List<KSPParticleEmitter> pSystemsKSP = EffectBehaviour.kspEmitters;
172+
for (int i = pSystemsKSP.Count; i-- > 0;)
173+
{
174+
KSPParticleEmitter particleSystemKSP = pSystemsKSP[i];
175+
176+
if (particleSystemKSP.IsNullOrDestroyed())
177+
{
178+
pSystemsKSP.RemoveAt(i);
179+
continue;
180+
}
181+
182+
int particleCount = particleSystemKSP.ps.particleCount;
183+
if (particleCount == 0 || !particleSystemKSP.useWorldSpace)
184+
continue;
185+
186+
activePS.Add(particleSystemKSP.ps.GetInstanceIDFast());
187+
188+
bool hasRigidbody = false;
189+
Rigidbody rb = particleSystemKSP.GetComponentInParent<Rigidbody>();
190+
if (rb.IsNotNullRef())
191+
{
192+
hasRigidbody = true;
193+
systemVelocity = rb.velocity + frameVelocity;
194+
}
195+
196+
NativeArray<ParticleSystem.Particle> particleBuffer = particleSystemKSP.ps.GetParticlesNativeArray(ref particleCount);
197+
for (int pIdx = particleCount; pIdx-- > 0;)
198+
{
199+
Vector3 particlePos = particleBuffer.GetParticlePosition(pIdx);
200+
201+
if (hasRigidbody)
202+
{
203+
float scalar = UnityEngine.Random.value * deltaTime;
204+
particlePos.Substract(systemVelocity.x * scalar, systemVelocity.y * scalar, systemVelocity.z * scalar);
205+
}
206+
207+
particlePos.Substract(offsetNonKrakensbaneF);
208+
particleBuffer.SetParticlePosition(pIdx, particlePos);
209+
}
210+
particleSystemKSP.ps.SetParticles(particleBuffer, particleCount);
211+
}
212+
213+
// Just have another handling of the same stuff, sometimes overlapping, sometimes not, because why not ?
214+
for (int i = fo.particleSystems.Count; i-- > 0;)
215+
{
216+
ParticleSystem particleSystem = fo.particleSystems[i];
217+
if (particleSystem.IsNullOrDestroyed() || activePS.Contains(particleSystem.GetInstanceIDFast()))
218+
{
219+
fo.particleSystems.RemoveAt(i);
220+
continue;
221+
}
222+
223+
int particleCount = particleSystem.particleCount;
224+
if (particleCount == 0)
225+
continue;
226+
227+
if (particleSystem.main.simulationSpace != ParticleSystemSimulationSpace.World)
228+
continue;
229+
230+
if (activePS.Contains(particleSystem.GetInstanceIDFast()))
231+
{
232+
fo.particleSystems.RemoveAt(i);
233+
continue;
234+
}
235+
236+
NativeArray<ParticleSystem.Particle> particleBuffer = particleSystem.GetParticlesNativeArray(ref particleCount);
237+
for (int pIdx = particleCount; pIdx-- > 0;)
238+
{
239+
Vector3 particlePos = particleBuffer.GetParticlePosition(pIdx);
240+
particlePos.Substract(offsetNonKrakensbaneF);
241+
particleBuffer.SetParticlePosition(pIdx, particlePos);
242+
}
243+
244+
particleSystem.SetParticles(particleBuffer, particleCount);
245+
}
246+
247+
// more particle system (explosions, fireworks...) moving in here, but this is getting silly, I don't care anymore...
248+
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.radarAltitude < fo.altToStopMovingExplosions)
249+
FXMonger.OffsetPositions(-fo.offsetNonKrakensbane);
250+
251+
for (int i = FlightGlobals.physicalObjects.Count; i-- > 0;)
252+
{
253+
physicalObject physicalObject = FlightGlobals.physicalObjects[i];
254+
if (physicalObject.IsNotNullOrDestroyed())
255+
{
256+
Transform obj = physicalObject.transform;
257+
obj.position -= offsetF;
258+
}
259+
}
260+
261+
FloatingOrigin.TerrainShaderOffset += fo.offsetNonKrakensbane;
262+
GameEvents.onFloatingOriginShift.Fire(fo.offset, nonFrame);
263+
}
264+
}
265+
}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ User options are available from the "ESC" in-game settings menu :<br/><img src="
139139
- [**CollisionEnhancerFastUpdate**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/257) [KSP 1.12.3 - 1.12.5]<br/>Optimization of the `CollisionEnhancer` component (responsible for part to terrain collision detection).
140140
- [**PartSystemsFastUpdate**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/257) [KSP 1.12.3 - 1.12.5]<br/>Optimization of various flight scene auxiliary subsystems : temperature gauges, highlighter, strut position tracking...
141141
- [**MinorPerfTweaks**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/257) [KSP 1.12.3 - 1.12.5]<br/>Various small performance patches (volume normalizer, eva module checks)
142+
- [**FloatingOriginPerf**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/257) [KSP 1.12.3 - 1.12.5]<br/>General micro-optimization of floating origin shifts. Main benefit is in large particle count situations (ie, launches with many engines) but this helps a bit in other cases as well.
142143
- [**FasterPartFindTransform**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/255) [KSP 1.12.3 - 1.12.5]<br/>Faster, and minimal GC alloc relacements for the Part FindModelTransform* and FindHeirarchyTransform* methods.
143144

144145
#### API and modding tools
@@ -209,6 +210,7 @@ If doing so in the `Debug` configuration and if your KSP install is modified to
209210
- **CollisionEnhancerFastUpdate** : Optimization of the `CollisionEnhancer` component (responsible for part to terrain collision detection).
210211
- **PartSystemsFastUpdate** : Optimization of various flight scene auxiliary subsystems : temperature gauges, highlighter, strut position tracking...
211212
- **MinorPerfTweaks** : Various small performance patches (volume normalizer, eva module checks)
213+
- **FloatingOriginPerf** : General micro-optimization of floating origin shifts. Main benefit is in large particle count situations (ie, launches with many engines) but this helps a bit in other cases as well.
212214
- New KSP bufix : [**DragCubeLoadException**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/232) [KSP 1.8.0 - 1.12.5] : Fix loading of drag cubes without a name failing with an IndexOutOfRangeException (contributed by @Nazfib).
213215
- New KSP bufix : [**TimeWarpBodyCollision**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/259) [KSP 1.12.0 - 1.12.5] : Fix timewarp rate not always being limited on SOI transistions, sometimes resulting in failure to detect an encounter/collision with the body in the next SOI (contributed by @JonnyOThan).
214216
- New modding API improvement : [**KSPFieldEnumDesc**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/243) [KSP 1.12.2 - 1.12.5] : Disabled by default, you can enable it with a MM patch. Adds display name and localization support for enum KSPFields. To use add `Description` attribute to the field (contributed by @siimav).

0 commit comments

Comments
 (0)