Merge branch 'fix/skin-fixer-rc'

This commit is contained in:
Ottermandias 2023-09-05 14:48:18 +02:00
commit 1cb74aeb9a
7 changed files with 86 additions and 88 deletions

@ -1 +1 @@
Subproject commit 8c7a309d039fdf008c85cf51923b4eac51b32428
Subproject commit 86ec4d72c9c9ed57aa7be4a7d0c81069c5b94ad7

View file

@ -16,9 +16,6 @@ public sealed class CreatedCharacterBase : EventWrapper<Action<nint, ModCollecti
{
/// <seealso cref="PenumbraApi.CreatedCharacterBase"/>
Api = int.MinValue,
/// <seealso cref="Interop.Services.SkinFixer.OnCharacterBaseCreated"/>
SkinFixer = 0,
}
public CreatedCharacterBase()

View file

@ -0,0 +1,24 @@
using System;
using OtterGui.Classes;
namespace Penumbra.Communication;
/// <summary> <list type="number">
/// <item>Parameter is the material resource handle for which the shader package has been loaded. </item>
/// <item>Parameter is the associated game object. </item>
/// </list> </summary>
public sealed class MtrlShpkLoaded : EventWrapper<Action<nint, nint>, MtrlShpkLoaded.Priority>
{
public enum Priority
{
/// <seealso cref="Interop.Services.SkinFixer.OnMtrlShpkLoaded"/>
SkinFixer = 0,
}
public MtrlShpkLoaded()
: base(nameof(MtrlShpkLoaded))
{ }
public void Invoke(nint mtrlResourceHandle, nint gameObject)
=> Invoke(this, mtrlResourceHandle, gameObject);
}

View file

@ -11,6 +11,7 @@ using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.Services;
using Penumbra.Interop.Structs;
using Penumbra.Services;
using Penumbra.String;
using Penumbra.String.Classes;
using Penumbra.Util;
@ -27,19 +28,21 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePai
private readonly PerformanceTracker _performance;
private readonly ResourceLoader _loader;
private readonly GameEventManager _events;
private readonly CommunicatorService _communicator;
private readonly ThreadLocal<ResolveData> _mtrlData = new(() => ResolveData.Invalid);
private readonly ThreadLocal<ResolveData> _avfxData = new(() => ResolveData.Invalid);
private readonly ConcurrentDictionary<nint, ResolveData> _subFileCollection = new();
public SubfileHelper(PerformanceTracker performance, ResourceLoader loader, GameEventManager events)
public SubfileHelper(PerformanceTracker performance, ResourceLoader loader, GameEventManager events, CommunicatorService communicator)
{
SignatureHelper.Initialise(this);
_performance = performance;
_loader = loader;
_events = events;
_communicator = communicator;
_loadMtrlShpkHook.Enable();
_loadMtrlTexHook.Enable();
@ -151,9 +154,11 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePai
{
using var performance = _performance.Measure(PerformanceType.LoadShaders);
var last = _mtrlData.Value;
_mtrlData.Value = LoadFileHelper(mtrlResourceHandle);
var mtrlData = LoadFileHelper(mtrlResourceHandle);
_mtrlData.Value = mtrlData;
var ret = _loadMtrlShpkHook.Original(mtrlResourceHandle);
_mtrlData.Value = last;
_communicator.MtrlShpkLoaded.Invoke(mtrlResourceHandle, mtrlData.AssociatedGameObject);
return ret;
}

View file

@ -1,30 +1,20 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Penumbra.Collections;
using OtterGui.Classes;
using Penumbra.Communication;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.SafeHandles;
using Penumbra.Services;
using Penumbra.String.Classes;
namespace Penumbra.Interop.Services;
public sealed unsafe class SkinFixer : IDisposable
{
public static readonly Utf8GamePath SkinShpkPath =
Utf8GamePath.FromSpan("shader/sm5/shpk/skin.shpk"u8, out var p) ? p : Utf8GamePath.Empty;
public static ReadOnlySpan<byte> SkinShpkName
=> "skin.shpk"u8;
[Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)]
private readonly nint* _humanVTable = null!;
@ -45,107 +35,85 @@ public sealed unsafe class SkinFixer : IDisposable
private readonly GameEventManager _gameEvents;
private readonly CommunicatorService _communicator;
private readonly ResourceLoader _resources;
private readonly CharacterUtility _utility;
// CharacterBase to ShpkHandle
private readonly ConcurrentDictionary<nint, SafeResourceHandle> _skinShpks = new();
// MaterialResourceHandle set
private readonly ConcurrentSet<nint> _moddedSkinShpkMaterials = new();
private readonly object _lock = new();
private int _moddedSkinShpkCount = 0;
private ulong _slowPathCallDelta = 0;
// ConcurrentDictionary.Count uses a lock in its current implementation.
private int _moddedSkinShpkCount;
private ulong _slowPathCallDelta;
public bool Enabled { get; internal set; } = true;
public int ModdedSkinShpkCount
=> _moddedSkinShpkCount;
public SkinFixer(GameEventManager gameEvents, ResourceLoader resources, CharacterUtility utility, CommunicatorService communicator)
public SkinFixer(GameEventManager gameEvents, CharacterUtility utility, CommunicatorService communicator)
{
SignatureHelper.Initialise(this);
_gameEvents = gameEvents;
_resources = resources;
_utility = utility;
_communicator = communicator;
_onRenderMaterialHook = Hook<OnRenderMaterialDelegate>.FromAddress(_humanVTable[62], OnRenderHumanMaterial);
_communicator.CreatedCharacterBase.Subscribe(OnCharacterBaseCreated, CreatedCharacterBase.Priority.SkinFixer);
_gameEvents.CharacterBaseDestructor += OnCharacterBaseDestructor;
_communicator.MtrlShpkLoaded.Subscribe(OnMtrlShpkLoaded, MtrlShpkLoaded.Priority.SkinFixer);
_gameEvents.ResourceHandleDestructor += OnResourceHandleDestructor;
_onRenderMaterialHook.Enable();
}
public void Dispose()
{
_onRenderMaterialHook.Dispose();
_communicator.CreatedCharacterBase.Unsubscribe(OnCharacterBaseCreated);
_gameEvents.CharacterBaseDestructor -= OnCharacterBaseDestructor;
foreach (var skinShpk in _skinShpks.Values)
skinShpk.Dispose();
_skinShpks.Clear();
_communicator.MtrlShpkLoaded.Unsubscribe(OnMtrlShpkLoaded);
_gameEvents.ResourceHandleDestructor -= OnResourceHandleDestructor;
_moddedSkinShpkMaterials.Clear();
_moddedSkinShpkCount = 0;
}
public ulong GetAndResetSlowPathCallDelta()
=> Interlocked.Exchange(ref _slowPathCallDelta, 0);
private void OnCharacterBaseCreated(nint gameObject, ModCollection collection, nint drawObject)
private static bool IsSkinMaterial(Structs.MtrlResource* mtrlResource)
{
if (((CharacterBase*)drawObject)->GetModelType() != CharacterBase.ModelType.Human)
if (mtrlResource == null)
return false;
var shpkName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlResource->ShpkString);
return SkinShpkName.SequenceEqual(shpkName);
}
private void OnMtrlShpkLoaded(nint mtrlResourceHandle, nint gameObject)
{
var mtrl = (Structs.MtrlResource*)mtrlResourceHandle;
var shpk = mtrl->ShpkResourceHandle;
if (shpk == null)
return;
Task.Run(() =>
{
var skinShpk = SafeResourceHandle.CreateInvalid();
try
{
var data = collection.ToResolveData(gameObject);
if (data.Valid)
{
var loadedShpk = _resources.LoadResolvedResource(ResourceCategory.Shader, ResourceType.Shpk, SkinShpkPath.Path, data);
skinShpk = new SafeResourceHandle((ResourceHandle*)loadedShpk, false);
}
}
catch (Exception e)
{
Penumbra.Log.Error($"Error while resolving skin.shpk for human {drawObject:X}: {e}");
}
if (!IsSkinMaterial(mtrl) || (nint)shpk == _utility.DefaultSkinShpkResource)
return;
if (!skinShpk.IsInvalid)
{
if (_skinShpks.TryAdd(drawObject, skinShpk))
{
if ((nint)skinShpk.ResourceHandle != _utility.DefaultSkinShpkResource)
if (_moddedSkinShpkMaterials.TryAdd(mtrlResourceHandle))
Interlocked.Increment(ref _moddedSkinShpkCount);
}
else
{
skinShpk.Dispose();
}
}
});
}
private void OnCharacterBaseDestructor(nint characterBase)
private void OnResourceHandleDestructor(Structs.ResourceHandle* handle)
{
if (!_skinShpks.Remove(characterBase, out var skinShpk))
return;
var handle = skinShpk.ResourceHandle;
skinShpk.Dispose();
if ((nint)handle != _utility.DefaultSkinShpkResource)
if (_moddedSkinShpkMaterials.TryRemove((nint)handle))
Interlocked.Decrement(ref _moddedSkinShpkCount);
}
private nint OnRenderHumanMaterial(nint human, OnRenderMaterialParams* param)
{
// If we don't have any on-screen instances of modded skin.shpk, we don't need the slow path at all.
if (!Enabled || _moddedSkinShpkCount == 0 || !_skinShpks.TryGetValue(human, out var skinShpk) || skinShpk.IsInvalid)
return _onRenderMaterialHook!.Original(human, param);
if (!Enabled || _moddedSkinShpkCount == 0)
return _onRenderMaterialHook.Original(human, param);
var material = param->Model->Materials[param->MaterialIndex];
var shpkResource = ((Structs.MtrlResource*)material->MaterialResourceHandle)->ShpkResourceHandle;
if ((nint)shpkResource != (nint)skinShpk.ResourceHandle)
return _onRenderMaterialHook!.Original(human, param);
var mtrlResource = (Structs.MtrlResource*)material->MaterialResourceHandle;
if (!IsSkinMaterial(mtrlResource))
return _onRenderMaterialHook.Original(human, param);
Interlocked.Increment(ref _slowPathCallDelta);
@ -158,8 +126,8 @@ public sealed unsafe class SkinFixer : IDisposable
{
try
{
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)skinShpk.ResourceHandle;
return _onRenderMaterialHook!.Original(human, param);
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)mtrlResource->ShpkResourceHandle;
return _onRenderMaterialHook.Original(human, param);
}
finally
{

View file

@ -24,6 +24,9 @@ public class CommunicatorService : IDisposable
/// <inheritdoc cref="Communication.CreatedCharacterBase"/>
public readonly CreatedCharacterBase CreatedCharacterBase = new();
/// <inheritdoc cref="Communication.MtrlShpkLoaded"/>
public readonly MtrlShpkLoaded MtrlShpkLoaded = new();
/// <inheritdoc cref="Communication.ModDataChanged"/>
public readonly ModDataChanged ModDataChanged = new();
@ -75,6 +78,7 @@ public class CommunicatorService : IDisposable
TemporaryGlobalModChange.Dispose();
CreatingCharacterBase.Dispose();
CreatedCharacterBase.Dispose();
MtrlShpkLoaded.Dispose();
ModDataChanged.Dispose();
ModOptionChanged.Dispose();
ModDiscoveryStarted.Dispose();

View file

@ -603,7 +603,7 @@ public class DebugTab : Window, ITab
ImGui.SameLine();
ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0));
ImGui.SameLine();
ImGui.TextUnformatted($"Draw Objects with Modded skin.shpk: {_skinFixer.ModdedSkinShpkCount}");
ImGui.TextUnformatted($"Materials with Modded skin.shpk: {_skinFixer.ModdedSkinShpkCount}");
}
using var table = Table("##CharacterUtility", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,