Skip to content

Commit

Permalink
add FloatingOriginPerf patch (by gotmachine)
Browse files Browse the repository at this point in the history
  • Loading branch information
JonnyOThan committed Oct 14, 2024
1 parent 281a6df commit 35ee370
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 4 deletions.
4 changes: 4 additions & 0 deletions GameData/KSPCommunityFixes/Settings.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,10 @@ KSP_COMMUNITY_FIXES
// framerate in large part count situations.
FlightPerf = true
// General micro-optimization of floating origin shifts. Main benefit is in large particle count situations
// but this helps a bit in other cases as well.
FloatingOriginPerf = true
// ##########################
// Modding
// ##########################
Expand Down
1 change: 1 addition & 0 deletions KSPCommunityFixes/KSPCommunityFixes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
<Compile Include="Performance\AsteroidAndCometDrillCache.cs" />
<Compile Include="BugFixes\DoubleCurvePreserveTangents.cs" />
<Compile Include="BugFixes\RestoreMaxPhysicsDT.cs" />
<Compile Include="Performance\FloatingOriginPerf.cs" />
<Compile Include="Performance\PartSystemsFastUpdate.cs" />
<Compile Include="Performance\CollisionEnhancerFastUpdate.cs" />
<Compile Include="Performance\CollisionManagerFastUpdate.cs" />
Expand Down
49 changes: 45 additions & 4 deletions KSPCommunityFixes/Library/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;

namespace KSPCommunityFixes
Expand All @@ -23,4 +22,46 @@ public static bool IsPAWOpen(this Part part)
return part.PartActionWindow.IsNotNullOrDestroyed() && part.PartActionWindow.isActiveAndEnabled;
}
}

