Resource Tree: Deduplicate nodes, add skp

This commit is contained in:
Exter-N 2023-09-01 04:52:54 +02:00
parent ccc0b51a99
commit db521dd21c
4 changed files with 114 additions and 34 deletions

View file

@ -11,21 +11,27 @@ using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.String; using Penumbra.String;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using static Penumbra.GameData.Files.ShpkFile;
namespace Penumbra.Interop.ResourceTree; namespace Penumbra.Interop.ResourceTree;
internal record class GlobalResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames) internal record class GlobalResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames)
{ {
public readonly Dictionary<nint, ResourceNode> Nodes = new(128);
public ResolveContext CreateContext(EquipSlot slot, CharacterArmor equipment) public ResolveContext CreateContext(EquipSlot slot, CharacterArmor equipment)
=> new(Config, Identifier, TreeBuildCache, Collection, Skeleton, WithNames, slot, equipment); => new(Config, Identifier, TreeBuildCache, Collection, Skeleton, WithNames, Nodes, slot, equipment);
} }
internal record class ResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames, EquipSlot Slot, internal record class ResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames,
CharacterArmor Equipment) Dictionary<nint, ResourceNode> Nodes, EquipSlot Slot, CharacterArmor Equipment)
{ {
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true); private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
private unsafe ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, ByteString gamePath, bool @internal) private unsafe ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, ByteString gamePath, bool @internal)
{ {
if (Nodes.TryGetValue((nint)resourceHandle, out var cached))
return cached;
if (gamePath.IsEmpty) if (gamePath.IsEmpty)
return null; return null;
if (!Utf8GamePath.FromByteString(ByteString.Join((byte)'/', ShpkPrefix, gamePath), out var path, false)) if (!Utf8GamePath.FromByteString(ByteString.Join((byte)'/', ShpkPrefix, gamePath), out var path, false))
@ -36,6 +42,9 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
private unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* resourceHandle, ByteString gamePath, bool @internal, bool dx11) private unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* resourceHandle, ByteString gamePath, bool @internal, bool dx11)
{ {
if (Nodes.TryGetValue((nint)resourceHandle, out var cached))
return cached;
if (dx11) if (dx11)
{ {
var lastDirectorySeparator = gamePath.LastIndexOf((byte)'/'); var lastDirectorySeparator = gamePath.LastIndexOf((byte)'/');
@ -69,7 +78,11 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
if (fullPath.InternalName.IsEmpty) if (fullPath.InternalName.IsEmpty)
fullPath = Collection.ResolvePath(gamePath) ?? new FullPath(gamePath); fullPath = Collection.ResolvePath(gamePath) ?? new FullPath(gamePath);
return new(null, type, objectAddress, (nint)resourceHandle, gamePath, FilterFullPath(fullPath), GetResourceHandleLength(resourceHandle), @internal); var node = new ResourceNode(null, type, objectAddress, (nint)resourceHandle, gamePath, FilterFullPath(fullPath), GetResourceHandleLength(resourceHandle), @internal);
if (resourceHandle != null)
Nodes.Add((nint)resourceHandle, node);
return node;
} }
private unsafe ResourceNode? CreateNodeFromResourceHandle(ResourceType type, nint objectAddress, ResourceHandle* handle, bool @internal, private unsafe ResourceNode? CreateNodeFromResourceHandle(ResourceType type, nint objectAddress, ResourceHandle* handle, bool @internal,
@ -95,6 +108,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
return new ResourceNode(null, type, objectAddress, (nint)handle, gamePaths.ToArray(), fullPath, GetResourceHandleLength(handle), @internal); return new ResourceNode(null, type, objectAddress, (nint)handle, gamePaths.ToArray(), fullPath, GetResourceHandleLength(handle), @internal);
} }
public unsafe ResourceNode? CreateHumanSkeletonNode(GenderRace gr) public unsafe ResourceNode? CreateHumanSkeletonNode(GenderRace gr)
{ {
var raceSexIdStr = gr.ToRaceCode(); var raceSexIdStr = gr.ToRaceCode();
@ -108,6 +122,9 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
public unsafe ResourceNode? CreateNodeFromImc(ResourceHandle* imc) public unsafe ResourceNode? CreateNodeFromImc(ResourceHandle* imc)
{ {
if (Nodes.TryGetValue((nint)imc, out var cached))
return cached;
var node = CreateNodeFromResourceHandle(ResourceType.Imc, 0, imc, true, false); var node = CreateNodeFromResourceHandle(ResourceType.Imc, 0, imc, true, false);
if (node == null) if (node == null)
return null; return null;
@ -118,17 +135,31 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
node = node.WithName(name != null ? $"IMC: {name}" : null); node = node.WithName(name != null ? $"IMC: {name}" : null);
} }
Nodes.Add((nint)imc, node);
return node; return node;
} }
public unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex) public unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex)
=> CreateNodeFromResourceHandle(ResourceType.Tex, (nint)tex->KernelTexture, &tex->Handle, false, WithNames); {
if (Nodes.TryGetValue((nint)tex, out var cached))
return cached;
var node = CreateNodeFromResourceHandle(ResourceType.Tex, (nint)tex->KernelTexture, &tex->Handle, false, WithNames);
if (node != null)
Nodes.Add((nint)tex, node);
return node;
}
public unsafe ResourceNode? CreateNodeFromRenderModel(RenderModel* mdl) public unsafe ResourceNode? CreateNodeFromRenderModel(RenderModel* mdl)
{ {
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara) if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
return null; return null;
if (Nodes.TryGetValue((nint)mdl->ResourceHandle, out var cached))
return cached;
var node = CreateNodeFromResourceHandle(ResourceType.Mdl, (nint) mdl, mdl->ResourceHandle, false, false); var node = CreateNodeFromResourceHandle(ResourceType.Mdl, (nint) mdl, mdl->ResourceHandle, false, false);
if (node == null) if (node == null)
return null; return null;
@ -148,6 +179,8 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
: mtrlNode); : mtrlNode);
} }
Nodes.Add((nint)mdl->ResourceHandle, node);
return node; return node;
} }
@ -189,6 +222,9 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
return null; return null;
var resource = mtrl->ResourceHandle; var resource = mtrl->ResourceHandle;
if (Nodes.TryGetValue((nint)resource, out var cached))
return cached;
var node = CreateNodeFromResourceHandle(ResourceType.Mtrl, (nint) mtrl, &resource->Handle, false, WithNames); var node = CreateNodeFromResourceHandle(ResourceType.Mtrl, (nint) mtrl, &resource->Handle, false, WithNames);
if (node == null) if (node == null)
return null; return null;
@ -231,6 +267,8 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
} }
} }
Nodes.Add((nint)resource, node);
return node; return node;
} }
@ -239,7 +277,29 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
if (sklb->SkeletonResourceHandle == null) if (sklb->SkeletonResourceHandle == null)
return null; return null;
return CreateNodeFromResourceHandle(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, false, WithNames); if (Nodes.TryGetValue((nint)sklb->SkeletonResourceHandle, out var cached))
return cached;
var node = CreateNodeFromResourceHandle(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, false, WithNames);
if (node != null)
Nodes.Add((nint)sklb->SkeletonResourceHandle, node);
return node;
}
public unsafe ResourceNode? CreateParameterNodeFromPartialSkeleton(FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton* sklb)
{
if (sklb->SkeletonParameterResourceHandle == null)
return null;
if (Nodes.TryGetValue((nint)sklb->SkeletonParameterResourceHandle, out var cached))
return cached;
var node = CreateNodeFromResourceHandle(ResourceType.Skp, (nint)sklb, (ResourceHandle*)sklb->SkeletonParameterResourceHandle, true, WithNames);
if (node != null)
Nodes.Add((nint)sklb->SkeletonParameterResourceHandle, node);
return node;
} }
private FullPath FilterFullPath(FullPath fullPath) private FullPath FilterFullPath(FullPath fullPath)
@ -356,7 +416,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
return name; return name;
} }
static unsafe ulong GetResourceHandleLength(ResourceHandle* handle) private static unsafe ulong GetResourceHandleLength(ResourceHandle* handle)
{ {
if (handle == null) if (handle == null)
return 0; return 0;

View file

@ -14,28 +14,32 @@ namespace Penumbra.Interop.ResourceTree;
public class ResourceTree public class ResourceTree
{ {
public readonly string Name; public readonly string Name;
public readonly nint SourceAddress; public readonly nint GameObjectAddress;
public readonly nint DrawObjectAddress;
public readonly bool PlayerRelated; public readonly bool PlayerRelated;
public readonly string CollectionName; public readonly string CollectionName;
public readonly List<ResourceNode> Nodes; public readonly List<ResourceNode> Nodes;
public readonly HashSet<ResourceNode> FlatNodes;
public int ModelId; public int ModelId;
public CustomizeData CustomizeData; public CustomizeData CustomizeData;
public GenderRace RaceCode; public GenderRace RaceCode;
public ResourceTree(string name, nint sourceAddress, bool playerRelated, string collectionName) public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName)
{ {
Name = name; Name = name;
SourceAddress = sourceAddress; GameObjectAddress = gameObjectAddress;
DrawObjectAddress = drawObjectAddress;
PlayerRelated = playerRelated; PlayerRelated = playerRelated;
CollectionName = collectionName; CollectionName = collectionName;
Nodes = new List<ResourceNode>(); Nodes = new List<ResourceNode>();
FlatNodes = new HashSet<ResourceNode>();
} }
internal unsafe void LoadResources(GlobalResolveContext globalContext) internal unsafe void LoadResources(GlobalResolveContext globalContext)
{ {
var character = (Character*)SourceAddress; var character = (Character*)GameObjectAddress;
var model = (CharacterBase*)character->GameObject.GetDrawObject(); var model = (CharacterBase*)DrawObjectAddress;
var equipment = new ReadOnlySpan<CharacterArmor>(&character->DrawData.Head, 10); var equipment = new ReadOnlySpan<CharacterArmor>(&character->DrawData.Head, 10);
// var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 ); // var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 );
ModelId = character->CharacterData.ModelCharaId; ModelId = character->CharacterData.ModelCharaId;
@ -130,6 +134,10 @@ public class ResourceTree
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]); var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
if (sklbNode != null) if (sklbNode != null)
nodes.Add(context.WithNames ? sklbNode.WithName($"{prefix}Skeleton #{i}") : sklbNode); nodes.Add(context.WithNames ? sklbNode.WithName($"{prefix}Skeleton #{i}") : sklbNode);
var skpNode = context.CreateParameterNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
if (skpNode != null)
nodes.Add(context.WithNames ? skpNode.WithName($"{prefix}Skeleton #{i} Parameters") : skpNode);
} }
} }
} }

