diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index d5ceb7d..5ba87e3 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -4,6 +4,9 @@ KSP_COMMUNITY_FIXES { + OnDemandPartTextures = true + + // ########################## // Major bugfixes // ########################## diff --git a/KSPCommunityFixes/KSPCommunityFixes.csproj b/KSPCommunityFixes/KSPCommunityFixes.csproj index 5ccec49..84d10ec 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.csproj +++ b/KSPCommunityFixes/KSPCommunityFixes.csproj @@ -170,6 +170,7 @@ + diff --git a/KSPCommunityFixes/Library/StaticHelpers.cs b/KSPCommunityFixes/Library/StaticHelpers.cs index ab45b73..31617c7 100644 --- a/KSPCommunityFixes/Library/StaticHelpers.cs +++ b/KSPCommunityFixes/Library/StaticHelpers.cs @@ -7,6 +7,36 @@ namespace KSPCommunityFixes { + // see https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide + internal enum DDSFourCC : uint + { + DXT1 = 0x31545844, // "DXT1" + DXT2 = 0x32545844, // "DXT2" + DXT3 = 0x33545844, // "DXT3" + DXT4 = 0x34545844, // "DXT4" + DXT5 = 0x35545844, // "DXT5" + BC4U_ATI = 0x31495441, // "ATI1" (actually BC4U) + BC4U = 0x55344342, // "BC4U" + BC4S = 0x53344342, // "BC4S" + BC5U_ATI = 0x32495441, // "ATI2" (actually BC5U) + BC5U = 0x55354342, // "BC5U" + BC5S = 0x53354342, // "BC5S" + RGBG = 0x47424752, // "RGBG" + GRGB = 0x42475247, // "GRGB" + UYVY = 0x59565955, // "UYVY" + YUY2 = 0x32595559, // "YUY2" + DX10 = 0x30315844, // "DX10", actual DXGI format specified in DX10 header + R16G16B16A16_UNORM = 36, + R16G16B16A16_SNORM = 110, + R16_FLOAT = 111, + R16G16_FLOAT = 112, + R16G16B16A16_FLOAT = 113, + R32_FLOAT = 114, + R32G32_FLOAT = 115, + R32G32B32A32_FLOAT = 116, + CxV8U8 = 117, + } + static class StaticHelpers { public static string HumanReadableBytes(long bytes) diff --git a/KSPCommunityFixes/Performance/FastLoader.cs b/KSPCommunityFixes/Performance/FastLoader.cs index f26b41a..24e49cf 100644 --- a/KSPCommunityFixes/Performance/FastLoader.cs +++ b/KSPCommunityFixes/Performance/FastLoader.cs @@ -303,6 +303,8 @@ static IEnumerator FastAssetLoader(List configFileTypes) gdb.progressTitle = "Searching assets to load..."; yield return null; + OnDemandPartTextures.GetTextures(out HashSet allPartTextures); + double nextFrameTime = ElapsedTime + minFrameTimeD; // Files loaded by our custom loaders @@ -350,26 +352,29 @@ static IEnumerator FastAssetLoader(List configFileTypes) } break; case FileType.Texture: + + bool isOnDemand = allPartTextures.Contains(file.url); + switch (file.fileExtension) { case "dds": - textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureDDS)); + textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureDDS, isOnDemand)); break; case "jpg": case "jpeg": - textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureJPG)); + textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureJPG, isOnDemand)); break; case "mbm": - textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureMBM)); + textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureMBM, isOnDemand)); break; case "png": - textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TexturePNG)); + textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TexturePNG, isOnDemand)); break; case "tga": - textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureTGA)); + textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureTGA, isOnDemand)); break; case "truecolor": - textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureTRUECOLOR)); + textureAssets.Add(new RawAsset(file, RawAsset.AssetType.TextureTRUECOLOR, isOnDemand)); break; default: unsupportedTextureFiles.Add(file); @@ -802,6 +807,12 @@ static void ReadAssetsThread(List files, Deque buffer) { foreach (RawAsset rawAsset in files) { + if (rawAsset.IsOnDemand) + { + buffer.AddToFront(rawAsset); + continue; + } + rawAsset.ReadFromDiskWorkerThread(); SpinWait spin = new SpinWait(); @@ -836,7 +847,7 @@ static void ReadAssetsThread(List files, Deque buffer) /// /// Asset wrapper class, actual implementation of the disk reader, individual texture/model formats loaders /// - private class RawAsset + internal class RawAsset { public enum AssetType { @@ -881,18 +892,21 @@ public enum Result private BinaryReader binaryReader; private Result result; private string resultMessage; + private bool isOnDemand; public UrlFile File => file; public Result State => result; public string Message => resultMessage; public int DataLength => dataLength; public string TypeName => assetTypeNames[(int)assetType]; + public bool IsOnDemand => isOnDemand; - public RawAsset(UrlFile file, AssetType assetType) + public RawAsset(UrlFile file, AssetType assetType, bool isOnDemand = false) { this.result = Result.Valid; this.file = file; this.assetType = assetType; + this.isOnDemand = isOnDemand; } private void SetError(string message) @@ -1002,35 +1016,49 @@ public void LoadAndDisposeMainThread() if (file.fileType == FileType.Texture) { TextureInfo textureInfo; - switch (assetType) + + if (isOnDemand) { - case AssetType.TextureDDS: - textureInfo = LoadDDS(); - break; - case AssetType.TextureJPG: - textureInfo = LoadJPG(); - break; - case AssetType.TextureMBM: - textureInfo = LoadMBM(); - break; - case AssetType.TexturePNG: - textureInfo = LoadPNG(); - break; - case AssetType.TexturePNGCached: - textureInfo = LoadPNGCached(); - break; - case AssetType.TextureTGA: - textureInfo = LoadTGA(); - break; - case AssetType.TextureTRUECOLOR: - textureInfo = LoadTRUECOLOR(); - break; - default: - SetError("Unknown texture format"); - return; + textureInfo = new OnDemandTextureInfo(file, assetType); } + else + { + switch (assetType) + { + case AssetType.TextureDDS: + textureInfo = LoadDDS(); + break; + case AssetType.TextureJPG: + textureInfo = LoadJPG(); + break; + case AssetType.TextureMBM: + textureInfo = LoadMBM(); + break; + case AssetType.TexturePNG: + textureInfo = LoadPNG(); + break; + case AssetType.TexturePNGCached: + textureInfo = LoadPNGCached(); + break; + case AssetType.TextureTGA: + textureInfo = LoadTGA(); + break; + case AssetType.TextureTRUECOLOR: + textureInfo = LoadTRUECOLOR(); + break; + default: + SetError("Unknown texture format"); + return; + } - if (result == Result.Failed || textureInfo == null || textureInfo.texture.IsNullOrDestroyed()) + if (textureInfo.texture.IsNullOrDestroyed()) + result = Result.Failed; + + textureInfo.texture.name = file.url; + textureInfo.name = file.url; + } + + if (result == Result.Failed || textureInfo == null) { result = Result.Failed; if (string.IsNullOrEmpty(resultMessage)) @@ -1038,8 +1066,6 @@ public void LoadAndDisposeMainThread() } else { - textureInfo.name = file.url; - textureInfo.texture.name = file.url; Instance.databaseTexture.Add(textureInfo); } } @@ -1110,36 +1136,6 @@ public void CheckTextureCache() this.cachedTextureInfo = cachedTextureInfo; } - // see https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide - private enum DDSFourCC : uint - { - DXT1 = 0x31545844, // "DXT1" - DXT2 = 0x32545844, // "DXT2" - DXT3 = 0x33545844, // "DXT3" - DXT4 = 0x34545844, // "DXT4" - DXT5 = 0x35545844, // "DXT5" - BC4U_ATI = 0x31495441, // "ATI1" (actually BC4U) - BC4U = 0x55344342, // "BC4U" - BC4S = 0x53344342, // "BC4S" - BC5U_ATI = 0x32495441, // "ATI2" (actually BC5U) - BC5U = 0x55354342, // "BC5U" - BC5S = 0x53354342, // "BC5S" - RGBG = 0x47424752, // "RGBG" - GRGB = 0x42475247, // "GRGB" - UYVY = 0x59565955, // "UYVY" - YUY2 = 0x32595559, // "YUY2" - DX10 = 0x30315844, // "DX10", actual DXGI format specified in DX10 header - R16G16B16A16_UNORM = 36, - R16G16B16A16_SNORM = 110, - R16_FLOAT = 111, - R16G16_FLOAT = 112, - R16G16B16A16_FLOAT = 113, - R32_FLOAT = 114, - R32G32_FLOAT = 115, - R32G32B32A32_FLOAT = 116, - CxV8U8 = 117, - } - private TextureInfo LoadDDS() { memoryStream = new MemoryStream(buffer, 0, dataLength); diff --git a/KSPCommunityFixes/Performance/OnDemandPartTextures.cs b/KSPCommunityFixes/Performance/OnDemandPartTextures.cs new file mode 100644 index 0000000..3886f0e --- /dev/null +++ b/KSPCommunityFixes/Performance/OnDemandPartTextures.cs @@ -0,0 +1,749 @@ +using DDSHeaders; +using HarmonyLib; +using KSPCommunityFixes.QoL; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using UnityEngine; +using UnityEngine.Experimental.Rendering; +using static KSPCommunityFixes.Performance.KSPCFFastLoader; + +namespace KSPCommunityFixes.Performance +{ + public class OnDemandPartTextures : BasePatch + { + internal static Dictionary> partsTextures; + internal static Dictionary prefabsData = new Dictionary(); + + protected override void ApplyPatches(List patches) + { + MethodInfo m_Instantiate = null; + foreach (MethodInfo methodInfo in typeof(UnityEngine.Object).GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (methodInfo.IsGenericMethod + && methodInfo.Name == nameof(UnityEngine.Object.Instantiate) + && methodInfo.GetParameters().Length == 1) + { + m_Instantiate = methodInfo.MakeGenericMethod(typeof(UnityEngine.Object)); + break; + } + } + + patches.Add(new PatchInfo( + PatchMethodType.Prefix, + m_Instantiate, + this, nameof(Instantiate_Prefix))); + + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(PartLoader), nameof(PartLoader.ParsePart)), + this)); + + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(Part), nameof(Part.Awake)), + this)); + } + + static void Part_Awake_Postfix(Part __instance) + { + if (__instance.partInfo == null) + return; + + if (!prefabsData.TryGetValue(__instance.partInfo, out PrefabData prefabData)) + return; + + if (prefabData.areTexturesLoaded) + return; + + prefabData.LoadTextures(); + } + + static void Instantiate_Prefix(UnityEngine.Object original) + { + //if (original is Part part + // && part.partInfo != null + // && part.partInfo.partPrefab.IsNotNullRef() // skip instantiation of the special prefabs (kerbals, flag) during loading + // && prefabsData.TryGetValue(part.partInfo, out PrefabData prefabData) + // && !prefabData.areTexturesLoaded) + //{ + // // load textures + // // set them on the prefab + // prefabData.areTexturesLoaded = true; + //} + } + + static void PartLoader_ParsePart_Postfix(AvailablePart __result) + { + if (!partsTextures.TryGetValue(__result.name, out List textures)) + return; + + Transform modelTransform = __result.partPrefab.transform.Find("model"); + if (modelTransform == null) + return; + + + HashSet modelTextures = new HashSet(); + PrefabData prefabData = null; + + foreach (Renderer renderer in modelTransform.GetComponentsInChildren(true)) + { + if (renderer is ParticleSystemRenderer || renderer.sharedMaterial.IsNullOrDestroyed()) + continue; + + Material material = renderer.sharedMaterial; + MaterialTextures materialTextures = null; + + if (material.HasProperty("_MainTex")) + { + Texture currentTex = material.GetTexture("_MainTex"); + if (currentTex.IsNotNullRef()) + { + string currentTexName = currentTex.name; + if (OnDemandTextureInfo.texturesByUrl.TryGetValue(currentTexName, out OnDemandTextureInfo textureInfo)) + { + if (prefabData == null) + prefabData = new PrefabData(); + + if (materialTextures == null) + materialTextures = new MaterialTextures(material); + + prefabData.materials.Add(materialTextures); + materialTextures.mainTex = textureInfo; + modelTextures.Add(currentTexName); + } + + } + } + + if (material.HasProperty("_BumpMap")) + { + Texture currentTex = material.GetTexture("_BumpMap"); + if (currentTex.IsNotNullRef()) + { + string currentTexName = currentTex.name; + if (OnDemandTextureInfo.texturesByUrl.TryGetValue(currentTexName, out OnDemandTextureInfo textureInfo)) + { + if (prefabData == null) + prefabData = new PrefabData(); + + if (materialTextures == null) + materialTextures = new MaterialTextures(material); + + prefabData.materials.Add(materialTextures); + materialTextures.bumpMap = textureInfo; + modelTextures.Add(currentTexName); + } + + } + } + + if (material.HasProperty("_Emissive")) + { + Texture currentTex = material.GetTexture("_Emissive"); + if (currentTex.IsNotNullRef()) + { + string currentTexName = currentTex.name; + if (OnDemandTextureInfo.texturesByUrl.TryGetValue(currentTexName, out OnDemandTextureInfo textureInfo)) + { + if (prefabData == null) + prefabData = new PrefabData(); + + if (materialTextures == null) + materialTextures = new MaterialTextures(material); + + prefabData.materials.Add(materialTextures); + materialTextures.emissive = textureInfo; + modelTextures.Add(currentTexName); + } + + } + } + + if (material.HasProperty("_SpecMap")) + { + Texture currentTex = material.GetTexture("_SpecMap"); + if (currentTex.IsNotNullRef()) + { + string currentTexName = currentTex.name; + if (OnDemandTextureInfo.texturesByUrl.TryGetValue(currentTexName, out OnDemandTextureInfo textureInfo)) + { + if (prefabData == null) + prefabData = new PrefabData(); + + if (materialTextures == null) + materialTextures = new MaterialTextures(material); + + prefabData.materials.Add(materialTextures); + materialTextures.specMap = textureInfo; + modelTextures.Add(currentTexName); + } + + } + } + } + + if (prefabData != null) + { + prefabsData[__result] = prefabData; + + if (partsTextures.TryGetValue(__result.name, out List partTextures)) + { + foreach (string partTexture in partTextures) + { + if (modelTextures.Contains(partTexture)) + continue; + + if (OnDemandTextureInfo.texturesByUrl.TryGetValue(partTexture, out OnDemandTextureInfo textureInfo)) + { + prefabData.additonalTextures.Add(textureInfo); + } + } + } + } + } + + private static readonly char[] textureSplitChars = { ':', ',', ';' }; + + public static void GetTextures(out HashSet allPartTextures) + { + Dictionary> modelParts = new Dictionary>(); + Dictionary> partTextureReplacements = new Dictionary>(); + + foreach (UrlDir.UrlConfig urlConfig in GameDatabase.Instance.root.AllConfigs) + { + if (urlConfig.type != "PART") + continue; + + bool hasModelInModelNode = false; + + string partName = urlConfig.config.GetValue("name"); + if (partName == null) + continue; + + partName = partName.Replace('_', '.'); + + foreach (ConfigNode partNode in urlConfig.config.nodes) + { + if (partNode.name == "MODEL") + { + List texturePaths = null; + foreach (ConfigNode.Value modelValue in partNode.values) + { + if (modelValue.name == "model") + { + hasModelInModelNode = true; + + if (!modelParts.TryGetValue(modelValue.value, out List parts)) + { + parts = new List(); + modelParts[modelValue.value] = parts; + } + + parts.Add(partName); + } + else if (modelValue.name == "texture") + { + string[] array = modelValue.value.Split(textureSplitChars, StringSplitOptions.RemoveEmptyEntries); + if (array.Length != 2) + continue; + + if (texturePaths == null) + texturePaths = new List(); + + texturePaths.Add(new TextureReplacement(array[0].Trim(), array[1].Trim())); + } + } + + if (texturePaths != null) + partTextureReplacements[partName] = texturePaths; + } + } + + if (!hasModelInModelNode) + { + foreach (UrlDir.UrlFile urlFile in urlConfig.parent.parent.files) + { + if (urlFile.fileExtension == "mu") + { + if (!modelParts.TryGetValue(urlFile.url, out List parts)) + { + parts = new List(); + modelParts[urlFile.url] = parts; + } + + parts.Add(partName); + break; // only first model found should be added + } + } + } + } + + partsTextures = new Dictionary>(500); + allPartTextures = new HashSet(500); + + HashSet allTextures = new HashSet(2000); + HashSet allReplacedTextures = new HashSet(500); + List texturePathsBuffer = new List(); + List textureFileNameBuffer = new List(); + + foreach (UrlDir.UrlFile urlFile in GameDatabase.Instance.root.AllFiles) + { + if (urlFile.fileType == UrlDir.FileType.Texture) + { + allTextures.Add(urlFile.url); + continue; + } + + if (urlFile.fileType != UrlDir.FileType.Model) + continue; + + if (!modelParts.TryGetValue(urlFile.url, out List parts)) + continue; + + foreach (string textureFile in MuParser.GetModelTextures(urlFile.fullPath)) + { + string textureFileName = Path.GetFileNameWithoutExtension(textureFile); + textureFileNameBuffer.Add(textureFileName); + texturePathsBuffer.Add(urlFile.parent.url + "/" + textureFileName); + } + + if (texturePathsBuffer.Count == 0) + { + modelParts.Remove(urlFile.url); + continue; + } + + foreach (string part in parts) + { + if (partTextureReplacements.TryGetValue(part, out List textureReplacements)) + { + foreach (TextureReplacement textureReplacement in textureReplacements) + { + for (int i = 0; i < textureFileNameBuffer.Count; i++) + { + if (textureReplacement.textureName == textureFileNameBuffer[i]) + { + allReplacedTextures.Add(texturePathsBuffer[i]); + texturePathsBuffer[i] = textureReplacement.replacementUrl; + } + } + } + } + + if (!partsTextures.TryGetValue(part, out List textures)) + { + textures = new List(texturePathsBuffer); + partsTextures[part] = textures; + } + else + { + textures.AddRange(texturePathsBuffer); + } + } + + texturePathsBuffer.Clear(); + textureFileNameBuffer.Clear(); + } + + List stringBuffer = new List(); + foreach (KeyValuePair> partTextures in partsTextures) + { + for (int i = partTextures.Value.Count; i-- > 0;) + { + string texture = partTextures.Value[i]; + if (!allTextures.Contains(texture)) + { + partTextures.Value.RemoveAt(i); + } + else + { + allPartTextures.Add(texture); + } + } + + if (partTextures.Value.Count == 0) + stringBuffer.Add(partTextures.Key); + } + + for (int i = stringBuffer.Count; i-- > 0;) + partsTextures.Remove(stringBuffer[i]); + + stringBuffer.Clear(); + foreach (string replacedTexture in allReplacedTextures) + if (allPartTextures.Contains(replacedTexture)) + stringBuffer.Add(replacedTexture); + + for (int i = stringBuffer.Count; i-- > 0;) + allReplacedTextures.Remove(stringBuffer[i]); + } + } + + + [KSPAddon(KSPAddon.Startup.Instantly, true)] + public class OnDemandPartTexturesLoader : MonoBehaviour + { + public static OnDemandPartTexturesLoader instance; + + void Start() + { + DontDestroyOnLoad(this); + instance = this; + } + } + + internal class TextureReplacement + { + public string textureName; + public string replacementUrl; + + public TextureReplacement(string textureName, string replacementUrl) + { + this.textureName = textureName; + this.replacementUrl = replacementUrl; + } + } + + internal class PrefabData + { + public bool areTexturesLoaded; + public List materials = new List(); + public HashSet additonalTextures = new HashSet(); + + public void LoadTextures() + { + foreach (MaterialTextures materialTextures in materials) + { + materialTextures.LoadTextures(); + } + + foreach (OnDemandTextureInfo textureInfo in additonalTextures) + { + if (!textureInfo.isLoaded) + { + textureInfo.Load(); + } + } + + areTexturesLoaded = true; + } + } + + internal class MaterialTextures + { + private Material material; + public OnDemandTextureInfo mainTex; + public OnDemandTextureInfo bumpMap; + public OnDemandTextureInfo emissive; + public OnDemandTextureInfo specMap; + + public MaterialTextures(Material material) + { + this.material = material; + } + + public void LoadTextures() + { + if (mainTex != null) + { + if (mainTex.isLoaded) + material.SetTexture("_MainTex", mainTex.texture); + else + OnDemandPartTexturesLoader.instance.StartCoroutine(LoadMainTex()); + } + + if (bumpMap != null) + { + if (bumpMap.isLoaded) + material.SetTexture("_BumpMap", bumpMap.texture); + else + OnDemandPartTexturesLoader.instance.StartCoroutine(LoadBumpMap()); + } + + if (emissive != null) + { + if (emissive.isLoaded) + material.SetTexture("_Emissive", emissive.texture); + else + OnDemandPartTexturesLoader.instance.StartCoroutine(LoadEmissive()); + } + + if (specMap != null) + { + if (specMap.isLoaded) + material.SetTexture("_SpecMap", specMap.texture); + else + OnDemandPartTexturesLoader.instance.StartCoroutine(LoadSpecMap()); + } + } + + private IEnumerator LoadMainTex() + { + mainTex.Load(); + + while (!mainTex.isLoaded) + yield return null; + + material.SetTexture("_MainTex", mainTex.texture); + } + + private IEnumerator LoadBumpMap() + { + bumpMap.Load(); + + while (!bumpMap.isLoaded) + yield return null; + + material.SetTexture("_BumpMap", bumpMap.texture); + } + + private IEnumerator LoadEmissive() + { + emissive.Load(); + + while (!emissive.isLoaded) + yield return null; + + material.SetTexture("_Emissive", emissive.texture); + } + + private IEnumerator LoadSpecMap() + { + specMap.Load(); + + while (!specMap.isLoaded) + yield return null; + + material.SetTexture("_SpecMap", specMap.texture); + } + } + + internal class OnDemandTextureInfo : GameDatabase.TextureInfo + { + public static Dictionary texturesByUrl = new Dictionary(); + + private Texture2D dummyTexture; + + public bool isLoaded; + private RawAsset.AssetType textureType; + + public OnDemandTextureInfo(UrlDir.UrlFile file, RawAsset.AssetType textureType, bool isNormalMap = false, bool isReadable = false, bool isCompressed = false, Texture2D texture = null) + : base(file, texture, isNormalMap, isReadable, isCompressed) + { + name = file.url; + this.textureType = textureType; + dummyTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); + dummyTexture.Apply(false, true); + dummyTexture.name = name; + this.texture = dummyTexture; + texturesByUrl.Add(name, this); + } + + public void Load() + { + switch (textureType) + { + case RawAsset.AssetType.TextureDDS: + LoadDDS(); + break; + case RawAsset.AssetType.TextureJPG: + break; + case RawAsset.AssetType.TextureMBM: + break; + case RawAsset.AssetType.TexturePNG: + break; + case RawAsset.AssetType.TexturePNGCached: + break; + case RawAsset.AssetType.TextureTGA: + break; + case RawAsset.AssetType.TextureTRUECOLOR: + break; + } + } + + private void LoadDDS() + { + FileStream fileStream = new FileStream(file.fullPath, FileMode.Open); + BinaryReader binaryReader = new BinaryReader(fileStream); + + if (binaryReader.ReadUInt32() != DDSValues.uintMagic) + { + binaryReader.Dispose(); + fileStream.Dispose(); + return; + } + DDSHeader dDSHeader = new DDSHeader(binaryReader); + bool mipChain = (dDSHeader.dwCaps & DDSPixelFormatCaps.MIPMAP) != 0; + bool isNormalMap = (dDSHeader.ddspf.dwFlags & 0x80000u) != 0 || (dDSHeader.ddspf.dwFlags & 0x80000000u) != 0; + + DDSFourCC ddsFourCC = (DDSFourCC)dDSHeader.ddspf.dwFourCC; + GraphicsFormat graphicsFormat = GraphicsFormat.None; + + switch (ddsFourCC) + { + case DDSFourCC.DXT1: + graphicsFormat = GraphicsFormatUtility.GetGraphicsFormat(TextureFormat.DXT1, true); + break; + case DDSFourCC.DXT5: + graphicsFormat = GraphicsFormatUtility.GetGraphicsFormat(TextureFormat.DXT5, true); + break; + case DDSFourCC.BC4U_ATI: + case DDSFourCC.BC4U: + graphicsFormat = GraphicsFormat.R_BC4_UNorm; + break; + case DDSFourCC.BC4S: + graphicsFormat = GraphicsFormat.R_BC4_SNorm; + break; + case DDSFourCC.BC5U_ATI: + case DDSFourCC.BC5U: + graphicsFormat = GraphicsFormat.RG_BC5_UNorm; + break; + case DDSFourCC.BC5S: + graphicsFormat = GraphicsFormat.RG_BC5_SNorm; + break; + case DDSFourCC.R16G16B16A16_UNORM: + graphicsFormat = GraphicsFormat.R16G16B16A16_UNorm; + break; + case DDSFourCC.R16G16B16A16_SNORM: + graphicsFormat = GraphicsFormat.R16G16B16A16_SNorm; + break; + case DDSFourCC.R16_FLOAT: + graphicsFormat = GraphicsFormat.R16_SFloat; + break; + case DDSFourCC.R16G16_FLOAT: + graphicsFormat = GraphicsFormat.R16G16_SFloat; + break; + case DDSFourCC.R16G16B16A16_FLOAT: + graphicsFormat = GraphicsFormat.R16G16B16A16_SFloat; + break; + case DDSFourCC.R32_FLOAT: + graphicsFormat = GraphicsFormat.R32_SFloat; + break; + case DDSFourCC.R32G32_FLOAT: + graphicsFormat = GraphicsFormat.R32G32_SFloat; + break; + case DDSFourCC.R32G32B32A32_FLOAT: + graphicsFormat = GraphicsFormat.R32G32B32A32_SFloat; + break; + case DDSFourCC.DX10: + DDSHeaderDX10 dx10Header = new DDSHeaderDX10(binaryReader); + switch (dx10Header.dxgiFormat) + { + case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM: + graphicsFormat = GraphicsFormat.RGBA_DXT1_UNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM_SRGB: + graphicsFormat = GraphicsFormat.RGBA_DXT1_SRGB; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM: + graphicsFormat = GraphicsFormat.RGBA_DXT5_UNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM_SRGB: + graphicsFormat = GraphicsFormat.RGBA_DXT5_SRGB; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC4_SNORM: + graphicsFormat = GraphicsFormat.R_BC4_SNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM: + graphicsFormat = GraphicsFormat.R_BC4_UNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC5_SNORM: + graphicsFormat = GraphicsFormat.RG_BC5_SNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM: + graphicsFormat = GraphicsFormat.RG_BC5_UNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM: + graphicsFormat = GraphicsFormat.RGBA_BC7_UNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM_SRGB: + graphicsFormat = GraphicsFormat.RGBA_BC7_SRGB; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC6H_SF16: + graphicsFormat = GraphicsFormat.RGB_BC6H_SFloat; + break; + case DXGI_FORMAT.DXGI_FORMAT_BC6H_UF16: + graphicsFormat = GraphicsFormat.RGB_BC6H_UFloat; + break; + case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM: + graphicsFormat = GraphicsFormat.R16G16B16A16_UNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_SNORM: + graphicsFormat = GraphicsFormat.R16G16B16A16_SNorm; + break; + case DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT: + graphicsFormat = GraphicsFormat.R16_SFloat; + break; + case DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT: + graphicsFormat = GraphicsFormat.R16G16_SFloat; + break; + case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT: + graphicsFormat = GraphicsFormat.R16G16B16A16_SFloat; + break; + case DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT: + graphicsFormat = GraphicsFormat.R32_SFloat; + break; + case DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT: + graphicsFormat = GraphicsFormat.R32G32_SFloat; + break; + case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT: + graphicsFormat = GraphicsFormat.R32G32B32A32_SFloat; + break; + default: + //SetError($"DDS: The '{dx10Header.dxgiFormat}' DXT10 format isn't supported"); + break; + } + break; + case DDSFourCC.DXT2: + case DDSFourCC.DXT3: + case DDSFourCC.DXT4: + case DDSFourCC.RGBG: + case DDSFourCC.GRGB: + case DDSFourCC.UYVY: + case DDSFourCC.YUY2: + case DDSFourCC.CxV8U8: + //SetError($"DDS: The '{ddsFourCC}' format isn't supported, use DXT1 for RGB textures or DXT5 for RGBA textures"); + break; + default: + //SetError($"DDS: Unknown dwFourCC format '0x{ddsFourCC:X}'"); + break; + } + + if (graphicsFormat != GraphicsFormat.None) + { + if (!SystemInfo.IsFormatSupported(graphicsFormat, FormatUsage.Sample)) + { + if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX && + (graphicsFormat == GraphicsFormat.RGBA_BC7_UNorm + || graphicsFormat == GraphicsFormat.RGBA_BC7_SRGB + || graphicsFormat == GraphicsFormat.RGB_BC6H_SFloat + || graphicsFormat == GraphicsFormat.RGB_BC6H_UFloat)) + { + //SetError($"DDS: The '{graphicsFormat}' format is not supported on MacOS"); + } + else + { + //SetError($"DDS: The '{graphicsFormat}' format is not supported by your GPU or OS"); + } + } + else + { + texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, graphicsFormat, mipChain ? TextureCreationFlags.MipChain : TextureCreationFlags.None); + if (texture.IsNullOrDestroyed()) + { + //SetError($"DDS: Failed to load texture, unknown error"); + } + else + { + byte[] ddsData = binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position)); + texture.LoadRawTextureData(ddsData); + texture.Apply(updateMipmaps: false, makeNoLongerReadable: true); + isLoaded = true; + } + } + } + } + } +}