From 40bb7fc2fbb9bf6096a5727d328d8f117b6a4c6a Mon Sep 17 00:00:00 2001
From: gotmachine <24925209+gotmachine@users.noreply.github.com>
Date: Sun, 19 May 2024 19:45:55 +0200
Subject: [PATCH 1/2] For now, this is just a big mess
---
GameData/KSPCommunityFixes/Settings.cfg | 3 +
KSPCommunityFixes/KSPCommunityFixes.csproj | 1 +
KSPCommunityFixes/Performance/FastLoader.cs | 98 ++--
.../Performance/OnDemandPartTextures.cs | 470 ++++++++++++++++++
4 files changed, 536 insertions(+), 36 deletions(-)
create mode 100644 KSPCommunityFixes/Performance/OnDemandPartTextures.cs
diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg
index d5ceb7d1..5ba87e37 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 5ccec49f..84d10eca 100644
--- a/KSPCommunityFixes/KSPCommunityFixes.csproj
+++ b/KSPCommunityFixes/KSPCommunityFixes.csproj
@@ -170,6 +170,7 @@
+
diff --git a/KSPCommunityFixes/Performance/FastLoader.cs b/KSPCommunityFixes/Performance/FastLoader.cs
index f26b41a4..6563d0ce 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 (textureInfo.texture.IsNullOrDestroyed())
+ result = Result.Failed;
- if (result == Result.Failed || textureInfo == null || textureInfo.texture.IsNullOrDestroyed())
+ 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);
}
}
diff --git a/KSPCommunityFixes/Performance/OnDemandPartTextures.cs b/KSPCommunityFixes/Performance/OnDemandPartTextures.cs
new file mode 100644
index 00000000..884fa8da
--- /dev/null
+++ b/KSPCommunityFixes/Performance/OnDemandPartTextures.cs
@@ -0,0 +1,470 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using HarmonyLib;
+using KSP.UI.Screens;
+using KSPCommunityFixes.QoL;
+using UnityEngine;
+using static KSP.UI.Screens.Settings.SettingsSetup;
+using static KSPCommunityFixes.Performance.KSPCFFastLoader;
+using static KSPCommunityFixes.QoL.NoIVA;
+using static ProceduralSpaceObject;
+
+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));
+ }
+
+ 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;
+
+ 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;
+ }
+
+ }
+ }
+
+ 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;
+ }
+
+ }
+ }
+
+ 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;
+ }
+
+ }
+ }
+
+ 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;
+ }
+
+ }
+ }
+ }
+
+ if (prefabData != null)
+ prefabsData[__result] = prefabData;
+ }
+
+ 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
+ {
+
+
+
+
+ // 1
+ // build a > dictionary
+ // 1.a.
+ // - Parse part configs
+ // - find MODEL > model reference
+ // - find MODEL > texture refernces
+ // - find ModulePartVariants > TEXTURE references
+ // - In all found model references :
+ // - find all texture references
+ // Patch Part.Awake(), after RelinkPrefab()
+ // - swap the model textures
+
+ // overview of where textures can come from :
+ // - defined in the model(s) materials
+ // - PART > "mesh" : depreciated/unused
+ // - If no PART > MODEL node : the first found (as ordered in GameDatabase.databaseModel) *.mu model placed in the same directory as the part config (cfg.parent.parent.url)
+ // - If PART > MODEL node(s) : the model defined in the "model" value
+ // - not sure if it is possible to set a path to the texture in the model ? I don't think so, but...
+ // - defined as a texture replacement in the PART > MODEL node
+ // - the replacement is done on the prefab renderers sharedMaterial
+ // - as far as I can tell, this require (a potentially dummy) texture with the same name as what is backed in the model to be sitting next to the model in the same directory
+
+
+ }
+
+ 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;
+
+ public void LoadTextures()
+ {
+
+ }
+ }
+
+ 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)
+ {
+ mainTex.Load();
+ material.SetTexture("_MainTex", mainTex.texture);
+ }
+
+ if (bumpMap != null)
+ {
+ bumpMap.Load();
+ material.SetTexture("_BumpMap", bumpMap.texture);
+ }
+
+ if (emissive != null)
+ {
+ emissive.Load();
+ material.SetTexture("_Emissive", emissive.texture);
+ }
+
+ if (specMap != null)
+ {
+ specMap.Load();
+ material.SetTexture("_SpecMap", specMap.texture);
+ }
+ }
+ }
+
+ internal class OnDemandTextureInfo : GameDatabase.TextureInfo
+ {
+ public static Dictionary texturesByUrl = new Dictionary();
+
+ private Texture2D dummyTexture;
+
+ private 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()
+ {
+
+ }
+ }
+}
From d9f1f7de9ec7e865341595c3929d81eb29be5771 Mon Sep 17 00:00:00 2001
From: gotmachine <24925209+gotmachine@users.noreply.github.com>
Date: Sat, 25 May 2024 10:05:11 +0200
Subject: [PATCH 2/2] Nope, relying on prefabs sharedMaterial won't do
---
KSPCommunityFixes/Library/StaticHelpers.cs | 30 ++
KSPCommunityFixes/Performance/FastLoader.cs | 30 --
.../Performance/OnDemandPartTextures.cs | 395 +++++++++++++++---
3 files changed, 367 insertions(+), 88 deletions(-)
diff --git a/KSPCommunityFixes/Library/StaticHelpers.cs b/KSPCommunityFixes/Library/StaticHelpers.cs
index ab45b73d..31617c7a 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 6563d0ce..24e49cfc 100644
--- a/KSPCommunityFixes/Performance/FastLoader.cs
+++ b/KSPCommunityFixes/Performance/FastLoader.cs
@@ -1136,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
index 884fa8da..3886f0ee 100644
--- a/KSPCommunityFixes/Performance/OnDemandPartTextures.cs
+++ b/KSPCommunityFixes/Performance/OnDemandPartTextures.cs
@@ -1,18 +1,14 @@
-using System;
+using DDSHeaders;
+using HarmonyLib;
+using KSPCommunityFixes.QoL;
+using System;
+using System.Collections;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-using HarmonyLib;
-using KSP.UI.Screens;
-using KSPCommunityFixes.QoL;
using UnityEngine;
-using static KSP.UI.Screens.Settings.SettingsSetup;
+using UnityEngine.Experimental.Rendering;
using static KSPCommunityFixes.Performance.KSPCFFastLoader;
-using static KSPCommunityFixes.QoL.NoIVA;
-using static ProceduralSpaceObject;
namespace KSPCommunityFixes.Performance
{
@@ -44,20 +40,39 @@ protected override void ApplyPatches(List patches)
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;
- }
+ //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)
@@ -69,6 +84,8 @@ static void PartLoader_ParsePart_Postfix(AvailablePart __result)
if (modelTransform == null)
return;
+
+ HashSet modelTextures = new HashSet();
PrefabData prefabData = null;
foreach (Renderer renderer in modelTransform.GetComponentsInChildren(true))
@@ -95,6 +112,7 @@ static void PartLoader_ParsePart_Postfix(AvailablePart __result)
prefabData.materials.Add(materialTextures);
materialTextures.mainTex = textureInfo;
+ modelTextures.Add(currentTexName);
}
}
@@ -116,6 +134,7 @@ static void PartLoader_ParsePart_Postfix(AvailablePart __result)
prefabData.materials.Add(materialTextures);
materialTextures.bumpMap = textureInfo;
+ modelTextures.Add(currentTexName);
}
}
@@ -137,6 +156,7 @@ static void PartLoader_ParsePart_Postfix(AvailablePart __result)
prefabData.materials.Add(materialTextures);
materialTextures.emissive = textureInfo;
+ modelTextures.Add(currentTexName);
}
}
@@ -158,6 +178,7 @@ static void PartLoader_ParsePart_Postfix(AvailablePart __result)
prefabData.materials.Add(materialTextures);
materialTextures.specMap = textureInfo;
+ modelTextures.Add(currentTexName);
}
}
@@ -165,7 +186,23 @@ static void PartLoader_ParsePart_Postfix(AvailablePart __result)
}
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 = { ':', ',', ';' };
@@ -345,36 +382,16 @@ public static void GetTextures(out HashSet allPartTextures)
}
- //[KSPAddon(KSPAddon.Startup.Instantly, true)]
- public class OnDemandPartTexturesLoader// : MonoBehaviour
+ [KSPAddon(KSPAddon.Startup.Instantly, true)]
+ public class OnDemandPartTexturesLoader : MonoBehaviour
{
+ public static OnDemandPartTexturesLoader instance;
-
-
-
- // 1
- // build a > dictionary
- // 1.a.
- // - Parse part configs
- // - find MODEL > model reference
- // - find MODEL > texture refernces
- // - find ModulePartVariants > TEXTURE references
- // - In all found model references :
- // - find all texture references
- // Patch Part.Awake(), after RelinkPrefab()
- // - swap the model textures
-
- // overview of where textures can come from :
- // - defined in the model(s) materials
- // - PART > "mesh" : depreciated/unused
- // - If no PART > MODEL node : the first found (as ordered in GameDatabase.databaseModel) *.mu model placed in the same directory as the part config (cfg.parent.parent.url)
- // - If PART > MODEL node(s) : the model defined in the "model" value
- // - not sure if it is possible to set a path to the texture in the model ? I don't think so, but...
- // - defined as a texture replacement in the PART > MODEL node
- // - the replacement is done on the prefab renderers sharedMaterial
- // - as far as I can tell, this require (a potentially dummy) texture with the same name as what is backed in the model to be sitting next to the model in the same directory
-
-
+ void Start()
+ {
+ DontDestroyOnLoad(this);
+ instance = this;
+ }
}
internal class TextureReplacement
@@ -392,11 +409,25 @@ public TextureReplacement(string textureName, string replacementUrl)
internal class PrefabData
{
public bool areTexturesLoaded;
- public List materials;
+ 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;
}
}
@@ -417,28 +448,76 @@ public void LoadTextures()
{
if (mainTex != null)
{
- mainTex.Load();
- material.SetTexture("_MainTex", mainTex.texture);
+ if (mainTex.isLoaded)
+ material.SetTexture("_MainTex", mainTex.texture);
+ else
+ OnDemandPartTexturesLoader.instance.StartCoroutine(LoadMainTex());
}
if (bumpMap != null)
{
- bumpMap.Load();
- material.SetTexture("_BumpMap", bumpMap.texture);
+ if (bumpMap.isLoaded)
+ material.SetTexture("_BumpMap", bumpMap.texture);
+ else
+ OnDemandPartTexturesLoader.instance.StartCoroutine(LoadBumpMap());
}
if (emissive != null)
{
- emissive.Load();
- material.SetTexture("_Emissive", emissive.texture);
+ if (emissive.isLoaded)
+ material.SetTexture("_Emissive", emissive.texture);
+ else
+ OnDemandPartTexturesLoader.instance.StartCoroutine(LoadEmissive());
}
if (specMap != null)
{
- specMap.Load();
- material.SetTexture("_SpecMap", specMap.texture);
+ 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
@@ -447,7 +526,7 @@ internal class OnDemandTextureInfo : GameDatabase.TextureInfo
private Texture2D dummyTexture;
- private bool isLoaded;
+ 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)
@@ -464,7 +543,207 @@ public OnDemandTextureInfo(UrlDir.UrlFile file, RawAsset.AssetType textureType,
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;
+ }
+ }
+ }
}
}
}