Skip to content

Commit 5367575

Browse files
authored
Optimise VectorLine camera projection for improved Map View performance (#281)
This patch improves performance by caching the camera's projection matrix multiplied with its view matrix once per frame, and replacing calls to different camera projection functions in VectorLine with our cached versions.
1 parent 8a44584 commit 5367575

File tree

6 files changed

+273
-0
lines changed

6 files changed

+273
-0
lines changed

GameData/KSPCommunityFixes/Settings.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,10 @@ KSP_COMMUNITY_FIXES
467467
// General micro-optimization of floating origin shifts. Main benefit is in large particle count situations
468468
// but this helps a bit in other cases as well.
469469
FloatingOriginPerf = true
470+
471+
// Improve performance in the Map View when a large number of vessels and bodies are visible via faster drawing
472+
// of orbit lines and CommNet lines.
473+
OptimisedVectorLines = true
470474

471475
PartParsingPerf = true
472476

KSPCommunityFixes/KSPCommunityFixes.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public class KSPCommunityFixes : MonoBehaviour
2525

2626
public static long FixedUpdateCount { get; private set; }
2727

28+
// Frame counter that doesn't use a call to C++ like Time.frameCount.
29+
public static long UpdateCount { get; private set; }
30+
2831
private static string modPath;
2932
public static string ModPath
3033
{
@@ -133,5 +136,10 @@ void FixedUpdate()
133136
{
134137
FixedUpdateCount++;
135138
}
139+
140+
void Update()
141+
{
142+
UpdateCount++;
143+
}
136144
}
137145
}

KSPCommunityFixes/KSPCommunityFixes.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
<Compile Include="BugFixes\DoubleCurvePreserveTangents.cs" />
166166
<Compile Include="BugFixes\RestoreMaxPhysicsDT.cs" />
167167
<Compile Include="Performance\FloatingOriginPerf.cs" />
168+
<Compile Include="Performance\OptimisedVectorLines.cs" />
168169
<Compile Include="Performance\GameDatabasePerf.cs" />
169170
<Compile Include="Performance\PartSystemsFastUpdate.cs" />
170171
<Compile Include="Performance\CollisionEnhancerFastUpdate.cs" />

KSPCommunityFixes/Library/Numerics.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,19 @@ public void MutateMultiplyPoint3x4(ref Vector3d point)
719719
point.z = m20 * x + m21 * y + m22 * z + m23;
720720
}
721721

