< Summary

Class:DCL.Rendering.CullingController
Assembly:CullingController
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Rendering/Culling/CullingController.cs
Covered lines:158
Uncovered lines:42
Coverable lines:200
Total lines:489
Line coverage:79% (158 of 200)
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%2.022083.33%
Stop()0%330100%
StopInternal()0%220100%
ProcessProfile()0%24.3920077.78%
UpdateCoroutine()0%13.113091.67%
SetCullingForRenderer(...)0%550100%
ProcessAnimations()0%8.068090.48%
ResetObjects()0%13130100%
Dispose()0%110100%
Initialize()0%110100%
OnRendererStateChange(...)0%3.033085.71%
OnPlayerUnityPositionChange(...)0%110100%
MarkDirty()0%110100%
IsDirty()0%110100%
SetSettings(...)0%330100%
GetSettingsCopy()0%110100%
SetObjectCulling(...)0%12300%
SetAnimationCulling(...)0%12300%
SetShadowCulling(...)0%12300%
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
 6524        private HashSet<Renderer> hiddenRenderers = new HashSet<Renderer>();
 6525        private HashSet<Renderer> shadowlessRenderers = new HashSet<Renderer>();
 26
 27        public UniversalRenderPipelineAsset urpAsset;
 28
 5829        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        {
 5541            return new CullingController(
 42                GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset,
 43                new CullingControllerSettings()
 44            );
 45        }
 46
 047        private CullingController() { }
 48
 6549        public CullingController(UniversalRenderPipelineAsset urpAsset, CullingControllerSettings settings, ICullingObje
 50        {
 6551            if (cullingObjectsTracker == null)
 5552                objectsTracker = new CullingObjectsTracker();
 53            else
 1054                objectsTracker = cullingObjectsTracker;
 55
 6556            objectsTracker.SetIgnoredLayersMask(settings.ignoredLayersMask);
 57
 6558            this.urpAsset = urpAsset;
 6559            this.settings = settings;
 6560        }
 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        {
 5968            if (running)
 069                return;
 70
 5971            running = true;
 5972            CommonScriptableObjects.rendererState.OnChange += OnRendererStateChange;
 5973            CommonScriptableObjects.playerUnityPosition.OnChange += OnPlayerUnityPositionChange;
 5974            MeshesInfo.OnAnyUpdated += MarkDirty;
 5975            objectsTracker?.MarkDirty();
 5976            StartInternal();
 5977        }
 78
 79        private void StartInternal()
 80        {
 6081            if (updateCoroutine != null)
 082                return;
 83
 6084            RaiseDataReport();
 6085            profiles = new List<CullingControllerProfile> { settings.rendererProfile, settings.skinnedRendererProfile };
 6086            updateCoroutine = CoroutineStarter.Start(UpdateCoroutine());
 6087        }
 88
 89        /// <summary>
 90        /// Stops culling update coroutine.
 91        /// </summary>
 92        public void Stop()
 93        {
 6994            if (!running)
 1095                return;
 96
 5997            running = false;
 5998            CommonScriptableObjects.rendererState.OnChange -= OnRendererStateChange;
 5999            CommonScriptableObjects.playerUnityPosition.OnChange -= OnPlayerUnityPositionChange;
 59100            MeshesInfo.OnAnyUpdated -= MarkDirty;
 59101            StopInternal();
 59102            objectsTracker?.ForcePopulateRenderersList(true);
 59103            ResetObjects();
 59104        }
 105
 106        public void StopInternal()
 107        {
 61108            if (updateCoroutine == null)
 1109                return;
 110
 60111            CoroutineStarter.Stop(updateCoroutine);
 60112            updateCoroutine = null;
 60113        }
 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.
 18126            if (profile == settings.rendererProfile)
 9127                renderers = objectsTracker.GetRenderers();
 128            else
 9129                renderers = objectsTracker.GetSkinnedRenderers();
 130
 131
 82132            for (var i = 0; i < renderers.Length; i++)
 133            {
 23134                if (timeBudgetCount > settings.maxTimeBudget)
 135                {
 0136                    timeBudgetCount = 0;
 0137                    yield return null;
 138                }
 139
 23140                Renderer r = renderers[i];
 141
 23142                if (r == null)
 143                    continue;
 144
 23145                bool rendererIsInIgnoreLayer = ((1 << r.gameObject.layer) & settings.ignoredLayersMask) != 0;
 146
 23147                if (rendererIsInIgnoreLayer)
 148                {
 0149                    SetCullingForRenderer(r, true, true);
 0150                    continue;
 151                }
 152
 153
 23154                float startTime = Time.realtimeSinceStartup;
 155
 156                //NOTE(Brian): Need to retrieve positions every frame to take into account
 157                //             world repositioning.
 23158                Vector3 playerPosition = CommonScriptableObjects.playerUnityPosition;
 159
 23160                Bounds bounds = r.GetSafeBounds();
 161
 23162                Vector3 boundingPoint = bounds.ClosestPoint(playerPosition);
 23163                float distance = Vector3.Distance(playerPosition, boundingPoint);
 23164                bool boundsContainsPlayer = bounds.Contains(playerPosition);
 23165                float boundsSize = bounds.size.magnitude;
 23166                float viewportSize = (boundsSize / distance) * Mathf.Rad2Deg;
 167
 23168                bool isEmissive = IsEmissive(r);
 23169                bool isOpaque = IsOpaque(r);
 170
 23171                float shadowTexelSize = ComputeShadowMapTexelSize(boundsSize, urpAsset.shadowDistance, urpAsset.mainLigh
 172
 23173                bool shouldBeVisible = !settings.enableObjectCulling || TestRendererVisibleRule(profile, viewportSize, d
 23174                bool shouldHaveShadow = !settings.enableShadowCulling || TestRendererShadowRule(profile, viewportSize, d
 175
 23176                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
 23192                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
 23201                SetCullingForRenderer(r, shouldBeVisible, shouldHaveShadow);
 202#if UNITY_EDITOR
 23203                DrawDebugGizmos(shouldBeVisible, bounds, boundingPoint);
 204#endif
 23205                timeBudgetCount += Time.realtimeSinceStartup - startTime;
 206            }
 18207        }
 208
 209        /// <summary>
 210        /// Main culling loop. Controlled by Start() and Stop() methods.
 211        /// </summary>
 212        IEnumerator UpdateCoroutine()
 213        {
 5214            while (true)
 215            {
 834216                bool shouldCheck = objectPositionsDirty || playerPositionDirty;
 217
 834218                playerPositionDirty = false;
 834219                objectPositionsDirty = false;
 220
 834221                if (!shouldCheck)
 222                {
 821223                    timeBudgetCount = 0;
 821224                    yield return null;
 769225                    continue;
 226                }
 227
 13228                yield return objectsTracker.PopulateRenderersList();
 229
 8230                if (resetObjectsNextFrame)
 231                {
 4232                    ResetObjects();
 4233                    resetObjectsNextFrame = false;
 234                }
 235
 8236                yield return ProcessAnimations();
 237
 8238                if (OnDataReport != null)
 239                {
 0240                    hiddenRenderers.Clear();
 0241                    shadowlessRenderers.Clear();
 242                }
 243
 8244                int profilesCount = profiles.Count;
 245
 44246                for (var pIndex = 0; pIndex < profilesCount; pIndex++)
 247                {
 16248                    yield return ProcessProfile(profiles[pIndex]);
 249                }
 250
 6251                RaiseDataReport();
 6252                timeBudgetCount = 0;
 6253                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        {
 33265            var targetMode = shouldHaveShadow ? ShadowCastingMode.On : ShadowCastingMode.Off;
 266
 33267            if (r.forceRenderingOff != !shouldBeVisible)
 14268                r.forceRenderingOff = !shouldBeVisible;
 269
 33270            if (r.shadowCastingMode != targetMode)
 15271                r.shadowCastingMode = targetMode;
 33272        }
 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        {
 12280            if (!settings.enableAnimationCulling)
 9281                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        {
 64319            var skinnedRenderers = objectsTracker.GetSkinnedRenderers();
 64320            var renderers = objectsTracker.GetRenderers();
 64321            var animations = objectsTracker.GetAnimations();
 322
 130323            for (var i = 0; i < skinnedRenderers?.Length; i++)
 324            {
 1325                if (skinnedRenderers[i] != null)
 1326                    skinnedRenderers[i].updateWhenOffscreen = true;
 327            }
 328
 130329            for (var i = 0; i < animations?.Length; i++)
 330            {
 1331                if (animations[i] != null)
 1332                    animations[i].cullingType = AnimationCullingType.AlwaysAnimate;
 333            }
 334
 654335            for (var i = 0; i < renderers?.Length; i++)
 336            {
 263337                if (renderers[i] != null)
 263338                    renderers[i].forceRenderingOff = false;
 339            }
 64340        }
 341
 342        public void Dispose()
 343        {
 64344            objectsTracker.Dispose();
 64345            Stop();
 64346        }
 347
 348        public void Initialize()
 349        {
 55350            Start();
 55351        }
 352
 353        /// <summary>
 354        /// Method suscribed to renderer state change
 355        /// </summary>
 356        private void OnRendererStateChange(bool rendererState, bool oldRendererState)
 357        {
 3358            if (!running)
 0359                return;
 360
 3361            MarkDirty();
 362
 3363            if (rendererState)
 1364                StartInternal();
 365            else
 2366                StopInternal();
 2367        }
 368
 369        /// <summary>
 370        /// Method suscribed to playerUnityPosition change
 371        /// </summary>
 82372        private void OnPlayerUnityPositionChange(Vector3 previous, Vector3 current) { playerPositionDirty = true; }
 373
 374        /// <summary>
 375        /// Sets the scene objects dirtiness.
 376        /// In the next update iteration, all the scene objects are going to be gathered.
 377        /// This method has performance impact.
 378        /// </summary>
 24379        public void MarkDirty() { objectPositionsDirty = true; }
 380
 381        /// <summary>
 382        /// Gets the scene objects dirtiness.
 383        /// </summary>
 3384        public bool IsDirty() { return objectPositionsDirty; }
 385
 386        /// <summary>
 387        /// Set settings. This will dirty the scene objects and has performance impact.
 388        /// </summary>
 389        /// <param name="settings">Settings to be set</param>
 390        public void SetSettings(CullingControllerSettings settings)
 391        {
 18392            this.settings = settings;
 18393            profiles = new List<CullingControllerProfile> { settings.rendererProfile, settings.skinnedRendererProfile };
 394
 18395            objectsTracker?.SetIgnoredLayersMask(settings.ignoredLayersMask);
 18396            objectsTracker?.MarkDirty();
 18397            MarkDirty();
 18398            resetObjectsNextFrame = true;
 18399        }
 400
 401        /// <summary>
 402        /// Get current settings copy. If you need to modify it, you must set them via SetSettings afterwards.
 403        /// </summary>
 404        /// <returns>Current settings object copy.</returns>
 7405        public CullingControllerSettings GetSettingsCopy() { return settings.Clone(); }
 406
 407        /// <summary>
 408        /// Enable or disable object visibility culling.
 409        /// </summary>
 410        /// <param name="enabled">If disabled, object visibility culling will be toggled.
 411        /// </param>
 412        public void SetObjectCulling(bool enabled)
 413        {
 0414            if (settings.enableObjectCulling == enabled)
 0415                return;
 416
 0417            settings.enableObjectCulling = enabled;
 0418            resetObjectsNextFrame = true;
 0419            MarkDirty();
 0420            objectsTracker?.MarkDirty();
 0421        }
 422
 423        /// <summary>
 424        /// Enable or disable animation culling.
 425        /// </summary>
 426        /// <param name="enabled">If disabled, animation culling will be toggled.</param>
 427        public void SetAnimationCulling(bool enabled)
 428        {
 0429            if (settings.enableAnimationCulling == enabled)
 0430                return;
 431
 0432            settings.enableAnimationCulling = enabled;
 0433            resetObjectsNextFrame = true;
 0434            MarkDirty();
 0435            objectsTracker?.MarkDirty();
 0436        }
 437
 438        /// <summary>
 439        /// Enable or disable shadow culling
 440        /// </summary>
 441        /// <param name="enabled">If disabled, no shadows will be toggled.</param>
 442        public void SetShadowCulling(bool enabled)
 443        {
 0444            if (settings.enableShadowCulling == enabled)
 0445                return;
 446
 0447            settings.enableShadowCulling = enabled;
 0448            resetObjectsNextFrame = true;
 0449            MarkDirty();
 0450            objectsTracker?.MarkDirty();
 0451        }
 452
 453        /// <summary>
 454        /// Fire the DataReport event. This will be useful for showing stats in a debug panel.
 455        /// </summary>
 456        private void RaiseDataReport()
 457        {
 66458            if (OnDataReport == null)
 66459                return;
 460
 0461            int rendererCount = (objectsTracker.GetRenderers()?.Length ?? 0) + (objectsTracker.GetSkinnedRenderers()?.Le
 462
 0463            OnDataReport.Invoke(rendererCount, hiddenRenderers.Count, shadowlessRenderers.Count);
 0464        }
 465
 466        /// <summary>
 467        /// Returns true if the culling loop is running
 468        /// </summary>
 469        public bool IsRunning()
 470        {
 6471            return updateCoroutine != null;
 472        }
 473
 474        /// <summary>
 475        /// Draw debug gizmos on the scene view.
 476        /// </summary>
 477        /// <param name="shouldBeVisible"></param>
 478        /// <param name="bounds"></param>
 479        /// <param name="boundingPoint"></param>
 480        private static void DrawDebugGizmos(bool shouldBeVisible, Bounds bounds, Vector3 boundingPoint)
 481        {
 23482            if (!shouldBeVisible)
 483            {
 8484                CullingControllerUtils.DrawBounds(bounds, Color.blue, 1);
 8485                CullingControllerUtils.DrawBounds(new Bounds() { center = boundingPoint, size = Vector3.one }, Color.red
 486            }
 23487        }
 488    }
 489}