mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-27 02:49:18 +01:00
Move all texture loading functionalities from IM to TM
This commit is contained in:
parent
248c7911a0
commit
71b84bcf40
12 changed files with 230 additions and 298 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue