< Summary

Class:DCL.Rendering.CullingController
Assembly:CullingController
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Rendering/Culling/CullingController.cs
Covered lines:173
Uncovered lines:25
Coverable lines:198
Total lines:481
Line coverage:87.3% (173 of 198)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
CullingController()0%2100%
CullingController(...)0%220100%
Create()0%110100%
Start()0%3.013088.89%
StartInternal()0%220100%
Stop()0%330100%
StopInternal()0%220100%
ProcessProfile()0%22.2520082.22%
UpdateCoroutine()0%13.113091.67%
SetCullingForRenderer(...)0%550100%
ProcessAnimations()0%8.068090.48%
ResetObjects()0%13130100%
Dispose()0%110100%
OnRendererStateChange(...)0%3.033085.71%
OnPlayerUnityPositionChange(...)0%110100%
MarkDirty()0%110100%
IsDirty()0%110100%
SetSettings(...)0%330100%
GetSettingsCopy()0%110100%
SetObjectCulling(...)0%330100%
SetAnimationCulling(...)0%12300%
SetShadowCulling(...)0%330100%
RaiseDataReport()0%13.786040%
IsRunning()0%110100%
DrawDebugGizmos(...)0%220100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Rendering/Culling/CullingController.cs

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using DCL.Helpers;
 4using DCL.Models;
 5using UnityEngine;
 6using UnityEngine.Rendering;
 7using UniversalRenderPipelineAsset = UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset;
 8using static DCL.Rendering.CullingControllerUtils;
 9
 10namespace DCL.Rendering
 11{
 12    /// <summary>
 13    /// CullingController has the following responsibilities:
 14    /// - Hides small renderers (detail objects).
 15    /// - Disable unneeded shadows.
 16    /// - Enable/disable animation culling for skinned renderers and animation components.
 17    /// </summary>
 18    public class CullingController : ICullingController
 19    {
 20        internal List<CullingControllerProfile> profiles = null;
 21
 22        private CullingControllerSettings settings;
 23
 67624        private HashSet<Renderer> hiddenRenderers = new HashSet<Renderer>();
 67625        private HashSet<Renderer> shadowlessRenderers = new HashSet<Renderer>();
 26
 27        public UniversalRenderPipelineAsset urpAsset;
 28
 202229        public ICullingObjectsTracker objectsTracker { get; private set; }
 30        private Coroutine updateCoroutine;
 31        private float timeBudgetCount = 0;
 32        private bool resetObjectsNextFrame = false;
 33        private bool playerPositionDirty;
 34        private bool objectPositionsDirty;
 35        private bool running = false;
 36
 37        public event ICullingController.DataReport OnDataReport;
 38
 39        public static CullingController Create()
 40        {
 66641            return new CullingController(
 42                GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset,
 43                new CullingControllerSettings()
 44            );
 45        }
 46
 047        public CullingController() { }
 48
 67649        public CullingController(UniversalRenderPipelineAsset urpAsset, CullingControllerSettings settings, ICullingObje
 50        {
 67651            if (cullingObjectsTracker == null)
 66652                objectsTracker = new CullingObjectsTracker();
 53            else
 1054                objectsTracker = cullingObjectsTracker;
 55
 67656            objectsTracker.SetIgnoredLayersMask(settings.ignoredLayersMask);
 57
 67658            this.urpAsset = urpAsset;
 67659            this.settings = settings;
 67660        }
 61
 62        /// <summary>
 63        /// Starts culling update coroutine.
 64        /// The coroutine will keep running until Stop() is called or this class is disposed.
 65        /// </summary>
 66        public void Start()
 67        {
 67468            if (running)
 069                return;
 70
 67471            running = true;
 67472            CommonScriptableObjects.rendererState.OnChange += OnRendererStateChange;
 67473            CommonScriptableObjects.playerUnityPosition.OnChange += OnPlayerUnityPositionChange;
 67474            MeshesInfo.OnAnyUpdated += MarkDirty;
 67475            objectsTracker?.MarkDirty();
 67476            StartInternal();
 67477        }
 78
 79        private void StartInternal()
 80        {
 77781            if (updateCoroutine != null)
 10282                return;
 83
 67584            RaiseDataReport();
 67585            profiles = new List<CullingControllerProfile> { settings.rendererProfile, settings.skinnedRendererProfile };
 67586            updateCoroutine = CoroutineStarter.Start(UpdateCoroutine());
 67587        }
 88
 89        /// <summary>
 90        /// Stops culling update coroutine.
 91        /// </summary>
 92        public void Stop()
 93        {
 134194            if (!running)
 66795                return;
 96
 67497            running = false;
 67498            CommonScriptableObjects.rendererState.OnChange -= OnRendererStateChange;
 67499            CommonScriptableObjects.playerUnityPosition.OnChange -= OnPlayerUnityPositionChange;
 674100            MeshesInfo.OnAnyUpdated -= MarkDirty;
 674101            StopInternal();
 674102            objectsTracker?.ForcePopulateRenderersList(true);
 674103            ResetObjects();
 674104        }
 105
 106        public void StopInternal()
 107        {
 676108            if (updateCoroutine == null)
 1109                return;
 110
 675111            CoroutineStarter.Stop(updateCoroutine);
 675112            updateCoroutine = null;
 675113        }
 114
 115        /// <summary>
 116        /// Process all sceneObject renderers with the parameters set by the given profile.
 117        /// </summary>
 118        /// <param name="profile">any CullingControllerProfile</param>
 119        /// <returns>IEnumerator to be yielded.</returns>
 120        internal IEnumerator ProcessProfile(CullingControllerProfile profile)
 121        {
 122            Renderer[] renderers;
 123
 124            // If profile matches the skinned renderer profile in settings,
 125            // the skinned renderers are going to be used.
 209126            if (profile == settings.rendererProfile)
 201127                renderers = objectsTracker.GetRenderers();
 128            else
 8129                renderers = objectsTracker.GetSkinnedRenderers();
 130
 131
 7616132            for (var i = 0; i < renderers.Length; i++)
 133            {
 3601134                if (timeBudgetCount > settings.maxTimeBudget)
 135                {
 2136                    timeBudgetCount = 0;
 2137                    yield return null;
 138                }
 139
 3599140                Renderer r = renderers[i];
 141
 3599142                if (r == null)
 143                    continue;
 144
 3599145                bool rendererIsInIgnoreLayer = ((1 << r.gameObject.layer) & settings.ignoredLayersMask) != 0;
 146
 3599147                if (rendererIsInIgnoreLayer)
 148                {
 0149                    SetCullingForRenderer(r, true, true);
 0150                    continue;
 151                }
 152
 153
 3599154                float startTime = Time.realtimeSinceStartup;
 155
 156                //NOTE(Brian): Need to retrieve positions every frame to take into account
 157                //             world repositioning.
 3599158                Vector3 playerPosition = CommonScriptableObjects.playerUnityPosition;
 159
 3599160                Bounds bounds = r.GetSafeBounds();
 161
 3599162                Vector3 boundingPoint = bounds.ClosestPoint(playerPosition);
 3599163                float distance = Vector3.Distance(playerPosition, boundingPoint);
 3599164                bool boundsContainsPlayer = bounds.Contains(playerPosition);
 3599165                float boundsSize = bounds.size.magnitude;
 3599166                float viewportSize = (boundsSize / distance) * Mathf.Rad2Deg;
 167
 3599168                bool isEmissive = IsEmissive(r);
 3599169                bool isOpaque = IsOpaque(r);
 170
 3599171                float shadowTexelSize = ComputeShadowMapTexelSize(boundsSize, urpAsset.shadowDistance, urpAsset.mainLigh
 172
 3599173                bool shouldBeVisible = !settings.enableObjectCulling || TestRendererVisibleRule(profile, viewportSize, d
 3599174                bool shouldHaveShadow = !settings.enableShadowCulling || TestRendererShadowRule(profile, viewportSize, d
 175
 3599176                if (r is SkinnedMeshRenderer skr)
 177                {
 5178                    Material mat = skr.sharedMaterial;
 5179                    bool isAvatarRenderer = false;
 180
 5181                    if (mat != null && mat.shader != null)
 0182                        isAvatarRenderer = mat.shader.name == "DCL/Toon Shader";
 183
 5184                    if (isAvatarRenderer)
 185                    {
 0186                        shouldHaveShadow &= TestAvatarShadowRule(profile, distance);
 187                    }
 188
 5189                    skr.updateWhenOffscreen = TestSkinnedRendererOffscreenRule(settings, distance);
 190                }
 191
 3599192                if (OnDataReport != null)
 193                {
 0194                    if (!shouldBeVisible && !hiddenRenderers.Contains(r))
 0195                        hiddenRenderers.Add(r);
 196
 0197                    if (shouldBeVisible && !shouldHaveShadow && !shadowlessRenderers.Contains(r))
 0198                        shadowlessRenderers.Add(r);
 199                }
 200
 3599201                SetCullingForRenderer(r, shouldBeVisible, shouldHaveShadow);
 202#if UNITY_EDITOR
 3599203                DrawDebugGizmos(shouldBeVisible, bounds, boundingPoint);
 204#endif
 3599205                timeBudgetCount += Time.realtimeSinceStartup - startTime;
 206            }
 207207        }
 208
 209        /// <summary>
 210        /// Main culling loop. Controlled by Start() and Stop() methods.
 211        /// </summary>
 212        IEnumerator UpdateCoroutine()
 213        {
 5214            while (true)
 215            {
 3288216                bool shouldCheck = objectPositionsDirty || playerPositionDirty;
 217
 3288218                playerPositionDirty = false;
 3288219                objectPositionsDirty = false;
 220
 3288221                if (!shouldCheck)
 222                {
 2923223                    timeBudgetCount = 0;
 2923224                    yield return null;
 2608225                    continue;
 226                }
 227
 365228                yield return objectsTracker.PopulateRenderersList();
 229
 219230                if (resetObjectsNextFrame)
 231                {
 4232                    ResetObjects();
 4233                    resetObjectsNextFrame = false;
 234                }
 235
 219236                yield return ProcessAnimations();
 237
 200238                if (OnDataReport != null)
 239                {
 0240                    hiddenRenderers.Clear();
 0241                    shadowlessRenderers.Clear();
 242                }
 243
 200244                int profilesCount = profiles.Count;
 245
 424246                for (var pIndex = 0; pIndex < profilesCount; pIndex++)
 247                {
 207248                    yield return ProcessProfile(profiles[pIndex]);
 249                }
 250
 5251                RaiseDataReport();
 5252                timeBudgetCount = 0;
 5253                yield return null;
 254            }
 255        }
 256
 257        /// <summary>
 258        /// Sets shadows and visibility for a given renderer.
 259        /// </summary>
 260        /// <param name="r">Renderer to be culled</param>
 261        /// <param name="shouldBeVisible">If false, the renderer visibility will be set to false.</param>
 262        /// <param name="shouldHaveShadow">If false, the renderer shadow will be toggled off.</param>
 263        internal void SetCullingForRenderer(Renderer r, bool shouldBeVisible, bool shouldHaveShadow)
 264        {
 3609265            var targetMode = shouldHaveShadow ? ShadowCastingMode.On : ShadowCastingMode.Off;
 266
 3609267            if (r.forceRenderingOff != !shouldBeVisible)
 514268                r.forceRenderingOff = !shouldBeVisible;
 269
 3609270            if (r.shadowCastingMode != targetMode)
 1165271                r.shadowCastingMode = targetMode;
 3609272        }
 273
 274        /// <summary>
 275        /// Sets cullingType to all tracked animation components according to our culling rules.
 276        /// </summary>
 277        /// <returns>IEnumerator to be yielded.</returns>
 278        internal IEnumerator ProcessAnimations()
 279        {
 223280            if (!settings.enableAnimationCulling)
 220281                yield break;
 282
 3283            Animation[] animations = objectsTracker.GetAnimations();
 3284            int animsLength = animations.Length;
 285
 24286            for (var i = 0; i < animsLength; i++)
 287            {
 9288                if (timeBudgetCount > settings.maxTimeBudget)
 289                {
 0290                    timeBudgetCount = 0;
 0291                    yield return null;
 292                }
 293
 9294                Animation anim = animations[i];
 295
 9296                if (anim == null)
 297                    continue;
 298
 9299                float startTime = Time.realtimeSinceStartup;
 9300                Transform t = anim.transform;
 301
 9302                Vector3 playerPosition = CommonScriptableObjects.playerUnityPosition;
 9303                float distance = Vector3.Distance(playerPosition, t.position);
 304
 9305                if (distance > settings.enableAnimationCullingDistance)
 4306                    anim.cullingType = AnimationCullingType.BasedOnRenderers;
 307                else
 5308                    anim.cullingType = AnimationCullingType.AlwaysAnimate;
 309
 9310                timeBudgetCount += Time.realtimeSinceStartup - startTime;
 311            }
 3312        }
 313
 314        /// <summary>
 315        /// Reset all tracked renderers properties. Needed when toggling or changing settings.
 316        /// </summary>
 317        internal void ResetObjects()
 318        {
 679319            var skinnedRenderers = objectsTracker.GetSkinnedRenderers();
 679320            var renderers = objectsTracker.GetRenderers();
 679321            var animations = objectsTracker.GetAnimations();
 322
 1360323            for (var i = 0; i < skinnedRenderers?.Length; i++)
 324            {
 1325                if (skinnedRenderers[i] != null)
 1326                    skinnedRenderers[i].updateWhenOffscreen = true;
 327            }
 328
 1360329            for (var i = 0; i < animations?.Length; i++)
 330            {
 1331                if (animations[i] != null)
 1332                    animations[i].cullingType = AnimationCullingType.AlwaysAnimate;
 333            }
 334
 28642335            for (var i = 0; i < renderers?.Length; i++)
 336            {
 13642337                if (renderers[i] != null)
 13642338                    renderers[i].forceRenderingOff = false;
 339            }
 679340        }
 341
 342        public void Dispose()
 343        {
 696344            objectsTracker.Dispose();
 696345            Stop();
 696346        }
 347
 348        /// <summary>
 349        /// Method suscribed to renderer state change
 350        /// </summary>
 351        private void OnRendererStateChange(bool rendererState, bool oldRendererState)
 352        {
 105353            if (!running)
 0354                return;
 355
 105356            MarkDirty();
 357
 105358            if (rendererState)
 103359                StartInternal();
 360            else
 2361                StopInternal();
 2362        }
 363
 364        /// <summary>
 365        /// Method suscribed to playerUnityPosition change
 366        /// </summary>
 2020367        private void OnPlayerUnityPositionChange(Vector3 previous, Vector3 current) { playerPositionDirty = true; }
 368
 369        /// <summary>
 370        /// Sets the scene objects dirtiness.
 371        /// In the next update iteration, all the scene objects are going to be gathered.
 372        /// This method has performance impact.
 373        /// </summary>
 682374        public void MarkDirty() { objectPositionsDirty = true; }
 375
 376        /// <summary>
 377        /// Gets the scene objects dirtiness.
 378        /// </summary>
 3379        public bool IsDirty() { return objectPositionsDirty; }
 380
 381        /// <summary>
 382        /// Set settings. This will dirty the scene objects and has performance impact.
 383        /// </summary>
 384        /// <param name="settings">Settings to be set</param>
 385        public void SetSettings(CullingControllerSettings settings)
 386        {
 19387            this.settings = settings;
 19388            profiles = new List<CullingControllerProfile> { settings.rendererProfile, settings.skinnedRendererProfile };
 389
 19390            objectsTracker?.SetIgnoredLayersMask(settings.ignoredLayersMask);
 19391            objectsTracker?.MarkDirty();
 19392            MarkDirty();
 19393            resetObjectsNextFrame = true;
 19394        }
 395
 396        /// <summary>
 397        /// Get current settings copy. If you need to modify it, you must set them via SetSettings afterwards.
 398        /// </summary>
 399        /// <returns>Current settings object copy.</returns>
 8400        public CullingControllerSettings GetSettingsCopy() { return settings.Clone(); }
 401
 402        /// <summary>
 403        /// Enable or disable object visibility culling.
 404        /// </summary>
 405        /// <param name="enabled">If disabled, object visibility culling will be toggled.
 406        /// </param>
 407        public void SetObjectCulling(bool enabled)
 408        {
 2409            if (settings.enableObjectCulling == enabled)
 1410                return;
 411
 1412            settings.enableObjectCulling = enabled;
 1413            resetObjectsNextFrame = true;
 1414            MarkDirty();
 1415            objectsTracker?.MarkDirty();
 1416        }
 417
 418        /// <summary>
 419        /// Enable or disable animation culling.
 420        /// </summary>
 421        /// <param name="enabled">If disabled, animation culling will be toggled.</param>
 422        public void SetAnimationCulling(bool enabled)
 423        {
 0424            if (settings.enableAnimationCulling == enabled)
 0425                return;
 426
 0427            settings.enableAnimationCulling = enabled;
 0428            resetObjectsNextFrame = true;
 0429            MarkDirty();
 0430            objectsTracker?.MarkDirty();
 0431        }
 432
 433        /// <summary>
 434        /// Enable or disable shadow culling
 435        /// </summary>
 436        /// <param name="enabled">If disabled, no shadows will be toggled.</param>
 437        public void SetShadowCulling(bool enabled)
 438        {
 2439            if (settings.enableShadowCulling == enabled)
 1440                return;
 441
 1442            settings.enableShadowCulling = enabled;
 1443            resetObjectsNextFrame = true;
 1444            MarkDirty();
 1445            objectsTracker?.MarkDirty();
 1446        }
 447
 448        /// <summary>
 449        /// Fire the DataReport event. This will be useful for showing stats in a debug panel.
 450        /// </summary>
 451        private void RaiseDataReport()
 452        {
 680453            if (OnDataReport == null)
 680454                return;
 455
 0456            int rendererCount = (objectsTracker.GetRenderers()?.Length ?? 0) + (objectsTracker.GetSkinnedRenderers()?.Le
 457
 0458            OnDataReport.Invoke(rendererCount, hiddenRenderers.Count, shadowlessRenderers.Count);
 0459        }
 460
 461        /// <summary>
 462        /// Returns true if the culling loop is running
 463        /// </summary>
 6464        public bool IsRunning() { return updateCoroutine != null; }
 465
 466        /// <summary>
 467        /// Draw debug gizmos on the scene view.
 468        /// </summary>
 469        /// <param name="shouldBeVisible"></param>
 470        /// <param name="bounds"></param>
 471        /// <param name="boundingPoint"></param>
 472        private static void DrawDebugGizmos(bool shouldBeVisible, Bounds bounds, Vector3 boundingPoint)
 473        {
 3599474            if (!shouldBeVisible)
 475            {
 508476                CullingControllerUtils.DrawBounds(bounds, Color.blue, 1);
 508477                CullingControllerUtils.DrawBounds(new Bounds() { center = boundingPoint, size = Vector3.one }, Color.red
 478            }
 3599479        }
 480    }
 481}