using System.Collections; using System.Collections.Generic; using Oculus.Avatar; using UnityEngine; public class OvrAvatarTextureCopyManager : MonoBehaviour { [System.Serializable] public struct FallbackTextureSet { public bool Initialized; public Texture2D DiffuseRoughness; public Texture2D Normal; } // Fallback texture sets are indexed with ovrAvatarAssetLevelOfDetail. // We currently only use 1, 3 (mobile default), 5 (PC default). public FallbackTextureSet[] FallbackTextureSets = new FallbackTextureSet[(int)ovrAvatarAssetLevelOfDetail.Highest + 1]; struct CopyTextureParams { public Texture Src; public Texture Dst; public int Mip; public int SrcSize; public int DstElement; public CopyTextureParams( Texture src, Texture dst, int mip, int srcSize, int dstElement) { Src = src; Dst = dst; Mip = mip; SrcSize = srcSize; DstElement = dstElement; } } private Queue texturesToCopy; public struct TextureSet { // Contains all texture asset IDs that are part of an avatar spec. // Used by DeleteTextureSet(). // Textures that are part of combined mesh avatars can be safely deleted once they have been // uploaded to the texture arrays. // Textures that are part of single component meshes will remain in memory. public Dictionary TextureIDSingleMeshPair; public bool IsProcessed; public TextureSet( Dictionary textureIDSingleMeshPair, bool isProcessed) { TextureIDSingleMeshPair = textureIDSingleMeshPair; IsProcessed = isProcessed; } } private Dictionary textureSets; private const int TEXTURES_TO_COPY_QUEUE_CAPACITY = 256; private const int COPIES_PER_FRAME = 8; // Fallback texture paths are indexed with ovrAvatarAssetLevelOfDetail // We currently only use 1, 3 (mobile default), 5 (PC default) private readonly string[] FALLBACK_TEXTURE_PATHS_DIFFUSE_ROUGHNESS = new string[] { "null", PATH_LOWEST_DIFFUSE_ROUGHNESS, "null", PATH_MEDIUM_DIFFUSE_ROUGHNESS, "null", PATH_HIGHEST_DIFFUSE_ROUGHNESS, }; private readonly string[] FALLBACK_TEXTURE_PATHS_NORMAL = new string[] { "null", PATH_LOWEST_NORMAL, "null", PATH_MEDIUM_NORMAL, "null", PATH_HIGHEST_NORMAL, }; private const string PATH_HIGHEST_DIFFUSE_ROUGHNESS = "FallbackTextures/fallback_diffuse_roughness_2048"; private const string PATH_MEDIUM_DIFFUSE_ROUGHNESS = "FallbackTextures/fallback_diffuse_roughness_1024"; private const string PATH_LOWEST_DIFFUSE_ROUGHNESS = "FallbackTextures/fallback_diffuse_roughness_256"; private const string PATH_HIGHEST_NORMAL = "FallbackTextures/fallback_normal_2048"; private const string PATH_MEDIUM_NORMAL = "FallbackTextures/fallback_normal_1024"; private const string PATH_LOWEST_NORMAL = "FallbackTextures/fallback_normal_256"; private const int GPU_TEXTURE_COPY_WAIT_TIME = 10; public OvrAvatarTextureCopyManager() { texturesToCopy = new Queue(TEXTURES_TO_COPY_QUEUE_CAPACITY); textureSets = new Dictionary(); } public void Update() { if (texturesToCopy.Count == 0) { return; } lock (texturesToCopy) { for (int i = 0; i < Mathf.Min(COPIES_PER_FRAME, texturesToCopy.Count); ++i) { CopyTexture(texturesToCopy.Dequeue()); } } } public int GetTextureCount() { return texturesToCopy.Count; } public void CopyTexture( Texture src, Texture dst, int mipLevel, int mipSize, int dstElement, bool useQueue = true) { var copyTextureParams = new CopyTextureParams(src, dst, mipLevel, mipSize, dstElement); if (useQueue) { lock (texturesToCopy) { if (texturesToCopy.Count < TEXTURES_TO_COPY_QUEUE_CAPACITY) { texturesToCopy.Enqueue(copyTextureParams); } else { // Queue is full so copy texture immediately CopyTexture(copyTextureParams); } } } else { CopyTexture(copyTextureParams); } } private void CopyTexture(CopyTextureParams copyTextureParams) { Graphics.CopyTexture( copyTextureParams.Src, 0, copyTextureParams.Mip, copyTextureParams.Dst, copyTextureParams.DstElement, copyTextureParams.Mip); } public void AddTextureIDToTextureSet(int gameobjectID, ulong textureID, bool isSingleMesh) { if (!textureSets.ContainsKey(gameobjectID)) { TextureSet newTextureSet = new TextureSet(new Dictionary(), false); newTextureSet.TextureIDSingleMeshPair.Add(textureID, isSingleMesh); textureSets.Add(gameobjectID, newTextureSet); } else { bool TexIDSingleMesh; if (textureSets[gameobjectID].TextureIDSingleMeshPair.TryGetValue(textureID, out TexIDSingleMesh)) { if (!TexIDSingleMesh && isSingleMesh) { textureSets[gameobjectID].TextureIDSingleMeshPair[textureID] = true; } } else { textureSets[gameobjectID].TextureIDSingleMeshPair.Add(textureID, isSingleMesh); } } } // This is called by a fully loaded avatar using combined mesh to safely delete unused textures. public void DeleteTextureSet(int gameobjectID) { TextureSet textureSetToDelete; if (!textureSets.TryGetValue(gameobjectID, out textureSetToDelete)) { return; }; if (textureSetToDelete.IsProcessed) { return; } StartCoroutine(DeleteTextureSetCoroutine(textureSetToDelete, gameobjectID)); } private IEnumerator DeleteTextureSetCoroutine(TextureSet textureSetToDelete, int gameobjectID) { // Wait a conservative amount of time for gpu upload to finish. Unity 2017 doesn't support async GPU calls, // so this 10 second time is a very conservative delay for this process to occur, which should be <1 sec. yield return new WaitForSeconds(GPU_TEXTURE_COPY_WAIT_TIME); // Spin if an avatar is loading while (OvrAvatarSDKManager.Instance.IsAvatarLoading()) { yield return null; } // The avatar's texture set is compared against all other loaded or loading avatar texture sets. foreach (var textureIdAndSingleMeshFlag in textureSetToDelete.TextureIDSingleMeshPair) { bool triggerDelete = !textureIdAndSingleMeshFlag.Value; if (triggerDelete) { foreach (KeyValuePair textureSet in textureSets) { if (textureSet.Key == gameobjectID) { continue; } foreach (var comparisonTextureIDSingleMeshPair in textureSet.Value.TextureIDSingleMeshPair) { // Mark the texture as not deletable if it's present in another set and that set hasn't been processed // or that texture ID is marked as part of a single mesh component. if (comparisonTextureIDSingleMeshPair.Key == textureIdAndSingleMeshFlag.Key && (!textureSet.Value.IsProcessed || comparisonTextureIDSingleMeshPair.Value)) { triggerDelete = false; break; } } if (!triggerDelete) { break; } } } if (triggerDelete) { Texture2D textureToDelete = OvrAvatarComponent.GetLoadedTexture(textureIdAndSingleMeshFlag.Key); if (textureToDelete != null) { AvatarLogger.Log("Deleting texture " + textureIdAndSingleMeshFlag.Key); OvrAvatarSDKManager.Instance.DeleteAssetFromCache(textureIdAndSingleMeshFlag.Key); Destroy(textureToDelete); } } } textureSetToDelete.IsProcessed = true; textureSets.Remove(gameobjectID); } public void CheckFallbackTextureSet(ovrAvatarAssetLevelOfDetail lod) { if (FallbackTextureSets[(int)lod].Initialized) { return; } InitFallbackTextureSet(lod); } private void InitFallbackTextureSet(ovrAvatarAssetLevelOfDetail lod) { FallbackTextureSets[(int)lod].DiffuseRoughness = FallbackTextureSets[(int)lod].DiffuseRoughness = Resources.Load(FALLBACK_TEXTURE_PATHS_DIFFUSE_ROUGHNESS[(int)lod]); FallbackTextureSets[(int)lod].Normal = FallbackTextureSets[(int)lod].Normal = Resources.Load(FALLBACK_TEXTURE_PATHS_NORMAL[(int)lod]); FallbackTextureSets[(int)lod].Initialized = true; } }