Penumbra/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs
2025-03-27 03:55:19 +01:00

117 lines
3.8 KiB
C#

using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.Interop;
using Penumbra.Interop.SafeHandles;
namespace Penumbra.Interop.MaterialPreview;
public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase
{
private readonly IFramework _framework;
private readonly Texture** _colorTableTexture;
private readonly SafeTextureHandle _originalColorTableTexture;
private bool _updatePending;
public int Width { get; }
public int Height { get; }
public Half[] ColorTable { get; }
public LiveColorTablePreviewer(ObjectManager objects, IFramework 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 = DrawObject->ColorTableTextures;
if (colorSetTextures == null)
throw new InvalidOperationException("Draw object doesn't have color table textures");
_colorTableTexture = colorSetTextures + (MaterialInfo.ModelSlot * CharacterBase.MaterialsPerSlot + MaterialInfo.MaterialSlot);
_originalColorTableTexture = new SafeTextureHandle(*_colorTableTexture, true);
if (_originalColorTableTexture.Texture == null)
throw new InvalidOperationException("Material doesn't have a color table");
Width = (int)_originalColorTableTexture.Texture->ActualWidth;
Height = (int)_originalColorTableTexture.Texture->ActualHeight;
ColorTable = new Half[Width * Height * 4];
_updatePending = true;
framework.Update += OnFrameworkUpdate;
}
public Span<Half> GetColorRow(int i)
=> ColorTable.AsSpan().Slice(Width * 4 * i, Width * 4);
protected override void Clear(bool disposing, bool reset)
{
_framework.Update -= OnFrameworkUpdate;
base.Clear(disposing, reset);
if (reset)
_originalColorTableTexture.Exchange(ref *(nint*)_colorTableTexture);
_originalColorTableTexture.Dispose();
}
public void ScheduleUpdate()
{
_updatePending = true;
}
[SkipLocalsInit]
private void OnFrameworkUpdate(IFramework _)
{
if (!_updatePending)
return;
_updatePending = false;
if (!CheckValidity())
return;
var textureSize = stackalloc int[2];
textureSize[0] = Width;
textureSize[1] = Height;
using var texture =
new SafeTextureHandle(
Device.Instance()->CreateTexture2D(textureSize, 1, TextureFormat.R16G16B16A16_FLOAT,
TextureFlags.TextureNoSwizzle | TextureFlags.Immutable | TextureFlags.Managed, 7), false);
if (texture.IsInvalid)
return;
bool success;
lock (ColorTable)
{
fixed (Half* colorTable = ColorTable)
{
success = texture.Texture->InitializeContents(colorTable);
}
}
if (success)
texture.Exchange(ref *(nint*)_colorTableTexture);
}
protected override bool IsStillValid()
{
if (!base.IsStillValid())
return false;
var colorSetTextures = DrawObject->ColorTableTextures;
if (colorSetTextures == null)
return false;
return _colorTableTexture == colorSetTextures + (MaterialInfo.ModelSlot * CharacterBase.MaterialsPerSlot + MaterialInfo.MaterialSlot);
}
}