722+
/// <summary>
723+
/// Transform point, slightly faster than using a Vector3d.
724+
/// </summary>
725+
public void MutateMultiplyPoint3x4(ref double x, ref double y, ref double z)
726+
{
727+
double x1 = x;
728+
double y1 = y;
729+
double z1 = z;
730+
x = m00 * x1 + m01 * y1 + m02 * z1 + m03;
731+
y = m10 * x1 + m11 * y1 + m12 * z1 + m13;
732+
z = m20 * x1 + m21 * y1 + m22 * z1 + m23;
733+
}
734+
722735
/// <summary>
723736
/// Transform point
724737
/// </summary>
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
using HarmonyLib;
2+
using KSPCommunityFixes.Library;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Reflection;
6+
using System.Reflection.Emit;
7+
using UnityEngine;
8+
using Vectrosity;
9+
10+
namespace KSPCommunityFixes.Performance
11+
{
12+
public class OptimisedVectorLines : BasePatch
13+
{
14+
protected override Version VersionMin => new Version(1, 12, 0);
15+
16+
protected override void ApplyPatches()
17+
{
18+
AddPatch(PatchType.Transpiler, typeof(VectorLine), nameof(VectorLine.Line3D));
19+
20+
AddPatch(PatchType.Transpiler, typeof(VectorLine), nameof(VectorLine.BehindCamera));
21+
AddPatch(PatchType.Transpiler, typeof(VectorLine), nameof(VectorLine.IntersectAndDoSkip));
22+
23+
AddPatch(PatchType.Transpiler, typeof(VectorLine), nameof(VectorLine.Draw3D));
24+
}
25+
26+
#region VectorLine Patches
27+
28+
static IEnumerable<CodeInstruction> VectorLine_Line3D_Transpiler(IEnumerable<CodeInstruction> instructions) =>
29+
ReplaceWorldToScreenPoint(instructions, 2);
30+
31+
static IEnumerable<CodeInstruction> VectorLine_BehindCamera_Transpiler(IEnumerable<CodeInstruction> instructions) =>
32+
ReplaceWorldToViewportPoint(instructions, 2);
33+
34+
static IEnumerable<CodeInstruction> VectorLine_IntersectAndDoSkip_Transpiler(IEnumerable<CodeInstruction> instructions) =>
35+
ReplaceWorldToScreenPoint(instructions, 2);
36+
37+
static IEnumerable<CodeInstruction> VectorLine_Draw3D_Transpiler(IEnumerable<CodeInstruction> instructions)
38+
{
39+
instructions = ReplaceWorldToScreenPoint(instructions, 2);
40+
instructions = ReplaceScreenToWorldPoint(instructions, 4);
41+
42+
return instructions;
43+
}
44+
45+
static IEnumerable<CodeInstruction> VectorLine_SetIntersectionPoint3D_Transpiler(IEnumerable<CodeInstruction> instructions)
46+
{
47+
return ReplaceScreenToWorldPoint(instructions, 2);
48+
}
49+
50+
private static IEnumerable<CodeInstruction> ReplaceCall(IEnumerable<CodeInstruction> instructions, MethodInfo original, MethodInfo replacement, int count = 1)
51+
{
52+
List<CodeInstruction> code = new List<CodeInstruction>(instructions);
53+
int counter = 0;
54+
55+
for (int i = 0; i < code.Count; i++)
56+
{
57+
if (code[i].opcode == OpCodes.Callvirt && code[i].Calls(original))
58+
{
59+
code[i].opcode = OpCodes.Call;
60+
code[i].operand = replacement;
61+
62+
if (++counter == count)
63+
break;
64+
}
65+
}
66+
67+
return code;
68+
}
69+
70+
private static IEnumerable<CodeInstruction> ReplaceWorldToViewportPoint(IEnumerable<CodeInstruction> instructions, int count)
71+
{
72+
MethodInfo Camera_WorldToViewportPoint = AccessTools.Method(typeof(Camera), nameof(Camera.WorldToViewportPoint), new Type[] { typeof(Vector3) });
73+
MethodInfo VectorLineOptimisation_WorldToViewportPoint = AccessTools.Method(typeof(VectorLineCameraProjection), nameof(VectorLineCameraProjection.WorldToViewportPoint));
74+
75+
return ReplaceCall(instructions, Camera_WorldToViewportPoint, VectorLineOptimisation_WorldToViewportPoint, count);
76+
}
77+
78+
private static IEnumerable<CodeInstruction> ReplaceWorldToScreenPoint(IEnumerable<CodeInstruction> instructions, int count)
79+
{
80+
MethodInfo Camera_WorldToScreenPoint = AccessTools.Method(typeof(Camera), nameof(Camera.WorldToScreenPoint), new Type[] { typeof(Vector3) });
81+
MethodInfo VectorLineOptimisation_WorldToScreenPoint = AccessTools.Method(typeof(VectorLineCameraProjection), nameof(VectorLineCameraProjection.WorldToScreenPoint));
82+
83+
return ReplaceCall(instructions, Camera_WorldToScreenPoint, VectorLineOptimisation_WorldToScreenPoint, count);
84+
}
85+
86+
private static IEnumerable<CodeInstruction> ReplaceScreenToWorldPoint(IEnumerable<CodeInstruction> instructions, int count)
87+
{
88+
MethodInfo Camera_ScreenToWorldPoint = AccessTools.Method(typeof(Camera), nameof(Camera.ScreenToWorldPoint), new Type[] { typeof(Vector3) });
89+
MethodInfo VectorLineOptimisation_ScreenToWorldPoint = AccessTools.Method(typeof(VectorLineCameraProjection), nameof(VectorLineCameraProjection.ScreenToWorldPoint));
90+
91+
return ReplaceCall(instructions, Camera_ScreenToWorldPoint, VectorLineOptimisation_ScreenToWorldPoint, count);
92+
}
93+
94+
private static IEnumerable<CodeInstruction> ReplaceScreenToViewportPoint(IEnumerable<CodeInstruction> instructions, int count)
95+
{
96+
MethodInfo Camera_ScreenToViewportPoint = AccessTools.Method(typeof(Camera), nameof(Camera.ScreenToViewportPoint), new Type[] { typeof(Vector3) });
97+
MethodInfo VectorLineOptimisation_ScreenToViewportPoint = AccessTools.Method(typeof(VectorLineCameraProjection), nameof(VectorLineCameraProjection.ScreenToViewportPoint));
98+
99+
return ReplaceCall(instructions, Camera_ScreenToViewportPoint, VectorLineOptimisation_ScreenToViewportPoint, count);
100+
}
101+
102+
#endregion
103+
}
104+
105+
public static class VectorLineCameraProjection
106+
{
107+
// Based on CameraProjectionCache from UnityCsReference.
108+
// https://github.com/Unity-Technologies/UnityCsReference/blob/2019.4/Editor/Mono/Camera/CameraProjectionCache.cs
109+
110+
public static bool patchEnabled = true;
111+
public static long lastCachedFrame;
112+
113+
private static TransformMatrix worldToClip;
114+
private static TransformMatrix clipToWorld;
115+
116+
// Storing viewport info instead of using Rect properties grants us a few extra frames.
117+
public struct ViewportInfo
118+
{
119+
public double halfWidth;
120+
public double halfHeight;
121+
public double width;
122+
public double height;
123+
public double x;
124+
public double y;
125+
126+
public ViewportInfo(Rect viewport)
127+
{
128+
width = viewport.width;
129+
height = viewport.height;
130+
halfWidth = width * 0.5;
131+
halfHeight = height * 0.5;
132+
x = viewport.x;
133+
y = viewport.y;
134+
}
135+
}
136+
137+
private static ViewportInfo viewport;
138+
139+
private static void UpdateCache()
140+
{
141+
lastCachedFrame = KSPCommunityFixes.UpdateCount;
142+
Camera camera = VectorLine.cam3D;
143+
144+
viewport = new ViewportInfo(camera.pixelRect);
145+
146+
Matrix4x4 worldToClip = camera.projectionMatrix * camera.worldToCameraMatrix;
147+
VectorLineCameraProjection.worldToClip = new TransformMatrix(ref worldToClip);
148+
149+
Matrix4x4 worldToCameraInv = camera.worldToCameraMatrix.inverse;
150+
Matrix4x4 projectionInv = camera.projectionMatrix.inverse;
151+
projectionInv.m02 += projectionInv.m03;
152+
projectionInv.m12 += projectionInv.m13;
153+
projectionInv.m22 += projectionInv.m23;
154+
155+
Matrix4x4 clipToWorld = worldToCameraInv * projectionInv;
156+
VectorLineCameraProjection.clipToWorld = new TransformMatrix(clipToWorld.m00, clipToWorld.m01, clipToWorld.m02, camera.worldToCameraMatrix.inverse.m03,
157+
clipToWorld.m10, clipToWorld.m11, clipToWorld.m12, camera.worldToCameraMatrix.inverse.m13,
158+
clipToWorld.m20, clipToWorld.m21, clipToWorld.m22, camera.worldToCameraMatrix.inverse.m23);
159+
}
160+
161+
#region World to Clip
162+
163+
public static Vector3 WorldToScreenPoint(Camera camera, Vector3 worldPosition)
164+
{
165+
// These patchEnabled checks are commented out atm in case they affect performance.
166+
// For testing they can be re-enabled and patchEnabled edited in UnityExplorer.
167+
168+
//if (!patchEnabled)
169+
// return camera.WorldToScreenPoint(worldPosition);
170+
171+
if (lastCachedFrame != KSPCommunityFixes.UpdateCount)
172+
UpdateCache();
173+
174+
double x = worldPosition.x;
175+
double y = worldPosition.y;
176+
double z = worldPosition.z;
177+
178+
worldToClip.MutateMultiplyPoint3x4(ref x, ref y, ref z);
179+
180+
double num = 0.5 / z;
181+
x = (0.5 + num * x) * viewport.width + viewport.x;
182+
y = (0.5 + num * y) * viewport.height + viewport.y;
183+
184+
return new Vector3((float)x, (float)y, (float)z);
185+
}
186+
187+
public static Vector3 WorldToViewportPoint(Camera camera, Vector3 worldPosition)
188+
{
189+
//if (!patchEnabled)
190+
// return camera.WorldToViewportPoint(worldPosition);
191+
192+
if (lastCachedFrame != KSPCommunityFixes.UpdateCount)
193+
UpdateCache();
194+
195+
double x = worldPosition.x;
196+
double y = worldPosition.y;
197+
double z = worldPosition.z;
198+
199+
worldToClip.MutateMultiplyPoint3x4(ref x, ref y, ref z);
200+
201+
double num = 0.5 / z;
202+
x = 0.5 + num * x;
203+
y = 0.5 + num * y;
204+
205+
return new Vector3((float)x, (float)y, (float)z);
206+
}
207+
208+
#endregion
209+
210+
#region Clip to World
211+
212+
public static Vector3 ScreenToWorldPoint(Camera camera, Vector3 screenPosition)
213+
{
214+
//if (!patchEnabled)
215+
// return camera.ScreenToWorldPoint(screenPosition);
216+
217+
if (lastCachedFrame != KSPCommunityFixes.UpdateCount)
218+
UpdateCache();
219+
220+
double x = screenPosition.x;
221+
double y = screenPosition.y;
222+
double z = screenPosition.z;
223+
224+
x = z * ((x - viewport.x) / viewport.halfWidth - 1);
225+
y = z * ((y - viewport.y) / viewport.halfHeight - 1);
226+
227+
clipToWorld.MutateMultiplyPoint3x4(ref x, ref y, ref z);
228+
229+
return new Vector3((float)x, (float)y, (float)z);
230+
}
231+
232+
public static Vector3 ScreenToViewportPoint(Camera camera, Vector3 position)
233+
{
234+
//if (!patchEnabled)
235+
// return camera.ScreenToViewportPoint(position);
236+
237+
//if (lastCachedFrame != KSPCommunityFixes.frameCount)
238+
// UpdateCache();
239+
240+
// Not used by VectorLine.
241+
throw new NotImplementedException();
242+
}
243+
244+
#endregion
245+
}
246+
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ User options are available from the "ESC" in-game settings menu :<br/><img src="
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)
142142
- [**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.
143143
- [**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.
144+
- [**OptimisedVectorLines**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/281) [KSP 1.12.0 - 1.12.5]<br/>Improve performance in the Map View when a large number of vessels and bodies are visible via faster drawing of orbit lines and CommNet lines.
144145

145146
#### API and modding tools
146147
- **MultipleModuleInPartAPI** [KSP 1.8.0 - 1.12.5]<br/>This API allow other plugins to implement PartModules that can exist in multiple occurrence in a single part and won't suffer "module indexing mismatch" persistent data losses following part configuration changes. [See documentation on the wiki](https://github.com/KSPModdingLibs/KSPCommunityFixes/wiki/MultipleModuleInPartAPI).

0 commit comments

Comments
 (0)