mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-27 02:49:18 +01:00
Cleanup
This commit is contained in:
parent
0aa75306d4
commit
3415df5d40
32 changed files with 1718 additions and 1368 deletions
|
|
@ -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;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
internal sealed partial class TextureManager
|
||||
{
|
||||
private ComPtr<IWICImagingFactory> wicFactory;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[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<string, object>? 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string[]> GetLoadSupportedImageExtensions() =>
|
||||
this.GetSupportedContainerFormats(WICComponentType.WICDecoder).Values;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string[]> GetSaveSupportedImageExtensions() =>
|
||||
this.GetSupportedContainerFormats(WICComponentType.WICEncoder).Values;
|
||||
|
||||
/// <summary>Creates a texture from the given bytes of an image file. Skips the load throttler; intended to be used
|
||||
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
|
||||
/// <param name="bytes">The data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The loaded texture.</returns>
|
||||
internal unsafe IDalamudTextureWrap NoThrottleCreateFromImage(
|
||||
ReadOnlyMemory<byte> 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<IWICStream>);
|
||||
this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
|
||||
wicStream.Get()->InitializeFromMemory(p, checked((uint)bytes.Length)).ThrowOnError();
|
||||
return this.NoThrottleCreateFromWicStream((IStream*)wicStream.Get(), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a texture from the given path to an image file. Skips the load throttler; intended to be used
|
||||
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
|
||||
/// <param name="path">The path of the file..</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The loaded texture.</returns>
|
||||
internal async Task<IDalamudTextureWrap> NoThrottleCreateFromFileAsync(
|
||||
string path,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(this.disposing, this);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (char* pPath = path)
|
||||
{
|
||||
using var wicStream = default(ComPtr<IWICStream>);
|
||||
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<TexFile.TexHeader>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding <see cref="DXGI_FORMAT"/> from a <see cref="Guid"/> containing a WIC pixel format.
|
||||
/// </summary>
|
||||
/// <param name="fmt">The WIC pixel format.</param>
|
||||
/// <returns>The corresponding <see cref="DXGI_FORMAT"/>, or <see cref="DXGI_FORMAT.DXGI_FORMAT_UNKNOWN"/> if
|
||||
/// unavailable.</returns>
|
||||
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<IWICBitmapDecoder>);
|
||||
this.wicFactory.Get()->CreateDecoderFromStream(
|
||||
wicStream,
|
||||
null,
|
||||
WICDecodeOptions.WICDecodeMetadataCacheOnDemand,
|
||||
decoder.GetAddressOf()).ThrowOnError();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using var frame = default(ComPtr<IWICBitmapFrameDecode>);
|
||||
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<IWICBitmapSource>);
|
||||
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<IWICBitmap>);
|
||||
using var bitmapLock = default(ComPtr<IWICBitmapLock>);
|
||||
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<Guid, string[]> GetSupportedContainerFormats(WICComponentType componentType)
|
||||
{
|
||||
var result = new Dictionary<Guid, string[]>();
|
||||
using var enumUnknown = default(ComPtr<IEnumUnknown>);
|
||||
this.wicFactory.Get()->CreateComponentEnumerator(
|
||||
(uint)componentType,
|
||||
(uint)WICComponentEnumerateOptions.WICComponentEnumerateDefault,
|
||||
enumUnknown.GetAddressOf()).ThrowOnError();
|
||||
|
||||
while (true)
|
||||
{
|
||||
using var entry = default(ComPtr<IUnknown>);
|
||||
var fetched = 0u;
|
||||
enumUnknown.Get()->Next(1, entry.GetAddressOf(), &fetched).ThrowOnError();
|
||||
if (fetched == 0)
|
||||
break;
|
||||
|
||||
using var codecInfo = default(ComPtr<IWICBitmapCodecInfo>);
|
||||
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<ComPtr<IPropertyBag2>> 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<IWICBitmapEncoder>);
|
||||
using var encoderFrame = default(ComPtr<IWICBitmapFrameEncode>);
|
||||
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<IPropertyBag2>);
|
||||
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<IWICBitmap>);
|
||||
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<IWICBitmapSource>);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITextureProvider>]
|
||||
[ResolveVia<ITextureSubstitutionProvider>]
|
||||
#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<Dalamud>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly TextureLoadThrottler textureLoadThrottler = Service<TextureLoadThrottler>.Get();
|
||||
|
||||
private readonly ConcurrentLru<GameIconLookup, string> lookupToPath = new(PathLookupLruCount);
|
||||
|
||||
private readonly ConcurrentDictionary<string, SharedImmediateTexture> gamePathTextures = new();
|
||||
|
||||
private readonly ConcurrentDictionary<string, SharedImmediateTexture> fileSystemTextures = new();
|
||||
|
||||
private readonly ConcurrentDictionary<(Assembly Assembly, string Name), SharedImmediateTexture>
|
||||
manifestResourceTextures = new();
|
||||
|
||||
private readonly HashSet<SharedImmediateTexture> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="TextureManager"/> class.</summary>
|
||||
~TextureManager() => this.Dispose();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
||||
|
||||
/// <summary>Gets all the loaded textures from game resources.</summary>
|
||||
public ICollection<SharedImmediateTexture> GamePathTexturesForDebug => this.gamePathTextures.Values;
|
||||
|
||||
/// <summary>Gets all the loaded textures from filesystem.</summary>
|
||||
public ICollection<SharedImmediateTexture> FileSystemTexturesForDebug => this.fileSystemTextures.Values;
|
||||
|
||||
/// <summary>Gets all the loaded textures from assembly manifest resources.</summary>
|
||||
public ICollection<SharedImmediateTexture> ManifestResourceTexturesForDebug => this.manifestResourceTextures.Values;
|
||||
|
||||
/// <summary>Gets all the loaded textures that are invalidated from <see cref="InvalidatePaths"/>.</summary>
|
||||
/// <remarks><c>lock</c> on use of the value returned from this property.</remarks>
|
||||
[SuppressMessage(
|
||||
"ReSharper",
|
||||
"InconsistentlySynchronizedField",
|
||||
Justification = "Debug use only; users are expected to lock around this")]
|
||||
public ICollection<SharedImmediateTexture> InvalidatedTexturesForDebug => this.invalidatedTextures;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<T>(ConcurrentDictionary<T, SharedImmediateTexture> dict)
|
||||
{
|
||||
foreach (var v in dict.Values)
|
||||
v.ReleaseSelfReference(true);
|
||||
dict.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#region API9 compat
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
/// <inheritdoc/>
|
||||
[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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[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();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Obsolete("See interface definition.")]
|
||||
IDalamudTextureWrap? ITextureProvider.GetTextureFromGame(string path, bool keepAlive) =>
|
||||
this.GetFromGame(path).GetAvailableOnAccessWrapForApi9();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[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
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetFromGameIcon"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) =>
|
||||
this.GetFromGame(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue));
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetFromGame"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SharedImmediateTexture GetFromGame(string path)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(this.disposing, this);
|
||||
return this.gamePathTextures.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetFromFile"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SharedImmediateTexture GetFromFile(string path)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(this.disposing, this);
|
||||
return this.fileSystemTextures.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetFromFile"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SharedImmediateTexture GetFromManifestResource(Assembly assembly, string name)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(this.disposing, this);
|
||||
return this.manifestResourceTextures.GetOrAdd(
|
||||
(assembly, name),
|
||||
ManifestResourceSharedImmediateTexture.CreatePlaceholder);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
ISharedImmediateTexture ITextureProvider.GetFromGameIcon(in GameIconLookup lookup) => this.GetFromGameIcon(lookup);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
ISharedImmediateTexture ITextureProvider.GetFromGame(string path) => this.GetFromGame(path);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
ISharedImmediateTexture ITextureProvider.GetFromFile(string path) => this.GetFromFile(path);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
ISharedImmediateTexture ITextureProvider.GetFromManifestResource(Assembly assembly, string name) =>
|
||||
this.GetFromManifestResource(assembly, name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> CreateFromImageAsync(
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct),
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> 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();
|
||||
|
||||
/// <inheritdoc/>
|
||||
// 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<byte> bytes) => this.NoThrottleCreateFromRaw(specs, bytes);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> CreateFromRawAsync(
|
||||
RawImageSpecification specs,
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
_ => Task.FromResult(this.NoThrottleCreateFromRaw(specs, bytes.Span)),
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> 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();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
|
||||
TexFile file,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
ct => Task.Run(() => this.NoThrottleCreateFromTexFile(file), ct),
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
|
||||
this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.IsDxgiFormatSupported"/>
|
||||
public bool IsDxgiFormatSupported(DXGI_FORMAT dxgiFormat)
|
||||
{
|
||||
if (this.interfaceManager.Scene is not { } scene)
|
||||
{
|
||||
_ = Service<InterfaceManager.InterfaceManagerWithScene>.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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetIconPath(in GameIconLookup lookup) =>
|
||||
this.TryGetIconPath(lookup, out var path) ? path : throw new FileNotFoundException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void InvalidatePaths(IEnumerable<string> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.CreateFromRaw"/>
|
||||
internal IDalamudTextureWrap NoThrottleCreateFromRaw(
|
||||
RawImageSpecification specs,
|
||||
ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
if (this.interfaceManager.Scene is not { } scene)
|
||||
{
|
||||
_ = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
|
||||
scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
ShaderResourceView resView;
|
||||
unsafe
|
||||
{
|
||||
fixed (void* pData = bytes)
|
||||
{
|
||||
var texDesc = new Texture2DDescription
|
||||
{
|
||||
Width = specs.Width,
|
||||
Height = specs.Height,
|
||||
MipLevels = 1,
|
||||
ArraySize = 1,
|
||||
Format = (Format)specs.DxgiFormat,
|
||||
SampleDescription = new(1, 0),
|
||||
Usage = ResourceUsage.Immutable,
|
||||
BindFlags = BindFlags.ShaderResource,
|
||||
CpuAccessFlags = CpuAccessFlags.None,
|
||||
OptionFlags = ResourceOptionFlags.None,
|
||||
};
|
||||
|
||||
using var texture = new Texture2D(scene.Device, texDesc, new DataRectangle(new(pData), specs.Pitch));
|
||||
resView = new(
|
||||
scene.Device,
|
||||
texture,
|
||||
new()
|
||||
{
|
||||
Format = texDesc.Format,
|
||||
Dimension = ShaderResourceViewDimension.Texture2D,
|
||||
Texture2D = { MipLevels = texDesc.MipLevels },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// no sampler for now because the ImGui implementation we copied doesn't allow for changing it
|
||||
return new DalamudTextureWrap(new D3DTextureWrap(resView, specs.Width, specs.Height));
|
||||
}
|
||||
|
||||
/// <summary>Creates a texture from the given <see cref="TexFile"/>. Skips the load throttler; intended to be used
|
||||
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
|
||||
/// <param name="file">The data.</param>
|
||||
/// <returns>The loaded texture.</returns>
|
||||
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<T>(ConcurrentDictionary<T, SharedImmediateTexture> 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();
|
||||
}
|
||||
|
|
@ -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<TextureManager>.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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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<NotificationManager>.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<string, object>
|
||||
{
|
||||
["CompressionQuality"] = 1.0f,
|
||||
["ImageQuality"] = 1.0f,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveImmediateTexture)}");
|
||||
Service<NotificationManager>.Get().AddNotification(
|
||||
$"Failed to save file: {e}",
|
||||
this.DisplayName,
|
||||
NotificationType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Service<NotificationManager>.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<Task<IDalamudTextureWrap>> textureGetter)
|
||||
{
|
||||
try
|
||||
{
|
||||
BitmapCodecInfo encoder;
|
||||
{
|
||||
var off = ImGui.GetCursorScreenPos();
|
||||
var first = true;
|
||||
var encoders = this.textureManager
|
||||
.Wic
|
||||
.GetSupportedEncoderInfos()
|
||||
.ToList();
|
||||
var tcs = new TaskCompletionSource<BitmapCodecInfo>();
|
||||
Service<InterfaceManager>.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<InterfaceManager>.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<string>();
|
||||
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<string, object>
|
||||
{
|
||||
["CompressionQuality"] = 1.0f,
|
||||
["ImageQuality"] = 1.0f,
|
||||
});
|
||||
|
||||
Service<NotificationManager>.Get().AddNotification(
|
||||
$"File saved to: {path}",
|
||||
this.DisplayName,
|
||||
NotificationType.Success);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveTextureAsync)}");
|
||||
Service<NotificationManager>.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<IDalamudTextureWrap> 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() };
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<IDalamudTextureWrap>?[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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
using ImGuiScene;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
19
Dalamud/Interface/Textures/IBitmapCodecInfo.cs
Normal file
19
Dalamud/Interface/Textures/IBitmapCodecInfo.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Dalamud.Interface.Textures;
|
||||
|
||||
/// <summary>Represents an available bitmap codec.</summary>
|
||||
public interface IBitmapCodecInfo
|
||||
{
|
||||
/// <summary>Gets the friendly name for the codec.</summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>Gets the <see cref="Guid"/> representing the container.</summary>
|
||||
Guid ContainerGuid { get; }
|
||||
|
||||
/// <summary>Gets the suggested file extensions.</summary>
|
||||
IReadOnlyCollection<string> Extensions { get; }
|
||||
|
||||
/// <summary>Gets the corresponding mime types.</summary>
|
||||
IReadOnlyCollection<string> MimeTypes { get; }
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Textures.Internal;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
|||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
namespace Dalamud.Interface.Textures;
|
||||
|
||||
/// <summary>A texture with a backing instance of <see cref="IDalamudTextureWrap"/> that is shared across multiple
|
||||
/// requesters.</summary>
|
||||
55
Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs
Normal file
55
Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Utility;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal;
|
||||
|
||||
/// <summary>Represents an available bitmap codec.</summary>
|
||||
internal sealed class BitmapCodecInfo : IBitmapCodecInfo
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="BitmapCodecInfo"/> class.</summary>
|
||||
/// <param name="codecInfo">The source codec info. Ownership is not transferred.</param>
|
||||
public unsafe BitmapCodecInfo(ComPtr<IWICBitmapCodecInfo> codecInfo)
|
||||
{
|
||||
this.Name = ReadStringUsing(
|
||||
codecInfo,
|
||||
((IWICBitmapCodecInfo.Vtbl<IWICBitmapCodecInfo>*)codecInfo.Get()->lpVtbl)->GetFriendlyName);
|
||||
Guid temp;
|
||||
codecInfo.Get()->GetContainerFormat(&temp).ThrowOnError();
|
||||
this.ContainerGuid = temp;
|
||||
this.Extensions = ReadStringUsing(
|
||||
codecInfo,
|
||||
((IWICBitmapCodecInfo.Vtbl<IWICBitmapCodecInfo>*)codecInfo.Get()->lpVtbl)->GetFileExtensions)
|
||||
.Split(',');
|
||||
this.MimeTypes = ReadStringUsing(
|
||||
codecInfo,
|
||||
((IWICBitmapCodecInfo.Vtbl<IWICBitmapCodecInfo>*)codecInfo.Get()->lpVtbl)->GetMimeTypes)
|
||||
.Split(',');
|
||||
}
|
||||
|
||||
/// <summary>Gets the friendly name for the codec.</summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>Gets the <see cref="Guid"/> representing the container.</summary>
|
||||
public Guid ContainerGuid { get; }
|
||||
|
||||
/// <summary>Gets the suggested file extensions.</summary>
|
||||
public IReadOnlyCollection<string> Extensions { get; }
|
||||
|
||||
/// <summary>Gets the corresponding mime types.</summary>
|
||||
public IReadOnlyCollection<string> MimeTypes { get; }
|
||||
|
||||
private static unsafe string ReadStringUsing(
|
||||
IWICBitmapCodecInfo* codecInfo,
|
||||
delegate* unmanaged<IWICBitmapCodecInfo*, uint, ushort*, uint*, int> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
namespace Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A texture wrap that ignores <see cref="IDisposable.Dispose"/> calls.
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>Represents a sharable texture, based on a file on the system filesystem.</summary>
|
||||
internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
|
||||
|
|
@ -42,7 +43,7 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
|
|||
|
||||
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var tm = await Service<TextureManager>.GetAsync();
|
||||
var tm = await Service<Interface.Textures.Internal.TextureManager>.GetAsync();
|
||||
return await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>Represents a sharable texture, based on a file in game resources.</summary>
|
||||
internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
|
||||
|
|
@ -46,8 +47,9 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
|
|||
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var dm = await Service<DataManager>.GetAsync();
|
||||
var tm = await Service<TextureManager>.GetAsync();
|
||||
if (dm.GetFile<TexFile>(this.path) is not { } file)
|
||||
var tm = await Service<Interface.Textures.Internal.TextureManager>.GetAsync();
|
||||
var substPath = tm.GetSubstitutedPath(this.path);
|
||||
if (dm.GetFile<TexFile>(substPath) is not { } file)
|
||||
throw new FileNotFoundException();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return tm.NoThrottleCreateFromTexFile(file);
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>Represents a sharable texture, based on a manifest texture obtained from
|
||||
/// <see cref="Assembly.GetManifestResourceStream(string)"/>.</summary>
|
||||
|
|
@ -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<TextureManager>.GetAsync();
|
||||
var tm = await Service<Interface.Textures.Internal.TextureManager>.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);
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>Represents a texture that may have multiple reference holders (owners).</summary>
|
||||
internal abstract class SharedImmediateTexture
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing texture loads.
|
||||
55
Dalamud/Interface/Textures/Internal/TextureManager.Api9.cs
Normal file
55
Dalamud/Interface/Textures/Internal/TextureManager.Api9.cs
Normal file
|
|
@ -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
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
internal sealed partial class TextureManager
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[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();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Obsolete("See interface definition.")]
|
||||
IDalamudTextureWrap? ITextureProvider.GetTextureFromGame(string path, bool keepAlive) =>
|
||||
this.Shared.GetFromGame(path).GetAvailableOnAccessWrapForApi9();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Obsolete("See interface definition.")]
|
||||
IDalamudTextureWrap? ITextureProvider.GetTextureFromFile(FileInfo file, bool keepAlive) =>
|
||||
this.Shared.GetFromFile(file.FullName).GetAvailableOnAccessWrapForApi9();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
internal sealed partial class TextureManager
|
||||
|
|
@ -119,16 +120,16 @@ internal sealed partial class TextureManager
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetRawDataAsync"/>
|
||||
public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataAsync(
|
||||
/// <inheritdoc cref="ITextureProvider.GetRawDataFromExistingTextureAsync"/>
|
||||
public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataFromExistingTextureAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
Vector2 uv0,
|
||||
Vector2 uv1,
|
||||
120
Dalamud/Interface/Textures/Internal/TextureManager.GamePath.cs
Normal file
120
Dalamud/Interface/Textures/Internal/TextureManager.GamePath.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Interface.Textures.Internal;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
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";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetIconPath(in GameIconLookup lookup) =>
|
||||
this.TryGetIconPath(lookup, out var path) ? path : throw new FileNotFoundException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void InvalidatePaths(IEnumerable<string> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
internal sealed partial class TextureManager
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
ISharedImmediateTexture ITextureProvider.GetFromGameIcon(in GameIconLookup lookup) =>
|
||||
this.Shared.GetFromGameIcon(lookup);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ISharedImmediateTexture ITextureProvider.GetFromGame(string path) =>
|
||||
this.Shared.GetFromGame(path);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ISharedImmediateTexture ITextureProvider.GetFromFile(string path) =>
|
||||
this.Shared.GetFromFile(path);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ISharedImmediateTexture ITextureProvider.GetFromManifestResource(Assembly assembly, string name) =>
|
||||
this.Shared.GetFromManifestResource(assembly, name);
|
||||
|
||||
/// <summary>A part of texture manager that deals with <see cref="ISharedImmediateTexture"/>s.</summary>
|
||||
internal sealed class SharedTextureManager : IDisposable
|
||||
{
|
||||
private const int PathLookupLruCount = 8192;
|
||||
|
||||
private readonly TextureManager textureManager;
|
||||
private readonly ConcurrentLru<GameIconLookup, string> lookupCache = new(PathLookupLruCount);
|
||||
private readonly ConcurrentDictionary<string, SharedImmediateTexture> gameDict = new();
|
||||
private readonly ConcurrentDictionary<string, SharedImmediateTexture> fileDict = new();
|
||||
private readonly ConcurrentDictionary<(Assembly, string), SharedImmediateTexture> manifestResourceDict = new();
|
||||
private readonly HashSet<SharedImmediateTexture> invalidatedTextures = new();
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="SharedTextureManager"/> class.</summary>
|
||||
/// <param name="textureManager">An instance of <see cref="Interface.Textures.Internal.TextureManager"/>.</param>
|
||||
public SharedTextureManager(TextureManager textureManager)
|
||||
{
|
||||
this.textureManager = textureManager;
|
||||
this.textureManager.framework.Update += this.FrameworkOnUpdate;
|
||||
}
|
||||
|
||||
/// <summary>Gets all the loaded textures from game resources.</summary>
|
||||
public ICollection<SharedImmediateTexture> ForDebugGamePathTextures => this.gameDict.Values;
|
||||
|
||||
/// <summary>Gets all the loaded textures from filesystem.</summary>
|
||||
public ICollection<SharedImmediateTexture> ForDebugFileSystemTextures => this.fileDict.Values;
|
||||
|
||||
/// <summary>Gets all the loaded textures from assembly manifest resources.</summary>
|
||||
public ICollection<SharedImmediateTexture> ForDebugManifestResourceTextures => this.manifestResourceDict.Values;
|
||||
|
||||
/// <summary>Gets all the loaded textures that are invalidated from <see cref="InvalidatePaths"/>.</summary>
|
||||
/// <remarks><c>lock</c> on use of the value returned from this property.</remarks>
|
||||
[SuppressMessage(
|
||||
"ReSharper",
|
||||
"InconsistentlySynchronizedField",
|
||||
Justification = "Debug use only; users are expected to lock around this")]
|
||||
public ICollection<SharedImmediateTexture> ForDebugInvalidatedTextures => this.invalidatedTextures;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<T>(ConcurrentDictionary<T, SharedImmediateTexture> dict)
|
||||
{
|
||||
foreach (var v in dict.Values)
|
||||
v.ReleaseSelfReference(true);
|
||||
dict.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetFromGameIcon"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) =>
|
||||
this.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue));
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetFromGame"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SharedImmediateTexture GetFromGame(string path) =>
|
||||
this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder);
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetFromFile"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SharedImmediateTexture GetFromFile(string path) =>
|
||||
this.fileDict.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder);
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.GetFromFile"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SharedImmediateTexture GetFromManifestResource(Assembly assembly, string name) =>
|
||||
this.manifestResourceDict.GetOrAdd(
|
||||
(assembly, name),
|
||||
ManifestResourceSharedImmediateTexture.CreatePlaceholder);
|
||||
|
||||
/// <summary>Invalidates a cached item from <see cref="GetFromGame"/> and <see cref="GetFromGameIcon"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to invalidate.</param>
|
||||
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<T>(ConcurrentDictionary<T, SharedImmediateTexture> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
527
Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
Normal file
527
Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
Normal file
|
|
@ -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;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
[SuppressMessage(
|
||||
"StyleCop.CSharp.LayoutRules",
|
||||
"SA1519:Braces should not be omitted from multi-line child statement",
|
||||
Justification = "Multiple fixed blocks")]
|
||||
internal sealed partial class TextureManager
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[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<string, object>? 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task SaveToFileAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
Guid containerGuid,
|
||||
string path,
|
||||
IReadOnlyDictionary<string, object>? 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerable<IBitmapCodecInfo> ITextureProvider.GetSupportedImageDecoderInfos() =>
|
||||
this.Wic.GetSupportedDecoderInfos();
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerable<IBitmapCodecInfo> ITextureProvider.GetSupportedImageEncoderInfos() =>
|
||||
this.Wic.GetSupportedEncoderInfos();
|
||||
|
||||
/// <summary>Creates a texture from the given bytes of an image file. Skips the load throttler; intended to be used
|
||||
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
|
||||
/// <param name="bytes">The data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The loaded texture.</returns>
|
||||
internal IDalamudTextureWrap NoThrottleCreateFromImage(
|
||||
ReadOnlyMemory<byte> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a texture from the given path to an image file. Skips the load throttler; intended to be used
|
||||
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
|
||||
/// <param name="path">The path of the file..</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The loaded texture.</returns>
|
||||
internal async Task<IDalamudTextureWrap> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A part of texture manager that uses Windows Imaging Component under the hood.</summary>
|
||||
internal sealed class WicManager : IDisposable
|
||||
{
|
||||
private readonly TextureManager textureManager;
|
||||
private ComPtr<IWICImagingFactory> wicFactory;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="WicManager"/> class.</summary>
|
||||
/// <param name="textureManager">An instance of <see cref="Interface.Textures.Internal.TextureManager"/>.</param>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="WicManager"/> class.
|
||||
/// </summary>
|
||||
~WicManager() => this.ReleaseUnmanagedResource();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.ReleaseUnmanagedResource();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="IStream"/> from a <see cref="MemoryHandle"/>.</summary>
|
||||
/// <param name="handle">An instance of <see cref="MemoryHandle"/>.</param>
|
||||
/// <param name="length">The number of bytes in the memory.</param>
|
||||
/// <returns>The new instance of <see cref="IStream"/>.</returns>
|
||||
public unsafe ComPtr<IStream> CreateIStreamFromMemory(MemoryHandle handle, int length)
|
||||
{
|
||||
using var wicStream = default(ComPtr<IWICStream>);
|
||||
this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
|
||||
wicStream.Get()->InitializeFromMemory((byte*)handle.Pointer, checked((uint)length)).ThrowOnError();
|
||||
|
||||
var res = default(ComPtr<IStream>);
|
||||
wicStream.As(ref res).ThrowOnError();
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="IStream"/> from a file path.</summary>
|
||||
/// <param name="path">The file path.</param>
|
||||
/// <returns>The new instance of <see cref="IStream"/>.</returns>
|
||||
public unsafe ComPtr<IStream> CreateIStreamFromFile(string path)
|
||||
{
|
||||
using var wicStream = default(ComPtr<IWICStream>);
|
||||
this.wicFactory.Get()->CreateStream(wicStream.GetAddressOf()).ThrowOnError();
|
||||
fixed (char* pPath = path)
|
||||
wicStream.Get()->InitializeFromFilename((ushort*)pPath, GENERIC_READ).ThrowOnError();
|
||||
|
||||
var res = default(ComPtr<IStream>);
|
||||
wicStream.As(ref res).ThrowOnError();
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="IDalamudTextureWrap"/> from a <see cref="IStream"/>.</summary>
|
||||
/// <param name="stream">The stream that will NOT be closed after.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The newly loaded texture.</returns>
|
||||
public unsafe IDalamudTextureWrap NoThrottleCreateFromWicStream(
|
||||
ComPtr<IStream> stream,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using var decoder = default(ComPtr<IWICBitmapDecoder>);
|
||||
this.wicFactory.Get()->CreateDecoderFromStream(
|
||||
stream,
|
||||
null,
|
||||
WICDecodeOptions.WICDecodeMetadataCacheOnDemand,
|
||||
decoder.GetAddressOf()).ThrowOnError();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using var frame = default(ComPtr<IWICBitmapFrameDecode>);
|
||||
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<IWICBitmapSource>);
|
||||
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<IWICBitmap>);
|
||||
using var bitmapLock = default(ComPtr<IWICBitmapLock>);
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>Gets the supported bitmap codecs.</summary>
|
||||
/// <returns>The supported encoders.</returns>
|
||||
public IEnumerable<BitmapCodecInfo> GetSupportedEncoderInfos()
|
||||
{
|
||||
foreach (var ptr in new ComponentEnumerable<IWICBitmapCodecInfo>(
|
||||
this.wicFactory,
|
||||
WICComponentType.WICEncoder))
|
||||
yield return new(ptr);
|
||||
}
|
||||
|
||||
/// <summary>Gets the supported bitmap codecs.</summary>
|
||||
/// <returns>The supported decoders.</returns>
|
||||
public IEnumerable<BitmapCodecInfo> GetSupportedDecoderInfos()
|
||||
{
|
||||
foreach (var ptr in new ComponentEnumerable<IWICBitmapCodecInfo>(
|
||||
this.wicFactory,
|
||||
WICComponentType.WICDecoder))
|
||||
yield return new(ptr);
|
||||
}
|
||||
|
||||
/// <summary>Saves the given raw bitmap to a stream.</summary>
|
||||
/// <param name="specs">The raw bitmap specifications.</param>
|
||||
/// <param name="bytes">The raw bitmap bytes.</param>
|
||||
/// <param name="containerFormat">The container format from <see cref="GetSupportedEncoderInfos"/>.</param>
|
||||
/// <param name="stream">The stream to write to. The ownership is not transferred.</param>
|
||||
/// <param name="props">The encoder properties.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public unsafe void SaveToStreamUsingWic(
|
||||
RawImageSpecification specs,
|
||||
byte[] bytes,
|
||||
Guid containerFormat,
|
||||
ComPtr<IStream> stream,
|
||||
IReadOnlyDictionary<string, object>? props = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var encoder = default(ComPtr<IWICBitmapEncoder>);
|
||||
using var encoderFrame = default(ComPtr<IWICBitmapFrameEncode>);
|
||||
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<IPropertyBag2>);
|
||||
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<IWICBitmap>);
|
||||
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<IWICBitmapSource>);
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the corresponding <see cref="DXGI_FORMAT"/> from a <see cref="Guid"/> containing a WIC pixel format.
|
||||
/// </summary>
|
||||
/// <param name="fmt">The WIC pixel format.</param>
|
||||
/// <returns>The corresponding <see cref="DXGI_FORMAT"/>, or <see cref="DXGI_FORMAT.DXGI_FORMAT_UNKNOWN"/> if
|
||||
/// unavailable.</returns>
|
||||
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<T> : IEnumerable<ComPtr<T>>
|
||||
where T : unmanaged, IWICComponentInfo.Interface
|
||||
{
|
||||
private readonly ComPtr<IWICImagingFactory> factory;
|
||||
private readonly WICComponentType componentType;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ComponentEnumerable{T}"/> struct.</summary>
|
||||
/// <param name="factory">The WIC factory. Ownership is not transferred.
|
||||
/// </param>
|
||||
/// <param name="componentType">The component type to enumerate.</param>
|
||||
public ComponentEnumerable(ComPtr<IWICImagingFactory> factory, WICComponentType componentType)
|
||||
{
|
||||
this.factory = factory;
|
||||
this.componentType = componentType;
|
||||
}
|
||||
|
||||
public unsafe ManagedIEnumUnknownEnumerator<T> GetEnumerator()
|
||||
{
|
||||
var enumUnknown = default(ComPtr<IEnumUnknown>);
|
||||
this.factory.Get()->CreateComponentEnumerator(
|
||||
(uint)this.componentType,
|
||||
(uint)WICComponentEnumerateOptions.WICComponentEnumerateDefault,
|
||||
enumUnknown.GetAddressOf()).ThrowOnError();
|
||||
return new(enumUnknown);
|
||||
}
|
||||
|
||||
IEnumerator<ComPtr<T>> IEnumerable<ComPtr<T>>.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
291
Dalamud/Interface/Textures/Internal/TextureManager.cs
Normal file
291
Dalamud/Interface/Textures/Internal/TextureManager.cs
Normal file
|
|
@ -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;
|
||||
|
||||
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITextureProvider>]
|
||||
[ResolveVia<ITextureSubstitutionProvider>]
|
||||
#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<Dalamud>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly TextureLoadThrottler textureLoadThrottler = Service<TextureLoadThrottler>.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);
|
||||
}
|
||||
|
||||
/// <summary>Gets the shared texture manager.</summary>
|
||||
public SharedTextureManager Shared =>
|
||||
this.sharedTextureManager ??
|
||||
throw new ObjectDisposedException(nameof(this.sharedTextureManager));
|
||||
|
||||
/// <summary>Gets the WIC manager.</summary>
|
||||
public WicManager Wic =>
|
||||
this.wicManager ??
|
||||
throw new ObjectDisposedException(nameof(this.sharedTextureManager));
|
||||
|
||||
/// <inheritdoc/>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> CreateFromImageAsync(
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct),
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> 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();
|
||||
|
||||
/// <inheritdoc/>
|
||||
// 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<byte> bytes) => this.NoThrottleCreateFromRaw(specs, bytes);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> CreateFromRawAsync(
|
||||
RawImageSpecification specs,
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
_ => Task.FromResult(this.NoThrottleCreateFromRaw(specs, bytes.Span)),
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> 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();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
|
||||
TexFile file,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
this.textureLoadThrottler.LoadTextureAsync(
|
||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||
ct => Task.Run(() => this.NoThrottleCreateFromTexFile(file), ct),
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
|
||||
this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.IsDxgiFormatSupported"/>
|
||||
public bool IsDxgiFormatSupported(DXGI_FORMAT dxgiFormat)
|
||||
{
|
||||
if (this.interfaceManager.Scene is not { } scene)
|
||||
{
|
||||
_ = Service<InterfaceManager.InterfaceManagerWithScene>.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;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ITextureProvider.CreateFromRaw"/>
|
||||
internal IDalamudTextureWrap NoThrottleCreateFromRaw(
|
||||
RawImageSpecification specs,
|
||||
ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
if (this.interfaceManager.Scene is not { } scene)
|
||||
{
|
||||
_ = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
|
||||
scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
ShaderResourceView resView;
|
||||
unsafe
|
||||
{
|
||||
fixed (void* pData = bytes)
|
||||
{
|
||||
var texDesc = new Texture2DDescription
|
||||
{
|
||||
Width = specs.Width,
|
||||
Height = specs.Height,
|
||||
MipLevels = 1,
|
||||
ArraySize = 1,
|
||||
Format = (Format)specs.DxgiFormat,
|
||||
SampleDescription = new(1, 0),
|
||||
Usage = ResourceUsage.Immutable,
|
||||
BindFlags = BindFlags.ShaderResource,
|
||||
CpuAccessFlags = CpuAccessFlags.None,
|
||||
OptionFlags = ResourceOptionFlags.None,
|
||||
};
|
||||
|
||||
using var texture = new Texture2D(scene.Device, texDesc, new DataRectangle(new(pData), specs.Pitch));
|
||||
resView = new(
|
||||
scene.Device,
|
||||
texture,
|
||||
new()
|
||||
{
|
||||
Format = texDesc.Format,
|
||||
Dimension = ShaderResourceViewDimension.Texture2D,
|
||||
Texture2D = { MipLevels = texDesc.MipLevels },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// no sampler for now because the ImGui implementation we copied doesn't allow for changing it
|
||||
return new DalamudTextureWrap(new D3DTextureWrap(resView, specs.Width, specs.Height));
|
||||
}
|
||||
|
||||
/// <summary>Creates a texture from the given <see cref="TexFile"/>. Skips the load throttler; intended to be used
|
||||
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
|
||||
/// <param name="file">The data.</param>
|
||||
/// <returns>The loaded texture.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>Creates a texture from the given <paramref name="fileBytes"/>, trying to interpret it as a
|
||||
/// <see cref="TexFile"/>.</summary>
|
||||
/// <param name="fileBytes">The file bytes.</param>
|
||||
/// <returns>The loaded texture.</returns>
|
||||
internal IDalamudTextureWrap NoThrottleCreateFromTexFile(ReadOnlySpan<byte> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// A texture wrap that is created by cloning the underlying <see cref="IDalamudTextureWrap.ImGuiHandle"/>.
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/// <summary>Gets the supported bitmap decoders.</summary>
|
||||
/// <returns>The supported bitmap decoders.</returns>
|
||||
/// <remarks>
|
||||
/// The following functions support the files of the container types pointed by yielded values.
|
||||
/// <ul>
|
||||
/// <li><see cref="GetFromFile"/></li>
|
||||
/// <li><see cref="GetFromManifestResource"/></li>
|
||||
/// <li><see cref="CreateFromImageAsync(ReadOnlyMemory{byte},CancellationToken)"/></li>
|
||||
/// <li><see cref="CreateFromImageAsync(Stream,bool,CancellationToken)"/></li>
|
||||
/// </ul>
|
||||
/// </remarks>
|
||||
IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos();
|
||||
|
||||
/// <summary>Gets the supported bitmap encoders.</summary>
|
||||
/// <returns>The supported bitmap encoders.</returns>
|
||||
/// <remarks>
|
||||
/// The following function supports the files of the container types pointed by yielded values.
|
||||
/// <ul>
|
||||
/// <li><see cref="SaveToStreamAsync"/></li>
|
||||
/// </ul>
|
||||
/// </remarks>
|
||||
IEnumerable<IBitmapCodecInfo> GetSupportedImageEncoderInfos();
|
||||
|
||||
/// <summary>Gets a shared texture corresponding to the given game resource icon specifier.</summary>
|
||||
/// <param name="lookup">A game icon specifier.</param>
|
||||
/// <returns>The shared texture that you may use to obtain the loaded texture wrap and load states.</returns>
|
||||
/// <remarks>This function is under the effect of <see cref="ITextureSubstitutionProvider.GetSubstitutedPath"/>.
|
||||
/// </remarks>
|
||||
ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup);
|
||||
|
||||
/// <summary>Gets a shared texture corresponding to the given path to a game resource.</summary>
|
||||
/// <param name="path">A path to a game resource.</param>
|
||||
/// <returns>The shared texture that you may use to obtain the loaded texture wrap and load states.</returns>
|
||||
/// <remarks>This function is under the effect of <see cref="ITextureSubstitutionProvider.GetSubstitutedPath"/>.
|
||||
/// </remarks>
|
||||
ISharedImmediateTexture GetFromGame(string path);
|
||||
|
||||
/// <summary>Gets a shared texture corresponding to the given file on the filesystem.</summary>
|
||||
|
|
@ -173,26 +201,16 @@ public partial interface ITextureProvider
|
|||
/// then the source data will be returned.</para>
|
||||
/// <para>This function can fail.</para>
|
||||
/// </remarks>
|
||||
Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataAsync(
|
||||
Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataFromExistingTextureAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
Vector2 uv0,
|
||||
Vector2 uv1,
|
||||
int dxgiFormat = 0,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Gets the supported image file extensions available for loading.</summary>
|
||||
/// <returns>The supported extensions. Each <c>string[]</c> entry indicates that there can be multiple extensions
|
||||
/// that correspond to one container format.</returns>
|
||||
IEnumerable<string[]> GetLoadSupportedImageExtensions();
|
||||
|
||||
/// <summary>Gets the supported image file extensions available for saving.</summary>
|
||||
/// <returns>The supported extensions. Each <c>string[]</c> entry indicates that there can be multiple extensions
|
||||
/// that correspond to one container format.</returns>
|
||||
IEnumerable<string[]> GetSaveSupportedImageExtensions();
|
||||
|
||||
/// <summary>Saves a texture wrap to a stream in an image file format.</summary>
|
||||
/// <param name="wrap">The texture wrap to save.</param>
|
||||
/// <param name="extension">The extension of the file to deduce the file format with the leading dot.</param>
|
||||
/// <param name="containerGuid">The container GUID, obtained from <see cref="GetSupportedImageEncoderInfos"/>.</param>
|
||||
/// <param name="stream">The stream to save to.</param>
|
||||
/// <param name="leaveOpen">Whether to leave <paramref name="stream"/> open.</param>
|
||||
/// <param name="props">Properties to pass to the encoder. See
|
||||
|
|
@ -202,21 +220,34 @@ public partial interface ITextureProvider
|
|||
/// <returns>A task representing the save process.</returns>
|
||||
/// <remarks>
|
||||
/// <para><paramref name="wrap"/> may be disposed as soon as this function returns.</para>
|
||||
/// <para>If no image container format corresponding to <paramref name="extension"/> is found, then the image will
|
||||
/// be saved in png format.</para>
|
||||
/// </remarks>
|
||||
[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<string, object>? props = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Saves a texture wrap to a file as an image file.</summary>
|
||||
/// <param name="wrap">The texture wrap to save.</param>
|
||||
/// <param name="containerGuid">The container GUID, obtained from <see cref="GetSupportedImageEncoderInfos"/>.</param>
|
||||
/// <param name="path">The target file path. The target file will be overwritten if it exist.</param>
|
||||
/// <param name="props">Properties to pass to the encoder. See
|
||||
/// <a href="https://learn.microsoft.com/en-us/windows/win32/wic/-wic-creating-encoder#encoder-options">Microsoft
|
||||
/// Learn</a> for available parameters.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task representing the save process.</returns>
|
||||
/// <remarks>
|
||||
/// <para><paramref name="wrap"/> may be disposed as soon as this function returns.</para>
|
||||
/// </remarks>
|
||||
Task SaveToFileAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
Guid containerGuid,
|
||||
string path,
|
||||
IReadOnlyDictionary<string, object>? props = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the system supports the given DXGI format.
|
||||
/// For use with <see cref="RawImageSpecification.DxgiFormat"/>.
|
||||
|
|
|
|||
|
|
@ -9,14 +9,24 @@ namespace Dalamud.Plugin.Services;
|
|||
/// </summary>
|
||||
/// <param name="Width">The width of the image.</param>
|
||||
/// <param name="Height">The height of the image.</param>
|
||||
/// <param name="Pitch">The pitch of the image.</param>
|
||||
/// <param name="Pitch">The pitch of the image in bytes. The value may not always exactly match
|
||||
/// <c><paramref name="Width"/> * bytesPerPixelFromDxgiFormat</c>.</param>
|
||||
/// <param name="DxgiFormat">The format of the image. See <a href="https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format">DXGI_FORMAT</a>.</param>
|
||||
[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.";
|
||||
|
||||
/// <summary>Gets the number of bits per pixel.</summary>
|
||||
/// <exception cref="NotSupportedException">Thrown if <see cref="DxgiFormat"/> is not supported.</exception>
|
||||
public int BitsPerPixel =>
|
||||
GetFormatInfo((DXGI_FORMAT)this.DxgiFormat, out var bitsPerPixel, out _)
|
||||
? bitsPerPixel
|
||||
: throw new NotSupportedException(FormatNotSupportedMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="RawImageSpecification"/> 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
|
|||
/// <param name="height">The height.</param>
|
||||
/// <param name="format">The format.</param>
|
||||
/// <returns>The new instance.</returns>
|
||||
/// <exception cref="NotSupportedException">Thrown if <see cref="DxgiFormat"/> is not supported.</exception>
|
||||
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
|
|||
/// <returns>The new instance.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
59
Dalamud/Utility/TerraFxCom/ManagedIEnumUnknownEnumerator.cs
Normal file
59
Dalamud/Utility/TerraFxCom/ManagedIEnumUnknownEnumerator.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Dalamud.Utility.TerraFxCom;
|
||||
|
||||
/// <summary>Managed iterator for <see cref="IEnumUnknown"/>.</summary>
|
||||
/// <typeparam name="T">The unknown type.</typeparam>
|
||||
internal sealed class ManagedIEnumUnknownEnumerator<T> : IEnumerator<ComPtr<T>>
|
||||
where T : unmanaged, IUnknown.Interface
|
||||
{
|
||||
private ComPtr<IEnumUnknown> unknownEnumerator;
|
||||
private ComPtr<T> current;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ManagedIEnumUnknownEnumerator{T}"/> class.</summary>
|
||||
/// <param name="unknownEnumerator">An instance of <see cref="IEnumUnknown"/>. Ownership is transferred.</param>
|
||||
public ManagedIEnumUnknownEnumerator(ComPtr<IEnumUnknown> unknownEnumerator) =>
|
||||
this.unknownEnumerator = unknownEnumerator;
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="ManagedIEnumUnknownEnumerator{T}"/> class.</summary>
|
||||
~ManagedIEnumUnknownEnumerator() => this.ReleaseUnmanagedResources();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ComPtr<T> Current => this.current;
|
||||
|
||||
/// <inheritdoc/>
|
||||
object IEnumerator.Current => this.current;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe bool MoveNext()
|
||||
{
|
||||
using var punk = default(ComPtr<IUnknown>);
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void Reset() => this.unknownEnumerator.Get()->Reset().ThrowOnError();
|
||||
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
this.unknownEnumerator.Reset();
|
||||
this.current.Reset();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>An <see cref="IStream"/> wrapper for <see cref="Stream"/>.</summary>
|
||||
[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<IStream> vtbl;
|
||||
private GCHandle gchThis;
|
||||
|
|
@ -23,11 +23,10 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
|
|||
private GCHandle gchVtbl;
|
||||
private int refCount;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ManagedIStream"/> class.</summary>
|
||||
/// <param name="inner">The inner stream.</param>
|
||||
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();
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="IStream"/> based on a managed <see cref="Stream"/>.</summary>
|
||||
/// <param name="innerStream">The inner stream.</param>
|
||||
/// <param name="leaveOpen">Whether to leave <paramref name="innerStream"/> open on final release.</param>
|
||||
/// <returns>The new instance of <see cref="IStream"/> based on <paramref name="innerStream"/>.</returns>
|
||||
public static ComPtr<IStream> Create(Stream innerStream, bool leaveOpen = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = default(ComPtr<IStream>);
|
||||
res.Attach(new ManagedIStream(innerStream, leaveOpen));
|
||||
return res;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!leaveOpen)
|
||||
innerStream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue