MB_TextureArrays.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System;
  4. using UnityEngine;
  5. namespace DigitalOpus.MB.Core
  6. {
  7. public class MB_TextureArrays
  8. {
  9. internal class TexturePropertyData
  10. {
  11. public bool[] doMips;
  12. public int[] numMipMaps;
  13. public TextureFormat[] formats;
  14. public Vector2[] sizes;
  15. }
  16. internal static bool[] DetermineWhichPropertiesHaveTextures(MB_AtlasesAndRects[] resultAtlasesAndRectSlices)
  17. {
  18. bool[] hasTexForProperty = new bool[resultAtlasesAndRectSlices[0].texPropertyNames.Length];
  19. for (int i = 0; i < hasTexForProperty.Length; i++)
  20. {
  21. hasTexForProperty[i] = false;
  22. }
  23. int numSlices = resultAtlasesAndRectSlices.Length;
  24. for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++)
  25. {
  26. MB_AtlasesAndRects slice = resultAtlasesAndRectSlices[sliceIdx];
  27. Debug.Assert(slice.texPropertyNames.Length == hasTexForProperty.Length);
  28. for (int propIdx = 0; propIdx < hasTexForProperty.Length; propIdx++)
  29. {
  30. if (slice.atlases[propIdx] != null)
  31. {
  32. hasTexForProperty[propIdx] = true;
  33. }
  34. }
  35. }
  36. return hasTexForProperty;
  37. }
  38. /// <summary>
  39. /// Creates one texture array per texture property.
  40. /// </summary>
  41. /// <returns></returns>
  42. internal static Texture2DArray[] CreateTextureArraysForResultMaterial(TexturePropertyData texPropertyData, MB_AtlasesAndRects[] resultAtlasesAndRectSlices,
  43. bool[] hasTexForProperty, MB3_TextureCombiner combiner, MB2_LogLevel LOG_LEVEL)
  44. {
  45. Debug.Assert(texPropertyData.sizes.Length == hasTexForProperty.Length);
  46. // ASSUMPTION all slices in the same format and the same size, alpha channel and mipMapCount
  47. string[] texPropertyNames = resultAtlasesAndRectSlices[0].texPropertyNames;
  48. Debug.Assert(texPropertyNames.Length == hasTexForProperty.Length);
  49. Texture2DArray[] texArrays = new Texture2DArray[texPropertyNames.Length];
  50. // Each texture property (_MainTex, _Bump, ...) becomes a Texture2DArray
  51. for (int propIdx = 0; propIdx < texPropertyNames.Length; propIdx++)
  52. {
  53. if (!hasTexForProperty[propIdx]) continue;
  54. int numSlices = resultAtlasesAndRectSlices.Length;
  55. int w = (int)texPropertyData.sizes[propIdx].x;
  56. int h = (int)texPropertyData.sizes[propIdx].y;
  57. int numMips = (int)texPropertyData.numMipMaps[propIdx];
  58. TextureFormat format = texPropertyData.formats[propIdx];
  59. bool doMipMaps = texPropertyData.doMips[propIdx];
  60. Texture2DArray texArray = new Texture2DArray(w, h, numSlices, format, doMipMaps);
  61. if (LOG_LEVEL >= MB2_LogLevel.info) Debug.LogFormat("Creating Texture2DArray for property: {0} w: {1} h: {2} format: {3} doMips: {4}", texPropertyNames[propIdx], w, h, format, doMipMaps);
  62. for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++)
  63. {
  64. Debug.Assert(resultAtlasesAndRectSlices[sliceIdx].atlases.Length == texPropertyNames.Length);
  65. Debug.Assert(resultAtlasesAndRectSlices[sliceIdx].texPropertyNames[propIdx] == texPropertyNames[propIdx]);
  66. Texture2D srcTex = resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx];
  67. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.LogFormat("Slice: {0} texture: {1}", sliceIdx, srcTex);
  68. bool isCopy = false;
  69. if (srcTex == null)
  70. {
  71. // Slices might not have all textures create a dummy if needed.
  72. srcTex = combiner._createTemporaryTexture(texPropertyNames[propIdx], w, h, format, doMipMaps);
  73. }
  74. Debug.Assert(srcTex.width == texArray.width, "Source texture is not the same width as the texture array " + srcTex);
  75. Debug.Assert(srcTex.height == texArray.height, "Source texture is not the same height as the texture array " + srcTex);
  76. Debug.Assert(srcTex.mipmapCount == numMips, "Source texture does have not the same number of mips as the texture array: " + srcTex + " numMipsTex: " + srcTex.mipmapCount + " numMipsTexArray: " + numMips + " texDims: " + srcTex.width + "x" + srcTex.height);
  77. Debug.Assert(srcTex.format == format, "Formats should have been converted before this. Texture: " + srcTex + "Source: " + srcTex.format + " Targ: " + format);
  78. for (int mipIdx = 0; mipIdx < numMips; mipIdx++)
  79. {
  80. Graphics.CopyTexture(srcTex, 0, mipIdx, texArray, sliceIdx, mipIdx);
  81. }
  82. if (isCopy) MB_Utility.Destroy(srcTex);
  83. }
  84. texArray.Apply();
  85. texArrays[propIdx] = texArray;
  86. }
  87. return texArrays;
  88. }
  89. internal static bool ConvertTexturesToReadableFormat(TexturePropertyData texturePropertyData,
  90. MB_AtlasesAndRects[] resultAtlasesAndRectSlices,
  91. bool[] hasTexForProperty, List<ShaderTextureProperty> textureShaderProperties,
  92. MB3_TextureCombiner combiner,
  93. MB2_LogLevel logLevel,
  94. List<Texture2D> createdTemporaryTextureAssets,
  95. MB2_EditorMethodsInterface textureEditorMethods)
  96. {
  97. for (int propIdx = 0; propIdx < hasTexForProperty.Length; propIdx++)
  98. {
  99. if (!hasTexForProperty[propIdx]) continue;
  100. TextureFormat format = texturePropertyData.formats[propIdx];
  101. if (!textureEditorMethods.TextureImporterFormatExistsForTextureFormat(format))
  102. {
  103. Debug.LogError("Could not find target importer format matching " + format);
  104. return false;
  105. }
  106. int numSlices = resultAtlasesAndRectSlices.Length;
  107. int targetWidth = (int)texturePropertyData.sizes[propIdx].x;
  108. int targetHeight = (int)texturePropertyData.sizes[propIdx].y;
  109. for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++)
  110. {
  111. Texture2D sliceTex = resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx];
  112. Debug.Assert(sliceTex != null, "sliceIdx " + sliceIdx + " " + propIdx);
  113. if (sliceTex != null)
  114. {
  115. if (!MBVersion.IsTextureReadable(sliceTex))
  116. {
  117. textureEditorMethods.SetReadWriteFlag(sliceTex, true, true);
  118. }
  119. bool isAsset = textureEditorMethods.IsAnAsset(sliceTex);
  120. if (logLevel >= MB2_LogLevel.trace) Debug.Log("Considering format of texture: " + sliceTex + " format:" + sliceTex.format);
  121. if ((sliceTex.width != targetWidth || sliceTex.height != targetHeight) ||
  122. (!isAsset && sliceTex.format != format))
  123. {
  124. // Do this the horrible hard way. It is only possible to resize textures in TrueColor formats,
  125. // And only possible to switch formats using the Texture importer.
  126. // Create a resized temporary texture asset in ARGB32 format. Then set its texture format and reimport
  127. resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx] = textureEditorMethods.CreateTemporaryAssetCopy(sliceTex, targetWidth, targetHeight, format, logLevel);
  128. createdTemporaryTextureAssets.Add(resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx]);
  129. }
  130. else if (sliceTex.format != format)
  131. {
  132. textureEditorMethods.AddTextureFormat(sliceTex, format, textureShaderProperties[propIdx].isNormalMap);
  133. }
  134. }
  135. else
  136. {
  137. }
  138. if (resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx].format != format)
  139. {
  140. Debug.LogError("Could not convert texture to format " + format +
  141. ". This can happen if the target build platform in build settings does not support textures in this format." +
  142. " It may be necessary to switch the build platform in order to build texture arrays in this format.");
  143. return false;
  144. }
  145. }
  146. }
  147. return true;
  148. }
  149. /// <summary>
  150. /// Third coord is num mipmaps.
  151. /// </summary>
  152. internal static void FindBestSizeAndMipCountAndFormatForTextureArrays(List<ShaderTextureProperty> texPropertyNames, int maxAtlasSize, MB_TextureArrayFormatSet targetFormatSet, MB_AtlasesAndRects[] resultAtlasesAndRectSlices,
  153. TexturePropertyData texturePropertyData)
  154. {
  155. texturePropertyData.sizes = new Vector2[texPropertyNames.Count];
  156. texturePropertyData.doMips = new bool[texPropertyNames.Count];
  157. texturePropertyData.numMipMaps = new int[texPropertyNames.Count];
  158. texturePropertyData.formats = new TextureFormat[texPropertyNames.Count];
  159. for (int propIdx = 0; propIdx < texPropertyNames.Count; propIdx++)
  160. {
  161. int numSlices = resultAtlasesAndRectSlices.Length;
  162. texturePropertyData.sizes[propIdx] = new Vector3(16, 16, 1);
  163. bool hasMips = false;
  164. int mipCount = 1;
  165. for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++)
  166. {
  167. Debug.Assert(resultAtlasesAndRectSlices[sliceIdx].atlases.Length == texPropertyNames.Count);
  168. Debug.Assert(resultAtlasesAndRectSlices[sliceIdx].texPropertyNames[propIdx] == texPropertyNames[propIdx].name);
  169. Texture2D sliceTex = resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx];
  170. if (sliceTex != null)
  171. {
  172. if (sliceTex.mipmapCount > 1) hasMips = true;
  173. mipCount = Mathf.Max(mipCount, sliceTex.mipmapCount);
  174. texturePropertyData.sizes[propIdx].x = Mathf.Min(Mathf.Max(texturePropertyData.sizes[propIdx].x, sliceTex.width), maxAtlasSize);
  175. texturePropertyData.sizes[propIdx].y = Mathf.Min(Mathf.Max(texturePropertyData.sizes[propIdx].y, sliceTex.height), maxAtlasSize);
  176. //texturePropertyData.sizes[propIdx].z = Mathf.Max(texturePropertyData.sizes[propIdx].z, sliceTex.mipmapCount);
  177. texturePropertyData.formats[propIdx] = targetFormatSet.GetFormatForProperty(texPropertyNames[propIdx].name);
  178. }
  179. }
  180. int numberMipsForMaxAtlasSize = Mathf.CeilToInt(Mathf.Log(maxAtlasSize, 2)) + 1;
  181. texturePropertyData.numMipMaps[propIdx] = Mathf.Min(numberMipsForMaxAtlasSize, mipCount);
  182. texturePropertyData.doMips[propIdx] = hasMips;
  183. }
  184. }
  185. internal static IEnumerator _CreateAtlasesCoroutineSingleResultMaterial(int resMatIdx,
  186. MB_TextureArrayResultMaterial bakedMatsAndSlicesResMat,
  187. MB_MultiMaterialTexArray resMatConfig,
  188. List<GameObject> objsToMesh,
  189. MB3_TextureCombiner combiner,
  190. MB_TextureArrayFormatSet[] textureArrayOutputFormats,
  191. MB_MultiMaterialTexArray[] resultMaterialsTexArray,
  192. List<ShaderTextureProperty> customShaderProperties,
  193. ProgressUpdateDelegate progressInfo,
  194. MB3_TextureCombiner.CreateAtlasesCoroutineResult coroutineResult,
  195. bool saveAtlasesAsAssets = false,
  196. MB2_EditorMethodsInterface editorMethods = null,
  197. float maxTimePerFrame = .01f)
  198. {
  199. MB2_LogLevel LOG_LEVEL = combiner.LOG_LEVEL;
  200. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Baking atlases for result material " + resMatIdx + " num slices:" + resMatConfig.slices.Count);
  201. // Each result material can be one set of slices per textureProperty. Each slice can be an atlas.
  202. // Create atlases for each slice.
  203. List<MB3_TextureCombiner.TemporaryTexture> generatedTemporaryAtlases = new List<MB3_TextureCombiner.TemporaryTexture>();
  204. {
  205. combiner.saveAtlasesAsAssets = false; // Don't want generated atlas slices to be assets
  206. List<MB_TexArraySlice> slicesConfig = resMatConfig.slices;
  207. for (int sliceIdx = 0; sliceIdx < slicesConfig.Count; sliceIdx++)
  208. {
  209. Material resMatToPass = null;
  210. List<MB_TexArraySliceRendererMatPair> srcMatAndObjPairs = slicesConfig[sliceIdx].sourceMaterials;
  211. if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" Baking atlases for result material:" + resMatIdx + " slice:" + sliceIdx);
  212. resMatToPass = resMatConfig.combinedMaterial;
  213. combiner.fixOutOfBoundsUVs = slicesConfig[sliceIdx].considerMeshUVs;
  214. MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult coroutineResult2 = new MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult();
  215. MB_AtlasesAndRects sliceAtlasesAndRectOutput = bakedMatsAndSlicesResMat.slices[sliceIdx];
  216. List<Material> usedMats = new List<Material>();
  217. slicesConfig[sliceIdx].GetAllUsedMaterials(usedMats);
  218. yield return combiner.CombineTexturesIntoAtlasesCoroutine(progressInfo, sliceAtlasesAndRectOutput, resMatToPass, slicesConfig[sliceIdx].GetAllUsedRenderers(objsToMesh), usedMats, editorMethods, coroutineResult2, maxTimePerFrame,
  219. onlyPackRects: false, splitAtlasWhenPackingIfTooBig: false);
  220. coroutineResult.success = coroutineResult2.success;
  221. if (!coroutineResult.success)
  222. {
  223. coroutineResult.isFinished = true;
  224. yield break;
  225. }
  226. // Track which slices are new generated texture instances. Atlases could be original texture assets (one tex per atlas) or temporary texture instances in memory that will need to be destroyed.
  227. {
  228. for (int texPropIdx = 0; texPropIdx < sliceAtlasesAndRectOutput.atlases.Length; texPropIdx++)
  229. {
  230. Texture2D atlas = sliceAtlasesAndRectOutput.atlases[texPropIdx];
  231. if (atlas != null)
  232. {
  233. bool atlasWasASourceTexture = false;
  234. for (int srcMatIdx = 0; srcMatIdx < srcMatAndObjPairs.Count; srcMatIdx++)
  235. {
  236. Material srcMat = srcMatAndObjPairs[srcMatIdx].sourceMaterial;
  237. if (srcMat.HasProperty(sliceAtlasesAndRectOutput.texPropertyNames[texPropIdx]) &&
  238. srcMat.GetTexture(sliceAtlasesAndRectOutput.texPropertyNames[texPropIdx]) == atlas)
  239. {
  240. atlasWasASourceTexture = true;
  241. break;
  242. }
  243. }
  244. if (!atlasWasASourceTexture)
  245. {
  246. generatedTemporaryAtlases.Add(new MB3_TextureCombiner.TemporaryTexture(sliceAtlasesAndRectOutput.texPropertyNames[texPropIdx], atlas));
  247. }
  248. }
  249. }
  250. } // end visit slices
  251. Debug.Assert(combiner._getNumTemporaryTextures() == 0, "Combiner should have no temporary textures.");
  252. }
  253. combiner.saveAtlasesAsAssets = saveAtlasesAsAssets; // Restore original setting.
  254. }
  255. // Generated atlas textures are temporary for texture arrays. They exist only in memory. Need to be cleaned up after we create slices.
  256. for (int i = 0; i < generatedTemporaryAtlases.Count; i++)
  257. {
  258. combiner.AddTemporaryTexture(generatedTemporaryAtlases[i]);
  259. }
  260. List<ShaderTextureProperty> texPropertyNames = new List<ShaderTextureProperty>();
  261. MB3_TextureCombinerPipeline._CollectPropertyNames(texPropertyNames, customShaderProperties, resMatConfig.combinedMaterial, LOG_LEVEL);
  262. // The slices are built from different source-material-lists. Each slice can have different sets of texture properties missing (nulls).
  263. // Build a master list of texture properties.
  264. bool[] hasTexForProperty = MB_TextureArrays.DetermineWhichPropertiesHaveTextures(bakedMatsAndSlicesResMat.slices);
  265. List<Texture2D> temporaryTextureAssets = new List<Texture2D>();
  266. try
  267. {
  268. MB_MultiMaterialTexArray resMaterial = resMatConfig;
  269. Dictionary<string, MB_TexArrayForProperty> resTexArraysByProperty = new Dictionary<string, MB_TexArrayForProperty>();
  270. {
  271. // Initialize so I don't need to check if properties exist later.
  272. for (int propIdx = 0; propIdx < texPropertyNames.Count; propIdx++)
  273. {
  274. if (hasTexForProperty[propIdx]) resTexArraysByProperty[texPropertyNames[propIdx].name] =
  275. new MB_TexArrayForProperty(texPropertyNames[propIdx].name, new MB_TextureArrayReference[textureArrayOutputFormats.Length]);
  276. }
  277. }
  278. MB3_TextureCombinerNonTextureProperties textureBlender = null;
  279. textureBlender = new MB3_TextureCombinerNonTextureProperties(LOG_LEVEL, combiner.considerNonTextureProperties);
  280. textureBlender.LoadTextureBlendersIfNeeded(resMatConfig.combinedMaterial);
  281. textureBlender.AdjustNonTextureProperties(resMatConfig.combinedMaterial, texPropertyNames, editorMethods);
  282. // Vist each TextureFormatSet
  283. for (int texFormatSetIdx = 0; texFormatSetIdx < textureArrayOutputFormats.Length; texFormatSetIdx++)
  284. {
  285. MB_TextureArrayFormatSet textureArrayFormatSet = textureArrayOutputFormats[texFormatSetIdx];
  286. editorMethods.Clear();
  287. MB_TextureArrays.TexturePropertyData texPropertyData = new MB_TextureArrays.TexturePropertyData();
  288. MB_TextureArrays.FindBestSizeAndMipCountAndFormatForTextureArrays(texPropertyNames, combiner.maxAtlasSize, textureArrayFormatSet, bakedMatsAndSlicesResMat.slices, texPropertyData);
  289. // Create textures we might need if they don't exist.
  290. {
  291. for (int propIdx = 0; propIdx < hasTexForProperty.Length; propIdx++)
  292. {
  293. if (hasTexForProperty[propIdx])
  294. {
  295. TextureFormat format = texPropertyData.formats[propIdx];
  296. int numSlices = bakedMatsAndSlicesResMat.slices.Length;
  297. int targetWidth = (int)texPropertyData.sizes[propIdx].x;
  298. int targetHeight = (int)texPropertyData.sizes[propIdx].y;
  299. for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++)
  300. {
  301. if (bakedMatsAndSlicesResMat.slices[sliceIdx].atlases[propIdx] == null)
  302. {
  303. Texture2D sliceTex = new Texture2D(targetWidth, targetHeight, format, texPropertyData.doMips[propIdx]);
  304. Color col = textureBlender.GetColorForTemporaryTexture(resMatConfig.slices[sliceIdx].sourceMaterials[0].sourceMaterial, texPropertyNames[propIdx]);
  305. MB_Utility.setSolidColor(sliceTex, col);
  306. bakedMatsAndSlicesResMat.slices[sliceIdx].atlases[propIdx] = editorMethods.CreateTemporaryAssetCopy(sliceTex, targetWidth, targetHeight, format, LOG_LEVEL);
  307. temporaryTextureAssets.Add(bakedMatsAndSlicesResMat.slices[sliceIdx].atlases[propIdx]);
  308. MB_Utility.Destroy(sliceTex);
  309. }
  310. }
  311. }
  312. }
  313. }
  314. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Converting source textures to readable formats.");
  315. if (MB_TextureArrays.ConvertTexturesToReadableFormat(texPropertyData, bakedMatsAndSlicesResMat.slices, hasTexForProperty, texPropertyNames, combiner, LOG_LEVEL, temporaryTextureAssets, editorMethods))
  316. {
  317. // We now have a set of slices (one per textureProperty). Build these into Texture2DArray's.
  318. if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Creating texture arrays");
  319. if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("THERE MAY BE ERRORS IN THE CONSOLE ABOUT 'Rebuilding mipmaps ... not supported'. THESE ARE PROBABLY FALSE POSITIVES AND CAN BE IGNORED.");
  320. Texture2DArray[] textureArrays = MB_TextureArrays.CreateTextureArraysForResultMaterial(texPropertyData, bakedMatsAndSlicesResMat.slices, hasTexForProperty, combiner, LOG_LEVEL);
  321. // Now have texture arrays for a result material, for all props. Save it.
  322. for (int propIdx = 0; propIdx < textureArrays.Length; propIdx++)
  323. {
  324. if (hasTexForProperty[propIdx])
  325. {
  326. MB_TextureArrayReference texRef = new MB_TextureArrayReference(textureArrayFormatSet.name, textureArrays[propIdx]);
  327. resTexArraysByProperty[texPropertyNames[propIdx].name].formats[texFormatSetIdx] = texRef;
  328. if (saveAtlasesAsAssets)
  329. {
  330. editorMethods.SaveTextureArrayToAssetDatabase(textureArrays[propIdx],
  331. textureArrayFormatSet.GetFormatForProperty(texPropertyNames[propIdx].name),
  332. bakedMatsAndSlicesResMat.slices[0].texPropertyNames[propIdx],
  333. propIdx, resMaterial.combinedMaterial);
  334. }
  335. }
  336. }
  337. }
  338. } // end vist format set
  339. resMaterial.textureProperties = new List<MB_TexArrayForProperty>();
  340. foreach (MB_TexArrayForProperty val in resTexArraysByProperty.Values)
  341. {
  342. resMaterial.textureProperties.Add(val);
  343. }
  344. }
  345. catch (Exception e)
  346. {
  347. Debug.LogError(e.Message + "\n" + e.StackTrace.ToString());
  348. coroutineResult.isFinished = true;
  349. coroutineResult.success = false;
  350. }
  351. finally
  352. {
  353. editorMethods.RestoreReadFlagsAndFormats(progressInfo);
  354. combiner._destroyAllTemporaryTextures();
  355. for (int i = 0; i < temporaryTextureAssets.Count; i++)
  356. {
  357. editorMethods.DestroyAsset(temporaryTextureAssets[i]);
  358. }
  359. temporaryTextureAssets.Clear();
  360. }
  361. }
  362. }
  363. }