This commit is contained in:
Soreepeong 2024-03-05 17:41:27 +09:00
parent 9ba0a297c9
commit 70eecdaaef
30 changed files with 767 additions and 345 deletions

View file

@ -0,0 +1,55 @@
using System.Text;
namespace Dalamud.Interface.Textures;
/// <summary>Represents a lookup for a game icon.</summary>
public readonly record struct GameIconLookup
{
/// <summary>Initializes a new instance of the <see cref="GameIconLookup"/> class.</summary>
/// <param name="iconId">The icon ID.</param>
/// <param name="itemHq">Whether the HQ icon is requested, where HQ is in the context of items.</param>
/// <param name="hiRes">Whether the high-resolution icon is requested.</param>
/// <param name="language">The language of the icon to load.</param>
public GameIconLookup(uint iconId, bool itemHq = false, bool hiRes = true, ClientLanguage? language = null)
{
this.IconId = iconId;
this.ItemHq = itemHq;
this.HiRes = hiRes;
this.Language = language;
}
public static implicit operator GameIconLookup(int iconId) => new(checked((uint)iconId));
public static implicit operator GameIconLookup(uint iconId) => new(iconId);
/// <summary>Gets the icon ID.</summary>
public uint IconId { get; init; }
/// <summary>Gets a value indicating whether the HQ icon is requested, where HQ is in the context of items.</summary>
public bool ItemHq { get; init; }
/// <summary>Gets a value indicating whether the high-resolution icon is requested.</summary>
public bool HiRes { get; init; }
/// <summary>Gets the language of the icon to load.</summary>
/// <remarks>
/// <para><c>null</c> will use the active game language.</para>
/// <para>If the specified resource does not have variants per language, the language-neutral texture will be used.
/// </para>
/// </remarks>
public ClientLanguage? Language { get; init; }
/// <inheritdoc/>
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(nameof(GameIconLookup)).Append('(').Append(this.IconId);
if (this.ItemHq)
sb.Append(", HQ");
if (this.HiRes)
sb.Append(", HR1");
if (this.Language is not null)
sb.Append(", ").Append(Enum.GetName(this.Language.Value));
return sb.Append(')').ToString();
}
}

View file

