diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
index 85a9507c9..6114c0f62 100644
--- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
@@ -443,6 +443,9 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
///
public double UiBuilderHitch { get; set; } = 100;
+ /// Gets or sets a value indicating whether to track texture allocation by plugins.
+ public bool UseTexturePluginTracking { get; set; }
+
///
/// Gets or sets the page of the plugin installer that is shown by default when opened.
///
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
index 092b3f74f..d14aeb5ab 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
@@ -6,6 +6,7 @@ using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
+using Dalamud.Configuration.Internal;
using Dalamud.Interface.Components;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications;
@@ -108,6 +109,11 @@ internal class TexWidget : IDataWindowWidget
if (ImGui.Button("GC"))
GC.Collect();
+ ImGui.PushID("blames");
+ if (ImGui.CollapsingHeader($"All Loaded Textures: {this.textureManager.AllBlamesForDebug.Count:g}###header"))
+ this.DrawBlame(this.textureManager.AllBlamesForDebug);
+ ImGui.PopID();
+
ImGui.PushID("loadedGameTextures");
if (ImGui.CollapsingHeader(
$"Loaded Game Textures: {this.textureManager.Shared.ForDebugGamePathTextures.Count:g}###header"))
@@ -292,6 +298,96 @@ internal class TexWidget : IDataWindowWidget
this.fileDialogManager.Draw();
}
+ private unsafe void DrawBlame(IReadOnlyList allBlames)
+ {
+ var conf = Service.Get();
+ var im = Service.Get();
+ var blame = conf.UseTexturePluginTracking;
+ if (ImGui.Checkbox("Enable", ref blame))
+ {
+ conf.UseTexturePluginTracking = blame;
+ conf.QueueSave();
+ }
+
+ if (!ImGui.BeginTable("##table", 5))
+ return;
+
+ const int numIcons = 1;
+ float iconWidths;
+ using (im.IconFontHandle?.Push())
+ iconWidths = ImGui.CalcTextSize(FontAwesomeIcon.Save.ToIconString()).X;
+
+ ImGui.TableSetupScrollFreeze(0, 1);
+ ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("00000x00000").X);
+ ImGui.TableSetupColumn(
+ "Format",
+ ImGuiTableColumnFlags.WidthFixed,
+ ImGui.CalcTextSize("R32G32B32A32_TYPELESS").X);
+ ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch);
+ ImGui.TableSetupColumn(
+ "Actions",
+ ImGuiTableColumnFlags.WidthFixed,
+ iconWidths +
+ (ImGui.GetStyle().FramePadding.X * 2 * numIcons) +
+ (ImGui.GetStyle().ItemSpacing.X * 1 * numIcons));
+ ImGui.TableSetupColumn(
+ "Plugins",
+ ImGuiTableColumnFlags.WidthFixed,
+ ImGui.CalcTextSize("Aaaaaaaaaa Aaaaaaaaaa Aaaaaaaaaa").X);
+ ImGui.TableHeadersRow();
+
+ var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
+ clipper.Begin(allBlames.Count);
+
+ while (clipper.Step())
+ {
+ for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
+ {
+ var wrap = allBlames[i];
+ ImGui.TableNextRow();
+ ImGui.PushID(i);
+
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted($"{wrap.Width}x{wrap.Height}");
+
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(Enum.GetName(wrap.Format)?[12..] ?? wrap.Format.ToString());
+
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(wrap.Name);
+
+ ImGui.TableNextColumn();
+ if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
+ {
+ this.SaveTextureAsync(
+ $"{wrap.ImGuiHandle:X16}",
+ () => Task.FromResult(wrap.CreateWrapSharingLowLevelResource()));
+ }
+
+ if (ImGui.IsItemHovered())
+ {
+ ImGui.BeginTooltip();
+ ImGui.Image(wrap.ImGuiHandle, wrap.Size);
+ ImGui.EndTooltip();
+ }
+
+ ImGui.TableNextColumn();
+ lock (wrap.OwnerPlugins)
+ {
+ foreach (var plugin in wrap.OwnerPlugins)
+ ImGui.TextUnformatted(plugin.Name);
+ }
+
+ ImGui.PopID();
+ }
+ }
+
+ clipper.Destroy();
+ ImGui.EndTable();
+
+ ImGuiHelpers.ScaledDummy(10);
+ }
+
private unsafe void DrawLoadedTextures(ICollection textures)
{
var im = Service.Get();
@@ -354,6 +450,7 @@ internal class TexWidget : IDataWindowWidget
}
var remain = texture.SelfReferenceExpiresInForDebug;
+ ImGui.PushID(row);
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
@@ -400,6 +497,8 @@ internal class TexWidget : IDataWindowWidget
ImGui.SetTooltip("Release self-reference immediately.");
if (remain <= 0)
ImGui.EndDisabled();
+
+ ImGui.PopID();
}
if (!valid)
@@ -609,7 +708,8 @@ internal class TexWidget : IDataWindowWidget
new()
{
Api10 = this.textureManager.CreateFromImGuiViewportAsync(
- this.viewportTextureArgs with { ViewportId = viewports[this.viewportIndexInt].ID }),
+ this.viewportTextureArgs with { ViewportId = viewports[this.viewportIndexInt].ID },
+ null),
});
}
}
@@ -758,6 +858,9 @@ internal class TexWidget : IDataWindowWidget
}
catch (Exception e)
{
+ if (e is OperationCanceledException)
+ return;
+
Log.Error(e, $"{nameof(TexWidget)}.{nameof(this.SaveTextureAsync)}");
Service.Get().AddNotification(
$"Failed to save file: {e}",
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
index 39e969fb8..a9b393d3a 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
@@ -45,13 +45,11 @@ internal sealed partial class FontAtlasFactory
DataManager dataManager,
Framework framework,
InterfaceManager interfaceManager,
- DalamudAssetManager dalamudAssetManager,
- TextureManager textureManager)
+ DalamudAssetManager dalamudAssetManager)
{
this.Framework = framework;
this.InterfaceManager = interfaceManager;
this.dalamudAssetManager = dalamudAssetManager;
- this.TextureManager = textureManager;
this.SceneTask = Service
.GetAsync()
.ContinueWith(r => r.Result.Manager.Scene);
@@ -148,7 +146,7 @@ internal sealed partial class FontAtlasFactory
///
/// Gets the service instance of .
///
- public TextureManager TextureManager { get; }
+ public TextureManager TextureManager => Service.Get();
///
/// Gets the async task for inside .
diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
index 4735c1af7..9e6af982d 100644
--- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/FileSystemSharedImmediateTexture.cs
@@ -28,6 +28,8 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
protected override async Task CreateTextureAsync(CancellationToken cancellationToken)
{
var tm = await Service.GetAsync();
- return await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken);
+ var wrap = await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken);
+ tm.BlameSetName(wrap, this.ToString());
+ return wrap;
}
}
diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
index da3cc1a8d..e33091127 100644
--- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
@@ -37,6 +37,8 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
if (dm.GetFile(substPath) is not { } file)
throw new FileNotFoundException();
cancellationToken.ThrowIfCancellationRequested();
- return tm.NoThrottleCreateFromTexFile(file);
+ var wrap = tm.NoThrottleCreateFromTexFile(file);
+ tm.BlameSetName(wrap, this.ToString());
+ return wrap;
}
}
diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
index 00e2f34d5..525e25159 100644
--- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/ManifestResourceSharedImmediateTexture.cs
@@ -40,6 +40,8 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
var tm = await Service.GetAsync();
var ms = new MemoryStream(stream.CanSeek ? checked((int)stream.Length) : 0);
await stream.CopyToAsync(ms, cancellationToken);
- return tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, checked((int)ms.Length)), cancellationToken);
+ var wrap = tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, checked((int)ms.Length)), cancellationToken);
+ tm.BlameSetName(wrap, this.ToString());
+ return wrap;
}
}
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs b/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs
new file mode 100644
index 000000000..a401a9a73
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.BlameTracker.cs
@@ -0,0 +1,373 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Dalamud.Interface.Internal;
+using Dalamud.Plugin.Internal.Types;
+using Dalamud.Plugin.Services;
+using Dalamud.Storage.Assets;
+using Dalamud.Utility;
+
+using TerraFX.Interop;
+using TerraFX.Interop.DirectX;
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Interface.Textures.Internal;
+
+/// Service responsible for loading and disposing ImGui texture wraps.
+internal sealed partial class TextureManager
+{
+ private readonly List blameTracker = new();
+
+ /// A wrapper for underlying texture2D resources.
+ public interface IBlameableDalamudTextureWrap : IDalamudTextureWrap
+ {
+ /// Gets the name of the underlying resource of this texture wrap.
+ public string Name { get; }
+
+ /// Gets the format of the texture.
+ public DXGI_FORMAT Format { get; }
+
+ /// Gets the list of owner plugins.
+ public List OwnerPlugins { get; }
+ }
+
+ /// Gets all the loaded textures from plugins.
+ /// The enumerable that goes through all textures and relevant plugins.
+ /// Returned value must be used inside a lock.
+ [SuppressMessage("ReSharper", "InconsistentlySynchronizedField", Justification = "Caller locks the return value.")]
+ public IReadOnlyList AllBlamesForDebug => this.blameTracker;
+
+ /// Puts a plugin on blame for a texture.
+ /// The texture.
+ /// The plugin.
+ /// Same .
+ public unsafe IDalamudTextureWrap Blame(IDalamudTextureWrap textureWrap, LocalPlugin? ownerPlugin)
+ {
+ if (!this.dalamudConfiguration.UseTexturePluginTracking)
+ return textureWrap;
+
+ using var wrapAux = new WrapAux(textureWrap, true);
+ var blame = BlameTag.From(wrapAux.ResPtr, out var isNew);
+
+ if (ownerPlugin is not null)
+ {
+ lock (blame.OwnerPlugins)
+ blame.OwnerPlugins.Add(ownerPlugin);
+ }
+
+ if (isNew)
+ {
+ lock (this.blameTracker)
+ this.blameTracker.Add(blame);
+ }
+
+ return textureWrap;
+ }
+
+ /// Sets the blame name for a texture.
+ /// The texture.
+ /// The name.
+ /// Same .
+ public unsafe IDalamudTextureWrap BlameSetName(IDalamudTextureWrap textureWrap, string name)
+ {
+ if (!this.dalamudConfiguration.UseTexturePluginTracking)
+ return textureWrap;
+
+ using var wrapAux = new WrapAux(textureWrap, true);
+ var blame = BlameTag.From(wrapAux.ResPtr, out var isNew);
+ blame.Name = name;
+
+ if (isNew)
+ {
+ lock (this.blameTracker)
+ this.blameTracker.Add(blame);
+ }
+
+ return textureWrap;
+ }
+
+ private void BlameTrackerUpdate(IFramework unused)
+ {
+ lock (this.blameTracker)
+ {
+ for (var i = 0; i < this.blameTracker.Count;)
+ {
+ var entry = this.blameTracker[i];
+ if (entry.TestIsReleasedOrShouldRelease())
+ {
+ this.blameTracker[i] = this.blameTracker[^1];
+ this.blameTracker.RemoveAt(this.blameTracker.Count - 1);
+ }
+ else
+ {
+ ++i;
+ }
+ }
+ }
+ }
+
+ /// A COM object that works by tagging itself to a DirectX resource. When the resource destructs, it will
+ /// also release our instance of the tag, letting us know that it is no longer being used, and can be evicted from
+ /// our tracker.
+ [Guid("2c3809e4-4f22-4c50-abde-4f22e5120875")]
+ private sealed unsafe class BlameTag : IUnknown.Interface, IRefCountable, IBlameableDalamudTextureWrap
+ {
+ private static readonly Guid MyGuid = typeof(BlameTag).GUID;
+
+ private readonly nint[] comObject;
+ private readonly IUnknown.Vtbl vtbl;
+ private readonly D3D11_TEXTURE2D_DESC desc;
+
+ private ID3D11Texture2D* tex2D;
+ private GCHandle gchThis;
+ private GCHandle gchComObject;
+ private GCHandle gchVtbl;
+ private int refCount;
+
+ private ComPtr srvDebugPreview;
+ private long srvDebugPreviewExpiryTick;
+
+ private BlameTag(IUnknown* trackWhat)
+ {
+ try
+ {
+ fixed (Guid* piid = &IID.IID_ID3D11Texture2D)
+ fixed (ID3D11Texture2D** ppTex2D = &this.tex2D)
+ trackWhat->QueryInterface(piid, (void**)ppTex2D).ThrowOnError();
+
+ fixed (D3D11_TEXTURE2D_DESC* pDesc = &this.desc)
+ this.tex2D->GetDesc(pDesc);
+
+ this.comObject = new nint[2];
+
+ this.vtbl.QueryInterface = &QueryInterfaceStatic;
+ this.vtbl.AddRef = &AddRefStatic;
+ this.vtbl.Release = &ReleaseStatic;
+
+ 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;
+ }
+ catch
+ {
+ this.refCount = 0;
+ if (this.gchComObject.IsAllocated)
+ this.gchComObject.Free();
+ if (this.gchVtbl.IsAllocated)
+ this.gchVtbl.Free();
+ if (this.gchThis.IsAllocated)
+ this.gchThis.Free();
+ this.tex2D->Release();
+ throw;
+ }
+
+ try
+ {
+ fixed (Guid* pMyGuid = &MyGuid)
+ this.tex2D->SetPrivateDataInterface(pMyGuid, this).ThrowOnError();
+ }
+ finally
+ {
+ // We don't own this.
+ this.tex2D->Release();
+
+ // If the try block above failed, then we will dispose ourselves right away.
+ // Otherwise, we are transferring our ownership to the device child tagging system.
+ this.Release();
+ }
+
+ return;
+
+ [UnmanagedCallersOnly]
+ static int QueryInterfaceStatic(IUnknown* pThis, Guid* riid, void** ppvObject) =>
+ ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED;
+
+ [UnmanagedCallersOnly]
+ static uint AddRefStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0);
+
+ [UnmanagedCallersOnly]
+ static uint ReleaseStatic(IUnknown* pThis) => (uint)(ToManagedObject(pThis)?.Release() ?? 0);
+ }
+
+ ///
+ public static Guid* NativeGuid => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in MyGuid));
+
+ ///
+ public List OwnerPlugins { get; } = new();
+
+ ///
+ public string Name { get; set; } = "";
+
+ ///
+ public DXGI_FORMAT Format => this.desc.Format;
+
+ ///
+ public IntPtr ImGuiHandle
+ {
+ get
+ {
+ if (this.refCount == 0)
+ return Service.Get().Empty4X4.ImGuiHandle;
+
+ this.srvDebugPreviewExpiryTick = Environment.TickCount64 + 1000;
+ if (!this.srvDebugPreview.IsEmpty())
+ return (nint)this.srvDebugPreview.Get();
+ var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
+ this.tex2D,
+ D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
+
+ using var device = default(ComPtr);
+ this.tex2D->GetDevice(device.GetAddressOf());
+
+ using var srv = default(ComPtr);
+ if (device.Get()->CreateShaderResourceView((ID3D11Resource*)this.tex2D, &srvDesc, srv.GetAddressOf())
+ .FAILED)
+ return Service.Get().Empty4X4.ImGuiHandle;
+
+ srv.Swap(ref this.srvDebugPreview);
+ return (nint)this.srvDebugPreview.Get();
+ }
+ }
+
+ ///
+ public int Width => (int)this.desc.Width;
+
+ ///
+ public int Height => (int)this.desc.Height;
+
+ public static implicit operator IUnknown*(BlameTag bt) => (IUnknown*)bt.gchComObject.AddrOfPinnedObject();
+
+ /// Gets or creates an instance of for the given resource.
+ /// The COM object to track.
+ /// true if the tracker is new.
+ /// A COM object type.
+ /// A new instance of .
+ public static BlameTag From(T* trackWhat, out bool isNew) where T : unmanaged, IUnknown.Interface
+ {
+ using var deviceChild = default(ComPtr);
+ fixed (Guid* piid = &IID.IID_ID3D11DeviceChild)
+ trackWhat->QueryInterface(piid, (void**)deviceChild.GetAddressOf()).ThrowOnError();
+
+ fixed (Guid* pMyGuid = &MyGuid)
+ {
+ var dataSize = (uint)sizeof(nint);
+ IUnknown* existingTag;
+ if (deviceChild.Get()->GetPrivateData(pMyGuid, &dataSize, &existingTag).SUCCEEDED)
+ {
+ if (ToManagedObject(existingTag) is { } existingTagInstance)
+ {
+ existingTagInstance.Release();
+ isNew = false;
+ return existingTagInstance;
+ }
+ }
+ }
+
+ isNew = true;
+ return new((IUnknown*)trackWhat);
+ }
+
+ /// Tests whether the tag and the underlying resource are released or should be released.
+ /// true if there are no more remaining references to this instance.
+ public bool TestIsReleasedOrShouldRelease()
+ {
+ if (this.srvDebugPreviewExpiryTick <= Environment.TickCount64)
+ this.srvDebugPreview.Reset();
+
+ return this.refCount == 0;
+ }
+
+ ///
+ public HRESULT QueryInterface(Guid* riid, void** ppvObject)
+ {
+ if (ppvObject == null)
+ return E.E_POINTER;
+
+ if (*riid == IID.IID_IUnknown ||
+ *riid == MyGuid)
+ {
+ try
+ {
+ this.AddRef();
+ }
+ catch
+ {
+ return E.E_FAIL;
+ }
+
+ *ppvObject = (IUnknown*)this;
+ return S.S_OK;
+ }
+
+ *ppvObject = null;
+ return E.E_NOINTERFACE;
+ }
+
+ ///
+ public int AddRef() => IRefCountable.AlterRefCount(1, ref this.refCount, out var newRefCount) switch
+ {
+ IRefCountable.RefCountResult.StillAlive => newRefCount,
+ IRefCountable.RefCountResult.AlreadyDisposed => throw new ObjectDisposedException(nameof(BlameTag)),
+ IRefCountable.RefCountResult.FinalRelease => throw new InvalidOperationException(),
+ _ => throw new InvalidOperationException(),
+ };
+
+ ///
+ 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(BlameTag));
+
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ ///
+ uint IUnknown.Interface.AddRef()
+ {
+ try
+ {
+ return (uint)this.AddRef();
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+ ///
+ uint IUnknown.Interface.Release()
+ {
+ this.srvDebugPreviewExpiryTick = 0;
+ try
+ {
+ return (uint)this.Release();
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static BlameTag? ToManagedObject(void* pThis) =>
+ GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as BlameTag;
+ }
+}
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
index 3f0b69b96..41829f88c 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
+using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@@ -71,23 +72,34 @@ internal sealed partial class TextureManager
var desc = default(D3D11_TEXTURE2D_DESC);
tex.Get()->GetDesc(&desc);
- return new UnknownTextureWrap(
+
+ var outWrap = new UnknownTextureWrap(
(IUnknown*)srv.Get(),
(int)desc.Width,
(int)desc.Height,
true);
+ this.BlameSetName(
+ outWrap,
+ $"{nameof(this.CreateFromExistingTextureAsync)}({nameof(wrap)}, {nameof(args)}, {nameof(leaveWrapOpen)}, {nameof(cancellationToken)})");
+ return outWrap;
}
},
cancellationToken,
leaveWrapOpen ? null : wrap);
///
+ Task ITextureProvider.CreateFromImGuiViewportAsync(
+ ImGuiViewportTextureArgs args,
+ CancellationToken cancellationToken) => this.CreateFromImGuiViewportAsync(args, null, cancellationToken);
+
+ ///
public Task CreateFromImGuiViewportAsync(
ImGuiViewportTextureArgs args,
+ LocalPlugin? ownerPlugin,
CancellationToken cancellationToken = default)
{
args.ThrowOnInvalidValues();
- var t = new ViewportTextureWrap(args, cancellationToken);
+ var t = new ViewportTextureWrap(args, ownerPlugin, cancellationToken);
t.QueueUpdate();
return t.FirstUpdateTask;
}
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.cs b/Dalamud/Interface/Textures/Internal/TextureManager.cs
index 25f3c634e..6d631a8ec 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.cs
@@ -1,5 +1,3 @@
-using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -11,7 +9,6 @@ using Dalamud.Game;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Logging.Internal;
-using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@@ -64,6 +61,8 @@ internal sealed partial class TextureManager
failsafe.Add(this.sharedTextureManager = new(this));
failsafe.Add(this.wicManager = new(this));
failsafe.Add(this.simpleDrawer = new());
+ this.framework.Update += this.BlameTrackerUpdate;
+ failsafe.Add(() => this.framework.Update -= this.BlameTrackerUpdate);
this.simpleDrawer.Setup(this.device.Get());
failsafe.Cancel();
@@ -116,21 +115,18 @@ internal sealed partial class TextureManager
GC.SuppressFinalize(this);
}
- /// Puts a plugin on blame for a texture.
- /// The texture.
- /// The plugin.
- public void Blame(IDalamudTextureWrap textureWrap, LocalPlugin ownerPlugin)
- {
- // nop for now
- }
-
///
public Task CreateFromImageAsync(
ReadOnlyMemory bytes,
CancellationToken cancellationToken = default) =>
this.DynamicPriorityTextureLoader.LoadAsync(
null,
- ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct),
+ ct => Task.Run(
+ () =>
+ this.BlameSetName(
+ this.NoThrottleCreateFromImage(bytes.ToArray(), ct),
+ $"{nameof(this.CreateFromImageAsync)}({nameof(bytes)}, {nameof(cancellationToken)})"),
+ ct),
cancellationToken);
///
@@ -144,7 +140,9 @@ internal sealed partial class TextureManager
{
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
- return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct);
+ return this.BlameSetName(
+ this.NoThrottleCreateFromImage(ms.GetBuffer(), ct),
+ $"{nameof(this.CreateFromImageAsync)}({nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})");
},
cancellationToken,
leaveOpen ? null : stream);
@@ -153,7 +151,10 @@ internal sealed partial class TextureManager
// It probably doesn't make sense to throttle this, as it copies the passed bytes to GPU without any transformation.
public IDalamudTextureWrap CreateFromRaw(
RawImageSpecification specs,
- ReadOnlySpan bytes) => this.NoThrottleCreateFromRaw(specs, bytes);
+ ReadOnlySpan bytes) =>
+ this.BlameSetName(
+ this.NoThrottleCreateFromRaw(specs, bytes),
+ $"{nameof(this.CreateFromRaw)}({nameof(specs)}, {nameof(bytes)})");
///
public Task CreateFromRawAsync(
@@ -162,7 +163,10 @@ internal sealed partial class TextureManager
CancellationToken cancellationToken = default) =>
this.DynamicPriorityTextureLoader.LoadAsync(
null,
- _ => Task.FromResult(this.NoThrottleCreateFromRaw(specs, bytes.Span)),
+ _ => Task.FromResult(
+ this.BlameSetName(
+ this.NoThrottleCreateFromRaw(specs, bytes.Span),
+ $"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(bytes)}, {nameof(cancellationToken)})")),
cancellationToken);
///
@@ -177,13 +181,18 @@ internal sealed partial class TextureManager
{
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
- return this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length));
+ return this.BlameSetName(
+ this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length)),
+ $"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})");
},
cancellationToken,
leaveOpen ? null : stream);
///
- public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result;
+ public IDalamudTextureWrap CreateFromTexFile(TexFile file) =>
+ this.BlameSetName(
+ this.CreateFromTexFileAsync(file).Result,
+ $"{nameof(this.CreateFromTexFile)}({nameof(file)})");
///
public Task CreateFromTexFileAsync(
@@ -191,7 +200,10 @@ internal sealed partial class TextureManager
CancellationToken cancellationToken = default) =>
this.DynamicPriorityTextureLoader.LoadAsync(
null,
- _ => Task.FromResult(this.NoThrottleCreateFromTexFile(file)),
+ _ => Task.FromResult(
+ this.BlameSetName(
+ this.NoThrottleCreateFromTexFile(file),
+ $"{nameof(this.CreateFromTexFile)}({nameof(file)})")),
cancellationToken);
///
@@ -244,7 +256,9 @@ internal sealed partial class TextureManager
this.device.Get()->CreateShaderResourceView((ID3D11Resource*)texture.Get(), &viewDesc, view.GetAddressOf())
.ThrowOnError();
- return new UnknownTextureWrap((IUnknown*)view.Get(), specs.Width, specs.Height, true);
+ var wrap = new UnknownTextureWrap((IUnknown*)view.Get(), specs.Width, specs.Height, true);
+ this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromRaw)}({nameof(specs)}, {nameof(bytes)})");
+ return wrap;
}
/// Creates a texture from the given . Skips the load throttler; intended to be used
@@ -264,9 +278,9 @@ internal sealed partial class TextureManager
buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8);
}
- return this.NoThrottleCreateFromRaw(
- new(buffer.Width, buffer.Height, dxgiFormat),
- buffer.RawData);
+ var wrap = this.NoThrottleCreateFromRaw(new(buffer.Width, buffer.Height, dxgiFormat), buffer.RawData);
+ this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({nameof(file)})");
+ return wrap;
}
/// Creates a texture from the given , trying to interpret it as a
@@ -289,7 +303,10 @@ internal sealed partial class TextureManager
tf,
new object?[] { new LuminaBinaryReader(bytesArray) });
// Note: FileInfo and FilePath are not used from TexFile; skip it.
- return this.NoThrottleCreateFromTexFile(tf);
+
+ var wrap = this.NoThrottleCreateFromTexFile(tf);
+ this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({nameof(fileBytes)})");
+ return wrap;
}
private void ReleaseUnmanagedResources() => this.device.Reset();
diff --git a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs
index 6ec346c30..8971409a7 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs
@@ -40,8 +40,10 @@ internal sealed partial class TextureManagerPluginScoped
private Task? managerTaskNullable;
+ /// Initializes a new instance of the class.
+ /// The plugin.
[ServiceManager.ServiceConstructor]
- private TextureManagerPluginScoped(LocalPlugin plugin)
+ public TextureManagerPluginScoped(LocalPlugin plugin)
{
this.plugin = plugin;
if (plugin.Manifest is LocalPluginManifest lpm)
@@ -155,7 +157,7 @@ internal sealed partial class TextureManagerPluginScoped
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
- var textureWrap = await manager.CreateFromImGuiViewportAsync(args, cancellationToken);
+ var textureWrap = await manager.CreateFromImGuiViewportAsync(args, this.plugin, cancellationToken);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
diff --git a/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs b/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs
index a8692b323..daa247170 100644
--- a/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs
+++ b/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs
@@ -4,6 +4,8 @@ using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Interface.Internal;
+using Dalamud.Plugin.Internal.Types;
+using Dalamud.Storage.Assets;
using Dalamud.Utility;
using ImGuiNET;
@@ -18,6 +20,7 @@ namespace Dalamud.Interface.Textures.Internal;
/// A texture wrap that takes its buffer from the frame buffer (of swap chain).
internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDisposable
{
+ private readonly LocalPlugin? ownerPlugin;
private readonly CancellationToken cancellationToken;
private readonly TaskCompletionSource firstUpdateTaskCompletionSource = new();
@@ -31,10 +34,13 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
/// Initializes a new instance of the class.
/// The arguments for creating a texture.
+ /// The owner plugin.
/// The cancellation token.
- public ViewportTextureWrap(ImGuiViewportTextureArgs args, CancellationToken cancellationToken)
+ public ViewportTextureWrap(
+ ImGuiViewportTextureArgs args, LocalPlugin? ownerPlugin, CancellationToken cancellationToken)
{
this.args = args;
+ this.ownerPlugin = ownerPlugin;
this.cancellationToken = cancellationToken;
}
@@ -42,7 +48,14 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
~ViewportTextureWrap() => this.Dispose(false);
///
- public unsafe nint ImGuiHandle => (nint)this.srv.Get();
+ public unsafe nint ImGuiHandle
+ {
+ get
+ {
+ var t = (nint)this.srv.Get();
+ return t == nint.Zero ? Service.Get().Empty4X4.ImGuiHandle : t;
+ }
+ }
///
public int Width => (int)this.desc.Width;
@@ -134,6 +147,11 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
srvTemp.Swap(ref this.srv);
rtvTemp.Swap(ref this.rtv);
texTemp.Swap(ref this.tex);
+
+ Service.Get().Blame(this, this.ownerPlugin);
+ Service.Get().BlameSetName(
+ this,
+ $"{nameof(ViewportTextureWrap)}({this.args})");
}
// context.Get()->CopyResource((ID3D11Resource*)this.tex.Get(), (ID3D11Resource*)backBuffer.Get());
diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs
index 8874c85f0..3b6a754a9 100644
--- a/Dalamud/Interface/UiBuilder.cs
+++ b/Dalamud/Interface/UiBuilder.cs
@@ -42,6 +42,7 @@ public sealed class UiBuilder : IDisposable
private readonly DalamudConfiguration configuration = Service.Get();
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
+ private readonly TextureManagerPluginScoped scopedTextureProvider;
private bool hasErrorWindow = false;
private bool lastFrameUiHideState = false;
@@ -54,8 +55,9 @@ public sealed class UiBuilder : IDisposable
/// Initializes a new instance of the class and registers it.
/// You do not have to call this manually.
///
+ /// The plugin.
/// The plugin namespace.
- internal UiBuilder(string namespaceName)
+ internal UiBuilder(LocalPlugin plugin, string namespaceName)
{
try
{
@@ -69,6 +71,8 @@ public sealed class UiBuilder : IDisposable
this.interfaceManager.ResizeBuffers += this.OnResizeBuffers;
this.scopedFinalizer.Add(() => this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers);
+ this.scopedFinalizer.Add(this.scopedTextureProvider = new(plugin));
+
this.FontAtlas =
this.scopedFinalizer
.Add(
@@ -381,8 +385,6 @@ public sealed class UiBuilder : IDisposable
private Task InterfaceManagerWithSceneAsync =>
Service.GetAsync().ContinueWith(task => task.Result.Manager);
- private ITextureProvider TextureProvider => Service.Get();
-
///
/// Loads an image from the specified file.
///
@@ -391,7 +393,7 @@ public sealed class UiBuilder : IDisposable
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.GetFromFile)}.")]
public IDalamudTextureWrap LoadImage(string filePath) =>
- this.TextureProvider.GetFromFile(filePath).RentAsync().Result;
+ this.scopedTextureProvider.GetFromFile(filePath).RentAsync().Result;
///
/// Loads an image from a byte stream, such as a png downloaded into memory.
@@ -401,7 +403,7 @@ public sealed class UiBuilder : IDisposable
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.CreateFromImageAsync)}.")]
public IDalamudTextureWrap LoadImage(byte[] imageData) =>
- this.TextureProvider.CreateFromImageAsync(imageData).Result;
+ this.scopedTextureProvider.CreateFromImageAsync(imageData).Result;
///
/// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use .
@@ -416,7 +418,7 @@ public sealed class UiBuilder : IDisposable
public IDalamudTextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) =>
numChannels switch
{
- 4 => this.TextureProvider.CreateFromRaw(RawImageSpecification.Rgba32(width, height), imageData),
+ 4 => this.scopedTextureProvider.CreateFromRaw(RawImageSpecification.Rgba32(width, height), imageData),
_ => throw new NotSupportedException(),
};
@@ -436,7 +438,7 @@ public sealed class UiBuilder : IDisposable
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.GetFromFile)}.")]
public Task LoadImageAsync(string filePath) =>
- this.TextureProvider.GetFromFile(filePath).RentAsync();
+ this.scopedTextureProvider.GetFromFile(filePath).RentAsync();
///
/// Asynchronously loads an image from a byte stream, such as a png downloaded into memory, when it's possible to do so.
@@ -446,7 +448,7 @@ public sealed class UiBuilder : IDisposable
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete($"Use {nameof(ITextureProvider.CreateFromImageAsync)}.")]
public Task LoadImageAsync(byte[] imageData) =>
- this.TextureProvider.CreateFromImageAsync(imageData);
+ this.scopedTextureProvider.CreateFromImageAsync(imageData);
///
/// Asynchronously loads an image from raw unformatted pixel data, with no type or header information, when it's possible to do so. To load formatted data, use .
@@ -461,7 +463,7 @@ public sealed class UiBuilder : IDisposable
public Task LoadImageRawAsync(byte[] imageData, int width, int height, int numChannels) =>
numChannels switch
{
- 4 => this.TextureProvider.CreateFromRawAsync(RawImageSpecification.Rgba32(width, height), imageData),
+ 4 => this.scopedTextureProvider.CreateFromRawAsync(RawImageSpecification.Rgba32(width, height), imageData),
_ => Task.FromException(new NotSupportedException()),
};
diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs
index 82f19aa49..2b184288d 100644
--- a/Dalamud/Plugin/DalamudPluginInterface.cs
+++ b/Dalamud/Plugin/DalamudPluginInterface.cs
@@ -52,7 +52,7 @@ public sealed class DalamudPluginInterface : IDisposable
var dataManager = Service.Get();
var localization = Service.Get();
- this.UiBuilder = new UiBuilder(plugin.Name);
+ this.UiBuilder = new(plugin, plugin.Name);
this.configs = Service.Get().PluginConfigs;
this.Reason = reason;
diff --git a/Dalamud/Utility/TerraFxCom/ManagedIStream.cs b/Dalamud/Utility/TerraFxCom/ManagedIStream.cs
index 942a9baf3..caec65da2 100644
--- a/Dalamud/Utility/TerraFxCom/ManagedIStream.cs
+++ b/Dalamud/Utility/TerraFxCom/ManagedIStream.cs
@@ -54,64 +54,64 @@ internal sealed unsafe class ManagedIStream : IStream.Interface, IRefCountable
return;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- static IStream.Interface? ToManagedObject(void* pThis) =>
- GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as IStream.Interface;
+ static ManagedIStream? ToManagedObject(void* pThis) =>
+ GCHandle.FromIntPtr(((nint*)pThis)[1]).Target as ManagedIStream;
[UnmanagedCallersOnly]
static int QueryInterfaceStatic(IStream* pThis, Guid* riid, void** ppvObject) =>
- ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_FAIL;
+ ToManagedObject(pThis)?.QueryInterface(riid, ppvObject) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
- static uint AddRefStatic(IStream* pThis) => ToManagedObject(pThis)?.AddRef() ?? 0;
+ static uint AddRefStatic(IStream* pThis) => (uint)(ToManagedObject(pThis)?.AddRef() ?? 0);
[UnmanagedCallersOnly]
- static uint ReleaseStatic(IStream* pThis) => ToManagedObject(pThis)?.Release() ?? 0;
+ static uint ReleaseStatic(IStream* pThis) => (uint)(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;
+ ToManagedObject(pThis)?.Read(pv, cb, pcbRead) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
static int WriteStatic(IStream* pThis, void* pv, uint cb, uint* pcbWritten) =>
- ToManagedObject(pThis)?.Write(pv, cb, pcbWritten) ?? E.E_FAIL;
+ ToManagedObject(pThis)?.Write(pv, cb, pcbWritten) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
static int SeekStatic(
IStream* pThis, LARGE_INTEGER dlibMove, uint dwOrigin, ULARGE_INTEGER* plibNewPosition) =>
- ToManagedObject(pThis)?.Seek(dlibMove, dwOrigin, plibNewPosition) ?? E.E_FAIL;
+ ToManagedObject(pThis)?.Seek(dlibMove, dwOrigin, plibNewPosition) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
static int SetSizeStatic(IStream* pThis, ULARGE_INTEGER libNewSize) =>
- ToManagedObject(pThis)?.SetSize(libNewSize) ?? E.E_FAIL;
+ ToManagedObject(pThis)?.SetSize(libNewSize) ?? E.E_UNEXPECTED;
[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;
+ ToManagedObject(pThis)?.CopyTo(pstm, cb, pcbRead, pcbWritten) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
static int CommitStatic(IStream* pThis, uint grfCommitFlags) =>
- ToManagedObject(pThis)?.Commit(grfCommitFlags) ?? E.E_FAIL;
+ ToManagedObject(pThis)?.Commit(grfCommitFlags) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
- static int RevertStatic(IStream* pThis) => ToManagedObject(pThis)?.Revert() ?? E.E_FAIL;
+ static int RevertStatic(IStream* pThis) => ToManagedObject(pThis)?.Revert() ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
static int LockRegionStatic(IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
- ToManagedObject(pThis)?.LockRegion(libOffset, cb, dwLockType) ?? E.E_FAIL;
+ ToManagedObject(pThis)?.LockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
static int UnlockRegionStatic(
IStream* pThis, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, uint dwLockType) =>
- ToManagedObject(pThis)?.UnlockRegion(libOffset, cb, dwLockType) ?? E.E_FAIL;
+ ToManagedObject(pThis)?.UnlockRegion(libOffset, cb, dwLockType) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
static int StatStatic(IStream* pThis, STATSTG* pstatstg, uint grfStatFlag) =>
- ToManagedObject(pThis)?.Stat(pstatstg, grfStatFlag) ?? E.E_FAIL;
+ ToManagedObject(pThis)?.Stat(pstatstg, grfStatFlag) ?? E.E_UNEXPECTED;
[UnmanagedCallersOnly]
- static int CloneStatic(IStream* pThis, IStream** ppstm) => ToManagedObject(pThis)?.Clone(ppstm) ?? E.E_FAIL;
+ static int CloneStatic(IStream* pThis, IStream** ppstm) => ToManagedObject(pThis)?.Clone(ppstm) ?? E.E_UNEXPECTED;
}
///