This commit is contained in:
Soreepeong 2024-03-04 21:13:00 +09:00
parent 5fd7457df4
commit 0a658477c6
3 changed files with 230 additions and 178 deletions

View file

@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -57,7 +58,9 @@ internal sealed partial class TextureManager
async Task<IDalamudTextureWrap> ImmediateLoadFunction(CancellationToken ct) async Task<IDalamudTextureWrap> ImmediateLoadFunction(CancellationToken ct)
{ {
using var tex = await this.NoThrottleCreateFromExistingTextureAsync(wrap, args); // leaveWrapOpen is taken care from calling LoadTextureAsync
using var wrapAux = new WrapAux(wrap, true);
using var tex = await this.NoThrottleCreateFromExistingTextureAsync(wrapAux, args);
unsafe unsafe
{ {
@ -100,34 +103,19 @@ internal sealed partial class TextureManager
bool leaveWrapOpen = false, bool leaveWrapOpen = false,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
using var wrapDispose = leaveWrapOpen ? null : wrap; using var wrapAux = new WrapAux(wrap, leaveWrapOpen);
using var texSrv = default(ComPtr<ID3D11ShaderResourceView>); return await this.GetRawImageAsync(wrapAux, args, cancellationToken);
using var context = default(ComPtr<ID3D11DeviceContext>); }
using var tex2D = default(ComPtr<ID3D11Texture2D>);
var texDesc = default(D3D11_TEXTURE2D_DESC);
unsafe private async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawImageAsync(
WrapAux wrapAux,
TextureModificationArgs args = default,
CancellationToken cancellationToken = default)
{
using var tex2D = wrapAux.NewTexRef();
if (!args.IsCompleteSourceCopy(wrapAux.Desc))
{ {
fixed (Guid* piid = &IID.IID_ID3D11ShaderResourceView) using var tmp = await this.NoThrottleCreateFromExistingTextureAsync(wrapAux, args);
((IUnknown*)wrap.ImGuiHandle)->QueryInterface(piid, (void**)texSrv.GetAddressOf()).ThrowOnError();
this.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 (!args.IsCompleteSourceCopy(texDesc))
{
using var tmp = await this.NoThrottleCreateFromExistingTextureAsync(wrap, args);
unsafe unsafe
{ {
tex2D.Swap(&tmp); tex2D.Swap(&tmp);
@ -136,11 +124,10 @@ internal sealed partial class TextureManager
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
return await this.interfaceManager.RunBeforeImGuiRender( return await this.interfaceManager.RunBeforeImGuiRender(
() => ExtractMappedResource(this.Device, context, tex2D, cancellationToken)); () => ExtractMappedResource(wrapAux, tex2D, cancellationToken));
static unsafe (RawImageSpecification Specification, byte[] RawData) ExtractMappedResource( static unsafe (RawImageSpecification Specification, byte[] RawData) ExtractMappedResource(
ComPtr<ID3D11Device> device, in WrapAux wrapAux,
ComPtr<ID3D11DeviceContext> context,
ComPtr<ID3D11Texture2D> tex2D, ComPtr<ID3D11Texture2D> tex2D,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
@ -150,11 +137,9 @@ internal sealed partial class TextureManager
try try
{ {
using var tmpTex = default(ComPtr<ID3D11Texture2D>); using var tmpTex = default(ComPtr<ID3D11Texture2D>);
D3D11_TEXTURE2D_DESC desc; if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) == 0)
tex2D.Get()->GetDesc(&desc);
if ((desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) == 0)
{ {
var tmpTexDesc = desc with var tmpTexDesc = wrapAux.Desc with
{ {
MipLevels = 1, MipLevels = 1,
ArraySize = 1, ArraySize = 1,
@ -164,15 +149,15 @@ internal sealed partial class TextureManager
CPUAccessFlags = (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ, CPUAccessFlags = (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ,
MiscFlags = 0u, MiscFlags = 0u,
}; };
device.Get()->CreateTexture2D(&tmpTexDesc, null, tmpTex.GetAddressOf()).ThrowOnError(); wrapAux.DevPtr->CreateTexture2D(&tmpTexDesc, null, tmpTex.GetAddressOf()).ThrowOnError();
context.Get()->CopyResource((ID3D11Resource*)tmpTex.Get(), (ID3D11Resource*)tex2D.Get()); wrapAux.CtxPtr->CopyResource((ID3D11Resource*)tmpTex.Get(), (ID3D11Resource*)tex2D.Get());
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
} }
D3D11_MAPPED_SUBRESOURCE mapped; D3D11_MAPPED_SUBRESOURCE mapped;
mapWhat = (ID3D11Resource*)(tmpTex.IsEmpty() ? tex2D.Get() : tmpTex.Get()); mapWhat = (ID3D11Resource*)(tmpTex.IsEmpty() ? tex2D.Get() : tmpTex.Get());
context.Get()->Map( wrapAux.CtxPtr->Map(
mapWhat, mapWhat,
0, 0,
D3D11_MAP.D3D11_MAP_READ, D3D11_MAP.D3D11_MAP_READ,
@ -180,9 +165,9 @@ internal sealed partial class TextureManager
&mapped).ThrowOnError(); &mapped).ThrowOnError();
var specs = new RawImageSpecification( var specs = new RawImageSpecification(
(int)desc.Width, (int)wrapAux.Desc.Width,
(int)desc.Height, (int)wrapAux.Desc.Height,
(int)desc.Format, (int)wrapAux.Desc.Format,
(int)mapped.RowPitch); (int)mapped.RowPitch);
var bytes = new Span<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray(); var bytes = new Span<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray();
return (specs, bytes); return (specs, bytes);
@ -190,44 +175,23 @@ internal sealed partial class TextureManager
finally finally
{ {
if (mapWhat is not null) if (mapWhat is not null)
context.Get()->Unmap(mapWhat, 0); wrapAux.CtxPtr->Unmap(mapWhat, 0);
} }
} }
} }
private async Task<ComPtr<ID3D11Texture2D>> NoThrottleCreateFromExistingTextureAsync( private async Task<ComPtr<ID3D11Texture2D>> NoThrottleCreateFromExistingTextureAsync(
IDalamudTextureWrap wrap, WrapAux wrapAux,
TextureModificationArgs args) TextureModificationArgs args)
{ {
args.ThrowOnInvalidValues(); args.ThrowOnInvalidValues();
using var texSrv = default(ComPtr<ID3D11ShaderResourceView>);
using var context = default(ComPtr<ID3D11DeviceContext>);
using var tex2D = default(ComPtr<ID3D11Texture2D>);
var texDesc = default(D3D11_TEXTURE2D_DESC);
unsafe
{
fixed (Guid* piid = &IID.IID_ID3D11ShaderResourceView)
((IUnknown*)wrap.ImGuiHandle)->QueryInterface(piid, (void**)texSrv.GetAddressOf()).ThrowOnError();
this.Device.Get()->GetImmediateContext(context.GetAddressOf());
using (var texRes = default(ComPtr<ID3D11Resource>))
{
texSrv.Get()->GetResource(texRes.GetAddressOf());
texRes.As(&tex2D).ThrowOnError();
}
tex2D.Get()->GetDesc(&texDesc);
}
if (args.Format == DXGI_FORMAT.DXGI_FORMAT_UNKNOWN) if (args.Format == DXGI_FORMAT.DXGI_FORMAT_UNKNOWN)
args = args with { Format = texDesc.Format }; args = args with { Format = wrapAux.Desc.Format };
if (args.NewWidth == 0) if (args.NewWidth == 0)
args = args with { NewWidth = (int)MathF.Round((args.Uv1Effective.X - args.Uv0.X) * texDesc.Width) }; args = args with { NewWidth = (int)MathF.Round((args.Uv1Effective.X - args.Uv0.X) * wrapAux.Desc.Width) };
if (args.NewHeight == 0) if (args.NewHeight == 0)
args = args with { NewHeight = (int)MathF.Round((args.Uv1Effective.Y - args.Uv0.Y) * texDesc.Height) }; args = args with { NewHeight = (int)MathF.Round((args.Uv1Effective.Y - args.Uv0.Y) * wrapAux.Desc.Height) };
using var tex2DCopyTemp = default(ComPtr<ID3D11Texture2D>); using var tex2DCopyTemp = default(ComPtr<ID3D11Texture2D>);
unsafe unsafe
@ -263,20 +227,120 @@ internal sealed partial class TextureManager
&rtvCopyTempDesc, &rtvCopyTempDesc,
rtvCopyTemp.GetAddressOf()).ThrowOnError(); rtvCopyTemp.GetAddressOf()).ThrowOnError();
context.Get()->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null); wrapAux.CtxPtr->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
this.SimpleDrawer.Draw( this.SimpleDrawer.Draw(
context.Get(), wrapAux.CtxPtr,
texSrv.Get(), wrapAux.SrvPtr,
args.Uv0, args.Uv0,
args.Uv1Effective); args.Uv1Effective);
if (args.MakeOpaque) if (args.MakeOpaque)
this.SimpleDrawer.StripAlpha(context.Get()); this.SimpleDrawer.StripAlpha(wrapAux.CtxPtr);
var dummy = default(ID3D11RenderTargetView*); var dummy = default(ID3D11RenderTargetView*);
context.Get()->OMSetRenderTargets(1u, &dummy, null); wrapAux.CtxPtr->OMSetRenderTargets(1u, &dummy, null);
} }
}); });
return new(tex2DCopyTemp); return new(tex2DCopyTemp);
} }
/// <summary>Auxiliary data from <see cref="IDalamudTextureWrap"/>.</summary>
private unsafe struct WrapAux : IDisposable
{
public readonly D3D11_TEXTURE2D_DESC Desc;
private IDalamudTextureWrap? wrapToClose;
private ComPtr<ID3D11ShaderResourceView> srv;
private ComPtr<ID3D11Resource> res;
private ComPtr<ID3D11Texture2D> tex;
private ComPtr<ID3D11Device> device;
private ComPtr<ID3D11DeviceContext> context;
public WrapAux(IDalamudTextureWrap wrap, bool leaveWrapOpen)
{
this.wrapToClose = leaveWrapOpen ? null : wrap;
using var unk = new ComPtr<IUnknown>((IUnknown*)wrap.ImGuiHandle);
using var srvTemp = default(ComPtr<ID3D11ShaderResourceView>);
unk.As(&srvTemp).ThrowOnError();
using var resTemp = default(ComPtr<ID3D11Resource>);
srvTemp.Get()->GetResource(resTemp.GetAddressOf());
using var texTemp = default(ComPtr<ID3D11Texture2D>);
resTemp.As(&texTemp).ThrowOnError();
using var deviceTemp = default(ComPtr<ID3D11Device>);
texTemp.Get()->GetDevice(deviceTemp.GetAddressOf());
using var contextTemp = default(ComPtr<ID3D11DeviceContext>);
deviceTemp.Get()->GetImmediateContext(contextTemp.GetAddressOf());
fixed (D3D11_TEXTURE2D_DESC* pDesc = &this.Desc)
texTemp.Get()->GetDesc(pDesc);
srvTemp.Swap(ref this.srv);
resTemp.Swap(ref this.res);
texTemp.Swap(ref this.tex);
deviceTemp.Swap(ref this.device);
contextTemp.Swap(ref this.context);
}
public ID3D11ShaderResourceView* SrvPtr
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.srv.Get();
}
public ID3D11Resource* ResPtr
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.res.Get();
}
public ID3D11Texture2D* TexPtr
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.tex.Get();
}
public ID3D11Device* DevPtr
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.device.Get();
}
public ID3D11DeviceContext* CtxPtr
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.context.Get();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComPtr<ID3D11ShaderResourceView> NewSrvRef() => new(this.srv);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComPtr<ID3D11Resource> NewResRef() => new(this.res);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComPtr<ID3D11Texture2D> NewTexRef() => new(this.tex);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComPtr<ID3D11Device> NewDevRef() => new(this.device);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComPtr<ID3D11DeviceContext> NewCtxRef() => new(this.context);
public void Dispose()
{
this.srv.Reset();
this.res.Reset();
this.tex.Reset();
this.device.Reset();
this.context.Reset();
Interlocked.Exchange(ref this.wrapToClose, null)?.Dispose();
}
}
} }