@ -1,9 +1,12 @@
using System.Numerics;
using System.Text;
using Dalamud.Interface.Internal;
using ImGuiNET;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.Textures;
/// <summary>Describes how to take a texture of an existing ImGui viewport.</summary>
@ -44,6 +47,30 @@ public record struct ImGuiViewportTextureArgs()
/// <summary>Gets the effective value of <see cref="Uv1"/>.</summary>
internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1;
/// <inheritdoc/>
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(nameof(ImGuiViewportTextureArgs)).Append('(');
sb.Append($"0x{this.ViewportId:X}");
if (this.AutoUpdate)
sb.Append($", {nameof(this.AutoUpdate)}");
if (this.TakeBeforeImGuiRender)
sb.Append($", {nameof(this.TakeBeforeImGuiRender)}");
if (this.KeepTransparency)
sb.Append($", {nameof(this.KeepTransparency)}");
if (this.Uv0 != Vector2.Zero || this.Uv1Effective != Vector2.One)
{
sb.Append(", ")
.Append(this.Uv0.ToString())
.Append('-')
.Append(this.Uv1.ToString());
}
return sb.Append(')').ToString();
}
/// <summary>Checks the properties and throws an exception if values are invalid.</summary>
internal void ThrowOnInvalidValues()
{

View file

@ -18,6 +18,7 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
/// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary>
/// <param name="path">The path.</param>
/// <returns>The new instance.</returns>
/// <remarks>Only to be used from <see cref="TextureManager.SharedTextureManager.GetFromFile"/>.</remarks>
public static SharedImmediateTexture CreatePlaceholder(string path) => new FileSystemSharedImmediateTexture(path);
/// <inheritdoc/>

View file

@ -22,6 +22,7 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
/// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary>
/// <param name="path">The path.</param>
/// <returns>The new instance.</returns>
/// <remarks>Only to be used from <see cref="TextureManager.SharedTextureManager.GetFromGame"/>.</remarks>
public static SharedImmediateTexture CreatePlaceholder(string path) => new GamePathSharedImmediateTexture(path);
/// <inheritdoc/>

View file

@ -27,6 +27,8 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
/// <summary>Creates a new placeholder instance of <see cref="ManifestResourceSharedImmediateTexture"/>.</summary>
/// <param name="args">The arguments to pass to the constructor.</param>
/// <returns>The new instance.</returns>
/// <remarks>Only to be used from <see cref="TextureManager.SharedTextureManager.GetFromManifestResource"/>.
/// </remarks>
public static SharedImmediateTexture CreatePlaceholder((Assembly Assembly, string Name) args) =>
new ManifestResourceSharedImmediateTexture(args.Assembly, args.Name);

View file

@ -6,6 +6,8 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
@ -22,6 +24,7 @@ internal abstract class SharedImmediateTexture
private static long instanceCounter;
private readonly object reviveLock = new();
private readonly List<LocalPlugin> ownerPlugins = new();
private bool resourceReleased;
private int refCount;
@ -43,10 +46,11 @@ internal abstract class SharedImmediateTexture
this.IsOpportunistic = true;
this.resourceReleased = true;
this.FirstRequestedTick = this.LatestRequestedTick = Environment.TickCount64;
this.PublicUseInstance = new(this);
}
/// <summary>Gets the list of owner plugins.</summary>
public List<LocalPlugin> OwnerPlugins { get; } = new();
/// <summary>Gets a wrapper for this instance which disables resource reference management.</summary>
public PureImpl PublicUseInstance { get; }
/// <summary>Gets the instance ID. Debug use only.</summary>
public long InstanceIdForDebug { get; }
@ -280,15 +284,15 @@ internal abstract class SharedImmediateTexture
return this.availableOnAccessWrapForApi9;
}
/// <summary>Adds a plugin to <see cref="OwnerPlugins"/>, in a thread-safe way.</summary>
/// <summary>Adds a plugin to <see cref="ownerPlugins"/>, in a thread-safe way.</summary>
/// <param name="plugin">The plugin to add.</param>
public void AddOwnerPlugin(LocalPlugin plugin)
{
lock (this.OwnerPlugins)
lock (this.ownerPlugins)
{
if (!this.OwnerPlugins.Contains(plugin))
if (!this.ownerPlugins.Contains(plugin))
{
this.OwnerPlugins.Add(plugin);
this.ownerPlugins.Add(plugin);
this.UnderlyingWrap?.ContinueWith(
r =>
{
@ -314,14 +318,14 @@ internal abstract class SharedImmediateTexture
protected void LoadUnderlyingWrap()
{
int addLen;
lock (this.OwnerPlugins)
lock (this.ownerPlugins)
{
this.UnderlyingWrap = Service<TextureManager>.Get().DynamicPriorityTextureLoader.LoadAsync(
this,
this.CreateTextureAsync,
this.LoadCancellationToken);
addLen = this.OwnerPlugins.Count;
addLen = this.ownerPlugins.Count;
}
if (addLen == 0)
@ -331,8 +335,11 @@ internal abstract class SharedImmediateTexture
{
if (!r.IsCompletedSuccessfully)
return;
foreach (var op in this.OwnerPlugins.Take(addLen))
Service<TextureManager>.Get().Blame(r.Result, op);
lock (this.ownerPlugins)
{
foreach (var op in this.ownerPlugins.Take(addLen))
Service<TextureManager>.Get().Blame(r.Result, op);
}
},
default(CancellationToken));
}
@ -427,6 +434,52 @@ internal abstract class SharedImmediateTexture
}
}
/// <summary>A wrapper around <see cref="SharedImmediateTexture"/>, to prevent external consumers from mistakenly
/// calling <see cref="IDisposable.Dispose"/> or <see cref="IRefCountable.Release"/>.</summary>
internal sealed class PureImpl : ISharedImmediateTexture
{
private readonly SharedImmediateTexture inner;
/// <summary>Initializes a new instance of the <see cref="PureImpl"/> class.</summary>
/// <param name="inner">The actual instance.</param>
public PureImpl(SharedImmediateTexture inner) => this.inner = inner;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDalamudTextureWrap GetWrapOrEmpty() =>
this.inner.GetWrapOrEmpty();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNullIfNotNull(nameof(defaultWrap))]
public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap = null) =>
this.inner.GetWrapOrDefault(defaultWrap);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception) =>
this.inner.TryGetWrap(out texture, out exception);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Task<IDalamudTextureWrap> RentAsync(CancellationToken cancellationToken = default) =>
this.inner.RentAsync(cancellationToken);
/// <inheritdoc cref="SharedImmediateTexture.GetAvailableOnAccessWrapForApi9"/>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDalamudTextureWrap? GetAvailableOnAccessWrapForApi9() =>
this.inner.GetAvailableOnAccessWrapForApi9();
/// <inheritdoc cref="SharedImmediateTexture.AddOwnerPlugin"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddOwnerPlugin(LocalPlugin plugin) =>
this.inner.AddOwnerPlugin(plugin);
/// <inheritdoc/>
public override string ToString() => $"{this.inner}({nameof(PureImpl)})";
}
/// <summary>Same with <see cref="DisposeSuppressingTextureWrap"/>, but with a custom implementation of
/// <see cref="CreateWrapSharingLowLevelResource"/>.</summary>
private sealed class NotOwnedTextureWrap : DisposeSuppressingTextureWrap

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -18,11 +17,12 @@ namespace Dalamud.Interface.Textures.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
internal sealed partial class TextureManager
{
private readonly List<BlameTag> blameTracker = new();
/// <summary>A wrapper for underlying texture2D resources.</summary>
public interface IBlameableDalamudTextureWrap : IDalamudTextureWrap
{
/// <summary>Gets the address of the native resource.</summary>
public nint ResourceAddress { get; }
/// <summary>Gets the name of the underlying resource of this texture wrap.</summary>
public string Name { get; }
@ -31,13 +31,27 @@ internal sealed partial class TextureManager
/// <summary>Gets the list of owner plugins.</summary>
public List<LocalPlugin> OwnerPlugins { get; }
/// <summary>Gets the raw image specification.</summary>
public RawImageSpecification RawSpecs { get; }
/// <summary>Tests whether the tag and the underlying resource are released or should be released.</summary>
/// <returns><c>true</c> if there are no more remaining references to this instance.</returns>
bool TestIsReleasedOrShouldRelease();
}
/// <summary>Gets all the loaded textures from plugins.</summary>
/// <returns>The enumerable that goes through all textures and relevant plugins.</returns>
/// <summary>Gets the list containing all the loaded textures from plugins.</summary>
/// <remarks>Returned value must be used inside a lock.</remarks>
[SuppressMessage("ReSharper", "InconsistentlySynchronizedField", Justification = "Caller locks the return value.")]
public IReadOnlyList<IBlameableDalamudTextureWrap> AllBlamesForDebug => this.blameTracker;
public List<IBlameableDalamudTextureWrap> BlameTracker { get; } = new();
/// <summary>Gets the blame for a texture wrap.</summary>
/// <param name="textureWrap">The texture wrap.</param>
/// <returns>The blame, if it exists.</returns>
public unsafe IBlameableDalamudTextureWrap? GetBlame(IDalamudTextureWrap textureWrap)
{
using var wrapAux = new WrapAux(textureWrap, true);
return BlameTag.Get(wrapAux.ResPtr);
}
/// <summary>Puts a plugin on blame for a texture.</summary>
/// <param name="textureWrap">The texture.</param>
@ -59,7 +73,7 @@ internal sealed partial class TextureManager
}
using var wrapAux = new WrapAux(textureWrap, true);
var blame = BlameTag.From(wrapAux.ResPtr, out var isNew);
var blame = BlameTag.GetOrCreate(wrapAux.ResPtr, out var isNew);
if (ownerPlugin is not null)
{
@ -69,8 +83,8 @@ internal sealed partial class TextureManager
if (isNew)
{
lock (this.blameTracker)
this.blameTracker.Add(blame);
lock (this.BlameTracker)
this.BlameTracker.Add(blame);
}
return textureWrap;
@ -96,13 +110,13 @@ internal sealed partial class TextureManager
}
using var wrapAux = new WrapAux(textureWrap, true);
var blame = BlameTag.From(wrapAux.ResPtr, out var isNew);
blame.Name = name;
var blame = BlameTag.GetOrCreate(wrapAux.ResPtr, out var isNew);
blame.Name = name.Length <= 1024 ? name : $"{name[..1024]}...";
if (isNew)
{
lock (this.blameTracker)
this.blameTracker.Add(blame);
lock (this.BlameTracker)
this.BlameTracker.Add(blame);
}
return textureWrap;
@ -110,15 +124,15 @@ internal sealed partial class TextureManager
private void BlameTrackerUpdate(IFramework unused)
{
lock (this.blameTracker)
lock (this.BlameTracker)
{
for (var i = 0; i < this.blameTracker.Count;)
for (var i = 0; i < this.BlameTracker.Count;)
{
var entry = this.blameTracker[i];
var entry = this.BlameTracker[i];
if (entry.TestIsReleasedOrShouldRelease())
{
this.blameTracker[i] = this.blameTracker[^1];
this.blameTracker.RemoveAt(this.blameTracker.Count - 1);
this.BlameTracker[i] = this.BlameTracker[^1];
this.BlameTracker.RemoveAt(this.BlameTracker.Count - 1);
}
else
{
@ -220,12 +234,22 @@ internal sealed partial class TextureManager
/// <inheritdoc/>
public List<LocalPlugin> OwnerPlugins { get; } = new();
/// <inheritdoc/>
public nint ResourceAddress => (nint)this.tex2D;
/// <inheritdoc/>
public string Name { get; set; } = "<unnamed>";
/// <inheritdoc/>
public DXGI_FORMAT Format => this.desc.Format;
/// <inheritdoc/>
public RawImageSpecification RawSpecs => new(
(int)this.desc.Width,
(int)this.desc.Height,
(int)this.desc.Format,
0);
/// <inheritdoc/>
public IntPtr ImGuiHandle
{
@ -267,7 +291,23 @@ internal sealed partial class TextureManager
/// <param name="isNew"><c>true</c> if the tracker is new.</param>
/// <typeparam name="T">A COM object type.</typeparam>
/// <returns>A new instance of <see cref="BlameTag"/>.</returns>
public static BlameTag From<T>(T* trackWhat, out bool isNew) where T : unmanaged, IUnknown.Interface
public static BlameTag GetOrCreate<T>(T* trackWhat, out bool isNew) where T : unmanaged, IUnknown.Interface
{
if (Get(trackWhat) is { } v)
{
isNew = false;
return v;
}
isNew = true;
return new((IUnknown*)trackWhat);
}
/// <summary>Gets an existing instance of <see cref="BlameTag"/> for the given resource.</summary>
/// <param name="trackWhat">The COM object to track.</param>
/// <typeparam name="T">A COM object type.</typeparam>
/// <returns>An existing instance of <see cref="BlameTag"/>.</returns>
public static BlameTag? Get<T>(T* trackWhat) where T : unmanaged, IUnknown.Interface
{
using var deviceChild = default(ComPtr<ID3D11DeviceChild>);
fixed (Guid* piid = &IID.IID_ID3D11DeviceChild)
@ -282,18 +322,15 @@ internal sealed partial class TextureManager
if (ToManagedObject(existingTag) is { } existingTagInstance)
{
existingTagInstance.Release();
isNew = false;
return existingTagInstance;
}
}
}
isNew = true;
return new((IUnknown*)trackWhat);
return null;
}
/// <summary>Tests whether the tag and the underlying resource are released or should be released.</summary>
/// <returns><c>true</c> if there are no more remaining references to this instance.</returns>
/// <inheritdoc/>
public bool TestIsReleasedOrShouldRelease()
{
if (this.srvDebugPreviewExpiryTick <= Environment.TickCount64)

View file

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@ -82,7 +83,7 @@ internal sealed partial class TextureManager
this.BlameSetName(
outWrap,
debugName ??
$"{nameof(this.CreateFromExistingTextureAsync)}({nameof(wrap)}, {nameof(args)}, {nameof(leaveWrapOpen)}, {nameof(cancellationToken)})");
$"{nameof(this.CreateFromExistingTextureAsync)}({wrap}, {args})");
return outWrap;
}
},
@ -136,59 +137,57 @@ internal sealed partial class TextureManager
}
cancellationToken.ThrowIfCancellationRequested();
return await this.interfaceManager.RunBeforeImGuiRender(
() => ExtractMappedResource(wrapAux, tex2D, cancellationToken));
// ID3D11DeviceContext is not a threadsafe resource, and it must be used from the UI thread.
return await this.RunDuringPresent(() => ExtractMappedResource(tex2D, cancellationToken));
static unsafe (RawImageSpecification Specification, byte[] RawData) ExtractMappedResource(
in WrapAux wrapAux,
ComPtr<ID3D11Texture2D> tex2D,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ID3D11Resource* mapWhat = null;
D3D11_TEXTURE2D_DESC desc;
tex2D.Get()->GetDesc(&desc);
using var device = default(ComPtr<ID3D11Device>);
tex2D.Get()->GetDevice(device.GetAddressOf());
using var context = default(ComPtr<ID3D11DeviceContext>);
device.Get()->GetImmediateContext(context.GetAddressOf());
using var tmpTex = default(ComPtr<ID3D11Texture2D>);
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();
}
var mapWhat = (ID3D11Resource*)(tmpTex.IsEmpty() ? tex2D.Get() : tmpTex.Get());
D3D11_MAPPED_SUBRESOURCE mapped;
context.Get()->Map(mapWhat, 0, D3D11_MAP.D3D11_MAP_READ, 0, &mapped).ThrowOnError();
try
{
using var tmpTex = default(ComPtr<ID3D11Texture2D>);
if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) == 0)
{
var tmpTexDesc = wrapAux.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,
};
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());
wrapAux.CtxPtr->Map(
mapWhat,
0,
D3D11_MAP.D3D11_MAP_READ,
0,
&mapped).ThrowOnError();
var specs = new RawImageSpecification(
(int)wrapAux.Desc.Width,
(int)wrapAux.Desc.Height,
(int)wrapAux.Desc.Format,
(int)mapped.RowPitch);
var specs = new RawImageSpecification(desc, mapped.RowPitch);
var bytes = new Span<byte>(mapped.pData, checked((int)mapped.DepthPitch)).ToArray();
return (specs, bytes);
}
finally
{
if (mapWhat is not null)
wrapAux.CtxPtr->Unmap(mapWhat, 0);
context.Get()->Unmap(mapWhat, 0);
}
}
}
@ -226,35 +225,34 @@ internal sealed partial class TextureManager
this.device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
}
await this.interfaceManager.RunBeforeImGuiRender(
() =>
{
unsafe
{
using var rtvCopyTemp = default(ComPtr<ID3D11RenderTargetView>);
var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
tex2DCopyTemp,
D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
this.device.Get()->CreateRenderTargetView(
(ID3D11Resource*)tex2DCopyTemp.Get(),
&rtvCopyTempDesc,
rtvCopyTemp.GetAddressOf()).ThrowOnError();
wrapAux.CtxPtr->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
this.SimpleDrawer.Draw(
wrapAux.CtxPtr,
wrapAux.SrvPtr,
args.Uv0,
args.Uv1Effective);
if (args.MakeOpaque)
this.SimpleDrawer.StripAlpha(wrapAux.CtxPtr);
var dummy = default(ID3D11RenderTargetView*);
wrapAux.CtxPtr->OMSetRenderTargets(1u, &dummy, null);
}
});
await this.RunDuringPresent(() => DrawSourceTextureToTarget(wrapAux, args, this.SimpleDrawer, tex2DCopyTemp));
return new(tex2DCopyTemp);
static unsafe void DrawSourceTextureToTarget(
WrapAux wrapAux,
TextureModificationArgs args,
SimpleDrawerImpl simpleDrawer,
ComPtr<ID3D11Texture2D> tex2DCopyTemp)
{
using var rtvCopyTemp = default(ComPtr<ID3D11RenderTargetView>);
var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
tex2DCopyTemp,
D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
wrapAux.DevPtr->CreateRenderTargetView(
(ID3D11Resource*)tex2DCopyTemp.Get(),
&rtvCopyTempDesc,
rtvCopyTemp.GetAddressOf())
.ThrowOnError();
wrapAux.CtxPtr->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
simpleDrawer.Draw(wrapAux.CtxPtr, wrapAux.SrvPtr, args.Uv0, args.Uv1Effective);
if (args.MakeOpaque)
simpleDrawer.StripAlpha(wrapAux.CtxPtr);
var dummy = default(ID3D11RenderTargetView*);
wrapAux.CtxPtr->OMSetRenderTargets(1u, &dummy, null);
}
}
/// <summary>Auxiliary data from <see cref="IDalamudTextureWrap"/>.</summary>

