Fix material editor and improve pinning logic

This commit is contained in:
Exter-N 2025-03-29 16:52:08 +01:00 committed by Ottermandias
parent 5a5a1487a3
commit cb0214ca2f
6 changed files with 110 additions and 71 deletions

@ -1 +1 @@
Subproject commit e717a66f33b0656a7c5c971ffa2f63fd96477d94 Subproject commit b6b91f846096d15276b728ba2078f27b95317d15

View file

@ -288,7 +288,7 @@ public class MaterialExporter
const uint valueFace = 0x6E5B8F10; const uint valueFace = 0x6E5B8F10;
var isFace = material.Mtrl.ShaderPackage.ShaderKeys var isFace = material.Mtrl.ShaderPackage.ShaderKeys
.Any(key => key is { Category: categoryHairType, Value: valueFace }); .Any(key => key is { Key: categoryHairType, Value: valueFace });
var normal = material.Textures[TextureUsage.SamplerNormal]; var normal = material.Textures[TextureUsage.SamplerNormal];
var mask = material.Textures[TextureUsage.SamplerMask]; var mask = material.Textures[TextureUsage.SamplerMask];
@ -363,7 +363,7 @@ public class MaterialExporter
// Face is the default for the skin shader, so a lack of skin type category is also correct. // Face is the default for the skin shader, so a lack of skin type category is also correct.
var isFace = !material.Mtrl.ShaderPackage.ShaderKeys var isFace = !material.Mtrl.ShaderPackage.ShaderKeys
.Any(key => key.Category == categorySkinType && key.Value != valueFace); .Any(key => key.Key == categorySkinType && key.Value != valueFace);
// TODO: There's more nuance to skin than this, but this should be enough for a baseline reference. // TODO: There's more nuance to skin than this, but this should be enough for a baseline reference.
// TODO: Specular? // TODO: Specular?

View file

@ -22,7 +22,7 @@ public partial class MtrlTab
private bool DrawColorTableSection(bool disabled) private bool DrawColorTableSection(bool disabled)
{ {
if (!_shpkLoading && !SamplerIds.Contains(ShpkFile.TableSamplerId) || Mtrl.Table == null) if (!_shpkLoading && !TextureIds.Contains(ShpkFile.TableSamplerId) || Mtrl.Table == null)
return false; return false;
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));

View file

@ -216,7 +216,7 @@ public partial class MtrlTab
else else
foreach (var (key, index) in Mtrl.ShaderPackage.ShaderKeys.WithIndex()) foreach (var (key, index) in Mtrl.ShaderPackage.ShaderKeys.WithIndex())
{ {
var keyName = Names.KnownNames.TryResolve(key.Category); var keyName = Names.KnownNames.TryResolve(key.Key);
var valueName = keyName.WithKnownSuffixes().TryResolve(Names.KnownNames, key.Value); var valueName = keyName.WithKnownSuffixes().TryResolve(Names.KnownNames, key.Value);
_shaderKeys.Add((keyName.ToString(), index, string.Empty, true, [(valueName.ToString(), key.Value, string.Empty)])); _shaderKeys.Add((keyName.ToString(), index, string.Empty, true, [(valueName.ToString(), key.Value, string.Empty)]));
} }
@ -366,6 +366,7 @@ public partial class MtrlTab
ret = true; ret = true;
_associatedShpk = null; _associatedShpk = null;
_loadedShpkPath = FullPath.Empty; _loadedShpkPath = FullPath.Empty;
UnpinResources(true);
LoadShpk(FindAssociatedShpk(out _, out _)); LoadShpk(FindAssociatedShpk(out _, out _));
} }
@ -442,8 +443,8 @@ public partial class MtrlTab
{ {
using var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont); using var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont);
ref var key = ref Mtrl.ShaderPackage.ShaderKeys[index]; ref var key = ref Mtrl.ShaderPackage.ShaderKeys[index];
using var id = ImUtf8.PushId((int)key.Category); using var id = ImUtf8.PushId((int)key.Key);
var shpkKey = _associatedShpk?.GetMaterialKeyById(key.Category); var shpkKey = _associatedShpk?.GetMaterialKeyById(key.Key);
var currentValue = key.Value; var currentValue = key.Value;
var (currentLabel, _, currentDescription) = var (currentLabel, _, currentDescription) =
values.FirstOrNull(v => v.Value == currentValue) ?? ($"0x{currentValue:X8}", currentValue, string.Empty); values.FirstOrNull(v => v.Value == currentValue) ?? ($"0x{currentValue:X8}", currentValue, string.Empty);
@ -459,6 +460,7 @@ public partial class MtrlTab
{ {
key.Value = value; key.Value = value;
ret = true; ret = true;
UnpinResources(false);
Update(); Update();
} }

