mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-13 12:14:18 +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(
|
public sealed unsafe class AdvancedDyePopup(
|
||||||
Configuration config,
|
Configuration config,
|
||||||
StateManager stateManager,
|
StateManager stateManager,
|
||||||
LiveColorTablePreviewer preview) : IService
|
LiveColorTablePreviewer preview,
|
||||||
|
DirectXService directX) : IService
|
||||||
{
|
{
|
||||||
private MaterialValueIndex? _drawIndex;
|
private MaterialValueIndex? _drawIndex;
|
||||||
private ActorState _state = null!;
|
private ActorState _state = null!;
|
||||||
private Actor _actor;
|
private Actor _actor;
|
||||||
private byte _selectedMaterial = byte.MaxValue;
|
private byte _selectedMaterial = byte.MaxValue;
|
||||||
private bool _anyChanged = false;
|
private bool _anyChanged;
|
||||||
|
|
||||||
private bool ShouldBeDrawn()
|
private bool ShouldBeDrawn()
|
||||||
{
|
{
|
||||||
|
|
@ -94,7 +95,7 @@ public sealed unsafe class AdvancedDyePopup(
|
||||||
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
|
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
|
||||||
{
|
{
|
||||||
var index = _drawIndex!.Value with { MaterialIndex = 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 })
|
if (index == preview.LastValueIndex with { RowIndex = 0 })
|
||||||
table = preview.LastOriginalColorTable;
|
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;
|
_actor = actor;
|
||||||
_state = state;
|
_state = state;
|
||||||
|
|
@ -236,20 +237,20 @@ public sealed unsafe class AdvancedDyePopup(
|
||||||
? _state.ModelData.Weapon(slot)
|
? _state.ModelData.Weapon(slot)
|
||||||
: _state.ModelData.Armor(slot).ToWeapon(0);
|
: _state.ModelData.Armor(slot).ToWeapon(0);
|
||||||
var value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual);
|
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);
|
ImGui.SameLine(0, spacing);
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged,
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged,
|
||||||
true))
|
true))
|
||||||
for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
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)
|
private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex index, in MtrlFile.ColorTable table)
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId(index.RowIndex);
|
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)
|
if (!changed)
|
||||||
{
|
{
|
||||||
var internalRow = new ColorRow(row);
|
var internalRow = new ColorRow(row);
|
||||||
|
|
@ -314,7 +315,7 @@ public sealed unsafe class AdvancedDyePopup(
|
||||||
stateManager.ResetMaterialValue(_state, index, ApplySettings.Game);
|
stateManager.ResetMaterialValue(_state, index, ApplySettings.Game);
|
||||||
|
|
||||||
if (applied)
|
if (applied)
|
||||||
stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual);
|
stateManager.ChangeMaterialValue(_state, index, value, ApplySettings.Manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LabelStruct _label = new();
|
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
|
public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
||||||
{
|
{
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
private readonly IFramework _framework;
|
private readonly IFramework _framework;
|
||||||
|
private readonly DirectXService _directXService;
|
||||||
|
|
||||||
public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid;
|
public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid;
|
||||||
public MtrlFile.ColorTable LastOriginalColorTable { get; private set; }
|
public MtrlFile.ColorTable LastOriginalColorTable { get; private set; }
|
||||||
|
|
@ -19,11 +20,11 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
||||||
private ObjectIndex _objectIndex = ObjectIndex.AnyIndex;
|
private ObjectIndex _objectIndex = ObjectIndex.AnyIndex;
|
||||||
private MtrlFile.ColorTable _originalColorTable;
|
private MtrlFile.ColorTable _originalColorTable;
|
||||||
|
|
||||||
|
public LiveColorTablePreviewer(IObjectTable objects, IFramework framework, DirectXService directXService)
|
||||||
public LiveColorTablePreviewer(IObjectTable objects, IFramework framework)
|
|
||||||
{
|
{
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_framework = framework;
|
_framework = framework;
|
||||||
|
_directXService = directXService;
|
||||||
_framework.Update += OnFramework;
|
_framework.Update += OnFramework;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
||||||
|
|
||||||
var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index);
|
var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index);
|
||||||
if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture))
|
if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture))
|
||||||
MaterialService.ReplaceColorTable(texture, LastOriginalColorTable);
|
_directXService.ReplaceColorTable(texture, LastOriginalColorTable);
|
||||||
|
|
||||||
LastValueIndex = MaterialValueIndex.Invalid;
|
LastValueIndex = MaterialValueIndex.Invalid;
|
||||||
_lastObjectIndex = ObjectIndex.AnyIndex;
|
_lastObjectIndex = ObjectIndex.AnyIndex;
|
||||||
|
|
@ -78,15 +79,14 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i)
|
||||||
{
|
{
|
||||||
table[i].Diffuse = diffuse;
|
table[i].Diffuse = diffuse;
|
||||||
table[i].Emissive = emissive;
|
table[i].Emissive = emissive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialService.ReplaceColorTable(texture, table);
|
_directXService.ReplaceColorTable(texture, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
_valueIndex = MaterialValueIndex.Invalid;
|
_valueIndex = MaterialValueIndex.Invalid;
|
||||||
|
|
|
||||||
|
|
@ -13,34 +13,6 @@ public static unsafe class MaterialService
|
||||||
public const int TextureHeight = ColorTable.NumRows;
|
public const int TextureHeight = ColorTable.NumRows;
|
||||||
public const int MaterialsPerModel = 4;
|
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)
|
public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture)
|
||||||
{
|
{
|
||||||
var textureSize = stackalloc int[2];
|
var textureSize = stackalloc int[2];
|
||||||
|
|
|
||||||
|
|
@ -109,31 +109,6 @@ public readonly record struct MaterialValueIndex(
|
||||||
return true;
|
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)
|
public static MaterialValueIndex FromKey(uint key)
|
||||||
=> new(key);
|
=> new(key);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Glamourer.State;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Glamourer.State;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
|
||||||
|
|
@ -10,13 +11,15 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
|
||||||
private readonly PenumbraService _penumbra;
|
private readonly PenumbraService _penumbra;
|
||||||
private readonly StateManager _state;
|
private readonly StateManager _state;
|
||||||
private readonly ObjectManager _objects;
|
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;
|
_penumbra = penumbra;
|
||||||
_config = config;
|
_config = config;
|
||||||
_state = state;
|
_state = state;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
|
_framework = framework;
|
||||||
_penumbra.ModSettingChanged += OnModSettingChange;
|
_penumbra.ModSettingChanged += OnModSettingChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,28 +29,31 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService
|
||||||
private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited)
|
private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited)
|
||||||
{
|
{
|
||||||
if (type is ModSettingChange.TemporaryMod)
|
if (type is ModSettingChange.TemporaryMod)
|
||||||
{
|
_framework.RunOnFrameworkThread(() =>
|
||||||
_objects.Update();
|
|
||||||
foreach (var (id, state) in _state)
|
|
||||||
{
|
{
|
||||||
if (!_objects.TryGetValue(id, out var actors) || !actors.Valid)
|
_objects.Update();
|
||||||
continue;
|
foreach (var (id, state) in _state)
|
||||||
|
{
|
||||||
|
if (!_objects.TryGetValue(id, out var actors) || !actors.Valid)
|
||||||
|
continue;
|
||||||
|
|
||||||
var collection = _penumbra.GetActorCollection(actors.Objects[0]);
|
var collection = _penumbra.GetActorCollection(actors.Objects[0]);
|
||||||
if (collection != name)
|
if (collection != name)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var actor in actors.Objects)
|
foreach (var actor in actors.Objects)
|
||||||
_state.ReapplyState(actor, state, StateSource.IpcManual);
|
_state.ReapplyState(actor, state, StateSource.IpcManual);
|
||||||
Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}.");
|
Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}.");
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
else if (_config.AutoRedrawEquipOnChanges)
|
else if (_config.AutoRedrawEquipOnChanges)
|
||||||
{
|
_framework.RunOnFrameworkThread(() =>
|
||||||
var playerName = _penumbra.GetCurrentPlayerCollection();
|
{
|
||||||
if (playerName == name)
|
var playerName = _penumbra.GetCurrentPlayerCollection();
|
||||||
_state.ReapplyState(_objects.Player, StateSource.IpcManual);
|
if (playerName == name)
|
||||||
Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player).");
|
_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,
|
MetaService _metaService,
|
||||||
ObjectManager _objects,
|
ObjectManager _objects,
|
||||||
CrestService _crests,
|
CrestService _crests,
|
||||||
Configuration _config)
|
Configuration _config,
|
||||||
|
DirectXService _directX)
|
||||||
{
|
{
|
||||||
/// <summary> Simply force a redraw regardless of conditions. </summary>
|
/// <summary> Simply force a redraw regardless of conditions. </summary>
|
||||||
public void ForceRedraw(ActorData data)
|
public void ForceRedraw(ActorData data)
|
||||||
|
|
@ -286,7 +287,7 @@ public class StateApplier(
|
||||||
if (!index.TryGetTexture(actor, out var texture))
|
if (!index.TryGetTexture(actor, out var texture))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!index.TryGetColorTable(texture, out var table))
|
if (!_directX.TryGetColorTable(*texture, out var table))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (value.HasValue)
|
if (value.HasValue)
|
||||||
|
|
@ -296,7 +297,43 @@ public class StateApplier(
|
||||||
else
|
else
|
||||||
continue;
|
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));
|
ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
|
||||||
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
|
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
|
||||||
ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
|
ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
|
||||||
}
|
|
||||||
|
|
||||||
if (state.ModelData.IsHuman)
|
if (state.ModelData.IsHuman)
|
||||||
{
|
{
|
||||||
ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
|
ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
|
||||||
ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
|
ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
|
||||||
ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
|
ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
|
||||||
ChangeCrests(actors, state.ModelData.CrestVisibility);
|
ChangeCrests(actors, state.ModelData.CrestVisibility);
|
||||||
ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
|
ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
|
||||||
foreach (var material in state.Materials.Values)
|
// This should never be applied when caused through IPC, then redraw should be true.
|
||||||
ChangeMaterialValue(actors, MaterialValueIndex.FromKey(material.Key), material.Value.Model, state.IsLocked);
|
ChangeMaterialValues(actors, state.Materials, state.IsLocked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return actors;
|
return actors;
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,8 @@ public class StateEditor(
|
||||||
settings.Source, out _, settings.Key);
|
settings.Source, out _, settings.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requiresRedraw |= mergedDesign.Design.Materials.Count > 0 && settings.Source.IsIpc();
|
||||||
}
|
}
|
||||||
|
|
||||||
var actors = settings.Source.RequiresChange()
|
var actors = settings.Source.RequiresChange()
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,8 @@ public sealed class StateManager(
|
||||||
|| !state.ModelData.IsHuman
|
|| !state.ModelData.IsHuman
|
||||||
|| CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw();
|
|| 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);
|
state.ModelData.SetIsWet(false);
|
||||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||||
state.Sources[index] = StateSource.Game;
|
state.Sources[index] = StateSource.Game;
|
||||||
|
|
@ -339,15 +340,13 @@ public sealed class StateManager(
|
||||||
if (!GetOrCreate(actor, out var state))
|
if (!GetOrCreate(actor, out var state))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var data = Applier.ApplyAll(state,
|
ReapplyState(actor, state, source);
|
||||||
!actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false);
|
|
||||||
StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReapplyState(Actor actor, ActorState state, StateSource source)
|
public void ReapplyState(Actor actor, ActorState state, StateSource source)
|
||||||
{
|
{
|
||||||
var data = Applier.ApplyAll(state,
|
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);
|
StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue