diff --git a/Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs b/Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs index 18afa949..f927aa43 100644 --- a/Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs +++ b/Penumbra/Interop/MaterialPreview/LiveColorSetPreviewer.cs @@ -5,6 +5,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using Penumbra.GameData.Files; +using Penumbra.Interop.SafeHandles; namespace Penumbra.Interop.MaterialPreview; @@ -16,8 +17,8 @@ public sealed unsafe class LiveColorSetPreviewer : LiveMaterialPreviewerBase private readonly Framework _framework; - private readonly Texture** _colorSetTexture; - private readonly Texture* _originalColorSetTexture; + private readonly Texture** _colorSetTexture; + private readonly SafeTextureHandle _originalColorSetTexture; private Half[] _colorSet; private bool _updatePending; @@ -40,12 +41,10 @@ public sealed unsafe class LiveColorSetPreviewer : LiveMaterialPreviewerBase _colorSetTexture = colorSetTextures + (MaterialInfo.ModelSlot * 4 + MaterialInfo.MaterialSlot); - _originalColorSetTexture = *_colorSetTexture; + _originalColorSetTexture = new SafeTextureHandle(*_colorSetTexture, true); if (_originalColorSetTexture == null) throw new InvalidOperationException("Material doesn't have a color set"); - Structs.TextureUtility.IncRef(_originalColorSetTexture); - _colorSet = new Half[TextureLength]; _updatePending = true; @@ -59,15 +58,9 @@ public sealed unsafe class LiveColorSetPreviewer : LiveMaterialPreviewerBase 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); - } + _originalColorSetTexture.Exchange(ref *(nint*)_colorSetTexture); + + _originalColorSetTexture.Dispose(); } public void ScheduleUpdate() @@ -89,8 +82,8 @@ public sealed unsafe class LiveColorSetPreviewer : LiveMaterialPreviewerBase textureSize[0] = TextureWidth; textureSize[1] = TextureHeight; - var newTexture = Structs.TextureUtility.Create2D(Device.Instance(), textureSize, 1, 0x2460, 0x80000804, 7); - if (newTexture == null) + using var texture = new SafeTextureHandle(Structs.TextureUtility.Create2D(Device.Instance(), textureSize, 1, 0x2460, 0x80000804, 7), false); + if (texture.IsInvalid) return; bool success; @@ -98,20 +91,12 @@ public sealed unsafe class LiveColorSetPreviewer : LiveMaterialPreviewerBase { fixed (Half* colorSet = _colorSet) { - success = Structs.TextureUtility.InitializeContents(newTexture, colorSet); + success = Structs.TextureUtility.InitializeContents(texture.Texture, 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); - } + texture.Exchange(ref *(nint*)_colorSetTexture); } protected override bool IsStillValid() diff --git a/Penumbra/Interop/SafeHandles/SafeTextureHandle.cs b/Penumbra/Interop/SafeHandles/SafeTextureHandle.cs new file mode 100644 index 00000000..36cd4612 --- /dev/null +++ b/Penumbra/Interop/SafeHandles/SafeTextureHandle.cs @@ -0,0 +1,48 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using Penumbra.Interop.Structs; + +namespace Penumbra.Interop.SafeHandles; + +public unsafe class SafeTextureHandle : SafeHandle +{ + public Texture* Texture => (Texture*)handle; + + public override bool IsInvalid => handle == 0; + + public SafeTextureHandle(Texture* handle, bool incRef, bool ownsHandle = true) : base(0, ownsHandle) + { + if (incRef && !ownsHandle) + throw new ArgumentException("Non-owning SafeTextureHandle with IncRef is unsupported"); + if (incRef && handle != null) + TextureUtility.IncRef(handle); + SetHandle((nint)handle); + } + + public void Exchange(ref nint ppTexture) + { + lock (this) + { + handle = Interlocked.Exchange(ref ppTexture, handle); + } + } + + public static SafeTextureHandle CreateInvalid() + => new(null, incRef: false); + + protected override bool ReleaseHandle() + { + nint handle; + lock (this) + { + handle = this.handle; + this.handle = 0; + } + if (handle != 0) + TextureUtility.DecRef((Texture*)handle); + + return true; + } +} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ConstantEditor.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ConstantEditor.cs index e5b16a47..19e96539 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ConstantEditor.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ConstantEditor.cs @@ -13,7 +13,7 @@ public partial class ModEditWindow { private interface IConstantEditor { - bool Draw(Span values, bool disabled, float editorWidth); + bool Draw(Span values, bool disabled); } private sealed class FloatConstantEditor : IConstantEditor @@ -42,16 +42,18 @@ public partial class ModEditWindow _format = $"{_format} {unit.Replace("%", "%%")}"; } - public bool Draw(Span values, bool disabled, float editorWidth) + public bool Draw(Span values, bool disabled) { - var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length; + var spacing = ImGui.GetStyle().ItemInnerSpacing.X; + var fieldWidth = (ImGui.CalcItemWidth() - (values.Length - 1) * spacing) / values.Length; var ret = false; + // Not using DragScalarN because of _relativeSpeed and other points of lost flexibility. for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx) { if (valueIdx > 0) - ImGui.SameLine(); + ImGui.SameLine(0.0f, spacing); ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx)); @@ -101,16 +103,18 @@ public partial class ModEditWindow _format = $"{_format} {unit.Replace("%", "%%")}"; } - public bool Draw(Span values, bool disabled, float editorWidth) + public bool Draw(Span values, bool disabled) { - var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length; + var spacing = ImGui.GetStyle().ItemInnerSpacing.X; + var fieldWidth = (ImGui.CalcItemWidth() - (values.Length - 1) * spacing) / values.Length; var ret = false; + // Not using DragScalarN because of _relativeSpeed and other points of lost flexibility. for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx) { if (valueIdx > 0) - ImGui.SameLine(); + ImGui.SameLine(0.0f, spacing); ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx)); @@ -148,13 +152,12 @@ public partial class ModEditWindow _clamped = clamped; } - public bool Draw(Span values, bool disabled, float editorWidth) + public bool Draw(Span values, bool disabled) { switch (values.Length) { case 3: { - ImGui.SetNextItemWidth(editorWidth); var value = new Vector3(values); if (_squaredRgb) value = PseudoSqrtRgb(value); @@ -170,7 +173,6 @@ public partial class ModEditWindow } case 4: { - ImGui.SetNextItemWidth(editorWidth); var value = new Vector4(values); if (_squaredRgb) value = PseudoSqrtRgb(value); @@ -186,7 +188,7 @@ public partial class ModEditWindow value.CopyTo(values); return true; } - default: return FloatConstantEditor.Default.Draw(values, disabled, editorWidth); + default: return FloatConstantEditor.Default.Draw(values, disabled); } } } @@ -198,9 +200,10 @@ public partial class ModEditWindow public EnumConstantEditor(IReadOnlyList<(string Label, float Value, string Description)> values) => _values = values; - public bool Draw(Span values, bool disabled, float editorWidth) + public bool Draw(Span values, bool disabled) { - var fieldWidth = (editorWidth - (values.Length - 1) * ImGui.GetStyle().ItemSpacing.X) / values.Length; + var spacing = ImGui.GetStyle().ItemInnerSpacing.X; + var fieldWidth = (ImGui.CalcItemWidth() - (values.Length - 1) * spacing) / values.Length; var ret = false; @@ -208,7 +211,7 @@ public partial class ModEditWindow { using var id = ImRaii.PushId(valueIdx); if (valueIdx > 0) - ImGui.SameLine(); + ImGui.SameLine(0.0f, spacing); ImGui.SetNextItemWidth(MathF.Round(fieldWidth * (valueIdx + 1)) - MathF.Round(fieldWidth * valueIdx)); diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs index d230950a..aff30fb0 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs @@ -19,6 +19,7 @@ using Penumbra.GameData.Structs; using Penumbra.Interop.MaterialPreview; using Penumbra.String; using Penumbra.String.Classes; +using Penumbra.UI.Classes; using static Penumbra.GameData.Files.ShpkFile; namespace Penumbra.UI.AdvancedWindow; @@ -177,7 +178,7 @@ public partial class ModEditWindow if (mayVary && (data as JObject)?["Vary"] != null) { - var selector = BuildSelector(data["Vary"]! + var selector = BuildSelector(data!["Vary"]! .Select(key => (uint)key) .Select(key => Mtrl.GetShaderKey(key)?.Value ?? AssociatedShpk!.GetMaterialKeyById(key)!.Value.DefaultValue)); var index = (int)data["Selectors"]![selector.ToString()]!; @@ -668,12 +669,13 @@ public partial class ModEditWindow private static void ApplyHighlight(ref MtrlFile.ColorSet.Row row, float time) { - var level = Math.Sin(time * 2.0 * Math.PI) * 0.25 + 0.5; - var levelSq = (float)(level * level); + var level = (MathF.Sin(time * 2.0f * MathF.PI) + 2.0f) / 3.0f / 255.0f; + var baseColor = ColorId.InGameHighlight.Value(); + var color = level * new Vector3(baseColor & 0xFF, (baseColor >> 8) & 0xFF, (baseColor >> 16) & 0xFF); row.Diffuse = Vector3.Zero; row.Specular = Vector3.Zero; - row.Emissive = new Vector3(levelSq); + row.Emissive = color * color; } public void Update() diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.Shpk.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.Shpk.cs index 8fca8aa6..f6f480e7 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.Shpk.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.Shpk.cs @@ -259,7 +259,8 @@ public partial class ModEditWindow if (buffer.Length > 0) { using var id = ImRaii.PushId($"##{constant.Id:X8}:{slice.Start}"); - if (editor.Draw(buffer[slice], disabled, 250.0f)) + ImGui.SetNextItemWidth(250.0f); + if (editor.Draw(buffer[slice], disabled)) { ret = true; tab.SetMaterialParameter(constant.Id, slice.Start, buffer[slice]); diff --git a/Penumbra/UI/Classes/Colors.cs b/Penumbra/UI/Classes/Colors.cs index 450f3787..e2acc1a3 100644 --- a/Penumbra/UI/Classes/Colors.cs +++ b/Penumbra/UI/Classes/Colors.cs @@ -24,7 +24,8 @@ public enum ColorId RedundantAssignment, NoModsAssignment, NoAssignment, - SelectorPriority, + SelectorPriority, + InGameHighlight, } public static class Colors @@ -64,6 +65,7 @@ public static class Colors ColorId.NoModsAssignment => ( 0x50000080, "'Use No Mods' Collection Assignment", "A collection assignment set to not use any mods at all."), ColorId.NoAssignment => ( 0x00000000, "Unassigned Collection Assignment", "A collection assignment that is not configured to any collection and thus just has no specific treatment."), ColorId.SelectorPriority => ( 0xFF808080, "Mod Selector Priority", "The priority displayed for non-zero priority mods in the mod selector."), + ColorId.InGameHighlight => ( 0xFFEBCF89, "In-Game Highlight", "An in-game element that has been highlighted for ease of editing."), _ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ), // @formatter:on };