View file

@ -27,124 +27,131 @@ namespace Dalamud.Interface.Textures.Internal;
internal sealed partial class TextureManager internal sealed partial class TextureManager
{ {
/// <inheritdoc/> /// <inheritdoc/>
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed blocks")]
public async Task SaveToStreamAsync( public async Task SaveToStreamAsync(
IDalamudTextureWrap wrap, IDalamudTextureWrap? wrap,
Guid containerGuid, Guid containerGuid,
Stream stream, Stream? stream,
IReadOnlyDictionary<string, object>? props = null, IReadOnlyDictionary<string, object>? props = null,
bool leaveWrapOpen = false, bool leaveWrapOpen = false,
bool leaveStreamOpen = false, bool leaveStreamOpen = false,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
using var wrapDispose = leaveWrapOpen ? null : wrap; try
{
if (wrap is null)
throw new NullReferenceException($"{nameof(wrap)} cannot be null.");
if (stream is null)
throw new NullReferenceException($"{nameof(stream)} cannot be null.");
var dxgiFormat = this.GetFormatOf(wrap); using var istream = ManagedIStream.Create(stream, true);
if (!WicManager.GetCorrespondingWicPixelFormat(dxgiFormat, out _, out _)) using var wrapAux = new WrapAux(wrap, true);
dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
using var istream = ManagedIStream.Create(stream, leaveStreamOpen); var dxgiFormat =
WicManager.GetCorrespondingWicPixelFormat(wrapAux.Desc.Format, out _, out _)
? wrapAux.Desc.Format
: DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
var (specs, bytes) = await this.GetRawImageAsync( var (specs, bytes) = await this.GetRawImageAsync(wrapAux, new() { Format = dxgiFormat }, cancellationToken)
wrap, .ConfigureAwait(false);
new() { Format = dxgiFormat },
true,
cancellationToken).ConfigureAwait(false);
this.Wic.SaveToStreamUsingWic( await Task.Run(
specs, () => this.Wic.SaveToStreamUsingWic(
bytes, specs,
containerGuid, bytes,
istream, containerGuid,
props, istream,
cancellationToken); props,
cancellationToken),
cancellationToken);
}
finally
{
if (!leaveWrapOpen)
wrap?.Dispose();
if (!leaveStreamOpen)
stream?.Dispose();
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task SaveToFileAsync( public async Task SaveToFileAsync(
IDalamudTextureWrap wrap, IDalamudTextureWrap? wrap,
Guid containerGuid, Guid containerGuid,
string path, string? path,
IReadOnlyDictionary<string, object>? props = null, IReadOnlyDictionary<string, object>? props = null,
bool leaveWrapOpen = false, bool leaveWrapOpen = false,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
using var wrapDispose = leaveWrapOpen ? null : wrap;
var pathTemp = $"{path}.{GetCurrentThreadId():X08}{Environment.TickCount64:X16}.tmp";
try try
{ {
var dxgiFormat = this.GetFormatOf(wrap); if (wrap is null)
if (!WicManager.GetCorrespondingWicPixelFormat(dxgiFormat, out _, out _)) throw new NullReferenceException($"{nameof(wrap)} cannot be null.");
dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM; if (path is null)
throw new NullReferenceException($"{nameof(path)} cannot be null.");
using var istream = TerraFxComInterfaceExtensions.CreateIStreamFromFile( using var wrapAux = new WrapAux(wrap, true);
pathTemp, var pathTemp = $"{path}.{GetCurrentThreadId():X08}{Environment.TickCount64:X16}.tmp";
FileMode.Create, var trashfire = new List<Exception>();
FileAccess.Write,
FileShare.None);
var (specs, bytes) = await this.GetRawImageAsync(
wrap,
new() { Format = dxgiFormat },
true,
cancellationToken).ConfigureAwait(false);
this.Wic.SaveToStreamUsingWic(
specs,
bytes,
containerGuid,
istream,
props,
cancellationToken);
}
catch (Exception e)
{
try try
{ {
if (File.Exists(pathTemp)) using (var istream = TerraFxComInterfaceExtensions.CreateIStreamFromFile(
File.Delete(pathTemp); pathTemp,
FileMode.Create,
FileAccess.Write,
FileShare.None))
{
var dxgiFormat =
WicManager.GetCorrespondingWicPixelFormat(wrapAux.Desc.Format, out _, out _)
? wrapAux.Desc.Format
: DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
var (specs, bytes) = await this.GetRawImageAsync(
wrapAux,
new() { Format = dxgiFormat },
cancellationToken).ConfigureAwait(false);
await Task.Run(
() => this.Wic.SaveToStreamUsingWic(
specs,
bytes,
containerGuid,
istream,
props,
cancellationToken),
cancellationToken);
}
try
{
File.Replace(pathTemp, path, null, true);
}
catch (Exception e)
{
trashfire.Add(e);
File.Move(pathTemp, path, true);
}
return;
} }
catch (Exception e2) catch (Exception e)
{ {
throw new AggregateException( trashfire.Add(e);
"Failed to save the file, and failed to remove the temporary file.", try
e, {
e2); if (File.Exists(pathTemp))
File.Delete(pathTemp);
}
catch (Exception e2)
{
trashfire.Add(e2);
}
} }
throw; throw new AggregateException($"{nameof(this.SaveToFileAsync)} error.", trashfire);
} }
finally
try
{ {
try wrap?.Dispose();
{
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;
} }
} }
@ -221,26 +228,6 @@ internal sealed partial class TextureManager
} }
} }
private unsafe DXGI_FORMAT GetFormatOf(IDalamudTextureWrap wrap)
{
using var texSrv = default(ComPtr<ID3D11ShaderResourceView>);
using var context = default(ComPtr<ID3D11DeviceContext>);
fixed (Guid* piid = &IID.IID_ID3D11ShaderResourceView)
((IUnknown*)wrap.ImGuiHandle)->QueryInterface(piid, (void**)texSrv.GetAddressOf()).ThrowOnError();
this.Device.Get()->GetImmediateContext(context.GetAddressOf());
using var texRes = default(ComPtr<ID3D11Resource>);
texSrv.Get()->GetResource(texRes.GetAddressOf());
using var tex2D = default(ComPtr<ID3D11Texture2D>);
texRes.As(&tex2D).ThrowOnError();
var texDesc = default(D3D11_TEXTURE2D_DESC);
tex2D.Get()->GetDesc(&texDesc);
return texDesc.Format;
}
/// <summary>A part of texture manager that uses Windows Imaging Component under the hood.</summary> /// <summary>A part of texture manager that uses Windows Imaging Component under the hood.</summary>
internal sealed class WicManager : IDisposable internal sealed class WicManager : IDisposable
{ {

View file

@ -84,6 +84,7 @@ public interface ITextureReadbackProvider
/// <returns>A task representing the save process.</returns> /// <returns>A task representing the save process.</returns>
/// <remarks> /// <remarks>
/// <para><paramref name="wrap"/> must not be disposed until the task finishes.</para> /// <para><paramref name="wrap"/> must not be disposed until the task finishes.</para>
/// <para>If the target file exists, it will be overwritten only if the save operation is successful.</para>
/// <para>See the following webpages for the valid values for <paramref name="props"/> per /// <para>See the following webpages for the valid values for <paramref name="props"/> per
/// <paramref name="containerGuid"/>.</para> /// <paramref name="containerGuid"/>.</para>
/// <ul> /// <ul>