diff --git a/Dalamud/Interface/Internal/TextureManager.Wic.cs b/Dalamud/Interface/Internal/TextureManager.Wic.cs
deleted file mode 100644
index 66be9ca58..000000000
--- a/Dalamud/Interface/Internal/TextureManager.Wic.cs
+++ /dev/null
@@ -1,478 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Dalamud.Interface.Internal.SharedImmediateTextures;
-using Dalamud.Plugin.Services;
-using Dalamud.Utility;
-
-using Lumina.Data;
-using Lumina.Data.Files;
-
-using TerraFX.Interop.DirectX;
-using TerraFX.Interop.Windows;
-
-using static TerraFX.Interop.Windows.Windows;
-
-namespace Dalamud.Interface.Internal;
-
-/// Service responsible for loading and disposing ImGui texture wraps.
-internal sealed partial class TextureManager
-{
- private ComPtr wicFactory;
-
- ///
- [SuppressMessage(
- "StyleCop.CSharp.LayoutRules",
- "SA1519:Braces should not be omitted from multi-line child statement",
- Justification = "Multiple fixed blocks")]
- public Task SaveAsImageFormatToStreamAsync(
- IDalamudTextureWrap wrap,
- string extension,
- Stream stream,
- bool leaveOpen = false,
- IReadOnlyDictionary? props = null,
- CancellationToken cancellationToken = default)
- {
- var container = GUID.GUID_ContainerFormatPng;
- foreach (var (k, v) in this.GetSupportedContainerFormats(WICComponentType.WICEncoder))
- {
- if (v.Contains(extension, StringComparer.InvariantCultureIgnoreCase))
- container = k;
- }
-
- return this.SaveToStreamUsingWicAsync(
- wrap,
- container,
- pbag =>
- {
- if (props is null)
- return;
- unsafe
- {
- var nprop = 0u;
- pbag.Get()->CountProperties(&nprop).ThrowOnError();
- for (var i = 0u; i < nprop; i++)
- {
- var pbag2 = default(PROPBAG2);
- var npropread = 0u;
- pbag.Get()->GetPropertyInfo(i, 1, &pbag2, &npropread).ThrowOnError();
- if (npropread == 0)
- continue;
- var propName = new string((char*)pbag2.pstrName);
- if (props.TryGetValue(propName, out var untypedValue))
- {
- VARIANT val;
- VariantInit(&val);
-
- switch (untypedValue)
- {
- case null:
- val.vt = (ushort)VARENUM.VT_EMPTY;
- break;
- case bool value:
- val.vt = (ushort)VARENUM.VT_BOOL;
- val.boolVal = (short)(value ? 1 : 0);
- break;
- case byte value:
- val.vt = (ushort)VARENUM.VT_UI1;
- val.bVal = value;
- break;
- case ushort value:
- val.vt = (ushort)VARENUM.VT_UI2;
- val.uiVal = value;
- break;
- case uint value:
- val.vt = (ushort)VARENUM.VT_UI4;
- val.uintVal = value;
- break;
- case ulong value:
- val.vt = (ushort)VARENUM.VT_UI8;
- val.ullVal = value;
- break;
- case sbyte value:
- val.vt = (ushort)VARENUM.VT_I1;
- val.cVal = value;
- break;
- case short value:
- val.vt = (ushort)VARENUM.VT_I2;
- val.iVal = value;
- break;
- case int value:
- val.vt = (ushort)VARENUM.VT_I4;
- val.intVal = value;
- break;
- case long value:
- val.vt = (ushort)VARENUM.VT_I8;
- val.llVal = value;
- break;
- case float value:
- val.vt = (ushort)VARENUM.VT_R4;
- val.fltVal = value;
- break;
- case double value:
- val.vt = (ushort)VARENUM.VT_R8;
- val.dblVal = value;
- break;
- default:
- VariantClear(&val);
- continue;
- }
-
- VariantChangeType(&val, &val, 0, pbag2.vt).ThrowOnError();
- pbag.Get()->Write(1, &pbag2, &val).ThrowOnError();
- VariantClear(&val);
- }
-
- CoTaskMemFree(pbag2.pstrName);
- }
- }
- },
- stream,
- leaveOpen,
- cancellationToken);
- }
-
- ///
- public IEnumerable GetLoadSupportedImageExtensions() =>
- this.GetSupportedContainerFormats(WICComponentType.WICDecoder).Values;
-
- ///
- public IEnumerable GetSaveSupportedImageExtensions() =>
- this.GetSupportedContainerFormats(WICComponentType.WICEncoder).Values;
-
- /// Creates a texture from the given bytes of an image file. Skips the load throttler; intended to be used
- /// from implementation of s.
- /// The data.
- /// The cancellation token.
- /// The loaded texture.
- internal unsafe IDalamudTextureWrap NoThrottleCreateFromImage(
- ReadOnlyMemory bytes,
- CancellationToken cancellationToken = default)
- {
- ObjectDisposedException.ThrowIf(this.disposing, this);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (TexFileExtensions.IsPossiblyTexFile2D(bytes.Span))
- {
- var bytesArray = bytes.ToArray();
- var tf = new TexFile();
- typeof(TexFile).GetProperty(nameof(tf.Data))!.GetSetMethod(true)!.Invoke(
- tf,
- new object?[] { bytesArray });
- typeof(TexFile).GetProperty(nameof(tf.Reader))!.GetSetMethod(true)!.Invoke(
- tf,
- new object?[] { new LuminaBinaryReader(bytesArray) });
- // Note: FileInfo and FilePath are not used from TexFile; skip it.
- try
- {
- return this.NoThrottleCreateFromTexFile(tf);
- }
- catch (Exception)
- {
- // ignore
- }
- }
-
- fixed (byte* p = bytes.Span)
- {
- using var wicStream = default(ComPtr);
- this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
- wicStream.Get()->InitializeFromMemory(p, checked((uint)bytes.Length)).ThrowOnError();
- return this.NoThrottleCreateFromWicStream((IStream*)wicStream.Get(), cancellationToken);
- }
- }
-
- /// Creates a texture from the given path to an image file. Skips the load throttler; intended to be used
- /// from implementation of s.
- /// The path of the file..
- /// The cancellation token.
- /// The loaded texture.
- internal async Task NoThrottleCreateFromFileAsync(
- string path,
- CancellationToken cancellationToken = default)
- {
- ObjectDisposedException.ThrowIf(this.disposing, this);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- try
- {
- unsafe
- {
- fixed (char* pPath = path)
- {
- using var wicStream = default(ComPtr);
- this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
- wicStream.Get()->InitializeFromFilename((ushort*)pPath, GENERIC_READ).ThrowOnError();
- return this.NoThrottleCreateFromWicStream((IStream*)wicStream.Get(), cancellationToken);
- }
- }
- }
- catch
- {
- try
- {
- await using var fp = File.OpenRead(path);
- if (fp.Length >= Unsafe.SizeOf())
- {
- var bytesArray = new byte[fp.Length];
- await fp.ReadExactlyAsync(bytesArray, cancellationToken);
- if (TexFileExtensions.IsPossiblyTexFile2D(bytesArray))
- {
- var tf = new TexFile();
- typeof(TexFile).GetProperty(nameof(tf.Data))!.GetSetMethod(true)!.Invoke(
- tf,
- new object?[] { bytesArray });
- typeof(TexFile).GetProperty(nameof(tf.Reader))!.GetSetMethod(true)!.Invoke(
- tf,
- new object?[] { new LuminaBinaryReader(bytesArray) });
- // Note: FileInfo and FilePath are not used from TexFile; skip it.
- return this.NoThrottleCreateFromTexFile(tf);
- }
- }
- }
- catch (Exception)
- {
- // ignore
- }
-
- throw;
- }
- }
-
- ///
- /// Gets the corresponding from a containing a WIC pixel format.
- ///
- /// The WIC pixel format.
- /// The corresponding , or if
- /// unavailable.
- private static DXGI_FORMAT GetCorrespondingDxgiFormat(Guid fmt) => 0 switch
- {
- // See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile
- _ when fmt == GUID.GUID_WICPixelFormat128bppRGBAFloat => DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT,
- _ when fmt == GUID.GUID_WICPixelFormat64bppRGBAHalf => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT,
- _ when fmt == GUID.GUID_WICPixelFormat64bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102XR => DXGI_FORMAT.DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102 => DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat16bppBGRA5551 => DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat16bppBGR565 => DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppGrayFloat => DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT,
- _ when fmt == GUID.GUID_WICPixelFormat16bppGrayHalf => DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT,
- _ when fmt == GUID.GUID_WICPixelFormat16bppGray => DXGI_FORMAT.DXGI_FORMAT_R16_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat8bppGray => DXGI_FORMAT.DXGI_FORMAT_R8_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat8bppAlpha => DXGI_FORMAT.DXGI_FORMAT_A8_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppBGRA => DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppBGR => DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM,
- _ => DXGI_FORMAT.DXGI_FORMAT_UNKNOWN,
- };
-
- private unsafe IDalamudTextureWrap NoThrottleCreateFromWicStream(
- IStream* wicStream,
- CancellationToken cancellationToken = default)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- using var decoder = default(ComPtr);
- this.wicFactory.Get()->CreateDecoderFromStream(
- wicStream,
- null,
- WICDecodeOptions.WICDecodeMetadataCacheOnDemand,
- decoder.GetAddressOf()).ThrowOnError();
-
- cancellationToken.ThrowIfCancellationRequested();
-
- using var frame = default(ComPtr);
- decoder.Get()->GetFrame(0, frame.GetAddressOf()).ThrowOnError();
- var pixelFormat = default(Guid);
- frame.Get()->GetPixelFormat(&pixelFormat).ThrowOnError();
- var dxgiFormat = GetCorrespondingDxgiFormat(pixelFormat);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- using var bitmapSource = default(ComPtr);
- if (dxgiFormat == DXGI_FORMAT.DXGI_FORMAT_UNKNOWN || !this.IsDxgiFormatSupported(dxgiFormat))
- {
- dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
- pixelFormat = GUID.GUID_WICPixelFormat32bppBGRA;
- WICConvertBitmapSource(&pixelFormat, (IWICBitmapSource*)frame.Get(), bitmapSource.GetAddressOf())
- .ThrowOnError();
- }
- else
- {
- frame.As(&bitmapSource);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- using var bitmap = default(ComPtr);
- using var bitmapLock = default(ComPtr);
- WICRect rcLock;
- uint stride;
- uint cbBufferSize;
- byte* pbData;
- if (bitmapSource.As(&bitmap).FAILED)
- {
- bitmapSource.Get()->GetSize((uint*)&rcLock.Width, (uint*)&rcLock.Height).ThrowOnError();
- this.wicFactory.Get()->CreateBitmap(
- (uint)rcLock.Width,
- (uint)rcLock.Height,
- &pixelFormat,
- WICBitmapCreateCacheOption.WICBitmapCacheOnDemand,
- bitmap.GetAddressOf()).ThrowOnError();
-
- bitmap.Get()->Lock(
- &rcLock,
- (uint)WICBitmapLockFlags.WICBitmapLockWrite,
- bitmapLock.ReleaseAndGetAddressOf())
- .ThrowOnError();
- bitmapLock.Get()->GetStride(&stride).ThrowOnError();
- bitmapLock.Get()->GetDataPointer(&cbBufferSize, &pbData).ThrowOnError();
- bitmapSource.Get()->CopyPixels(null, stride, cbBufferSize, pbData).ThrowOnError();
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- bitmap.Get()->Lock(
- &rcLock,
- (uint)WICBitmapLockFlags.WICBitmapLockRead,
- bitmapLock.ReleaseAndGetAddressOf())
- .ThrowOnError();
- bitmapSource.Get()->GetSize((uint*)&rcLock.Width, (uint*)&rcLock.Height).ThrowOnError();
- bitmapLock.Get()->GetStride(&stride).ThrowOnError();
- bitmapLock.Get()->GetDataPointer(&cbBufferSize, &pbData).ThrowOnError();
- bitmapSource.Get()->CopyPixels(null, stride, cbBufferSize, pbData).ThrowOnError();
- return this.NoThrottleCreateFromRaw(
- new RawImageSpecification(rcLock.Width, rcLock.Height, (int)stride, (int)dxgiFormat),
- new(pbData, (int)cbBufferSize));
- }
-
- [SuppressMessage(
- "StyleCop.CSharp.LayoutRules",
- "SA1519:Braces should not be omitted from multi-line child statement",
- Justification = "Multiple fixed blocks")]
- private unsafe Dictionary GetSupportedContainerFormats(WICComponentType componentType)
- {
- var result = new Dictionary();
- using var enumUnknown = default(ComPtr);
- this.wicFactory.Get()->CreateComponentEnumerator(
- (uint)componentType,
- (uint)WICComponentEnumerateOptions.WICComponentEnumerateDefault,
- enumUnknown.GetAddressOf()).ThrowOnError();
-
- while (true)
- {
- using var entry = default(ComPtr);
- var fetched = 0u;
- enumUnknown.Get()->Next(1, entry.GetAddressOf(), &fetched).ThrowOnError();
- if (fetched == 0)
- break;
-
- using var codecInfo = default(ComPtr);
- if (entry.As(&codecInfo).FAILED)
- continue;
-
- Guid containerFormat;
- if (codecInfo.Get()->GetContainerFormat(&containerFormat).FAILED)
- continue;
-
- var cch = 0u;
- _ = codecInfo.Get()->GetFileExtensions(0, null, &cch);
- var buf = new char[(int)cch + 1];
- fixed (char* pBuf = buf)
- {
- if (codecInfo.Get()->GetFileExtensions(cch + 1, (ushort*)pBuf, &cch).FAILED)
- continue;
- }
-
- result.Add(containerFormat, new string(buf, 0, buf.IndexOf('\0')).Split(","));
- }
-
- return result;
- }
-
- [SuppressMessage(
- "StyleCop.CSharp.LayoutRules",
- "SA1519:Braces should not be omitted from multi-line child statement",
- Justification = "Multiple fixed blocks")]
- private async Task SaveToStreamUsingWicAsync(
- IDalamudTextureWrap wrap,
- Guid containerFormat,
- Action> propertyBackSetterDelegate,
- Stream stream,
- bool leaveOpen = false,
- CancellationToken cancellationToken = default)
- {
- using var wrapCopy = wrap.CreateWrapSharingLowLevelResource();
- await using var streamCloser = leaveOpen ? null : stream;
-
- var (specs, bytes) = await this.GetRawDataAsync(
- wrapCopy,
- Vector2.Zero,
- Vector2.One,
- DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
- cancellationToken).ConfigureAwait(false);
-
- using var encoder = default(ComPtr);
- using var encoderFrame = default(ComPtr);
- using var wrappedStream = new ManagedIStream(stream);
- var guidPixelFormat = GUID.GUID_WICPixelFormat32bppBGRA;
- unsafe
- {
- this.wicFactory.Get()->CreateEncoder(&containerFormat, null, encoder.GetAddressOf()).ThrowOnError();
- cancellationToken.ThrowIfCancellationRequested();
-
- encoder.Get()->Initialize(wrappedStream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache)
- .ThrowOnError();
- cancellationToken.ThrowIfCancellationRequested();
-
- using var propertyBag = default(ComPtr);
- encoder.Get()->CreateNewFrame(encoderFrame.GetAddressOf(), propertyBag.GetAddressOf()).ThrowOnError();
- cancellationToken.ThrowIfCancellationRequested();
-
- propertyBackSetterDelegate.Invoke(propertyBag);
- encoderFrame.Get()->Initialize(propertyBag).ThrowOnError();
- cancellationToken.ThrowIfCancellationRequested();
-
- encoderFrame.Get()->SetPixelFormat(&guidPixelFormat).ThrowOnError();
- encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
-
- using var tempBitmap = default(ComPtr);
- fixed (Guid* pGuid = &GUID.GUID_WICPixelFormat32bppBGRA)
- fixed (byte* pBytes = bytes)
- {
- this.wicFactory.Get()->CreateBitmapFromMemory(
- (uint)specs.Width,
- (uint)specs.Height,
- pGuid,
- (uint)specs.Pitch,
- checked((uint)bytes.Length),
- pBytes,
- tempBitmap.GetAddressOf()).ThrowOnError();
- }
-
- using var tempBitmap2 = default(ComPtr);
- WICConvertBitmapSource(
- &guidPixelFormat,
- (IWICBitmapSource*)tempBitmap.Get(),
- tempBitmap2.GetAddressOf()).ThrowOnError();
-
- encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
- encoderFrame.Get()->WriteSource(tempBitmap2.Get(), null).ThrowOnError();
-
- cancellationToken.ThrowIfCancellationRequested();
-
- encoderFrame.Get()->Commit().ThrowOnError();
- cancellationToken.ThrowIfCancellationRequested();
-
- encoder.Get()->Commit().ThrowOnError();
- }
- }
-}
diff --git a/Dalamud/Interface/Internal/TextureManager.cs b/Dalamud/Interface/Internal/TextureManager.cs
deleted file mode 100644
index ce9f2a22d..000000000
--- a/Dalamud/Interface/Internal/TextureManager.cs
+++ /dev/null
@@ -1,582 +0,0 @@
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-using BitFaster.Caching.Lru;
-
-using Dalamud.Data;
-using Dalamud.Game;
-using Dalamud.Interface.Internal.SharedImmediateTextures;
-using Dalamud.IoC;
-using Dalamud.IoC.Internal;
-using Dalamud.Logging.Internal;
-using Dalamud.Plugin.Services;
-using Dalamud.Utility;
-
-using Lumina.Data.Files;
-
-using SharpDX;
-using SharpDX.Direct3D;
-using SharpDX.Direct3D11;
-using SharpDX.DXGI;
-
-using TerraFX.Interop.DirectX;
-using TerraFX.Interop.Windows;
-
-using static TerraFX.Interop.Windows.Windows;
-
-namespace Dalamud.Interface.Internal;
-
-/// Service responsible for loading and disposing ImGui texture wraps.
-[PluginInterface]
-[InterfaceVersion("1.0")]
-[ServiceManager.EarlyLoadedService]
-#pragma warning disable SA1015
-[ResolveVia]
-[ResolveVia]
-#pragma warning restore SA1015
-internal sealed partial class TextureManager : IServiceType, IDisposable, ITextureProvider, ITextureSubstitutionProvider
-{
- private const int PathLookupLruCount = 8192;
-
- private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
- private const string HighResolutionIconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}_hr1.tex";
-
- private static readonly ModuleLog Log = new(nameof(TextureManager));
-
- [ServiceManager.ServiceDependency]
- private readonly Dalamud dalamud = Service.Get();
-
- [ServiceManager.ServiceDependency]
- private readonly DataManager dataManager = Service.Get();
-
- [ServiceManager.ServiceDependency]
- private readonly Framework framework = Service.Get();
-
- [ServiceManager.ServiceDependency]
- private readonly InterfaceManager interfaceManager = Service.Get();
-
- [ServiceManager.ServiceDependency]
- private readonly TextureLoadThrottler textureLoadThrottler = Service.Get();
-
- private readonly ConcurrentLru lookupToPath = new(PathLookupLruCount);
-
- private readonly ConcurrentDictionary gamePathTextures = new();
-
- private readonly ConcurrentDictionary fileSystemTextures = new();
-
- private readonly ConcurrentDictionary<(Assembly Assembly, string Name), SharedImmediateTexture>
- manifestResourceTextures = new();
-
- private readonly HashSet invalidatedTextures = new();
-
- private bool disposing;
-
- [SuppressMessage(
- "StyleCop.CSharp.LayoutRules",
- "SA1519:Braces should not be omitted from multi-line child statement",
- Justification = "Multiple fixed blocks")]
- [ServiceManager.ServiceConstructor]
- private TextureManager()
- {
- this.framework.Update += this.FrameworkOnUpdate;
- unsafe
- {
- fixed (Guid* pclsidWicImagingFactory = &CLSID.CLSID_WICImagingFactory)
- fixed (Guid* piidWicImagingFactory = &IID.IID_IWICImagingFactory)
- {
- CoCreateInstance(
- pclsidWicImagingFactory,
- null,
- (uint)CLSCTX.CLSCTX_INPROC_SERVER,
- piidWicImagingFactory,
- (void**)this.wicFactory.GetAddressOf()).ThrowOnError();
- }
- }
- }
-
- /// Finalizes an instance of the class.
- ~TextureManager() => this.Dispose();
-
- ///
- public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
-
- /// Gets all the loaded textures from game resources.
- public ICollection GamePathTexturesForDebug => this.gamePathTextures.Values;
-
- /// Gets all the loaded textures from filesystem.
- public ICollection FileSystemTexturesForDebug => this.fileSystemTextures.Values;
-
- /// Gets all the loaded textures from assembly manifest resources.
- public ICollection ManifestResourceTexturesForDebug => this.manifestResourceTextures.Values;
-
- /// Gets all the loaded textures that are invalidated from .
- /// lock on use of the value returned from this property.
- [SuppressMessage(
- "ReSharper",
- "InconsistentlySynchronizedField",
- Justification = "Debug use only; users are expected to lock around this")]
- public ICollection InvalidatedTexturesForDebug => this.invalidatedTextures;
-
- ///
- public void Dispose()
- {
- if (this.disposing)
- return;
-
- this.disposing = true;
-
- ReleaseSelfReferences(this.gamePathTextures);
- ReleaseSelfReferences(this.fileSystemTextures);
- ReleaseSelfReferences(this.manifestResourceTextures);
- this.lookupToPath.Clear();
-
- this.drawsOneSquare?.Dispose();
- this.drawsOneSquare = null;
-
- this.wicFactory.Reset();
-
- return;
-
- static void ReleaseSelfReferences(ConcurrentDictionary dict)
- {
- foreach (var v in dict.Values)
- v.ReleaseSelfReference(true);
- dict.Clear();
- }
- }
-
- #region API9 compat
-
-#pragma warning disable CS0618 // Type or member is obsolete
- ///
- [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
- [Obsolete("See interface definition.")]
- string? ITextureProvider.GetIconPath(uint iconId, ITextureProvider.IconFlags flags, ClientLanguage? language)
- => this.TryGetIconPath(
- new(
- iconId,
- (flags & ITextureProvider.IconFlags.ItemHighQuality) != 0,
- (flags & ITextureProvider.IconFlags.HiRes) != 0,
- language),
- out var path)
- ? path
- : null;
-
- ///
- [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
- [Obsolete("See interface definition.")]
- IDalamudTextureWrap? ITextureProvider.GetIcon(
- uint iconId,
- ITextureProvider.IconFlags flags,
- ClientLanguage? language,
- bool keepAlive) =>
- this.GetFromGameIcon(
- new(
- iconId,
- (flags & ITextureProvider.IconFlags.ItemHighQuality) != 0,
- (flags & ITextureProvider.IconFlags.HiRes) != 0,
- language))
- .GetAvailableOnAccessWrapForApi9();
-
- ///
- [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
- [Obsolete("See interface definition.")]
- IDalamudTextureWrap? ITextureProvider.GetTextureFromGame(string path, bool keepAlive) =>
- this.GetFromGame(path).GetAvailableOnAccessWrapForApi9();
-
- ///
- [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
- [Obsolete("See interface definition.")]
- IDalamudTextureWrap? ITextureProvider.GetTextureFromFile(FileInfo file, bool keepAlive) =>
- this.GetFromFile(file.FullName).GetAvailableOnAccessWrapForApi9();
-#pragma warning restore CS0618 // Type or member is obsolete
-
- #endregion
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) =>
- this.GetFromGame(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue));
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public SharedImmediateTexture GetFromGame(string path)
- {
- ObjectDisposedException.ThrowIf(this.disposing, this);
- return this.gamePathTextures.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public SharedImmediateTexture GetFromFile(string path)
- {
- ObjectDisposedException.ThrowIf(this.disposing, this);
- return this.fileSystemTextures.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public SharedImmediateTexture GetFromManifestResource(Assembly assembly, string name)
- {
- ObjectDisposedException.ThrowIf(this.disposing, this);
- return this.manifestResourceTextures.GetOrAdd(
- (assembly, name),
- ManifestResourceSharedImmediateTexture.CreatePlaceholder);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- ISharedImmediateTexture ITextureProvider.GetFromGameIcon(in GameIconLookup lookup) => this.GetFromGameIcon(lookup);
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- ISharedImmediateTexture ITextureProvider.GetFromGame(string path) => this.GetFromGame(path);
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- ISharedImmediateTexture ITextureProvider.GetFromFile(string path) => this.GetFromFile(path);
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- ISharedImmediateTexture ITextureProvider.GetFromManifestResource(Assembly assembly, string name) =>
- this.GetFromManifestResource(assembly, name);
-
- ///
- public Task CreateFromImageAsync(
- ReadOnlyMemory bytes,
- CancellationToken cancellationToken = default) =>
- this.textureLoadThrottler.LoadTextureAsync(
- new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
- ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct),
- cancellationToken);
-
- ///
- public Task CreateFromImageAsync(
- Stream stream,
- bool leaveOpen = false,
- CancellationToken cancellationToken = default) =>
- this.textureLoadThrottler.LoadTextureAsync(
- new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
- async ct =>
- {
- await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
- await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
- return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct);
- },
- cancellationToken)
- .ContinueWith(
- r =>
- {
- if (!leaveOpen)
- stream.Dispose();
- return r;
- },
- default(CancellationToken))
- .Unwrap();
-
- ///
- // It probably doesn't make sense to throttle this, as it copies the passed bytes to GPU without any transformation.
- public IDalamudTextureWrap CreateFromRaw(
- RawImageSpecification specs,
- ReadOnlySpan bytes) => this.NoThrottleCreateFromRaw(specs, bytes);
-
- ///
- public Task CreateFromRawAsync(
- RawImageSpecification specs,
- ReadOnlyMemory bytes,
- CancellationToken cancellationToken = default) =>
- this.textureLoadThrottler.LoadTextureAsync(
- new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
- _ => Task.FromResult(this.NoThrottleCreateFromRaw(specs, bytes.Span)),
- cancellationToken);
-
- ///
- public Task CreateFromRawAsync(
- RawImageSpecification specs,
- Stream stream,
- bool leaveOpen = false,
- CancellationToken cancellationToken = default) =>
- this.textureLoadThrottler.LoadTextureAsync(
- new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
- async ct =>
- {
- await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
- await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
- return this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length));
- },
- cancellationToken)
- .ContinueWith(
- r =>
- {
- if (!leaveOpen)
- stream.Dispose();
- return r;
- },
- default(CancellationToken))
- .Unwrap();
-
- ///
- public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result;
-
- ///
- public Task CreateFromTexFileAsync(
- TexFile file,
- CancellationToken cancellationToken = default) =>
- this.textureLoadThrottler.LoadTextureAsync(
- new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
- ct => Task.Run(() => this.NoThrottleCreateFromTexFile(file), ct),
- cancellationToken);
-
- ///
- bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
- this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
-
- ///
- public bool IsDxgiFormatSupported(DXGI_FORMAT dxgiFormat)
- {
- if (this.interfaceManager.Scene is not { } scene)
- {
- _ = Service.Get();
- scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
- }
-
- var format = (Format)dxgiFormat;
- var support = scene.Device.CheckFormatSupport(format);
- const FormatSupport required = FormatSupport.Texture2D;
- return (support & required) == required;
- }
-
- ///
- public bool TryGetIconPath(in GameIconLookup lookup, out string path)
- {
- // 1. Item
- path = FormatIconPath(
- lookup.IconId,
- lookup.ItemHq ? "hq/" : string.Empty,
- lookup.HiRes);
- if (this.dataManager.FileExists(path))
- return true;
-
- var languageFolder = (lookup.Language ?? (ClientLanguage)(int)this.dalamud.StartInfo.Language) switch
- {
- ClientLanguage.Japanese => "ja/",
- ClientLanguage.English => "en/",
- ClientLanguage.German => "de/",
- ClientLanguage.French => "fr/",
- _ => null,
- };
-
- if (languageFolder is not null)
- {
- // 2. Regular icon, with language, hi-res
- path = FormatIconPath(
- lookup.IconId,
- languageFolder,
- lookup.HiRes);
- if (this.dataManager.FileExists(path))
- return true;
-
- if (lookup.HiRes)
- {
- // 3. Regular icon, with language, no hi-res
- path = FormatIconPath(
- lookup.IconId,
- languageFolder,
- false);
- if (this.dataManager.FileExists(path))
- return true;
- }
- }
-
- // 4. Regular icon, without language, hi-res
- path = FormatIconPath(
- lookup.IconId,
- null,
- lookup.HiRes);
- if (this.dataManager.FileExists(path))
- return true;
-
- // 4. Regular icon, without language, no hi-res
- if (lookup.HiRes)
- {
- path = FormatIconPath(
- lookup.IconId,
- null,
- false);
- if (this.dataManager.FileExists(path))
- return true;
- }
-
- return false;
- }
-
- ///
- public string GetIconPath(in GameIconLookup lookup) =>
- this.TryGetIconPath(lookup, out var path) ? path : throw new FileNotFoundException();
-
- ///
- public string GetSubstitutedPath(string originalPath)
- {
- if (this.InterceptTexDataLoad == null)
- return originalPath;
-
- string? interceptPath = null;
- this.InterceptTexDataLoad.Invoke(originalPath, ref interceptPath);
-
- if (interceptPath != null)
- {
- Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", originalPath, interceptPath);
- return interceptPath;
- }
-
- return originalPath;
- }
-
- ///
- public void InvalidatePaths(IEnumerable paths)
- {
- foreach (var path in paths)
- {
- if (this.gamePathTextures.TryRemove(path, out var r))
- {
- if (r.ReleaseSelfReference(true) != 0 || r.HasRevivalPossibility)
- {
- lock (this.invalidatedTextures)
- this.invalidatedTextures.Add(r);
- }
- }
-
- if (this.fileSystemTextures.TryRemove(path, out r))
- {
- if (r.ReleaseSelfReference(true) != 0 || r.HasRevivalPossibility)
- {
- lock (this.invalidatedTextures)
- this.invalidatedTextures.Add(r);
- }
- }
- }
- }
-
- ///
- internal IDalamudTextureWrap NoThrottleCreateFromRaw(
- RawImageSpecification specs,
- 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));
- }
-
- /// Creates a texture from the given . Skips the load throttler; intended to be used
- /// from implementation of s.
- /// The data.
- /// The loaded texture.
- internal IDalamudTextureWrap NoThrottleCreateFromTexFile(TexFile file)
- {
- ObjectDisposedException.ThrowIf(this.disposing, this);
-
- var buffer = file.TextureBuffer;
- var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false);
- if (conversion != TexFile.DxgiFormatConversion.NoConversion ||
- !this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat))
- {
- dxgiFormat = (int)Format.B8G8R8A8_UNorm;
- buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8);
- }
-
- return this.NoThrottleCreateFromRaw(
- RawImageSpecification.From(buffer.Width, buffer.Height, dxgiFormat),
- buffer.RawData);
- }
-
- private static string FormatIconPath(uint iconId, string? type, bool highResolution)
- {
- var format = highResolution ? HighResolutionIconFileFormat : IconFileFormat;
-
- type ??= string.Empty;
- if (type.Length > 0 && !type.EndsWith("/"))
- type += "/";
-
- return string.Format(format, iconId / 1000, type, iconId);
- }
-
- private void FrameworkOnUpdate(IFramework unused)
- {
- RemoveFinalReleased(this.gamePathTextures);
- RemoveFinalReleased(this.fileSystemTextures);
- RemoveFinalReleased(this.manifestResourceTextures);
-
- // ReSharper disable once InconsistentlySynchronizedField
- if (this.invalidatedTextures.Count != 0)
- {
- lock (this.invalidatedTextures)
- this.invalidatedTextures.RemoveWhere(TextureFinalReleasePredicate);
- }
-
- return;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- static void RemoveFinalReleased(ConcurrentDictionary dict)
- {
- if (!dict.IsEmpty)
- {
- foreach (var (k, v) in dict)
- {
- if (TextureFinalReleasePredicate(v))
- _ = dict.TryRemove(k, out _);
- }
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- static bool TextureFinalReleasePredicate(SharedImmediateTexture v) =>
- v.ContentQueried && v.ReleaseSelfReference(false) == 0 && !v.HasRevivalPossibility;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private string GetIconPathByValue(GameIconLookup lookup) =>
- this.TryGetIconPath(lookup, out var path) ? path : throw new FileNotFoundException();
-}
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs
index 1f4e5f29d..b7b897e68 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs
@@ -3,6 +3,7 @@ using System.Numerics;
using System.Threading.Tasks;
using Dalamud.Interface.Colors;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Utility;
using ImGuiNET;
@@ -141,7 +142,7 @@ public class IconBrowserWidget : IDataWindowWidget
var texm = Service.Get();
var cursor = ImGui.GetCursorScreenPos();
- if (texm.GetFromGameIcon(new((uint)iconId)).TryGetWrap(out var texture, out var exc))
+ if (texm.Shared.GetFromGameIcon(new((uint)iconId)).TryGetWrap(out var texture, out var exc))
{
ImGui.Image(texture.ImGuiHandle, this.iconSize);
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
index 45a3a5331..6779d0d60 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
@@ -9,7 +9,8 @@ using System.Threading.Tasks;
using Dalamud.Interface.Components;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications;
-using Dalamud.Interface.Internal.SharedImmediateTextures;
+using Dalamud.Interface.Textures.Internal;
+using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services;
using Dalamud.Storage.Assets;
@@ -21,6 +22,8 @@ using Serilog;
using TerraFX.Interop.DirectX;
+using TextureManager = Dalamud.Interface.Textures.Internal.TextureManager;
+
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
///
@@ -92,29 +95,29 @@ internal class TexWidget : IDataWindowWidget
ImGui.PushID("loadedGameTextures");
if (ImGui.CollapsingHeader(
- $"Loaded Game Textures: {this.textureManager.GamePathTexturesForDebug.Count:g}###header"))
- this.DrawLoadedTextures(this.textureManager.GamePathTexturesForDebug);
+ $"Loaded Game Textures: {this.textureManager.Shared.ForDebugGamePathTextures.Count:g}###header"))
+ this.DrawLoadedTextures(this.textureManager.Shared.ForDebugGamePathTextures);
ImGui.PopID();
ImGui.PushID("loadedFileTextures");
if (ImGui.CollapsingHeader(
- $"Loaded File Textures: {this.textureManager.FileSystemTexturesForDebug.Count:g}###header"))
- this.DrawLoadedTextures(this.textureManager.FileSystemTexturesForDebug);
+ $"Loaded File Textures: {this.textureManager.Shared.ForDebugFileSystemTextures.Count:g}###header"))
+ this.DrawLoadedTextures(this.textureManager.Shared.ForDebugFileSystemTextures);
ImGui.PopID();
ImGui.PushID("loadedManifestResourceTextures");
if (ImGui.CollapsingHeader(
- $"Loaded Manifest Resource Textures: {this.textureManager.ManifestResourceTexturesForDebug.Count:g}###header"))
- this.DrawLoadedTextures(this.textureManager.ManifestResourceTexturesForDebug);
+ $"Loaded Manifest Resource Textures: {this.textureManager.Shared.ForDebugManifestResourceTextures.Count:g}###header"))
+ this.DrawLoadedTextures(this.textureManager.Shared.ForDebugManifestResourceTextures);
ImGui.PopID();
- lock (this.textureManager.InvalidatedTexturesForDebug)
+ lock (this.textureManager.Shared.ForDebugInvalidatedTextures)
{
ImGui.PushID("invalidatedTextures");
if (ImGui.CollapsingHeader(
- $"Invalidated: {this.textureManager.InvalidatedTexturesForDebug.Count:g}###header"))
+ $"Invalidated: {this.textureManager.Shared.ForDebugInvalidatedTextures.Count:g}###header"))
{
- this.DrawLoadedTextures(this.textureManager.InvalidatedTexturesForDebug);
+ this.DrawLoadedTextures(this.textureManager.Shared.ForDebugInvalidatedTextures);
}
ImGui.PopID();
@@ -192,20 +195,9 @@ internal class TexWidget : IDataWindowWidget
ImGui.SameLine();
if (ImGui.Button("Save"))
{
- this.fileDialogManager.SaveFileDialog(
- "Save texture...",
- string.Join(
- ',',
- this.textureManager
- .GetSaveSupportedImageExtensions()
- .Select(x => $"{string.Join(" | ", x)}{{{string.Join(',', x)}}}")),
- $"Texture {t.Id}.png",
- ".png",
- (ok, path) =>
- {
- if (ok && t.GetTexture(this.textureManager) is { } source)
- Task.Run(() => this.SaveTextureWrap(source, path));
- });
+ this.SaveTextureAsync(
+ $"Texture {t.Id}",
+ () => t.CreateNewTextureWrapReference(this.textureManager));
}
ImGui.SameLine();
@@ -244,7 +236,7 @@ internal class TexWidget : IDataWindowWidget
}
else
{
- ImGui.TextUnformatted(t.DescribeError());
+ ImGui.TextUnformatted(t.DescribeError() ?? "Loading");
}
}
catch (Exception e)
@@ -343,20 +335,8 @@ internal class TexWidget : IDataWindowWidget
ImGui.TableNextColumn();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
{
- this.fileDialogManager.SaveFileDialog(
- "Save texture...",
- string.Join(
- ',',
- this.textureManager
- .GetSaveSupportedImageExtensions()
- .Select(x => $"{string.Join(" | ", x)}{{{string.Join(',', x)}}}")),
- Path.ChangeExtension(Path.GetFileName(texture.SourcePathForDebug), ".png"),
- ".png",
- (ok, path) =>
- {
- if (ok)
- Task.Run(() => this.SaveImmediateTexture(texture, path));
- });
+ var name = Path.ChangeExtension(Path.GetFileName(texture.SourcePathForDebug), null);
+ this.SaveTextureAsync(name, () => texture.RentAsync());
}
if (ImGui.IsItemHovered() && texture.GetWrapOrDefault(null) is { } immediate)
@@ -394,53 +374,6 @@ internal class TexWidget : IDataWindowWidget
ImGuiHelpers.ScaledDummy(10);
}
- private async void SaveImmediateTexture(ISharedImmediateTexture texture, string path)
- {
- try
- {
- using var rented = await texture.RentAsync();
- this.SaveTextureWrap(rented, path);
- }
- catch (Exception e)
- {
- Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveImmediateTexture)}");
- Service.Get().AddNotification(
- $"Failed to save file: {e}",
- this.DisplayName,
- NotificationType.Error);
- }
- }
-
- private async void SaveTextureWrap(IDalamudTextureWrap texture, string path)
- {
- try
- {
- await this.textureManager.SaveAsImageFormatToStreamAsync(
- texture,
- Path.GetExtension(path),
- File.Create(path),
- props: new Dictionary
- {
- ["CompressionQuality"] = 1.0f,
- ["ImageQuality"] = 1.0f,
- });
- }
- catch (Exception e)
- {
- Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveImmediateTexture)}");
- Service.Get().AddNotification(
- $"Failed to save file: {e}",
- this.DisplayName,
- NotificationType.Error);
- return;
- }
-
- Service.Get().AddNotification(
- $"File saved to: {path}",
- this.DisplayName,
- NotificationType.Success);
- }
-
private void DrawGetFromGameIcon()
{
ImGui.InputText("Icon ID", ref this.iconId, 32);
@@ -464,6 +397,7 @@ internal class TexWidget : IDataWindowWidget
this.addedTextures.Add(
new(
Api10: this.textureManager
+ .Shared
.GetFromGameIcon(new(uint.Parse(this.iconId), this.hq, this.hiRes))
.RentAsync()));
}
@@ -486,7 +420,7 @@ internal class TexWidget : IDataWindowWidget
ImGui.SameLine();
if (ImGui.Button("Load Tex (Async)"))
- this.addedTextures.Add(new(Api10: this.textureManager.GetFromGame(this.inputTexPath).RentAsync()));
+ this.addedTextures.Add(new(Api10: this.textureManager.Shared.GetFromGame(this.inputTexPath).RentAsync()));
ImGui.SameLine();
if (ImGui.Button("Load Tex (Immediate)"))
@@ -506,7 +440,7 @@ internal class TexWidget : IDataWindowWidget
ImGui.SameLine();
if (ImGui.Button("Load File (Async)"))
- this.addedTextures.Add(new(Api10: this.textureManager.GetFromFile(this.inputFilePath).RentAsync()));
+ this.addedTextures.Add(new(Api10: this.textureManager.Shared.GetFromFile(this.inputFilePath).RentAsync()));
ImGui.SameLine();
if (ImGui.Button("Load File (Immediate)"))
@@ -579,7 +513,7 @@ internal class TexWidget : IDataWindowWidget
if (ImGui.Button("Load File (Async)"))
{
this.addedTextures.Add(
- new(Api10: this.textureManager.GetFromManifestResource(assembly, name).RentAsync()));
+ new(Api10: this.textureManager.Shared.GetFromManifestResource(assembly, name).RentAsync()));
}
ImGui.SameLine();
@@ -600,6 +534,100 @@ internal class TexWidget : IDataWindowWidget
ImGuiHelpers.ScaledDummy(10);
}
+ private async void SaveTextureAsync(string name, Func> textureGetter)
+ {
+ try
+ {
+ BitmapCodecInfo encoder;
+ {
+ var off = ImGui.GetCursorScreenPos();
+ var first = true;
+ var encoders = this.textureManager
+ .Wic
+ .GetSupportedEncoderInfos()
+ .ToList();
+ var tcs = new TaskCompletionSource();
+ Service.Get().Draw += DrawChoices;
+
+ encoder = await tcs.Task;
+
+ void DrawChoices()
+ {
+ if (first)
+ {
+ ImGui.OpenPopup(nameof(this.SaveTextureAsync));
+ first = false;
+ }
+
+ ImGui.SetNextWindowPos(off, ImGuiCond.Appearing);
+ if (!ImGui.BeginPopup(
+ nameof(this.SaveTextureAsync),
+ ImGuiWindowFlags.AlwaysAutoResize |
+ ImGuiWindowFlags.NoTitleBar |
+ ImGuiWindowFlags.NoSavedSettings))
+ {
+ Service.Get().Draw -= DrawChoices;
+ tcs.TrySetCanceled();
+ return;
+ }
+
+ foreach (var encoder2 in encoders)
+ {
+ if (ImGui.Selectable(encoder2.Name))
+ tcs.TrySetResult(encoder2);
+ }
+
+ if (tcs.Task.IsCompleted)
+ ImGui.CloseCurrentPopup();
+
+ ImGui.EndPopup();
+ }
+ }
+
+ string path;
+ {
+ var tcs = new TaskCompletionSource();
+ this.fileDialogManager.SaveFileDialog(
+ "Save texture...",
+ $"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}",
+ name + encoder.Extensions.First(),
+ encoder.Extensions.First(),
+ (ok, path2) =>
+ {
+ if (!ok)
+ tcs.SetCanceled();
+ else
+ tcs.SetResult(path2);
+ });
+ path = await tcs.Task.ConfigureAwait(false);
+ }
+
+ using var textureWrap = await textureGetter.Invoke();
+ await this.textureManager.SaveToFileAsync(
+ textureWrap,
+ encoder.ContainerGuid,
+ path,
+ props: new Dictionary
+ {
+ ["CompressionQuality"] = 1.0f,
+ ["ImageQuality"] = 1.0f,
+ });
+
+ Service.Get().AddNotification(
+ $"File saved to: {path}",
+ this.DisplayName,
+ NotificationType.Success);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveTextureAsync)}");
+ Service.Get().AddNotification(
+ $"Failed to save file: {e}",
+ this.DisplayName,
+ NotificationType.Error);
+ }
+ }
+
private void TextRightAlign(string s)
{
var width = ImGui.CalcTextSize(s).X;
@@ -656,7 +684,7 @@ internal class TexWidget : IDataWindowWidget
_ = this.Api10?.ToContentDisposedTask();
}
- public string DescribeError()
+ public string? DescribeError()
{
if (this.SharedResource is not null)
return "Unknown error";
@@ -665,7 +693,7 @@ internal class TexWidget : IDataWindowWidget
if (this.Api10 is not null)
{
return !this.Api10.IsCompleted
- ? "Loading"
+ ? null
: this.Api10.Exception?.ToString() ?? (this.Api10.IsCanceled ? "Canceled" : "Unknown error");
}
@@ -704,6 +732,18 @@ internal class TexWidget : IDataWindowWidget
return null;
}
+ public async Task CreateNewTextureWrapReference(ITextureProvider tp)
+ {
+ while (true)
+ {
+ if (this.GetTexture(tp) is { } textureWrap)
+ return textureWrap.CreateWrapSharingLowLevelResource();
+ if (this.DescribeError() is { } err)
+ throw new(err);
+ await Task.Delay(100);
+ }
+ }
+
public TextureEntry CreateFromSharedLowLevelResource(ITextureProvider tp) =>
new() { SharedResource = this.GetTexture(tp)?.CreateWrapSharingLowLevelResource() };
diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
index 50450aaae..1042b0741 100644
--- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs
@@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Networking.Http;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.Types;
diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
index 0e30658ef..186263783 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs
@@ -16,6 +16,7 @@ using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal.Notifications;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
@@ -1745,7 +1746,7 @@ internal class PluginInstallerWindow : Window, IDisposable
if (!this.testerIconPath.IsNullOrEmpty())
{
- this.testerIcon = tm.GetFromFile(this.testerIconPath).RentAsync();
+ this.testerIcon = tm.Shared.GetFromFile(this.testerIconPath).RentAsync();
}
this.testerImages = new Task?[this.testerImagePaths.Length];
@@ -1756,7 +1757,7 @@ internal class PluginInstallerWindow : Window, IDisposable
continue;
_ = this.testerImages[i]?.ToContentDisposedTask();
- this.testerImages[i] = tm.GetFromFile(this.testerImagePaths[i]).RentAsync();
+ this.testerImages[i] = tm.Shared.GetFromFile(this.testerImagePaths[i]).RentAsync();
}
}
catch (Exception ex)
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
index 6f2a56394..87776f53a 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
@@ -11,6 +11,7 @@ using Dalamud.Game;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
diff --git a/Dalamud/Interface/Internal/DalamudTextureWrap.cs b/Dalamud/Interface/Textures/DalamudTextureWrap.cs
similarity index 97%
rename from Dalamud/Interface/Internal/DalamudTextureWrap.cs
rename to Dalamud/Interface/Textures/DalamudTextureWrap.cs
index b49c6f07b..3795abad2 100644
--- a/Dalamud/Interface/Internal/DalamudTextureWrap.cs
+++ b/Dalamud/Interface/Textures/DalamudTextureWrap.cs
@@ -2,6 +2,7 @@
using ImGuiScene;
+// ReSharper disable once CheckNamespace
namespace Dalamud.Interface.Internal;
///
diff --git a/Dalamud/Interface/Textures/IBitmapCodecInfo.cs b/Dalamud/Interface/Textures/IBitmapCodecInfo.cs
new file mode 100644
index 000000000..7a6f300ca
--- /dev/null
+++ b/Dalamud/Interface/Textures/IBitmapCodecInfo.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+
+namespace Dalamud.Interface.Textures;
+
+/// Represents an available bitmap codec.
+public interface IBitmapCodecInfo
+{
+ /// Gets the friendly name for the codec.
+ string Name { get; }
+
+ /// Gets the representing the container.
+ Guid ContainerGuid { get; }
+
+ /// Gets the suggested file extensions.
+ IReadOnlyCollection Extensions { get; }
+
+ /// Gets the corresponding mime types.
+ IReadOnlyCollection MimeTypes { get; }
+}
diff --git a/Dalamud/Interface/Internal/IDalamudTextureWrap.cs b/Dalamud/Interface/Textures/IDalamudTextureWrap.cs
similarity index 96%
rename from Dalamud/Interface/Internal/IDalamudTextureWrap.cs
rename to Dalamud/Interface/Textures/IDalamudTextureWrap.cs
index 8e2e56c26..d2915b5a0 100644
--- a/Dalamud/Interface/Internal/IDalamudTextureWrap.cs
+++ b/Dalamud/Interface/Textures/IDalamudTextureWrap.cs
@@ -1,7 +1,10 @@
using System.Numerics;
+using Dalamud.Interface.Textures.Internal;
+
using TerraFX.Interop.Windows;
+// ReSharper disable once CheckNamespace
namespace Dalamud.Interface.Internal;
///
diff --git a/Dalamud/Interface/ISharedImmediateTexture.cs b/Dalamud/Interface/Textures/ISharedImmediateTexture.cs
similarity index 99%
rename from Dalamud/Interface/ISharedImmediateTexture.cs
rename to Dalamud/Interface/Textures/ISharedImmediateTexture.cs
index f6c63ee10..f8c727557 100644
--- a/Dalamud/Interface/ISharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/ISharedImmediateTexture.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Utility;
-namespace Dalamud.Interface;
+namespace Dalamud.Interface.Textures;
/// A texture with a backing instance of that is shared across multiple
/// requesters.
diff --git a/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs b/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs
new file mode 100644
index 000000000..3d5456500
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+using Dalamud.Utility;
+
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Interface.Textures.Internal;
+
+/// Represents an available bitmap codec.
+internal sealed class BitmapCodecInfo : IBitmapCodecInfo
+{
+ /// Initializes a new instance of the class.
+ /// The source codec info. Ownership is not transferred.
+ public unsafe BitmapCodecInfo(ComPtr codecInfo)
+ {
+ this.Name = ReadStringUsing(
+ codecInfo,
+ ((IWICBitmapCodecInfo.Vtbl*)codecInfo.Get()->lpVtbl)->GetFriendlyName);
+ Guid temp;
+ codecInfo.Get()->GetContainerFormat(&temp).ThrowOnError();
+ this.ContainerGuid = temp;
+ this.Extensions = ReadStringUsing(
+ codecInfo,
+ ((IWICBitmapCodecInfo.Vtbl*)codecInfo.Get()->lpVtbl)->GetFileExtensions)
+ .Split(',');
+ this.MimeTypes = ReadStringUsing(
+ codecInfo,
+ ((IWICBitmapCodecInfo.Vtbl*)codecInfo.Get()->lpVtbl)->GetMimeTypes)
+ .Split(',');
+ }
+
+ /// Gets the friendly name for the codec.
+ public string Name { get; }
+
+ /// Gets the representing the container.
+ public Guid ContainerGuid { get; }
+
+ /// Gets the suggested file extensions.
+ public IReadOnlyCollection Extensions { get; }
+
+ /// Gets the corresponding mime types.
+ public IReadOnlyCollection MimeTypes { get; }
+
+ private static unsafe string ReadStringUsing(
+ IWICBitmapCodecInfo* codecInfo,
+ delegate* unmanaged readFuncPtr)
+ {
+ var cch = 0u;
+ _ = readFuncPtr(codecInfo, 0, null, &cch);
+ var buf = stackalloc char[(int)cch + 1];
+ Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, (ushort*)buf, &cch));
+ return new(buf, 0, (int)cch);
+ }
+}
diff --git a/Dalamud/Interface/Internal/DisposeSuppressingTextureWrap.cs b/Dalamud/Interface/Textures/Internal/DisposeSuppressingTextureWrap.cs
similarity index 90%
rename from Dalamud/Interface/Internal/DisposeSuppressingTextureWrap.cs
rename to Dalamud/Interface/Textures/Internal/DisposeSuppressingTextureWrap.cs
index d099bae69..17a88e270 100644
--- a/Dalamud/Interface/Internal/DisposeSuppressingTextureWrap.cs
+++ b/Dalamud/Interface/Textures/Internal/DisposeSuppressingTextureWrap.cs
@@ -1,4 +1,6 @@
-namespace Dalamud.Interface.Internal;
+using Dalamud.Interface.Internal;
+
+namespace Dalamud.Interface.Textures.Internal;
///
/// A texture wrap that ignores calls.
diff --git a/Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
similarity index 89%
rename from Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
rename to Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
index 06c366601..6cdd0aa25 100644
--- a/Dalamud/Interface/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
@@ -1,9 +1,10 @@
using System.Threading;
using System.Threading.Tasks;
+using Dalamud.Interface.Internal;
using Dalamud.Utility;
-namespace Dalamud.Interface.Internal.SharedImmediateTextures;
+namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
/// Represents a sharable texture, based on a file on the system filesystem.
internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
@@ -42,7 +43,7 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
private async Task CreateTextureAsync(CancellationToken cancellationToken)
{
- var tm = await Service.GetAsync();
+ var tm = await Service.GetAsync();
return await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken);
}
}
diff --git a/Dalamud/Interface/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
similarity index 85%
rename from Dalamud/Interface/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
rename to Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
index e22998813..a0562f1ef 100644
--- a/Dalamud/Interface/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
@@ -3,11 +3,12 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Data;
+using Dalamud.Interface.Internal;
using Dalamud.Utility;
using Lumina.Data.Files;
-namespace Dalamud.Interface.Internal.SharedImmediateTextures;
+namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
/// Represents a sharable texture, based on a file in game resources.
internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
@@ -46,8 +47,9 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
private async Task CreateTextureAsync(CancellationToken cancellationToken)
{
var dm = await Service.GetAsync();
- var tm = await Service.GetAsync();
- if (dm.GetFile(this.path) is not { } file)
+ var tm = await Service.GetAsync();
+ var substPath = tm.GetSubstitutedPath(this.path);
+ if (dm.GetFile(substPath) is not { } file)
throw new FileNotFoundException();
cancellationToken.ThrowIfCancellationRequested();
return tm.NoThrottleCreateFromTexFile(file);
diff --git a/Dalamud/Interface/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
similarity index 93%
rename from Dalamud/Interface/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
rename to Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
index c017a0764..c9bdea067 100644
--- a/Dalamud/Interface/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
@@ -3,9 +3,10 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using Dalamud.Interface.Internal;
using Dalamud.Utility;
-namespace Dalamud.Interface.Internal.SharedImmediateTextures;
+namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
/// Represents a sharable texture, based on a manifest texture obtained from
/// .
@@ -56,7 +57,7 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
if (stream is null)
throw new FileNotFoundException("The resource file could not be found.");
- var tm = await Service.GetAsync();
+ var tm = await Service.GetAsync();
var ms = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0);
await stream.CopyToAsync(ms, cancellationToken);
return tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, (int)ms.Length), cancellationToken);
diff --git a/Dalamud/Interface/Internal/SharedImmediateTextures/SharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs
similarity index 99%
rename from Dalamud/Interface/Internal/SharedImmediateTextures/SharedImmediateTexture.cs
rename to Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs
index 426c61b2c..f730637e4 100644
--- a/Dalamud/Interface/Internal/SharedImmediateTextures/SharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs
@@ -3,10 +3,11 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
+using Dalamud.Interface.Internal;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
-namespace Dalamud.Interface.Internal.SharedImmediateTextures;
+namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
/// Represents a texture that may have multiple reference holders (owners).
internal abstract class SharedImmediateTexture
diff --git a/Dalamud/Interface/Internal/TextureLoadThrottler.cs b/Dalamud/Interface/Textures/Internal/TextureLoadThrottler.cs
similarity index 99%
rename from Dalamud/Interface/Internal/TextureLoadThrottler.cs
rename to Dalamud/Interface/Textures/Internal/TextureLoadThrottler.cs
index 043906782..a996a2890 100644
--- a/Dalamud/Interface/Internal/TextureLoadThrottler.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureLoadThrottler.cs
@@ -4,7 +4,9 @@ using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
-namespace Dalamud.Interface.Internal;
+using Dalamud.Interface.Internal;
+
+namespace Dalamud.Interface.Textures.Internal;
///
/// Service for managing texture loads.
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Api9.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Api9.cs
new file mode 100644
index 000000000..8e08b43f8
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.Api9.cs
@@ -0,0 +1,55 @@
+using System.IO;
+
+using Dalamud.Interface.Internal;
+using Dalamud.Plugin.Services;
+using Dalamud.Utility;
+
+namespace Dalamud.Interface.Textures.Internal;
+
+#pragma warning disable CS0618 // Type or member is obsolete
+
+/// Service responsible for loading and disposing ImGui texture wraps.
+internal sealed partial class TextureManager
+{
+ ///
+ [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
+ [Obsolete("See interface definition.")]
+ string? ITextureProvider.GetIconPath(uint iconId, ITextureProvider.IconFlags flags, ClientLanguage? language)
+ => this.TryGetIconPath(
+ new(
+ iconId,
+ (flags & ITextureProvider.IconFlags.ItemHighQuality) != 0,
+ (flags & ITextureProvider.IconFlags.HiRes) != 0,
+ language),
+ out var path)
+ ? path
+ : null;
+
+ ///
+ [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
+ [Obsolete("See interface definition.")]
+ IDalamudTextureWrap? ITextureProvider.GetIcon(
+ uint iconId,
+ ITextureProvider.IconFlags flags,
+ ClientLanguage? language,
+ bool keepAlive) =>
+ this.Shared.GetFromGameIcon(
+ new(
+ iconId,
+ (flags & ITextureProvider.IconFlags.ItemHighQuality) != 0,
+ (flags & ITextureProvider.IconFlags.HiRes) != 0,
+ language))
+ .GetAvailableOnAccessWrapForApi9();
+
+ ///
+ [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
+ [Obsolete("See interface definition.")]
+ IDalamudTextureWrap? ITextureProvider.GetTextureFromGame(string path, bool keepAlive) =>
+ this.Shared.GetFromGame(path).GetAvailableOnAccessWrapForApi9();
+
+ ///
+ [Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
+ [Obsolete("See interface definition.")]
+ IDalamudTextureWrap? ITextureProvider.GetTextureFromFile(FileInfo file, bool keepAlive) =>
+ this.Shared.GetFromFile(file.FullName).GetAvailableOnAccessWrapForApi9();
+}
diff --git a/Dalamud/Interface/Internal/TextureManager.FormatConvert.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
similarity index 98%
rename from Dalamud/Interface/Internal/TextureManager.FormatConvert.cs
rename to Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
index 99474dca2..e3a130a14 100644
--- a/Dalamud/Interface/Internal/TextureManager.FormatConvert.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
@@ -4,6 +4,7 @@ using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
+using Dalamud.Interface.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@@ -15,7 +16,7 @@ using SharpDX.DXGI;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
-namespace Dalamud.Interface.Internal;
+namespace Dalamud.Interface.Textures.Internal;
/// Service responsible for loading and disposing ImGui texture wraps.
internal sealed partial class TextureManager
@@ -119,16 +120,16 @@ internal sealed partial class TextureManager
}
///
- Task<(RawImageSpecification Specification, byte[] RawData)> ITextureProvider.GetRawDataAsync(
+ Task<(RawImageSpecification Specification, byte[] RawData)> ITextureProvider.GetRawDataFromExistingTextureAsync(
IDalamudTextureWrap wrap,
Vector2 uv0,
Vector2 uv1,
int dxgiFormat,
CancellationToken cancellationToken) =>
- this.GetRawDataAsync(wrap, uv0, uv1, (DXGI_FORMAT)dxgiFormat, cancellationToken);
+ this.GetRawDataFromExistingTextureAsync(wrap, uv0, uv1, (DXGI_FORMAT)dxgiFormat, cancellationToken);
- ///
- public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataAsync(
+ ///
+ public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataFromExistingTextureAsync(
IDalamudTextureWrap wrap,
Vector2 uv0,
Vector2 uv1,
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.GamePath.cs b/Dalamud/Interface/Textures/Internal/TextureManager.GamePath.cs
new file mode 100644
index 000000000..455b6f504
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.GamePath.cs
@@ -0,0 +1,120 @@
+using System.Collections.Generic;
+using System.IO;
+
+using Dalamud.Plugin.Services;
+
+namespace Dalamud.Interface.Textures.Internal;
+
+/// Service responsible for loading and disposing ImGui texture wraps.
+internal sealed partial class TextureManager
+{
+ private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
+ private const string HighResolutionIconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}_hr1.tex";
+
+ ///
+ public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
+
+ ///
+ public bool TryGetIconPath(in GameIconLookup lookup, out string path)
+ {
+ // 1. Item
+ path = FormatIconPath(
+ lookup.IconId,
+ lookup.ItemHq ? "hq/" : string.Empty,
+ lookup.HiRes);
+ if (this.dataManager.FileExists(path))
+ return true;
+
+ var languageFolder = (lookup.Language ?? (ClientLanguage)(int)this.dalamud.StartInfo.Language) switch
+ {
+ ClientLanguage.Japanese => "ja/",
+ ClientLanguage.English => "en/",
+ ClientLanguage.German => "de/",
+ ClientLanguage.French => "fr/",
+ _ => null,
+ };
+
+ if (languageFolder is not null)
+ {
+ // 2. Regular icon, with language, hi-res
+ path = FormatIconPath(
+ lookup.IconId,
+ languageFolder,
+ lookup.HiRes);
+ if (this.dataManager.FileExists(path))
+ return true;
+
+ if (lookup.HiRes)
+ {
+ // 3. Regular icon, with language, no hi-res
+ path = FormatIconPath(
+ lookup.IconId,
+ languageFolder,
+ false);
+ if (this.dataManager.FileExists(path))
+ return true;
+ }
+ }
+
+ // 4. Regular icon, without language, hi-res
+ path = FormatIconPath(
+ lookup.IconId,
+ null,
+ lookup.HiRes);
+ if (this.dataManager.FileExists(path))
+ return true;
+
+ // 4. Regular icon, without language, no hi-res
+ if (lookup.HiRes)
+ {
+ path = FormatIconPath(
+ lookup.IconId,
+ null,
+ false);
+ if (this.dataManager.FileExists(path))
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ public string GetIconPath(in GameIconLookup lookup) =>
+ this.TryGetIconPath(lookup, out var path) ? path : throw new FileNotFoundException();
+
+ ///
+ public string GetSubstitutedPath(string originalPath)
+ {
+ if (this.InterceptTexDataLoad == null)
+ return originalPath;
+
+ string? interceptPath = null;
+ this.InterceptTexDataLoad.Invoke(originalPath, ref interceptPath);
+
+ if (interceptPath != null)
+ {
+ Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", originalPath, interceptPath);
+ return interceptPath;
+ }
+
+ return originalPath;
+ }
+
+ ///
+ public void InvalidatePaths(IEnumerable paths)
+ {
+ foreach (var path in paths)
+ this.Shared.FlushFromGameCache(path);
+ }
+
+ private static string FormatIconPath(uint iconId, string? type, bool highResolution)
+ {
+ var format = highResolution ? HighResolutionIconFileFormat : IconFileFormat;
+
+ type ??= string.Empty;
+ if (type.Length > 0 && !type.EndsWith("/"))
+ type += "/";
+
+ return string.Format(format, iconId / 1000, type, iconId);
+ }
+}
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs
new file mode 100644
index 000000000..3a121c4c5
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs
@@ -0,0 +1,163 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+using BitFaster.Caching.Lru;
+
+using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
+using Dalamud.Plugin.Services;
+
+namespace Dalamud.Interface.Textures.Internal;
+
+/// Service responsible for loading and disposing ImGui texture wraps.
+internal sealed partial class TextureManager
+{
+ ///
+ ISharedImmediateTexture ITextureProvider.GetFromGameIcon(in GameIconLookup lookup) =>
+ this.Shared.GetFromGameIcon(lookup);
+
+ ///
+ ISharedImmediateTexture ITextureProvider.GetFromGame(string path) =>
+ this.Shared.GetFromGame(path);
+
+ ///
+ ISharedImmediateTexture ITextureProvider.GetFromFile(string path) =>
+ this.Shared.GetFromFile(path);
+
+ ///
+ ISharedImmediateTexture ITextureProvider.GetFromManifestResource(Assembly assembly, string name) =>
+ this.Shared.GetFromManifestResource(assembly, name);
+
+ /// A part of texture manager that deals with s.
+ internal sealed class SharedTextureManager : IDisposable
+ {
+ private const int PathLookupLruCount = 8192;
+
+ private readonly TextureManager textureManager;
+ private readonly ConcurrentLru lookupCache = new(PathLookupLruCount);
+ private readonly ConcurrentDictionary gameDict = new();
+ private readonly ConcurrentDictionary fileDict = new();
+ private readonly ConcurrentDictionary<(Assembly, string), SharedImmediateTexture> manifestResourceDict = new();
+ private readonly HashSet invalidatedTextures = new();
+
+ /// Initializes a new instance of the class.
+ /// An instance of .
+ public SharedTextureManager(TextureManager textureManager)
+ {
+ this.textureManager = textureManager;
+ this.textureManager.framework.Update += this.FrameworkOnUpdate;
+ }
+
+ /// Gets all the loaded textures from game resources.
+ public ICollection ForDebugGamePathTextures => this.gameDict.Values;
+
+ /// Gets all the loaded textures from filesystem.
+ public ICollection ForDebugFileSystemTextures => this.fileDict.Values;
+
+ /// Gets all the loaded textures from assembly manifest resources.
+ public ICollection ForDebugManifestResourceTextures => this.manifestResourceDict.Values;
+
+ /// Gets all the loaded textures that are invalidated from .
+ /// lock on use of the value returned from this property.
+ [SuppressMessage(
+ "ReSharper",
+ "InconsistentlySynchronizedField",
+ Justification = "Debug use only; users are expected to lock around this")]
+ public ICollection ForDebugInvalidatedTextures => this.invalidatedTextures;
+
+ ///
+ public void Dispose()
+ {
+ this.textureManager.framework.Update -= this.FrameworkOnUpdate;
+ this.lookupCache.Clear();
+ ReleaseSelfReferences(this.gameDict);
+ ReleaseSelfReferences(this.fileDict);
+ ReleaseSelfReferences(this.manifestResourceDict);
+ return;
+
+ static void ReleaseSelfReferences(ConcurrentDictionary dict)
+ {
+ foreach (var v in dict.Values)
+ v.ReleaseSelfReference(true);
+ dict.Clear();
+ }
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) =>
+ this.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue));
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SharedImmediateTexture GetFromGame(string path) =>
+ this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SharedImmediateTexture GetFromFile(string path) =>
+ this.fileDict.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SharedImmediateTexture GetFromManifestResource(Assembly assembly, string name) =>
+ this.manifestResourceDict.GetOrAdd(
+ (assembly, name),
+ ManifestResourceSharedImmediateTexture.CreatePlaceholder);
+
+ /// Invalidates a cached item from and .
+ ///
+ /// The path to invalidate.
+ public void FlushFromGameCache(string path)
+ {
+ if (this.gameDict.TryRemove(path, out var r))
+ {
+ if (r.ReleaseSelfReference(true) != 0 || r.HasRevivalPossibility)
+ {
+ lock (this.invalidatedTextures)
+ this.invalidatedTextures.Add(r);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private string GetIconPathByValue(GameIconLookup lookup) =>
+ this.textureManager.TryGetIconPath(lookup, out var path) ? path : throw new FileNotFoundException();
+
+ private void FrameworkOnUpdate(IFramework unused)
+ {
+ RemoveFinalReleased(this.gameDict);
+ RemoveFinalReleased(this.fileDict);
+ RemoveFinalReleased(this.manifestResourceDict);
+
+ // ReSharper disable once InconsistentlySynchronizedField
+ if (this.invalidatedTextures.Count != 0)
+ {
+ lock (this.invalidatedTextures)
+ this.invalidatedTextures.RemoveWhere(TextureFinalReleasePredicate);
+ }
+
+ return;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static void RemoveFinalReleased(ConcurrentDictionary dict)
+ {
+ if (!dict.IsEmpty)
+ {
+ foreach (var (k, v) in dict)
+ {
+ if (TextureFinalReleasePredicate(v))
+ _ = dict.TryRemove(k, out _);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static bool TextureFinalReleasePredicate(SharedImmediateTexture v) =>
+ v.ContentQueried && v.ReleaseSelfReference(false) == 0 && !v.HasRevivalPossibility;
+ }
+ }
+}
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
new file mode 100644
index 000000000..6da68b7e0
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
@@ -0,0 +1,527 @@
+using System.Buffers;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
+using Dalamud.Plugin.Services;
+using Dalamud.Utility;
+using Dalamud.Utility.TerraFxCom;
+
+using TerraFX.Interop.DirectX;
+using TerraFX.Interop.Windows;
+
+using static TerraFX.Interop.Windows.Windows;
+
+namespace Dalamud.Interface.Textures.Internal;
+
+/// Service responsible for loading and disposing ImGui texture wraps.
+[SuppressMessage(
+ "StyleCop.CSharp.LayoutRules",
+ "SA1519:Braces should not be omitted from multi-line child statement",
+ Justification = "Multiple fixed blocks")]
+internal sealed partial class TextureManager
+{
+ ///
+ [SuppressMessage(
+ "StyleCop.CSharp.LayoutRules",
+ "SA1519:Braces should not be omitted from multi-line child statement",
+ Justification = "Multiple fixed blocks")]
+ public async Task SaveToStreamAsync(
+ IDalamudTextureWrap wrap,
+ Guid containerGuid,
+ Stream stream,
+ bool leaveOpen = false,
+ IReadOnlyDictionary? props = null,
+ CancellationToken cancellationToken = default)
+ {
+ using var istream = ManagedIStream.Create(stream, leaveOpen);
+
+ RawImageSpecification specs;
+ byte[] bytes;
+ using (var wrapCopy = wrap.CreateWrapSharingLowLevelResource())
+ {
+ (specs, bytes) = await this.GetRawDataFromExistingTextureAsync(
+ wrapCopy,
+ Vector2.Zero,
+ Vector2.One,
+ DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
+ cancellationToken).ConfigureAwait(false);
+ }
+
+ this.Wic.SaveToStreamUsingWic(
+ specs,
+ bytes,
+ containerGuid,
+ istream,
+ props,
+ cancellationToken);
+ }
+
+ ///
+ public async Task SaveToFileAsync(
+ IDalamudTextureWrap wrap,
+ Guid containerGuid,
+ string path,
+ IReadOnlyDictionary? props = null,
+ CancellationToken cancellationToken = default)
+ {
+ var pathTemp = $"{path}.{GetCurrentThreadId():X08}{Environment.TickCount64:X16}.tmp";
+ try
+ {
+ await this.SaveToStreamAsync(wrap, containerGuid, File.Create(pathTemp), false, props, cancellationToken);
+ }
+ catch (Exception e)
+ {
+ try
+ {
+ if (File.Exists(pathTemp))
+ File.Delete(pathTemp);
+ }
+ catch (Exception e2)
+ {
+ throw new AggregateException(
+ "Failed to save the file, and failed to remove the temporary file.",
+ e,
+ e2);
+ }
+
+ throw;
+ }
+
+ try
+ {
+ try
+ {
+ File.Replace(pathTemp, path, null, true);
+ }
+ catch
+ {
+ File.Move(pathTemp, path, true);
+ }
+ }
+ catch (Exception e)
+ {
+ try
+ {
+ if (File.Exists(pathTemp))
+ File.Delete(pathTemp);
+ }
+ catch (Exception e2)
+ {
+ throw new AggregateException(
+ "Failed to move the temporary file to the target path, and failed to remove the temporary file.",
+ e,
+ e2);
+ }
+
+ throw;
+ }
+ }
+
+ ///
+ IEnumerable ITextureProvider.GetSupportedImageDecoderInfos() =>
+ this.Wic.GetSupportedDecoderInfos();
+
+ ///
+ IEnumerable ITextureProvider.GetSupportedImageEncoderInfos() =>
+ this.Wic.GetSupportedEncoderInfos();
+
+ /// Creates a texture from the given bytes of an image file. Skips the load throttler; intended to be used
+ /// from implementation of s.
+ /// The data.
+ /// The cancellation token.
+ /// The loaded texture.
+ internal IDalamudTextureWrap NoThrottleCreateFromImage(
+ ReadOnlyMemory bytes,
+ CancellationToken cancellationToken = default)
+ {
+ ObjectDisposedException.ThrowIf(this.disposing, this);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ try
+ {
+ using var handle = bytes.Pin();
+ using var stream = this.Wic.CreateIStreamFromMemory(handle, bytes.Length);
+ return this.Wic.NoThrottleCreateFromWicStream(stream, cancellationToken);
+ }
+ catch (Exception e1)
+ {
+ try
+ {
+ return this.NoThrottleCreateFromTexFile(bytes.Span);
+ }
+ catch (Exception e2)
+ {
+ throw new AggregateException(e1, e2);
+ }
+ }
+ }
+
+ /// Creates a texture from the given path to an image file. Skips the load throttler; intended to be used
+ /// from implementation of s.
+ /// The path of the file..
+ /// The cancellation token.
+ /// The loaded texture.
+ internal async Task NoThrottleCreateFromFileAsync(
+ string path,
+ CancellationToken cancellationToken = default)
+ {
+ ObjectDisposedException.ThrowIf(this.disposing, this);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ try
+ {
+ using var stream = this.Wic.CreateIStreamFromFile(path);
+ return this.Wic.NoThrottleCreateFromWicStream(stream, cancellationToken);
+ }
+ catch (Exception e1)
+ {
+ try
+ {
+ return this.NoThrottleCreateFromTexFile(await File.ReadAllBytesAsync(path, cancellationToken));
+ }
+ catch (Exception e2)
+ {
+ throw new AggregateException(e1, e2);
+ }
+ }
+ }
+
+ /// A part of texture manager that uses Windows Imaging Component under the hood.
+ internal sealed class WicManager : IDisposable
+ {
+ private readonly TextureManager textureManager;
+ private ComPtr wicFactory;
+
+ /// Initializes a new instance of the class.
+ /// An instance of .
+ public WicManager(TextureManager textureManager)
+ {
+ this.textureManager = textureManager;
+ unsafe
+ {
+ fixed (Guid* pclsidWicImagingFactory = &CLSID.CLSID_WICImagingFactory)
+ fixed (Guid* piidWicImagingFactory = &IID.IID_IWICImagingFactory)
+ {
+ CoCreateInstance(
+ pclsidWicImagingFactory,
+ null,
+ (uint)CLSCTX.CLSCTX_INPROC_SERVER,
+ piidWicImagingFactory,
+ (void**)this.wicFactory.GetAddressOf()).ThrowOnError();
+ }
+ }
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~WicManager() => this.ReleaseUnmanagedResource();
+
+ ///
+ public void Dispose()
+ {
+ this.ReleaseUnmanagedResource();
+ GC.SuppressFinalize(this);
+ }
+
+ /// Creates a new instance of from a .
+ /// An instance of .
+ /// The number of bytes in the memory.
+ /// The new instance of .
+ public unsafe ComPtr CreateIStreamFromMemory(MemoryHandle handle, int length)
+ {
+ using var wicStream = default(ComPtr);
+ this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
+ wicStream.Get()->InitializeFromMemory((byte*)handle.Pointer, checked((uint)length)).ThrowOnError();
+
+ var res = default(ComPtr);
+ wicStream.As(ref res).ThrowOnError();
+ return res;
+ }
+
+ /// Creates a new instance of from a file path.
+ /// The file path.
+ /// The new instance of .
+ public unsafe ComPtr CreateIStreamFromFile(string path)
+ {
+ using var wicStream = default(ComPtr);
+ this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
+ fixed (char* pPath = path)
+ wicStream.Get()->InitializeFromFilename((ushort*)pPath, GENERIC_READ).ThrowOnError();
+
+ var res = default(ComPtr);
+ wicStream.As(ref res).ThrowOnError();
+ return res;
+ }
+
+ /// Creates a new instance of from a .
+ /// The stream that will NOT be closed after.
+ /// The cancellation token.
+ /// The newly loaded texture.
+ public unsafe IDalamudTextureWrap NoThrottleCreateFromWicStream(
+ ComPtr stream,
+ CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using var decoder = default(ComPtr);
+ this.wicFactory.Get()->CreateDecoderFromStream(
+ stream,
+ null,
+ WICDecodeOptions.WICDecodeMetadataCacheOnDemand,
+ decoder.GetAddressOf()).ThrowOnError();
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using var frame = default(ComPtr);
+ decoder.Get()->GetFrame(0, frame.GetAddressOf()).ThrowOnError();
+ var pixelFormat = default(Guid);
+ frame.Get()->GetPixelFormat(&pixelFormat).ThrowOnError();
+ var dxgiFormat = GetCorrespondingDxgiFormat(pixelFormat);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using var bitmapSource = default(ComPtr);
+ if (dxgiFormat == DXGI_FORMAT.DXGI_FORMAT_UNKNOWN || !this.textureManager.IsDxgiFormatSupported(dxgiFormat))
+ {
+ dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
+ pixelFormat = GUID.GUID_WICPixelFormat32bppBGRA;
+ WICConvertBitmapSource(&pixelFormat, (IWICBitmapSource*)frame.Get(), bitmapSource.GetAddressOf())
+ .ThrowOnError();
+ }
+ else
+ {
+ frame.As(&bitmapSource);
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using var bitmap = default(ComPtr);
+ using var bitmapLock = default(ComPtr);
+ WICRect rcLock;
+ uint stride;
+ uint cbBufferSize;
+ byte* pbData;
+ if (bitmapSource.As(&bitmap).FAILED)
+ {
+ bitmapSource.Get()->GetSize((uint*)&rcLock.Width, (uint*)&rcLock.Height).ThrowOnError();
+ this.wicFactory.Get()->CreateBitmap(
+ (uint)rcLock.Width,
+ (uint)rcLock.Height,
+ &pixelFormat,
+ WICBitmapCreateCacheOption.WICBitmapCacheOnDemand,
+ bitmap.GetAddressOf()).ThrowOnError();
+
+ bitmap.Get()->Lock(
+ &rcLock,
+ (uint)WICBitmapLockFlags.WICBitmapLockWrite,
+ bitmapLock.ReleaseAndGetAddressOf())
+ .ThrowOnError();
+ bitmapLock.Get()->GetStride(&stride).ThrowOnError();
+ bitmapLock.Get()->GetDataPointer(&cbBufferSize, &pbData).ThrowOnError();
+ bitmapSource.Get()->CopyPixels(null, stride, cbBufferSize, pbData).ThrowOnError();
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ bitmap.Get()->Lock(
+ &rcLock,
+ (uint)WICBitmapLockFlags.WICBitmapLockRead,
+ bitmapLock.ReleaseAndGetAddressOf())
+ .ThrowOnError();
+ bitmapSource.Get()->GetSize((uint*)&rcLock.Width, (uint*)&rcLock.Height).ThrowOnError();
+ bitmapLock.Get()->GetStride(&stride).ThrowOnError();
+ bitmapLock.Get()->GetDataPointer(&cbBufferSize, &pbData).ThrowOnError();
+ bitmapSource.Get()->CopyPixels(null, stride, cbBufferSize, pbData).ThrowOnError();
+ return this.textureManager.NoThrottleCreateFromRaw(
+ new(rcLock.Width, rcLock.Height, (int)stride, (int)dxgiFormat),
+ new(pbData, (int)cbBufferSize));
+ }
+
+ /// Gets the supported bitmap codecs.
+ /// The supported encoders.
+ public IEnumerable GetSupportedEncoderInfos()
+ {
+ foreach (var ptr in new ComponentEnumerable(
+ this.wicFactory,
+ WICComponentType.WICEncoder))
+ yield return new(ptr);
+ }
+
+ /// Gets the supported bitmap codecs.
+ /// The supported decoders.
+ public IEnumerable GetSupportedDecoderInfos()
+ {
+ foreach (var ptr in new ComponentEnumerable(
+ this.wicFactory,
+ WICComponentType.WICDecoder))
+ yield return new(ptr);
+ }
+
+ /// Saves the given raw bitmap to a stream.
+ /// The raw bitmap specifications.
+ /// The raw bitmap bytes.
+ /// The container format from .
+ /// The stream to write to. The ownership is not transferred.
+ /// The encoder properties.
+ /// The cancellation token.
+ public unsafe void SaveToStreamUsingWic(
+ RawImageSpecification specs,
+ byte[] bytes,
+ Guid containerFormat,
+ ComPtr stream,
+ IReadOnlyDictionary? props = null,
+ CancellationToken cancellationToken = default)
+ {
+ using var encoder = default(ComPtr);
+ using var encoderFrame = default(ComPtr);
+ var guidPixelFormat = GUID.GUID_WICPixelFormat32bppBGRA;
+ this.wicFactory.Get()->CreateEncoder(&containerFormat, null, encoder.GetAddressOf()).ThrowOnError();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ encoder.Get()->Initialize(stream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache)
+ .ThrowOnError();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using var propertyBag = default(ComPtr);
+ encoder.Get()->CreateNewFrame(encoderFrame.GetAddressOf(), propertyBag.GetAddressOf()).ThrowOnError();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (props is not null)
+ {
+ var nprop = 0u;
+ propertyBag.Get()->CountProperties(&nprop).ThrowOnError();
+ for (var i = 0u; i < nprop; i++)
+ {
+ var pbag2 = default(PROPBAG2);
+ var npropread = 0u;
+ propertyBag.Get()->GetPropertyInfo(i, 1, &pbag2, &npropread).ThrowOnError();
+ if (npropread == 0)
+ continue;
+ try
+ {
+ var propName = new string((char*)pbag2.pstrName);
+ if (props.TryGetValue(propName, out var untypedValue))
+ {
+ VARIANT val;
+ // Marshal calls VariantInit.
+ Marshal.GetNativeVariantForObject(untypedValue, (nint)(&val));
+ VariantChangeType(&val, &val, 0, pbag2.vt).ThrowOnError();
+ propertyBag.Get()->Write(1, &pbag2, &val).ThrowOnError();
+ VariantClear(&val);
+ }
+ }
+ finally
+ {
+ CoTaskMemFree(pbag2.pstrName);
+ }
+ }
+ }
+
+ encoderFrame.Get()->Initialize(propertyBag).ThrowOnError();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ encoderFrame.Get()->SetPixelFormat(&guidPixelFormat).ThrowOnError();
+ encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
+
+ using var tempBitmap = default(ComPtr);
+ fixed (Guid* pGuid = &GUID.GUID_WICPixelFormat32bppBGRA)
+ fixed (byte* pBytes = bytes)
+ {
+ this.wicFactory.Get()->CreateBitmapFromMemory(
+ (uint)specs.Width,
+ (uint)specs.Height,
+ pGuid,
+ (uint)specs.Pitch,
+ checked((uint)bytes.Length),
+ pBytes,
+ tempBitmap.GetAddressOf()).ThrowOnError();
+ }
+
+ using var tempBitmap2 = default(ComPtr);
+ WICConvertBitmapSource(
+ &guidPixelFormat,
+ (IWICBitmapSource*)tempBitmap.Get(),
+ tempBitmap2.GetAddressOf()).ThrowOnError();
+
+ encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
+ encoderFrame.Get()->WriteSource(tempBitmap2.Get(), null).ThrowOnError();
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ encoderFrame.Get()->Commit().ThrowOnError();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ encoder.Get()->Commit().ThrowOnError();
+ }
+
+ ///
+ /// Gets the corresponding from a containing a WIC pixel format.
+ ///
+ /// The WIC pixel format.
+ /// The corresponding , or if
+ /// unavailable.
+ private static DXGI_FORMAT GetCorrespondingDxgiFormat(Guid fmt) => 0 switch
+ {
+ // See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile
+ _ when fmt == GUID.GUID_WICPixelFormat128bppRGBAFloat => DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT,
+ _ when fmt == GUID.GUID_WICPixelFormat64bppRGBAHalf => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT,
+ _ when fmt == GUID.GUID_WICPixelFormat64bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102XR => DXGI_FORMAT
+ .DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102 => DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat16bppBGRA5551 => DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat16bppBGR565 => DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppGrayFloat => DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT,
+ _ when fmt == GUID.GUID_WICPixelFormat16bppGrayHalf => DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT,
+ _ when fmt == GUID.GUID_WICPixelFormat16bppGray => DXGI_FORMAT.DXGI_FORMAT_R16_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat8bppGray => DXGI_FORMAT.DXGI_FORMAT_R8_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat8bppAlpha => DXGI_FORMAT.DXGI_FORMAT_A8_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppBGRA => DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppBGR => DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM,
+ _ => DXGI_FORMAT.DXGI_FORMAT_UNKNOWN,
+ };
+
+ private void ReleaseUnmanagedResource() => this.wicFactory.Reset();
+
+ private readonly struct ComponentEnumerable : IEnumerable>
+ where T : unmanaged, IWICComponentInfo.Interface
+ {
+ private readonly ComPtr factory;
+ private readonly WICComponentType componentType;
+
+ /// Initializes a new instance of the struct.
+ /// The WIC factory. Ownership is not transferred.
+ ///
+ /// The component type to enumerate.
+ public ComponentEnumerable(ComPtr factory, WICComponentType componentType)
+ {
+ this.factory = factory;
+ this.componentType = componentType;
+ }
+
+ public unsafe ManagedIEnumUnknownEnumerator GetEnumerator()
+ {
+ var enumUnknown = default(ComPtr);
+ this.factory.Get()->CreateComponentEnumerator(
+ (uint)this.componentType,
+ (uint)WICComponentEnumerateOptions.WICComponentEnumerateDefault,
+ enumUnknown.GetAddressOf()).ThrowOnError();
+ return new(enumUnknown);
+ }
+
+ IEnumerator> IEnumerable>.GetEnumerator() => this.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ }
+ }
+}
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.cs b/Dalamud/Interface/Textures/Internal/TextureManager.cs
new file mode 100644
index 000000000..2a17f4d73
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.cs
@@ -0,0 +1,291 @@
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Dalamud.Data;
+using Dalamud.Game;
+using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
+using Dalamud.IoC;
+using Dalamud.IoC.Internal;
+using Dalamud.Logging.Internal;
+using Dalamud.Plugin.Services;
+using Dalamud.Utility;
+
+using Lumina.Data;
+using Lumina.Data.Files;
+
+using SharpDX;
+using SharpDX.Direct3D;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
+
+using TerraFX.Interop.DirectX;
+
+namespace Dalamud.Interface.Textures.Internal;
+
+/// Service responsible for loading and disposing ImGui texture wraps.
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.EarlyLoadedService]
+#pragma warning disable SA1015
+[ResolveVia]
+[ResolveVia]
+#pragma warning restore SA1015
+internal sealed partial class TextureManager : IServiceType, IDisposable, ITextureProvider, ITextureSubstitutionProvider
+{
+ private static readonly ModuleLog Log = new(nameof(TextureManager));
+
+ [ServiceManager.ServiceDependency]
+ private readonly Dalamud dalamud = Service.Get();
+
+ [ServiceManager.ServiceDependency]
+ private readonly DataManager dataManager = Service.Get();
+
+ [ServiceManager.ServiceDependency]
+ private readonly Framework framework = Service.Get();
+
+ [ServiceManager.ServiceDependency]
+ private readonly InterfaceManager interfaceManager = Service.Get();
+
+ [ServiceManager.ServiceDependency]
+ private readonly TextureLoadThrottler textureLoadThrottler = Service.Get();
+
+ private SharedTextureManager? sharedTextureManager;
+ private WicManager? wicManager;
+ private bool disposing;
+
+ [SuppressMessage(
+ "StyleCop.CSharp.LayoutRules",
+ "SA1519:Braces should not be omitted from multi-line child statement",
+ Justification = "Multiple fixed blocks")]
+ [ServiceManager.ServiceConstructor]
+ private TextureManager()
+ {
+ this.sharedTextureManager = new(this);
+ this.wicManager = new(this);
+ }
+
+ /// Gets the shared texture manager.
+ public SharedTextureManager Shared =>
+ this.sharedTextureManager ??
+ throw new ObjectDisposedException(nameof(this.sharedTextureManager));
+
+ /// Gets the WIC manager.
+ public WicManager Wic =>
+ this.wicManager ??
+ throw new ObjectDisposedException(nameof(this.sharedTextureManager));
+
+ ///
+ public void Dispose()
+ {
+ if (this.disposing)
+ return;
+
+ this.disposing = true;
+
+ this.drawsOneSquare?.Dispose();
+ this.drawsOneSquare = null;
+
+ Interlocked.Exchange(ref this.sharedTextureManager, null)?.Dispose();
+ Interlocked.Exchange(ref this.wicManager, null)?.Dispose();
+ }
+
+ ///
+ public Task CreateFromImageAsync(
+ ReadOnlyMemory bytes,
+ CancellationToken cancellationToken = default) =>
+ this.textureLoadThrottler.LoadTextureAsync(
+ new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
+ ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct),
+ cancellationToken);
+
+ ///
+ public Task CreateFromImageAsync(
+ Stream stream,
+ bool leaveOpen = false,
+ CancellationToken cancellationToken = default) =>
+ this.textureLoadThrottler.LoadTextureAsync(
+ new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
+ async ct =>
+ {
+ await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
+ await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
+ return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct);
+ },
+ cancellationToken)
+ .ContinueWith(
+ r =>
+ {
+ if (!leaveOpen)
+ stream.Dispose();
+ return r;
+ },
+ default(CancellationToken))
+ .Unwrap();
+
+ ///
+ // It probably doesn't make sense to throttle this, as it copies the passed bytes to GPU without any transformation.
+ public IDalamudTextureWrap CreateFromRaw(
+ RawImageSpecification specs,
+ ReadOnlySpan bytes) => this.NoThrottleCreateFromRaw(specs, bytes);
+
+ ///
+ public Task CreateFromRawAsync(
+ RawImageSpecification specs,
+ ReadOnlyMemory bytes,
+ CancellationToken cancellationToken = default) =>
+ this.textureLoadThrottler.LoadTextureAsync(
+ new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
+ _ => Task.FromResult(this.NoThrottleCreateFromRaw(specs, bytes.Span)),
+ cancellationToken);
+
+ ///
+ public Task CreateFromRawAsync(
+ RawImageSpecification specs,
+ Stream stream,
+ bool leaveOpen = false,
+ CancellationToken cancellationToken = default) =>
+ this.textureLoadThrottler.LoadTextureAsync(
+ new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
+ async ct =>
+ {
+ await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
+ await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
+ return this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length));
+ },
+ cancellationToken)
+ .ContinueWith(
+ r =>
+ {
+ if (!leaveOpen)
+ stream.Dispose();
+ return r;
+ },
+ default(CancellationToken))
+ .Unwrap();
+
+ ///
+ public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result;
+
+ ///
+ public Task CreateFromTexFileAsync(
+ TexFile file,
+ CancellationToken cancellationToken = default) =>
+ this.textureLoadThrottler.LoadTextureAsync(
+ new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
+ ct => Task.Run(() => this.NoThrottleCreateFromTexFile(file), ct),
+ cancellationToken);
+
+ ///
+ bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
+ this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
+
+ ///
+ public bool IsDxgiFormatSupported(DXGI_FORMAT dxgiFormat)
+ {
+ if (this.interfaceManager.Scene is not { } scene)
+ {
+ _ = Service.Get();
+ scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
+ }
+
+ var format = (Format)dxgiFormat;
+ var support = scene.Device.CheckFormatSupport(format);
+ const FormatSupport required = FormatSupport.Texture2D;
+ return (support & required) == required;
+ }
+
+ ///
+ internal IDalamudTextureWrap NoThrottleCreateFromRaw(
+ RawImageSpecification specs,
+ 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));
+ }
+
+ /// Creates a texture from the given . Skips the load throttler; intended to be used
+ /// from implementation of s.
+ /// The data.
+ /// The loaded texture.
+ internal IDalamudTextureWrap NoThrottleCreateFromTexFile(TexFile file)
+ {
+ ObjectDisposedException.ThrowIf(this.disposing, this);
+
+ var buffer = file.TextureBuffer;
+ var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false);
+ if (conversion != TexFile.DxgiFormatConversion.NoConversion ||
+ !this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat))
+ {
+ dxgiFormat = (int)Format.B8G8R8A8_UNorm;
+ buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8);
+ }
+
+ return this.NoThrottleCreateFromRaw(
+ RawImageSpecification.From(buffer.Width, buffer.Height, dxgiFormat),
+ buffer.RawData);
+ }
+
+ /// Creates a texture from the given , trying to interpret it as a
+ /// .
+ /// The file bytes.
+ /// The loaded texture.
+ internal IDalamudTextureWrap NoThrottleCreateFromTexFile(ReadOnlySpan fileBytes)
+ {
+ ObjectDisposedException.ThrowIf(this.disposing, this);
+
+ if (!TexFileExtensions.IsPossiblyTexFile2D(fileBytes))
+ throw new InvalidDataException("The file is not a TexFile.");
+
+ var bytesArray = fileBytes.ToArray();
+ var tf = new TexFile();
+ typeof(TexFile).GetProperty(nameof(tf.Data))!.GetSetMethod(true)!.Invoke(
+ tf,
+ new object?[] { bytesArray });
+ typeof(TexFile).GetProperty(nameof(tf.Reader))!.GetSetMethod(true)!.Invoke(
+ tf,
+ new object?[] { new LuminaBinaryReader(bytesArray) });
+ // Note: FileInfo and FilePath are not used from TexFile; skip it.
+ return this.NoThrottleCreateFromTexFile(tf);
+ }
+}
diff --git a/Dalamud/Interface/Internal/UnknownTextureWrap.cs b/Dalamud/Interface/Textures/Internal/UnknownTextureWrap.cs
similarity index 96%
rename from Dalamud/Interface/Internal/UnknownTextureWrap.cs
rename to Dalamud/Interface/Textures/Internal/UnknownTextureWrap.cs
index 41164f2c3..24e9a8bc1 100644
--- a/Dalamud/Interface/Internal/UnknownTextureWrap.cs
+++ b/Dalamud/Interface/Textures/Internal/UnknownTextureWrap.cs
@@ -1,10 +1,11 @@
using System.Threading;
+using Dalamud.Interface.Internal;
using Dalamud.Utility;
using TerraFX.Interop.Windows;
-namespace Dalamud.Interface.Internal;
+namespace Dalamud.Interface.Textures.Internal;
///
/// A texture wrap that is created by cloning the underlying .
diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs
index 3a718ef4c..f28f400c1 100644
--- a/Dalamud/Interface/UiBuilder.cs
+++ b/Dalamud/Interface/UiBuilder.cs
@@ -14,6 +14,7 @@ using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.ManagedFontAtlas.Internals;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
diff --git a/Dalamud/Interface/UldWrapper.cs b/Dalamud/Interface/UldWrapper.cs
index 289db6faf..507730662 100644
--- a/Dalamud/Interface/UldWrapper.cs
+++ b/Dalamud/Interface/UldWrapper.cs
@@ -4,6 +4,7 @@ using System.Linq;
using Dalamud.Data;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Lumina.Data.Files;
diff --git a/Dalamud/Plugin/Services/ITextureProvider.cs b/Dalamud/Plugin/Services/ITextureProvider.cs
index ac6ab8baf..7715bd5d0 100644
--- a/Dalamud/Plugin/Services/ITextureProvider.cs
+++ b/Dalamud/Plugin/Services/ITextureProvider.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Dalamud.Interface;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures;
using Lumina.Data.Files;
@@ -121,14 +122,41 @@ public partial interface ITextureProvider
TexFile file,
CancellationToken cancellationToken = default);
+ /// Gets the supported bitmap decoders.
+ /// The supported bitmap decoders.
+ ///
+ /// The following functions support the files of the container types pointed by yielded values.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IEnumerable GetSupportedImageDecoderInfos();
+
+ /// Gets the supported bitmap encoders.
+ /// The supported bitmap encoders.
+ ///
+ /// The following function supports the files of the container types pointed by yielded values.
+ ///
+ ///
+ IEnumerable GetSupportedImageEncoderInfos();
+
/// Gets a shared texture corresponding to the given game resource icon specifier.
/// A game icon specifier.
/// The shared texture that you may use to obtain the loaded texture wrap and load states.
+ /// This function is under the effect of .
+ ///
ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup);
/// Gets a shared texture corresponding to the given path to a game resource.
/// A path to a game resource.
/// The shared texture that you may use to obtain the loaded texture wrap and load states.
+ /// This function is under the effect of .
+ ///
ISharedImmediateTexture GetFromGame(string path);
/// Gets a shared texture corresponding to the given file on the filesystem.
@@ -173,26 +201,16 @@ public partial interface ITextureProvider
/// then the source data will be returned.
/// This function can fail.
///
- Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataAsync(
+ Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataFromExistingTextureAsync(
IDalamudTextureWrap wrap,
Vector2 uv0,
Vector2 uv1,
int dxgiFormat = 0,
CancellationToken cancellationToken = default);
- /// Gets the supported image file extensions available for loading.
- /// The supported extensions. Each string[] entry indicates that there can be multiple extensions
- /// that correspond to one container format.
- IEnumerable GetLoadSupportedImageExtensions();
-
- /// Gets the supported image file extensions available for saving.
- /// The supported extensions. Each string[] entry indicates that there can be multiple extensions
- /// that correspond to one container format.
- IEnumerable GetSaveSupportedImageExtensions();
-
/// Saves a texture wrap to a stream in an image file format.
/// The texture wrap to save.
- /// The extension of the file to deduce the file format with the leading dot.
+ /// The container GUID, obtained from .
/// The stream to save to.
/// Whether to leave open.
/// Properties to pass to the encoder. See
@@ -202,20 +220,33 @@ public partial interface ITextureProvider
/// A task representing the save process.
///
/// may be disposed as soon as this function returns.
- /// If no image container format corresponding to is found, then the image will
- /// be saved in png format.
///
- [SuppressMessage(
- "StyleCop.CSharp.LayoutRules",
- "SA1519:Braces should not be omitted from multi-line child statement",
- Justification = "Multiple fixed blocks")]
- Task SaveAsImageFormatToStreamAsync(
+ Task SaveToStreamAsync(
IDalamudTextureWrap wrap,
- string extension,
+ Guid containerGuid,
Stream stream,
bool leaveOpen = false,
IReadOnlyDictionary? props = null,
CancellationToken cancellationToken = default);
+
+ /// Saves a texture wrap to a file as an image file.
+ /// The texture wrap to save.
+ /// The container GUID, obtained from .
+ /// The target file path. The target file will be overwritten if it exist.
+ /// Properties to pass to the encoder. See
+ /// Microsoft
+ /// Learn for available parameters.
+ /// The cancellation token.
+ /// A task representing the save process.
+ ///
+ /// may be disposed as soon as this function returns.
+ ///
+ Task SaveToFileAsync(
+ IDalamudTextureWrap wrap,
+ Guid containerGuid,
+ string path,
+ IReadOnlyDictionary? props = null,
+ CancellationToken cancellationToken = default);
///
/// Determines whether the system supports the given DXGI format.
diff --git a/Dalamud/Plugin/Services/RawImageSpecification.cs b/Dalamud/Plugin/Services/RawImageSpecification.cs
index 206ce578e..4d0aa2e9e 100644
--- a/Dalamud/Plugin/Services/RawImageSpecification.cs
+++ b/Dalamud/Plugin/Services/RawImageSpecification.cs
@@ -9,14 +9,24 @@ namespace Dalamud.Plugin.Services;
///
/// The width of the image.
/// The height of the image.
-/// The pitch of the image.
+/// The pitch of the image in bytes. The value may not always exactly match
+/// * bytesPerPixelFromDxgiFormat.
/// The format of the image. See DXGI_FORMAT.
[SuppressMessage(
"StyleCop.CSharp.NamingRules",
"SA1313:Parameter names should begin with lower-case letter",
Justification = "no")]
-public record struct RawImageSpecification(int Width, int Height, int Pitch, int DxgiFormat)
+public readonly record struct RawImageSpecification(int Width, int Height, int Pitch, int DxgiFormat)
{
+ private const string FormatNotSupportedMessage = $"{nameof(DxgiFormat)} is not supported.";
+
+ /// Gets the number of bits per pixel.
+ /// Thrown if is not supported.
+ public int BitsPerPixel =>
+ GetFormatInfo((DXGI_FORMAT)this.DxgiFormat, out var bitsPerPixel, out _)
+ ? bitsPerPixel
+ : throw new NotSupportedException(FormatNotSupportedMessage);
+
///
/// Creates a new instance of record using the given resolution and pixel
/// format. Pitch will be automatically calculated.
@@ -25,167 +35,11 @@ public record struct RawImageSpecification(int Width, int Height, int Pitch, int
/// The height.
/// The format.
/// The new instance.
+ /// Thrown if is not supported.
public static RawImageSpecification From(int width, int height, int format)
{
- int bitsPerPixel;
- var isBlockCompression = false;
- switch ((DXGI_FORMAT)format)
- {
- case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_SINT:
- bitsPerPixel = 128;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_R32G32B32_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32B32_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32B32_SINT:
- bitsPerPixel = 96;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_SNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_SINT:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R32G32_SINT:
- case DXGI_FORMAT.DXGI_FORMAT_R32G8X24_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
- bitsPerPixel = 64;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R11G11B10_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_SNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_SINT:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16_SNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R16G16_SINT:
- case DXGI_FORMAT.DXGI_FORMAT_R32_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_R32_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R32_SINT:
- case DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_X24_TYPELESS_G8_UINT:
- bitsPerPixel = 32;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_R8G8_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8_SNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8_SINT:
- case DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT:
- case DXGI_FORMAT.DXGI_FORMAT_D16_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R16_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R16_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R16_SNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R16_SINT:
- bitsPerPixel = 16;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_R8_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_R8_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R8_UINT:
- case DXGI_FORMAT.DXGI_FORMAT_R8_SNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R8_SINT:
- case DXGI_FORMAT.DXGI_FORMAT_A8_UNORM:
- bitsPerPixel = 8;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_R1_UNORM:
- bitsPerPixel = 1;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_R9G9B9E5_SHAREDEXP:
- case DXGI_FORMAT.DXGI_FORMAT_R8G8_B8G8_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_G8R8_G8B8_UNORM:
- throw new NotSupportedException();
- case DXGI_FORMAT.DXGI_FORMAT_BC1_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM_SRGB:
- bitsPerPixel = 4;
- isBlockCompression = true;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_BC2_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM_SRGB:
- case DXGI_FORMAT.DXGI_FORMAT_BC3_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM_SRGB:
- bitsPerPixel = 8;
- isBlockCompression = true;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_BC4_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC4_SNORM:
- bitsPerPixel = 4;
- isBlockCompression = true;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_BC5_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC5_SNORM:
- bitsPerPixel = 8;
- isBlockCompression = true;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM:
- bitsPerPixel = 16;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
- case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
- bitsPerPixel = 32;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_BC6H_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_BC6H_UF16:
- case DXGI_FORMAT.DXGI_FORMAT_BC6H_SF16:
- case DXGI_FORMAT.DXGI_FORMAT_BC7_TYPELESS:
- case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM:
- case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM_SRGB:
- bitsPerPixel = 8;
- isBlockCompression = true;
- break;
- case DXGI_FORMAT.DXGI_FORMAT_AYUV:
- case DXGI_FORMAT.DXGI_FORMAT_Y410:
- case DXGI_FORMAT.DXGI_FORMAT_Y416:
- case DXGI_FORMAT.DXGI_FORMAT_NV12:
- case DXGI_FORMAT.DXGI_FORMAT_P010:
- case DXGI_FORMAT.DXGI_FORMAT_P016:
- case DXGI_FORMAT.DXGI_FORMAT_420_OPAQUE:
- case DXGI_FORMAT.DXGI_FORMAT_YUY2:
- case DXGI_FORMAT.DXGI_FORMAT_Y210:
- case DXGI_FORMAT.DXGI_FORMAT_Y216:
- case DXGI_FORMAT.DXGI_FORMAT_NV11:
- case DXGI_FORMAT.DXGI_FORMAT_AI44:
- case DXGI_FORMAT.DXGI_FORMAT_IA44:
- case DXGI_FORMAT.DXGI_FORMAT_P8:
- case DXGI_FORMAT.DXGI_FORMAT_A8P8:
- throw new NotSupportedException();
- case DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM:
- bitsPerPixel = 16;
- break;
- default:
- throw new NotSupportedException();
- }
+ if (!GetFormatInfo((DXGI_FORMAT)format, out var bitsPerPixel, out var isBlockCompression))
+ throw new NotSupportedException(FormatNotSupportedMessage);
var pitch = isBlockCompression
? Math.Max(1, (width + 3) / 4) * 2 * bitsPerPixel
@@ -223,4 +77,157 @@ public record struct RawImageSpecification(int Width, int Height, int Pitch, int
/// The new instance.
public static RawImageSpecification A8(int width, int height) =>
new(width, height, width, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM);
+
+ private static bool GetFormatInfo(DXGI_FORMAT format, out int bitsPerPixel, out bool isBlockCompression)
+ {
+ switch (format)
+ {
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_SINT:
+ bitsPerPixel = 128;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32B32_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32B32_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32B32_SINT:
+ bitsPerPixel = 96;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_SNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_SINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G32_SINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32G8X24_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
+ bitsPerPixel = 64;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R11G11B10_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_SNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_SINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16_SNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R16G16_SINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R32_SINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_X24_TYPELESS_G8_UINT:
+ bitsPerPixel = 32;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8_SNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R8G8_SINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT:
+ case DXGI_FORMAT.DXGI_FORMAT_D16_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R16_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R16_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R16_SNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R16_SINT:
+ bitsPerPixel = 16;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_R8_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_R8_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R8_UINT:
+ case DXGI_FORMAT.DXGI_FORMAT_R8_SNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R8_SINT:
+ case DXGI_FORMAT.DXGI_FORMAT_A8_UNORM:
+ bitsPerPixel = 8;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_R1_UNORM:
+ bitsPerPixel = 1;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_BC1_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM_SRGB:
+ bitsPerPixel = 4;
+ isBlockCompression = true;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_BC2_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM_SRGB:
+ case DXGI_FORMAT.DXGI_FORMAT_BC3_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM_SRGB:
+ bitsPerPixel = 8;
+ isBlockCompression = true;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_BC4_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_BC4_SNORM:
+ bitsPerPixel = 4;
+ isBlockCompression = true;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_BC5_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_BC5_SNORM:
+ bitsPerPixel = 8;
+ isBlockCompression = true;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM:
+ bitsPerPixel = 16;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+ case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
+ bitsPerPixel = 32;
+ isBlockCompression = false;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_BC6H_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_BC6H_UF16:
+ case DXGI_FORMAT.DXGI_FORMAT_BC6H_SF16:
+ case DXGI_FORMAT.DXGI_FORMAT_BC7_TYPELESS:
+ case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM:
+ case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM_SRGB:
+ bitsPerPixel = 8;
+ isBlockCompression = true;
+ return true;
+ case DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM:
+ bitsPerPixel = 16;
+ isBlockCompression = true;
+ return false;
+ default:
+ bitsPerPixel = 0;
+ isBlockCompression = false;
+ return false;
+ }
+ }
}
diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs
index 9db6d55a4..a9293eb6d 100644
--- a/Dalamud/Storage/Assets/DalamudAssetManager.cs
+++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs
@@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.Internal;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Networking.Http;
diff --git a/Dalamud/Utility/TerraFxCom/ManagedIEnumUnknownEnumerator.cs b/Dalamud/Utility/TerraFxCom/ManagedIEnumUnknownEnumerator.cs
new file mode 100644
index 000000000..1f1ac9ffb
--- /dev/null
+++ b/Dalamud/Utility/TerraFxCom/ManagedIEnumUnknownEnumerator.cs
@@ -0,0 +1,59 @@
+using System.Collections;
+using System.Collections.Generic;
+
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Utility.TerraFxCom;
+
+/// Managed iterator for .
+/// The unknown type.
+internal sealed class ManagedIEnumUnknownEnumerator : IEnumerator>
+ where T : unmanaged, IUnknown.Interface
+{
+ private ComPtr unknownEnumerator;
+ private ComPtr current;
+
+ /// Initializes a new instance of the class.
+ /// An instance of . Ownership is transferred.
+ public ManagedIEnumUnknownEnumerator(ComPtr unknownEnumerator) =>
+ this.unknownEnumerator = unknownEnumerator;
+
+ /// Finalizes an instance of the class.
+ ~ManagedIEnumUnknownEnumerator() => this.ReleaseUnmanagedResources();
+
+ ///
+ public ComPtr Current => this.current;
+
+ ///
+ object IEnumerator.Current => this.current;
+
+ ///
+ public void Dispose()
+ {
+ this.ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ public unsafe bool MoveNext()
+ {
+ using var punk = default(ComPtr);
+ var fetched = 0u;
+ while (this.unknownEnumerator.Get()->Next(1u, punk.ReleaseAndGetAddressOf(), &fetched) == S.S_OK && fetched == 1)
+ {
+ if (punk.As(ref this.current).SUCCEEDED)
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ public unsafe void Reset() => this.unknownEnumerator.Get()->Reset().ThrowOnError();
+
+ private void ReleaseUnmanagedResources()
+ {
+ this.unknownEnumerator.Reset();
+ this.current.Reset();
+ }
+}
diff --git a/Dalamud/Utility/ManagedIStream.cs b/Dalamud/Utility/TerraFxCom/ManagedIStream.cs
similarity index 88%
rename from Dalamud/Utility/ManagedIStream.cs
rename to Dalamud/Utility/TerraFxCom/ManagedIStream.cs
index 33c05111c..942a9baf3 100644
--- a/Dalamud/Utility/ManagedIStream.cs
+++ b/Dalamud/Utility/TerraFxCom/ManagedIStream.cs
@@ -1,5 +1,4 @@
using System.Buffers;
-using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -7,7 +6,7 @@ using System.Runtime.InteropServices;
using TerraFX.Interop;
using TerraFX.Interop.Windows;
-namespace Dalamud.Utility;
+namespace Dalamud.Utility.TerraFxCom;
/// An wrapper for .
[Guid("a620678b-56b9-4202-a1da-b821214dc972")]
@@ -15,7 +14,8 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
{
private static readonly Guid MyGuid = typeof(ManagedIStream).GUID;
- private readonly Stream inner;
+ private readonly Stream innerStream;
+ private readonly bool leaveOpen;
private readonly nint[] comObject;
private readonly IStream.Vtbl vtbl;
private GCHandle gchThis;
@@ -23,11 +23,10 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
private GCHandle gchVtbl;
private int refCount;
- /// Initializes a new instance of the class.
- /// The inner stream.
- public ManagedIStream(Stream inner)
+ private ManagedIStream(Stream innerStream, bool leaveOpen = false)
{
- this.inner = inner;
+ this.innerStream = innerStream ?? throw new NullReferenceException();
+ this.leaveOpen = leaveOpen;
this.comObject = new nint[2];
this.vtbl.QueryInterface = &QueryInterfaceStatic;
@@ -127,6 +126,26 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
public static implicit operator IStream*(ManagedIStream mis) =>
(IStream*)mis.gchComObject.AddrOfPinnedObject();
+ /// Creates a new instance of based on a managed .
+ /// The inner stream.
+ /// Whether to leave open on final release.
+ /// The new instance of based on .
+ public static ComPtr Create(Stream innerStream, bool leaveOpen = false)
+ {
+ try
+ {
+ var res = default(ComPtr);
+ res.Attach(new ManagedIStream(innerStream, leaveOpen));
+ return res;
+ }
+ catch
+ {
+ if (!leaveOpen)
+ innerStream.Dispose();
+ throw;
+ }
+ }
+
///
public HRESULT QueryInterface(Guid* riid, void** ppvObject)
{
@@ -176,6 +195,8 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
this.gchThis.Free();
this.gchComObject.Free();
this.gchVtbl.Free();
+ if (!this.leaveOpen)
+ this.innerStream.Dispose();
return newRefCount;
case IRefCountable.RefCountResult.AlreadyDisposed:
@@ -225,7 +246,7 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
for (read = 0u; read < cb;)
{
var chunkSize = unchecked((int)Math.Min(0x10000000u, cb));
- var chunkRead = (uint)this.inner.Read(new(pv, chunkSize));
+ var chunkRead = (uint)this.innerStream.Read(new(pv, chunkSize));
if (chunkRead == 0)
break;
pv = (byte*)pv + chunkRead;
@@ -250,7 +271,7 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
for (written = 0u; written < cb;)
{
var chunkSize = Math.Min(0x10000000u, cb);
- this.inner.Write(new(pv, (int)chunkSize));
+ this.innerStream.Write(new(pv, (int)chunkSize));
pv = (byte*)pv + chunkSize;
written += chunkSize;
}
@@ -293,7 +314,7 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
try
{
- var position = this.inner.Seek(dlibMove.QuadPart, seekOrigin);
+ var position = this.innerStream.Seek(dlibMove.QuadPart, seekOrigin);
if (plibNewPosition != null)
{
*plibNewPosition = new() { QuadPart = (ulong)position };
@@ -312,7 +333,7 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
{
try
{
- this.inner.SetLength(checked((long)libNewSize.QuadPart));
+ this.innerStream.SetLength(checked((long)libNewSize.QuadPart));
return S.S_OK;
}
catch (Exception e) when (e.HResult == unchecked((int)(0x80070000u | ERROR.ERROR_HANDLE_DISK_FULL)))
@@ -355,7 +376,7 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
{
while (cbRead < cb)
{
- var read = checked((uint)this.inner.Read(buf.AsSpan()));
+ var read = checked((uint)this.innerStream.Read(buf.AsSpan()));
if (read == 0)
break;
cbRead += read;
@@ -414,13 +435,13 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
return STG.STG_E_INVALIDPOINTER;
ref var streamStats = ref *pstatstg;
streamStats.type = (uint)STGTY.STGTY_STREAM;
- streamStats.cbSize = (ulong)this.inner.Length;
+ streamStats.cbSize = (ulong)this.innerStream.Length;
streamStats.grfMode = 0;
- if (this.inner.CanRead && this.inner.CanWrite)
+ if (this.innerStream.CanRead && this.innerStream.CanWrite)
streamStats.grfMode |= STGM.STGM_READWRITE;
- else if (this.inner.CanRead)
+ else if (this.innerStream.CanRead)
streamStats.grfMode |= STGM.STGM_READ;
- else if (this.inner.CanWrite)
+ else if (this.innerStream.CanWrite)
streamStats.grfMode |= STGM.STGM_WRITE;
else
return STG.STG_E_REVERTED;