Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimise VectorLine camera projection for improved Map View performance #281

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
4 changes: 4 additions & 0 deletions GameData/KSPCommunityFixes/Settings.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,10 @@ KSP_COMMUNITY_FIXES
// 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

// 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.
OptimisedVectorLines = true

// ##########################
// Modding
Expand Down
8 changes: 8 additions & 0 deletions KSPCommunityFixes/KSPCommunityFixes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public class KSPCommunityFixes : MonoBehaviour

public static long FixedUpdateCount { get; private set; }

// Frame counter that doesn't use an external call like Time.frameCount.
public static long frameCount;
Halbann marked this conversation as resolved.
Show resolved Hide resolved

private static string modPath;
public static string ModPath
{
Expand Down Expand Up @@ -132,5 +135,10 @@ void FixedUpdate()
{
FixedUpdateCount++;
}

void Update()
{
frameCount++;
}
}
}
3 changes: 2 additions & 1 deletion KSPCommunityFixes/KSPCommunityFixes.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
gotmachine marked this conversation as resolved.
Show resolved Hide resolved
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Krafs.Publicizer.2.2.1\build\Krafs.Publicizer.props" Condition="Exists('..\packages\Krafs.Publicizer.2.2.1\build\Krafs.Publicizer.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
Expand Down Expand Up @@ -162,6 +162,7 @@
<Compile Include="BugFixes\DoubleCurvePreserveTangents.cs" />
<Compile Include="BugFixes\RestoreMaxPhysicsDT.cs" />
<Compile Include="Performance\FloatingOriginPerf.cs" />
<Compile Include="Performance\OptimisedVectorLines.cs" />
<Compile Include="Performance\PartSystemsFastUpdate.cs" />
<Compile Include="Performance\CollisionEnhancerFastUpdate.cs" />
<Compile Include="Performance\CollisionManagerFastUpdate.cs" />
Expand Down
13 changes: 13 additions & 0 deletions KSPCommunityFixes/Library/Numerics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,19 @@ public void MutateMultiplyPoint3x4(ref Vector3d point)
point.z = m20 * x + m21 * y + m22 * z + m23;
}

/// <summary>
/// Transform point, slightly faster than using a Vector3d.
/// </summary>
public void MutateMultiplyPoint3x4(ref double x, ref double y, ref double z)
{
double x1 = x;
double y1 = y;
double z1 = z;
x = m00 * x1 + m01 * y1 + m02 * z1 + m03;
y = m10 * x1 + m11 * y1 + m12 * z1 + m13;
z = m20 * x1 + m21 * y1 + m22 * z1 + m23;
}

/// <summary>
/// Transform point
/// </summary>
Expand Down
246 changes: 246 additions & 0 deletions KSPCommunityFixes/Performance/OptimisedVectorLines.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
using HarmonyLib;
using KSPCommunityFixes.Library;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;
using Vectrosity;

namespace KSPCommunityFixes.Performance
{
public class OptimisedVectorLines : BasePatch
{
protected override Version VersionMin => new Version(1, 12, 0);

protected override void ApplyPatches()
{
AddPatch(PatchType.Transpiler, typeof(VectorLine), nameof(VectorLine.Line3D));

AddPatch(PatchType.Transpiler, typeof(VectorLine), nameof(VectorLine.BehindCamera));
AddPatch(PatchType.Transpiler, typeof(VectorLine), nameof(VectorLine.IntersectAndDoSkip));

AddPatch(PatchType.Transpiler, typeof(VectorLine), nameof(VectorLine.Draw3D));
}

#region VectorLine Patches

static IEnumerable<CodeInstruction> VectorLine_Line3D_Transpiler(IEnumerable<CodeInstruction> instructions) =>
ReplaceWorldToScreenPoint(instructions, 2);

static IEnumerable<CodeInstruction> VectorLine_BehindCamera_Transpiler(IEnumerable<CodeInstruction> instructions) =>
ReplaceWorldToViewportPoint(instructions, 2);

static IEnumerable<CodeInstruction> VectorLine_IntersectAndDoSkip_Transpiler(IEnumerable<CodeInstruction> instructions) =>
ReplaceWorldToScreenPoint(instructions, 2);

static IEnumerable<CodeInstruction> VectorLine_Draw3D_Transpiler(IEnumerable<CodeInstruction> instructions)
{
instructions = ReplaceWorldToScreenPoint(instructions, 2);
instructions = ReplaceScreenToWorldPoint(instructions, 4);

return instructions;
}

static IEnumerable<CodeInstruction> VectorLine_SetIntersectionPoint3D_Transpiler(IEnumerable<CodeInstruction> instructions)
{
return ReplaceScreenToWorldPoint(instructions, 2);
}

private static IEnumerable<CodeInstruction> ReplaceCall(IEnumerable<CodeInstruction> instructions, MethodInfo original, MethodInfo replacement, int count = 1)
{
List<CodeInstruction> code = new List<CodeInstruction>(instructions);
int counter = 0;

for (int i = 0; i < code.Count; i++)
{
if (code[i].opcode == OpCodes.Callvirt && code[i].Calls(original))
{
code[i].opcode = OpCodes.Call;
code[i].operand = replacement;

if (++counter == count)
break;
}
}

return code;
}

private static IEnumerable<CodeInstruction> ReplaceWorldToViewportPoint(IEnumerable<CodeInstruction> instructions, int count)
{
MethodInfo Camera_WorldToViewportPoint = AccessTools.Method(typeof(Camera), nameof(Camera.WorldToViewportPoint), new Type[] { typeof(Vector3) });
MethodInfo VectorLineOptimisation_WorldToViewportPoint = AccessTools.Method(typeof(VectorLineCameraProjection), nameof(VectorLineCameraProjection.WorldToViewportPoint));

return ReplaceCall(instructions, Camera_WorldToViewportPoint, VectorLineOptimisation_WorldToViewportPoint, count);
}

private static IEnumerable<CodeInstruction> ReplaceWorldToScreenPoint(IEnumerable<CodeInstruction> instructions, int count)
{
MethodInfo Camera_WorldToScreenPoint = AccessTools.Method(typeof(Camera), nameof(Camera.WorldToScreenPoint), new Type[] { typeof(Vector3) });
MethodInfo VectorLineOptimisation_WorldToScreenPoint = AccessTools.Method(typeof(VectorLineCameraProjection), nameof(VectorLineCameraProjection.WorldToScreenPoint));

return ReplaceCall(instructions, Camera_WorldToScreenPoint, VectorLineOptimisation_WorldToScreenPoint, count);
}

private static IEnumerable<CodeInstruction> ReplaceScreenToWorldPoint(IEnumerable<CodeInstruction> instructions, int count)
{
MethodInfo Camera_ScreenToWorldPoint = AccessTools.Method(typeof(Camera), nameof(Camera.ScreenToWorldPoint), new Type[] { typeof(Vector3) });
MethodInfo VectorLineOptimisation_ScreenToWorldPoint = AccessTools.Method(typeof(VectorLineCameraProjection), nameof(VectorLineCameraProjection.ScreenToWorldPoint));

return ReplaceCall(instructions, Camera_ScreenToWorldPoint, VectorLineOptimisation_ScreenToWorldPoint, count);
}

private static IEnumerable<CodeInstruction> ReplaceScreenToViewportPoint(IEnumerable<CodeInstruction> instructions, int count)
{
MethodInfo Camera_ScreenToViewportPoint = AccessTools.Method(typeof(Camera), nameof(Camera.ScreenToViewportPoint), new Type[] { typeof(Vector3) });
MethodInfo VectorLineOptimisation_ScreenToViewportPoint = AccessTools.Method(typeof(VectorLineCameraProjection), nameof(VectorLineCameraProjection.ScreenToViewportPoint));

return ReplaceCall(instructions, Camera_ScreenToViewportPoint, VectorLineOptimisation_ScreenToViewportPoint, count);
}

#endregion
}

public static class VectorLineCameraProjection
{
// Based on CameraProjectionCache from UnityCsReference.
// https://github.com/Unity-Technologies/UnityCsReference/blob/2019.4/Editor/Mono/Camera/CameraProjectionCache.cs

public static bool patchEnabled = true;
public static long lastCachedFrame;

private static TransformMatrix worldToClip;
private static TransformMatrix clipToWorld;

// Storing viewport info instead of using Rect properties grants us a few extra frames.
public struct ViewportInfo
{
public double halfWidth;
public double halfHeight;
public double width;
public double height;
public double x;
public double y;

public ViewportInfo(Rect viewport)
{
width = viewport.width;
height = viewport.height;
halfWidth = width * 0.5;
halfHeight = height * 0.5;
x = viewport.x;
y = viewport.y;
}
}

private static ViewportInfo viewport;

private static void UpdateCache()
{
lastCachedFrame = KSPCommunityFixes.frameCount;
Camera camera = VectorLine.cam3D;

viewport = new ViewportInfo(camera.pixelRect);

Matrix4x4 worldToClip = camera.projectionMatrix * camera.worldToCameraMatrix;
VectorLineCameraProjection.worldToClip = new TransformMatrix(ref worldToClip);

Matrix4x4 worldToCameraInv = camera.worldToCameraMatrix.inverse;
Matrix4x4 projectionInv = camera.projectionMatrix.inverse;
projectionInv.m02 += projectionInv.m03;
projectionInv.m12 += projectionInv.m13;
projectionInv.m22 += projectionInv.m23;

Matrix4x4 clipToWorld = worldToCameraInv * projectionInv;
VectorLineCameraProjection.clipToWorld = new TransformMatrix(clipToWorld.m00, clipToWorld.m01, clipToWorld.m02, camera.worldToCameraMatrix.inverse.m03,
clipToWorld.m10, clipToWorld.m11, clipToWorld.m12, camera.worldToCameraMatrix.inverse.m13,
clipToWorld.m20, clipToWorld.m21, clipToWorld.m22, camera.worldToCameraMatrix.inverse.m23);
}

#region World to Clip

public static Vector3 WorldToScreenPoint(Camera camera, Vector3 worldPosition)
{
// These patchEnabled checks are commented out atm in case they affect performance.
// For testing they can be re-enabled and patchEnabled edited in UnityExplorer.

//if (!patchEnabled)
// return camera.WorldToScreenPoint(worldPosition);

if (lastCachedFrame != KSPCommunityFixes.frameCount)
UpdateCache();

double x = worldPosition.x;
double y = worldPosition.y;
double z = worldPosition.z;

worldToClip.MutateMultiplyPoint3x4(ref x, ref y, ref z);

double num = 0.5 / z;
x = (0.5 + num * x) * viewport.width + viewport.x;
y = (0.5 + num * y) * viewport.height + viewport.y;

return new Vector3((float)x, (float)y, (float)z);
}

public static Vector3 WorldToViewportPoint(Camera camera, Vector3 worldPosition)
{
//if (!patchEnabled)
// return camera.WorldToViewportPoint(worldPosition);

if (lastCachedFrame != KSPCommunityFixes.frameCount)
UpdateCache();

double x = worldPosition.x;
double y = worldPosition.y;
double z = worldPosition.z;

worldToClip.MutateMultiplyPoint3x4(ref x, ref y, ref z);

double num = 0.5 / z;
x = 0.5 + num * x;
y = 0.5 + num * y;

return new Vector3((float)x, (float)y, (float)z);
}

#endregion

#region Clip to World

public static Vector3 ScreenToWorldPoint(Camera camera, Vector3 screenPosition)
{
//if (!patchEnabled)
// return camera.ScreenToWorldPoint(screenPosition);

if (lastCachedFrame != KSPCommunityFixes.frameCount)
UpdateCache();

double x = screenPosition.x;
double y = screenPosition.y;
double z = screenPosition.z;

x = z * ((x - viewport.x) / viewport.halfWidth - 1);
y = z * ((y - viewport.y) / viewport.halfHeight - 1);

clipToWorld.MutateMultiplyPoint3x4(ref x, ref y, ref z);

return new Vector3((float)x, (float)y, (float)z);
}

public static Vector3 ScreenToViewportPoint(Camera camera, Vector3 position)
{
//if (!patchEnabled)
// return camera.ScreenToViewportPoint(position);

//if (lastCachedFrame != KSPCommunityFixes.frameCount)
// UpdateCache();

// Not used by VectorLine.
throw new NotImplementedException();
}

#endregion
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ User options are available from the "ESC" in-game settings menu :<br/><img src="
- [**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.
- [**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.

#### API and modding tools
- **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).
Expand Down