From 764f2a8a0f8feb7c5613ff7af387189d2eeb2919 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Wed, 4 Aug 2021 14:27:56 +0200 Subject: [PATCH] Optimized lightmap uploads by converting the raw byte pointer from Quake to a NativeArray (unsafe code required) and passing that directly to SetPixelData. This does away with the extra copying step and removes garbage created by Texture2D's implementation. The old code path is still kept around since the NativeArray version doesn't work inside the Editor for some reason, but it does in standalone builds. --- Assets/Scripts/Game/GameAssets.cs | 25 ++++++++++++++++--- .../Scripts/Modules/RenderModule.Interop.cs | 23 ++++++++++++++--- Assets/Scripts/Modules/RenderModule.cs | 10 ++++++++ Assets/Scripts/UniQuake.cs | 5 ++++ ProjectSettings/ProjectSettings.asset | 2 +- 5 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Assets/Scripts/Game/GameAssets.cs b/Assets/Scripts/Game/GameAssets.cs index b0db6dc..ae0063c 100644 --- a/Assets/Scripts/Game/GameAssets.cs +++ b/Assets/Scripts/Game/GameAssets.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using Unity.Collections; using UnityEngine; public class GameAssets @@ -63,15 +64,33 @@ public class GameAssets } public void UploadLightmap(int lightmapNum, int width, int height, byte[] data) + { + var lightmap = GetOrCreateLightmap(lightmapNum, width, height); + lightmap.SetPixelData(data, 0); + lightmap.Apply(); + } + + public void UploadLightmap(int lightmapNum, int width, int height, NativeArray data) + { + var lightmap = GetOrCreateLightmap(lightmapNum, width, height); + lightmap.SetPixelData(data, 0); + lightmap.Apply(); + } + + private Texture2D GetOrCreateLightmap(int lightmapNum, int width, int height) { if (!lightmaps.TryGetValue(lightmapNum, out var lightmap)) { - lightmap = new Texture2D(width, height, TextureFormat.RGBA32, false) { name = $"Lightmap_{lightmapNum}", wrapMode = TextureWrapMode.Clamp }; + lightmap = new Texture2D(width, height, TextureFormat.RGBA32, false) + { + name = $"Lightmap_{lightmapNum}", + wrapMode = TextureWrapMode.Clamp, + }; + lightmaps.Add(lightmapNum, lightmap); } - lightmap.SetPixelData(data, 0); - lightmap.Apply(); + return lightmap; } public bool TryGetLightmap(int lightmapNum, out Texture2D lightmap) diff --git a/Assets/Scripts/Modules/RenderModule.Interop.cs b/Assets/Scripts/Modules/RenderModule.Interop.cs index ac39906..d9c7e33 100644 --- a/Assets/Scripts/Modules/RenderModule.Interop.cs +++ b/Assets/Scripts/Modules/RenderModule.Interop.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices; using AOT; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using UnityEngine.Profiling; @@ -138,7 +140,9 @@ public partial class RenderModule: CallbackHandler return result; } +#if UNITY_EDITOR private readonly byte[] lightmapBytes = new byte[QConstants.LightmapBlockWidth * QConstants.LightmapBlockHeight * 4]; // 32 bits per pixel +#endif [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool UploadLightmapCallback(IntPtr context, int lmap, IntPtr data); @@ -146,13 +150,26 @@ public partial class RenderModule: CallbackHandler [MonoPInvokeCallback(typeof(UploadLightmapCallback))] private static bool Callback_UploadLightmap(IntPtr context, int lmap, IntPtr data) { + bool result; Profiler.BeginSample("UploadLightmap"); - // TODO: this is a fairly pointless additional data copy step; we could probably make this faster by wrapping the IntPtr directly into a NativeArray +#if UNITY_EDITOR + // This is fairly pointless additional data copy step; the below NativeArray approach is faster and doesn't + // produce any garbage, but it doesn't work inside the Unity Editor for some reason. var self = GetSelf(context); Marshal.Copy(data, self.lightmapBytes, 0, self.lightmapBytes.Length); - bool result = self.UploadLightmap(lmap, QConstants.LightmapBlockWidth, QConstants.LightmapBlockHeight, self.lightmapBytes); - + result = self.UploadLightmap(lmap, QConstants.LightmapBlockWidth, QConstants.LightmapBlockHeight, self.lightmapBytes); +#else + unsafe + { + // More efficient code path that passes the native byte buffer directly to the Texture2D's pixel data + var dataArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(data.ToPointer(), + QConstants.LightmapBlockWidth * QConstants.LightmapBlockHeight * 4, Allocator.None); + + result = GetSelf(context).UploadLightmap(lmap, QConstants.LightmapBlockWidth, QConstants.LightmapBlockHeight, dataArray); + } +#endif + Profiler.EndSample(); return result; } diff --git a/Assets/Scripts/Modules/RenderModule.cs b/Assets/Scripts/Modules/RenderModule.cs index bf75e35..3cd480b 100644 --- a/Assets/Scripts/Modules/RenderModule.cs +++ b/Assets/Scripts/Modules/RenderModule.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Unity.Collections; using UnityEngine; using UnityEngine.Rendering; @@ -77,6 +78,15 @@ public partial class RenderModule return true; } + private bool UploadLightmap(int lightmapNum, int width, int height, NativeArray data) + { + if (width == 0 || height == 0) + return false; + + uq.GameAssets.UploadLightmap(lightmapNum, width, height, data); + return true; + } + private void SetupView(QVec3 origin, QVec3 angles, QLeaf viewLeaf) { var cam = uq.Camera; diff --git a/Assets/Scripts/UniQuake.cs b/Assets/Scripts/UniQuake.cs index 7c513ec..a537829 100644 --- a/Assets/Scripts/UniQuake.cs +++ b/Assets/Scripts/UniQuake.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Text; using UnityEngine; +using UnityEngine.Profiling; public partial class UniQuake: MonoBehaviour { @@ -133,6 +134,8 @@ public partial class UniQuake: MonoBehaviour logHandler = CollectLog; // Collect and dump logs to Unity once per frame + Profiler.BeginSample("QuakeEngineUpdate"); + try { UniQuake_Update(Time.deltaTime); @@ -152,6 +155,8 @@ public partial class UniQuake: MonoBehaviour Shutdown(); } + Profiler.EndSample(); + FlushLog(); } diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 9bdbf4c..ebbe776 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -609,7 +609,7 @@ PlayerSettings: managedStrippingLevel: {} incrementalIl2cppBuild: {} suppressCommonWarnings: 1 - allowUnsafeCode: 0 + allowUnsafeCode: 1 useDeterministicCompilation: 1 useReferenceAssemblies: 1 enableRoslynAnalyzers: 1