View file

@ -62,7 +62,8 @@ public class ResourceTreeFactory
return null; return null;
var gameObjStruct = (GameObject*)character.Address; var gameObjStruct = (GameObject*)character.Address;
if (gameObjStruct->GetDrawObject() == null) var drawObjStruct = gameObjStruct->GetDrawObject();
if (drawObjStruct == null)
return null; return null;
var collectionResolveData = _collectionResolver.IdentifyCollection(gameObjStruct, true); var collectionResolveData = _collectionResolver.IdentifyCollection(gameObjStruct, true);
@ -70,10 +71,11 @@ public class ResourceTreeFactory
return null; return null;
var (name, related) = GetCharacterName(character, cache); var (name, related) = GetCharacterName(character, cache);
var tree = new ResourceTree(name, (nint)gameObjStruct, related, collectionResolveData.ModCollection.Name); var tree = new ResourceTree(name, (nint)gameObjStruct, (nint)drawObjStruct, related, collectionResolveData.ModCollection.Name);
var globalContext = new GlobalResolveContext(_config, _identifier.AwaitedService, cache, collectionResolveData.ModCollection, var globalContext = new GlobalResolveContext(_config, _identifier.AwaitedService, cache, collectionResolveData.ModCollection,
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withNames); ((Character*)gameObjStruct)->CharacterData.ModelCharaId, withNames);
tree.LoadResources(globalContext); tree.LoadResources(globalContext);
tree.FlatNodes.UnionWith(globalContext.Nodes.Values);
return tree; return tree;
} }

