Use better event in SkinFixer and some cleanup.

This commit is contained in:
Ottermandias 2023-08-30 20:52:21 +02:00
parent f238049750
commit 6d3e930440
5 changed files with 57 additions and 79 deletions

View file

@ -142,7 +142,7 @@ public unsafe class MetaState : IDisposable
_characterBaseCreateMetaChanges = DisposableContainer.Empty;
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero)
_communicator.CreatedCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject,
_lastCreatedCollection.ModCollection.Name, drawObject);
_lastCreatedCollection.ModCollection, drawObject);
_lastCreatedCollection = ResolveData.Invalid;
}

View file

@ -10,16 +10,18 @@ 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 Penumbra.Communication;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.Interop.PathResolving;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.SafeHandles;
using Penumbra.Services;
using Penumbra.String.Classes;
namespace Penumbra.Interop.Services;
public unsafe class SkinFixer : IDisposable
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;
@ -34,60 +36,48 @@ public unsafe class SkinFixer : IDisposable
{
[FieldOffset(0x0)]
public Model* Model;
[FieldOffset(0x8)]
public uint MaterialIndex;
}
private readonly Hook<OnRenderMaterialDelegate> _onRenderMaterialHook;
private readonly CollectionResolver _collectionResolver;
private readonly GameEventManager _gameEvents;
private readonly ResourceLoader _resources;
private readonly CharacterUtility _utility;
private readonly ConcurrentDictionary<nint /* CharacterBase* */, SafeResourceHandle> _skinShpks = new();
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();
private readonly object _lock = new();
private bool _enabled = true;
private int _moddedSkinShpkCount = 0;
private ulong _slowPathCallDelta = 0;
public bool Enabled
{
get => _enabled;
set => _enabled = value;
}
private ulong _slowPathCallDelta = 0;
public bool Enabled { get; internal set; } = true;
public int ModdedSkinShpkCount
=> _moddedSkinShpkCount;
public SkinFixer(CollectionResolver collectionResolver, GameEventManager gameEvents, ResourceLoader resources, CharacterUtility utility, DrawObjectState _)
public SkinFixer(GameEventManager gameEvents, ResourceLoader resources, CharacterUtility utility, CommunicatorService communicator)
{
SignatureHelper.Initialise(this);
_collectionResolver = collectionResolver;
_gameEvents = gameEvents;
_resources = resources;
_utility = utility;
_communicator = communicator;
_onRenderMaterialHook = Hook<OnRenderMaterialDelegate>.FromAddress(_humanVTable[62], OnRenderHumanMaterial);
_gameEvents.CharacterBaseCreated += OnCharacterBaseCreated; // The dependency on DrawObjectState shall ensure that this handler is registered after its one.
_communicator.CreatedCharacterBase.Subscribe(OnCharacterBaseCreated, CreatedCharacterBase.Priority.SkinFixer);
_gameEvents.CharacterBaseDestructor += OnCharacterBaseDestructor;
_onRenderMaterialHook.Enable();
}
~SkinFixer()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
_onRenderMaterialHook.Dispose();
_gameEvents.CharacterBaseCreated -= OnCharacterBaseCreated;
_communicator.CreatedCharacterBase.Unsubscribe(OnCharacterBaseCreated);
_gameEvents.CharacterBaseDestructor -= OnCharacterBaseDestructor;
foreach (var skinShpk in _skinShpks.Values)
skinShpk.Dispose();
@ -98,21 +88,21 @@ public unsafe class SkinFixer : IDisposable
public ulong GetAndResetSlowPathCallDelta()
=> Interlocked.Exchange(ref _slowPathCallDelta, 0);
private void OnCharacterBaseCreated(uint modelCharaId, nint customize, nint equipment, nint drawObject)
private void OnCharacterBaseCreated(nint gameObject, ModCollection collection, nint drawObject)
{
if (((CharacterBase*)drawObject)->GetModelType() != CharacterBase.ModelType.Human)
return;
Task.Run(delegate
Task.Run(() =>
{
var skinShpk = SafeResourceHandle.CreateInvalid();
try
{
var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
var data = collection.ToResolveData(gameObject);
if (data.Valid)
{
var loadedShpk = _resources.LoadResolvedResource(ResourceCategory.Shader, ResourceType.Shpk, SkinShpkPath.Path, data);
skinShpk = new SafeResourceHandle((ResourceHandle*)loadedShpk, incRef: false);
skinShpk = new SafeResourceHandle((ResourceHandle*)loadedShpk, false);
}
}
catch (Exception e)
@ -128,30 +118,31 @@ public unsafe class SkinFixer : IDisposable
Interlocked.Increment(ref _moddedSkinShpkCount);
}
else
{
skinShpk.Dispose();
}
}
});
}
private void OnCharacterBaseDestructor(nint characterBase)
{
if (_skinShpks.Remove(characterBase, out var skinShpk))
{
var handle = skinShpk.ResourceHandle;
skinShpk.Dispose();
if ((nint)handle != _utility.DefaultSkinShpkResource)
Interlocked.Decrement(ref _moddedSkinShpkCount);
}
if (!_skinShpks.Remove(characterBase, out var skinShpk))
return;
var handle = skinShpk.ResourceHandle;
skinShpk.Dispose();
if ((nint)handle != _utility.DefaultSkinShpkResource)
Interlocked.Decrement(ref _moddedSkinShpkCount);
}
private nint OnRenderHumanMaterial(nint human, OnRenderMaterialParams* param)
{
if (!_enabled || // Can be toggled on the debug tab.
_moddedSkinShpkCount == 0 || // If we don't have any on-screen instances of modded skin.shpk, we don't need the slow path at all.
!_skinShpks.TryGetValue(human, out var skinShpk))
// 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);
var material = param->Model->Materials[param->MaterialIndex];
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);
@ -164,6 +155,7 @@ public unsafe class SkinFixer : IDisposable
// - Swapping path is taken up to hundreds of times a frame.
// At the time of writing, the lock doesn't seem to have a noticeable impact in either framerate or CPU usage, but the swapping path shall still be avoided as much as possible.
lock (_lock)
{
try
{
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)skinShpk.ResourceHandle;
@ -173,5 +165,6 @@ public unsafe class SkinFixer : IDisposable
{
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)_utility.DefaultSkinShpkResource;
}
}
}
}