View file

@ -88,25 +88,28 @@ internal sealed partial class TextureManager
/// <inheritdoc cref="ITextureProvider.GetFromGameIcon"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup) =>
public SharedImmediateTexture.PureImpl GetFromGameIcon(in GameIconLookup lookup) =>
this.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue));
/// <inheritdoc cref="ITextureProvider.GetFromGame"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SharedImmediateTexture GetFromGame(string path) =>
this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder);
public SharedImmediateTexture.PureImpl GetFromGame(string path) =>
this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder)
.PublicUseInstance;
/// <inheritdoc cref="ITextureProvider.GetFromFile"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SharedImmediateTexture GetFromFile(string path) =>
this.fileDict.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder);
public SharedImmediateTexture.PureImpl GetFromFile(string path) =>
this.fileDict.GetOrAdd(path, FileSystemSharedImmediateTexture.CreatePlaceholder)
.PublicUseInstance;
/// <inheritdoc cref="ITextureProvider.GetFromFile"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SharedImmediateTexture GetFromManifestResource(Assembly assembly, string name) =>
public SharedImmediateTexture.PureImpl GetFromManifestResource(Assembly assembly, string name) =>
this.manifestResourceDict.GetOrAdd(
(assembly, name),
ManifestResourceSharedImmediateTexture.CreatePlaceholder);
ManifestResourceSharedImmediateTexture.CreatePlaceholder)
.PublicUseInstance;
/// <summary>Invalidates a cached item from <see cref="GetFromGame"/> and <see cref="GetFromGameIcon"/>.
/// </summary>

