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.String;
using Penumbra.String.Classes;
using static Penumbra.GameData.Files.ShpkFile;
namespace Penumbra.Interop.ResourceTree;
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)
=> 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,
CharacterArmor Equipment)
internal record class ResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames,
Dictionary<nint, ResourceNode> Nodes, EquipSlot Slot, CharacterArmor Equipment)
{
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
private unsafe ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, ByteString gamePath, bool @internal)
{
if (Nodes.TryGetValue((nint)resourceHandle, out var cached))
return cached;
if (gamePath.IsEmpty)
return null;
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)
{
if (Nodes.TryGetValue((nint)resourceHandle, out var cached))
return cached;
if (dx11)
{
var lastDirectorySeparator = gamePath.LastIndexOf((byte)'/');
@ -69,7 +78,11 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
if (fullPath.InternalName.IsEmpty)
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,
@ -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);
}
public unsafe ResourceNode? CreateHumanSkeletonNode(GenderRace gr)
{
var raceSexIdStr = gr.ToRaceCode();
@ -108,6 +122,9 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
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);
if (node == null)
return null;
@ -118,17 +135,31 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
node = node.WithName(name != null ? $"IMC: {name}" : null);
}
Nodes.Add((nint)imc, node);
return node;
}
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)
{
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
return null;
if (Nodes.TryGetValue((nint)mdl->ResourceHandle, out var cached))
return cached;
var node = CreateNodeFromResourceHandle(ResourceType.Mdl, (nint) mdl, mdl->ResourceHandle, false, false);
if (node == null)
return null;
@ -148,6 +179,8 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
: mtrlNode);
}
Nodes.Add((nint)mdl->ResourceHandle, node);
return node;
}
@ -188,7 +221,10 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
if (mtrl == 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);
if (node == null)
return null;
@ -231,6 +267,8 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
}
}
Nodes.Add((nint)resource, node);
return node;
}
@ -239,7 +277,29 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
if (sklb->SkeletonResourceHandle == 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)
@ -356,7 +416,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
return name;
}
static unsafe ulong GetResourceHandleLength(ResourceHandle* handle)
private static unsafe ulong GetResourceHandleLength(ResourceHandle* handle)
{
if (handle == null)
return 0;

View file

@ -13,29 +13,33 @@ namespace Penumbra.Interop.ResourceTree;
public class ResourceTree
{
public readonly string Name;
public readonly nint SourceAddress;
public readonly bool PlayerRelated;
public readonly string CollectionName;
public readonly List<ResourceNode> Nodes;
public readonly string Name;
public readonly nint GameObjectAddress;
public readonly nint DrawObjectAddress;
public readonly bool PlayerRelated;
public readonly string CollectionName;
public readonly List<ResourceNode> Nodes;
public readonly HashSet<ResourceNode> FlatNodes;
public int ModelId;
public CustomizeData CustomizeData;
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;
SourceAddress = sourceAddress;
PlayerRelated = playerRelated;
CollectionName = collectionName;
Nodes = new List<ResourceNode>();
Name = name;
GameObjectAddress = gameObjectAddress;
DrawObjectAddress = drawObjectAddress;
PlayerRelated = playerRelated;
CollectionName = collectionName;
Nodes = new List<ResourceNode>();
FlatNodes = new HashSet<ResourceNode>();
}
internal unsafe void LoadResources(GlobalResolveContext globalContext)
{
var character = (Character*)SourceAddress;
var model = (CharacterBase*)character->GameObject.GetDrawObject();
var character = (Character*)GameObjectAddress;
var model = (CharacterBase*)DrawObjectAddress;
var equipment = new ReadOnlySpan<CharacterArmor>(&character->DrawData.Head, 10);
// var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 );
ModelId = character->CharacterData.ModelCharaId;
@ -130,6 +134,10 @@ public class ResourceTree
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
if (sklbNode != null)
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;
var gameObjStruct = (GameObject*)character.Address;
if (gameObjStruct->GetDrawObject() == null)
var drawObjStruct = gameObjStruct->GetDrawObject();
if (drawObjStruct == null)
return null;
var collectionResolveData = _collectionResolver.IdentifyCollection(gameObjStruct, true);
@ -70,10 +71,11 @@ public class ResourceTreeFactory
return null;
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,
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withNames);
tree.LoadResources(globalContext);
tree.FlatNodes.UnionWith(globalContext.Nodes.Values);
return tree;
}

View file

@ -18,7 +18,7 @@ public class ResourceTreeViewer
private readonly int _actionCapacity;
private readonly Action _onRefresh;
private readonly Action<ResourceNode, Vector2> _drawActions;
private readonly HashSet<ResourceNode> _unfolded;
private readonly HashSet<nint> _unfolded;
private Task<ResourceTree[]>? _task;
@ -30,7 +30,7 @@ public class ResourceTreeViewer
_actionCapacity = actionCapacity;
_onRefresh = onRefresh;
_drawActions = drawActions;
_unfolded = new HashSet<ResourceNode>();
_unfolded = new HashSet<nint>();
}
public void Draw()
@ -82,7 +82,7 @@ public class ResourceTreeViewer
(_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight());
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 frameHeight = ImGui.GetFrameHeight();
@ -109,26 +109,36 @@ public class ResourceTreeViewer
foreach (var (resourceNode, index) in resourceNodes.WithIndex())
{
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);
ImGui.TableNextColumn();
var unfolded = _unfolded.Contains(resourceNode);
var unfolded = _unfolded.Contains(nodePathHash);
using (var indent = ImRaii.PushIndent(level))
{
ImGui.TableHeader((resourceNode.Children.Count > 0 ? unfolded ? "[-] " : "[+] " : string.Empty) + resourceNode.Name);
if (ImGui.IsItemClicked() && resourceNode.Children.Count > 0)
{
if (unfolded)
_unfolded.Remove(resourceNode);
_unfolded.Remove(nodePathHash);
else
_unfolded.Add(resourceNode);
_unfolded.Add(nodePathHash);
unfolded = !unfolded;
}
if (debugMode)
if (debugMode)
{
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
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();
@ -171,7 +181,7 @@ public class ResourceTreeViewer
}
if (unfolded)
DrawNodes(resourceNode.Children, level + 1);
DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31));
}
}
}