static class ParticleBuffer
{
private static NativeArray<ParticleSystem.Particle> particleBuffer = new NativeArray<ParticleSystem.Particle>(1000, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
private static long particleSize = UnsafeUtility.SizeOf<ParticleSystem.Particle>();

/// <summary>
/// Get a native array of active Particle in this ParticleSystem
/// </summary>
/// <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>
/// <returns></returns>
public static NativeArray<ParticleSystem.Particle> GetParticlesNativeArray(this ParticleSystem particleSystem, ref int particleCount)
{
if (particleBuffer.Length < particleCount)
{
particleBuffer.Dispose();
particleBuffer = new NativeArray<ParticleSystem.Particle>(particleCount * 2, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
}
particleCount = particleSystem.GetParticles(particleBuffer);
return particleBuffer;
}

/// <summary>
/// Get the position of the particle at the specified index, avoiding to have to make copies of the (huge) particle struct
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe Vector3 GetParticlePosition(this NativeArray<ParticleSystem.Particle> buffer, int particleIndex)
{
// note : the position Vector3 is the first field of the struct
return *(Vector3*)((byte*)buffer.m_Buffer + particleIndex * particleSize);
}

/// <summary>
/// Set the position of the particle at the specified index, avoiding to have to make copies of the (huge) particle struct
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void SetParticlePosition(this NativeArray<ParticleSystem.Particle> buffer, int particleIndex, Vector3 position)
{
// note : the position Vector3 is the first field of the struct
*(Vector3*)((byte*)buffer.m_Buffer + particleIndex * particleSize) = position;
}
}
}
262 changes: 262 additions & 0 deletions KSPCommunityFixes/Performance/FloatingOriginPerf.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
using KSPCommunityFixes.Library;
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;

namespace KSPCommunityFixes.Performance
{
internal class FloatingOriginPerf : BasePatch
{
protected override Version VersionMin => new Version(1, 12, 3);

protected override void ApplyPatches()
{
AddPatch(PatchType.Override, typeof(FloatingOrigin), nameof(FloatingOrigin.setOffset));
}

private static HashSet<int> activePS = new HashSet<int>(200);

private static void FloatingOrigin_setOffset_Override(FloatingOrigin fo, Vector3d refPos, Vector3d nonFrame)
{
if (refPos.IsInvalid())
return;

if (double.IsInfinity(refPos.sqrMagnitude))
return;

fo.SetOffsetThisFrame = true;
fo.offset = refPos;
fo.reverseoffset = new Vector3d(0.0 - refPos.x, 0.0 - refPos.y, 0.0 - refPos.z);
fo.offsetNonKrakensbane = fo.offset + nonFrame;

Vector3 offsetF = fo.offset;
Vector3 offsetNonKrakensbaneF = fo.offsetNonKrakensbane;
float deltaTime = Time.deltaTime;
Vector3 frameVelocity = Krakensbane.GetFrameVelocity();

activePS.Clear();

List<CelestialBody> bodies = FlightGlobals.Bodies;
for (int i = bodies.Count; i-- > 0;)
bodies[i].position -= fo.offsetNonKrakensbane;

bool needCoMRecalc = fo.offset.sqrMagnitude > fo.CoMRecalcOffsetMaxSqr;
List<Vessel> vessels = FlightGlobals.Vessels;

for (int i = vessels.Count; i-- > 0;)
{
Vessel vessel = vessels[i];

if (vessel.state == Vessel.State.DEAD)
continue;

Vector3d vesselOffset = (!vessel.loaded || vessel.packed || vessel.LandedOrSplashed) ? fo.offsetNonKrakensbane : fo.offset;
vessel.SetPosition((Vector3d)vessel.transform.position - vesselOffset);

if (needCoMRecalc && vessel.packed)
{
vessel.precalc.CalculatePhysicsStats();
}
else
{
vessel.CoMD -= vesselOffset;
vessel.CoM = vessel.CoMD;
}

// Update legacy (?) particle system
for (int j = vessel.parts.Count; j-- > 0;)
{
Part part = vessel.parts[j];

if (part.fxGroups.Count == 0)
continue;

bool partDataComputed = false;
bool hasRigidbody = false;
Vector3 partVelocity = Vector3.zero;

for (int k = part.fxGroups.Count; k-- > 0;)
{
FXGroup fXGroup = part.fxGroups[k];
for (int l = fXGroup.fxEmittersNewSystem.Count; l-- > 0;)
{
ParticleSystem particleSystem = fXGroup.fxEmittersNewSystem[l];

int particleCount = particleSystem.particleCount;
if (particleCount == 0 || particleSystem.main.simulationSpace != ParticleSystemSimulationSpace.World)
continue;

activePS.Add(particleSystem.GetInstanceIDFast());

if (!partDataComputed)
{
partDataComputed = true;
Rigidbody partRB = part.Rigidbody;
if (partRB.IsNotNullOrDestroyed())
{
hasRigidbody = true;
partVelocity = partRB.velocity + frameVelocity;
}
}

NativeArray<ParticleSystem.Particle> particleBuffer = particleSystem.GetParticlesNativeArray(ref particleCount);
for (int pIdx = particleCount; pIdx-- > 0;)
{
Vector3 particlePos = particleBuffer.GetParticlePosition(pIdx);

if (hasRigidbody)
{
float scalar = UnityEngine.Random.value * deltaTime;
particlePos.Substract(partVelocity.x * scalar, partVelocity.y * scalar, partVelocity.z * scalar);
}

particlePos.Substract(offsetNonKrakensbaneF);
particleBuffer.SetParticlePosition(pIdx, particlePos);
}
particleSystem.SetParticles(particleBuffer, particleCount);
}
}
}
}

// update "new" (but just as shitty) particle system (this replicate a call to EffectBehaviour.OffsetParticles())
Vector3 systemVelocity = Vector3.zero;

List<ParticleSystem> pSystems = EffectBehaviour.emitters;
for (int i = pSystems.Count; i-- > 0;)
{
ParticleSystem particleSystem = pSystems[i];

if (particleSystem.IsNullOrDestroyed())
{
pSystems.RemoveAt(i);
continue;
}

int particleCount = particleSystem.particleCount;
if (particleCount == 0 || particleSystem.main.simulationSpace != ParticleSystemSimulationSpace.World)
continue;

activePS.Add(particleSystem.GetInstanceIDFast());

bool hasRigidbody = false;
Rigidbody rb = particleSystem.GetComponentInParent<Rigidbody>();
if (rb.IsNotNullRef())
{
hasRigidbody = true;
systemVelocity = rb.velocity + frameVelocity;
}

NativeArray<ParticleSystem.Particle> particleBuffer = particleSystem.GetParticlesNativeArray(ref particleCount);
for (int pIdx = particleCount; pIdx-- > 0;)
{
Vector3 particlePos = particleBuffer.GetParticlePosition(pIdx);

if (hasRigidbody)
{
float scalar = UnityEngine.Random.value * deltaTime;
particlePos.Substract(systemVelocity.x * scalar, systemVelocity.y * scalar, systemVelocity.z * scalar);
}

particlePos.Substract(offsetNonKrakensbaneF);
particleBuffer.SetParticlePosition(pIdx, particlePos);
}
particleSystem.SetParticles(particleBuffer, particleCount);
}

List<KSPParticleEmitter> pSystemsKSP = EffectBehaviour.kspEmitters;
for (int i = pSystemsKSP.Count; i-- > 0;)
{
KSPParticleEmitter particleSystemKSP = pSystemsKSP[i];

if (particleSystemKSP.IsNullOrDestroyed())
{
pSystemsKSP.RemoveAt(i);
continue;
}

int particleCount = particleSystemKSP.ps.particleCount;
if (particleCount == 0 || !particleSystemKSP.useWorldSpace)
continue;

activePS.Add(particleSystemKSP.ps.GetInstanceIDFast());

bool hasRigidbody = false;
Rigidbody rb = particleSystemKSP.GetComponentInParent<Rigidbody>();
if (rb.IsNotNullRef())
{
hasRigidbody = true;
systemVelocity = rb.velocity + frameVelocity;
}

NativeArray<ParticleSystem.Particle> particleBuffer = particleSystemKSP.ps.GetParticlesNativeArray(ref particleCount);
for (int pIdx = particleCount; pIdx-- > 0;)
{
Vector3 particlePos = particleBuffer.GetParticlePosition(pIdx);

if (hasRigidbody)
{
float scalar = UnityEngine.Random.value * deltaTime;
particlePos.Substract(systemVelocity.x * scalar, systemVelocity.y * scalar, systemVelocity.z * scalar);
}

particlePos.Substract(offsetNonKrakensbaneF);
particleBuffer.SetParticlePosition(pIdx, particlePos);
}
particleSystemKSP.ps.SetParticles(particleBuffer, particleCount);
}

// Just have another handling of the same stuff, sometimes overlapping, sometimes not, because why not ?
for (int i = fo.particleSystems.Count; i-- > 0;)
{
ParticleSystem particleSystem = fo.particleSystems[i];
if (particleSystem.IsNullOrDestroyed() || activePS.Contains(particleSystem.GetInstanceIDFast()))
{
fo.particleSystems.RemoveAt(i);
continue;
}

int particleCount = particleSystem.particleCount;
if (particleCount == 0)
continue;

if (particleSystem.main.simulationSpace != ParticleSystemSimulationSpace.World)
continue;

if (activePS.Contains(particleSystem.GetInstanceIDFast()))
{
fo.particleSystems.RemoveAt(i);
continue;
}

NativeArray<ParticleSystem.Particle> particleBuffer = particleSystem.GetParticlesNativeArray(ref particleCount);
for (int pIdx = particleCount; pIdx-- > 0;)
{
Vector3 particlePos = particleBuffer.GetParticlePosition(pIdx);
particlePos.Substract(offsetNonKrakensbaneF);
particleBuffer.SetParticlePosition(pIdx, particlePos);
}

particleSystem.SetParticles(particleBuffer, particleCount);
}

// more particle system (explosions, fireworks...) moving in here, but this is getting silly, I don't care anymore...
if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.radarAltitude < fo.altToStopMovingExplosions)
FXMonger.OffsetPositions(-fo.offsetNonKrakensbane);

for (int i = FlightGlobals.physicalObjects.Count; i-- > 0;)
{
physicalObject physicalObject = FlightGlobals.physicalObjects[i];
if (physicalObject.IsNotNullOrDestroyed())
{
Transform obj = physicalObject.transform;
obj.position -= offsetF;
}
}

FloatingOrigin.TerrainShaderOffset += fo.offsetNonKrakensbane;
GameEvents.onFloatingOriginShift.Fire(fo.offset, nonFrame);
}
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ User options are available from the "ESC" in-game settings menu :<br/><img src="
- [**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).
- [**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...
- [**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)
- [**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.
- [**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.

#### API and modding tools
Expand Down

0 comments on commit 35ee370

Please sign in to comment.