ResourceTree: Are we fast yet?

This commit is contained in:
Exter-N 2023-11-14 20:38:21 +01:00
parent b2bf6eb0f7
commit 60551c8739
5 changed files with 33 additions and 25 deletions

View file

@ -187,11 +187,6 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file)
=> _imcCache.GetImcFile(path, out file); => _imcCache.GetImcFile(path, out file);
public ImcEntry GetImcEntry(Utf8GamePath path, EquipSlot slot, Variant variantIdx, out bool exists)
=> GetImcFile(path, out var file)
? file.GetEntry(Meta.Files.ImcFile.PartIndex(slot), variantIdx, out exists)
: Meta.Files.ImcFile.GetDefault(_manager, path, slot, variantIdx, out exists);
internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, SetId setId) internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, SetId setId)
{ {
var eqdpFile = _eqdpCache.EqdpFile(race, accessory); var eqdpFile = _eqdpCache.EqdpFile(race, accessory);

View file

@ -1,8 +1,10 @@
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.String; using Penumbra.String;
using Penumbra.String.Classes; using Penumbra.String.Classes;
@ -74,22 +76,22 @@ internal partial record ResolveContext
return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
} }
private unsafe Utf8GamePath ResolveMaterialPath(Utf8GamePath modelPath, Utf8GamePath imcPath, byte* mtrlFileName) private unsafe Utf8GamePath ResolveMaterialPath(Utf8GamePath modelPath, ResourceHandle* imc, byte* mtrlFileName)
{ {
// Safety: // Safety:
// Resolving a material path through the game's code can dereference null pointers for equipment materials. // Resolving a material path through the game's code can dereference null pointers for equipment materials.
return ModelType switch return ModelType switch
{ {
ModelType.Human when SlotIndex < 10 && mtrlFileName[8] != (byte)'b' => ResolveEquipmentMaterialPath(modelPath, imcPath, mtrlFileName), ModelType.Human when SlotIndex < 10 && mtrlFileName[8] != (byte)'b' => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName),
ModelType.DemiHuman => ResolveEquipmentMaterialPath(modelPath, imcPath, mtrlFileName), ModelType.DemiHuman => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName),
ModelType.Weapon => ResolveWeaponMaterialPath(modelPath, imcPath, mtrlFileName), ModelType.Weapon => ResolveWeaponMaterialPath(modelPath, imc, mtrlFileName),
_ => ResolveMaterialPathNative(mtrlFileName), _ => ResolveMaterialPathNative(mtrlFileName),
}; };
} }
private unsafe Utf8GamePath ResolveEquipmentMaterialPath(Utf8GamePath modelPath, Utf8GamePath imcPath, byte* mtrlFileName) private unsafe Utf8GamePath ResolveEquipmentMaterialPath(Utf8GamePath modelPath, ResourceHandle* imc, byte* mtrlFileName)
{ {
var variant = ResolveMaterialVariant(imcPath); var variant = ResolveMaterialVariant(imc);
var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName); var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName);
Span<byte> pathBuffer = stackalloc byte[260]; Span<byte> pathBuffer = stackalloc byte[260];
@ -98,7 +100,7 @@ internal partial record ResolveContext
return Utf8GamePath.FromSpan(pathBuffer, out var path) ? path.Clone() : Utf8GamePath.Empty; return Utf8GamePath.FromSpan(pathBuffer, out var path) ? path.Clone() : Utf8GamePath.Empty;
} }
private unsafe Utf8GamePath ResolveWeaponMaterialPath(Utf8GamePath modelPath, Utf8GamePath imcPath, byte* mtrlFileName) private unsafe Utf8GamePath ResolveWeaponMaterialPath(Utf8GamePath modelPath, ResourceHandle* imc, byte* mtrlFileName)
{ {
var setIdHigh = Equipment.Set.Id / 100; var setIdHigh = Equipment.Set.Id / 100;
// All MCH (20??) weapons' materials C are one and the same // All MCH (20??) weapons' materials C are one and the same
@ -111,7 +113,7 @@ internal partial record ResolveContext
var setIdLow = Equipment.Set.Id % 100; var setIdLow = Equipment.Set.Id % 100;
if (setIdLow > 50) if (setIdLow > 50)
{ {
var variant = ResolveMaterialVariant(imcPath); var variant = ResolveMaterialVariant(imc);
var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName); var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName);
var mirroredSetId = (ushort)(Equipment.Set.Id - 50); var mirroredSetId = (ushort)(Equipment.Set.Id - 50);
@ -132,16 +134,19 @@ internal partial record ResolveContext
} }
} }
return ResolveEquipmentMaterialPath(modelPath, imcPath, mtrlFileName); return ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName);
} }
private byte ResolveMaterialVariant(Utf8GamePath imcPath) private unsafe byte ResolveMaterialVariant(ResourceHandle* imc)
{ {
var metaCache = Global.Collection.MetaCache; var imcFileData = imc->GetDataSpan();
if (metaCache == null) if (imcFileData.IsEmpty)
{
Penumbra.Log.Warning($"IMC resource handle with path {GetResourceHandlePath(imc, false)} doesn't have a valid data span");
return Equipment.Variant.Id; return Equipment.Variant.Id;
}
var entry = metaCache.GetImcEntry(imcPath, Slot, Equipment.Variant, out var exists); var entry = ImcFile.GetEntry(imcFileData, Slot, Equipment.Variant, out var exists);
if (!exists) if (!exists)
return Equipment.Variant.Id; return Equipment.Variant.Id;

View file

@ -148,7 +148,7 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, path); return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, path);
} }
public unsafe ResourceNode? CreateNodeFromModel(Model* mdl, Utf8GamePath imcPath) public unsafe ResourceNode? CreateNodeFromModel(Model* mdl, ResourceHandle* imc)
{ {
if (mdl == null || mdl->ModelResourceHandle == null) if (mdl == null || mdl->ModelResourceHandle == null)
return null; return null;
@ -169,7 +169,7 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
continue; continue;
var mtrlFileName = GetMaterialFileNameBySlot(mdlResource, (uint)i); var mtrlFileName = GetMaterialFileNameBySlot(mdlResource, (uint)i);
var mtrlNode = CreateNodeFromMaterial(mtrl, ResolveMaterialPath(path, imcPath, mtrlFileName)); var mtrlNode = CreateNodeFromMaterial(mtrl, ResolveMaterialPath(path, imc, mtrlFileName));
if (mtrlNode != null) if (mtrlNode != null)
{ {
if (Global.WithUiData) if (Global.WithUiData)
@ -358,7 +358,7 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
return i >= 0 && i < array.Length ? array[i] : null; return i >= 0 && i < array.Length ? array[i] : null;
} }
internal static unsafe ByteString GetResourceHandlePath(ResourceHandle* handle) internal static unsafe ByteString GetResourceHandlePath(ResourceHandle* handle, bool stripPrefix = true)
{ {
if (handle == null) if (handle == null)
return ByteString.Empty; return ByteString.Empty;
@ -367,7 +367,7 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
if (name.IsEmpty) if (name.IsEmpty)
return ByteString.Empty; return ByteString.Empty;
if (name[0] == (byte)'|') if (stripPrefix && name[0] == (byte)'|')
{ {
var pos = name.IndexOf((byte)'|', 1); var pos = name.IndexOf((byte)'|', 1);
if (pos < 0) if (pos < 0)

View file

@ -83,7 +83,7 @@ public class ResourceTree
} }
var mdl = model->Models[i]; var mdl = model->Models[i];
var mdlNode = slotContext.CreateNodeFromModel(mdl, imcNode?.GamePath ?? Utf8GamePath.Empty); var mdlNode = slotContext.CreateNodeFromModel(mdl, imc);
if (mdlNode != null) if (mdlNode != null)
{ {
if (globalContext.WithUiData) if (globalContext.WithUiData)
@ -135,7 +135,7 @@ public class ResourceTree
} }
var mdl = subObject->Models[i]; var mdl = subObject->Models[i];
var mdlNode = slotContext.CreateNodeFromModel(mdl, imcNode?.GamePath ?? Utf8GamePath.Empty); var mdlNode = slotContext.CreateNodeFromModel(mdl, imc);
if (mdlNode != null) if (mdlNode != null)
{ {
if (globalContext.WithUiData) if (globalContext.WithUiData)

View file

@ -168,11 +168,19 @@ public unsafe class ImcFile : MetaBaseFile
if (file == null) if (file == null)
throw new Exception(); throw new Exception();
fixed (byte* ptr = file.Data) return GetEntry(file.Data, slot, variantIdx, out exists);
}
public static ImcEntry GetEntry(ReadOnlySpan<byte> imcFileData, EquipSlot slot, Variant variantIdx, out bool exists)
{
fixed (byte* ptr = imcFileData)
{ {
var entry = VariantPtr(ptr, PartIndex(slot), variantIdx); var entry = VariantPtr(ptr, PartIndex(slot), variantIdx);
if (entry == null) if (entry == null)
{
exists = false;
return new ImcEntry(); return new ImcEntry();
}
exists = true; exists = true;
return *entry; return *entry;