View file

@ -318,6 +318,7 @@ internal sealed partial class TextureManager
{
// See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT => GUID.GUID_WICPixelFormat96bppRGBFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat64bppRGBAHalf,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppRGBA,
DXGI_FORMAT.DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => GUID.GUID_WICPixelFormat32bppRGBA1010102XR,
@ -497,10 +498,10 @@ internal sealed partial class TextureManager
var outPixelFormat = specs.Format switch
{
DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT when !this.wicFactory2.IsEmpty() =>
GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat32bppBGRA,
DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT => GUID.GUID_WICPixelFormat96bppRGBFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => GUID.GUID_WICPixelFormat128bppRGBAFloat,
DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppBGRA,
DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => GUID.GUID_WICPixelFormat24bppBGR,
DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => GUID.GUID_WICPixelFormat16bppBGRA5551,
DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM => GUID.GUID_WICPixelFormat16bppBGR565,
DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => GUID.GUID_WICPixelFormat8bppGray,
@ -508,9 +509,25 @@ internal sealed partial class TextureManager
DXGI_FORMAT.DXGI_FORMAT_R16_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_R8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
_ => GUID.GUID_WICPixelFormat24bppBGR,
_ => GUID.GUID_WICPixelFormat32bppBGRA,
};
var accepted = false;
foreach (var pfi in new ComponentEnumerable<IWICPixelFormatInfo>(
this.wicFactory,
WICComponentType.WICPixelFormat))
{
Guid tmp;
if (pfi.Get()->GetFormatGUID(&tmp).FAILED)
continue;
accepted = tmp == outPixelFormat;
if (accepted)
break;
}
if (!accepted)
outPixelFormat = GUID.GUID_WICPixelFormat32bppBGRA;
encoder.Get()->Initialize(stream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache)
.ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
@ -613,8 +630,7 @@ internal sealed partial class TextureManager
private readonly WICComponentType componentType;
/// <summary>Initializes a new instance of the <see cref="ComponentEnumerable{T}"/> struct.</summary>
/// <param name="factory">The WIC factory. Ownership is not transferred.
/// </param>
/// <param name="factory">The WIC factory. Ownership is not transferred.</param>
/// <param name="componentType">The component type to enumerate.</param>
public ComponentEnumerable(ComPtr<IWICImagingFactory> factory, WICComponentType componentType)
{

View file

@ -8,6 +8,7 @@ using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@ -127,7 +128,7 @@ internal sealed partial class TextureManager
this.BlameSetName(
this.NoThrottleCreateFromImage(bytes.ToArray(), ct),
debugName ??
$"{nameof(this.CreateFromImageAsync)}({nameof(bytes)}, {nameof(cancellationToken)})"),
$"{nameof(this.CreateFromImageAsync)}({bytes.Length:n0}b)"),
ct),
cancellationToken);
@ -146,7 +147,7 @@ internal sealed partial class TextureManager
return this.BlameSetName(
this.NoThrottleCreateFromImage(ms.GetBuffer(), ct),
debugName ??
$"{nameof(this.CreateFromImageAsync)}({nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})");
$"{nameof(this.CreateFromImageAsync)}(stream)");
},
cancellationToken,
leaveOpen ? null : stream);
@ -159,7 +160,7 @@ internal sealed partial class TextureManager
string? debugName = null) =>
this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, bytes),
debugName ?? $"{nameof(this.CreateFromRaw)}({nameof(specs)}, {nameof(bytes)})");
debugName ?? $"{nameof(this.CreateFromRaw)}({specs}, {bytes.Length:n0})");
/// <inheritdoc/>
public Task<IDalamudTextureWrap> CreateFromRawAsync(
@ -173,7 +174,7 @@ internal sealed partial class TextureManager
this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, bytes.Span),
debugName ??
$"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(bytes)}, {nameof(cancellationToken)})")),
$"{nameof(this.CreateFromRawAsync)}({specs}, {bytes.Length:n0})")),
cancellationToken);
/// <inheritdoc/>
@ -192,7 +193,7 @@ internal sealed partial class TextureManager
return this.BlameSetName(
this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length)),
debugName ??
$"{nameof(this.CreateFromRawAsync)}({nameof(specs)}, {nameof(stream)}, {nameof(leaveOpen)}, {nameof(cancellationToken)})");
$"{nameof(this.CreateFromRawAsync)}({specs}, stream)");
},
cancellationToken,
leaveOpen ? null : stream);
@ -207,15 +208,19 @@ internal sealed partial class TextureManager
public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
TexFile file,
string? debugName = null,
CancellationToken cancellationToken = default) =>
this.DynamicPriorityTextureLoader.LoadAsync(
CancellationToken cancellationToken = default)
{
return this.DynamicPriorityTextureLoader.LoadAsync(
null,
_ => Task.FromResult(
this.BlameSetName(
this.NoThrottleCreateFromTexFile(file),
debugName ?? $"{nameof(this.CreateFromTexFile)}({nameof(file)})")),
debugName ?? $"{nameof(this.CreateFromTexFile)}({ForceNullable(file.FilePath)?.Path})")),
cancellationToken);
static T? ForceNullable<T>(T s) => s;
}
/// <inheritdoc/>
bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
@ -267,7 +272,7 @@ internal sealed partial class TextureManager
.ThrowOnError();
var wrap = new UnknownTextureWrap((IUnknown*)view.Get(), specs.Width, specs.Height, true);
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromRaw)}({nameof(specs)}, {nameof(bytes)})");
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromRaw)}({specs}, {bytes.Length:n0})");
return wrap;
}
@ -289,8 +294,10 @@ internal sealed partial class TextureManager
}
var wrap = this.NoThrottleCreateFromRaw(new(buffer.Width, buffer.Height, dxgiFormat), buffer.RawData);
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({nameof(file)})");
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({ForceNullable(file.FilePath).Path})");
return wrap;
static T? ForceNullable<T>(T s) => s;
}
/// <summary>Creates a texture from the given <paramref name="fileBytes"/>, trying to interpret it as a
@ -315,9 +322,31 @@ internal sealed partial class TextureManager
// Note: FileInfo and FilePath are not used from TexFile; skip it.
var wrap = this.NoThrottleCreateFromTexFile(tf);
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({nameof(fileBytes)})");
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({fileBytes.Length:n0})");
return wrap;
}
private void ReleaseUnmanagedResources() => this.device.Reset();
/// <summary>Runs the given action in IDXGISwapChain.Present immediately or waiting as needed.</summary>
/// <param name="action">The action to run.</param>
// Not sure why this and the below can't be unconditional RunOnFrameworkThread
private async Task RunDuringPresent(Action action)
{
if (this.interfaceManager.IsInPresent && ThreadSafety.IsMainThread)
action();
else
await this.interfaceManager.RunBeforeImGuiRender(action);
}
/// <summary>Runs the given function in IDXGISwapChain.Present immediately or waiting as needed.</summary>
/// <typeparam name="T">The type of the return value.</typeparam>
/// <param name="func">The function to run.</param>
/// <returns>The return value from the function.</returns>
private async Task<T> RunDuringPresent<T>(Func<T> func)
{
if (this.interfaceManager.IsInPresent && ThreadSafety.IsMainThread)
return func();
return await this.interfaceManager.RunBeforeImGuiRender(func);
}
}

