mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 12:23:39 +01:00
Use WIC to implement ITP.SaveAsImageFormatToStreamAsync
This commit is contained in:
parent
54ebe8c02a
commit
5367d288d6
7 changed files with 1105 additions and 129 deletions
|
|
@ -287,7 +287,47 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
|
|
||||||
/// <summary>Queues an action to be run before Present call.</summary>
|
/// <summary>Queues an action to be run before Present call.</summary>
|
||||||
/// <param name="action">The action.</param>
|
/// <param name="action">The action.</param>
|
||||||
public void RunBeforePresent(Action action) => this.runBeforePresent.Enqueue(action);
|
/// <returns>A <see cref="Task"/> that resolves once <paramref name="action"/> is run.</returns>
|
||||||
|
public Task RunBeforePresent(Action action)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource();
|
||||||
|
this.runBeforePresent.Enqueue(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
tcs.SetResult();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
tcs.SetException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Queues a function to be run before Present call.</summary>
|
||||||
|
/// <typeparam name="T">The type of the return value.</typeparam>
|
||||||
|
/// <param name="func">The function.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> that resolves once <paramref name="func"/> is run.</returns>
|
||||||
|
public Task<T> RunBeforePresent<T>(Func<T> func)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<T>();
|
||||||
|
this.runBeforePresent.Enqueue(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tcs.SetResult(func());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
tcs.SetException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get video memory information.
|
/// Get video memory information.
|
||||||
|
|
|
||||||
|
|
@ -75,57 +75,37 @@ internal sealed partial class TextureManager
|
||||||
var wrapCopy = wrap.CreateWrapSharingLowLevelResource();
|
var wrapCopy = wrap.CreateWrapSharingLowLevelResource();
|
||||||
return this.textureLoadThrottler.LoadTextureAsync(
|
return this.textureLoadThrottler.LoadTextureAsync(
|
||||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||||
ct =>
|
async _ =>
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<IDalamudTextureWrap>();
|
using var tex = await this.NoThrottleCreateFromExistingTextureAsync(
|
||||||
this.interfaceManager.RunBeforePresent(
|
wrapCopy,
|
||||||
() =>
|
uv0,
|
||||||
{
|
uv1,
|
||||||
try
|
format);
|
||||||
{
|
using var device = default(ComPtr<ID3D11Device>);
|
||||||
ct.ThrowIfCancellationRequested();
|
using var srv = default(ComPtr<ID3D11ShaderResourceView>);
|
||||||
unsafe
|
var desc = default(D3D11_TEXTURE2D_DESC);
|
||||||
{
|
unsafe
|
||||||
using var tex = default(ComPtr<ID3D11Texture2D>);
|
{
|
||||||
tex.Attach(
|
tex.Get()->GetDevice(device.GetAddressOf());
|
||||||
this.NoThrottleCreateFromExistingTextureCore(
|
|
||||||
wrapCopy,
|
|
||||||
uv0,
|
|
||||||
uv1,
|
|
||||||
format,
|
|
||||||
false));
|
|
||||||
|
|
||||||
using var device = default(ComPtr<ID3D11Device>);
|
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
|
||||||
tex.Get()->GetDevice(device.GetAddressOf());
|
tex,
|
||||||
|
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
|
||||||
|
device.Get()->CreateShaderResourceView(
|
||||||
|
(ID3D11Resource*)tex.Get(),
|
||||||
|
&srvDesc,
|
||||||
|
srv.GetAddressOf())
|
||||||
|
.ThrowOnError();
|
||||||
|
|
||||||
using var srv = default(ComPtr<ID3D11ShaderResourceView>);
|
tex.Get()->GetDesc(&desc);
|
||||||
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
|
|
||||||
tex,
|
|
||||||
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
|
|
||||||
device.Get()->CreateShaderResourceView(
|
|
||||||
(ID3D11Resource*)tex.Get(),
|
|
||||||
&srvDesc,
|
|
||||||
srv.GetAddressOf())
|
|
||||||
.ThrowOnError();
|
|
||||||
|
|
||||||
var desc = default(D3D11_TEXTURE2D_DESC);
|
return new UnknownTextureWrap(
|
||||||
tex.Get()->GetDesc(&desc);
|
(IUnknown*)srv.Get(),
|
||||||
|
(int)desc.Width,
|
||||||
tcs.SetResult(
|
(int)desc.Height,
|
||||||
new UnknownTextureWrap(
|
true);
|
||||||
(IUnknown*)srv.Get(),
|
}
|
||||||
(int)desc.Width,
|
|
||||||
(int)desc.Height,
|
|
||||||
true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
tcs.SetException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return tcs.Task;
|
|
||||||
},
|
},
|
||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ContinueWith(
|
.ContinueWith(
|
||||||
|
|
@ -138,95 +118,209 @@ internal sealed partial class TextureManager
|
||||||
.Unwrap();
|
.Unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe ID3D11Texture2D* NoThrottleCreateFromExistingTextureCore(
|
/// <inheritdoc/>
|
||||||
|
Task<(RawImageSpecification Specification, byte[] RawData)> ITextureProvider.GetRawDataAsync(
|
||||||
IDalamudTextureWrap wrap,
|
IDalamudTextureWrap wrap,
|
||||||
Vector2 uv0,
|
Vector2 uv0,
|
||||||
Vector2 uv1,
|
Vector2 uv1,
|
||||||
DXGI_FORMAT format,
|
int dxgiFormat,
|
||||||
bool enableCpuRead)
|
CancellationToken cancellationToken) =>
|
||||||
|
this.GetRawDataAsync(wrap, uv0, uv1, (DXGI_FORMAT)dxgiFormat, cancellationToken);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ITextureProvider.GetRawDataAsync"/>
|
||||||
|
public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataAsync(
|
||||||
|
IDalamudTextureWrap wrap,
|
||||||
|
Vector2 uv0,
|
||||||
|
Vector2 uv1,
|
||||||
|
DXGI_FORMAT dxgiFormat,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ThreadSafety.AssertMainThread();
|
using var resUnk = default(ComPtr<IUnknown>);
|
||||||
|
|
||||||
using var resUnk = new ComPtr<IUnknown>((IUnknown*)wrap.ImGuiHandle);
|
|
||||||
|
|
||||||
using var texSrv = default(ComPtr<ID3D11ShaderResourceView>);
|
using var texSrv = default(ComPtr<ID3D11ShaderResourceView>);
|
||||||
resUnk.As(&texSrv).ThrowOnError();
|
|
||||||
|
|
||||||
using var device = default(ComPtr<ID3D11Device>);
|
using var device = default(ComPtr<ID3D11Device>);
|
||||||
texSrv.Get()->GetDevice(device.GetAddressOf());
|
using var context = default(ComPtr<ID3D11DeviceContext>);
|
||||||
|
|
||||||
using var deviceContext = default(ComPtr<ID3D11DeviceContext>);
|
|
||||||
device.Get()->GetImmediateContext(deviceContext.GetAddressOf());
|
|
||||||
|
|
||||||
using var tex2D = default(ComPtr<ID3D11Texture2D>);
|
using var tex2D = default(ComPtr<ID3D11Texture2D>);
|
||||||
using (var texRes = default(ComPtr<ID3D11Resource>))
|
var texDesc = default(D3D11_TEXTURE2D_DESC);
|
||||||
|
|
||||||
|
unsafe
|
||||||
{
|
{
|
||||||
texSrv.Get()->GetResource(texRes.GetAddressOf());
|
resUnk.Attach((IUnknown*)wrap.ImGuiHandle);
|
||||||
texRes.As(&tex2D).ThrowOnError();
|
resUnk.Get()->AddRef();
|
||||||
|
|
||||||
|
resUnk.As(&texSrv).ThrowOnError();
|
||||||
|
|
||||||
|
texSrv.Get()->GetDevice(device.GetAddressOf());
|
||||||
|
|
||||||
|
device.Get()->GetImmediateContext(context.GetAddressOf());
|
||||||
|
|
||||||
|
using (var texRes = default(ComPtr<ID3D11Resource>))
|
||||||
|
{
|
||||||
|
texSrv.Get()->GetResource(texRes.GetAddressOf());
|
||||||
|
|
||||||
|
using var tex2DTemp = default(ComPtr<ID3D11Texture2D>);
|
||||||
|
texRes.As(&tex2DTemp).ThrowOnError();
|
||||||
|
tex2D.Swap(&tex2DTemp);
|
||||||
|
}
|
||||||
|
|
||||||
|
tex2D.Get()->GetDesc(&texDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (texDesc.Format != dxgiFormat && dxgiFormat != DXGI_FORMAT.DXGI_FORMAT_UNKNOWN)
|
||||||
|
{
|
||||||
|
using var tmp = await this.NoThrottleCreateFromExistingTextureAsync(wrap, uv0, uv1, dxgiFormat);
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
tex2D.Swap(&tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
return await this.interfaceManager.RunBeforePresent(
|
||||||
|
() => ExtractMappedResource(device, context, tex2D, cancellationToken));
|
||||||
|
|
||||||
|
static unsafe (RawImageSpecification Specification, byte[] RawData) ExtractMappedResource(
|
||||||
|
ComPtr<ID3D11Device> device,
|
||||||
|
ComPtr<ID3D11DeviceContext> context,
|
||||||
|
ComPtr<ID3D11Texture2D> tex2D,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
ID3D11Resource* mapWhat = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var tmpTex = default(ComPtr<ID3D11Texture2D>);
|
||||||
|
D3D11_TEXTURE2D_DESC desc;
|
||||||
|
tex2D.Get()->GetDesc(&desc);
|
||||||
|
if ((desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) == 0)
|
||||||
|
{
|
||||||
|
var tmpTexDesc = desc with
|
||||||
|
{
|
||||||
|
MipLevels = 1,
|
||||||
|
ArraySize = 1,
|
||||||
|
SampleDesc = new(1, 0),
|
||||||
|
Usage = D3D11_USAGE.D3D11_USAGE_STAGING,
|
||||||
|
BindFlags = 0u,
|
||||||
|
CPUAccessFlags = (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ,
|
||||||
|
MiscFlags = 0u,
|
||||||
|
};
|
||||||
|
device.Get()->CreateTexture2D(&tmpTexDesc, null, tmpTex.GetAddressOf()).ThrowOnError();
|
||||||
|
context.Get()->CopyResource((ID3D11Resource*)tmpTex.Get(), (ID3D11Resource*)tex2D.Get());
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||||
|
mapWhat = (ID3D11Resource*)(tmpTex.IsEmpty() ? tex2D.Get() : tmpTex.Get());
|
||||||
|
context.Get()->Map(
|
||||||
|
mapWhat,
|
||||||
|
0,
|
||||||
|
D3D11_MAP.D3D11_MAP_READ,
|
||||||
|
0,
|
||||||
|
&mapped).ThrowOnError();
|
||||||
|
|
||||||
|
var specs = RawImageSpecification.From((int)desc.Width, (int)desc.Height, (int)desc.Format);
|
||||||
|
var bytes = new Span<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray();
|
||||||
|
return (specs, bytes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (mapWhat is not null)
|
||||||
|
context.Get()->Unmap(mapWhat, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ComPtr<ID3D11Texture2D>> NoThrottleCreateFromExistingTextureAsync(
|
||||||
|
IDalamudTextureWrap wrap,
|
||||||
|
Vector2 uv0,
|
||||||
|
Vector2 uv1,
|
||||||
|
DXGI_FORMAT format)
|
||||||
|
{
|
||||||
|
using var resUnk = default(ComPtr<IUnknown>);
|
||||||
|
using var texSrv = default(ComPtr<ID3D11ShaderResourceView>);
|
||||||
|
using var device = default(ComPtr<ID3D11Device>);
|
||||||
|
using var context = default(ComPtr<ID3D11DeviceContext>);
|
||||||
|
using var tex2D = default(ComPtr<ID3D11Texture2D>);
|
||||||
var texDesc = default(D3D11_TEXTURE2D_DESC);
|
var texDesc = default(D3D11_TEXTURE2D_DESC);
|
||||||
tex2D.Get()->GetDesc(&texDesc);
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
resUnk.Attach((IUnknown*)wrap.ImGuiHandle);
|
||||||
|
resUnk.Get()->AddRef();
|
||||||
|
|
||||||
|
using (var texSrv2 = default(ComPtr<ID3D11ShaderResourceView>))
|
||||||
|
{
|
||||||
|
resUnk.As(&texSrv2).ThrowOnError();
|
||||||
|
texSrv.Attach(texSrv2);
|
||||||
|
texSrv2.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
texSrv.Get()->GetDevice(device.GetAddressOf());
|
||||||
|
|
||||||
|
device.Get()->GetImmediateContext(context.GetAddressOf());
|
||||||
|
|
||||||
|
using (var texRes = default(ComPtr<ID3D11Resource>))
|
||||||
|
{
|
||||||
|
texSrv.Get()->GetResource(texRes.GetAddressOf());
|
||||||
|
texRes.As(&tex2D).ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
tex2D.Get()->GetDesc(&texDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newWidth = checked((uint)MathF.Round((uv1.X - uv0.X) * texDesc.Width));
|
||||||
|
var newHeight = checked((uint)MathF.Round((uv1.Y - uv0.Y) * texDesc.Height));
|
||||||
|
|
||||||
using var tex2DCopyTemp = default(ComPtr<ID3D11Texture2D>);
|
using var tex2DCopyTemp = default(ComPtr<ID3D11Texture2D>);
|
||||||
var tex2DCopyTempDesc = new D3D11_TEXTURE2D_DESC
|
unsafe
|
||||||
{
|
{
|
||||||
Width = checked((uint)MathF.Round((uv1.X - uv0.X) * wrap.Width)),
|
var tex2DCopyTempDesc = new D3D11_TEXTURE2D_DESC
|
||||||
Height = checked((uint)MathF.Round((uv1.Y - uv0.Y) * wrap.Height)),
|
{
|
||||||
MipLevels = 1,
|
Width = newWidth,
|
||||||
ArraySize = 1,
|
Height = newHeight,
|
||||||
Format = format,
|
MipLevels = 1,
|
||||||
SampleDesc = new(1, 0),
|
ArraySize = 1,
|
||||||
Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT,
|
Format = format,
|
||||||
BindFlags = (uint)(D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET),
|
SampleDesc = new(1, 0),
|
||||||
CPUAccessFlags = 0u,
|
Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT,
|
||||||
MiscFlags = 0u,
|
BindFlags = (uint)(D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET),
|
||||||
};
|
CPUAccessFlags = 0u,
|
||||||
device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
|
MiscFlags = 0u,
|
||||||
|
};
|
||||||
using (var rtvCopyTemp = default(ComPtr<ID3D11RenderTargetView>))
|
device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
|
||||||
{
|
|
||||||
var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
|
|
||||||
tex2DCopyTemp,
|
|
||||||
D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
|
|
||||||
device.Get()->CreateRenderTargetView(
|
|
||||||
(ID3D11Resource*)tex2DCopyTemp.Get(),
|
|
||||||
&rtvCopyTempDesc,
|
|
||||||
rtvCopyTemp.GetAddressOf()).ThrowOnError();
|
|
||||||
|
|
||||||
this.drawsOneSquare ??= new();
|
|
||||||
this.drawsOneSquare.Setup(device.Get());
|
|
||||||
|
|
||||||
deviceContext.Get()->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
|
|
||||||
this.drawsOneSquare.Draw(
|
|
||||||
deviceContext.Get(),
|
|
||||||
texSrv.Get(),
|
|
||||||
(int)tex2DCopyTempDesc.Width,
|
|
||||||
(int)tex2DCopyTempDesc.Height,
|
|
||||||
uv0,
|
|
||||||
uv1);
|
|
||||||
deviceContext.Get()->OMSetRenderTargets(0, null, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enableCpuRead)
|
await this.interfaceManager.RunBeforePresent(
|
||||||
{
|
() =>
|
||||||
tex2DCopyTemp.Get()->AddRef();
|
{
|
||||||
return tex2DCopyTemp.Get();
|
unsafe
|
||||||
}
|
{
|
||||||
|
using var rtvCopyTemp = default(ComPtr<ID3D11RenderTargetView>);
|
||||||
|
var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
|
||||||
|
tex2DCopyTemp,
|
||||||
|
D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
|
||||||
|
device.Get()->CreateRenderTargetView(
|
||||||
|
(ID3D11Resource*)tex2DCopyTemp.Get(),
|
||||||
|
&rtvCopyTempDesc,
|
||||||
|
rtvCopyTemp.GetAddressOf()).ThrowOnError();
|
||||||
|
|
||||||
using var tex2DTarget = default(ComPtr<ID3D11Texture2D>);
|
this.drawsOneSquare ??= new();
|
||||||
var tex2DTargetDesc = tex2DCopyTempDesc with
|
this.drawsOneSquare.Setup(device.Get());
|
||||||
{
|
|
||||||
Usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC,
|
|
||||||
BindFlags = 0u,
|
|
||||||
CPUAccessFlags = (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ,
|
|
||||||
};
|
|
||||||
device.Get()->CreateTexture2D(&tex2DTargetDesc, null, tex2DTarget.GetAddressOf()).ThrowOnError();
|
|
||||||
|
|
||||||
deviceContext.Get()->CopyResource((ID3D11Resource*)tex2DTarget.Get(), (ID3D11Resource*)tex2DCopyTemp.Get());
|
context.Get()->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
|
||||||
|
this.drawsOneSquare.Draw(
|
||||||
|
context.Get(),
|
||||||
|
texSrv.Get(),
|
||||||
|
(int)newWidth,
|
||||||
|
(int)newHeight,
|
||||||
|
uv0,
|
||||||
|
uv1);
|
||||||
|
context.Get()->OMSetRenderTargets(0, null, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
tex2DTarget.Get()->AddRef();
|
return new(tex2DCopyTemp);
|
||||||
return tex2DTarget.Get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage(
|
[SuppressMessage(
|
||||||
|
|
|
||||||
273
Dalamud/Interface/Internal/TextureManager.Wic.cs
Normal file
273
Dalamud/Interface/Internal/TextureManager.Wic.cs
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
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> factory;
|
||||||
|
|
||||||
|
/// <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())
|
||||||
|
{
|
||||||
|
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[]> GetSupportedImageExtensions() => this.GetSupportedContainerFormats().Values;
|
||||||
|
|
||||||
|
[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()
|
||||||
|
{
|
||||||
|
var result = new Dictionary<Guid, string[]>();
|
||||||
|
using var enumUnknown = default(ComPtr<IEnumUnknown>);
|
||||||
|
this.factory.Get()->CreateComponentEnumerator(
|
||||||
|
(uint)WICComponentType.WICEncoder,
|
||||||
|
(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.factory.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();
|
||||||
|
|
||||||
|
if (guidPixelFormat == GUID.GUID_WICPixelFormat32bppBGRA)
|
||||||
|
{
|
||||||
|
fixed (byte* pByte = bytes)
|
||||||
|
{
|
||||||
|
encoderFrame.Get()->WritePixels(
|
||||||
|
(uint)specs.Height,
|
||||||
|
(uint)specs.Pitch,
|
||||||
|
checked((uint)bytes.Length),
|
||||||
|
pByte).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var tempBitmap = default(ComPtr<IWICBitmap>);
|
||||||
|
fixed (Guid* pGuid = &GUID.GUID_WICPixelFormat32bppBGRA)
|
||||||
|
fixed (byte* pBytes = bytes)
|
||||||
|
{
|
||||||
|
this.factory.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,9 @@ using SharpDX.Direct3D11;
|
||||||
using SharpDX.DXGI;
|
using SharpDX.DXGI;
|
||||||
|
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal;
|
namespace Dalamud.Interface.Internal;
|
||||||
|
|
||||||
|
|
@ -75,8 +78,31 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
|
||||||
|
|
||||||
private bool disposing;
|
private bool disposing;
|
||||||
|
|
||||||
|
[SuppressMessage(
|
||||||
|
"StyleCop.CSharp.LayoutRules",
|
||||||
|
"SA1519:Braces should not be omitted from multi-line child statement",
|
||||||
|
Justification = "Multiple fixed blocks")]
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private TextureManager() => this.framework.Update += this.FrameworkOnUpdate;
|
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.factory.GetAddressOf()).ThrowOnError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Finalizes an instance of the <see cref="TextureManager"/> class.</summary>
|
||||||
|
~TextureManager() => this.Dispose();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
||||||
|
|
@ -114,6 +140,8 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
|
||||||
this.drawsOneSquare?.Dispose();
|
this.drawsOneSquare?.Dispose();
|
||||||
this.drawsOneSquare = null;
|
this.drawsOneSquare = null;
|
||||||
|
|
||||||
|
this.factory.Reset();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
static void ReleaseSelfReferences<T>(ConcurrentDictionary<T, SharedImmediateTexture> dict)
|
static void ReleaseSelfReferences<T>(ConcurrentDictionary<T, SharedImmediateTexture> dict)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Runtime.Loader;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Interface.Internal.SharedImmediateTextures;
|
using Dalamud.Interface.Internal.SharedImmediateTextures;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
|
@ -16,6 +17,8 @@ using Dalamud.Utility;
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
|
|
@ -42,6 +45,7 @@ internal class TexWidget : IDataWindowWidget
|
||||||
private Vector4 inputTintCol = Vector4.One;
|
private Vector4 inputTintCol = Vector4.One;
|
||||||
private Vector2 inputTexScale = Vector2.Zero;
|
private Vector2 inputTexScale = Vector2.Zero;
|
||||||
private TextureManager textureManager = null!;
|
private TextureManager textureManager = null!;
|
||||||
|
private FileDialogManager fileDialogManager = null!;
|
||||||
|
|
||||||
private string[]? supportedRenderTargetFormatNames;
|
private string[]? supportedRenderTargetFormatNames;
|
||||||
private DXGI_FORMAT[]? supportedRenderTargetFormats;
|
private DXGI_FORMAT[]? supportedRenderTargetFormats;
|
||||||
|
|
@ -74,6 +78,7 @@ internal class TexWidget : IDataWindowWidget
|
||||||
this.inputManifestResourceNameIndex = 0;
|
this.inputManifestResourceNameIndex = 0;
|
||||||
this.supportedRenderTargetFormats = null;
|
this.supportedRenderTargetFormats = null;
|
||||||
this.supportedRenderTargetFormatNames = null;
|
this.supportedRenderTargetFormatNames = null;
|
||||||
|
this.fileDialogManager = new();
|
||||||
this.Ready = true;
|
this.Ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,6 +238,8 @@ internal class TexWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
runLater?.Invoke();
|
runLater?.Invoke();
|
||||||
|
|
||||||
|
this.fileDialogManager.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void DrawLoadedTextures(ICollection<SharedImmediateTexture> textures)
|
private unsafe void DrawLoadedTextures(ICollection<SharedImmediateTexture> textures)
|
||||||
|
|
@ -241,11 +248,11 @@ internal class TexWidget : IDataWindowWidget
|
||||||
if (!ImGui.BeginTable("##table", 6))
|
if (!ImGui.BeginTable("##table", 6))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const int numIcons = 3;
|
const int numIcons = 4;
|
||||||
float iconWidths;
|
float iconWidths;
|
||||||
using (im.IconFontHandle?.Push())
|
using (im.IconFontHandle?.Push())
|
||||||
{
|
{
|
||||||
iconWidths = ImGui.CalcTextSize(FontAwesomeIcon.Image.ToIconString()).X;
|
iconWidths = ImGui.CalcTextSize(FontAwesomeIcon.Save.ToIconString()).X;
|
||||||
iconWidths += ImGui.CalcTextSize(FontAwesomeIcon.Sync.ToIconString()).X;
|
iconWidths += ImGui.CalcTextSize(FontAwesomeIcon.Sync.ToIconString()).X;
|
||||||
iconWidths += ImGui.CalcTextSize(FontAwesomeIcon.Trash.ToIconString()).X;
|
iconWidths += ImGui.CalcTextSize(FontAwesomeIcon.Trash.ToIconString()).X;
|
||||||
}
|
}
|
||||||
|
|
@ -315,7 +322,24 @@ internal class TexWidget : IDataWindowWidget
|
||||||
ImGui.TextUnformatted(texture.HasRevivalPossibility ? "Yes" : "No");
|
ImGui.TextUnformatted(texture.HasRevivalPossibility ? "Yes" : "No");
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiComponents.IconButton(FontAwesomeIcon.Image);
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
|
||||||
|
{
|
||||||
|
this.fileDialogManager.SaveFileDialog(
|
||||||
|
"Save texture...",
|
||||||
|
string.Join(
|
||||||
|
',',
|
||||||
|
this.textureManager
|
||||||
|
.GetSupportedImageExtensions()
|
||||||
|
.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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered() && texture.GetWrapOrDefault(null) is { } immediate)
|
if (ImGui.IsItemHovered() && texture.GetWrapOrDefault(null) is { } immediate)
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
|
|
@ -351,6 +375,37 @@ internal class TexWidget : IDataWindowWidget
|
||||||
ImGuiHelpers.ScaledDummy(10);
|
ImGuiHelpers.ScaledDummy(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void SaveImmediateTexture(ISharedImmediateTexture texture, string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var rented = await texture.RentAsync();
|
||||||
|
await this.textureManager.SaveAsImageFormatToStreamAsync(
|
||||||
|
rented,
|
||||||
|
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()
|
private void DrawGetFromGameIcon()
|
||||||
{
|
{
|
||||||
ImGui.InputText("Icon ID", ref this.iconId, 32);
|
ImGui.InputText("Icon ID", ref this.iconId, 32);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -112,9 +113,7 @@ public partial interface ITextureProvider
|
||||||
/// <returns>A texture wrap that can be used to render the texture. Dispose after use.</returns>
|
/// <returns>A texture wrap that can be used to render the texture. Dispose after use.</returns>
|
||||||
IDalamudTextureWrap CreateFromTexFile(TexFile file);
|
IDalamudTextureWrap CreateFromTexFile(TexFile file);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Get a texture handle for the specified Lumina <see cref="TexFile"/>.</summary>
|
||||||
/// Get a texture handle for the specified Lumina <see cref="TexFile"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="file">The texture to obtain a handle to.</param>
|
/// <param name="file">The texture to obtain a handle to.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>A texture wrap that can be used to render the texture. Dispose after use.</returns>
|
/// <returns>A texture wrap that can be used to render the texture. Dispose after use.</returns>
|
||||||
|
|
@ -143,9 +142,7 @@ public partial interface ITextureProvider
|
||||||
/// <returns>The shared texture that you may use to obtain the loaded texture wrap and load states.</returns>
|
/// <returns>The shared texture that you may use to obtain the loaded texture wrap and load states.</returns>
|
||||||
ISharedImmediateTexture GetFromManifestResource(Assembly assembly, string name);
|
ISharedImmediateTexture GetFromManifestResource(Assembly assembly, string name);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Get a path for a specific icon's .tex file.</summary>
|
||||||
/// Get a path for a specific icon's .tex file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lookup">The icon lookup.</param>
|
/// <param name="lookup">The icon lookup.</param>
|
||||||
/// <returns>The path to the icon.</returns>
|
/// <returns>The path to the icon.</returns>
|
||||||
/// <exception cref="FileNotFoundException">If a corresponding file could not be found.</exception>
|
/// <exception cref="FileNotFoundException">If a corresponding file could not be found.</exception>
|
||||||
|
|
@ -159,6 +156,62 @@ public partial interface ITextureProvider
|
||||||
/// <returns><c>true</c> if the corresponding file exists and <paramref name="path"/> has been set.</returns>
|
/// <returns><c>true</c> if the corresponding file exists and <paramref name="path"/> has been set.</returns>
|
||||||
bool TryGetIconPath(in GameIconLookup lookup, [NotNullWhen(true)] out string? path);
|
bool TryGetIconPath(in GameIconLookup lookup, [NotNullWhen(true)] out string? path);
|
||||||
|
|
||||||
|
/// <summary>Gets the raw data of a texture wrap.</summary>
|
||||||
|
/// <param name="wrap">The source texture wrap. The passed value may be disposed once this function returns,
|
||||||
|
/// without having to wait for the completion of the returned <see cref="Task{TResult}"/>.</param>
|
||||||
|
/// <param name="uv0">The left top coordinates relative to the size of the source texture.</param>
|
||||||
|
/// <param name="uv1">The right bottom coordinates relative to the size of the source texture.</param>
|
||||||
|
/// <param name="dxgiFormat">The desired target format.
|
||||||
|
/// If 0 (unknown) is passed, then the format will not be converted.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The raw data and its specifications.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>The length of the returned <c>RawData</c> may not match
|
||||||
|
/// <see cref="RawImageSpecification.Height"/> * <see cref="RawImageSpecification.Pitch"/>.</para>
|
||||||
|
/// <para>If <paramref name="uv0"/> is <see cref="Vector2.Zero"/>,
|
||||||
|
/// <paramref name="uv1"/> is <see cref="Vector2.One"/>, and <paramref name="dxgiFormat"/> is <c>0</c>,
|
||||||
|
/// then the source data will be returned.</para>
|
||||||
|
/// <para>This function can fail.</para>
|
||||||
|
/// </remarks>
|
||||||
|
Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataAsync(
|
||||||
|
IDalamudTextureWrap wrap,
|
||||||
|
Vector2 uv0,
|
||||||
|
Vector2 uv1,
|
||||||
|
int dxgiFormat = 0,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>Gets the supported image file extensions.</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[]> GetSupportedImageExtensions();
|
||||||
|
|
||||||
|
/// <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="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
|
||||||
|
/// <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>
|
||||||
|
/// <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(
|
||||||
|
IDalamudTextureWrap wrap,
|
||||||
|
string extension,
|
||||||
|
Stream stream,
|
||||||
|
bool leaveOpen = false,
|
||||||
|
IReadOnlyDictionary<string, object>? props = null,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the system supports the given DXGI format.
|
/// Determines whether the system supports the given DXGI format.
|
||||||
/// For use with <see cref="RawImageSpecification.DxgiFormat"/>.
|
/// For use with <see cref="RawImageSpecification.DxgiFormat"/>.
|
||||||
|
|
|
||||||
433
Dalamud/Utility/ManagedIStream.cs
Normal file
433
Dalamud/Utility/ManagedIStream.cs
Normal file
|
|
@ -0,0 +1,433 @@
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using TerraFX.Interop;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
namespace Dalamud.Utility;
|
||||||
|
|
||||||
|
/// <summary>An <see cref="IStream"/> wrapper for <see cref="Stream"/>.</summary>
|
||||||
|
[Guid("a620678b-56b9-4202-a1da-b821214dc972")]
|
||||||
|
internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
|
||||||
|
{
|
||||||
|
private static readonly Guid MyGuid = typeof(ManagedIStream).GUID;
|
||||||
|
|
||||||
|
private readonly Stream inner;
|
||||||
|
private readonly nint[] comObject;
|
||||||
|
private readonly IStream.Vtbl<IStream> vtbl;
|
||||||
|
private GCHandle gchThis;
|
||||||
|
private GCHandle gchComObject;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
this.inner = inner;
|
||||||
|
this.comObject = new nint[2];
|
||||||
|
|
||||||
|
this.vtbl.QueryInterface = &QueryInterfaceStatic;
|
||||||
|
this.vtbl.AddRef = &AddRefStatic;
|
||||||
|
this.vtbl.Release = &ReleaseStatic;
|
||||||
|
this.vtbl.Read = &ReadStatic;
|
||||||
|
this.vtbl.Write = &WriteStatic;
|
||||||
|
this.vtbl.Seek = &SeekStatic;
|
||||||
|
this.vtbl.SetSize = &SetSizeStatic;
|
||||||
|
this.vtbl.CopyTo = &CopyToStatic;
|
||||||
|
this.vtbl.Commit = &CommitStatic;
|
||||||
|
this.vtbl.Revert = &RevertStatic;
|
||||||
|
this.vtbl.LockRegion = &LockRegionStatic;
|
||||||
|
this.vtbl.UnlockRegion = &UnlockRegionStatic;
|
||||||
|
this.vtbl.Stat = &StatStatic;
|
||||||
|
this.vtbl.Clone = &CloneStatic;
|
||||||
|
|
||||||
|
this.gchThis = GCHandle.Alloc(this);
|
||||||
|
this.gchVtbl = GCHandle.Alloc(this.vtbl, GCHandleType.Pinned);
|
||||||
|
this.gchComObject = GCHandle.Alloc(this.comObject, GCHandleType.Pinned);
|
||||||
|
this.comObject[0] = this.gchVtbl.AddrOfPinnedObject();
|
||||||
|
this.comObject[1] = GCHandle.ToIntPtr(this.gchThis);
|
||||||
|
this.refCount = 1;
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
static IStream.Interface? ToManagedObject(void* pThis) =>
|
||||||
|
GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as IStream.Interface;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int QueryInterfaceStatic(IStream* pThis, Guid* riid, void** ppvObject) =>
|
||||||
|
ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static uint AddRefStatic(IStream* pThis) => ToManagedObject(pThis)?.AddRef() ?? 0;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static uint ReleaseStatic(IStream* pThis) => ToManagedObject(pThis)?.Release() ?? 0;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int ReadStatic(IStream* pThis, void* pv, uint cb, uint* pcbRead) =>
|
||||||
|
ToManagedObject(pThis)?.Read(pv, cb, pcbRead) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int WriteStatic(IStream* pThis, void* pv, uint cb, uint* pcbWritten) =>
|
||||||
|
ToManagedObject(pThis)?.Write(pv, cb, pcbWritten) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int SeekStatic(
|
||||||
|
IStream* pThis, LARGE_INTEGER dlibMove, uint dwOrigin, ULARGE_INTEGER* plibNewPosition) =>
|
||||||
|
ToManagedObject(pThis)?.Seek(dlibMove, dwOrigin, plibNewPosition) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int SetSizeStatic(IStream* pThis, ULARGE_INTEGER libNewSize) =>
|
||||||
|
ToManagedObject(pThis)?.SetSize(libNewSize) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int CopyToStatic(
|
||||||
|
IStream* pThis, IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead,
|
||||||
|
ULARGE_INTEGER* pcbWritten) =>
|
||||||
|
ToManagedObject(pThis)?.CopyTo(pstm, cb, pcbRead, pcbWritten) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int CommitStatic(IStream* pThis, uint grfCommitFlags) =>
|
||||||
|
ToManagedObject(pThis)?.Commit(grfCommitFlags) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int RevertStatic(IStream* pThis) => ToManagedObject(pThis)?.Revert() ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int LockRegionStatic(IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
|
||||||
|
ToManagedObject(pThis)?.LockRegion(libOffset, cb, dwLockType) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int UnlockRegionStatic(
|
||||||
|
IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
|
||||||
|
ToManagedObject(pThis)?.UnlockRegion(libOffset, cb, dwLockType) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int StatStatic(IStream* pThis, STATSTG* pstatstg, uint grfStatFlag) =>
|
||||||
|
ToManagedObject(pThis)?.Stat(pstatstg, grfStatFlag) ?? E.E_FAIL;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static int CloneStatic(IStream* pThis, IStream** ppstm) => ToManagedObject(pThis)?.Clone(ppstm) ?? E.E_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="INativeGuid.NativeGuid"/>
|
||||||
|
public static Guid* NativeGuid => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in MyGuid));
|
||||||
|
|
||||||
|
public static implicit operator IUnknown*(ManagedIStream mis) =>
|
||||||
|
(IUnknown*)mis.gchComObject.AddrOfPinnedObject();
|
||||||
|
|
||||||
|
public static implicit operator ISequentialStream*(ManagedIStream mis) =>
|
||||||
|
(ISequentialStream*)mis.gchComObject.AddrOfPinnedObject();
|
||||||
|
|
||||||
|
public static implicit operator IStream*(ManagedIStream mis) =>
|
||||||
|
(IStream*)mis.gchComObject.AddrOfPinnedObject();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public HRESULT QueryInterface(Guid* riid, void** ppvObject)
|
||||||
|
{
|
||||||
|
if (ppvObject == null)
|
||||||
|
return E.E_POINTER;
|
||||||
|
|
||||||
|
if (*riid == IID.IID_IUnknown ||
|
||||||
|
*riid == IID.IID_ISequentialStream ||
|
||||||
|
*riid == IID.IID_IStream ||
|
||||||
|
*riid == MyGuid)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.AddRef();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return E.E_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ppvObject = (IUnknown*)this;
|
||||||
|
return S.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ppvObject = null;
|
||||||
|
return E.E_NOINTERFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int AddRef() => IRefCountable.AlterRefCount(1, ref this.refCount, out var newRefCount) switch
|
||||||
|
{
|
||||||
|
IRefCountable.RefCountResult.StillAlive => newRefCount,
|
||||||
|
IRefCountable.RefCountResult.AlreadyDisposed => throw new ObjectDisposedException(nameof(ManagedIStream)),
|
||||||
|
IRefCountable.RefCountResult.FinalRelease => throw new InvalidOperationException(),
|
||||||
|
_ => throw new InvalidOperationException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Release()
|
||||||
|
{
|
||||||
|
switch (IRefCountable.AlterRefCount(-1, ref this.refCount, out var newRefCount))
|
||||||
|
{
|
||||||
|
case IRefCountable.RefCountResult.StillAlive:
|
||||||
|
return newRefCount;
|
||||||
|
|
||||||
|
case IRefCountable.RefCountResult.FinalRelease:
|
||||||
|
this.gchThis.Free();
|
||||||
|
this.gchComObject.Free();
|
||||||
|
this.gchVtbl.Free();
|
||||||
|
return newRefCount;
|
||||||
|
|
||||||
|
case IRefCountable.RefCountResult.AlreadyDisposed:
|
||||||
|
throw new ObjectDisposedException(nameof(ManagedIStream));
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
uint IUnknown.Interface.AddRef()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (uint)this.AddRef();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
uint IUnknown.Interface.Release()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (uint)this.Release();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public HRESULT Read(void* pv, uint cb, uint* pcbRead)
|
||||||
|
{
|
||||||
|
if (pcbRead == null)
|
||||||
|
{
|
||||||
|
var tmp = stackalloc uint[1];
|
||||||
|
pcbRead = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var read = ref *pcbRead;
|
||||||
|
for (read = 0u; read < cb;)
|
||||||
|
{
|
||||||
|
var chunkSize = unchecked((int)Math.Min(0x10000000u, cb));
|
||||||
|
var chunkRead = (uint)this.inner.Read(new(pv, chunkSize));
|
||||||
|
if (chunkRead == 0)
|
||||||
|
break;
|
||||||
|
pv = (byte*)pv + chunkRead;
|
||||||
|
read += chunkRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return read == cb ? S.S_OK : S.S_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public HRESULT Write(void* pv, uint cb, uint* pcbWritten)
|
||||||
|
{
|
||||||
|
if (pcbWritten == null)
|
||||||
|
{
|
||||||
|
var tmp = stackalloc uint[1];
|
||||||
|
pcbWritten = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var written = ref *pcbWritten;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (written = 0u; written < cb;)
|
||||||
|
{
|
||||||
|
var chunkSize = Math.Min(0x10000000u, cb);
|
||||||
|
this.inner.Write(new(pv, (int)chunkSize));
|
||||||
|
pv = (byte*)pv + chunkSize;
|
||||||
|
written += chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return S.S_OK;
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e.HResult == unchecked((int)(0x80070000u | ERROR.ERROR_HANDLE_DISK_FULL)))
|
||||||
|
{
|
||||||
|
return STG.STG_E_MEDIUMFULL;
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e.HResult == unchecked((int)(0x80070000u | ERROR.ERROR_DISK_FULL)))
|
||||||
|
{
|
||||||
|
return STG.STG_E_MEDIUMFULL;
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
return STG.STG_E_CANTSAVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public HRESULT Seek(LARGE_INTEGER dlibMove, uint dwOrigin, ULARGE_INTEGER* plibNewPosition)
|
||||||
|
{
|
||||||
|
SeekOrigin seekOrigin;
|
||||||
|
|
||||||
|
switch ((STREAM_SEEK)dwOrigin)
|
||||||
|
{
|
||||||
|
case STREAM_SEEK.STREAM_SEEK_SET:
|
||||||
|
seekOrigin = SeekOrigin.Begin;
|
||||||
|
break;
|
||||||
|
case STREAM_SEEK.STREAM_SEEK_CUR:
|
||||||
|
seekOrigin = SeekOrigin.Current;
|
||||||
|
break;
|
||||||
|
case STREAM_SEEK.STREAM_SEEK_END:
|
||||||
|
seekOrigin = SeekOrigin.End;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return STG.STG_E_INVALIDFUNCTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var position = this.inner.Seek(dlibMove.QuadPart, seekOrigin);
|
||||||
|
if (plibNewPosition != null)
|
||||||
|
{
|
||||||
|
*plibNewPosition = new() { QuadPart = (ulong)position };
|
||||||
|
}
|
||||||
|
|
||||||
|
return S.S_OK;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return STG.STG_E_INVALIDFUNCTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public HRESULT SetSize(ULARGE_INTEGER libNewSize)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.inner.SetLength(checked((long)libNewSize.QuadPart));
|
||||||
|
return S.S_OK;
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e.HResult == unchecked((int)(0x80070000u | ERROR.ERROR_HANDLE_DISK_FULL)))
|
||||||
|
{
|
||||||
|
return STG.STG_E_MEDIUMFULL;
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e.HResult == unchecked((int)(0x80070000u | ERROR.ERROR_DISK_FULL)))
|
||||||
|
{
|
||||||
|
return STG.STG_E_MEDIUMFULL;
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
return STG.STG_E_INVALIDFUNCTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public HRESULT CopyTo(IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead, ULARGE_INTEGER* pcbWritten)
|
||||||
|
{
|
||||||
|
if (pcbRead == null)
|
||||||
|
{
|
||||||
|
var temp = stackalloc ULARGE_INTEGER[1];
|
||||||
|
pcbRead = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pcbWritten == null)
|
||||||
|
{
|
||||||
|
var temp = stackalloc ULARGE_INTEGER[1];
|
||||||
|
pcbWritten = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var cbRead = ref pcbRead->QuadPart;
|
||||||
|
ref var cbWritten = ref pcbWritten->QuadPart;
|
||||||
|
cbRead = cbWritten = 0;
|
||||||
|
|
||||||
|
var buf = ArrayPool<byte>.Shared.Rent(8192);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fixed (byte* pbuf = buf)
|
||||||
|
{
|
||||||
|
while (cbRead < cb)
|
||||||
|
{
|
||||||
|
var read = checked((uint)this.inner.Read(buf.AsSpan()));
|
||||||
|
if (read == 0)
|
||||||
|
break;
|
||||||
|
cbRead += read;
|
||||||
|
|
||||||
|
var written = 0u;
|
||||||
|
var writeResult = pstm->Write(pbuf, read, &written);
|
||||||
|
if (writeResult.FAILED)
|
||||||
|
return writeResult;
|
||||||
|
cbWritten += written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return S.S_OK;
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e.HResult == unchecked((int)(0x80070000u | ERROR.ERROR_HANDLE_DISK_FULL)))
|
||||||
|
{
|
||||||
|
return STG.STG_E_MEDIUMFULL;
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e.HResult == unchecked((int)(0x80070000u | ERROR.ERROR_DISK_FULL)))
|
||||||
|
{
|
||||||
|
return STG.STG_E_MEDIUMFULL;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// Undefined return value according to the documentation, but meh
|
||||||
|
return e.HResult < 0 ? e.HResult : E.E_FAIL;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
// On streams open in direct mode, this method has no effect.
|
||||||
|
public HRESULT Commit(uint grfCommitFlags) => S.S_OK;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
// On streams open in direct mode, this method has no effect.
|
||||||
|
public HRESULT Revert() => S.S_OK;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
// Locking is not supported at all or the specific type of lock requested is not supported.
|
||||||
|
public HRESULT LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
|
||||||
|
STG.STG_E_INVALIDFUNCTION;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
// Locking is not supported at all or the specific type of lock requested is not supported.
|
||||||
|
public HRESULT UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
|
||||||
|
STG.STG_E_INVALIDFUNCTION;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public HRESULT Stat(STATSTG* pstatstg, uint grfStatFlag)
|
||||||
|
{
|
||||||
|
if (pstatstg is null)
|
||||||
|
return STG.STG_E_INVALIDPOINTER;
|
||||||
|
ref var streamStats = ref *pstatstg;
|
||||||
|
streamStats.type = (uint)STGTY.STGTY_STREAM;
|
||||||
|
streamStats.cbSize = (ulong)this.inner.Length;
|
||||||
|
streamStats.grfMode = 0;
|
||||||
|
if (this.inner.CanRead && this.inner.CanWrite)
|
||||||
|
streamStats.grfMode |= STGM.STGM_READWRITE;
|
||||||
|
else if (this.inner.CanRead)
|
||||||
|
streamStats.grfMode |= STGM.STGM_READ;
|
||||||
|
else if (this.inner.CanWrite)
|
||||||
|
streamStats.grfMode |= STGM.STGM_WRITE;
|
||||||
|
else
|
||||||
|
return STG.STG_E_REVERTED;
|
||||||
|
return S.S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
// Undefined return value according to the documentation, but meh
|
||||||
|
public HRESULT Clone(IStream** ppstm) => E.E_NOTIMPL;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue