Skip to content

Commit

Permalink
Merge branch 'feature/material-editor-2099'
Browse files Browse the repository at this point in the history
  • Loading branch information
Ottermandias committed Aug 31, 2023
2 parents 5ba993c + 616a463 commit 82cecda
Show file tree
Hide file tree
Showing 24 changed files with 2,684 additions and 1,255 deletions.
2 changes: 1 addition & 1 deletion OtterGui
Submodule OtterGui updated 1 files
+36 −23 Util.cs
2 changes: 1 addition & 1 deletion Penumbra.GameData
131 changes: 131 additions & 0 deletions Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 149 additions & 0 deletions Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
70 changes: 70 additions & 0 deletions Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 82cecda

Please sign in to comment.