View file

@ -12,7 +12,7 @@ public record struct RawImageSpecification
/// <param name="height">The height of the raw image.</param>
/// <param name="dxgiFormat">The DXGI format of the raw image.</param>
/// <param name="pitch">The pitch of the raw image in bytes.
/// Specify <c>-1</c> to calculate it from other parameters.</param>
/// Specify <c>-1</c> to calculate from other parameters.</param>
public RawImageSpecification(int width, int height, int dxgiFormat, int pitch = -1)
{
if (pitch < 0)
@ -31,6 +31,14 @@ public record struct RawImageSpecification
this.DxgiFormat = dxgiFormat;
}
/// <summary>Initializes a new instance of the <see cref="RawImageSpecification"/> class.</summary>
/// <param name="desc">The source texture description.</param>
/// <param name="pitch">The pitch of the raw image in bytes.</param>
internal RawImageSpecification(in D3D11_TEXTURE2D_DESC desc, uint pitch)
: this((int)desc.Width, (int)desc.Height, (int)desc.Format, checked((int)pitch))
{
}
/// <summary>Gets or sets the width of the raw image.</summary>
public int Width { get; set; }
@ -102,6 +110,10 @@ public record struct RawImageSpecification
public static RawImageSpecification A8(int width, int height) =>
new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM, width);
/// <inheritdoc/>
public override string ToString() =>
$"{nameof(RawImageSpecification)}({this.Width}x{this.Height}, {this.Format}, {this.Pitch}b)";
private static bool GetFormatInfo(DXGI_FORMAT format, out int bitsPerPixel, out bool isBlockCompression)
{
switch (format)
@ -246,7 +258,7 @@ public record struct RawImageSpecification
return true;
case DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM:
bitsPerPixel = 16;
isBlockCompression = true;
isBlockCompression = false;
return true;
default:
bitsPerPixel = 0;

View file

@ -1,4 +1,5 @@
using System.Numerics;
using System.Text;
using Dalamud.Plugin.Services;
@ -52,6 +53,36 @@ public record struct TextureModificationArgs()
/// <summary>Gets the effective value of <see cref="Uv1"/>.</summary>
internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1;
/// <inheritdoc/>
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(nameof(TextureModificationArgs)).Append('(');
if (this.MakeOpaque)
sb.Append($"{nameof(this.MakeOpaque)}, ");
if (this.Format != DXGI_FORMAT.DXGI_FORMAT_UNKNOWN)
sb.Append(Enum.GetName(this.Format) is { } name ? name[12..] : this.Format.ToString()).Append(", ");
if (this.NewWidth != 0 || this.NewHeight != 0)
{
sb.Append(this.NewWidth == 0 ? "?" : this.NewWidth.ToString())
.Append('x')
.Append(this.NewHeight == 0 ? "?" : this.NewHeight.ToString())
.Append(", ");
}
if (this.Uv0 != Vector2.Zero || this.Uv1Effective != Vector2.One)
{
sb.Append(this.Uv0.ToString())
.Append('-')
.Append(this.Uv1.ToString())
.Append(", ");
}
if (sb[^1] != '(')
sb.Remove(sb.Length - 2, 2);
return sb.Append(')').ToString();
}
/// <summary>Test if this instance of <see cref="TextureModificationArgs"/> does not instruct to change the
/// underlying data of a texture.</summary>
/// <param name="desc">The texture description to test against.</param>

View file

@ -3,11 +3,11 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.Textures;
namespace Dalamud.Interface.Textures.TextureWraps;
/// <summary>Base class for implementations of <see cref="IDalamudTextureWrap"/> that forwards to another.</summary>
public abstract class ForwardingTextureWrap : IDalamudTextureWrap

View file

@ -2,6 +2,8 @@ using System.Numerics;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using TerraFX.Interop.Windows;

View file

@ -1,6 +1,6 @@
using Dalamud.Interface.Internal;
namespace Dalamud.Interface.Textures.Internal;
namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// <summary>A texture wrap that ignores <see cref="IDisposable.Dispose"/> calls.</summary>
internal class DisposeSuppressingTextureWrap : ForwardingTextureWrap

View file

@ -1,11 +1,12 @@
using System.Threading;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Utility;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.Textures.Internal;
namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// <summary>A texture wrap that is created from an <see cref="IUnknown"/>.</summary>
internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferredDisposable
@ -50,6 +51,10 @@ internal sealed unsafe class UnknownTextureWrap : IDalamudTextureWrap, IDeferred
GC.SuppressFinalize(this);
}
/// <inheritdoc/>
public override string ToString() =>
$"{nameof(UnknownTextureWrap)}({Service<TextureManager>.GetNullable()?.GetBlame(this)?.Name ?? $"{this.imGuiHandle:X}"})";
/// <summary>Actually dispose the wrapped texture.</summary>
void IDeferredDisposable.RealDispose()
{

View file

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
@ -15,7 +16,7 @@ using TerraFX.Interop.Windows;
using NotSupportedException = System.NotSupportedException;
namespace Dalamud.Interface.Textures.Internal;
namespace Dalamud.Interface.Textures.TextureWraps.Internal;
/// <summary>A texture wrap that takes its buffer from the frame buffer (of swap chain).</summary>
internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDisposable