From 71b84bcf404662ba29a50e0f473f8a41820d8e15 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Thu, 22 Feb 2024 17:57:24 +0900 Subject: [PATCH] Move all texture loading functionalities from IM to TM --- .../Interface/Internal/InterfaceManager.cs | 168 ------------------ .../FileSystemSharableTexture.cs | 10 +- .../GamePathSharableTexture.cs | 7 +- Dalamud/Interface/Internal/TextureManager.cs | 112 ++++++++++-- .../Internal/Windows/PluginImageCache.cs | 22 +-- .../PluginInstaller/PluginInstallerWindow.cs | 76 +++++--- .../FontAtlasFactory.BuildToolkit.cs | 25 ++- .../Internals/FontAtlasFactory.cs | 24 ++- Dalamud/Interface/UiBuilder.cs | 40 ++--- .../Plugin/Services/RawImageSpecification.cs | 10 ++ Dalamud/Storage/Assets/DalamudAssetManager.cs | 6 +- .../Assets/DalamudAssetRawTextureAttribute.cs | 28 +-- 12 files changed, 230 insertions(+), 298 deletions(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 5bafc4ff3..7e9c8eed0 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -26,14 +26,9 @@ using Dalamud.Utility.Timing; using ImGuiNET; using ImGuiScene; -using Lumina.Data.Files; -using Lumina.Data.Parsing.Tex.Buffers; - using PInvoke; using Serilog; using SharpDX; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; using SharpDX.DXGI; // general dev notes, here because it's easiest @@ -261,169 +256,6 @@ internal class InterfaceManager : IDisposable, IServiceType } } -#nullable enable - - /// - /// Load an image from disk. - /// - /// The filepath to load. - /// A texture, ready to use in ImGui. - public IDalamudTextureWrap? LoadImage(string filePath) - { - if (this.scene == null) - throw new InvalidOperationException("Scene isn't ready."); - - try - { - var wrap = this.scene?.LoadImage(filePath); - return wrap != null ? new DalamudTextureWrap(wrap) : null; - } - catch (Exception ex) - { - Log.Error(ex, $"Failed to load image from {filePath}"); - } - - return null; - } - - /// - /// Load an image from an array of bytes. - /// - /// The data to load. - /// A texture, ready to use in ImGui. - public IDalamudTextureWrap? LoadImage(byte[] imageData) - { - if (this.scene == null) - throw new InvalidOperationException("Scene isn't ready."); - - try - { - var wrap = this.scene?.LoadImage(imageData); - return wrap != null ? new DalamudTextureWrap(wrap) : null; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to load image from memory"); - } - - return null; - } - - /// - /// Load an image from an array of bytes. - /// - /// The data to load. - /// The width in pixels. - /// The height in pixels. - /// The number of channels. - /// A texture, ready to use in ImGui. - public IDalamudTextureWrap? LoadImageRaw(byte[] imageData, int width, int height, int numChannels) - { - if (this.scene == null) - throw new InvalidOperationException("Scene isn't ready."); - - try - { - var wrap = this.scene?.LoadImageRaw(imageData, width, height, numChannels); - return wrap != null ? new DalamudTextureWrap(wrap) : null; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to load image from raw data"); - } - - return null; - } - - /// - /// Check whether the current D3D11 Device supports the given DXGI format. - /// - /// DXGI format to check. - /// Whether it is supported. - public bool SupportsDxgiFormat(Format dxgiFormat) => this.scene is null - ? throw new InvalidOperationException("Scene isn't ready.") - : this.scene.Device.CheckFormatSupport(dxgiFormat).HasFlag(FormatSupport.Texture2D); - - /// - /// Load an image from a span of bytes of specified format. - /// - /// The data to load. - /// The pitch(stride) in bytes. - /// The width in pixels. - /// The height in pixels. - /// Format of the texture. - /// A texture, ready to use in ImGui. - public DalamudTextureWrap LoadImageFromDxgiFormat(ReadOnlySpan data, int pitch, int width, int height, Format dxgiFormat) - { - if (this.scene == null) - throw new InvalidOperationException("Scene isn't ready."); - - ShaderResourceView resView; - unsafe - { - fixed (void* pData = data) - { - var texDesc = new Texture2DDescription - { - Width = width, - Height = height, - MipLevels = 1, - ArraySize = 1, - Format = dxgiFormat, - SampleDescription = new(1, 0), - Usage = ResourceUsage.Immutable, - BindFlags = BindFlags.ShaderResource, - CpuAccessFlags = CpuAccessFlags.None, - OptionFlags = ResourceOptionFlags.None, - }; - - using var texture = new Texture2D(this.Device, texDesc, new DataRectangle(new(pData), pitch)); - resView = new(this.Device, texture, new() - { - Format = texDesc.Format, - Dimension = ShaderResourceViewDimension.Texture2D, - Texture2D = { MipLevels = texDesc.MipLevels }, - }); - } - } - - // no sampler for now because the ImGui implementation we copied doesn't allow for changing it - return new DalamudTextureWrap(new D3DTextureWrap(resView, width, height)); - } - -#nullable restore - - /// - /// Get a texture handle for the specified Lumina TexFile. - /// - /// The texture to obtain a handle to. - /// A texture wrap that can be used to render the texture. - /// Thrown when the graphics system is not available yet. Relevant for plugins when LoadRequiredState is set to 0 or 1. - /// Thrown when the given is not supported. Most likely is that the file is corrupt. - public DalamudTextureWrap LoadImageFromTexFile(TexFile file) - { - if (!this.IsReady) - throw new InvalidOperationException("Cannot create textures before scene is ready"); - - var buffer = file.TextureBuffer; - var bpp = 1 << (((int)file.Header.Format & (int)TexFile.TextureFormat.BppMask) >> - (int)TexFile.TextureFormat.BppShift); - - var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false); - if (conversion != TexFile.DxgiFormatConversion.NoConversion || !this.SupportsDxgiFormat((Format)dxgiFormat)) - { - dxgiFormat = (int)Format.B8G8R8A8_UNorm; - buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8); - bpp = 32; - } - - var pitch = buffer is BlockCompressionTextureBuffer - ? Math.Max(1, (buffer.Width + 3) / 4) * 2 * bpp - : ((buffer.Width * bpp) + 7) / 8; - - return this.LoadImageFromDxgiFormat(buffer.RawData, pitch, buffer.Width, buffer.Height, (Format)dxgiFormat); - } - /// /// Sets up a deferred invocation of font rebuilding, before the next render frame. /// diff --git a/Dalamud/Interface/Internal/SharableTextures/FileSystemSharableTexture.cs b/Dalamud/Interface/Internal/SharableTextures/FileSystemSharableTexture.cs index bd867d6a3..fe1b16de8 100644 --- a/Dalamud/Interface/Internal/SharableTextures/FileSystemSharableTexture.cs +++ b/Dalamud/Interface/Internal/SharableTextures/FileSystemSharableTexture.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -64,11 +66,9 @@ internal sealed class FileSystemSharableTexture : SharableTexture this.CreateTextureAsync, this.LoadCancellationToken); - private Task CreateTextureAsync(CancellationToken cancellationToken) + private async Task CreateTextureAsync(CancellationToken cancellationToken) { - var w = (IDalamudTextureWrap)Service.Get().LoadImage(this.path) - ?? throw new("Failed to load image because of an unknown reason."); - this.DisposeSuppressingWrap = new(w); - return Task.FromResult(w); + var tm = await Service.GetAsync(); + return tm.NoThrottleGetFromImage(await File.ReadAllBytesAsync(this.path, cancellationToken)); } } diff --git a/Dalamud/Interface/Internal/SharableTextures/GamePathSharableTexture.cs b/Dalamud/Interface/Internal/SharableTextures/GamePathSharableTexture.cs index 82f2d1b48..d7c478187 100644 --- a/Dalamud/Interface/Internal/SharableTextures/GamePathSharableTexture.cs +++ b/Dalamud/Interface/Internal/SharableTextures/GamePathSharableTexture.cs @@ -70,10 +70,11 @@ internal sealed class GamePathSharableTexture : SharableTexture private async Task CreateTextureAsync(CancellationToken cancellationToken) { var dm = await Service.GetAsync(); - var im = await Service.GetAsync(); - var file = dm.GetFile(this.path); + var tm = await Service.GetAsync(); + if (dm.GetFile(this.path) is not { } file) + throw new FileNotFoundException(); cancellationToken.ThrowIfCancellationRequested(); - var t = (IDalamudTextureWrap)im.LoadImageFromTexFile(file ?? throw new FileNotFoundException()); + var t = tm.NoThrottleGetFromTexFile(file); this.DisposeSuppressingWrap = new(t); return t; } diff --git a/Dalamud/Interface/Internal/TextureManager.cs b/Dalamud/Interface/Internal/TextureManager.cs index 378697e88..a4edb4449 100644 --- a/Dalamud/Interface/Internal/TextureManager.cs +++ b/Dalamud/Interface/Internal/TextureManager.cs @@ -19,6 +19,11 @@ using Dalamud.Utility; using Lumina.Data.Files; +using SharpDX; +using SharpDX.Direct3D; +using SharpDX.Direct3D11; +using SharpDX.DXGI; + namespace Dalamud.Interface.Internal; // TODO API10: Remove keepAlive from public APIs @@ -241,9 +246,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid CancellationToken cancellationToken = default) => this.textureLoadThrottler.CreateLoader( new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(), - _ => Task.FromResult( - this.interfaceManager.LoadImage(bytes.ToArray()) - ?? throw new("Failed to load image because of an unknown reason.")), + ct => Task.Run(() => this.NoThrottleGetFromImage(bytes.ToArray()), ct), cancellationToken); /// @@ -273,13 +276,46 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid /// public IDalamudTextureWrap GetFromRaw( RawImageSpecification specs, - ReadOnlySpan bytes) => - this.interfaceManager.LoadImageFromDxgiFormat( - bytes, - specs.Pitch, - specs.Width, - specs.Height, - (SharpDX.DXGI.Format)specs.DxgiFormat); + ReadOnlySpan bytes) + { + if (this.interfaceManager.Scene is not { } scene) + { + _ = Service.Get(); + scene = this.interfaceManager.Scene ?? throw new InvalidOperationException(); + } + + ShaderResourceView resView; + unsafe + { + fixed (void* pData = bytes) + { + var texDesc = new Texture2DDescription + { + Width = specs.Width, + Height = specs.Height, + MipLevels = 1, + ArraySize = 1, + Format = (Format)specs.DxgiFormat, + SampleDescription = new(1, 0), + Usage = ResourceUsage.Immutable, + BindFlags = BindFlags.ShaderResource, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None, + }; + + using var texture = new Texture2D(scene.Device, texDesc, new DataRectangle(new(pData), specs.Pitch)); + resView = new(scene.Device, texture, new() + { + Format = texDesc.Format, + Dimension = ShaderResourceViewDimension.Texture2D, + Texture2D = { MipLevels = texDesc.MipLevels }, + }); + } + } + + // no sampler for now because the ImGui implementation we copied doesn't allow for changing it + return new DalamudTextureWrap(new D3DTextureWrap(resView, specs.Width, specs.Height)); + } /// public Task GetFromRawAsync( @@ -325,12 +361,20 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid CancellationToken cancellationToken = default) => this.textureLoadThrottler.CreateLoader( new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(), - _ => Task.FromResult(this.interfaceManager.LoadImageFromTexFile(file)), + ct => Task.Run(() => this.NoThrottleGetFromTexFile(file), ct), cancellationToken); - + /// - public bool SupportsDxgiFormat(int dxgiFormat) => - this.interfaceManager.SupportsDxgiFormat((SharpDX.DXGI.Format)dxgiFormat); + public bool SupportsDxgiFormat(int dxgiFormat) + { + if (this.interfaceManager.Scene is not { } scene) + { + _ = Service.Get(); + scene = this.interfaceManager.Scene ?? throw new InvalidOperationException(); + } + + return scene.Device.CheckFormatSupport((Format)dxgiFormat).HasFlag(FormatSupport.Texture2D); + } /// public bool TryGetIconPath(in GameIconLookup lookup, out string path) @@ -443,6 +487,46 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid } } + /// + /// Gets a texture from the given image. Skips the load throttler; intended to be used from implementation of + /// s. + /// + /// The data. + /// The loaded texture. + internal IDalamudTextureWrap NoThrottleGetFromImage(ReadOnlyMemory bytes) + { + if (this.interfaceManager.Scene is not { } scene) + { + _ = Service.Get(); + scene = this.interfaceManager.Scene ?? throw new InvalidOperationException(); + } + + return new DalamudTextureWrap( + scene.LoadImage(bytes.ToArray()) + ?? throw new("Failed to load image because of an unknown reason.")); + } + + /// + /// Gets a texture from the given . Skips the load throttler; intended to be used from + /// implementation of s. + /// + /// The data. + /// The loaded texture. + internal IDalamudTextureWrap NoThrottleGetFromTexFile(TexFile file) + { + var buffer = file.TextureBuffer; + var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false); + if (conversion != TexFile.DxgiFormatConversion.NoConversion || !this.SupportsDxgiFormat(dxgiFormat)) + { + dxgiFormat = (int)Format.B8G8R8A8_UNorm; + buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8); + } + + return this.GetFromRaw( + RawImageSpecification.From(buffer.Width, buffer.Height, dxgiFormat), + buffer.RawData); + } + private static string FormatIconPath(uint iconId, string? type, bool highResolution) { var format = highResolution ? HighResolutionIconFileFormat : IconFileFormat; diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index 29adbb3e5..6ae45c962 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -269,33 +269,17 @@ internal class PluginImageCache : IDisposable, IServiceType if (bytes == null) return null; - var interfaceManager = (await Service.GetAsync()).Manager; - var framework = await Service.GetAsync(); + var textureManager = await Service.GetAsync(); IDalamudTextureWrap? image; // FIXME(goat): This is a hack around this call failing randomly in certain situations. Might be related to not being called on the main thread. try { - image = interfaceManager.LoadImage(bytes); + image = await textureManager.GetFromImageAsync(bytes); } catch (Exception ex) { - Log.Error(ex, "Access violation during load plugin {name} from {Loc} (Async Thread)", name, loc); - - try - { - image = await framework.RunOnFrameworkThread(() => interfaceManager.LoadImage(bytes)); - } - catch (Exception ex2) - { - Log.Error(ex2, "Access violation during load plugin {name} from {Loc} (Framework Thread)", name, loc); - return null; - } - } - - if (image == null) - { - Log.Error($"Could not load {name} for {manifest.InternalName} at {loc}"); + Log.Error(ex, $"Could not load {name} for {manifest.InternalName} at {loc}"); return null; } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 95c227662..29e76434b 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -69,8 +69,8 @@ internal class PluginInstallerWindow : Window, IDisposable private string[] testerImagePaths = new string[5]; private string testerIconPath = string.Empty; - private IDalamudTextureWrap?[]? testerImages; - private IDalamudTextureWrap? testerIcon; + private Task?[]? testerImages; + private Task? testerIcon; private bool testerError = false; private bool testerUpdateAvailable = false; @@ -1510,10 +1510,10 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.SetCursorPos(startCursor); - var hasIcon = this.testerIcon != null; + var hasIcon = this.testerIcon?.IsCompletedSuccessfully is true; var iconTex = this.imageCache.DefaultIcon; - if (hasIcon) iconTex = this.testerIcon; + if (hasIcon) iconTex = this.testerIcon.Result; var iconSize = ImGuiHelpers.ScaledVector2(64, 64); @@ -1607,10 +1607,24 @@ internal class PluginInstallerWindow : Window, IDisposable for (var i = 0; i < this.testerImages.Length; i++) { var popupId = $"pluginTestingImage{i}"; - var image = this.testerImages[i]; - if (image == null) + var imageTask = this.testerImages[i]; + if (imageTask == null) continue; + if (!imageTask.IsCompleted) + { + ImGui.TextUnformatted("Loading..."); + continue; + } + + if (imageTask.Exception is not null) + { + ImGui.TextUnformatted(imageTask.Exception.ToString()); + continue; + } + + var image = imageTask.Result; + ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); @@ -1666,14 +1680,37 @@ internal class PluginInstallerWindow : Window, IDisposable ImGuiHelpers.ScaledDummy(20); - static void CheckImageSize(IDalamudTextureWrap? image, int maxWidth, int maxHeight, bool requireSquare) + static void CheckImageSize(Task? imageTask, int maxWidth, int maxHeight, bool requireSquare) { - if (image == null) + if (imageTask == null) return; - if (image.Width > maxWidth || image.Height > maxHeight) - ImGui.TextColored(ImGuiColors.DalamudRed, $"Image is larger than the maximum allowed resolution ({image.Width}x{image.Height} > {maxWidth}x{maxHeight})"); - if (requireSquare && image.Width != image.Height) - ImGui.TextColored(ImGuiColors.DalamudRed, $"Image must be square! Current size: {image.Width}x{image.Height}"); + + if (!imageTask.IsCompleted) + { + ImGui.Text("Loading..."); + return; + } + + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + + if (imageTask.Exception is { } exc) + { + ImGui.TextUnformatted(exc.ToString()); + } + else + { + var image = imageTask.Result; + if (image.Width > maxWidth || image.Height > maxHeight) + { + ImGui.TextUnformatted( + $"Image is larger than the maximum allowed resolution ({image.Width}x{image.Height} > {maxWidth}x{maxHeight})"); + } + + if (requireSquare && image.Width != image.Height) + ImGui.TextUnformatted($"Image must be square! Current size: {image.Width}x{image.Height}"); + } + + ImGui.PopStyleColor(); } ImGui.InputText("Icon Path", ref this.testerIconPath, 1000); @@ -1695,7 +1732,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (this.testerImages?.Length > 4) CheckImageSize(this.testerImages[4], PluginImageCache.PluginImageWidth, PluginImageCache.PluginImageHeight, false); - var im = Service.Get(); + var tm = Service.Get(); if (ImGui.Button("Load")) { try @@ -1708,23 +1745,18 @@ internal class PluginInstallerWindow : Window, IDisposable if (!this.testerIconPath.IsNullOrEmpty()) { - this.testerIcon = im.LoadImage(this.testerIconPath); + this.testerIcon = tm.GetFromFileAsync(this.testerIconPath); } - this.testerImages = new IDalamudTextureWrap[this.testerImagePaths.Length]; + this.testerImages = new Task?[this.testerImagePaths.Length]; for (var i = 0; i < this.testerImagePaths.Length; i++) { if (this.testerImagePaths[i].IsNullOrEmpty()) continue; - if (this.testerImages[i] != null) - { - this.testerImages[i].Dispose(); - this.testerImages[i] = null; - } - - this.testerImages[i] = im.LoadImage(this.testerImagePaths[i]); + _ = this.testerImages[i]?.ToContentDisposedTask(); + this.testerImages[i] = tm.GetFromFileAsync(this.testerImagePaths[i]); } } catch (Exception ex) diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs index 55af20329..0148e80dd 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs @@ -10,6 +10,7 @@ using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.GameFonts; using Dalamud.Interface.Internal; using Dalamud.Interface.Utility; +using Dalamud.Plugin.Services; using Dalamud.Storage.Assets; using Dalamud.Utility; @@ -579,7 +580,7 @@ internal sealed partial class FontAtlasFactory var buf = Array.Empty(); try { - var use4 = this.factory.InterfaceManager.SupportsDxgiFormat(Format.B4G4R4A4_UNorm); + var use4 = this.factory.TextureManager.SupportsDxgiFormat((int)Format.B4G4R4A4_UNorm); var bpp = use4 ? 2 : 4; var width = this.NewImAtlas.TexWidth; var height = this.NewImAtlas.TexHeight; @@ -591,12 +592,9 @@ internal sealed partial class FontAtlasFactory } else if (texture.TexPixelsRGBA32 is not null) { - var wrap = this.factory.InterfaceManager.LoadImageFromDxgiFormat( - new(texture.TexPixelsRGBA32, width * height * 4), - width * 4, - width, - height, - use4 ? Format.B4G4R4A4_UNorm : Format.R8G8B8A8_UNorm); + var wrap = this.factory.TextureManager.GetFromRaw( + RawImageSpecification.Rgba32(width, height), + new(texture.TexPixelsRGBA32, width * height * 4)); this.data.AddExistingTexture(wrap); texture.TexID = wrap.ImGuiHandle; } @@ -634,12 +632,13 @@ internal sealed partial class FontAtlasFactory } } - var wrap = this.factory.InterfaceManager.LoadImageFromDxgiFormat( - buf, - width * bpp, - width, - height, - use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm); + var wrap = this.factory.TextureManager.GetFromRaw( + new( + width, + height, + width * bpp, + (int)(use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm)), + buf); this.data.AddExistingTexture(wrap); texture.TexID = wrap.ImGuiHandle; continue; diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs index 3e0fd1394..ffddcc272 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs @@ -46,11 +46,13 @@ internal sealed partial class FontAtlasFactory DataManager dataManager, Framework framework, InterfaceManager interfaceManager, - DalamudAssetManager dalamudAssetManager) + DalamudAssetManager dalamudAssetManager, + TextureManager textureManager) { this.Framework = framework; this.InterfaceManager = interfaceManager; this.dalamudAssetManager = dalamudAssetManager; + this.TextureManager = textureManager; this.SceneTask = Service .GetAsync() .ContinueWith(r => r.Result.Manager.Scene); @@ -144,6 +146,11 @@ internal sealed partial class FontAtlasFactory /// public InterfaceManager InterfaceManager { get; } + /// + /// Gets the service instance of . + /// + public TextureManager TextureManager { get; } + /// /// Gets the async task for inside . /// @@ -346,7 +353,7 @@ internal sealed partial class FontAtlasFactory var numPixels = texFile.Header.Width * texFile.Header.Height; _ = Service.Get(); - var targetIsB4G4R4A4 = this.InterfaceManager.SupportsDxgiFormat(Format.B4G4R4A4_UNorm); + var targetIsB4G4R4A4 = this.TextureManager.SupportsDxgiFormat((int)Format.B4G4R4A4_UNorm); var bpp = targetIsB4G4R4A4 ? 2 : 4; var buffer = ArrayPool.Shared.Rent(numPixels * bpp); try @@ -369,12 +376,13 @@ internal sealed partial class FontAtlasFactory } return this.scopedFinalizer.Add( - this.InterfaceManager.LoadImageFromDxgiFormat( - buffer, - texFile.Header.Width * bpp, - texFile.Header.Width, - texFile.Header.Height, - targetIsB4G4R4A4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm)); + this.TextureManager.GetFromRaw( + new( + texFile.Header.Width, + texFile.Header.Height, + texFile.Header.Width * bpp, + (int)(targetIsB4G4R4A4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm)), + buffer)); } finally { diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index ca0ecb71c..74a718507 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -379,6 +379,8 @@ public sealed class UiBuilder : IDisposable private Task InterfaceManagerWithSceneAsync => Service.GetAsync().ContinueWith(task => task.Result.Manager); + private ITextureProvider TextureProvider => Service.Get(); + /// /// Loads an image from the specified file. /// @@ -386,9 +388,7 @@ public sealed class UiBuilder : IDisposable /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete($"Use {nameof(ITextureProvider.GetFromFileAsync)}.")] - public IDalamudTextureWrap LoadImage(string filePath) - => this.InterfaceManagerWithScene?.LoadImage(filePath) - ?? throw new InvalidOperationException("Load failed."); + public IDalamudTextureWrap LoadImage(string filePath) => this.TextureProvider.GetFromFileAsync(filePath).Result; /// /// Loads an image from a byte stream, such as a png downloaded into memory. @@ -397,9 +397,7 @@ public sealed class UiBuilder : IDisposable /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete($"Use {nameof(ITextureProvider.GetFromImageAsync)}.")] - public IDalamudTextureWrap LoadImage(byte[] imageData) - => this.InterfaceManagerWithScene?.LoadImage(imageData) - ?? throw new InvalidOperationException("Load failed."); + public IDalamudTextureWrap LoadImage(byte[] imageData) => this.TextureProvider.GetFromImageAsync(imageData).Result; /// /// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use . @@ -411,9 +409,12 @@ public sealed class UiBuilder : IDisposable /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete($"Use {nameof(ITextureProvider.GetFromRaw)} or {nameof(ITextureProvider.GetFromRawAsync)}.")] - public IDalamudTextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) - => this.InterfaceManagerWithScene?.LoadImageRaw(imageData, width, height, numChannels) - ?? throw new InvalidOperationException("Load failed."); + public IDalamudTextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) => + numChannels switch + { + 4 => this.TextureProvider.GetFromRaw(RawImageSpecification.Rgba32(width, height), imageData), + _ => throw new NotSupportedException(), + }; /// /// Loads an ULD file that can load textures containing multiple icons in a single texture. @@ -430,10 +431,7 @@ public sealed class UiBuilder : IDisposable /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete($"Use {nameof(ITextureProvider.GetFromFileAsync)}.")] - public Task LoadImageAsync(string filePath) => Task.Run( - async () => - (await this.InterfaceManagerWithSceneAsync).LoadImage(filePath) - ?? throw new InvalidOperationException("Load failed.")); + public Task LoadImageAsync(string filePath) => this.TextureProvider.GetFromFileAsync(filePath); /// /// Asynchronously loads an image from a byte stream, such as a png downloaded into memory, when it's possible to do so. @@ -442,10 +440,8 @@ public sealed class UiBuilder : IDisposable /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete($"Use {nameof(ITextureProvider.GetFromImageAsync)}.")] - public Task LoadImageAsync(byte[] imageData) => Task.Run( - async () => - (await this.InterfaceManagerWithSceneAsync).LoadImage(imageData) - ?? throw new InvalidOperationException("Load failed.")); + public Task LoadImageAsync(byte[] imageData) => + this.TextureProvider.GetFromImageAsync(imageData); /// /// Asynchronously loads an image from raw unformatted pixel data, with no type or header information, when it's possible to do so. To load formatted data, use . @@ -457,10 +453,12 @@ public sealed class UiBuilder : IDisposable /// A object wrapping the created image. Use inside ImGui.Image(). [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)] [Obsolete($"Use {nameof(ITextureProvider.GetFromRawAsync)}.")] - public Task LoadImageRawAsync(byte[] imageData, int width, int height, int numChannels) => Task.Run( - async () => - (await this.InterfaceManagerWithSceneAsync).LoadImageRaw(imageData, width, height, numChannels) - ?? throw new InvalidOperationException("Load failed.")); + public Task LoadImageRawAsync(byte[] imageData, int width, int height, int numChannels) => + numChannels switch + { + 4 => this.TextureProvider.GetFromRawAsync(RawImageSpecification.Rgba32(width, height), imageData), + _ => Task.FromException(new NotSupportedException()), + }; /// /// Waits for UI to become available for use. diff --git a/Dalamud/Plugin/Services/RawImageSpecification.cs b/Dalamud/Plugin/Services/RawImageSpecification.cs index 696b3d6b6..206ce578e 100644 --- a/Dalamud/Plugin/Services/RawImageSpecification.cs +++ b/Dalamud/Plugin/Services/RawImageSpecification.cs @@ -213,4 +213,14 @@ public record struct RawImageSpecification(int Width, int Height, int Pitch, int /// The new instance. public static RawImageSpecification Rgba32(int width, int height) => new(width, height, width * 4, (int)DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM); + + /// + /// Creates a new instance of record using the given resolution, + /// in A8 UNorm pixel format. + /// + /// The width. + /// The height. + /// The new instance. + public static RawImageSpecification A8(int width, int height) => + new(width, height, width, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM); } diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs index 8abf42e5c..83f03e274 100644 --- a/Dalamud/Storage/Assets/DalamudAssetManager.cs +++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs @@ -302,17 +302,17 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA var buf = Array.Empty(); try { - var im = (await Service.GetAsync()).Manager; + var tm = await Service.GetAsync(); await using var stream = await this.CreateStreamAsync(asset); var length = checked((int)stream.Length); buf = ArrayPool.Shared.Rent(length); stream.ReadExactly(buf, 0, length); var image = purpose switch { - DalamudAssetPurpose.TextureFromPng => im.LoadImage(buf), + DalamudAssetPurpose.TextureFromPng => await tm.GetFromImageAsync(buf), DalamudAssetPurpose.TextureFromRaw => asset.GetAttribute() is { } raw - ? im.LoadImageFromDxgiFormat(buf, raw.Pitch, raw.Width, raw.Height, raw.Format) + ? await tm.GetFromRawAsync(raw.Specification, buf) : throw new InvalidOperationException( "TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."), _ => null, diff --git a/Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs b/Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs index b79abb7d7..99253411b 100644 --- a/Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs +++ b/Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs @@ -1,4 +1,6 @@ -using SharpDX.DXGI; +using Dalamud.Plugin.Services; + +using SharpDX.DXGI; namespace Dalamud.Storage.Assets; @@ -17,29 +19,11 @@ internal class DalamudAssetRawTextureAttribute : Attribute /// The format. public DalamudAssetRawTextureAttribute(int width, int pitch, int height, Format format) { - this.Width = width; - this.Pitch = pitch; - this.Height = height; - this.Format = format; + this.Specification = new(width, height, pitch, (int)format); } /// - /// Gets the width. + /// Gets the specification. /// - public int Width { get; } - - /// - /// Gets the pitch. - /// - public int Pitch { get; } - - /// - /// Gets the height. - /// - public int Height { get; } - - /// - /// Gets the format. - /// - public Format Format { get; } + public RawImageSpecification Specification { get; } }