| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.Linq; |
| | 4 | | using DCL.Shaders; |
| | 5 | | using MainScripts.DCL.Helpers.Utils; |
| | 6 | | using System.Diagnostics; |
| | 7 | | using UnityEngine; |
| | 8 | | using Debug = UnityEngine.Debug; |
| | 9 | | using logger = DCL.MeshCombinerLogger; |
| | 10 | |
|
| | 11 | |
|
| | 12 | | namespace DCL |
| | 13 | | { |
| | 14 | | public static class CombineLayerUtils |
| | 15 | | { |
| | 16 | | // This heuristic forces double-sided opaque objects to have backface culling. |
| | 17 | | // As many wearables are incorrectly modeled as double-sided, this greatly increases |
| | 18 | | // the cases of avatars rendered with one draw call. Temporarily disabled until some wearables are fixed. |
| 1 | 19 | | public static bool ENABLE_CULL_OPAQUE_HEURISTIC = false; |
| | 20 | |
|
| | 21 | | private const int MAX_TEXTURE_ID_COUNT = 12; |
| 1 | 22 | | private static readonly int[] textureIds = { ShaderUtils.BaseMap, ShaderUtils.EmissionMap }; |
| | 23 | |
|
| | 24 | | /// <summary> |
| | 25 | | /// This method takes a skinned mesh renderer list and turns it into a series of CombineLayer elements.<br/> |
| | 26 | | /// |
| | 27 | | /// Each CombineLayer element represents a combining group, and the renderers are grouped using a set of criteri |
| | 28 | | /// |
| | 29 | | /// <ul> |
| | 30 | | /// <li>Each layer must correspond to renderers that share the same cull mode and blend state.</li> |
| | 31 | | /// <li>Each layer can't contain renderers that sum up over MAX_TEXTURE_ID_COUNT textures.</li> |
| | 32 | | /// </ul> |
| | 33 | | /// Those layers can later be used to combine meshes as efficiently as possible by encoding their map |
| | 34 | | /// samplers to UV attributes. This allows the usage of a special shader that can branch samplers according to |
| | 35 | | /// the UV data. By encoding the samplers this way, materials that use different textures and uniform values |
| | 36 | | /// can be grouped together, and thus, meshes can be further combined. |
| | 37 | | /// </summary> |
| | 38 | | /// <param name="renderers">List of renderers to slice.</param> |
| | 39 | | /// <returns>List of CombineLayer objects that can be used to produce a highly optimized combined mesh.</returns |
| | 40 | | internal static bool TrySlice(IReadOnlyList<SkinnedMeshRenderer> renderers, CombineLayersList result) |
| | 41 | | { |
| | 42 | | logger.Log("Slice Start!"); |
| | 43 | |
|
| 14 | 44 | | using (var rental = PoolUtils.RentListOfDisposables<CombineLayer>()) |
| | 45 | | { |
| 14 | 46 | | var rawLayers = rental.GetList(); |
| 14 | 47 | | SliceByRenderState.Execute(renderers, rawLayers, ENABLE_CULL_OPAQUE_HEURISTIC); |
| | 48 | |
|
| | 49 | | logger.Log($"Preparing slice. Found {rawLayers.Count} groups."); |
| | 50 | |
|
| | 51 | | // Now, we sub-slice the rawLayers. |
| | 52 | | // A single rawLayer will be sub-sliced if the textures exceed the sampler limit (12 in this case). |
| | 53 | | // Also, in this step the textureToId map is populated. |
| | 54 | |
|
| 84 | 55 | | for (int i = 0; i < rawLayers.Count; i++) |
| | 56 | | { |
| 28 | 57 | | var rawLayer = rawLayers[i]; |
| | 58 | |
|
| | 59 | | logger.Log($"Processing group {i}. Renderer count: {rawLayer.Renderers.Count}. cullMode: {rawLayer.c |
| 28 | 60 | | SubsliceLayerByTextures(rawLayer, result); |
| | 61 | | } |
| 14 | 62 | | } |
| | 63 | |
|
| | 64 | | // No valid materials were found |
| 14 | 65 | | if (result.Count == 1 && result[0].textureToId.Count == 0 && result[0].Renderers.Count == 0) |
| | 66 | | { |
| | 67 | | logger.Log("Slice End Fail!"); |
| 0 | 68 | | return false; |
| | 69 | | } |
| | 70 | |
|
| 14 | 71 | | result.Sanitize(); |
| | 72 | |
|
| | 73 | | [Conditional(MeshCombinerLogger.COMPILATION_DEFINE)] |
| | 74 | | static void LogLayers(CombineLayersList result) |
| | 75 | | { |
| 0 | 76 | | int layInd = 0; |
| | 77 | |
|
| 0 | 78 | | foreach (var layer in result.Layers) |
| | 79 | | { |
| 0 | 80 | | string rendererNames = layer.Renderers |
| 0 | 81 | | .Select((x) => $"{x.transform.parent.name}") |
| 0 | 82 | | .Aggregate((i, j) => i + "\n" + j); |
| | 83 | |
|
| | 84 | | logger.Log($"Layer index: {layInd} ... renderer count: {layer.Renderers.Count} ... textures found: { |
| 0 | 85 | | layInd++; |
| | 86 | | } |
| 0 | 87 | | } |
| | 88 | |
|
| | 89 | | LogLayers(result); |
| | 90 | |
|
| | 91 | | logger.Log("Slice End Success!"); |
| 14 | 92 | | return true; |
| | 93 | | } |
| | 94 | |
|
| | 95 | | /// <summary> |
| | 96 | | /// <p> |
| | 97 | | /// This method takes a single CombineLayer and sub-slices it according to the texture count of the |
| | 98 | | /// contained renderers of the given layer. |
| | 99 | | /// </p> |
| | 100 | | /// <p> |
| | 101 | | /// The resulting layers will have their <i>textureToId</i> field populated with the found textures. |
| | 102 | | /// The <i>textureToId</i> int value is what will have to be passed over the uv attributes of the combined meshe |
| | 103 | | /// </p> |
| | 104 | | /// </summary> |
| | 105 | | /// <param name="layer">The CombineLayer layer to subdivide and populate by the ids.</param> |
| | 106 | | /// <returns>A list that at least is guaranteed to contain the given layer. |
| | 107 | | /// If the given layer exceeds the max texture count, more than a layer can be returned. |
| | 108 | | /// </returns> |
| | 109 | | internal static void SubsliceLayerByTextures(CombineLayer layer, CombineLayersList results) |
| | 110 | | { |
| | 111 | | int textureId; |
| | 112 | |
|
| | 113 | | CombineLayer currentResultLayer; |
| | 114 | |
|
| | 115 | | void AddLayerToResult() |
| | 116 | | { |
| 31 | 117 | | textureId = 0; |
| 31 | 118 | | currentResultLayer = CombineLayer.Rent(layer.cullMode, layer.isOpaque); |
| 31 | 119 | | results.Add(currentResultLayer); |
| 31 | 120 | | } |
| | 121 | |
|
| 30 | 122 | | AddLayerToResult(); |
| | 123 | |
|
| 262 | 124 | | for (int rendererIndex = 0; rendererIndex < layer.Renderers.Count; rendererIndex++) |
| | 125 | | { |
| 101 | 126 | | var r = layer.Renderers[rendererIndex]; |
| | 127 | |
|
| 101 | 128 | | using var materialRent = PoolUtils.RentList<Material>(); |
| 101 | 129 | | var mats = materialRent.GetList(); |
| | 130 | |
|
| 101 | 131 | | r.GetSharedMaterials(mats); |
| | 132 | |
|
| 101 | 133 | | using var mapIdsToInsertRental = PoolUtils.RentDictionary<Texture2D, int>(); |
| 101 | 134 | | var mapIdsToInsert = mapIdsToInsertRental.GetDictionary(); |
| | 135 | |
|
| 101 | 136 | | AddMapIds( |
| | 137 | | currentResultLayer.textureToId, |
| | 138 | | mapIdsToInsert, |
| | 139 | | mats, |
| | 140 | | textureId); |
| | 141 | |
|
| | 142 | | // The renderer is too big to fit in a single layer? (This should never happen). |
| 101 | 143 | | if (mapIdsToInsert.Count > MAX_TEXTURE_ID_COUNT) |
| | 144 | | { |
| 0 | 145 | | logger.LogWarning("The renderer is too big to fit in a single layer? (This should never happen)."); |
| 0 | 146 | | AddLayerToResult(); |
| 0 | 147 | | continue; |
| | 148 | | } |
| | 149 | |
|
| | 150 | | // The renderer can fit in a single layer. |
| | 151 | | // But can't fit in this one, as previous renderers filled this layer out. |
| 101 | 152 | | if (textureId + mapIdsToInsert.Count > MAX_TEXTURE_ID_COUNT) |
| | 153 | | { |
| 0 | 154 | | rendererIndex--; |
| 0 | 155 | | AddLayerToResult(); |
| 0 | 156 | | continue; |
| | 157 | | } |
| | 158 | |
|
| | 159 | | // put GetMapIds result into currentLayer id map. |
| 386 | 160 | | foreach (var kvp in mapIdsToInsert) |
| 92 | 161 | | currentResultLayer.textureToId[kvp.Key] = kvp.Value; |
| | 162 | |
|
| 101 | 163 | | results.AddRenderer(currentResultLayer, r); |
| | 164 | |
|
| 101 | 165 | | textureId += mapIdsToInsert.Count; |
| | 166 | |
|
| 101 | 167 | | if (textureId >= MAX_TEXTURE_ID_COUNT) |
| 1 | 168 | | AddLayerToResult(); |
| | 169 | | } |
| | 170 | |
|
| | 171 | | [Conditional(MeshCombinerLogger.COMPILATION_DEFINE)] |
| | 172 | | static void LogResults(CombineLayersList results) |
| | 173 | | { |
| 0 | 174 | | for (int i = 0; i < results.Count; i++) |
| | 175 | | { |
| 0 | 176 | | var c = results[i]; |
| 0 | 177 | | Debug.Log($"layer {i} - {c}"); |
| | 178 | | } |
| 0 | 179 | | } |
| | 180 | |
|
| | 181 | | LogResults(results); |
| 30 | 182 | | } |
| | 183 | |
|
| | 184 | | internal static void AddMapIds(IReadOnlyDictionary<Texture2D, int> refDict, IDictionary<Texture2D, int> candidat |
| | 185 | | { |
| 420 | 186 | | for (int i = 0; i < mats.Count; i++) |
| | 187 | | { |
| 107 | 188 | | var mat = mats[i]; |
| | 189 | |
|
| 107 | 190 | | if (mat == null) |
| | 191 | | continue; |
| | 192 | |
|
| 642 | 193 | | for (int texIdIndex = 0; texIdIndex < textureIds.Length; texIdIndex++) |
| | 194 | | { |
| 214 | 195 | | var texture = (Texture2D)mat.GetTexture(textureIds[texIdIndex]); |
| | 196 | |
|
| 214 | 197 | | if (texture == null) |
| | 198 | | continue; |
| | 199 | |
|
| 136 | 200 | | if (refDict.ContainsKey(texture) || candidates.ContainsKey(texture)) |
| | 201 | | continue; |
| | 202 | |
|
| 102 | 203 | | candidates.Add(texture, startingId); |
| 102 | 204 | | startingId++; |
| | 205 | | } |
| | 206 | | } |
| 103 | 207 | | } |
| | 208 | | } |
| | 209 | | } |