Move all texture loading functionalities from IM to TM

This commit is contained in:
Soreepeong 2024-02-22 17:57:24 +09:00
parent 248c7911a0
commit 71b84bcf40
12 changed files with 230 additions and 298 deletions

View file

@ -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
/// <summary>
/// Load an image from disk.
/// </summary>
/// <param name="filePath">The filepath to load.</param>
/// <returns>A texture, ready to use in ImGui.</returns>
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;
}
/// <summary>
/// Load an image from an array of bytes.
/// </summary>
/// <param name="imageData">The data to load.</param>
/// <returns>A texture, ready to use in ImGui.</returns>
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;
}
/// <summary>
/// Load an image from an array of bytes.
/// </summary>
/// <param name="imageData">The data to load.</param>
/// <param name="width">The width in pixels.</param>
/// <param name="height">The height in pixels.</param>
/// <param name="numChannels">The number of channels.</param>
/// <returns>A texture, ready to use in ImGui.</returns>
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;
}
/// <summary>
/// Check whether the current D3D11 Device supports the given DXGI format.
/// </summary>
/// <param name="dxgiFormat">DXGI format to check.</param>
/// <returns>Whether it is supported.</returns>
public bool SupportsDxgiFormat(Format dxgiFormat) => this.scene is null
? throw new InvalidOperationException("Scene isn't ready.")
: this.scene.Device.CheckFormatSupport(dxgiFormat).HasFlag(FormatSupport.Texture2D);
/// <summary>
/// Load an image from a span of bytes of specified format.
/// </summary>
/// <param name="data">The data to load.</param>
/// <param name="pitch">The pitch(stride) in bytes.</param>
/// <param name="width">The width in pixels.</param>
/// <param name="height">The height in pixels.</param>
/// <param name="dxgiFormat">Format of the texture.</param>
/// <returns>A texture, ready to use in ImGui.</returns>
public DalamudTextureWrap LoadImageFromDxgiFormat(ReadOnlySpan<byte> 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
/// <summary>
/// Get a texture handle for the specified Lumina TexFile.
/// </summary>
/// <param name="file">The texture to obtain a handle to.</param>
/// <returns>A texture wrap that can be used to render the texture.</returns>
/// <exception cref="InvalidOperationException">Thrown when the graphics system is not available yet. Relevant for plugins when LoadRequiredState is set to 0 or 1.</exception>
/// <exception cref="NotSupportedException">Thrown when the given <see cref="TexFile"/> is not supported. Most likely is that the file is corrupt.</exception>
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);
}
/// <summary>
/// Sets up a deferred invocation of font rebuilding, before the next render frame.
/// </summary>

View file

@ -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<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
{
var w = (IDalamudTextureWrap)Service<InterfaceManager>.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<TextureManager>.GetAsync();
return tm.NoThrottleGetFromImage(await File.ReadAllBytesAsync(this.path, cancellationToken));
}
}

View file

@ -70,10 +70,11 @@ internal sealed class GamePathSharableTexture : SharableTexture
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
{
var dm = await Service<DataManager>.GetAsync();
var im = await Service<InterfaceManager>.GetAsync();
var file = dm.GetFile<TexFile>(this.path);
var tm = await Service<TextureManager>.GetAsync();
if (dm.GetFile<TexFile>(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;
}

View file

@ -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);
/// <inheritdoc/>
@ -273,13 +276,46 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
/// <inheritdoc/>
public IDalamudTextureWrap GetFromRaw(
RawImageSpecification specs,
ReadOnlySpan<byte> bytes) =>
this.interfaceManager.LoadImageFromDxgiFormat(
bytes,
specs.Pitch,
specs.Width,
specs.Height,
(SharpDX.DXGI.Format)specs.DxgiFormat);
ReadOnlySpan<byte> bytes)
{
if (this.interfaceManager.Scene is not { } scene)
{
_ = Service<InterfaceManager.InterfaceManagerWithScene>.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));
}
/// <inheritdoc/>
public Task<IDalamudTextureWrap> GetFromRawAsync(
@ -325,12 +361,20 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.CreateLoader(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
_ => Task.FromResult<IDalamudTextureWrap>(this.interfaceManager.LoadImageFromTexFile(file)),
ct => Task.Run(() => this.NoThrottleGetFromTexFile(file), ct),
cancellationToken);
/// <inheritdoc/>
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<InterfaceManager.InterfaceManagerWithScene>.Get();
scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
}
return scene.Device.CheckFormatSupport((Format)dxgiFormat).HasFlag(FormatSupport.Texture2D);
}
/// <inheritdoc/>
public bool TryGetIconPath(in GameIconLookup lookup, out string path)
@ -443,6 +487,46 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
}
}
/// <summary>
/// Gets a texture from the given image. Skips the load throttler; intended to be used from implementation of
/// <see cref="SharableTexture"/>s.
/// </summary>
/// <param name="bytes">The data.</param>
/// <returns>The loaded texture.</returns>
internal IDalamudTextureWrap NoThrottleGetFromImage(ReadOnlyMemory<byte> bytes)
{
if (this.interfaceManager.Scene is not { } scene)
{
_ = Service<InterfaceManager.InterfaceManagerWithScene>.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."));
}
/// <summary>
/// Gets a texture from the given <see cref="TexFile"/>. Skips the load throttler; intended to be used from
/// implementation of <see cref="SharableTexture"/>s.
/// </summary>
/// <param name="file">The data.</param>
/// <returns>The loaded texture.</returns>
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;

View file

@ -269,33 +269,17 @@ internal class PluginImageCache : IDisposable, IServiceType
if (bytes == null)
return null;
var interfaceManager = (await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync()).Manager;
var framework = await Service<Framework>.GetAsync();
var textureManager = await Service<TextureManager>.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;
}

