mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-20 23:07:51 +01:00
Restructure Live Preview.
This commit is contained in:
parent
e5e555b981
commit
a768b039a8
6 changed files with 478 additions and 518 deletions
131
Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs
Normal file
131
Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
149
Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs
Normal file
149
Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs
Normal file
|
|
@ -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<float>();
|
||||
|
||||
_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<float> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
120
Penumbra/Interop/MaterialPreview/MaterialInfo.cs
Normal file
120
Penumbra/Interop/MaterialPreview/MaterialInfo.cs
Normal file
|
|
@ -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<MaterialInfo> FindMaterials(IObjectTable objects, string materialPath)
|
||||
{
|
||||
var needle = ByteString.FromString(materialPath.Replace('/', '\\'), out var m, true) ? m : ByteString.Empty;
|
||||
|
||||
var result = new List<MaterialInfo>(Enum.GetValues<DrawObjectType>().Length);
|
||||
foreach (var type in Enum.GetValues<DrawObjectType>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue