-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add characterglass.shpk to the skin.shpk fixer
- Loading branch information
1 parent
da423b7
commit 29c93f4
Showing
9 changed files
with
290 additions
and
155 deletions.
There are no files selected for viewing
Submodule Penumbra.GameData
updated
6 files
+31 −7 | Actors/ActorIdentifier.cs | |
+91 −9 | Actors/ActorIdentifierFactory.cs | |
+1 −0 | DataContainers/DictJobGroup.cs | |
+11 −6 | Gui/Debug/DictJobGroupDrawer.cs | |
+6 −0 | Signatures.cs | |
+1 −1 | Structs/JobGroup.cs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using Dalamud.Plugin.Services; | ||
using Dalamud.Utility.Signatures; | ||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; | ||
using Penumbra.GameData; | ||
|
||
namespace Penumbra.Interop.Services; | ||
|
||
// TODO ClientStructs-ify (https://github.com/aers/FFXIVClientStructs/pull/817) | ||
public unsafe class ModelRenderer : IDisposable | ||
{ | ||
// Will be Manager.Instance()->ModelRenderer.CharacterGlassShaderPackage in CS | ||
private const nint ModelRendererOffset = 0x13660; | ||
private const nint CharacterGlassShaderPackageOffset = 0xD0; | ||
|
||
/// <summary> A static pointer to the Render::Manager address. </summary> | ||
[Signature(Sigs.RenderManager, ScanType = ScanType.StaticAddress)] | ||
private readonly nint* _renderManagerAddress = null; | ||
|
||
public bool Ready { get; private set; } | ||
|
||
public ShaderPackageResourceHandle** CharacterGlassShaderPackage | ||
=> *_renderManagerAddress == 0 | ||
? null | ||
: (ShaderPackageResourceHandle**)(*_renderManagerAddress + ModelRendererOffset + CharacterGlassShaderPackageOffset).ToPointer(); | ||
|
||
public ShaderPackageResourceHandle* DefaultCharacterGlassShaderPackage { get; private set; } | ||
|
||
private readonly IFramework _framework; | ||
|
||
public ModelRenderer(IFramework framework, IGameInteropProvider interop) | ||
{ | ||
interop.InitializeFromAttributes(this); | ||
_framework = framework; | ||
LoadDefaultResources(null!); | ||
if (!Ready) | ||
_framework.Update += LoadDefaultResources; | ||
} | ||
|
||
/// <summary> We store the default data of the resources so we can always restore them. </summary> | ||
private void LoadDefaultResources(object _) | ||
{ | ||
if (*_renderManagerAddress == 0) | ||
return; | ||
|
||
var anyMissing = false; | ||
|
||
if (DefaultCharacterGlassShaderPackage == null) | ||
{ | ||
DefaultCharacterGlassShaderPackage = *CharacterGlassShaderPackage; | ||
anyMissing |= DefaultCharacterGlassShaderPackage == null; | ||
} | ||
|
||
if (anyMissing) | ||
return; | ||
|
||
Ready = true; | ||
_framework.Update -= LoadDefaultResources; | ||
} | ||
|
||
/// <summary> Return all relevant resources to the default resource. </summary> | ||
public void ResetAll() | ||
{ | ||
if (!Ready) | ||
return; | ||
|
||
*CharacterGlassShaderPackage = DefaultCharacterGlassShaderPackage; | ||
} | ||
|
||
public void Dispose() | ||
=> ResetAll(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
using Dalamud.Hooking; | ||
using Dalamud.Plugin.Services; | ||
using Dalamud.Utility.Signatures; | ||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render; | ||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; | ||
using OtterGui.Classes; | ||
using Penumbra.Communication; | ||
using Penumbra.GameData; | ||
using Penumbra.Interop.Hooks.Resources; | ||
using Penumbra.Services; | ||
|
||
namespace Penumbra.Interop.Services; | ||
|
||
public sealed unsafe class ShaderReplacementFixer : IDisposable | ||
{ | ||
public static ReadOnlySpan<byte> SkinShpkName | ||
=> "skin.shpk"u8; | ||
|
||
public static ReadOnlySpan<byte> CharacterGlassShpkName | ||
=> "characterglass.shpk"u8; | ||
|
||
[Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] | ||
private readonly nint* _humanVTable = null!; | ||
|
||
private delegate nint CharacterBaseOnRenderMaterialDelegate(nint drawObject, OnRenderMaterialParams* param); | ||
private delegate nint ModelRendererOnRenderMaterialDelegate(nint modelRenderer, nint outFlags, nint param, Material* material, uint materialIndex); | ||
|
||
[StructLayout(LayoutKind.Explicit)] | ||
private struct OnRenderMaterialParams | ||
{ | ||
[FieldOffset(0x0)] | ||
public Model* Model; | ||
|
||
[FieldOffset(0x8)] | ||
public uint MaterialIndex; | ||
} | ||
|
||
private readonly Hook<CharacterBaseOnRenderMaterialDelegate> _humanOnRenderMaterialHook; | ||
|
||
[Signature(Sigs.ModelRendererOnRenderMaterial, DetourName = nameof(ModelRendererOnRenderMaterialDetour))] | ||
private readonly Hook<ModelRendererOnRenderMaterialDelegate> _modelRendererOnRenderMaterialHook = null!; | ||
|
||
private readonly ResourceHandleDestructor _resourceHandleDestructor; | ||
private readonly CommunicatorService _communicator; | ||
private readonly CharacterUtility _utility; | ||
private readonly ModelRenderer _modelRenderer; | ||
|
||
// MaterialResourceHandle set | ||
private readonly ConcurrentSet<nint> _moddedSkinShpkMaterials = new(); | ||
private readonly ConcurrentSet<nint> _moddedCharacterGlassShpkMaterials = new(); | ||
|
||
private readonly object _skinLock = new(); | ||
private readonly object _characterGlassLock = new(); | ||
|
||
// ConcurrentDictionary.Count uses a lock in its current implementation. | ||
private int _moddedSkinShpkCount; | ||
private int _moddedCharacterGlassShpkCount; | ||
private ulong _skinSlowPathCallDelta; | ||
private ulong _characterGlassSlowPathCallDelta; | ||
|
||
public bool Enabled { get; internal set; } = true; | ||
|
||
public int ModdedSkinShpkCount | ||
=> _moddedSkinShpkCount; | ||
|
||
public int ModdedCharacterGlassShpkCount | ||
=> _moddedCharacterGlassShpkCount; | ||
|
||
public ShaderReplacementFixer(ResourceHandleDestructor resourceHandleDestructor, CharacterUtility utility, ModelRenderer modelRenderer, | ||
CommunicatorService communicator, IGameInteropProvider interop) | ||
{ | ||
interop.InitializeFromAttributes(this); | ||
_resourceHandleDestructor = resourceHandleDestructor; | ||
_utility = utility; | ||
_modelRenderer = modelRenderer; | ||
_communicator = communicator; | ||
_humanOnRenderMaterialHook = interop.HookFromAddress<CharacterBaseOnRenderMaterialDelegate>(_humanVTable[62], OnRenderHumanMaterial); | ||
_communicator.MtrlShpkLoaded.Subscribe(OnMtrlShpkLoaded, MtrlShpkLoaded.Priority.ShaderReplacementFixer); | ||
_resourceHandleDestructor.Subscribe(OnResourceHandleDestructor, ResourceHandleDestructor.Priority.ShaderReplacementFixer); | ||
_humanOnRenderMaterialHook.Enable(); | ||
_modelRendererOnRenderMaterialHook.Enable(); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_modelRendererOnRenderMaterialHook.Dispose(); | ||
_humanOnRenderMaterialHook.Dispose(); | ||
_communicator.MtrlShpkLoaded.Unsubscribe(OnMtrlShpkLoaded); | ||
_resourceHandleDestructor.Unsubscribe(OnResourceHandleDestructor); | ||
_moddedCharacterGlassShpkMaterials.Clear(); | ||
_moddedSkinShpkMaterials.Clear(); | ||
_moddedCharacterGlassShpkCount = 0; | ||
_moddedSkinShpkCount = 0; | ||
} | ||
|
||
public (ulong Skin, ulong CharacterGlass) GetAndResetSlowPathCallDeltas() | ||
=> (Interlocked.Exchange(ref _skinSlowPathCallDelta, 0), Interlocked.Exchange(ref _characterGlassSlowPathCallDelta, 0)); | ||
|
||
private static bool IsMaterialWithShpk(MaterialResourceHandle* mtrlResource, ReadOnlySpan<byte> shpkName) | ||
{ | ||
if (mtrlResource == null) | ||
return false; | ||
|
||
return shpkName.SequenceEqual(mtrlResource->ShpkNameSpan); | ||
} | ||
|
||
private void OnMtrlShpkLoaded(nint mtrlResourceHandle, nint gameObject) | ||
{ | ||
var mtrl = (MaterialResourceHandle*)mtrlResourceHandle; | ||
var shpk = mtrl->ShaderPackageResourceHandle; | ||
if (shpk == null) | ||
return; | ||
|
||
var shpkName = mtrl->ShpkNameSpan; | ||
|
||
if (SkinShpkName.SequenceEqual(shpkName) && (nint)shpk != _utility.DefaultSkinShpkResource) | ||
{ | ||
if (_moddedSkinShpkMaterials.TryAdd(mtrlResourceHandle)) | ||
Interlocked.Increment(ref _moddedSkinShpkCount); | ||
} | ||
|
||
if (CharacterGlassShpkName.SequenceEqual(shpkName) && shpk != _modelRenderer.DefaultCharacterGlassShaderPackage) | ||
{ | ||
if (_moddedCharacterGlassShpkMaterials.TryAdd(mtrlResourceHandle)) | ||
Interlocked.Increment(ref _moddedCharacterGlassShpkCount); | ||
} | ||
} | ||
|
||
private void OnResourceHandleDestructor(Structs.ResourceHandle* handle) | ||
{ | ||
if (_moddedSkinShpkMaterials.TryRemove((nint)handle)) | ||
Interlocked.Decrement(ref _moddedSkinShpkCount); | ||
|
||
if (_moddedCharacterGlassShpkMaterials.TryRemove((nint)handle)) | ||
Interlocked.Decrement(ref _moddedCharacterGlassShpkCount); | ||
} | ||
|
||
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) | ||
return _humanOnRenderMaterialHook.Original(human, param); | ||
|
||
var material = param->Model->Materials[param->MaterialIndex]; | ||
var mtrlResource = material->MaterialResourceHandle; | ||
if (!IsMaterialWithShpk(mtrlResource, SkinShpkName)) | ||
return _humanOnRenderMaterialHook.Original(human, param); | ||
|
||
Interlocked.Increment(ref _skinSlowPathCallDelta); | ||
|
||
// Performance considerations: | ||
// - This function is called from several threads simultaneously, hence the need for synchronization in the swapping path ; | ||
// - Function is called each frame for each material on screen, after culling, i. e. up to thousands of times a frame in crowded areas ; | ||
// - 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 (_skinLock) | ||
{ | ||
try | ||
{ | ||
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)mtrlResource->ShaderPackageResourceHandle; | ||
return _humanOnRenderMaterialHook.Original(human, param); | ||
} | ||
finally | ||
{ | ||
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)_utility.DefaultSkinShpkResource; | ||
} | ||
} | ||
} | ||
|
||
private nint ModelRendererOnRenderMaterialDetour(nint modelRenderer, nint outFlags, nint param, Material* material, uint materialIndex) | ||
{ | ||
|
||
// If we don't have any on-screen instances of modded characterglass.shpk, we don't need the slow path at all. | ||
if (!Enabled || _moddedCharacterGlassShpkCount == 0) | ||
return _modelRendererOnRenderMaterialHook.Original(modelRenderer, outFlags, param, material, materialIndex); | ||
|
||
var mtrlResource = material->MaterialResourceHandle; | ||
if (!IsMaterialWithShpk(mtrlResource, CharacterGlassShpkName)) | ||
return _modelRendererOnRenderMaterialHook.Original(modelRenderer, outFlags, param, material, materialIndex); | ||
|
||
Interlocked.Increment(ref _characterGlassSlowPathCallDelta); | ||
|
||
// Same performance considerations as above. | ||
lock (_characterGlassLock) | ||
{ | ||
try | ||
{ | ||
*_modelRenderer.CharacterGlassShaderPackage = mtrlResource->ShaderPackageResourceHandle; | ||
return _modelRendererOnRenderMaterialHook.Original(modelRenderer, outFlags, param, material, materialIndex); | ||
} | ||
finally | ||
{ | ||
*_modelRenderer.CharacterGlassShaderPackage = _modelRenderer.DefaultCharacterGlassShaderPackage; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.