//#define NATIVE_CODE_FOR_CMD_CONVERT_TEXTURE using UnityEngine; using System.Collections.Generic; using UnityEngine.Rendering; #if UNITY_EDITOR using UnityEditor; #endif namespace UnityEngine.Experimental.Rendering { public class TextureCache2D : TextureCache { private Texture2DArray m_Cache; public override void TransferToSlice(CommandBuffer cmd, int sliceIndex, Texture texture) { var mismatch = (m_Cache.width != texture.width) || (m_Cache.height != texture.height); if (texture is Texture2D) { mismatch |= (m_Cache.format != (texture as Texture2D).format); } if (mismatch) { #if NATIVE_CODE_FOR_CMD_CONVERT_TEXTURE cmd.ConvertTexture(texture, 0, m_Cache, sliceIndex); #else UnityEngine.Graphics.ConvertTexture(texture, 0, m_Cache, sliceIndex); #endif } else { cmd.CopyTexture(texture, 0, m_Cache, sliceIndex); } } public override Texture GetTexCache() { return m_Cache; } public bool AllocTextureArray(int numTextures, int width, int height, TextureFormat format, bool isMipMapped) { var res = AllocTextureArray(numTextures); m_NumMipLevels = GetNumMips(width, height); m_Cache = new Texture2DArray(width, height, numTextures, format, isMipMapped) { hideFlags = HideFlags.HideAndDontSave, wrapMode = TextureWrapMode.Clamp }; return res; } public void Release() { Texture.DestroyImmediate(m_Cache); // do I need this? } } public class TextureCacheCubemap : TextureCache { private CubemapArray m_Cache; // the member variables below are only in use when TextureCache.supportsCubemapArrayTextures is false private Texture2DArray m_CacheNoCubeArray; private RenderTexture[] m_StagingRTs; private int m_NumPanoMipLevels; private Material m_CubeBlitMaterial; private int m_CubeMipLevelPropName; private int m_cubeSrcTexPropName; public override void TransferToSlice(CommandBuffer cmd, int sliceIndex, Texture texture) { if (!TextureCache.supportsCubemapArrayTextures) TransferToPanoCache(cmd, sliceIndex, texture); else { var mismatch = (m_Cache.width != texture.width) || (m_Cache.height != texture.height); if (texture is Cubemap) { mismatch |= (m_Cache.format != (texture as Cubemap).format); } if (mismatch) { for (int f = 0; f < 6; f++) { #if NATIVE_CODE_FOR_CMD_CONVERT_TEXTURE cmd.ConvertTexture(texture, f, m_Cache, 6 * sliceIndex + f); #else UnityEngine.Graphics.ConvertTexture(texture, f, m_Cache, 6 * sliceIndex + f); #endif } } else { for (int f = 0; f < 6; f++) cmd.CopyTexture(texture, f, m_Cache, 6 * sliceIndex + f); } } } public override Texture GetTexCache() { return !TextureCache.supportsCubemapArrayTextures ? (Texture)m_CacheNoCubeArray : m_Cache; } public bool AllocTextureArray(int numCubeMaps, int width, TextureFormat format, bool isMipMapped) { var res = AllocTextureArray(numCubeMaps); m_NumMipLevels = GetNumMips(width, width); // will calculate same way whether we have cube array or not if (!TextureCache.supportsCubemapArrayTextures) { if (!m_CubeBlitMaterial) m_CubeBlitMaterial = new Material(Shader.Find("Hidden/CubeToPano")) { hideFlags = HideFlags.HideAndDontSave }; int panoWidthTop = 4 * width; int panoHeightTop = 2 * width; // create panorama 2D array. Hardcoding the render target for now. No convenient way atm to // map from TextureFormat to RenderTextureFormat and don't want to deal with sRGB issues for now. m_CacheNoCubeArray = new Texture2DArray(panoWidthTop, panoHeightTop, numCubeMaps, TextureFormat.RGBAHalf, isMipMapped) { hideFlags = HideFlags.HideAndDontSave, wrapMode = TextureWrapMode.Repeat, wrapModeV = TextureWrapMode.Clamp, filterMode = FilterMode.Trilinear, anisoLevel = 0 }; m_NumPanoMipLevels = isMipMapped ? GetNumMips(panoWidthTop, panoHeightTop) : 1; m_StagingRTs = new RenderTexture[m_NumPanoMipLevels]; for (int m = 0; m < m_NumPanoMipLevels; m++) { m_StagingRTs[m] = new RenderTexture(Mathf.Max(1, panoWidthTop >> m), Mathf.Max(1, panoHeightTop >> m), 0, RenderTextureFormat.ARGBHalf) { hideFlags = HideFlags.HideAndDontSave }; } if (m_CubeBlitMaterial) { m_CubeMipLevelPropName = Shader.PropertyToID("_cubeMipLvl"); m_cubeSrcTexPropName = Shader.PropertyToID("_srcCubeTexture"); } } else { m_Cache = new CubemapArray(width, numCubeMaps, format, isMipMapped) { hideFlags = HideFlags.HideAndDontSave, wrapMode = TextureWrapMode.Clamp, filterMode = FilterMode.Trilinear, anisoLevel = 0 // It is important to set 0 here, else unity force anisotropy filtering }; } return res; } public void Release() { if (m_CacheNoCubeArray) { Texture.DestroyImmediate(m_CacheNoCubeArray); for (int m = 0; m < m_NumPanoMipLevels; m++) { m_StagingRTs[m].Release(); } m_StagingRTs = null; if (m_CubeBlitMaterial) Material.DestroyImmediate(m_CubeBlitMaterial); } if (m_Cache) Texture.DestroyImmediate(m_Cache); } private void TransferToPanoCache(CommandBuffer cmd, int sliceIndex, Texture texture) { m_CubeBlitMaterial.SetTexture(m_cubeSrcTexPropName, texture); for (int m = 0; m < m_NumPanoMipLevels; m++) { m_CubeBlitMaterial.SetInt(m_CubeMipLevelPropName, Mathf.Min(m_NumMipLevels - 1, m)); cmd.Blit(null, m_StagingRTs[m], m_CubeBlitMaterial, 0); } for (int m = 0; m < m_NumPanoMipLevels; m++) cmd.CopyTexture(m_StagingRTs[m], 0, 0, m_CacheNoCubeArray, sliceIndex, m); } } public abstract class TextureCache { protected int m_NumMipLevels; public static bool isMobileBuildTarget { get { #if UNITY_EDITOR switch (EditorUserBuildSettings.activeBuildTarget) { case BuildTarget.iOS: case BuildTarget.Android: case BuildTarget.Tizen: case BuildTarget.WSAPlayer: // Note: We return true on purpose even if Windows Store Apps are running on Desktop. return true; default: return false; } #else return Application.isMobilePlatform; #endif } } public static TextureFormat GetPreferredHDRCompressedTextureFormat { get { var format = TextureFormat.RGBAHalf; var probeFormat = TextureFormat.BC6H; // // On editor the texture is uncompressed when operating against mobile build targets //#if UNITY_2017_2_OR_NEWER if (SystemInfo.SupportsTextureFormat(probeFormat) && !UnityEngine.Rendering.GraphicsSettings.HasShaderDefine(UnityEngine.Rendering.BuiltinShaderDefine.UNITY_NO_DXT5nm)) format = probeFormat; //#else // if (SystemInfo.SupportsTextureFormat(probeFormat) && !TextureCache.isMobileBuildTarget) // format = probeFormat; //#endif return format; } } public static bool supportsCubemapArrayTextures { get { //#if UNITY_2017_2_OR_NEWER return !UnityEngine.Rendering.GraphicsSettings.HasShaderDefine(UnityEngine.Rendering.BuiltinShaderDefine.UNITY_NO_CUBEMAP_ARRAY); //#else // return (SystemInfo.supportsCubemapArrayTextures && !TextureCache.isMobileBuildTarget); //#endif } } private struct SSliceEntry { public uint texId; public uint countLRU; public uint updateCount; }; private int m_NumTextures; private int[] m_SortedIdxArray; private SSliceEntry[] m_SliceArray; Dictionary m_LocatorInSliceArray; private static uint g_MaxFrameCount = unchecked((uint)(-1)); private static uint g_InvalidTexID = (uint)0; public uint GetTextureUpdateCount(Texture texture) { uint updateCount = texture.updateCount; // For baked probes in the editor we need to factor in the actual hash of texture because we can't increment the update count of a texture that's baked on the disk. // This code leaks logic from reflection probe baking into the texture cache which is not good... TODO: Find a way to do that outside of the texture cache. #if UNITY_EDITOR updateCount += (uint)texture.imageContentsHash.GetHashCode(); #endif return updateCount; } public int ReserveSlice(Texture texture, out bool needUpdate) { needUpdate = false; if (texture == null) return -1; var texId = (uint)texture.GetInstanceID(); if (texId == g_InvalidTexID) return -1; // search for existing copy var sliceIndex = -1; var foundIndex = -1; if (m_LocatorInSliceArray.TryGetValue(texId, out foundIndex)) { sliceIndex = foundIndex; var updateCount = GetTextureUpdateCount(texture); needUpdate |= (m_SliceArray[sliceIndex].updateCount != updateCount); Debug.Assert(m_SliceArray[sliceIndex].texId == texId); } // If no existing copy found in the array if(sliceIndex == -1) { // look for first non zero entry. Will by the least recently used entry // since the array was pre-sorted (in linear time) in NewFrame() var bFound = false; int j = 0, idx = 0; while ((!bFound) && j < m_NumTextures) { idx = m_SortedIdxArray[j]; if (m_SliceArray[idx].countLRU == 0) ++j; // if entry already snagged by a new texture in this frame then ++j else bFound = true; } if (bFound) { needUpdate = true; // if we are replacing an existing entry delete it from m_locatorInSliceArray. if (m_SliceArray[idx].texId != g_InvalidTexID) { m_LocatorInSliceArray.Remove(m_SliceArray[idx].texId); } m_LocatorInSliceArray.Add(texId, idx); m_SliceArray[idx].texId = texId; sliceIndex = idx; } } if(sliceIndex != -1) { m_SliceArray[sliceIndex].countLRU = 0; // mark slice as in use this frame } return sliceIndex; } // In case the texture content with which we update the cache is not the input texture, we need to provide the right update count. public void UpdateSlice(CommandBuffer cmd, int sliceIndex, Texture content, uint updateCount) { // transfer new slice to sliceIndex from source texture m_SliceArray[sliceIndex].updateCount = updateCount; TransferToSlice(cmd, sliceIndex, content); } public void UpdateSlice(CommandBuffer cmd, int sliceIndex, Texture content) { UpdateSlice(cmd, sliceIndex, content, GetTextureUpdateCount(content)); } public int FetchSlice(CommandBuffer cmd, Texture texture, bool forceReinject=false) { bool needUpdate = false; var sliceIndex = ReserveSlice(texture, out needUpdate); var bSwapSlice = forceReinject || needUpdate; // wrap up Debug.Assert(sliceIndex != -1, "The texture cache doesn't have enough space to store all textures. Please either increase the size of the texture cache, or use fewer unique textures."); if (sliceIndex != -1 && bSwapSlice) { UpdateSlice(cmd, sliceIndex, texture); } return sliceIndex; } private static List s_TempIntList = new List(); public void NewFrame() { var numNonZeros = 0; s_TempIntList.Clear(); for (int i = 0; i < m_NumTextures; i++) { s_TempIntList.Add(m_SortedIdxArray[i]); // copy buffer if (m_SliceArray[m_SortedIdxArray[i]].countLRU != 0) ++numNonZeros; } int nonZerosBase = 0, zerosBase = 0; for (int i = 0; i < m_NumTextures; i++) { if (m_SliceArray[s_TempIntList[i]].countLRU == 0) { m_SortedIdxArray[zerosBase + numNonZeros] = s_TempIntList[i]; ++zerosBase; } else { m_SortedIdxArray[nonZerosBase] = s_TempIntList[i]; ++nonZerosBase; } } for (int i = 0; i < m_NumTextures; i++) { if (m_SliceArray[i].countLRU < g_MaxFrameCount) ++m_SliceArray[i].countLRU; // next frame } //for(int q=1; q=m_SliceArray[m_SortedIdxArray[q]].CountLRU); } protected TextureCache() { m_NumTextures = 0; m_NumMipLevels = 0; } public virtual void TransferToSlice(CommandBuffer cmd, int sliceIndex, Texture texture) { } public virtual Texture GetTexCache() { return null; } protected bool AllocTextureArray(int numTextures) { if (numTextures > 0) { m_SliceArray = new SSliceEntry[numTextures]; m_SortedIdxArray = new int[numTextures]; m_LocatorInSliceArray = new Dictionary(); m_NumTextures = numTextures; for (int i = 0; i < m_NumTextures; i++) { m_SliceArray[i].countLRU = g_MaxFrameCount; // never used before m_SliceArray[i].texId = g_InvalidTexID; m_SortedIdxArray[i] = i; } } //return m_SliceArray != NULL && m_SortedIdxArray != NULL && numTextures > 0; return numTextures > 0; } // should not really be used in general. Assuming lights are culled properly entries will automatically be replaced efficiently. public void RemoveEntryFromSlice(Texture texture) { var texId = (uint)texture.GetInstanceID(); //assert(TexID!=g_InvalidTexID); if (texId == g_InvalidTexID) return; // search for existing copy if (!m_LocatorInSliceArray.ContainsKey(texId)) return; var sliceIndex = m_LocatorInSliceArray[texId]; //assert(m_SliceArray[sliceIndex].TexID==TexID); // locate entry sorted by uCountLRU in m_pSortedIdxArray var foundIdxSortLRU = false; var i = 0; while ((!foundIdxSortLRU) && i < m_NumTextures) { if (m_SortedIdxArray[i] == sliceIndex) foundIdxSortLRU = true; else ++i; } if (!foundIdxSortLRU) return; // relocate sliceIndex to front of m_pSortedIdxArray since uCountLRU will be set to maximum. for (int j = 0; j < i; j++) { m_SortedIdxArray[j + 1] = m_SortedIdxArray[j]; } m_SortedIdxArray[0] = sliceIndex; // delete from m_locatorInSliceArray and m_pSliceArray. m_LocatorInSliceArray.Remove(texId); m_SliceArray[sliceIndex].countLRU = g_MaxFrameCount; // never used before m_SliceArray[sliceIndex].texId = g_InvalidTexID; } protected int GetNumMips(int width, int height) { return GetNumMips(width > height ? width : height); } protected int GetNumMips(int dim) { var uDim = (uint)dim; var iNumMips = 0; while (uDim > 0) { ++iNumMips; uDim >>= 1; } return iNumMips; } } }