View file

@ -18,7 +18,7 @@ public class ResourceTreeViewer
private readonly int _actionCapacity; private readonly int _actionCapacity;
private readonly Action _onRefresh; private readonly Action _onRefresh;
private readonly Action<ResourceNode, Vector2> _drawActions; private readonly Action<ResourceNode, Vector2> _drawActions;
private readonly HashSet<ResourceNode> _unfolded; private readonly HashSet<nint> _unfolded;
private Task<ResourceTree[]>? _task; private Task<ResourceTree[]>? _task;
@ -30,7 +30,7 @@ public class ResourceTreeViewer
_actionCapacity = actionCapacity; _actionCapacity = actionCapacity;
_onRefresh = onRefresh; _onRefresh = onRefresh;
_drawActions = drawActions; _drawActions = drawActions;
_unfolded = new HashSet<ResourceNode>(); _unfolded = new HashSet<nint>();
} }
public void Draw() public void Draw()
@ -82,7 +82,7 @@ public class ResourceTreeViewer
(_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight()); (_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight());
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
DrawNodes(tree.Nodes, 0); DrawNodes(tree.Nodes, 0, 0);
} }
} }
} }
@ -101,7 +101,7 @@ public class ResourceTreeViewer
} }
}); });
private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level) private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level, nint pathHash)
{ {
var debugMode = _config.DebugMode; var debugMode = _config.DebugMode;
var frameHeight = ImGui.GetFrameHeight(); var frameHeight = ImGui.GetFrameHeight();
@ -111,24 +111,34 @@ public class ResourceTreeViewer
if (resourceNode.Internal && !debugMode) if (resourceNode.Internal && !debugMode)
continue; continue;
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
var textColorInternal = (textColor & 0x00FFFFFFu) | ((textColor & 0xFE000000u) >> 1); // Half opacity
using var mutedColor = ImRaii.PushColor(ImGuiCol.Text, textColorInternal, resourceNode.Internal);
var nodePathHash = unchecked(pathHash + resourceNode.ResourceHandle);
using var id = ImRaii.PushId(index); using var id = ImRaii.PushId(index);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var unfolded = _unfolded.Contains(resourceNode); var unfolded = _unfolded.Contains(nodePathHash);
using (var indent = ImRaii.PushIndent(level)) using (var indent = ImRaii.PushIndent(level))
{ {
ImGui.TableHeader((resourceNode.Children.Count > 0 ? unfolded ? "[-] " : "[+] " : string.Empty) + resourceNode.Name); ImGui.TableHeader((resourceNode.Children.Count > 0 ? unfolded ? "[-] " : "[+] " : string.Empty) + resourceNode.Name);
if (ImGui.IsItemClicked() && resourceNode.Children.Count > 0) if (ImGui.IsItemClicked() && resourceNode.Children.Count > 0)
{ {
if (unfolded) if (unfolded)
_unfolded.Remove(resourceNode); _unfolded.Remove(nodePathHash);
else else
_unfolded.Add(resourceNode); _unfolded.Add(nodePathHash);
unfolded = !unfolded; unfolded = !unfolded;
} }
if (debugMode) if (debugMode)
{
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
ImGuiUtil.HoverTooltip( ImGuiUtil.HoverTooltip(
$"Resource Type: {resourceNode.Type}\nObject Address: 0x{resourceNode.ObjectAddress:X16}\nResource Handle: 0x{resourceNode.ResourceHandle:X16}\nLength: 0x{resourceNode.Length:X}"); $"Resource Type: {resourceNode.Type}\nObject Address: 0x{resourceNode.ObjectAddress:X16}\nResource Handle: 0x{resourceNode.ResourceHandle:X16}\nLength: 0x{resourceNode.Length:X16}");
}
} }
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@ -171,7 +181,7 @@ public class ResourceTreeViewer
} }
if (unfolded) if (unfolded)
DrawNodes(resourceNode.Children, level + 1); DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31));
} }
} }
} }