From eb4d9aba7e080eac47c1b6f8dcb0f4c540eca757 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Thu, 22 Feb 2024 03:04:42 +0900 Subject: [PATCH] TexWidget: add test/examples for new APIs --- .../Windows/Data/Widgets/TexWidget.cs | 441 ++++++++++++++---- 1 file changed, 361 insertions(+), 80 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs index 8d6879ac1..351957974 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs @@ -1,12 +1,16 @@ using System.Collections.Generic; using System.IO; using System.Numerics; +using System.Threading.Tasks; +using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.SharableTextures; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; +using Dalamud.Storage.Assets; +using Dalamud.Utility; + using ImGuiNET; -using ImGuiScene; -using Serilog; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; @@ -15,23 +19,24 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal class TexWidget : IDataWindowWidget { - private readonly List addedTextures = new(); - + private readonly List addedTextures = new(); + private string iconId = "18"; private bool hiRes = true; private bool hq = false; - private bool keepAlive = false; private string inputTexPath = string.Empty; + private string inputFilePath = string.Empty; private Vector2 inputTexUv0 = Vector2.Zero; private Vector2 inputTexUv1 = Vector2.One; private Vector4 inputTintCol = Vector4.One; private Vector2 inputTexScale = Vector2.Zero; + private TextureManager textureManager = null!; /// public string[]? CommandShortcuts { get; init; } = { "tex", "texture" }; - + /// - public string DisplayName { get; init; } = "Tex"; + public string DisplayName { get; init; } = "Tex"; /// public bool Ready { get; set; } @@ -39,91 +44,81 @@ internal class TexWidget : IDataWindowWidget /// public void Load() { + this.addedTextures.AggregateToDisposable().Dispose(); + this.addedTextures.Clear(); + this.inputTexPath = "ui/loadingimage/-nowloading_base25_hr1.tex"; + this.inputFilePath = Path.Join( + Service.Get().StartInfo.AssetDirectory!, + DalamudAsset.Logo.GetAttribute()!.FileName); this.Ready = true; } /// public void Draw() { - var texManager = Service.Get(); + this.textureManager = Service.Get(); - ImGui.InputText("Icon ID", ref this.iconId, 32); - ImGui.Checkbox("HQ Item", ref this.hq); - ImGui.Checkbox("Hi-Res", ref this.hiRes); - ImGui.Checkbox("Keep alive", ref this.keepAlive); - if (ImGui.Button("Load Icon")) + if (ImGui.Button("GC")) + GC.Collect(); + + ImGui.PushID("loadedGameTextures"); + if (ImGui.CollapsingHeader($"Loaded Game Textures: {this.textureManager.GamePathTextures.Count}###header")) + this.DrawLoadedTextures(this.textureManager.GamePathTextures); + ImGui.PopID(); + + ImGui.PushID("loadedFileTextures"); + if (ImGui.CollapsingHeader($"Loaded File Textures: {this.textureManager.FileSystemTextures.Count}###header")) + this.DrawLoadedTextures(this.textureManager.FileSystemTextures); + ImGui.PopID(); + + if (ImGui.CollapsingHeader("Load Game File by Icon ID", ImGuiTreeNodeFlags.DefaultOpen)) + this.DrawIconInput(); + + if (ImGui.CollapsingHeader("Load Game File by Path", ImGuiTreeNodeFlags.DefaultOpen)) + this.DrawGamePathInput(); + + if (ImGui.CollapsingHeader("Load File", ImGuiTreeNodeFlags.DefaultOpen)) + this.DrawFileInput(); + + if (ImGui.CollapsingHeader("UV")) + this.DrawUvInput(); + + TextureEntry? toRemove = null; + TextureEntry? toCopy = null; + foreach (var t in this.addedTextures) { - try + ImGui.PushID(t.Id); + if (ImGui.CollapsingHeader($"Tex #{t.Id} {t}###header", ImGuiTreeNodeFlags.DefaultOpen)) { - var flags = ITextureProvider.IconFlags.None; - if (this.hq) - flags |= ITextureProvider.IconFlags.ItemHighQuality; - - if (this.hiRes) - flags |= ITextureProvider.IconFlags.HiRes; - - this.addedTextures.Add(texManager.GetIcon(uint.Parse(this.iconId), flags, keepAlive: this.keepAlive)); - } - catch (Exception ex) - { - Log.Error(ex, "Could not load tex"); - } - } - - ImGui.Separator(); - ImGui.InputText("Tex Path", ref this.inputTexPath, 255); - if (ImGui.Button("Load Tex")) - { - try - { - this.addedTextures.Add(texManager.GetTextureFromGame(this.inputTexPath, this.keepAlive)); - } - catch (Exception ex) - { - Log.Error(ex, "Could not load tex"); - } - } - - if (ImGui.Button("Load File")) - { - try - { - this.addedTextures.Add(texManager.GetTextureFromFile(new FileInfo(this.inputTexPath), this.keepAlive)); - } - catch (Exception ex) - { - Log.Error(ex, "Could not load tex"); - } - } - - ImGui.Separator(); - ImGui.InputFloat2("UV0", ref this.inputTexUv0); - ImGui.InputFloat2("UV1", ref this.inputTexUv1); - ImGui.InputFloat4("Tint", ref this.inputTintCol); - ImGui.InputFloat2("Scale", ref this.inputTexScale); - - ImGuiHelpers.ScaledDummy(10); - - IDalamudTextureWrap? toRemove = null; - for (var i = 0; i < this.addedTextures.Count; i++) - { - if (ImGui.CollapsingHeader($"Tex #{i}")) - { - var tex = this.addedTextures[i]; - - var scale = new Vector2(tex.Width, tex.Height); - if (this.inputTexScale != Vector2.Zero) - scale = this.inputTexScale; - - ImGui.Image(tex.ImGuiHandle, scale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol); - - if (ImGui.Button($"X##{i}")) - toRemove = tex; + if (ImGui.Button("X")) + toRemove = t; ImGui.SameLine(); - if (ImGui.Button($"Clone##{i}")) - this.addedTextures.Add(tex.CreateWrapSharingLowLevelResource()); + if (ImGui.Button("Copy")) + toCopy = t; + + try + { + if (t.GetTexture(this.textureManager) is { } tex) + { + var scale = new Vector2(tex.Width, tex.Height); + if (this.inputTexScale != Vector2.Zero) + scale = this.inputTexScale; + + ImGui.Image(tex.ImGuiHandle, scale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol); + } + else + { + ImGui.TextUnformatted(t.DescribeError()); + } + } + catch (Exception e) + { + ImGui.TextUnformatted(e.ToString()); + } } + + ImGui.PopID(); } if (toRemove != null) @@ -131,5 +126,291 @@ internal class TexWidget : IDataWindowWidget toRemove.Dispose(); this.addedTextures.Remove(toRemove); } + + if (toCopy != null) + { + this.addedTextures.Add(toCopy.CreateFromSharedLowLevelResource(this.textureManager)); + } + } + + private unsafe void DrawLoadedTextures(IReadOnlyDictionary textures) + { + if (!ImGui.BeginTable("##table", 6)) + return; + + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("000000").X); + ImGui.TableSetupColumn("Preview", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Preview_").X); + ImGui.TableSetupColumn("Source", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("RefCount", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("RefCount__").X); + ImGui.TableSetupColumn("SelfRef", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("00.000___").X); + ImGui.TableSetupColumn("CanRevive", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("CanRevive__").X); + ImGui.TableHeadersRow(); + + var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + clipper.Begin(textures.Count); + + using (var enu = textures.GetEnumerator()) + { + var row = 0; + while (clipper.Step()) + { + var valid = true; + for (; row < clipper.DisplayStart && valid; row++) + valid = enu.MoveNext(); + + if (!valid) + break; + + for (; row < clipper.DisplayEnd; row++) + { + valid = enu.MoveNext(); + if (!valid) + break; + + var (key, texture) = enu.Current; + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + this.TextRightAlign($"{texture.InstanceIdForDebug:n0}"); + + ImGui.TableNextColumn(); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); + ImGui.Button("Hover"); + ImGui.PopStyleColor(3); + ImGui.PopStyleVar(); + if (ImGui.IsItemHovered() && texture.GetImmediate() is { } immediate) + { + ImGui.BeginTooltip(); + ImGui.Image(immediate.ImGuiHandle, immediate.Size); + ImGui.EndTooltip(); + } + + ImGui.TableNextColumn(); + this.TextCopiable(key); + + ImGui.TableNextColumn(); + this.TextRightAlign($"{texture.RefCountForDebug:n0}"); + + ImGui.TableNextColumn(); + var remain = texture.SelfReferenceExpiresInForDebug; + this.TextRightAlign(remain <= 0 ? "-" : $"{remain:00.000}"); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(texture.RevivalPossibility?.TryGetTarget(out _) is true ? "Yes" : "No"); + } + + if (!valid) + break; + } + } + + clipper.Destroy(); + ImGui.EndTable(); + + ImGuiHelpers.ScaledDummy(10); + } + + private void DrawIconInput() + { + ImGui.InputText("Icon ID", ref this.iconId, 32); + ImGui.Checkbox("HQ Item", ref this.hq); + ImGui.Checkbox("Hi-Res", ref this.hiRes); +#pragma warning disable CS0618 // Type or member is obsolete + if (ImGui.Button("Load Icon (API9)")) + { + var flags = ITextureProvider.IconFlags.None; + if (this.hq) + flags |= ITextureProvider.IconFlags.ItemHighQuality; + if (this.hiRes) + flags |= ITextureProvider.IconFlags.HiRes; + this.addedTextures.Add(new(Api9: this.textureManager.GetIcon(uint.Parse(this.iconId), flags))); + } +#pragma warning restore CS0618 // Type or member is obsolete + + ImGui.SameLine(); + if (ImGui.Button("Load Icon (Async)")) + { + this.addedTextures.Add( + new( + Api10: this.textureManager.GetFromGameIconAsync( + new(uint.Parse(this.iconId), this.hq, this.hiRes)))); + } + + ImGui.SameLine(); + if (ImGui.Button("Load Icon (Immediate)")) + this.addedTextures.Add(new(Api10ImmGameIcon: new(uint.Parse(this.iconId), this.hq, this.hiRes))); + + ImGuiHelpers.ScaledDummy(10); + } + + private void DrawGamePathInput() + { + ImGui.InputText("Tex Path", ref this.inputTexPath, 255); + +#pragma warning disable CS0618 // Type or member is obsolete + if (ImGui.Button("Load Tex (API9)")) + this.addedTextures.Add(new(Api9: this.textureManager.GetTextureFromGame(this.inputTexPath))); +#pragma warning restore CS0618 // Type or member is obsolete + + ImGui.SameLine(); + if (ImGui.Button("Load Tex (Async)")) + this.addedTextures.Add(new(Api10: this.textureManager.GetFromGameAsync(this.inputTexPath))); + + ImGui.SameLine(); + if (ImGui.Button("Load Tex (Immediate)")) + this.addedTextures.Add(new(Api10ImmGamePath: this.inputTexPath)); + + ImGuiHelpers.ScaledDummy(10); + } + + private void DrawFileInput() + { + ImGui.InputText("File Path", ref this.inputFilePath, 255); + +#pragma warning disable CS0618 // Type or member is obsolete + if (ImGui.Button("Load File (API9)")) + this.addedTextures.Add(new(Api9: this.textureManager.GetTextureFromFile(new(this.inputFilePath)))); +#pragma warning restore CS0618 // Type or member is obsolete + + ImGui.SameLine(); + if (ImGui.Button("Load File (Async)")) + this.addedTextures.Add(new(Api10: this.textureManager.GetFromFileAsync(this.inputFilePath))); + + ImGui.SameLine(); + if (ImGui.Button("Load File (Immediate)")) + this.addedTextures.Add(new(Api10ImmFile: this.inputFilePath)); + + ImGuiHelpers.ScaledDummy(10); + } + + private void DrawUvInput() + { + ImGui.InputFloat2("UV0", ref this.inputTexUv0); + ImGui.InputFloat2("UV1", ref this.inputTexUv1); + ImGui.InputFloat4("Tint", ref this.inputTintCol); + ImGui.InputFloat2("Scale", ref this.inputTexScale); + + ImGuiHelpers.ScaledDummy(10); + } + + private void TextRightAlign(string s) + { + var width = ImGui.CalcTextSize(s).X; + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetColumnWidth() - width); + ImGui.TextUnformatted(s); + } + + private void TextCopiable(string s, bool framepad = false) + { + var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0); + if (framepad) + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(s); + if (ImGui.IsItemHovered()) + { + ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding); + var vp = ImGui.GetWindowViewport(); + var wrx = (vp.WorkPos.X + vp.WorkSize.X) - offset.X; + ImGui.SetNextWindowSizeConstraints(Vector2.One, new(wrx, float.MaxValue)); + ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(wrx); + ImGui.TextWrapped(s.Replace("%", "%%")); + ImGui.PopTextWrapPos(); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(s); + Service.Get().AddNotification( + $"Copied {ImGui.TableGetColumnName()} to clipboard.", + this.DisplayName, + NotificationType.Success); + } + } + + private record TextureEntry( + IDalamudTextureWrap? SharedResource = null, + IDalamudTextureWrap? Api9 = null, + Task? Api10 = null, + GameIconLookup? Api10ImmGameIcon = null, + string? Api10ImmGamePath = null, + string? Api10ImmFile = null) : IDisposable + { + private static int idCounter; + + public int Id { get; } = idCounter++; + + public void Dispose() + { + this.SharedResource?.Dispose(); + this.Api9?.Dispose(); + _ = this.Api10?.ToContentDisposedTask(); + } + + public string DescribeError() + { + if (this.SharedResource is not null) + return "Unknown error"; + if (this.Api9 is not null) + return "Unknown error"; + if (this.Api10 is not null) + { + return !this.Api10.IsCompleted + ? "Loading" + : this.Api10.Exception?.ToString() ?? (this.Api10.IsCanceled ? "Canceled" : "Unknown error"); + } + + if (this.Api10ImmGameIcon is not null) + return "Must not happen"; + if (this.Api10ImmGamePath is not null) + return "Must not happen"; + if (this.Api10ImmFile is not null) + return "Must not happen"; + return "Not implemented"; + } + + public IDalamudTextureWrap? GetTexture(ITextureProvider tp) + { + if (this.SharedResource is not null) + return this.SharedResource; + if (this.Api9 is not null) + return this.Api9; + if (this.Api10 is not null) + return this.Api10.IsCompletedSuccessfully ? this.Api10.Result : null; + if (this.Api10ImmGameIcon is not null) + return tp.ImmediateGetFromGameIcon(this.Api10ImmGameIcon.Value); + if (this.Api10ImmGamePath is not null) + return tp.ImmediateGetFromGame(this.Api10ImmGamePath); + if (this.Api10ImmFile is not null) + return tp.ImmediateGetFromFile(this.Api10ImmFile); + return null; + } + + public TextureEntry CreateFromSharedLowLevelResource(ITextureProvider tp) => + new() { SharedResource = this.GetTexture(tp)?.CreateWrapSharingLowLevelResource() }; + + public override string ToString() + { + if (this.SharedResource is not null) + return $"{nameof(this.SharedResource)}: {this.SharedResource}"; + if (this.Api9 is not null) + return $"{nameof(this.Api9)}: {this.Api9}"; + if (this.Api10 is { IsCompletedSuccessfully: true }) + return $"{nameof(this.Api10)}: {this.Api10.Result}"; + if (this.Api10 is not null) + return $"{nameof(this.Api10)}: {this.Api10}"; + if (this.Api10ImmGameIcon is not null) + return $"{nameof(this.Api10ImmGameIcon)}: {this.Api10ImmGameIcon}"; + if (this.Api10ImmGamePath is not null) + return $"{nameof(this.Api10ImmGamePath)}: {this.Api10ImmGamePath}"; + if (this.Api10ImmFile is not null) + return $"{nameof(this.Api10ImmFile)}: {this.Api10ImmFile}"; + return "Not implemented"; + } } }