diff --git a/Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs b/Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs new file mode 100644 index 00000000..18afa949 --- /dev/null +++ b/Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs @@ -0,0 +1,131 @@ +using System; +using System.Threading; +using Dalamud.Game; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using Penumbra.GameData.Files; + +namespace Penumbra.Interop.MaterialPreview; + +public sealed unsafe class LiveColorSetPreviewer : LiveMaterialPreviewerBase +{ + public const int TextureWidth = 4; + public const int TextureHeight = MtrlFile.ColorSet.RowArray.NumRows; + public const int TextureLength = TextureWidth * TextureHeight * 4; + + private readonly Framework _framework; + + private readonly Texture** _colorSetTexture; + private readonly Texture* _originalColorSetTexture; + + private Half[] _colorSet; + private bool _updatePending; + + public Half[] ColorSet + => _colorSet; + + public LiveColorSetPreviewer(IObjectTable objects, Framework framework, MaterialInfo materialInfo) + : base(objects, materialInfo) + { + _framework = framework; + + var mtrlHandle = Material->MaterialResourceHandle; + if (mtrlHandle == null) + throw new InvalidOperationException("Material doesn't have a resource handle"); + + var colorSetTextures = ((Structs.CharacterBaseExt*)DrawObject)->ColorSetTextures; + if (colorSetTextures == null) + throw new InvalidOperationException("Draw object doesn't have color set textures"); + + _colorSetTexture = colorSetTextures + (MaterialInfo.ModelSlot * 4 + MaterialInfo.MaterialSlot); + + _originalColorSetTexture = *_colorSetTexture; + if (_originalColorSetTexture == null) + throw new InvalidOperationException("Material doesn't have a color set"); + + Structs.TextureUtility.IncRef(_originalColorSetTexture); + + _colorSet = new Half[TextureLength]; + _updatePending = true; + + framework.Update += OnFrameworkUpdate; + } + + protected override void Clear(bool disposing, bool reset) + { + _framework.Update -= OnFrameworkUpdate; + + base.Clear(disposing, reset); + + if (reset) + { + var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)_originalColorSetTexture); + if (oldTexture != null) + Structs.TextureUtility.DecRef(oldTexture); + } + else + { + Structs.TextureUtility.DecRef(_originalColorSetTexture); + } + } + + public void ScheduleUpdate() + { + _updatePending = true; + } + + private void OnFrameworkUpdate(Framework _) + { + if (!_updatePending) + return; + + _updatePending = false; + + if (!CheckValidity()) + return; + + var textureSize = stackalloc int[2]; + textureSize[0] = TextureWidth; + textureSize[1] = TextureHeight; + + var newTexture = Structs.TextureUtility.Create2D(Device.Instance(), textureSize, 1, 0x2460, 0x80000804, 7); + if (newTexture == null) + return; + + bool success; + lock (_colorSet) + { + fixed (Half* colorSet = _colorSet) + { + success = Structs.TextureUtility.InitializeContents(newTexture, colorSet); + } + } + + if (success) + { + var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)newTexture); + if (oldTexture != null) + Structs.TextureUtility.DecRef(oldTexture); + } + else + { + Structs.TextureUtility.DecRef(newTexture); + } + } + + protected override bool IsStillValid() + { + if (!base.IsStillValid()) + return false; + + var colorSetTextures = ((Structs.CharacterBaseExt*)DrawObject)->ColorSetTextures; + if (colorSetTextures == null) + return false; + + if (_colorSetTexture != colorSetTextures + (MaterialInfo.ModelSlot * 4 + MaterialInfo.MaterialSlot)) + return false; + + return true; + } +} diff --git a/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs b/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs new file mode 100644 index 00000000..1b280b20 --- /dev/null +++ b/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs @@ -0,0 +1,149 @@ +using System; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; + +namespace Penumbra.Interop.MaterialPreview; + +public sealed unsafe class LiveMaterialPreviewer : LiveMaterialPreviewerBase +{ + private readonly ShaderPackage* _shaderPackage; + + private readonly uint _originalShPkFlags; + private readonly float[] _originalMaterialParameter; + private readonly uint[] _originalSamplerFlags; + + public LiveMaterialPreviewer(IObjectTable objects, MaterialInfo materialInfo) + : base(objects, materialInfo) + { + var mtrlHandle = Material->MaterialResourceHandle; + if (mtrlHandle == null) + throw new InvalidOperationException("Material doesn't have a resource handle"); + + var shpkHandle = ((Structs.MtrlResource*)mtrlHandle)->ShpkResourceHandle; + if (shpkHandle == null) + throw new InvalidOperationException("Material doesn't have a ShPk resource handle"); + + _shaderPackage = shpkHandle->ShaderPackage; + if (_shaderPackage == null) + throw new InvalidOperationException("Material doesn't have a shader package"); + + var material = (Structs.Material*)Material; + + _originalShPkFlags = material->ShaderPackageFlags; + + if (material->MaterialParameter->TryGetBuffer(out var materialParameter)) + _originalMaterialParameter = materialParameter.ToArray(); + else + _originalMaterialParameter = Array.Empty(); + + _originalSamplerFlags = new uint[material->TextureCount]; + for (var i = 0; i < _originalSamplerFlags.Length; ++i) + _originalSamplerFlags[i] = material->Textures[i].SamplerFlags; + } + + protected override void Clear(bool disposing, bool reset) + { + base.Clear(disposing, reset); + + if (reset) + { + var material = (Structs.Material*)Material; + + material->ShaderPackageFlags = _originalShPkFlags; + + if (material->MaterialParameter->TryGetBuffer(out var materialParameter)) + _originalMaterialParameter.AsSpan().CopyTo(materialParameter); + + for (var i = 0; i < _originalSamplerFlags.Length; ++i) + material->Textures[i].SamplerFlags = _originalSamplerFlags[i]; + } + } + + public void SetShaderPackageFlags(uint shPkFlags) + { + if (!CheckValidity()) + return; + + ((Structs.Material*)Material)->ShaderPackageFlags = shPkFlags; + } + + public void SetMaterialParameter(uint parameterCrc, Index offset, Span value) + { + if (!CheckValidity()) + return; + + var constantBuffer = ((Structs.Material*)Material)->MaterialParameter; + if (constantBuffer == null) + return; + + if (!constantBuffer->TryGetBuffer(out var buffer)) + return; + + for (var i = 0; i < _shaderPackage->MaterialElementCount; ++i) + { + ref var parameter = ref _shaderPackage->MaterialElements[i]; + if (parameter.CRC == parameterCrc) + { + if ((parameter.Offset & 0x3) != 0 + || (parameter.Size & 0x3) != 0 + || (parameter.Offset + parameter.Size) >> 2 > buffer.Length) + return; + + value.TryCopyTo(buffer.Slice(parameter.Offset >> 2, parameter.Size >> 2)[offset..]); + return; + } + } + } + + public void SetSamplerFlags(uint samplerCrc, uint samplerFlags) + { + if (!CheckValidity()) + return; + + var id = 0u; + var found = false; + + var samplers = (Structs.ShaderPackageUtility.Sampler*)_shaderPackage->Samplers; + for (var i = 0; i < _shaderPackage->SamplerCount; ++i) + { + if (samplers[i].Crc == samplerCrc) + { + id = samplers[i].Id; + found = true; + break; + } + } + + if (!found) + return; + + var material = (Structs.Material*)Material; + for (var i = 0; i < material->TextureCount; ++i) + { + if (material->Textures[i].Id == id) + { + material->Textures[i].SamplerFlags = (samplerFlags & 0xFFFFFDFF) | 0x000001C0; + break; + } + } + } + + protected override bool IsStillValid() + { + if (!base.IsStillValid()) + return false; + + var mtrlHandle = Material->MaterialResourceHandle; + if (mtrlHandle == null) + return false; + + var shpkHandle = ((Structs.MtrlResource*)mtrlHandle)->ShpkResourceHandle; + if (shpkHandle == null) + return false; + + if (_shaderPackage != shpkHandle->ShaderPackage) + return false; + + return true; + } +} diff --git a/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs b/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs new file mode 100644 index 00000000..88369725 --- /dev/null +++ b/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs @@ -0,0 +1,70 @@ +using System; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; + +namespace Penumbra.Interop.MaterialPreview; + +public abstract unsafe class LiveMaterialPreviewerBase : IDisposable +{ + private readonly IObjectTable _objects; + + public readonly MaterialInfo MaterialInfo; + public readonly CharacterBase* DrawObject; + protected readonly Material* Material; + + protected bool Valid; + + public LiveMaterialPreviewerBase(IObjectTable objects, MaterialInfo materialInfo) + { + _objects = objects; + + MaterialInfo = materialInfo; + var gameObject = MaterialInfo.GetCharacter(objects); + if (gameObject == nint.Zero) + throw new InvalidOperationException("Cannot retrieve game object."); + + DrawObject = (CharacterBase*)MaterialInfo.GetDrawObject(gameObject); + if (DrawObject == null) + throw new InvalidOperationException("Cannot retrieve draw object."); + + Material = MaterialInfo.GetDrawObjectMaterial(DrawObject); + if (Material == null) + throw new InvalidOperationException("Cannot retrieve material."); + + Valid = true; + } + + public void Dispose() + { + if (Valid) + Clear(true, IsStillValid()); + } + + public bool CheckValidity() + { + if (Valid && !IsStillValid()) + Clear(false, false); + return Valid; + } + + protected virtual void Clear(bool disposing, bool reset) + { + Valid = false; + } + + protected virtual bool IsStillValid() + { + var gameObject = MaterialInfo.GetCharacter(_objects); + if (gameObject == nint.Zero) + return false; + + if ((nint)DrawObject != MaterialInfo.GetDrawObject(gameObject)) + return false; + + if (Material != MaterialInfo.GetDrawObjectMaterial(DrawObject)) + return false; + + return true; + } +} diff --git a/Penumbra/Interop/MaterialPreview/MaterialInfo.cs b/Penumbra/Interop/MaterialPreview/MaterialInfo.cs new file mode 100644 index 00000000..f1c9c10e --- /dev/null +++ b/Penumbra/Interop/MaterialPreview/MaterialInfo.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Penumbra.Interop.ResourceTree; +using Penumbra.String; + +namespace Penumbra.Interop.MaterialPreview; + +public enum DrawObjectType +{ + PlayerCharacter, + PlayerMainhand, + PlayerOffhand, + PlayerVfx, + MinionCharacter, + MinionUnk1, + MinionUnk2, + MinionUnk3, +}; + +public readonly record struct MaterialInfo(DrawObjectType Type, int ModelSlot, int MaterialSlot) +{ + public nint GetCharacter(IObjectTable objects) + => GetCharacter(Type, objects); + + public static nint GetCharacter(DrawObjectType type, IObjectTable objects) + => type switch + { + DrawObjectType.PlayerCharacter => objects.GetObjectAddress(0), + DrawObjectType.PlayerMainhand => objects.GetObjectAddress(0), + DrawObjectType.PlayerOffhand => objects.GetObjectAddress(0), + DrawObjectType.PlayerVfx => objects.GetObjectAddress(0), + DrawObjectType.MinionCharacter => objects.GetObjectAddress(1), + DrawObjectType.MinionUnk1 => objects.GetObjectAddress(1), + DrawObjectType.MinionUnk2 => objects.GetObjectAddress(1), + DrawObjectType.MinionUnk3 => objects.GetObjectAddress(1), + _ => nint.Zero, + }; + + public nint GetDrawObject(nint address) + => GetDrawObject(Type, address); + + public static nint GetDrawObject(DrawObjectType type, IObjectTable objects) + => GetDrawObject(type, GetCharacter(type, objects)); + + public static unsafe nint GetDrawObject(DrawObjectType type, nint address) + { + var gameObject = (Character*)address; + if (gameObject == null) + return nint.Zero; + + return type switch + { + DrawObjectType.PlayerCharacter => (nint)gameObject->GameObject.GetDrawObject(), + DrawObjectType.PlayerMainhand => *((nint*)&gameObject->DrawData.MainHand + 1), + DrawObjectType.PlayerOffhand => *((nint*)&gameObject->DrawData.OffHand + 1), + DrawObjectType.PlayerVfx => *((nint*)&gameObject->DrawData.UnkF0 + 1), + DrawObjectType.MinionCharacter => (nint)gameObject->GameObject.GetDrawObject(), + DrawObjectType.MinionUnk1 => *((nint*)&gameObject->DrawData.MainHand + 1), + DrawObjectType.MinionUnk2 => *((nint*)&gameObject->DrawData.OffHand + 1), + DrawObjectType.MinionUnk3 => *((nint*)&gameObject->DrawData.UnkF0 + 1), + _ => nint.Zero, + }; + } + + public unsafe Material* GetDrawObjectMaterial(CharacterBase* drawObject) + { + if (drawObject == null) + return null; + + if (ModelSlot < 0 || ModelSlot >= drawObject->SlotCount) + return null; + + var model = drawObject->Models[ModelSlot]; + if (model == null) + return null; + + if (MaterialSlot < 0 || MaterialSlot >= model->MaterialCount) + return null; + + return model->Materials[MaterialSlot]; + } + + public static unsafe List FindMaterials(IObjectTable objects, string materialPath) + { + var needle = ByteString.FromString(materialPath.Replace('/', '\\'), out var m, true) ? m : ByteString.Empty; + + var result = new List(Enum.GetValues().Length); + foreach (var type in Enum.GetValues()) + { + var drawObject = (CharacterBase*)GetDrawObject(type, objects); + if (drawObject == null) + continue; + + for (var i = 0; i < drawObject->SlotCount; ++i) + { + var model = drawObject->Models[i]; + if (model == null) + continue; + + for (var j = 0; j < model->MaterialCount; ++j) + { + var material = model->Materials[j]; + if (material == null) + continue; + + var mtrlHandle = material->MaterialResourceHandle; + var path = ResolveContext.GetResourceHandlePath((Structs.ResourceHandle*)mtrlHandle); + if (path == needle) + result.Add(new MaterialInfo(type, i, j)); + } + } + } + + return result; + } +} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.LivePreview.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.LivePreview.cs deleted file mode 100644 index d2ce8796..00000000 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.LivePreview.cs +++ /dev/null @@ -1,483 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using Dalamud.Game; -using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Penumbra.GameData.Files; -using Penumbra.Interop.ResourceTree; -using Penumbra.String; -using Structs = Penumbra.Interop.Structs; - -namespace Penumbra.UI.AdvancedWindow; - -public partial class ModEditWindow -{ - private static unsafe Character* FindLocalPlayer(IObjectTable objects) - { - var localPlayer = objects[0]; - if (localPlayer is not Dalamud.Game.ClientState.Objects.Types.Character) - return null; - - return (Character*)localPlayer.Address; - } - - private static unsafe Character* FindSubActor(Character* character, int subActorType) - { - if (character == null) - return null; - - switch (subActorType) - { - case -1: - return character; - case 0: - return character->Mount.MountObject; - case 1: - var companion = character->Companion.CompanionObject; - if (companion == null) - return null; - return &companion->Character; - case 2: - var ornament = character->Ornament.OrnamentObject; - if (ornament == null) - return null; - return &ornament->Character; - default: - return null; - } - } - - private static unsafe List<(int SubActorType, int ChildObjectIndex, int ModelSlot, int MaterialSlot)> FindMaterial(CharacterBase* drawObject, int subActorType, string materialPath) - { - static void CollectMaterials(List<(int, int, int, int)> result, int subActorType, int childObjectIndex, CharacterBase* drawObject, ByteString materialPath) - { - for (var i = 0; i < drawObject->SlotCount; ++i) - { - var model = drawObject->Models[i]; - if (model == null) - continue; - - for (var j = 0; j < model->MaterialCount; ++j) - { - var material = model->Materials[j]; - if (material == null) - continue; - - var mtrlHandle = material->MaterialResourceHandle; - var path = ResolveContext.GetResourceHandlePath((Structs.ResourceHandle*)mtrlHandle); - if (path == materialPath) - result.Add((subActorType, childObjectIndex, i, j)); - } - } - } - - var result = new List<(int, int, int, int)>(); - - if (drawObject == null) - return result; - - var path = ByteString.FromString(materialPath.Replace('/', '\\'), out var m, true) ? m : ByteString.Empty; - CollectMaterials(result, subActorType, -1, drawObject, path); - - var firstChildObject = (CharacterBase*)drawObject->DrawObject.Object.ChildObject; - if (firstChildObject != null) - { - var childObject = firstChildObject; - var childObjectIndex = 0; - do - { - CollectMaterials(result, subActorType, childObjectIndex, childObject, path); - - childObject = (CharacterBase*)childObject->DrawObject.Object.NextSiblingObject; - ++childObjectIndex; - } - while (childObject != null && childObject != firstChildObject); - } - - return result; - } - - private static unsafe CharacterBase* GetChildObject(CharacterBase* drawObject, int index) - { - if (drawObject == null) - return null; - - if (index >= 0) - { - drawObject = (CharacterBase*)drawObject->DrawObject.Object.ChildObject; - if (drawObject == null) - return null; - } - - var first = drawObject; - while (index-- > 0) - { - drawObject = (CharacterBase*)drawObject->DrawObject.Object.NextSiblingObject; - if (drawObject == null || drawObject == first) - return null; - } - - return drawObject; - } - - private static unsafe Material* GetDrawObjectMaterial(CharacterBase* drawObject, int modelSlot, int materialSlot) - { - if (drawObject == null) - return null; - - if (modelSlot < 0 || modelSlot >= drawObject->SlotCount) - return null; - - var model = drawObject->Models[modelSlot]; - if (model == null) - return null; - - if (materialSlot < 0 || materialSlot >= model->MaterialCount) - return null; - - return model->Materials[materialSlot]; - } - - private abstract unsafe class LiveMaterialPreviewerBase : IDisposable - { - private readonly IObjectTable _objects; - - protected readonly int SubActorType; - protected readonly int ChildObjectIndex; - protected readonly int ModelSlot; - protected readonly int MaterialSlot; - - public readonly CharacterBase* DrawObject; - protected readonly Material* Material; - - protected bool Valid; - - public LiveMaterialPreviewerBase(IObjectTable objects, int subActorType, int childObjectIndex, int modelSlot, int materialSlot) - { - _objects = objects; - - SubActorType = subActorType; - ChildObjectIndex = childObjectIndex; - ModelSlot = modelSlot; - MaterialSlot = materialSlot; - - var localPlayer = FindLocalPlayer(objects); - if (localPlayer == null) - throw new InvalidOperationException("Cannot retrieve local player object"); - - var subActor = FindSubActor(localPlayer, subActorType); - if (subActor == null) - throw new InvalidOperationException("Cannot retrieve sub-actor (mount, companion or ornament)"); - - DrawObject = GetChildObject((CharacterBase*)subActor->GameObject.GetDrawObject(), childObjectIndex); - if (DrawObject == null) - throw new InvalidOperationException("Cannot retrieve draw object"); - - Material = GetDrawObjectMaterial(DrawObject, modelSlot, materialSlot); - if (Material == null) - throw new InvalidOperationException("Cannot retrieve material"); - - Valid = true; - } - - ~LiveMaterialPreviewerBase() - { - if (Valid) - Dispose(false, IsStillValid()); - } - - public void Dispose() - { - if (Valid) - Dispose(true, IsStillValid()); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing, bool reset) - { - Valid = false; - } - - public bool CheckValidity() - { - if (Valid && !IsStillValid()) - Dispose(false, false); - - return Valid; - } - - protected virtual bool IsStillValid() - { - var localPlayer = FindLocalPlayer(_objects); - if (localPlayer == null) - return false; - - var subActor = FindSubActor(localPlayer, SubActorType); - if (subActor == null) - return false; - - if (DrawObject != GetChildObject((CharacterBase*)subActor->GameObject.GetDrawObject(), ChildObjectIndex)) - return false; - - if (Material != GetDrawObjectMaterial(DrawObject, ModelSlot, MaterialSlot)) - return false; - - return true; - } - } - - private sealed unsafe class LiveMaterialPreviewer : LiveMaterialPreviewerBase - { - private readonly ShaderPackage* _shaderPackage; - - private readonly uint _originalShPkFlags; - private readonly float[] _originalMaterialParameter; - private readonly uint[] _originalSamplerFlags; - - public LiveMaterialPreviewer(IObjectTable objects, int subActorType, int childObjectIndex, int modelSlot, int materialSlot) : base(objects, subActorType, childObjectIndex, modelSlot, materialSlot) - { - var mtrlHandle = Material->MaterialResourceHandle; - if (mtrlHandle == null) - throw new InvalidOperationException("Material doesn't have a resource handle"); - - var shpkHandle = ((Structs.MtrlResource*)mtrlHandle)->ShpkResourceHandle; - if (shpkHandle == null) - throw new InvalidOperationException("Material doesn't have a ShPk resource handle"); - - _shaderPackage = shpkHandle->ShaderPackage; - if (_shaderPackage == null) - throw new InvalidOperationException("Material doesn't have a shader package"); - - var material = (Structs.Material*)Material; - - _originalShPkFlags = material->ShaderPackageFlags; - - if (material->MaterialParameter->TryGetBuffer(out var materialParameter)) - _originalMaterialParameter = materialParameter.ToArray(); - else - _originalMaterialParameter = Array.Empty(); - - _originalSamplerFlags = new uint[material->TextureCount]; - for (var i = 0; i < _originalSamplerFlags.Length; ++i) - _originalSamplerFlags[i] = material->Textures[i].SamplerFlags; - } - - protected override void Dispose(bool disposing, bool reset) - { - base.Dispose(disposing, reset); - - if (reset) - { - var material = (Structs.Material*)Material; - - material->ShaderPackageFlags = _originalShPkFlags; - - if (material->MaterialParameter->TryGetBuffer(out var materialParameter)) - _originalMaterialParameter.AsSpan().CopyTo(materialParameter); - - for (var i = 0; i < _originalSamplerFlags.Length; ++i) - material->Textures[i].SamplerFlags = _originalSamplerFlags[i]; - } - } - - public void SetShaderPackageFlags(uint shPkFlags) - { - if (!CheckValidity()) - return; - - ((Structs.Material*)Material)->ShaderPackageFlags = shPkFlags; - } - - public void SetMaterialParameter(uint parameterCrc, Index offset, Span value) - { - if (!CheckValidity()) - return; - - var cbuffer = ((Structs.Material*)Material)->MaterialParameter; - if (cbuffer == null) - return; - - if (!cbuffer->TryGetBuffer(out var buffer)) - return; - - for (var i = 0; i < _shaderPackage->MaterialElementCount; ++i) - { - ref var parameter = ref _shaderPackage->MaterialElements[i]; - if (parameter.CRC == parameterCrc) - { - if ((parameter.Offset & 0x3) != 0 || (parameter.Size & 0x3) != 0 || (parameter.Offset + parameter.Size) >> 2 > buffer.Length) - return; - - value.TryCopyTo(buffer.Slice(parameter.Offset >> 2, parameter.Size >> 2)[offset..]); - return; - } - } - } - - public void SetSamplerFlags(uint samplerCrc, uint samplerFlags) - { - if (!CheckValidity()) - return; - - var id = 0u; - var found = false; - - var samplers = (Structs.ShaderPackageUtility.Sampler*)_shaderPackage->Samplers; - for (var i = 0; i < _shaderPackage->SamplerCount; ++i) - { - if (samplers[i].Crc == samplerCrc) - { - id = samplers[i].Id; - found = true; - break; - } - } - - if (!found) - return; - - var material = (Structs.Material*)Material; - for (var i = 0; i < material->TextureCount; ++i) - { - if (material->Textures[i].Id == id) - { - material->Textures[i].SamplerFlags = (samplerFlags & 0xFFFFFDFF) | 0x000001C0; - break; - } - } - } - - protected override bool IsStillValid() - { - if (!base.IsStillValid()) - return false; - - var mtrlHandle = Material->MaterialResourceHandle; - if (mtrlHandle == null) - return false; - - var shpkHandle = ((Structs.MtrlResource*)mtrlHandle)->ShpkResourceHandle; - if (shpkHandle == null) - return false; - - if (_shaderPackage != shpkHandle->ShaderPackage) - return false; - - return true; - } - } - - private sealed unsafe class LiveColorSetPreviewer : LiveMaterialPreviewerBase - { - public const int TextureWidth = 4; - public const int TextureHeight = MtrlFile.ColorSet.RowArray.NumRows; - public const int TextureLength = TextureWidth * TextureHeight * 4; - - private readonly Framework _framework; - - private readonly Texture** _colorSetTexture; - private readonly Texture* _originalColorSetTexture; - - private Half[] _colorSet; - private bool _updatePending; - - public Half[] ColorSet => _colorSet; - - public LiveColorSetPreviewer(IObjectTable objects, Framework framework, int subActorType, int childObjectIndex, int modelSlot, int materialSlot) : base(objects, subActorType, childObjectIndex, modelSlot, materialSlot) - { - _framework = framework; - - var mtrlHandle = Material->MaterialResourceHandle; - if (mtrlHandle == null) - throw new InvalidOperationException("Material doesn't have a resource handle"); - - var colorSetTextures = ((Structs.CharacterBaseExt*)DrawObject)->ColorSetTextures; - if (colorSetTextures == null) - throw new InvalidOperationException("Draw object doesn't have color set textures"); - - _colorSetTexture = colorSetTextures + (modelSlot * 4 + materialSlot); - - _originalColorSetTexture = *_colorSetTexture; - if (_originalColorSetTexture == null) - throw new InvalidOperationException("Material doesn't have a color set"); - Structs.TextureUtility.IncRef(_originalColorSetTexture); - - _colorSet = new Half[TextureLength]; - _updatePending = true; - - framework.Update += OnFrameworkUpdate; - } - - protected override void Dispose(bool disposing, bool reset) - { - _framework.Update -= OnFrameworkUpdate; - - base.Dispose(disposing, reset); - - if (reset) - { - var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)_originalColorSetTexture); - if (oldTexture != null) - Structs.TextureUtility.DecRef(oldTexture); - } - else - Structs.TextureUtility.DecRef(_originalColorSetTexture); - } - - public void ScheduleUpdate() - { - _updatePending = true; - } - - private void OnFrameworkUpdate(Framework _) - { - if (!_updatePending) - return; - _updatePending = false; - - if (!CheckValidity()) - return; - - var textureSize = stackalloc int[2]; - textureSize[0] = TextureWidth; - textureSize[1] = TextureHeight; - - var newTexture = Structs.TextureUtility.Create2D(Device.Instance(), textureSize, 1, 0x2460, 0x80000804, 7); - if (newTexture == null) - return; - - bool success; - lock (_colorSet) - fixed (Half* colorSet = _colorSet) - success = Structs.TextureUtility.InitializeContents(newTexture, colorSet); - - if (success) - { - var oldTexture = (Texture*)Interlocked.Exchange(ref *(nint*)_colorSetTexture, (nint)newTexture); - if (oldTexture != null) - Structs.TextureUtility.DecRef(oldTexture); - } - else - Structs.TextureUtility.DecRef(newTexture); - } - - protected override bool IsStillValid() - { - if (!base.IsStillValid()) - return false; - - var colorSetTextures = ((Structs.CharacterBaseExt*)DrawObject)->ColorSetTextures; - if (colorSetTextures == null) - return false; - - if (_colorSetTexture != colorSetTextures + (ModelSlot * 4 + MaterialSlot)) - return false; - - return true; - } - } -} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs index 12f7acd7..6bb8b8c8 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs @@ -16,6 +16,7 @@ using Penumbra.GameData; using Penumbra.GameData.Data; using Penumbra.GameData.Files; using Penumbra.GameData.Structs; +using Penumbra.Interop.MaterialPreview; using Penumbra.String; using Penumbra.String.Classes; using static Penumbra.GameData.Files.ShpkFile; @@ -460,46 +461,19 @@ public partial class ModEditWindow { UnbindFromMaterialInstances(); - var localPlayer = LocalPlayer(_edit._dalamud.Objects); - if (null == localPlayer) - return; - - var drawObject = (CharacterBase*)localPlayer->GameObject.GetDrawObject(); - if (null == drawObject) - return; - - var instances = FindMaterial(drawObject, -1, FilePath); - - var drawObjects = stackalloc CharacterBase*[4]; - drawObjects[0] = drawObject; - drawObjects[1] = *((CharacterBase**)&localPlayer->DrawData.MainHand + 1); - drawObjects[2] = *((CharacterBase**)&localPlayer->DrawData.OffHand + 1); - drawObjects[3] = *((CharacterBase**)&localPlayer->DrawData.UnkF0 + 1); - for (var i = 0; i < 3; ++i) - { - var subActor = FindSubActor(localPlayer, i); - if (null == subActor) - continue; - - var subDrawObject = (CharacterBase*)subActor->GameObject.GetDrawObject(); - if (null == subDrawObject) - continue; - - instances.AddRange(FindMaterial(subDrawObject, i, FilePath)); - drawObjects[i + 1] = subDrawObject; - } + var instances = MaterialInfo.FindMaterials(_edit._dalamud.Objects, FilePath); var foundMaterials = new HashSet(); - foreach (var (subActorType, childObjectIndex, modelSlot, materialSlot) in instances) + foreach (var materialInfo in instances) { - var material = GetDrawObjectMaterial(drawObjects[subActorType + 1], modelSlot, materialSlot); + var drawObject = (CharacterBase*)MaterialInfo.GetDrawObject(materialInfo.Type, _edit._dalamud.Objects); + var material = materialInfo.GetDrawObjectMaterial(drawObject); if (foundMaterials.Contains((nint)material)) continue; try { - MaterialPreviewers.Add(new LiveMaterialPreviewer(_edit._dalamud.Objects, subActorType, childObjectIndex, modelSlot, - materialSlot)); + MaterialPreviewers.Add(new LiveMaterialPreviewer(_edit._dalamud.Objects, materialInfo)); foundMaterials.Add((nint)material); } catch (InvalidOperationException) @@ -515,12 +489,11 @@ public partial class ModEditWindow if (!colorSet.HasValue) return; - foreach (var (subActorType, childObjectIndex, modelSlot, materialSlot) in instances) + foreach (var materialInfo in instances) { try { - ColorSetPreviewers.Add(new LiveColorSetPreviewer(_edit._dalamud.Objects, _edit._dalamud.Framework, subActorType, - childObjectIndex, modelSlot, materialSlot)); + ColorSetPreviewers.Add(new LiveColorSetPreviewer(_edit._dalamud.Objects, _edit._dalamud.Framework, materialInfo)); } catch (InvalidOperationException) {