mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Run auto redraw on framework, add some locks, handle material value application differently for ApplyAll.
This commit is contained in:
parent
e5f62d3ea9
commit
d8ce81cdc4
10 changed files with 287 additions and 224 deletions
|
|
@ -20,13 +20,14 @@ namespace Glamourer.Gui.Materials;
|
|||
public sealed unsafe class AdvancedDyePopup(
|
||||
Configuration config,
|
||||
StateManager stateManager,
|
||||
LiveColorTablePreviewer preview) : IService
|
||||
LiveColorTablePreviewer preview,
|
||||
DirectXService directX) : IService
|
||||
{
|
||||
private MaterialValueIndex? _drawIndex;
|
||||
private ActorState _state = null!;
|
||||
private Actor _actor;
|
||||
private byte _selectedMaterial = byte.MaxValue;
|
||||
private bool _anyChanged = false;
|
||||
private bool _anyChanged;
|
||||
|
||||
private bool ShouldBeDrawn()
|
||||
{
|
||||
|
|
@ -94,7 +95,7 @@ public sealed unsafe class AdvancedDyePopup(
|
|||
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
|
||||
{
|
||||
var index = _drawIndex!.Value with { MaterialIndex = i };
|
||||
var available = index.TryGetTexture(textures, out var texture) && index.TryGetColorTable(texture, out var table);
|
||||
var available = index.TryGetTexture(textures, out var texture) && directX.TryGetColorTable(*texture, out var table);
|
||||
|
||||
if (index == preview.LastValueIndex with { RowIndex = 0 })
|
||||
table = preview.LastOriginalColorTable;
|
||||
|
|
@ -179,7 +180,7 @@ public sealed unsafe class AdvancedDyePopup(
|
|||
}
|
||||
}
|
||||
|
||||
public unsafe void Draw(Actor actor, ActorState state)
|
||||
public void Draw(Actor actor, ActorState state)
|
||||
{
|
||||
_actor = actor;
|
||||
_state = state;
|
||||
|
|
@ -236,20 +237,20 @@ public sealed unsafe class AdvancedDyePopup(
|
|||
? _state.ModelData.Weapon(slot)
|
||||
: _state.ModelData.Armor(slot).ToWeapon(0);
|
||||
var value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual);
|
||||
stateManager.ChangeMaterialValue(_state!, materialIndex with { RowIndex = (byte)idx }, value, ApplySettings.Manual);
|
||||
stateManager.ChangeMaterialValue(_state, materialIndex with { RowIndex = (byte)idx }, value, ApplySettings.Manual);
|
||||
}
|
||||
|
||||
ImGui.SameLine(0, spacing);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged,
|
||||
true))
|
||||
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||
stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = (byte)i }, ApplySettings.Game);
|
||||
stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game);
|
||||
}
|
||||
|
||||
private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex index, in MtrlFile.ColorTable table)
|
||||
{
|
||||
using var id = ImRaii.PushId(index.RowIndex);
|
||||
var changed = _state!.Materials.TryGetValue(index, out var value);
|
||||
var changed = _state.Materials.TryGetValue(index, out var value);
|
||||
if (!changed)
|
||||
{
|
||||
var internalRow = new ColorRow(row);
|
||||
|
|
@ -314,7 +315,7 @@ public sealed unsafe class AdvancedDyePopup(
|
|||
stateManager.ResetMaterialValue(_state, index, ApplySettings.Game);
|
||||
|
||||
if (applied)
|
||||
stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual);
|
||||
stateManager.ChangeMaterialValue(_state, index, value, ApplySettings.Manual);
|
||||
}
|
||||
|
||||
private LabelStruct _label = new();
|
||||
|
|
|
|||
187
Glamourer/Interop/Material/DirectXService.cs
Normal file
187
Glamourer/Interop/Material/DirectXService.cs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using Lumina.Data.Files;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.String.Functions;
|
||||
using SharpGen.Runtime;
|
||||
using Vortice.Direct3D11;
|
||||
using Vortice.DXGI;
|
||||
using static Penumbra.GameData.Files.MtrlFile;
|
||||
using MapFlags = Vortice.Direct3D11.MapFlags;
|
||||
using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture;
|
||||
|
||||
namespace Glamourer.Interop.Material;
|
||||
|
||||
public unsafe class DirectXService(IFramework framework) : IService
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
|
||||
private readonly ConcurrentDictionary<nint, (DateTime Update, ColorTable Table)> _textures = [];
|
||||
|
||||
/// <summary> Generate a color table the way the game does inside the original texture, and release the original. </summary>
|
||||
/// <param name="original"> The original texture that will be replaced with a new one. </param>
|
||||
/// <param name="colorTable"> The input color table. </param>
|
||||
/// <returns> Success or failure. </returns>
|
||||
public bool ReplaceColorTable(Texture** original, in ColorTable colorTable)
|
||||
{
|
||||
if (original == null)
|
||||
return false;
|
||||
|
||||
var textureSize = stackalloc int[2];
|
||||
textureSize[0] = MaterialService.TextureWidth;
|
||||
textureSize[1] = MaterialService.TextureHeight;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
using var texture = new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1,
|
||||
(uint)TexFile.TextureFormat.R16G16B16A16F,
|
||||
(uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7), false);
|
||||
if (texture.IsInvalid)
|
||||
return false;
|
||||
|
||||
fixed (ColorTable* ptr = &colorTable)
|
||||
{
|
||||
if (!texture.Texture->InitializeContents(ptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
Glamourer.Log.Verbose($"[{Thread.CurrentThread.ManagedThreadId}] Replaced texture {(ulong)*original:X} with new ColorTable.");
|
||||
texture.Exchange(ref *(nint*)original);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetColorTable(Texture* texture, out ColorTable table)
|
||||
{
|
||||
if (_textures.TryGetValue((nint)texture, out var p) && framework.LastUpdateUTC == p.Update)
|
||||
{
|
||||
table = p.Table;
|
||||
return true;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!TextureColorTable(texture, out table))
|
||||
return false;
|
||||
}
|
||||
|
||||
_textures[(nint)texture] = (framework.LastUpdateUTC, table);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Try to turn a color table GPU-loaded texture (R16G16B16A16Float, 4 Width, 16 Height) into an actual color table. </summary>
|
||||
/// <param name="texture"> A pointer to the internal texture struct containing the GPU handle. </param>
|
||||
/// <param name="table"> The returned color table. </param>
|
||||
/// <returns> Whether the table could be fetched. </returns>
|
||||
private static bool TextureColorTable(Texture* texture, out ColorTable table)
|
||||
{
|
||||
if (texture == null)
|
||||
{
|
||||
table = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create direct x resource and ensure that it is kept alive.
|
||||
using var tex = new ID3D11Texture2D1((nint)texture->D3D11Texture2D);
|
||||
tex.AddRef();
|
||||
|
||||
table = GetResourceData(tex, CreateStagedClone, GetTextureData);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Create a staging clone of the existing texture handle for stability reasons. </summary>
|
||||
private static ID3D11Texture2D1 CreateStagedClone(ID3D11Texture2D1 resource)
|
||||
{
|
||||
var desc = resource.Description1 with
|
||||
{
|
||||
Usage = ResourceUsage.Staging,
|
||||
BindFlags = 0,
|
||||
CPUAccessFlags = CpuAccessFlags.Read,
|
||||
MiscFlags = 0,
|
||||
};
|
||||
|
||||
var ret = resource.Device.As<ID3D11Device3>().CreateTexture2D1(desc);
|
||||
Glamourer.Log.Excessive(
|
||||
$"[{Thread.CurrentThread.ManagedThreadId}] Cloning resource {resource.NativePointer:X} to {ret.NativePointer:X}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Turn a mapped texture into a color table. </summary>
|
||||
private static ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map)
|
||||
{
|
||||
var desc = resource.Description1;
|
||||
|
||||
if (desc.Format is not Format.R16G16B16A16_Float
|
||||
|| desc.Width != MaterialService.TextureWidth
|
||||
|| desc.Height != MaterialService.TextureHeight
|
||||
|| map.DepthPitch != map.RowPitch * desc.Height)
|
||||
throw new InvalidDataException("The texture was not a valid color table texture.");
|
||||
|
||||
return ReadTexture(map.DataPointer, map.DepthPitch, desc.Height, map.RowPitch);
|
||||
}
|
||||
|
||||
/// <summary> Transform the GPU data into the color table. </summary>
|
||||
/// <param name="data"> The pointer to the raw texture data. </param>
|
||||
/// <param name="length"> The size of the raw texture data. </param>
|
||||
/// <param name="height"> The height of the texture. (Needs to be 16).</param>
|
||||
/// <param name="pitch"> The stride in the texture data. </param>
|
||||
/// <returns></returns>
|
||||
private static ColorTable ReadTexture(nint data, int length, int height, int pitch)
|
||||
{
|
||||
// Check that the data has sufficient dimension and size.
|
||||
var expectedSize = sizeof(Half) * MaterialService.TextureWidth * height * 4;
|
||||
if (length < expectedSize || sizeof(ColorTable) != expectedSize || height != MaterialService.TextureHeight)
|
||||
return default;
|
||||
|
||||
var ret = new ColorTable();
|
||||
var target = (byte*)&ret;
|
||||
// If the stride is the same as in the table, just copy.
|
||||
if (pitch == MaterialService.TextureWidth)
|
||||
MemoryUtility.MemCpyUnchecked(target, (void*)data, length);
|
||||
// Otherwise, adapt the stride.
|
||||
else
|
||||
|
||||
for (var y = 0; y < height; ++y)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(target + y * MaterialService.TextureWidth * sizeof(Half) * 4, (byte*)data + y * pitch,
|
||||
MaterialService.TextureWidth * sizeof(Half) * 4);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Get resources of a texture. </summary>
|
||||
private static TRet GetResourceData<T, TRet>(T res, Func<T, T> cloneResource, Func<T, MappedSubresource, TRet> getData)
|
||||
where T : ID3D11Resource
|
||||
{
|
||||
using var stagingRes = cloneResource(res);
|
||||
|
||||
res.Device.ImmediateContext.CopyResource(stagingRes, res);
|
||||
Glamourer.Log.Excessive(
|
||||
$"[{Thread.CurrentThread.ManagedThreadId}] Copied resource data {res.NativePointer:X} to {stagingRes.NativePointer:X}");
|
||||
stagingRes.Device.ImmediateContext.Map(stagingRes, 0, MapMode.Read, MapFlags.None, out var mapInfo).CheckError();
|
||||
Glamourer.Log.Excessive(
|
||||
$"[{Thread.CurrentThread.ManagedThreadId}] Mapped resource data for {stagingRes.NativePointer:X} to {mapInfo.DataPointer:X}");
|
||||
|
||||
try
|
||||
{
|
||||
return getData(stagingRes, mapInfo);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Glamourer.Log.Excessive($"[{Thread.CurrentThread.ManagedThreadId}] Obtained resource data.");
|
||||
stagingRes.Device.ImmediateContext.Unmap(stagingRes, 0);
|
||||
Glamourer.Log.Excessive($"[{Thread.CurrentThread.ManagedThreadId}] Unmapped resource data for {stagingRes.NativePointer:X}");
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Result WasStillDrawing = new(0x887A000A);
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.String.Functions;
|
||||
using SharpGen.Runtime;
|
||||
using Vortice.Direct3D11;
|
||||
using Vortice.DXGI;
|
||||
using MapFlags = Vortice.Direct3D11.MapFlags;
|
||||
|
||||
namespace Glamourer.Interop.Material;
|
||||
|
||||
public static unsafe class DirectXTextureHelper
|
||||
{
|
||||
/// <summary> Try to turn a color table GPU-loaded texture (R16G16B16A16Float, 4 Width, 16 Height) into an actual color table. </summary>
|
||||
/// <param name="texture"> A pointer to the internal texture struct containing the GPU handle. </param>
|
||||
/// <param name="table"> The returned color table. </param>
|
||||
/// <returns> Whether the table could be fetched. </returns>
|
||||
public static bool TryGetColorTable(Texture* texture, out MtrlFile.ColorTable table)
|
||||
{
|
||||
if (texture == null)
|
||||
{
|
||||
table = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create direct x resource and ensure that it is kept alive.
|
||||
using var tex = new ID3D11Texture2D1((nint)texture->D3D11Texture2D);
|
||||
tex.AddRef();
|
||||
|
||||
table = GetResourceData(tex, CreateStagedClone, GetTextureData);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Create a staging clone of the existing texture handle for stability reasons. </summary>
|
||||
private static ID3D11Texture2D1 CreateStagedClone(ID3D11Texture2D1 resource)
|
||||
{
|
||||
var desc = resource.Description1 with
|
||||
{
|
||||
Usage = ResourceUsage.Staging,
|
||||
BindFlags = 0,
|
||||
CPUAccessFlags = CpuAccessFlags.Read,
|
||||
MiscFlags = 0,
|
||||
};
|
||||
|
||||
return resource.Device.As<ID3D11Device3>().CreateTexture2D1(desc);
|
||||
}
|
||||
|
||||
/// <summary> Turn a mapped texture into a color table. </summary>
|
||||
private static MtrlFile.ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map)
|
||||
{
|
||||
var desc = resource.Description1;
|
||||
|
||||
if (desc.Format is not Format.R16G16B16A16_Float
|
||||
|| desc.Width != MaterialService.TextureWidth
|
||||
|| desc.Height != MaterialService.TextureHeight
|
||||
|| map.DepthPitch != map.RowPitch * desc.Height)
|
||||
throw new InvalidDataException("The texture was not a valid color table texture.");
|
||||
|
||||
return ReadTexture(map.DataPointer, map.DepthPitch, desc.Height, map.RowPitch);
|
||||
}
|
||||
|
||||
/// <summary> Transform the GPU data into the color table. </summary>
|
||||
/// <param name="data"> The pointer to the raw texture data. </param>
|
||||
/// <param name="length"> The size of the raw texture data. </param>
|
||||
/// <param name="height"> The height of the texture. (Needs to be 16).</param>
|
||||
/// <param name="pitch"> The stride in the texture data. </param>
|
||||
/// <returns></returns>
|
||||
private static MtrlFile.ColorTable ReadTexture(nint data, int length, int height, int pitch)
|
||||
{
|
||||
// Check that the data has sufficient dimension and size.
|
||||
var expectedSize = sizeof(Half) * MaterialService.TextureWidth * height * 4;
|
||||
if (length < expectedSize || sizeof(MtrlFile.ColorTable) != expectedSize || height != MaterialService.TextureHeight)
|
||||
return default;
|
||||
|
||||
var ret = new MtrlFile.ColorTable();
|
||||
var target = (byte*)&ret;
|
||||
// If the stride is the same as in the table, just copy.
|
||||
if (pitch == MaterialService.TextureWidth)
|
||||
MemoryUtility.MemCpyUnchecked(target, (void*)data, length);
|
||||
// Otherwise, adapt the stride.
|
||||
else
|
||||
|
||||
for (var y = 0; y < height; ++y)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(target + y * MaterialService.TextureWidth * sizeof(Half) * 4, (byte*)data + y * pitch,
|
||||
MaterialService.TextureWidth * sizeof(Half) * 4);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Get resources of a texture. </summary>
|
||||
private static TRet GetResourceData<T, TRet>(T res, Func<T, T> cloneResource, Func<T, MappedSubresource, TRet> getData)
|
||||
where T : ID3D11Resource
|
||||
{
|
||||
using var stagingRes = cloneResource(res);
|
||||
|
||||
res.Device.ImmediateContext.CopyResource(stagingRes, res);
|
||||
stagingRes.Device.ImmediateContext.Map(stagingRes, 0, MapMode.Read, MapFlags.None, out var mapInfo).CheckError();
|
||||
|
||||
try
|
||||
{
|
||||
return getData(stagingRes, mapInfo);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stagingRes.Device.ImmediateContext.Unmap(stagingRes, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,9 @@ namespace Glamourer.Interop.Material;
|
|||
|
||||
public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
||||
{
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly IFramework _framework;
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly IFramework _framework;
|
||||
private readonly DirectXService _directXService;
|
||||
|
||||
public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid;
|
||||
public MtrlFile.ColorTable LastOriginalColorTable { get; private set; }
|
||||
|
|
@ -19,11 +20,11 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
|||
private ObjectIndex _objectIndex = ObjectIndex.AnyIndex;
|
||||
private MtrlFile.ColorTable _originalColorTable;
|
||||
|
||||
|
||||
public LiveColorTablePreviewer(IObjectTable objects, IFramework framework)
|
||||
public LiveColorTablePreviewer(IObjectTable objects, IFramework framework, DirectXService directXService)
|
||||
{
|
||||
_objects = objects;
|
||||
_framework = framework;
|
||||
_directXService = directXService;
|
||||
_framework.Update += OnFramework;
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
|||
|
||||
var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index);
|
||||
if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture))
|
||||
MaterialService.ReplaceColorTable(texture, LastOriginalColorTable);
|
||||
_directXService.ReplaceColorTable(texture, LastOriginalColorTable);
|
||||
|
||||
LastValueIndex = MaterialValueIndex.Invalid;
|
||||
_lastObjectIndex = ObjectIndex.AnyIndex;
|
||||
|
|
@ -78,15 +79,14 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
|||
}
|
||||
else
|
||||
{
|
||||
|
||||
for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||
{
|
||||
table[i].Diffuse = diffuse;
|
||||
table[i].Diffuse = diffuse;
|
||||
table[i].Emissive = emissive;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialService.ReplaceColorTable(texture, table);
|
||||
_directXService.ReplaceColorTable(texture, table);
|
||||
}
|
||||
|
||||
_valueIndex = MaterialValueIndex.Invalid;
|
||||
|
|
|
|||
|
|
@ -13,34 +13,6 @@ public static unsafe class MaterialService
|
|||
public const int TextureHeight = ColorTable.NumRows;
|
||||
public const int MaterialsPerModel = 4;
|
||||
|
||||
/// <summary> Generate a color table the way the game does inside the original texture, and release the original. </summary>
|
||||
/// <param name="original"> The original texture that will be replaced with a new one. </param>
|
||||
/// <param name="colorTable"> The input color table. </param>
|
||||
/// <returns> Success or failure. </returns>
|
||||
public static bool ReplaceColorTable(Texture** original, in ColorTable colorTable)
|
||||
{
|
||||
if (original == null)
|
||||
return false;
|
||||
|
||||
var textureSize = stackalloc int[2];
|
||||
textureSize[0] = TextureWidth;
|
||||
textureSize[1] = TextureHeight;
|
||||
|
||||
using var texture = new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F,
|
||||
(uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7), false);
|
||||
if (texture.IsInvalid)
|
||||
return false;
|
||||
|
||||
fixed (ColorTable* ptr = &colorTable)
|
||||
{
|
||||
if (!texture.Texture->InitializeContents(ptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
texture.Exchange(ref *(nint*)original);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture)
|
||||
{
|
||||
var textureSize = stackalloc int[2];
|
||||
|
|
|
|||
|
|
@ -109,31 +109,6 @@ public readonly record struct MaterialValueIndex(
|
|||
return true;
|
||||
}
|
||||
|
||||
public unsafe bool TryGetColorTable(Actor actor, out MtrlFile.ColorTable table)
|
||||
{
|
||||
if (TryGetTexture(actor, out var texture))
|
||||
return TryGetColorTable(texture, out table);
|
||||
|
||||
table = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public unsafe bool TryGetColorTable(Texture** texture, out MtrlFile.ColorTable table)
|
||||
=> DirectXTextureHelper.TryGetColorTable(*texture, out table);
|
||||
|
||||
public bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row)
|
||||
{
|
||||
if (!TryGetColorTable(actor, out var table))
|
||||
{
|
||||
row = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
row = table[RowIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static MaterialValueIndex FromKey(uint key)
|
||||
=> new(key);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Glamourer.State;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.State;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
|
||||
|
|
@ -10,13 +11,15 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
|
|||
private readonly PenumbraService _penumbra;
|
||||
private readonly StateManager _state;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly IFramework _framework;
|
||||
|
||||
public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects)
|
||||
public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects, IFramework framework)
|
||||
{
|
||||
_penumbra = penumbra;
|
||||
_config = config;
|
||||
_state = state;
|
||||
_objects = objects;
|
||||
_framework = framework;
|
||||
_penumbra.ModSettingChanged += OnModSettingChange;
|
||||
}
|
||||
|
||||
|
|
@ -26,28 +29,31 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
|
|||
private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited)
|
||||
{
|
||||
if (type is ModSettingChange.TemporaryMod)
|
||||
{
|
||||
_objects.Update();
|
||||
foreach (var (id, state) in _state)
|
||||
_framework.RunOnFrameworkThread(() =>
|
||||
{
|
||||
if (!_objects.TryGetValue(id, out var actors) || !actors.Valid)
|
||||
continue;
|
||||
_objects.Update();
|
||||
foreach (var (id, state) in _state)
|
||||
{
|
||||
if (!_objects.TryGetValue(id, out var actors) || !actors.Valid)
|
||||
continue;
|
||||
|
||||
var collection = _penumbra.GetActorCollection(actors.Objects[0]);
|
||||
if (collection != name)
|
||||
continue;
|
||||
var collection = _penumbra.GetActorCollection(actors.Objects[0]);
|
||||
if (collection != name)
|
||||
continue;
|
||||
|
||||
foreach (var actor in actors.Objects)
|
||||
_state.ReapplyState(actor, state, StateSource.IpcManual);
|
||||
Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}.");
|
||||
}
|
||||
}
|
||||
foreach (var actor in actors.Objects)
|
||||
_state.ReapplyState(actor, state, StateSource.IpcManual);
|
||||
Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}.");
|
||||
}
|
||||
});
|
||||
else if (_config.AutoRedrawEquipOnChanges)
|
||||
{
|
||||
var playerName = _penumbra.GetCurrentPlayerCollection();
|
||||
if (playerName == name)
|
||||
_state.ReapplyState(_objects.Player, StateSource.IpcManual);
|
||||
Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player).");
|
||||
}
|
||||
_framework.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var playerName = _penumbra.GetCurrentPlayerCollection();
|
||||
if (playerName == name)
|
||||
_state.ReapplyState(_objects.Player, StateSource.IpcManual);
|
||||
Glamourer.Log.Debug(
|
||||
$"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player).");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ public class StateApplier(
|
|||
MetaService _metaService,
|
||||
ObjectManager _objects,
|
||||
CrestService _crests,
|
||||
Configuration _config)
|
||||
Configuration _config,
|
||||
DirectXService _directX)
|
||||
{
|
||||
/// <summary> Simply force a redraw regardless of conditions. </summary>
|
||||
public void ForceRedraw(ActorData data)
|
||||
|
|
@ -286,7 +287,7 @@ public class StateApplier(
|
|||
if (!index.TryGetTexture(actor, out var texture))
|
||||
continue;
|
||||
|
||||
if (!index.TryGetColorTable(texture, out var table))
|
||||
if (!_directX.TryGetColorTable(*texture, out var table))
|
||||
continue;
|
||||
|
||||
if (value.HasValue)
|
||||
|
|
@ -296,7 +297,43 @@ public class StateApplier(
|
|||
else
|
||||
continue;
|
||||
|
||||
MaterialService.ReplaceColorTable(texture, table);
|
||||
_directX.ReplaceColorTable(texture, table);
|
||||
}
|
||||
}
|
||||
|
||||
public ActorData ChangeMaterialValues(ActorState state, bool apply)
|
||||
{
|
||||
var data = GetData(state);
|
||||
if (apply)
|
||||
ChangeMaterialValues(data, state.Materials, state.IsLocked);
|
||||
return data;
|
||||
}
|
||||
|
||||
public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials, bool force)
|
||||
{
|
||||
if (!force && !_config.UseAdvancedDyes)
|
||||
return;
|
||||
|
||||
var groupedMaterialValues = materials.Values.Select(p => (MaterialValueIndex.FromKey(p.Key), p.Value))
|
||||
.GroupBy(p => (p.Item1.DrawObject, p.Item1.SlotIndex, p.Item1.MaterialIndex));
|
||||
|
||||
foreach (var group in groupedMaterialValues)
|
||||
{
|
||||
var values = group.ToList();
|
||||
var mainKey = values[0].Item1;
|
||||
foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true }))
|
||||
{
|
||||
if (!mainKey.TryGetTexture(actor, out var texture))
|
||||
continue;
|
||||
|
||||
if (!_directX.TryGetColorTable(*texture, out var table))
|
||||
continue;
|
||||
|
||||
foreach (var (key, value) in values)
|
||||
value.Model.Apply(ref table[key.RowIndex]);
|
||||
|
||||
_directX.ReplaceColorTable(texture, table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -332,17 +369,17 @@ public class StateApplier(
|
|||
ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
|
||||
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
|
||||
ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
|
||||
}
|
||||
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
|
||||
ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
|
||||
ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
|
||||
ChangeCrests(actors, state.ModelData.CrestVisibility);
|
||||
ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
|
||||
foreach (var material in state.Materials.Values)
|
||||
ChangeMaterialValue(actors, MaterialValueIndex.FromKey(material.Key), material.Value.Model, state.IsLocked);
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
|
||||
ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
|
||||
ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
|
||||
ChangeCrests(actors, state.ModelData.CrestVisibility);
|
||||
ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
|
||||
// This should never be applied when caused through IPC, then redraw should be true.
|
||||
ChangeMaterialValues(actors, state.Materials, state.IsLocked);
|
||||
}
|
||||
}
|
||||
|
||||
return actors;
|
||||
|
|
|
|||
|
|
@ -321,6 +321,8 @@ public class StateEditor(
|
|||
settings.Source, out _, settings.Key);
|
||||
}
|
||||
}
|
||||
|
||||
requiresRedraw |= mergedDesign.Design.Materials.Count > 0 && settings.Source.IsIpc();
|
||||
}
|
||||
|
||||
var actors = settings.Source.RequiresChange()
|
||||
|
|
|
|||
|
|
@ -224,7 +224,8 @@ public sealed class StateManager(
|
|||
|| !state.ModelData.IsHuman
|
||||
|| CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw();
|
||||
|
||||
state.ModelData = state.BaseData;
|
||||
redraw |= state.Materials.Values.Count > 0 && source.IsIpc();
|
||||
state.ModelData = state.BaseData;
|
||||
state.ModelData.SetIsWet(false);
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
state.Sources[index] = StateSource.Game;
|
||||
|
|
@ -339,15 +340,13 @@ public sealed class StateManager(
|
|||
if (!GetOrCreate(actor, out var state))
|
||||
return;
|
||||
|
||||
var data = Applier.ApplyAll(state,
|
||||
!actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false);
|
||||
StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null);
|
||||
ReapplyState(actor, state, source);
|
||||
}
|
||||
|
||||
public void ReapplyState(Actor actor, ActorState state, StateSource source)
|
||||
{
|
||||
var data = Applier.ApplyAll(state,
|
||||
!actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false);
|
||||
!actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw() || state.Materials.Values.Count > 0 && source.IsIpc(), false);
|
||||
StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue