diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs index 2f9874217..c9ec65502 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -57,7 +58,9 @@ internal sealed partial class TextureManager async Task 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 { @@ -100,34 +103,19 @@ internal sealed partial class TextureManager bool leaveWrapOpen = false, CancellationToken cancellationToken = default) { - using var wrapDispose = leaveWrapOpen ? null : wrap; - using var texSrv = default(ComPtr); - using var context = default(ComPtr); - using var tex2D = default(ComPtr); - var texDesc = default(D3D11_TEXTURE2D_DESC); + using var wrapAux = new WrapAux(wrap, leaveWrapOpen); + return await this.GetRawImageAsync(wrapAux, args, cancellationToken); + } - 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) - ((IUnknown*)wrap.ImGuiHandle)->QueryInterface(piid, (void**)texSrv.GetAddressOf()).ThrowOnError(); - - this.Device.Get()->GetImmediateContext(context.GetAddressOf()); - - using (var texRes = default(ComPtr)) - { - texSrv.Get()->GetResource(texRes.GetAddressOf()); - - using var tex2DTemp = default(ComPtr); - texRes.As(&tex2DTemp).ThrowOnError(); - tex2D.Swap(&tex2DTemp); - } - - tex2D.Get()->GetDesc(&texDesc); - } - - if (!args.IsCompleteSourceCopy(texDesc)) - { - using var tmp = await this.NoThrottleCreateFromExistingTextureAsync(wrap, args); + using var tmp = await this.NoThrottleCreateFromExistingTextureAsync(wrapAux, args); unsafe { tex2D.Swap(&tmp); @@ -136,11 +124,10 @@ internal sealed partial class TextureManager cancellationToken.ThrowIfCancellationRequested(); return await this.interfaceManager.RunBeforeImGuiRender( - () => ExtractMappedResource(this.Device, context, tex2D, cancellationToken)); + () => ExtractMappedResource(wrapAux, tex2D, cancellationToken)); static unsafe (RawImageSpecification Specification, byte[] RawData) ExtractMappedResource( - ComPtr device, - ComPtr context, + in WrapAux wrapAux, ComPtr tex2D, CancellationToken cancellationToken) { @@ -150,11 +137,9 @@ internal sealed partial class TextureManager try { using var tmpTex = default(ComPtr); - D3D11_TEXTURE2D_DESC desc; - tex2D.Get()->GetDesc(&desc); - if ((desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) == 0) + if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) == 0) { - var tmpTexDesc = desc with + var tmpTexDesc = wrapAux.Desc with { MipLevels = 1, ArraySize = 1, @@ -164,15 +149,15 @@ internal sealed partial class TextureManager 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()); + wrapAux.DevPtr->CreateTexture2D(&tmpTexDesc, null, tmpTex.GetAddressOf()).ThrowOnError(); + wrapAux.CtxPtr->CopyResource((ID3D11Resource*)tmpTex.Get(), (ID3D11Resource*)tex2D.Get()); cancellationToken.ThrowIfCancellationRequested(); } D3D11_MAPPED_SUBRESOURCE mapped; mapWhat = (ID3D11Resource*)(tmpTex.IsEmpty() ? tex2D.Get() : tmpTex.Get()); - context.Get()->Map( + wrapAux.CtxPtr->Map( mapWhat, 0, D3D11_MAP.D3D11_MAP_READ, @@ -180,9 +165,9 @@ internal sealed partial class TextureManager &mapped).ThrowOnError(); var specs = new RawImageSpecification( - (int)desc.Width, - (int)desc.Height, - (int)desc.Format, + (int)wrapAux.Desc.Width, + (int)wrapAux.Desc.Height, + (int)wrapAux.Desc.Format, (int)mapped.RowPitch); var bytes = new Span(mapped.pData, checked((int)mapped.DepthPitch)).ToArray(); return (specs, bytes); @@ -190,44 +175,23 @@ internal sealed partial class TextureManager finally { if (mapWhat is not null) - context.Get()->Unmap(mapWhat, 0); + wrapAux.CtxPtr->Unmap(mapWhat, 0); } } } private async Task> NoThrottleCreateFromExistingTextureAsync( - IDalamudTextureWrap wrap, + WrapAux wrapAux, TextureModificationArgs args) { args.ThrowOnInvalidValues(); - using var texSrv = default(ComPtr); - using var context = default(ComPtr); - using var tex2D = default(ComPtr); - 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)) - { - texSrv.Get()->GetResource(texRes.GetAddressOf()); - texRes.As(&tex2D).ThrowOnError(); - } - - tex2D.Get()->GetDesc(&texDesc); - } - 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) - 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) - 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); unsafe @@ -263,20 +227,120 @@ internal sealed partial class TextureManager &rtvCopyTempDesc, rtvCopyTemp.GetAddressOf()).ThrowOnError(); - context.Get()->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null); + wrapAux.CtxPtr->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null); this.SimpleDrawer.Draw( - context.Get(), - texSrv.Get(), + wrapAux.CtxPtr, + wrapAux.SrvPtr, args.Uv0, args.Uv1Effective); if (args.MakeOpaque) - this.SimpleDrawer.StripAlpha(context.Get()); + this.SimpleDrawer.StripAlpha(wrapAux.CtxPtr); var dummy = default(ID3D11RenderTargetView*); - context.Get()->OMSetRenderTargets(1u, &dummy, null); + wrapAux.CtxPtr->OMSetRenderTargets(1u, &dummy, null); } }); return new(tex2DCopyTemp); } + + /// Auxiliary data from . + private unsafe struct WrapAux : IDisposable + { + public readonly D3D11_TEXTURE2D_DESC Desc; + + private IDalamudTextureWrap? wrapToClose; + + private ComPtr srv; + private ComPtr res; + private ComPtr tex; + private ComPtr device; + private ComPtr context; + + public WrapAux(IDalamudTextureWrap wrap, bool leaveWrapOpen) + { + this.wrapToClose = leaveWrapOpen ? null : wrap; + + using var unk = new ComPtr((IUnknown*)wrap.ImGuiHandle); + + using var srvTemp = default(ComPtr); + unk.As(&srvTemp).ThrowOnError(); + + using var resTemp = default(ComPtr); + srvTemp.Get()->GetResource(resTemp.GetAddressOf()); + + using var texTemp = default(ComPtr); + resTemp.As(&texTemp).ThrowOnError(); + + using var deviceTemp = default(ComPtr); + texTemp.Get()->GetDevice(deviceTemp.GetAddressOf()); + + using var contextTemp = default(ComPtr); + 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 NewSrvRef() => new(this.srv); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ComPtr NewResRef() => new(this.res); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ComPtr NewTexRef() => new(this.tex); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ComPtr NewDevRef() => new(this.device); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ComPtr 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(); + } + } } diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs index 2311923f8..e59a3a1f2 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs @@ -27,124 +27,131 @@ namespace Dalamud.Interface.Textures.Internal; internal sealed partial class TextureManager { /// - [SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1519:Braces should not be omitted from multi-line child statement", - Justification = "Multiple fixed blocks")] public async Task SaveToStreamAsync( - IDalamudTextureWrap wrap, + IDalamudTextureWrap? wrap, Guid containerGuid, - Stream stream, + Stream? stream, IReadOnlyDictionary? props = null, bool leaveWrapOpen = false, bool leaveStreamOpen = false, 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); - if (!WicManager.GetCorrespondingWicPixelFormat(dxgiFormat, out _, out _)) - dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM; + using var istream = ManagedIStream.Create(stream, true); + using var wrapAux = new WrapAux(wrap, true); - 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( - wrap, - new() { Format = dxgiFormat }, - true, - cancellationToken).ConfigureAwait(false); + var (specs, bytes) = await this.GetRawImageAsync(wrapAux, new() { Format = dxgiFormat }, cancellationToken) + .ConfigureAwait(false); - this.Wic.SaveToStreamUsingWic( - specs, - bytes, - containerGuid, - istream, - props, - cancellationToken); + await Task.Run( + () => this.Wic.SaveToStreamUsingWic( + specs, + bytes, + containerGuid, + istream, + props, + cancellationToken), + cancellationToken); + } + finally + { + if (!leaveWrapOpen) + wrap?.Dispose(); + if (!leaveStreamOpen) + stream?.Dispose(); + } } /// public async Task SaveToFileAsync( - IDalamudTextureWrap wrap, + IDalamudTextureWrap? wrap, Guid containerGuid, - string path, + string? path, IReadOnlyDictionary? props = null, bool leaveWrapOpen = false, CancellationToken cancellationToken = default) { - using var wrapDispose = leaveWrapOpen ? null : wrap; - var pathTemp = $"{path}.{GetCurrentThreadId():X08}{Environment.TickCount64:X16}.tmp"; try { - var dxgiFormat = this.GetFormatOf(wrap); - if (!WicManager.GetCorrespondingWicPixelFormat(dxgiFormat, out _, out _)) - dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM; + if (wrap is null) + throw new NullReferenceException($"{nameof(wrap)} cannot be null."); + if (path is null) + throw new NullReferenceException($"{nameof(path)} cannot be null."); - using var istream = TerraFxComInterfaceExtensions.CreateIStreamFromFile( - pathTemp, - FileMode.Create, - 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) - { + using var wrapAux = new WrapAux(wrap, true); + var pathTemp = $"{path}.{GetCurrentThreadId():X08}{Environment.TickCount64:X16}.tmp"; + var trashfire = new List(); try { - if (File.Exists(pathTemp)) - File.Delete(pathTemp); + using (var istream = TerraFxComInterfaceExtensions.CreateIStreamFromFile( + 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( - "Failed to save the file, and failed to remove the temporary file.", - e, - e2); + trashfire.Add(e); + try + { + if (File.Exists(pathTemp)) + File.Delete(pathTemp); + } + catch (Exception e2) + { + trashfire.Add(e2); + } } - throw; + throw new AggregateException($"{nameof(this.SaveToFileAsync)} error.", trashfire); } - - try + finally { - 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; + wrap?.Dispose(); } } @@ -221,26 +228,6 @@ internal sealed partial class TextureManager } } - private unsafe DXGI_FORMAT GetFormatOf(IDalamudTextureWrap wrap) - { - using var texSrv = default(ComPtr); - using var context = default(ComPtr); - 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); - texSrv.Get()->GetResource(texRes.GetAddressOf()); - - using var tex2D = default(ComPtr); - texRes.As(&tex2D).ThrowOnError(); - - var texDesc = default(D3D11_TEXTURE2D_DESC); - tex2D.Get()->GetDesc(&texDesc); - return texDesc.Format; - } - /// A part of texture manager that uses Windows Imaging Component under the hood. internal sealed class WicManager : IDisposable { diff --git a/Dalamud/Plugin/Services/ITextureReadbackProvider.cs b/Dalamud/Plugin/Services/ITextureReadbackProvider.cs index fa666c3c2..96c817091 100644 --- a/Dalamud/Plugin/Services/ITextureReadbackProvider.cs +++ b/Dalamud/Plugin/Services/ITextureReadbackProvider.cs @@ -84,6 +84,7 @@ public interface ITextureReadbackProvider /// A task representing the save process. /// /// must not be disposed until the task finishes. + /// If the target file exists, it will be overwritten only if the save operation is successful. /// See the following webpages for the valid values for per /// . ///