View file

@ -3,7 +3,6 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text; using OtterGui.Text;
using Penumbra.GameData;
using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Files.MaterialStructs;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using static Penumbra.GameData.Files.MaterialStructs.SamplerFlags; using static Penumbra.GameData.Files.MaterialStructs.SamplerFlags;
@ -16,18 +15,22 @@ public partial class MtrlTab
public readonly List<(string Label, int TextureIndex, int SamplerIndex, string Description, bool MonoFont)> Textures = new(4); public readonly List<(string Label, int TextureIndex, int SamplerIndex, string Description, bool MonoFont)> Textures = new(4);
public readonly HashSet<int> UnfoldedTextures = new(4); public readonly HashSet<int> UnfoldedTextures = new(4);
public readonly HashSet<uint> TextureIds = new(16);
public readonly HashSet<uint> SamplerIds = new(16); public readonly HashSet<uint> SamplerIds = new(16);
public float TextureLabelWidth; public float TextureLabelWidth;
private bool _samplersPinned;
private void UpdateTextures() private void UpdateTextures()
{ {
Textures.Clear(); Textures.Clear();
TextureIds.Clear();
SamplerIds.Clear(); SamplerIds.Clear();
if (_associatedShpk == null) if (_associatedShpk == null)
{ {
TextureIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId));
SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId)); SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId));
if (Mtrl.Table != null) if (Mtrl.Table != null)
SamplerIds.Add(TableSamplerId); TextureIds.Add(TableSamplerId);
foreach (var (sampler, index) in Mtrl.ShaderPackage.Samplers.WithIndex()) foreach (var (sampler, index) in Mtrl.ShaderPackage.Samplers.WithIndex())
Textures.Add(($"0x{sampler.SamplerId:X8}", sampler.TextureIndex, index, string.Empty, true)); Textures.Add(($"0x{sampler.SamplerId:X8}", sampler.TextureIndex, index, string.Empty, true));
@ -35,31 +38,39 @@ public partial class MtrlTab
else else
{ {
foreach (var index in _vertexShaders) foreach (var index in _vertexShaders)
SamplerIds.UnionWith(_associatedShpk.VertexShaders[index].Samplers.Select(sampler => sampler.Id));
foreach (var index in _pixelShaders)
SamplerIds.UnionWith(_associatedShpk.PixelShaders[index].Samplers.Select(sampler => sampler.Id));
if (!_shadersKnown)
{ {
SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId)); TextureIds.UnionWith(_associatedShpk.VertexShaders[index].Textures.Select(texture => texture.Id));
if (Mtrl.Table != null) SamplerIds.UnionWith(_associatedShpk.VertexShaders[index].Samplers.Select(sampler => sampler.Id));
SamplerIds.Add(TableSamplerId);
} }
foreach (var samplerId in SamplerIds) foreach (var index in _pixelShaders)
{ {
var shpkSampler = _associatedShpk.GetSamplerById(samplerId); TextureIds.UnionWith(_associatedShpk.PixelShaders[index].Textures.Select(texture => texture.Id));
if (shpkSampler is not { Slot: 2 }) SamplerIds.UnionWith(_associatedShpk.PixelShaders[index].Samplers.Select(sampler => sampler.Id));
}
if (_samplersPinned || !_shadersKnown)
{
TextureIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId));
if (Mtrl.Table != null)
TextureIds.Add(TableSamplerId);
}
foreach (var textureId in TextureIds)
{
var shpkTexture = _associatedShpk.GetTextureById(textureId);
if (shpkTexture is not { Slot: 2 })
continue; continue;
var dkData = TryGetShpkDevkitData<DevkitSampler>("Samplers", samplerId, true); var dkData = TryGetShpkDevkitData<DevkitSampler>("Samplers", textureId, true);
var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label); var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label);
var sampler = Mtrl.GetOrAddSampler(samplerId, dkData?.DefaultTexture ?? string.Empty, out var samplerIndex); var sampler = Mtrl.GetOrAddSampler(textureId, dkData?.DefaultTexture ?? string.Empty, out var samplerIndex);
Textures.Add((hasDkLabel ? dkData!.Label : shpkSampler.Value.Name, sampler.TextureIndex, samplerIndex, Textures.Add((hasDkLabel ? dkData!.Label : shpkTexture.Value.Name, sampler.TextureIndex, samplerIndex,
dkData?.Description ?? string.Empty, !hasDkLabel)); dkData?.Description ?? string.Empty, !hasDkLabel));
} }
if (SamplerIds.Contains(TableSamplerId)) if (TextureIds.Contains(TableSamplerId))
Mtrl.Table ??= new ColorTable(); Mtrl.Table ??= new ColorTable();
} }
@ -205,58 +216,67 @@ public partial class MtrlTab
ret = true; ret = true;
} }
ref var samplerFlags = ref Wrap(ref sampler.Flags); if (SamplerIds.Contains(sampler.SamplerId))
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
var addressMode = samplerFlags.UAddressMode;
if (ComboTextureAddressMode("##UAddressMode"u8, ref addressMode))
{ {
samplerFlags.UAddressMode = addressMode; ref var samplerFlags = ref Wrap(ref sampler.Flags);
ret = true;
SetSamplerFlags(sampler.SamplerId, sampler.Flags); ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
var addressMode = samplerFlags.UAddressMode;
if (ComboTextureAddressMode("##UAddressMode"u8, ref addressMode))
{
samplerFlags.UAddressMode = addressMode;
ret = true;
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
}
ImGui.SameLine();
ImUtf8.LabeledHelpMarker("U Address Mode"u8,
"Method to use for resolving a U texture coordinate that is outside the 0 to 1 range.");
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
addressMode = samplerFlags.VAddressMode;
if (ComboTextureAddressMode("##VAddressMode"u8, ref addressMode))
{
samplerFlags.VAddressMode = addressMode;
ret = true;
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
}
ImGui.SameLine();
ImUtf8.LabeledHelpMarker("V Address Mode"u8,
"Method to use for resolving a V texture coordinate that is outside the 0 to 1 range.");
var lodBias = samplerFlags.LodBias;
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
if (ImUtf8.DragScalar("##LoDBias"u8, ref lodBias, -8.0f, 7.984375f, 0.1f))
{
samplerFlags.LodBias = lodBias;
ret = true;
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
}
ImGui.SameLine();
ImUtf8.LabeledHelpMarker("Level of Detail Bias"u8,
"Offset from the calculated mipmap level.\n\nHigher means that the texture will start to lose detail nearer.\nLower means that the texture will keep its detail until farther.");
var minLod = samplerFlags.MinLod;
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
if (ImUtf8.DragScalar("##MinLoD"u8, ref minLod, 0, 15, 0.1f))
{
samplerFlags.MinLod = minLod;
ret = true;
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
}
ImGui.SameLine();
ImUtf8.LabeledHelpMarker("Minimum Level of Detail"u8,
"Most detailed mipmap level to use.\n\n0 is the full-sized texture, 1 is the half-sized texture, 2 is the quarter-sized texture, and so on.\n15 will forcibly reduce the texture to its smallest mipmap.");
} }
else
ImGui.SameLine();
ImUtf8.LabeledHelpMarker("U Address Mode"u8, "Method to use for resolving a U texture coordinate that is outside the 0 to 1 range.");
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
addressMode = samplerFlags.VAddressMode;
if (ComboTextureAddressMode("##VAddressMode"u8, ref addressMode))
{ {
samplerFlags.VAddressMode = addressMode; ImUtf8.Text("This texture does not have a dedicated sampler."u8);
ret = true;
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
} }
ImGui.SameLine();
ImUtf8.LabeledHelpMarker("V Address Mode"u8, "Method to use for resolving a V texture coordinate that is outside the 0 to 1 range.");
var lodBias = samplerFlags.LodBias;
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
if (ImUtf8.DragScalar("##LoDBias"u8, ref lodBias, -8.0f, 7.984375f, 0.1f))
{
samplerFlags.LodBias = lodBias;
ret = true;
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
}
ImGui.SameLine();
ImUtf8.LabeledHelpMarker("Level of Detail Bias"u8,
"Offset from the calculated mipmap level.\n\nHigher means that the texture will start to lose detail nearer.\nLower means that the texture will keep its detail until farther.");
var minLod = samplerFlags.MinLod;
ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f);
if (ImUtf8.DragScalar("##MinLoD"u8, ref minLod, 0, 15, 0.1f))
{
samplerFlags.MinLod = minLod;
ret = true;
SetSamplerFlags(sampler.SamplerId, sampler.Flags);
}
ImGui.SameLine();
ImUtf8.LabeledHelpMarker("Minimum Level of Detail"u8,
"Most detailed mipmap level to use.\n\n0 is the full-sized texture, 1 is the half-sized texture, 2 is the quarter-sized texture, and so on.\n15 will forcibly reduce the texture to its smallest mipmap.");
using var t = ImUtf8.TreeNode("Advanced Settings"u8); using var t = ImUtf8.TreeNode("Advanced Settings"u8);
if (!t) if (!t)
return ret; return ret;