View file

@ -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<IDalamudTextureWrap>?[]? testerImages;
private Task<IDalamudTextureWrap>? 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<IDalamudTextureWrap>? 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<InterfaceManager>.Get();
var tm = Service<TextureManager>.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<IDalamudTextureWrap>?[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)

View file

@ -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<byte>();
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;

View file

@ -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<InterfaceManager.InterfaceManagerWithScene>
.GetAsync()
.ContinueWith(r => r.Result.Manager.Scene);
@ -144,6 +146,11 @@ internal sealed partial class FontAtlasFactory
/// </summary>
public InterfaceManager InterfaceManager { get; }
/// <summary>
/// Gets the service instance of <see cref="TextureManager"/>.
/// </summary>
public TextureManager TextureManager { get; }
/// <summary>
/// Gets the async task for <see cref="RawDX11Scene"/> inside <see cref="InterfaceManager"/>.
/// </summary>
@ -346,7 +353,7 @@ internal sealed partial class FontAtlasFactory
var numPixels = texFile.Header.Width * texFile.Header.Height;
_ = Service<InterfaceManager.InterfaceManagerWithScene>.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<byte>.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
{

View file

@ -379,6 +379,8 @@ public sealed class UiBuilder : IDisposable
private Task<InterfaceManager> InterfaceManagerWithSceneAsync =>
Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync().ContinueWith(task => task.Result.Manager);
private ITextureProvider TextureProvider => Service<TextureManager>.Get();
/// <summary>
/// Loads an image from the specified file.
/// </summary>
@ -386,9 +388,7 @@ public sealed class UiBuilder : IDisposable
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[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;
/// <summary>
/// Loads an image from a byte stream, such as a png downloaded into memory.
@ -397,9 +397,7 @@ public sealed class UiBuilder : IDisposable
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[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;
/// <summary>
/// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use <see cref="LoadImage(byte[])"/>.
@ -411,9 +409,12 @@ public sealed class UiBuilder : IDisposable
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[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(),
};
/// <summary>
/// Loads an ULD file that can load textures containing multiple icons in a single texture.
@ -430,10 +431,7 @@ public sealed class UiBuilder : IDisposable
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.GetFromFileAsync)}.")]
public Task<IDalamudTextureWrap> LoadImageAsync(string filePath) => Task.Run(
async () =>
(await this.InterfaceManagerWithSceneAsync).LoadImage(filePath)
?? throw new InvalidOperationException("Load failed."));
public Task<IDalamudTextureWrap> LoadImageAsync(string filePath) => this.TextureProvider.GetFromFileAsync(filePath);
/// <summary>
/// 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
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.GetFromImageAsync)}.")]
public Task<IDalamudTextureWrap> LoadImageAsync(byte[] imageData) => Task.Run(
async () =>
(await this.InterfaceManagerWithSceneAsync).LoadImage(imageData)
?? throw new InvalidOperationException("Load failed."));
public Task<IDalamudTextureWrap> LoadImageAsync(byte[] imageData) =>
this.TextureProvider.GetFromImageAsync(imageData);
/// <summary>
/// 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 <see cref="LoadImage(byte[])"/>.
@ -457,10 +453,12 @@ public sealed class UiBuilder : IDisposable
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.GetFromRawAsync)}.")]
public Task<IDalamudTextureWrap> 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<IDalamudTextureWrap> LoadImageRawAsync(byte[] imageData, int width, int height, int numChannels) =>
numChannels switch
{
4 => this.TextureProvider.GetFromRawAsync(RawImageSpecification.Rgba32(width, height), imageData),
_ => Task.FromException<IDalamudTextureWrap>(new NotSupportedException()),
};
/// <summary>
/// Waits for UI to become available for use.

View file

@ -213,4 +213,14 @@ public record struct RawImageSpecification(int Width, int Height, int Pitch, int
/// <returns>The new instance.</returns>
public static RawImageSpecification Rgba32(int width, int height) =>
new(width, height, width * 4, (int)DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM);
/// <summary>
/// Creates a new instance of <see cref="RawImageSpecification"/> record using the given resolution,
/// in A8 UNorm pixel format.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>The new instance.</returns>
public static RawImageSpecification A8(int width, int height) =>
new(width, height, width, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM);
}

View file

@ -302,17 +302,17 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
var buf = Array.Empty<byte>();
try
{
var im = (await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync()).Manager;
var tm = await Service<TextureManager>.GetAsync();
await using var stream = await this.CreateStreamAsync(asset);
var length = checked((int)stream.Length);
buf = ArrayPool<byte>.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<DalamudAssetRawTextureAttribute>() 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,

View file

@ -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
/// <param name="format">The format.</param>
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);
}
/// <summary>
/// Gets the width.
/// Gets the specification.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the pitch.
/// </summary>
public int Pitch { get; }
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the format.
/// </summary>
public Format Format { get; }
public RawImageSpecification Specification { get; }
}