diff --git a/src/Modules/MeshLod/MeshLodInjector.cs b/src/Modules/MeshLod/MeshLodInjector.cs index fbf994d..06a2d95 100644 --- a/src/Modules/MeshLod/MeshLodInjector.cs +++ b/src/Modules/MeshLod/MeshLodInjector.cs @@ -12,7 +12,6 @@ static class MeshLodInjector { static MeshLodSettings _settings = new(); static readonly List> _trackedCars = new(); - static Material? _bboxMat; // Debounced auto-refresh: set by MarkDirty, consumed by CheckAutoRefresh (called from Update). static float _pendingRefreshAt = -1f; @@ -33,7 +32,6 @@ static class MeshLodInjector { _trackedCars.Clear(); _pendingRefreshAt = -1f; - if (_bboxMat != null) { UnityEngine.Object.Destroy(_bboxMat); _bboxMat = null; } } public static (int total, int locos, int freight) GetTrackedCounts() @@ -129,6 +127,11 @@ static class MeshLodInjector try { ApplyLods(car, renderers); + // Opportunistically drop dead weak-refs so the list can't grow unbounded over a + // long session of cars loading/unloading (RefreshAll also compacts, but only runs + // when a setting changes). Cheap: the modulo check runs once per car load. + if (_trackedCars.Count > 0 && _trackedCars.Count % 128 == 0) + _trackedCars.RemoveAll(wr => !wr.TryGetTarget(out _)); _trackedCars.Add(new WeakReference(car)); } catch (Exception ex) { Log.Warn($"MeshLOD: apply failed for {car.DisplayName}: {ex.Message}"); } @@ -208,9 +211,12 @@ static class MeshLodInjector Renderer[] lod2Rend = sorted.Take(lod2Keep).Cast().ToArray(); // LOD3: 12-triangle proxy box derived from the largest renderer's mesh-local AABB. + // Reuse the largest renderer's own material so the box matches the car's colour + // (a shared/tinted material can't match per-car, and Material.color sets the legacy + // _Color, not URP's _BaseColor — both reasons the previous matte material rendered gray). Bounds localBounds = ComputeMeshLocalBounds(sorted[0], bodyRoot); MeshRenderer bboxMr = CreateBoundingBoxRenderer(bodyRoot, localBounds, - GetOrCreateBBoxMat(sorted[0].sharedMaterial), + sorted[0].sharedMaterial, sorted[0].gameObject.layer); Renderer[] lod3Rend = new[] { (Renderer)bboxMr }; @@ -261,7 +267,14 @@ static class MeshLodInjector } Transform? bbox = car.BodyTransform.Find("S3_BBox"); - if (bbox != null) UnityEngine.Object.Destroy(bbox.gameObject); + if (bbox != null) + { + // Destroy the procedural box mesh too: destroying the GameObject alone leaves + // the Mesh asset alive until scene unload (a small leak on every refresh). + MeshFilter? bf = bbox.GetComponent(); + if (bf != null && bf.sharedMesh != null) UnityEngine.Object.Destroy(bf.sharedMesh); + UnityEngine.Object.Destroy(bbox.gameObject); + } } // ── Bounds helpers ──────────────────────────────────────────────────────── @@ -316,26 +329,6 @@ static class MeshLodInjector // ── Proxy box mesh ──────────────────────────────────────────────────────── - // One matte material shared across all proxy boxes; tinted to the car body's - // albedo so it looks approximately correct under any lighting / post-processing. - // Tries URP first since Railroader uses the Universal Render Pipeline. - static Material GetOrCreateBBoxMat(Material? src) - { - if (_bboxMat != null) return _bboxMat; - var shader = Shader.Find("Universal Render Pipeline/Lit") - ?? Shader.Find("Universal Render Pipeline/Simple Lit") - ?? Shader.Find("Standard") - ?? Shader.Find("Legacy Shaders/Diffuse"); - _bboxMat = shader != null ? new Material(shader) : new Material(src); - _bboxMat.color = src != null && src.HasProperty("_Color") - ? src.color - : new Color(0.45f, 0.45f, 0.45f); - if (_bboxMat.HasProperty("_Metallic")) _bboxMat.SetFloat("_Metallic", 0f); - if (_bboxMat.HasProperty("_Glossiness")) _bboxMat.SetFloat("_Glossiness", 0.1f); // built-in - if (_bboxMat.HasProperty("_Smoothness")) _bboxMat.SetFloat("_Smoothness", 0.1f); // URP - return _bboxMat; - } - static MeshRenderer CreateBoundingBoxRenderer(Transform parent, Bounds localBounds, Material mat, int layer) { diff --git a/src/Modules/PhysicsOptimizer/PhysicsTimer.cs b/src/Modules/PhysicsOptimizer/PhysicsTimer.cs index e66f8e3..f051d91 100644 --- a/src/Modules/PhysicsOptimizer/PhysicsTimer.cs +++ b/src/Modules/PhysicsOptimizer/PhysicsTimer.cs @@ -102,7 +102,7 @@ public static class PhysicsTimer _frames = 0; } - // Called from PhysicsOverlayGUI.LateUpdate() to capture render frame time. + // Called from ProfilerOverlayGUI.LateUpdate() to capture render frame time. public static void RecordRenderFrame(float deltaMs) { _lastInstantRenderMs = deltaMs; // sampled into RingRender on the next RecordFrame call