View file

@ -57,6 +57,7 @@ public sealed partial class MtrlTab : IWritable, IDisposable
Mtrl = file; Mtrl = file;
FilePath = filePath; FilePath = filePath;
Writable = writable; Writable = writable;
_samplersPinned = true;
_associatedBaseDevkit = TryLoadShpkDevkit("_base", out _loadedBaseDevkitPathName); _associatedBaseDevkit = TryLoadShpkDevkit("_base", out _loadedBaseDevkitPathName);
Update(); Update();
LoadShpk(FindAssociatedShpk(out _, out _)); LoadShpk(FindAssociatedShpk(out _, out _));
@ -172,6 +173,22 @@ public sealed partial class MtrlTab : IWritable, IDisposable
Widget.DrawHexViewer(Mtrl.AdditionalData); Widget.DrawHexViewer(Mtrl.AdditionalData);
} }
private void UnpinResources(bool all)
{
_samplersPinned = false;
if (!all)
return;
var keys = Mtrl.ShaderPackage.ShaderKeys;
for (var i = 0; i < keys.Length; i++)
keys[i].Pinned = false;
var constants = Mtrl.ShaderPackage.Constants;
for (var i = 0; i < constants.Length; i++)
constants[i].Pinned = false;
}
private void Update() private void Update()
{ {
UpdateShaders(); UpdateShaders();
@ -192,7 +209,7 @@ public sealed partial class MtrlTab : IWritable, IDisposable
public byte[] Write() public byte[] Write()
{ {
var output = Mtrl.Clone(); var output = Mtrl.Clone();
output.GarbageCollect(_associatedShpk, SamplerIds); output.GarbageCollect(_associatedShpk, TextureIds);
return output.Write(); return output.Write();
} }