< Summary

Class:DCL.CombineLayerUtils
Assembly:AvatarMeshCombiner
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/CombineLayerUtils.cs
Covered lines:77
Uncovered lines:21
Coverable lines:98
Total lines:338
Line coverage:78.5% (77 of 98)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
CombineLayerUtils()0%330100%
Slice(...)0%15.2710062.5%
SubsliceLayerByTextures(...)0%10.979071.05%
SliceByRenderState(...)0%7.047090.48%
GetMapIds(...)0%770100%
IsOpaque(...)0%3.143075%
IsOpaque(...)0%110100%
GetCullMode(...)0%110100%
GetCullMode(...)0%110100%
GetCullModeWithoutCullOff(...)0%220100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/CombineLayerUtils.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Collections.ObjectModel;
 4using System.Linq;
 5using DCL.Helpers;
 6using UnityEngine;
 7using UnityEngine.Rendering;
 8
 9namespace DCL
 10{
 11    public static class CombineLayerUtils
 12    {
 13        // This heuristic forces double-sided opaque objects to have backface culling.
 14        // As many wearables are incorrectly modeled as double-sided, this greatly increases
 15        // the cases of avatars rendered with one draw call. Temporarily disabled until some wearables are fixed.
 116        public static bool ENABLE_CULL_OPAQUE_HEURISTIC = false;
 17
 118        private static bool VERBOSE = false;
 19        private const int MAX_TEXTURE_ID_COUNT = 12;
 120        private static ILogger logger = new Logger(Debug.unityLogger.logHandler) { filterLogType = VERBOSE ? LogType.Log
 121        private static readonly int[] textureIds = new int[] { ShaderUtils.BaseMap, ShaderUtils.EmissionMap };
 22
 23        /// <summary>
 24        /// This method takes a skinned mesh renderer list and turns it into a series of CombineLayer elements.<br/>
 25        ///
 26        /// Each CombineLayer element represents a combining group, and the renderers are grouped using a set of criteri
 27        ///
 28        /// <ul>
 29        /// <li>Each layer must correspond to renderers that share the same cull mode and blend state.</li>
 30        /// <li>Each layer can't contain renderers that sum up over MAX_TEXTURE_ID_COUNT textures.</li>
 31        /// </ul>
 32        /// Those layers can later be used to combine meshes as efficiently as possible by encoding their map
 33        /// samplers to UV attributes. This allows the usage of a special shader that can branch samplers according to
 34        /// the UV data. By encoding the samplers this way, materials that use different textures and uniform values
 35        /// can be grouped together, and thus, meshes can be further combined.
 36        /// </summary>
 37        /// <param name="renderers">List of renderers to slice.</param>
 38        /// <returns>List of CombineLayer objects that can be used to produce a highly optimized combined mesh.</returns
 39        internal static List<CombineLayer> Slice(SkinnedMeshRenderer[] renderers)
 40        {
 1841            logger.Log("Slice Start!");
 42
 1843            var rawLayers = SliceByRenderState(renderers);
 44
 1845            logger.Log($"Preparing slice. Found {rawLayers.Count} groups.");
 46
 47            //
 48            // Now, we sub-slice the rawLayers.
 49            // A single rawLayer will be sub-sliced if the textures exceed the sampler limit (12 in this case).
 50            // Also, in this step the textureToId map is populated.
 51            //
 1852            List<CombineLayer> result = new List<CombineLayer>();
 53
 9854            for (int i = 0; i < rawLayers.Count; i++)
 55            {
 3156                var rawLayer = rawLayers[i];
 3157                logger.Log($"Processing group {i}. Renderer count: {rawLayer.renderers.Count}. cullMode: {rawLayer.cullM
 3158                result.AddRange(SubsliceLayerByTextures(rawLayer));
 59            }
 60
 61            // No valid materials were found
 1862            if ( result.Count == 1 && result[0].textureToId.Count == 0 && result[0].renderers.Count == 0)
 63            {
 064                logger.Log("Slice End Fail!");
 065                return null;
 66            }
 67
 4968            result = result.Where( x => x.renderers != null && x.renderers.Count > 0 ).ToList();
 69
 1870            if ( VERBOSE )
 71            {
 072                int layInd = 0;
 073                foreach ( var layer in result )
 74                {
 075                    string rendererNames = layer.renderers
 076                        .Select( (x) => $"{x.transform.parent.name}" )
 077                        .Aggregate( (i, j) => i + "\n" + j);
 78
 079                    logger.Log($"Layer index: {layInd} ... renderer count: {layer.renderers.Count} ... textures found: {
 080                    layInd++;
 81                }
 82            }
 83
 1884            logger.Log("Slice End Success!");
 1885            return result;
 86        }
 87
 88        /// <summary>
 89        /// <p>
 90        /// This method takes a single CombineLayer and sub-slices it according to the texture count of the
 91        /// contained renderers of the given layer.
 92        /// </p>
 93        /// <p>
 94        /// The resulting layers will have their <i>textureToId</i> field populated with the found textures.
 95        /// The <i>textureToId</i> int value is what will have to be passed over the uv attributes of the combined meshe
 96        /// </p>
 97        /// </summary>
 98        /// <param name="layer">The CombineLayer layer to subdivide and populate by the ids.</param>
 99        /// <returns>A list that at least is guaranteed to contain the given layer.
 100        /// If the given layer exceeds the max texture count, more than a layer can be returned.
 101        /// </returns>
 102        internal static List<CombineLayer> SubsliceLayerByTextures( CombineLayer layer )
 103        {
 33104            var result = new List<CombineLayer>();
 33105            int textureId = 0;
 106
 33107            bool shouldAddLayerToResult = true;
 33108            CombineLayer currentResultLayer = null;
 109
 264110            for (int rendererIndex = 0; rendererIndex < layer.renderers.Count; rendererIndex++)
 111            {
 99112                var r = layer.renderers[rendererIndex];
 113
 99114                if ( shouldAddLayerToResult )
 115                {
 34116                    shouldAddLayerToResult = false;
 34117                    textureId = 0;
 118
 34119                    currentResultLayer = new CombineLayer
 120                    {
 121                        cullMode = layer.cullMode,
 122                        isOpaque = layer.isOpaque
 123                    };
 124
 34125                    result.Add(currentResultLayer);
 126                }
 127
 99128                var mats = r.sharedMaterials;
 129
 99130                var mapIdsToInsert = GetMapIds(
 131                    new ReadOnlyDictionary<Texture2D, int>(currentResultLayer.textureToId),
 132                    mats,
 133                    textureId );
 134
 135                // The renderer is too big to fit in a single layer? (This should never happen).
 99136                if (mapIdsToInsert.Count > MAX_TEXTURE_ID_COUNT)
 137                {
 0138                    logger.Log(LogType.Warning, "The renderer is too big to fit in a single layer? (This should never ha
 0139                    shouldAddLayerToResult = true;
 0140                    continue;
 141                }
 142
 143                // The renderer can fit in a single layer.
 144                // But can't fit in this one, as previous renderers filled this layer out.
 99145                if ( textureId + mapIdsToInsert.Count > MAX_TEXTURE_ID_COUNT )
 146                {
 0147                    rendererIndex--;
 0148                    shouldAddLayerToResult = true;
 0149                    continue;
 150                }
 151
 152                // put GetMapIds result into currentLayer id map.
 380153                foreach ( var kvp in mapIdsToInsert )
 154                {
 91155                    currentResultLayer.textureToId[ kvp.Key ] = kvp.Value;
 156                }
 157
 99158                currentResultLayer.renderers.Add(r);
 159
 99160                textureId += mapIdsToInsert.Count;
 161
 99162                if ( textureId >= MAX_TEXTURE_ID_COUNT )
 163                {
 1164                    shouldAddLayerToResult = true;
 165                }
 166            }
 167
 33168            if ( VERBOSE )
 169            {
 0170                for (int i = 0; i < result.Count; i++)
 171                {
 0172                    var c = result[i];
 0173                    Debug.Log($"layer {i} - {c}");
 174                }
 175            }
 176
 33177            return result;
 178        }
 179
 180        /// <summary>
 181        /// <p>
 182        /// This method takes a skinned mesh renderer list and turns it into a series of CombineLayer elements.
 183        /// Each CombineLayer element represents a combining group, and the renderers are grouped using a set of criteri
 184        /// </p>
 185        /// <p>
 186        /// For SliceByRenderState, the returned CombineLayer list will be grouped according to shared cull mode and
 187        /// blend state.
 188        /// </p>
 189        /// </summary>
 190        /// <param name="renderers">List of renderers to slice.</param>
 191        /// <returns>List of CombineLayer objects that can be used to produce a highly optimized combined mesh.</returns
 192        internal static List<CombineLayer> SliceByRenderState(SkinnedMeshRenderer[] renderers)
 193        {
 21194            List<CombineLayer> result = new List<CombineLayer>();
 195
 196            // Group renderers on opaque and transparent materials
 21197            var rendererByOpaqueMode = renderers.GroupBy( IsOpaque );
 198
 199            // Then, make subgroups to divide them between culling modes
 110200            foreach ( var byOpaqueMode in rendererByOpaqueMode )
 201            {
 202                // For opaque renderers, we replace the CullOff value by CullBack to reduce group count,
 203                // This workarounds many opaque wearables that use Culling Off by mistake.
 34204                Func<SkinnedMeshRenderer, CullMode> getCullModeFunc = null;
 205
 34206                if ( ENABLE_CULL_OPAQUE_HEURISTIC )
 207                {
 0208                    getCullModeFunc = byOpaqueMode.Key ? new Func<SkinnedMeshRenderer, CullMode>(GetCullModeWithoutCullO
 0209                }
 210                else
 211                {
 34212                    getCullModeFunc = GetCullMode;
 213                }
 214
 34215                var rendererByCullingMode = byOpaqueMode.GroupBy( getCullModeFunc );
 216
 146217                foreach ( var byCulling in rendererByCullingMode )
 218                {
 39219                    var byCullingRenderers = byCulling.ToList();
 220
 39221                    CombineLayer layer = new CombineLayer();
 39222                    result.Add(layer);
 39223                    layer.cullMode = byCulling.Key;
 39224                    layer.isOpaque = byOpaqueMode.Key;
 39225                    layer.renderers = byCullingRenderers;
 226                }
 227            }
 228
 229            /*
 230            * The grouping outcome ends up like this:
 231            *
 232            *                 Opaque           Transparent
 233            *             /     |     \        /    |    \
 234            *          Back - Front - Off - Back - Front - Off -> rendererGroups
 235            */
 236
 21237            return result;
 238        }
 239
 240        /// <summary>
 241        ///
 242        /// </summary>
 243        /// <param name="refDict"></param>
 244        /// <param name="mats"></param>
 245        /// <param name="startingId"></param>
 246        /// <returns></returns>
 247        internal static Dictionary<Texture2D, int> GetMapIds(ReadOnlyDictionary<Texture2D, int> refDict, in Material[] m
 248        {
 101249            var result = new Dictionary<Texture2D, int>();
 250
 412251            for ( int i = 0; i < mats.Length; i++ )
 252            {
 105253                var mat = mats[i];
 254
 105255                if (mat == null)
 256                    continue;
 257
 630258                for ( int texIdIndex = 0; texIdIndex < textureIds.Length; texIdIndex++ )
 259                {
 210260                    var texture = (Texture2D)mat.GetTexture(textureIds[texIdIndex]);
 261
 210262                    if ( texture == null )
 263                        continue;
 264
 131265                    if ( refDict.ContainsKey(texture) || result.ContainsKey(texture) )
 266                        continue;
 267
 101268                    result.Add(texture, startingId);
 101269                    startingId++;
 270                }
 271            }
 272
 101273            return result;
 274        }
 275
 276        /// <summary>
 277        /// Determines if the given renderer is going to be enqueued at the opaque section of the rendering pipeline.
 278        /// </summary>
 279        /// <param name="material">Material to be checked</param>
 280        /// <returns>True if its opaque</returns>
 281        internal static bool IsOpaque(Material material)
 282        {
 122283            if (material == null)
 0284                return true;
 285
 122286            bool isTransparent = material.HasProperty(ShaderUtils.ZWrite) &&
 287                                 (int) material.GetFloat(ShaderUtils.ZWrite) == 0;
 288
 122289            return !isTransparent;
 290        }
 291
 292        /// <summary>
 293        /// Determines if the given renderer is going to be enqueued at the opaque section of the rendering pipeline.
 294        /// </summary>
 295        /// <param name="renderer">Renderer to be checked.</param>
 296        /// <returns>True if its opaque</returns>
 297        internal static bool IsOpaque(Renderer renderer)
 298        {
 116299            return IsOpaque(renderer.sharedMaterials[0]);
 300        }
 301
 302        /// <summary>
 303        ///
 304        /// </summary>
 305        /// <param name="material"></param>
 306        /// <returns></returns>
 307        internal static CullMode GetCullMode(Material material)
 308        {
 122309            CullMode result = (CullMode)material.GetInt( ShaderUtils.Cull );
 122310            return result;
 311        }
 312
 313        /// <summary>
 314        ///
 315        /// </summary>
 316        /// <param name="renderer"></param>
 317        /// <returns></returns>
 318        internal static CullMode GetCullMode(Renderer renderer)
 319        {
 116320            return GetCullMode(renderer.sharedMaterials[0]);
 321        }
 322
 323        /// <summary>
 324        ///
 325        /// </summary>
 326        /// <param name="renderer"></param>
 327        /// <returns></returns>
 328        internal static CullMode GetCullModeWithoutCullOff(Renderer renderer)
 329        {
 3330            CullMode result = GetCullMode(renderer.sharedMaterials[0]);
 331
 3332            if (result == CullMode.Off)
 1333                result = CullMode.Back;
 334
 3335            return result;
 336        }
 337    }
 338}