-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feature/material-editor-2099'
- Loading branch information
Showing
24 changed files
with
2,684 additions
and
1,255 deletions.
There are no files selected for viewing
Submodule Penumbra.GameData
updated
6 files
+270 −0 | Files/MtrlFile.AddRemove.cs | |
+77 −11 | Files/MtrlFile.ColorSet.cs | |
+54 −50 | Files/MtrlFile.cs | |
+11 −5 | Files/ShpkFile.Write.cs | |
+116 −29 | Files/ShpkFile.cs | |
+19 −0 | UtilityFunctions.cs |
131 changes: 131 additions & 0 deletions
131
Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
149
Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
70
Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.