diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index 84ffd5a..ecaa679 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -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 diff --git a/KSPCommunityFixes/KSPCommunityFixes.cs b/KSPCommunityFixes/KSPCommunityFixes.cs index 0853461..943b037 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.cs +++ b/KSPCommunityFixes/KSPCommunityFixes.cs @@ -26,6 +26,9 @@ public class KSPCommunityFixes : MonoBehaviour public static long FixedUpdateCount { get; private set; } + // Frame counter that doesn't use a call to C++ like Time.frameCount. + public static long UpdateCount { get; private set; } + private static string modPath; public static string ModPath { @@ -132,5 +135,10 @@ void FixedUpdate() { FixedUpdateCount++; } + + void Update() + { + UpdateCount++; + } } } diff --git a/KSPCommunityFixes/KSPCommunityFixes.csproj b/KSPCommunityFixes/KSPCommunityFixes.csproj index f6e1cc7..9a372d2 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.csproj +++ b/KSPCommunityFixes/KSPCommunityFixes.csproj @@ -1,4 +1,4 @@ - + @@ -162,6 +162,7 @@ + diff --git a/KSPCommunityFixes/Library/Numerics.cs b/KSPCommunityFixes/Library/Numerics.cs index c405897..851a7f3 100644 --- a/KSPCommunityFixes/Library/Numerics.cs +++ b/KSPCommunityFixes/Library/Numerics.cs @@ -719,6 +719,19 @@ public void MutateMultiplyPoint3x4(ref Vector3d point) point.z = m20 * x + m21 * y + m22 * z + m23; } + /// + /// Transform point, slightly faster than using a Vector3d. + /// + 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; + } + /// /// Transform point /// diff --git a/KSPCommunityFixes/Performance/OptimisedVectorLines.cs b/KSPCommunityFixes/Performance/OptimisedVectorLines.cs new file mode 100644 index 0000000..da02dfa --- /dev/null +++ b/KSPCommunityFixes/Performance/OptimisedVectorLines.cs @@ -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 VectorLine_Line3D_Transpiler(IEnumerable instructions) => + ReplaceWorldToScreenPoint(instructions, 2); + + static IEnumerable VectorLine_BehindCamera_Transpiler(IEnumerable instructions) => + ReplaceWorldToViewportPoint(instructions, 2); + + static IEnumerable VectorLine_IntersectAndDoSkip_Transpiler(IEnumerable instructions) => + ReplaceWorldToScreenPoint(instructions, 2); + + static IEnumerable VectorLine_Draw3D_Transpiler(IEnumerable instructions) + { + instructions = ReplaceWorldToScreenPoint(instructions, 2); + instructions = ReplaceScreenToWorldPoint(instructions, 4); + + return instructions; + } + + static IEnumerable VectorLine_SetIntersectionPoint3D_Transpiler(IEnumerable instructions) + { + return ReplaceScreenToWorldPoint(instructions, 2); + } + + private static IEnumerable ReplaceCall(IEnumerable instructions, MethodInfo original, MethodInfo replacement, int count = 1) + { + List code = new List(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 ReplaceWorldToViewportPoint(IEnumerable 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 ReplaceWorldToScreenPoint(IEnumerable 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 ReplaceScreenToWorldPoint(IEnumerable 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 ReplaceScreenToViewportPoint(IEnumerable 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.UpdateCount; + 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.UpdateCount) + 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.UpdateCount) + 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.UpdateCount) + 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 + } +} diff --git a/README.md b/README.md index 1a44f08..a30a911 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ User options are available from the "ESC" in-game settings menu :