diff --git a/Dalamud/DalamudAsset.cs b/Dalamud/DalamudAsset.cs
index a7b35b196..15d342962 100644
--- a/Dalamud/DalamudAsset.cs
+++ b/Dalamud/DalamudAsset.cs
@@ -1,5 +1,7 @@
using Dalamud.Storage.Assets;
+using TerraFX.Interop.DirectX;
+
namespace Dalamud;
///
@@ -19,9 +21,16 @@ public enum DalamudAsset
/// : The fallback empty texture.
///
[DalamudAsset(DalamudAssetPurpose.TextureFromRaw, data: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 })]
- [DalamudAssetRawTexture(4, 8, 4, SharpDX.DXGI.Format.BC1_UNorm)]
+ [DalamudAssetRawTexture(4, 4, DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM, 8)]
Empty4X4 = 1000,
+ ///
+ /// : The fallback empty texture.
+ ///
+ [DalamudAsset(DalamudAssetPurpose.TextureFromRaw, data: new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0 })]
+ [DalamudAssetRawTexture(4, 4, DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM, 8)]
+ White4X4 = 1014,
+
///
/// : The Dalamud logo.
///
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 019b462cd..65c10d3a0 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -70,7 +70,8 @@ internal class InterfaceManager : IDisposable, IServiceType
[ServiceManager.ServiceDependency]
private readonly DalamudIme dalamudIme = Service.Get();
- private readonly ConcurrentQueue runBeforePresent = new();
+ private readonly ConcurrentQueue runBeforeImGuiRender = new();
+ private readonly ConcurrentQueue runAfterImGuiRender = new();
private readonly SwapChainVtableResolver address = new();
private readonly Hook setCursorHook;
@@ -285,13 +286,13 @@ internal class InterfaceManager : IDisposable, IServiceType
this.deferredDisposeDisposables.Add(locked);
}
- /// Queues an action to be run before Present call.
+ /// Queues an action to be run before call.
/// The action.
/// A that resolves once is run.
- public Task RunBeforePresent(Action action)
+ public Task RunBeforeImGuiRender(Action action)
{
var tcs = new TaskCompletionSource();
- this.runBeforePresent.Enqueue(
+ this.runBeforeImGuiRender.Enqueue(
() =>
{
try
@@ -307,14 +308,58 @@ internal class InterfaceManager : IDisposable, IServiceType
return tcs.Task;
}
- /// Queues a function to be run before Present call.
+ /// Queues a function to be run before call.
/// The type of the return value.
/// The function.
/// A that resolves once is run.
- public Task RunBeforePresent(Func func)
+ public Task RunBeforeImGuiRender(Func func)
{
var tcs = new TaskCompletionSource();
- this.runBeforePresent.Enqueue(
+ this.runBeforeImGuiRender.Enqueue(
+ () =>
+ {
+ try
+ {
+ tcs.SetResult(func());
+ }
+ catch (Exception e)
+ {
+ tcs.SetException(e);
+ }
+ });
+ return tcs.Task;
+ }
+
+ /// Queues an action to be run after call.
+ /// The action.
+ /// A that resolves once is run.
+ public Task RunAfterImGuiRender(Action action)
+ {
+ var tcs = new TaskCompletionSource();
+ this.runAfterImGuiRender.Enqueue(
+ () =>
+ {
+ try
+ {
+ action();
+ tcs.SetResult();
+ }
+ catch (Exception e)
+ {
+ tcs.SetException(e);
+ }
+ });
+ return tcs.Task;
+ }
+
+ /// Queues a function to be run after call.
+ /// The type of the return value.
+ /// The function.
+ /// A that resolves once is run.
+ public Task RunAfterImGuiRender(Func func)
+ {
+ var tcs = new TaskCompletionSource();
+ this.runAfterImGuiRender.Enqueue(
() =>
{
try
@@ -566,7 +611,7 @@ internal class InterfaceManager : IDisposable, IServiceType
if (!this.dalamudAtlas!.HasBuiltAtlas)
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
- while (this.runBeforePresent.TryDequeue(out var action))
+ while (this.runBeforeImGuiRender.TryDequeue(out var action))
action.InvokeSafely();
if (this.address.IsReshade)
@@ -574,19 +619,22 @@ internal class InterfaceManager : IDisposable, IServiceType
var pRes = this.presentHook!.Original(swapChain, syncInterval, presentFlags);
RenderImGui(this.scene!);
- this.CleanupPostImGuiRender();
+ this.PostImGuiRender();
return pRes;
}
RenderImGui(this.scene!);
- this.CleanupPostImGuiRender();
+ this.PostImGuiRender();
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
}
- private void CleanupPostImGuiRender()
+ private void PostImGuiRender()
{
+ while (this.runAfterImGuiRender.TryDequeue(out var action))
+ action.InvokeSafely();
+
if (!this.deferredDisposeTextures.IsEmpty)
{
var count = 0;
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
index fb4330bea..769df073b 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Dalamud.Interface.Components;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications;
+using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Interface.Utility;
@@ -21,6 +22,7 @@ using ImGuiNET;
using Serilog;
using TerraFX.Interop.DirectX;
+using TerraFX.Interop.Windows;
using TextureManager = Dalamud.Interface.Textures.Internal.TextureManager;
@@ -49,6 +51,7 @@ internal class TexWidget : IDataWindowWidget
private Vector2 inputTexScale = Vector2.Zero;
private TextureManager textureManager = null!;
private FileDialogManager fileDialogManager = null!;
+ private ExistingTextureModificationArgs existingTextureModificationArgs;
private string[]? supportedRenderTargetFormatNames;
private DXGI_FORMAT[]? supportedRenderTargetFormats;
@@ -82,6 +85,13 @@ internal class TexWidget : IDataWindowWidget
this.supportedRenderTargetFormats = null;
this.supportedRenderTargetFormatNames = null;
this.fileDialogManager = new();
+ this.existingTextureModificationArgs = new()
+ {
+ Uv0 = new(0.25f),
+ Uv1 = new(0.75f),
+ NewWidth = 320,
+ NewHeight = 240,
+ };
this.Ready = true;
}
@@ -123,6 +133,31 @@ internal class TexWidget : IDataWindowWidget
ImGui.PopID();
}
+ ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
+
+ ImGui.AlignTextToFramePadding();
+ ImGui.TextUnformatted("Capture: ");
+ if (ImGui.Button("Game"))
+ this.addedTextures.Add(new() { Api10 = this.textureManager.CreateFromGameScreen() });
+
+ ImGui.SameLine();
+ if (ImGui.Button("Game (Auto)"))
+ this.addedTextures.Add(new() { Api10 = this.textureManager.CreateFromGameScreen(true) });
+
+ ImGui.SameLine();
+ if (ImGui.Button("Main Viewport"))
+ {
+ this.addedTextures.Add(
+ new() { Api10 = this.textureManager.CreateFromImGuiViewport(ImGui.GetMainViewport().ID) });
+ }
+
+ ImGui.SameLine();
+ if (ImGui.Button("Main Viewport (Auto)"))
+ {
+ this.addedTextures.Add(
+ new() { Api10 = this.textureManager.CreateFromImGuiViewport(ImGui.GetMainViewport().ID, true) });
+ }
+
if (ImGui.CollapsingHeader(nameof(ITextureProvider.GetFromGameIcon), ImGuiTreeNodeFlags.DefaultOpen))
{
ImGui.PushID(nameof(this.DrawGetFromGameIcon));
@@ -158,24 +193,14 @@ internal class TexWidget : IDataWindowWidget
ImGui.PopID();
}
- ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
-
- if (this.supportedRenderTargetFormats is null)
+ if (ImGui.CollapsingHeader($"CropCopy##{this.DrawExistingTextureModificationArgs}"))
{
- this.supportedRenderTargetFormatNames = null;
- this.supportedRenderTargetFormats =
- Enum.GetValues()
- .Where(this.textureManager.IsDxgiFormatSupportedForCreateFromExistingTextureAsync)
- .ToArray();
- this.renderTargetChoiceInt = 0;
+ ImGui.PushID(nameof(this.DrawExistingTextureModificationArgs));
+ this.DrawExistingTextureModificationArgs();
+ ImGui.PopID();
}
- this.supportedRenderTargetFormatNames ??= this.supportedRenderTargetFormats.Select(Enum.GetName).ToArray();
- ImGui.Combo(
- "CropCopy Format",
- ref this.renderTargetChoiceInt,
- this.supportedRenderTargetFormatNames,
- this.supportedRenderTargetFormatNames.Length);
+ ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
Action? runLater = null;
foreach (var t in this.addedTextures)
@@ -217,13 +242,14 @@ internal class TexWidget : IDataWindowWidget
return;
var texTask = this.textureManager.CreateFromExistingTextureAsync(
source.CreateWrapSharingLowLevelResource(),
- new(0.25f),
- new(0.75f),
- supportedFormats[this.renderTargetChoiceInt]);
+ this.existingTextureModificationArgs with
+ {
+ Format = supportedFormats[this.renderTargetChoiceInt],
+ });
this.addedTextures.Add(new() { Api10 = texTask });
};
}
-
+
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
unsafe
@@ -233,7 +259,7 @@ internal class TexWidget : IDataWindowWidget
var psrv = (ID3D11ShaderResourceView*)source.ImGuiHandle;
var rcsrv = psrv->AddRef() - 1;
psrv->Release();
-
+
var pres = default(ID3D11Resource*);
psrv->GetResource(&pres);
var rcres = pres->AddRef() - 1;
@@ -558,6 +584,49 @@ internal class TexWidget : IDataWindowWidget
ImGuiHelpers.ScaledDummy(10);
}
+ private void DrawExistingTextureModificationArgs()
+ {
+ var vec2 = this.existingTextureModificationArgs.Uv0;
+ if (ImGui.InputFloat2("UV0", ref vec2))
+ this.existingTextureModificationArgs.Uv0 = vec2;
+
+ vec2 = this.existingTextureModificationArgs.Uv1;
+ if (ImGui.InputFloat2("UV1", ref vec2))
+ this.existingTextureModificationArgs.Uv1 = vec2;
+
+ Span wh = stackalloc int[2];
+ wh[0] = this.existingTextureModificationArgs.NewWidth;
+ wh[1] = this.existingTextureModificationArgs.NewHeight;
+ if (ImGui.InputInt2("New Size", ref wh[0]))
+ {
+ this.existingTextureModificationArgs.NewWidth = wh[0];
+ this.existingTextureModificationArgs.NewHeight = wh[1];
+ }
+
+ var b = this.existingTextureModificationArgs.MakeOpaque;
+ if (ImGui.Checkbox("Make Opaque", ref b))
+ this.existingTextureModificationArgs.MakeOpaque = b;
+
+ if (this.supportedRenderTargetFormats is null)
+ {
+ this.supportedRenderTargetFormatNames = null;
+ this.supportedRenderTargetFormats =
+ Enum.GetValues()
+ .Where(this.textureManager.IsDxgiFormatSupportedForCreateFromExistingTextureAsync)
+ .ToArray();
+ this.renderTargetChoiceInt = 0;
+ }
+
+ this.supportedRenderTargetFormatNames ??= this.supportedRenderTargetFormats.Select(Enum.GetName).ToArray();
+ ImGui.Combo(
+ "Format",
+ ref this.renderTargetChoiceInt,
+ this.supportedRenderTargetFormatNames,
+ this.supportedRenderTargetFormatNames.Length);
+
+ ImGuiHelpers.ScaledDummy(10);
+ }
+
private async void SaveTextureAsync(string name, Func> textureGetter)
{
try
@@ -627,15 +696,18 @@ internal class TexWidget : IDataWindowWidget
}
using var textureWrap = await textureGetter.Invoke();
+ var props = new Dictionary();
+ if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff)
+ props["CompressionQuality"] = 1.0f;
+ else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg ||
+ encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif ||
+ encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp)
+ props["ImageQuality"] = 1.0f;
await this.textureManager.SaveToFileAsync(
textureWrap,
encoder.ContainerGuid,
path,
- props: new Dictionary
- {
- ["CompressionQuality"] = 1.0f,
- ["ImageQuality"] = 1.0f,
- });
+ props: props);
Service.Get().AddNotification(
$"File saved to: {path}",
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
index 73530bf0e..316174c4e 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
@@ -638,8 +638,8 @@ internal sealed partial class FontAtlasFactory
new(
width,
height,
- width * bpp,
- (int)(use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm)),
+ (int)(use4 ? Format.B4G4R4A4_UNorm : Format.B8G8R8A8_UNorm),
+ width * bpp),
buf);
this.data.AddExistingTexture(wrap);
texture.TexID = wrap.ImGuiHandle;
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
index 27888bb0b..39e969fb8 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs
@@ -360,10 +360,10 @@ internal sealed partial class FontAtlasFactory
new(
texFile.Header.Width,
texFile.Header.Height,
- texFile.Header.Width * bpp,
(int)(targetIsB4G4R4A4
? DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM
- : DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM)),
+ : DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM),
+ texFile.Header.Width * bpp),
buffer));
}
finally
diff --git a/Dalamud/Interface/Textures/ExistingTextureModificationArgs.cs b/Dalamud/Interface/Textures/ExistingTextureModificationArgs.cs
new file mode 100644
index 000000000..9bcb9f34d
--- /dev/null
+++ b/Dalamud/Interface/Textures/ExistingTextureModificationArgs.cs
@@ -0,0 +1,95 @@
+using System.Numerics;
+
+using Dalamud.Plugin.Services;
+
+using TerraFX.Interop.DirectX;
+
+namespace Dalamud.Interface.Textures;
+
+/// Describes how to modify an existing texture.
+public record struct ExistingTextureModificationArgs()
+{
+ /// Gets or sets the left top coordinates relative to the size of the source texture.
+ /// Coordinates should be in range between 0 and 1.
+ public Vector2 Uv0 { get; set; } = Vector2.Zero;
+
+ /// Gets or sets the right bottom coordinates relative to the size of the source texture.
+ /// Coordinates should be in range between 0 and 1.
+ /// If set to , then it will be interpreted as ,
+ /// to accommodate the use of default value of this record struct.
+ public Vector2 Uv1 { get; set; } = Vector2.One;
+
+ /// Gets or sets the new width.
+ /// Set to 0 to automatically calculate according to the original texture size, , and
+ /// .
+ public int NewWidth { get; set; }
+
+ /// Gets or sets the new height.
+ /// Set to 0 to automatically calculate according to the original texture size, , and
+ /// .
+ public int NewHeight { get; set; }
+
+ /// Gets or sets a value indicating whether to make the texture opaque.
+ /// Alpha channel values will be filled with 1.0.
+ public bool MakeOpaque { get; set; } = false;
+
+ /// Gets or sets the new DXGI format.
+ ///
+ /// Set to 0 () to use the source pixel format.
+ /// Supported values can be queried with
+ /// . This may not necessarily
+ /// match .
+ ///
+ public int DxgiFormat { get; set; } = (int)DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
+
+ /// Gets or sets the format (typed).
+ internal DXGI_FORMAT Format
+ {
+ get => (DXGI_FORMAT)this.DxgiFormat;
+ set => this.DxgiFormat = (int)value;
+ }
+
+ /// Gets the effective value of .
+ internal Vector2 Uv1Effective => this.Uv1 == Vector2.Zero ? Vector2.One : this.Uv1;
+
+ /// Test if this instance of does not instruct to change the
+ /// underlying data of a texture.
+ /// The texture description to test against.
+ /// true if this instance of does not instruct to
+ /// change the underlying data of a texture.
+ internal bool IsCompleteSourceCopy(in D3D11_TEXTURE2D_DESC desc) =>
+ this.Uv0 == Vector2.Zero
+ && this.Uv1 == Vector2.One
+ && (this.NewWidth == 0 || this.NewWidth == desc.Width)
+ && (this.NewHeight == 0 || this.NewHeight == desc.Height)
+ && !this.MakeOpaque
+ && (this.Format == DXGI_FORMAT.DXGI_FORMAT_UNKNOWN || this.Format == desc.Format);
+
+ /// Checks the properties and throws an exception if values are invalid.
+ internal void ThrowOnInvalidValues()
+ {
+ if (this.Uv0.X is < 0 or > 1 or float.NaN)
+ throw new ArgumentException($"{nameof(this.Uv0)}.X is out of range.");
+
+ if (this.Uv0.Y is < 0 or > 1 or float.NaN)
+ throw new ArgumentException($"{nameof(this.Uv0)}.Y is out of range.");
+
+ if (this.Uv1Effective.X is < 0 or > 1 or float.NaN)
+ throw new ArgumentException($"{nameof(this.Uv1)}.X is out of range.");
+
+ if (this.Uv1Effective.Y is < 0 or > 1 or float.NaN)
+ throw new ArgumentException($"{nameof(this.Uv1)}.Y is out of range.");
+
+ if (this.Uv0.X >= this.Uv1Effective.X || this.Uv0.Y >= this.Uv1Effective.Y)
+ {
+ throw new ArgumentException(
+ $"{nameof(this.Uv0)} must be strictly less than {nameof(this.Uv1)} in a componentwise way.");
+ }
+
+ if (this.NewWidth < 0)
+ throw new ArgumentException($"{nameof(this.NewWidth)} cannot be a negative number.");
+
+ if (this.NewHeight < 0)
+ throw new ArgumentException($"{nameof(this.NewHeight)} cannot be a negative number.");
+ }
+}
diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
index a0562f1ef..1dfe96fe2 100644
--- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs
@@ -28,7 +28,8 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
public static SharedImmediateTexture CreatePlaceholder(string path) => new GamePathSharedImmediateTexture(path);
///
- public override string ToString() => $"{nameof(GamePathSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})";
+ public override string ToString() =>
+ $"{nameof(GamePathSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})";
///
protected override void ReleaseResources()
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Drawer.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Drawer.cs
new file mode 100644
index 000000000..c1043e914
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.Drawer.cs
@@ -0,0 +1,406 @@
+using System.Buffers;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+
+using Dalamud.Interface.Internal;
+using Dalamud.Storage.Assets;
+using Dalamud.Utility;
+
+using ImGuiNET;
+
+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 SimpleDrawerImpl? simpleDrawer;
+
+ [ServiceManager.CallWhenServicesReady("Need device")]
+ private unsafe void ContinueConstructionFromExistingTextures(InterfaceManager.InterfaceManagerWithScene withScene)
+ {
+ this.simpleDrawer = new();
+ this.simpleDrawer.Setup(this.Device.Get());
+ }
+
+ /// A class for drawing simple stuff.
+ [SuppressMessage(
+ "StyleCop.CSharp.LayoutRules",
+ "SA1519:Braces should not be omitted from multi-line child statement",
+ Justification = "Multiple fixed blocks")]
+ internal sealed unsafe class SimpleDrawerImpl : IDisposable
+ {
+ private ComPtr sampler;
+ private ComPtr vertexShader;
+ private ComPtr pixelShader;
+ private ComPtr inputLayout;
+ private ComPtr vertexConstantBuffer;
+ private ComPtr blendState;
+ private ComPtr blendStateForStrippingAlpha;
+ private ComPtr rasterizerState;
+ private ComPtr vertexBufferFill;
+ private ComPtr vertexBufferMutable;
+ private ComPtr indexBuffer;
+
+ /// Finalizes an instance of the class.
+ ~SimpleDrawerImpl() => this.Dispose();
+
+ ///
+ public void Dispose()
+ {
+ this.sampler.Reset();
+ this.vertexShader.Reset();
+ this.pixelShader.Reset();
+ this.inputLayout.Reset();
+ this.vertexConstantBuffer.Reset();
+ this.blendState.Reset();
+ this.blendStateForStrippingAlpha.Reset();
+ this.rasterizerState.Reset();
+ this.vertexBufferFill.Reset();
+ this.vertexBufferMutable.Reset();
+ this.indexBuffer.Reset();
+ GC.SuppressFinalize(this);
+ }
+
+ /// Sets up this instance of .
+ /// The device.
+ public void Setup(ID3D11Device* device)
+ {
+ var assembly = typeof(ImGuiScene.ImGui_Impl_DX11).Assembly;
+
+ // Create the vertex shader
+ if (this.vertexShader.IsEmpty() || this.inputLayout.IsEmpty())
+ {
+ this.vertexShader.Reset();
+ this.inputLayout.Reset();
+
+ using var stream = assembly.GetManifestResourceStream("imgui-vertex.hlsl.bytes")!;
+ var array = ArrayPool.Shared.Rent((int)stream.Length);
+ stream.ReadExactly(array, 0, (int)stream.Length);
+ fixed (byte* pArray = array)
+ fixed (ID3D11VertexShader** ppShader = &this.vertexShader.GetPinnableReference())
+ fixed (ID3D11InputLayout** ppInputLayout = &this.inputLayout.GetPinnableReference())
+ fixed (void* pszPosition = "POSITION"u8)
+ fixed (void* pszTexCoord = "TEXCOORD"u8)
+ fixed (void* pszColor = "COLOR"u8)
+ {
+ device->CreateVertexShader(pArray, (nuint)stream.Length, null, ppShader).ThrowOnError();
+
+ var ied = stackalloc D3D11_INPUT_ELEMENT_DESC[]
+ {
+ new()
+ {
+ SemanticName = (sbyte*)pszPosition,
+ Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT,
+ AlignedByteOffset = uint.MaxValue,
+ },
+ new()
+ {
+ SemanticName = (sbyte*)pszTexCoord,
+ Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT,
+ AlignedByteOffset = uint.MaxValue,
+ },
+ new()
+ {
+ SemanticName = (sbyte*)pszColor,
+ Format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
+ AlignedByteOffset = uint.MaxValue,
+ },
+ };
+ device->CreateInputLayout(ied, 3, pArray, (nuint)stream.Length, ppInputLayout).ThrowOnError();
+ }
+
+ ArrayPool.Shared.Return(array);
+ }
+
+ // Create the constant buffer
+ if (this.vertexConstantBuffer.IsEmpty())
+ {
+ var bufferDesc = new D3D11_BUFFER_DESC(
+ (uint)sizeof(Matrix4x4),
+ (uint)D3D11_BIND_FLAG.D3D11_BIND_CONSTANT_BUFFER,
+ D3D11_USAGE.D3D11_USAGE_IMMUTABLE);
+ var data = Matrix4x4.Identity;
+ var subr = new D3D11_SUBRESOURCE_DATA { pSysMem = &data };
+ fixed (ID3D11Buffer** ppBuffer = &this.vertexConstantBuffer.GetPinnableReference())
+ device->CreateBuffer(&bufferDesc, &subr, ppBuffer).ThrowOnError();
+ }
+
+ // Create the pixel shader
+ if (this.pixelShader.IsEmpty())
+ {
+ using var stream = assembly.GetManifestResourceStream("imgui-frag.hlsl.bytes")!;
+ var array = ArrayPool.Shared.Rent((int)stream.Length);
+ stream.ReadExactly(array, 0, (int)stream.Length);
+ fixed (byte* pArray = array)
+ fixed (ID3D11PixelShader** ppShader = &this.pixelShader.GetPinnableReference())
+ device->CreatePixelShader(pArray, (nuint)stream.Length, null, ppShader).ThrowOnError();
+
+ ArrayPool.Shared.Return(array);
+ }
+
+ // Create the blending setup
+ if (this.blendState.IsEmpty())
+ {
+ var blendStateDesc = new D3D11_BLEND_DESC
+ {
+ RenderTarget =
+ {
+ e0 =
+ {
+ BlendEnable = true,
+ SrcBlend = D3D11_BLEND.D3D11_BLEND_SRC_ALPHA,
+ DestBlend = D3D11_BLEND.D3D11_BLEND_INV_SRC_ALPHA,
+ BlendOp = D3D11_BLEND_OP.D3D11_BLEND_OP_ADD,
+ SrcBlendAlpha = D3D11_BLEND.D3D11_BLEND_INV_DEST_ALPHA,
+ DestBlendAlpha = D3D11_BLEND.D3D11_BLEND_ONE,
+ BlendOpAlpha = D3D11_BLEND_OP.D3D11_BLEND_OP_ADD,
+ RenderTargetWriteMask = (byte)D3D11_COLOR_WRITE_ENABLE.D3D11_COLOR_WRITE_ENABLE_ALL,
+ },
+ },
+ };
+ fixed (ID3D11BlendState** ppBlendState = &this.blendState.GetPinnableReference())
+ device->CreateBlendState(&blendStateDesc, ppBlendState).ThrowOnError();
+ }
+
+ if (this.blendStateForStrippingAlpha.IsEmpty())
+ {
+ var blendStateDesc = new D3D11_BLEND_DESC
+ {
+ RenderTarget =
+ {
+ e0 =
+ {
+ BlendEnable = true,
+ SrcBlend = D3D11_BLEND.D3D11_BLEND_ZERO,
+ DestBlend = D3D11_BLEND.D3D11_BLEND_ONE,
+ BlendOp = D3D11_BLEND_OP.D3D11_BLEND_OP_ADD,
+ SrcBlendAlpha = D3D11_BLEND.D3D11_BLEND_ONE,
+ DestBlendAlpha = D3D11_BLEND.D3D11_BLEND_ZERO,
+ BlendOpAlpha = D3D11_BLEND_OP.D3D11_BLEND_OP_ADD,
+ RenderTargetWriteMask = (byte)D3D11_COLOR_WRITE_ENABLE.D3D11_COLOR_WRITE_ENABLE_ALPHA,
+ },
+ },
+ };
+ fixed (ID3D11BlendState** ppBlendState = &this.blendStateForStrippingAlpha.GetPinnableReference())
+ device->CreateBlendState(&blendStateDesc, ppBlendState).ThrowOnError();
+ }
+
+ // Create the rasterizer state
+ if (this.rasterizerState.IsEmpty())
+ {
+ var rasterizerDesc = new D3D11_RASTERIZER_DESC
+ {
+ FillMode = D3D11_FILL_MODE.D3D11_FILL_SOLID,
+ CullMode = D3D11_CULL_MODE.D3D11_CULL_NONE,
+ };
+ fixed (ID3D11RasterizerState** ppRasterizerState = &this.rasterizerState.GetPinnableReference())
+ device->CreateRasterizerState(&rasterizerDesc, ppRasterizerState).ThrowOnError();
+ }
+
+ // Create the font sampler
+ if (this.sampler.IsEmpty())
+ {
+ var samplerDesc = new D3D11_SAMPLER_DESC(
+ D3D11_FILTER.D3D11_FILTER_MIN_MAG_MIP_LINEAR,
+ D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
+ D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
+ D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
+ 0f,
+ 0,
+ D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS,
+ null,
+ 0,
+ 0);
+ fixed (ID3D11SamplerState** ppSampler = &this.sampler.GetPinnableReference())
+ device->CreateSamplerState(&samplerDesc, ppSampler).ThrowOnError();
+ }
+
+ if (this.vertexBufferFill.IsEmpty())
+ {
+ var data = stackalloc ImDrawVert[]
+ {
+ new() { col = uint.MaxValue, pos = new(-1, 1), uv = new(0, 0) },
+ new() { col = uint.MaxValue, pos = new(-1, -1), uv = new(0, 1) },
+ new() { col = uint.MaxValue, pos = new(1, 1), uv = new(1, 0) },
+ new() { col = uint.MaxValue, pos = new(1, -1), uv = new(1, 1) },
+ };
+ var desc = new D3D11_BUFFER_DESC(
+ (uint)(sizeof(ImDrawVert) * 4),
+ (uint)D3D11_BIND_FLAG.D3D11_BIND_VERTEX_BUFFER,
+ D3D11_USAGE.D3D11_USAGE_IMMUTABLE);
+ var subr = new D3D11_SUBRESOURCE_DATA { pSysMem = data };
+ var buffer = default(ID3D11Buffer*);
+ device->CreateBuffer(&desc, &subr, &buffer).ThrowOnError();
+ this.vertexBufferFill.Attach(buffer);
+ }
+
+ if (this.vertexBufferMutable.IsEmpty())
+ {
+ var desc = new D3D11_BUFFER_DESC(
+ (uint)(sizeof(ImDrawVert) * 4),
+ (uint)D3D11_BIND_FLAG.D3D11_BIND_VERTEX_BUFFER,
+ D3D11_USAGE.D3D11_USAGE_DYNAMIC,
+ (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE);
+ var buffer = default(ID3D11Buffer*);
+ device->CreateBuffer(&desc, null, &buffer).ThrowOnError();
+ this.vertexBufferMutable.Attach(buffer);
+ }
+
+ if (this.indexBuffer.IsEmpty())
+ {
+ var data = stackalloc ushort[] { 0, 1, 2, 1, 2, 3 };
+ var desc = new D3D11_BUFFER_DESC(
+ sizeof(ushort) * 6,
+ (uint)D3D11_BIND_FLAG.D3D11_BIND_INDEX_BUFFER,
+ D3D11_USAGE.D3D11_USAGE_IMMUTABLE);
+ var subr = new D3D11_SUBRESOURCE_DATA { pSysMem = data };
+ var buffer = default(ID3D11Buffer*);
+ device->CreateBuffer(&desc, &subr, &buffer).ThrowOnError();
+ this.indexBuffer.Attach(buffer);
+ }
+ }
+
+ /// Draws the given shader resource view to the current render target.
+ /// An instance of .
+ /// The shader resource view.
+ /// The left top coordinates relative to the size of the source texture.
+ /// The right bottom coordinates relative to the size of the source texture.
+ /// This function does not throw.
+ public void Draw(
+ ID3D11DeviceContext* ctx,
+ ID3D11ShaderResourceView* srv,
+ Vector2 uv0,
+ Vector2 uv1)
+ {
+ using var rtv = default(ComPtr);
+ ctx->OMGetRenderTargets(1, rtv.GetAddressOf(), null);
+ if (rtv.IsEmpty())
+ return;
+
+ using var rtvRes = default(ComPtr);
+ rtv.Get()->GetResource(rtvRes.GetAddressOf());
+
+ using var rtvTex = default(ComPtr);
+ if (rtvRes.As(&rtvTex).FAILED)
+ return;
+
+ D3D11_TEXTURE2D_DESC texDesc;
+ rtvTex.Get()->GetDesc(&texDesc);
+
+ ID3D11Buffer* buffer;
+ if (uv0 == Vector2.Zero && uv1 == Vector2.One)
+ {
+ buffer = this.vertexBufferFill.Get();
+ }
+ else
+ {
+ buffer = this.vertexBufferMutable.Get();
+ var mapped = default(D3D11_MAPPED_SUBRESOURCE);
+ if (ctx->Map((ID3D11Resource*)buffer, 0, D3D11_MAP.D3D11_MAP_WRITE_DISCARD, 0u, &mapped).FAILED)
+ return;
+ _ = new Span(mapped.pData, 4)
+ {
+ [0] = new() { col = uint.MaxValue, pos = new(-1, 1), uv = uv0 },
+ [1] = new() { col = uint.MaxValue, pos = new(-1, -1), uv = new(uv0.X, uv1.Y) },
+ [2] = new() { col = uint.MaxValue, pos = new(1, 1), uv = new(uv1.X, uv0.Y) },
+ [3] = new() { col = uint.MaxValue, pos = new(1, -1), uv = uv1 },
+ };
+ ctx->Unmap((ID3D11Resource*)buffer, 0u);
+ }
+
+ var stride = (uint)sizeof(ImDrawVert);
+ var offset = 0u;
+
+ ctx->IASetInputLayout(this.inputLayout);
+ ctx->IASetVertexBuffers(0, 1, &buffer, &stride, &offset);
+ ctx->IASetIndexBuffer(this.indexBuffer, DXGI_FORMAT.DXGI_FORMAT_R16_UINT, 0);
+ ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY.D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ var viewport = new D3D11_VIEWPORT(0, 0, texDesc.Width, texDesc.Height);
+ ctx->RSSetState(this.rasterizerState);
+ ctx->RSSetViewports(1, &viewport);
+
+ var blendColor = default(Vector4);
+ ctx->OMSetBlendState(this.blendState, (float*)&blendColor, 0xffffffff);
+ ctx->OMSetDepthStencilState(null, 0);
+
+ ctx->VSSetShader(this.vertexShader.Get(), null, 0);
+ buffer = this.vertexConstantBuffer.Get();
+ ctx->VSSetConstantBuffers(0, 1, &buffer);
+
+ ctx->PSSetShader(this.pixelShader, null, 0);
+ var simp = this.sampler.Get();
+ ctx->PSSetSamplers(0, 1, &simp);
+ ctx->PSSetShaderResources(0, 1, &srv);
+
+ ctx->GSSetShader(null, null, 0);
+ ctx->HSSetShader(null, null, 0);
+ ctx->DSSetShader(null, null, 0);
+ ctx->CSSetShader(null, null, 0);
+ ctx->DrawIndexed(6, 0, 0);
+
+ var ppn = default(ID3D11ShaderResourceView*);
+ ctx->PSSetShaderResources(0, 1, &ppn);
+ }
+
+ /// Fills alpha channel to 1.0 from the current render target.
+ /// An instance of .
+ /// This function does not throw.
+ public void StripAlpha(ID3D11DeviceContext* ctx)
+ {
+ using var rtv = default(ComPtr);
+ ctx->OMGetRenderTargets(1, rtv.GetAddressOf(), null);
+ if (rtv.IsEmpty())
+ return;
+
+ using var rtvRes = default(ComPtr);
+ rtv.Get()->GetResource(rtvRes.GetAddressOf());
+
+ using var rtvTex = default(ComPtr);
+ if (rtvRes.As(&rtvTex).FAILED)
+ return;
+
+ D3D11_TEXTURE2D_DESC texDesc;
+ rtvTex.Get()->GetDesc(&texDesc);
+
+ var buffer = this.vertexBufferFill.Get();
+ var stride = (uint)sizeof(ImDrawVert);
+ var offset = 0u;
+
+ ctx->IASetInputLayout(this.inputLayout);
+ ctx->IASetVertexBuffers(0, 1, &buffer, &stride, &offset);
+ ctx->IASetIndexBuffer(this.indexBuffer, DXGI_FORMAT.DXGI_FORMAT_R16_UINT, 0);
+ ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY.D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ var viewport = new D3D11_VIEWPORT(0, 0, texDesc.Width, texDesc.Height);
+ ctx->RSSetState(this.rasterizerState);
+ ctx->RSSetViewports(1, &viewport);
+
+ var blendColor = default(Vector4);
+ ctx->OMSetBlendState(this.blendStateForStrippingAlpha, (float*)&blendColor, 0xffffffff);
+ ctx->OMSetDepthStencilState(null, 0);
+
+ ctx->VSSetShader(this.vertexShader.Get(), null, 0);
+ buffer = this.vertexConstantBuffer.Get();
+ ctx->VSSetConstantBuffers(0, 1, &buffer);
+
+ ctx->PSSetShader(this.pixelShader, null, 0);
+ var simp = this.sampler.Get();
+ ctx->PSSetSamplers(0, 1, &simp);
+ var ppn = (ID3D11ShaderResourceView*)Service.Get().White4X4.ImGuiHandle;
+ ctx->PSSetShaderResources(0, 1, &ppn);
+
+ ctx->GSSetShader(null, null, 0);
+ ctx->HSSetShader(null, null, 0);
+ ctx->DSSetShader(null, null, 0);
+ ctx->CSSetShader(null, null, 0);
+ ctx->DrawIndexed(6, 0, 0);
+
+ ppn = default;
+ ctx->PSSetShaderResources(0, 1, &ppn);
+ }
+ }
+}
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
index fc2a9e70f..56b0356e5 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs
@@ -1,6 +1,3 @@
-using System.Buffers;
-using System.Diagnostics.CodeAnalysis;
-using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
@@ -18,8 +15,6 @@ namespace Dalamud.Interface.Textures.Internal;
/// Service responsible for loading and disposing ImGui texture wraps.
internal sealed partial class TextureManager
{
- private DrawsOneSquare? drawsOneSquare;
-
///
bool ITextureProvider.IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat) =>
this.IsDxgiFormatSupportedForCreateFromExistingTextureAsync((DXGI_FORMAT)dxgiFormat);
@@ -50,27 +45,9 @@ internal sealed partial class TextureManager
}
///
- Task ITextureProvider.CreateFromExistingTextureAsync(
- IDalamudTextureWrap wrap,
- Vector2 uv0,
- Vector2 uv1,
- int dxgiFormat,
- bool leaveWrapOpen,
- CancellationToken cancellationToken) =>
- this.CreateFromExistingTextureAsync(
- wrap,
- uv0,
- uv1,
- (DXGI_FORMAT)dxgiFormat,
- leaveWrapOpen,
- cancellationToken);
-
- ///
public Task CreateFromExistingTextureAsync(
IDalamudTextureWrap wrap,
- Vector2 uv0,
- Vector2 uv1,
- DXGI_FORMAT format = DXGI_FORMAT.DXGI_FORMAT_UNKNOWN,
+ ExistingTextureModificationArgs args = default,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default)
{
@@ -82,7 +59,7 @@ internal sealed partial class TextureManager
async Task ImmediateLoadFunction(CancellationToken ct)
{
- using var tex = await this.NoThrottleCreateFromExistingTextureAsync(wrap, uv0, uv1, format);
+ using var tex = await this.NoThrottleCreateFromExistingTextureAsync(wrap, args);
unsafe
{
@@ -108,27 +85,52 @@ internal sealed partial class TextureManager
}
///
- Task<(RawImageSpecification Specification, byte[] RawData)> ITextureProvider.GetRawDataFromExistingTextureAsync(
- IDalamudTextureWrap wrap,
- Vector2 uv0,
- Vector2 uv1,
- int dxgiFormat,
- bool leaveWrapOpen,
- CancellationToken cancellationToken) =>
- this.GetRawDataFromExistingTextureAsync(
- wrap,
- uv0,
- uv1,
- (DXGI_FORMAT)dxgiFormat,
- leaveWrapOpen,
- cancellationToken);
+ public Task CreateFromGameScreen(
+ bool autoUpdate = false,
+ CancellationToken cancellationToken = default) =>
+ this.interfaceManager.RunBeforeImGuiRender(
+ () =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var t = new ViewportTextureWrap(ImGui.GetMainViewport().ID, true, autoUpdate, cancellationToken);
+ t.Update();
+ try
+ {
+ return t.FirstUpdateTask.Result;
+ }
+ catch
+ {
+ t.Dispose();
+ throw;
+ }
+ });
- ///
+ ///
+ public Task CreateFromImGuiViewport(
+ uint viewportId,
+ bool autoUpdate = false,
+ CancellationToken cancellationToken = default) =>
+ this.interfaceManager.RunBeforeImGuiRender(
+ () =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var t = new ViewportTextureWrap(viewportId, false, autoUpdate, cancellationToken);
+ t.Update();
+ try
+ {
+ return t.FirstUpdateTask.Result;
+ }
+ catch
+ {
+ t.Dispose();
+ throw;
+ }
+ });
+
+ ///
public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataFromExistingTextureAsync(
IDalamudTextureWrap wrap,
- Vector2 uv0,
- Vector2 uv1,
- DXGI_FORMAT dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_UNKNOWN,
+ ExistingTextureModificationArgs args = default,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default)
{
@@ -157,9 +159,9 @@ internal sealed partial class TextureManager
tex2D.Get()->GetDesc(&texDesc);
}
- if (texDesc.Format != dxgiFormat && dxgiFormat != DXGI_FORMAT.DXGI_FORMAT_UNKNOWN)
+ if (!args.IsCompleteSourceCopy(texDesc))
{
- using var tmp = await this.NoThrottleCreateFromExistingTextureAsync(wrap, uv0, uv1, dxgiFormat);
+ using var tmp = await this.NoThrottleCreateFromExistingTextureAsync(wrap, args);
unsafe
{
tex2D.Swap(&tmp);
@@ -167,7 +169,7 @@ internal sealed partial class TextureManager
}
cancellationToken.ThrowIfCancellationRequested();
- return await this.interfaceManager.RunBeforePresent(
+ return await this.interfaceManager.RunBeforeImGuiRender(
() => ExtractMappedResource(this.Device, context, tex2D, cancellationToken));
static unsafe (RawImageSpecification Specification, byte[] RawData) ExtractMappedResource(
@@ -214,8 +216,8 @@ internal sealed partial class TextureManager
var specs = new RawImageSpecification(
(int)desc.Width,
(int)desc.Height,
- (int)mapped.RowPitch,
- (int)desc.Format);
+ (int)desc.Format,
+ (int)mapped.RowPitch);
var bytes = new Span(mapped.pData, checked((int)mapped.DepthPitch)).ToArray();
return (specs, bytes);
}
@@ -229,10 +231,10 @@ internal sealed partial class TextureManager
private async Task> NoThrottleCreateFromExistingTextureAsync(
IDalamudTextureWrap wrap,
- Vector2 uv0,
- Vector2 uv1,
- DXGI_FORMAT format)
+ ExistingTextureModificationArgs args)
{
+ args.ThrowOnInvalidValues();
+
using var texSrv = default(ComPtr);
using var context = default(ComPtr);
using var tex2D = default(ComPtr);
@@ -254,33 +256,34 @@ internal sealed partial class TextureManager
tex2D.Get()->GetDesc(&texDesc);
}
- if (format == DXGI_FORMAT.DXGI_FORMAT_UNKNOWN)
- format = texDesc.Format;
-
- var newWidth = checked((uint)MathF.Round((uv1.X - uv0.X) * texDesc.Width));
- var newHeight = checked((uint)MathF.Round((uv1.Y - uv0.Y) * texDesc.Height));
+ if (args.Format == DXGI_FORMAT.DXGI_FORMAT_UNKNOWN)
+ args = args with { Format = texDesc.Format };
+ if (args.NewWidth == 0)
+ args = args with { NewWidth = (int)MathF.Round((args.Uv1Effective.X - args.Uv0.X) * texDesc.Width) };
+ if (args.NewHeight == 0)
+ args = args with { NewHeight = (int)MathF.Round((args.Uv1Effective.Y - args.Uv0.Y) * texDesc.Height) };
using var tex2DCopyTemp = default(ComPtr);
unsafe
{
var tex2DCopyTempDesc = new D3D11_TEXTURE2D_DESC
{
- Width = newWidth,
- Height = newHeight,
+ Width = (uint)args.NewWidth,
+ Height = (uint)args.NewHeight,
MipLevels = 1,
ArraySize = 1,
- Format = format,
+ Format = args.Format,
SampleDesc = new(1, 0),
Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT,
- BindFlags =
- (uint)(D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET),
+ BindFlags = (uint)(D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE |
+ D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET),
CPUAccessFlags = 0u,
MiscFlags = 0u,
};
this.Device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
}
- await this.interfaceManager.RunBeforePresent(
+ await this.interfaceManager.RunBeforeImGuiRender(
() =>
{
unsafe
@@ -294,292 +297,20 @@ internal sealed partial class TextureManager
&rtvCopyTempDesc,
rtvCopyTemp.GetAddressOf()).ThrowOnError();
- this.drawsOneSquare ??= new();
- this.drawsOneSquare.Setup(this.Device.Get());
-
context.Get()->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
- this.drawsOneSquare.Draw(
+ this.SimpleDrawer.Draw(
context.Get(),
texSrv.Get(),
- (int)newWidth,
- (int)newHeight,
- uv0,
- uv1);
- context.Get()->OMSetRenderTargets(0, null, null);
+ args.Uv0,
+ args.Uv1Effective);
+ if (args.MakeOpaque)
+ this.SimpleDrawer.StripAlpha(context.Get());
+
+ var dummy = default(ID3D11RenderTargetView*);
+ context.Get()->OMSetRenderTargets(1u, &dummy, null);
}
});
return new(tex2DCopyTemp);
}
-
- [SuppressMessage(
- "StyleCop.CSharp.LayoutRules",
- "SA1519:Braces should not be omitted from multi-line child statement",
- Justification = "Multiple fixed blocks")]
- private sealed unsafe class DrawsOneSquare : IDisposable
- {
- private ComPtr sampler;
- private ComPtr vertexShader;
- private ComPtr pixelShader;
- private ComPtr inputLayout;
- private ComPtr vertexConstantBuffer;
- private ComPtr blendState;
- private ComPtr rasterizerState;
- private ComPtr vertexBufferFill;
- private ComPtr vertexBufferMutable;
- private ComPtr indexBuffer;
-
- ~DrawsOneSquare() => this.Dispose();
-
- public void Dispose()
- {
- this.sampler.Reset();
- this.vertexShader.Reset();
- this.pixelShader.Reset();
- this.inputLayout.Reset();
- this.vertexConstantBuffer.Reset();
- this.blendState.Reset();
- this.rasterizerState.Reset();
- this.vertexBufferFill.Reset();
- this.vertexBufferMutable.Reset();
- this.indexBuffer.Reset();
- }
-
- public void Setup(T* device) where T : unmanaged, ID3D11Device.Interface
- {
- var assembly = typeof(ImGuiScene.ImGui_Impl_DX11).Assembly;
-
- // Create the vertex shader
- if (this.vertexShader.IsEmpty() || this.inputLayout.IsEmpty())
- {
- this.vertexShader.Reset();
- this.inputLayout.Reset();
-
- using var stream = assembly.GetManifestResourceStream("imgui-vertex.hlsl.bytes")!;
- var array = ArrayPool.Shared.Rent((int)stream.Length);
- stream.ReadExactly(array, 0, (int)stream.Length);
- fixed (byte* pArray = array)
- fixed (ID3D11VertexShader** ppShader = &this.vertexShader.GetPinnableReference())
- fixed (ID3D11InputLayout** ppInputLayout = &this.inputLayout.GetPinnableReference())
- fixed (void* pszPosition = "POSITION"u8)
- fixed (void* pszTexCoord = "TEXCOORD"u8)
- fixed (void* pszColor = "COLOR"u8)
- {
- device->CreateVertexShader(pArray, (nuint)stream.Length, null, ppShader).ThrowOnError();
-
- var ied = stackalloc D3D11_INPUT_ELEMENT_DESC[]
- {
- new()
- {
- SemanticName = (sbyte*)pszPosition,
- Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT,
- AlignedByteOffset = uint.MaxValue,
- },
- new()
- {
- SemanticName = (sbyte*)pszTexCoord,
- Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT,
- AlignedByteOffset = uint.MaxValue,
- },
- new()
- {
- SemanticName = (sbyte*)pszColor,
- Format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
- AlignedByteOffset = uint.MaxValue,
- },
- };
- device->CreateInputLayout(ied, 3, pArray, (nuint)stream.Length, ppInputLayout).ThrowOnError();
- }
-
- ArrayPool.Shared.Return(array);
- }
-
- // Create the constant buffer
- if (this.vertexConstantBuffer.IsEmpty())
- {
- var bufferDesc = new D3D11_BUFFER_DESC(
- (uint)sizeof(Matrix4x4),
- (uint)D3D11_BIND_FLAG.D3D11_BIND_CONSTANT_BUFFER,
- D3D11_USAGE.D3D11_USAGE_IMMUTABLE);
- var data = Matrix4x4.Identity;
- var subr = new D3D11_SUBRESOURCE_DATA { pSysMem = &data };
- fixed (ID3D11Buffer** ppBuffer = &this.vertexConstantBuffer.GetPinnableReference())
- device->CreateBuffer(&bufferDesc, &subr, ppBuffer).ThrowOnError();
- }
-
- // Create the pixel shader
- if (this.pixelShader.IsEmpty())
- {
- using var stream = assembly.GetManifestResourceStream("imgui-frag.hlsl.bytes")!;
- var array = ArrayPool.Shared.Rent((int)stream.Length);
- stream.ReadExactly(array, 0, (int)stream.Length);
- fixed (byte* pArray = array)
- fixed (ID3D11PixelShader** ppShader = &this.pixelShader.GetPinnableReference())
- device->CreatePixelShader(pArray, (nuint)stream.Length, null, ppShader).ThrowOnError();
-
- ArrayPool.Shared.Return(array);
- }
-
- // Create the blending setup
- if (this.blendState.IsEmpty())
- {
- var blendStateDesc = new D3D11_BLEND_DESC
- {
- RenderTarget =
- {
- e0 =
- {
- BlendEnable = true,
- SrcBlend = D3D11_BLEND.D3D11_BLEND_SRC_ALPHA,
- DestBlend = D3D11_BLEND.D3D11_BLEND_INV_SRC_ALPHA,
- BlendOp = D3D11_BLEND_OP.D3D11_BLEND_OP_ADD,
- SrcBlendAlpha = D3D11_BLEND.D3D11_BLEND_INV_DEST_ALPHA,
- DestBlendAlpha = D3D11_BLEND.D3D11_BLEND_ONE,
- BlendOpAlpha = D3D11_BLEND_OP.D3D11_BLEND_OP_ADD,
- RenderTargetWriteMask = (byte)D3D11_COLOR_WRITE_ENABLE.D3D11_COLOR_WRITE_ENABLE_ALL,
- },
- },
- };
- fixed (ID3D11BlendState** ppBlendState = &this.blendState.GetPinnableReference())
- device->CreateBlendState(&blendStateDesc, ppBlendState).ThrowOnError();
- }
-
- // Create the rasterizer state
- if (this.rasterizerState.IsEmpty())
- {
- var rasterizerDesc = new D3D11_RASTERIZER_DESC
- {
- FillMode = D3D11_FILL_MODE.D3D11_FILL_SOLID,
- CullMode = D3D11_CULL_MODE.D3D11_CULL_NONE,
- };
- fixed (ID3D11RasterizerState** ppRasterizerState = &this.rasterizerState.GetPinnableReference())
- device->CreateRasterizerState(&rasterizerDesc, ppRasterizerState).ThrowOnError();
- }
-
- // Create the font sampler
- if (this.sampler.IsEmpty())
- {
- var samplerDesc = new D3D11_SAMPLER_DESC(
- D3D11_FILTER.D3D11_FILTER_MIN_MAG_MIP_LINEAR,
- D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
- D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
- D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
- 0f,
- 0,
- D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS,
- null,
- 0,
- 0);
- fixed (ID3D11SamplerState** ppSampler = &this.sampler.GetPinnableReference())
- device->CreateSamplerState(&samplerDesc, ppSampler).ThrowOnError();
- }
-
- if (this.vertexBufferFill.IsEmpty())
- {
- var data = stackalloc ImDrawVert[]
- {
- new() { col = uint.MaxValue, pos = new(-1, 1), uv = new(0, 0) },
- new() { col = uint.MaxValue, pos = new(-1, -1), uv = new(0, 1) },
- new() { col = uint.MaxValue, pos = new(1, 1), uv = new(1, 0) },
- new() { col = uint.MaxValue, pos = new(1, -1), uv = new(1, 1) },
- };
- var desc = new D3D11_BUFFER_DESC(
- (uint)(sizeof(ImDrawVert) * 4),
- (uint)D3D11_BIND_FLAG.D3D11_BIND_VERTEX_BUFFER,
- D3D11_USAGE.D3D11_USAGE_IMMUTABLE);
- var subr = new D3D11_SUBRESOURCE_DATA { pSysMem = data };
- var buffer = default(ID3D11Buffer*);
- device->CreateBuffer(&desc, &subr, &buffer).ThrowOnError();
- this.vertexBufferFill.Attach(buffer);
- }
-
- if (this.vertexBufferMutable.IsEmpty())
- {
- var desc = new D3D11_BUFFER_DESC(
- (uint)(sizeof(ImDrawVert) * 4),
- (uint)D3D11_BIND_FLAG.D3D11_BIND_VERTEX_BUFFER,
- D3D11_USAGE.D3D11_USAGE_DYNAMIC,
- (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE);
- var buffer = default(ID3D11Buffer*);
- device->CreateBuffer(&desc, null, &buffer).ThrowOnError();
- this.vertexBufferMutable.Attach(buffer);
- }
-
- if (this.indexBuffer.IsEmpty())
- {
- var data = stackalloc ushort[] { 0, 1, 2, 1, 2, 3 };
- var desc = new D3D11_BUFFER_DESC(
- sizeof(ushort) * 6,
- (uint)D3D11_BIND_FLAG.D3D11_BIND_INDEX_BUFFER,
- D3D11_USAGE.D3D11_USAGE_IMMUTABLE);
- var subr = new D3D11_SUBRESOURCE_DATA { pSysMem = data };
- var buffer = default(ID3D11Buffer*);
- device->CreateBuffer(&desc, &subr, &buffer).ThrowOnError();
- this.indexBuffer.Attach(buffer);
- }
- }
-
- public void Draw(
- ID3D11DeviceContext* ctx,
- ID3D11ShaderResourceView* srv,
- int width,
- int height,
- Vector2 uv0,
- Vector2 uv1)
- {
- ID3D11Buffer* buffer;
- if (uv0 == Vector2.Zero && uv1 == Vector2.One)
- {
- buffer = this.vertexBufferFill.Get();
- }
- else
- {
- buffer = this.vertexBufferMutable.Get();
- var mapped = default(D3D11_MAPPED_SUBRESOURCE);
- ctx->Map((ID3D11Resource*)buffer, 0, D3D11_MAP.D3D11_MAP_WRITE_DISCARD, 0u, &mapped).ThrowOnError();
- _ = new Span(mapped.pData, 4)
- {
- [0] = new() { col = uint.MaxValue, pos = new(-1, 1), uv = uv0 },
- [1] = new() { col = uint.MaxValue, pos = new(-1, -1), uv = new(uv0.X, uv1.Y) },
- [2] = new() { col = uint.MaxValue, pos = new(1, 1), uv = new(uv1.X, uv0.Y) },
- [3] = new() { col = uint.MaxValue, pos = new(1, -1), uv = uv1 },
- };
- ctx->Unmap((ID3D11Resource*)buffer, 0u);
- }
-
- var stride = (uint)sizeof(ImDrawVert);
- var offset = 0u;
-
- ctx->IASetInputLayout(this.inputLayout);
- ctx->IASetVertexBuffers(0, 1, &buffer, &stride, &offset);
- ctx->IASetIndexBuffer(this.indexBuffer, DXGI_FORMAT.DXGI_FORMAT_R16_UINT, 0);
- ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY.D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
-
- var viewport = new D3D11_VIEWPORT(0, 0, width, height);
- ctx->RSSetState(this.rasterizerState);
- ctx->RSSetViewports(1, &viewport);
-
- var blendColor = default(Vector4);
- ctx->OMSetBlendState(this.blendState, (float*)&blendColor, 0xffffffff);
- ctx->OMSetDepthStencilState(null, 0);
-
- ctx->VSSetShader(this.vertexShader.Get(), null, 0);
- buffer = this.vertexConstantBuffer.Get();
- ctx->VSSetConstantBuffers(0, 1, &buffer);
-
- ctx->PSSetShader(this.pixelShader, null, 0);
- var simp = this.sampler.Get();
- ctx->PSSetSamplers(0, 1, &simp);
- ctx->PSSetShaderResources(0, 1, &srv);
-
- ctx->GSSetShader(null, null, 0);
- ctx->HSSetShader(null, null, 0);
- ctx->DSSetShader(null, null, 0);
- ctx->CSSetShader(null, null, 0);
- ctx->DrawIndexed(6, 0, 0);
-
- var ppn = default(ID3D11ShaderResourceView*);
- ctx->PSSetShaderResources(0, 1, &ppn);
- }
- }
}
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs
index 3a121c4c5..71f300479 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs
@@ -50,7 +50,7 @@ internal sealed partial class TextureManager
this.textureManager = textureManager;
this.textureManager.framework.Update += this.FrameworkOnUpdate;
}
-
+
/// Gets all the loaded textures from game resources.
public ICollection ForDebugGamePathTextures => this.gameDict.Values;
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
index 54e30dab0..b35864a17 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs
@@ -3,8 +3,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
-using System.Numerics;
-using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -43,13 +41,42 @@ internal sealed partial class TextureManager
CancellationToken cancellationToken = default)
{
using var wrapDispose = leaveWrapOpen ? null : wrap;
+ var texDesc = default(D3D11_TEXTURE2D_DESC);
+
+ unsafe
+ {
+ using var texSrv = default(ComPtr);
+ using var context = default(ComPtr);
+ using var tex2D = 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 tex2DTemp = default(ComPtr);
+ texRes.As(&tex2DTemp).ThrowOnError();
+ tex2D.Swap(&tex2DTemp);
+ }
+
+ tex2D.Get()->GetDesc(&texDesc);
+ }
+
+ var dxgiFormat = texDesc.Format;
+ if (!WicManager.GetCorrespondingWicPixelFormat(dxgiFormat, out _, out _))
+ dxgiFormat = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
+
using var istream = ManagedIStream.Create(stream, leaveStreamOpen);
var (specs, bytes) = await this.GetRawDataFromExistingTextureAsync(
wrap,
- Vector2.Zero,
- Vector2.One,
- DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
+ new()
+ {
+ Format = dxgiFormat,
+ },
true,
cancellationToken).ConfigureAwait(false);
@@ -80,9 +107,9 @@ internal sealed partial class TextureManager
containerGuid,
File.Create(pathTemp),
props,
- true,
- false,
- cancellationToken);
+ leaveWrapOpen: true,
+ leaveStreamOpen: false,
+ cancellationToken: cancellationToken);
}
catch (Exception e)
{
@@ -206,6 +233,7 @@ internal sealed partial class TextureManager
{
private readonly TextureManager textureManager;
private ComPtr wicFactory;
+ private ComPtr wicFactory2;
/// Initializes a new instance of the class.
/// An instance of .
@@ -216,13 +244,27 @@ internal sealed partial class TextureManager
{
fixed (Guid* pclsidWicImagingFactory = &CLSID.CLSID_WICImagingFactory)
fixed (Guid* piidWicImagingFactory = &IID.IID_IWICImagingFactory)
+ fixed (Guid* pclsidWicImagingFactory2 = &CLSID.CLSID_WICImagingFactory2)
+ fixed (Guid* piidWicImagingFactory2 = &IID.IID_IWICImagingFactory2)
{
- CoCreateInstance(
- pclsidWicImagingFactory,
- null,
- (uint)CLSCTX.CLSCTX_INPROC_SERVER,
- piidWicImagingFactory,
- (void**)this.wicFactory.GetAddressOf()).ThrowOnError();
+ if (CoCreateInstance(
+ pclsidWicImagingFactory2,
+ null,
+ (uint)CLSCTX.CLSCTX_INPROC_SERVER,
+ piidWicImagingFactory2,
+ (void**)this.wicFactory2.GetAddressOf()).SUCCEEDED)
+ {
+ this.wicFactory2.As(ref this.wicFactory).ThrowOnError();
+ }
+ else
+ {
+ CoCreateInstance(
+ pclsidWicImagingFactory,
+ null,
+ (uint)CLSCTX.CLSCTX_INPROC_SERVER,
+ piidWicImagingFactory,
+ (void**)this.wicFactory.GetAddressOf()).ThrowOnError();
+ }
}
}
}
@@ -232,6 +274,76 @@ internal sealed partial class TextureManager
///
~WicManager() => this.ReleaseUnmanagedResource();
+ ///
+ /// Gets the corresponding from a containing a WIC pixel format.
+ ///
+ /// The WIC pixel format.
+ /// The corresponding , or if
+ /// unavailable.
+ public static DXGI_FORMAT GetCorrespondingDxgiFormat(Guid fmt) => 0 switch
+ {
+ // See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile
+ _ when fmt == GUID.GUID_WICPixelFormat128bppRGBAFloat => DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT,
+ _ when fmt == GUID.GUID_WICPixelFormat64bppRGBAHalf => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT,
+ _ when fmt == GUID.GUID_WICPixelFormat64bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102XR => DXGI_FORMAT
+ .DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102 => DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat16bppBGRA5551 => DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat16bppBGR565 => DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppGrayFloat => DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT,
+ _ when fmt == GUID.GUID_WICPixelFormat16bppGrayHalf => DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT,
+ _ when fmt == GUID.GUID_WICPixelFormat16bppGray => DXGI_FORMAT.DXGI_FORMAT_R16_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat8bppGray => DXGI_FORMAT.DXGI_FORMAT_R8_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat8bppAlpha => DXGI_FORMAT.DXGI_FORMAT_A8_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppBGRA => DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
+ _ when fmt == GUID.GUID_WICPixelFormat32bppBGR => DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM,
+ _ => DXGI_FORMAT.DXGI_FORMAT_UNKNOWN,
+ };
+
+ ///
+ /// Gets the corresponding containing a WIC pixel format from a .
+ ///
+ /// The DXGI pixel format.
+ /// The corresponding .
+ /// Whether the image is in SRGB.
+ /// true if a corresponding pixel format exists.
+ public static bool GetCorrespondingWicPixelFormat(
+ DXGI_FORMAT dxgiPixelFormat,
+ out Guid wicPixelFormat,
+ out bool srgb)
+ {
+ wicPixelFormat = dxgiPixelFormat switch
+ {
+ // 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_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,
+ DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM => GUID.GUID_WICPixelFormat32bppRGBA1010102,
+ 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_WICPixelFormat32bppGrayFloat,
+ DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT => GUID.GUID_WICPixelFormat16bppGrayHalf,
+ DXGI_FORMAT.DXGI_FORMAT_R16_UNORM => GUID.GUID_WICPixelFormat16bppGray,
+ DXGI_FORMAT.DXGI_FORMAT_R8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
+ DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => GUID.GUID_WICPixelFormat8bppAlpha,
+ DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM => GUID.GUID_WICPixelFormat32bppRGBA,
+ DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => GUID.GUID_WICPixelFormat32bppRGBA,
+ DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => GUID.GUID_WICPixelFormat32bppBGRA,
+ DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => GUID.GUID_WICPixelFormat32bppBGRA,
+ DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => GUID.GUID_WICPixelFormat32bppBGR,
+ DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB => GUID.GUID_WICPixelFormat32bppBGR,
+ _ => Guid.Empty,
+ };
+ srgb = dxgiPixelFormat
+ is DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
+ or DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB
+ or DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+ return wicPixelFormat != Guid.Empty;
+ }
+
///
public void Dispose()
{
@@ -349,7 +461,7 @@ internal sealed partial class TextureManager
bitmapLock.Get()->GetDataPointer(&cbBufferSize, &pbData).ThrowOnError();
bitmapSource.Get()->CopyPixels(null, stride, cbBufferSize, pbData).ThrowOnError();
return this.textureManager.NoThrottleCreateFromRaw(
- new(rcLock.Width, rcLock.Height, (int)stride, (int)dxgiFormat),
+ new(rcLock.Width, rcLock.Height, (int)dxgiFormat, (int)stride),
new(pbData, (int)cbBufferSize));
}
@@ -382,15 +494,13 @@ internal sealed partial class TextureManager
/// The cancellation token.
public unsafe void SaveToStreamUsingWic(
RawImageSpecification specs,
- byte[] bytes,
+ ReadOnlySpan bytes,
Guid containerFormat,
ComPtr stream,
IReadOnlyDictionary? props = null,
CancellationToken cancellationToken = default)
{
- var outPixelFormat = GUID.GUID_WICPixelFormat32bppBGRA;
- var inPixelFormat = GetCorrespondingWicPixelFormat((DXGI_FORMAT)specs.DxgiFormat);
- if (inPixelFormat == Guid.Empty)
+ if (!GetCorrespondingWicPixelFormat((DXGI_FORMAT)specs.DxgiFormat, out var inPixelFormat, out var srgb))
throw new NotSupportedException("DXGI_FORMAT from specs is not supported by WIC.");
using var encoder = default(ComPtr);
@@ -398,6 +508,24 @@ internal sealed partial class TextureManager
this.wicFactory.Get()->CreateEncoder(&containerFormat, null, encoder.GetAddressOf()).ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
+ // See: DirectXTK/Src/ScreenGrab.cpp
+ var outPixelFormat = (DXGI_FORMAT)specs.DxgiFormat 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_R16G16B16A16_UNORM => GUID.GUID_WICPixelFormat64bppBGRA,
+ 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,
+ DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT => GUID.GUID_WICPixelFormat8bppGray,
+ 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,
+ };
+
encoder.Get()->Initialize(stream, WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache)
.ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
@@ -406,35 +534,14 @@ internal sealed partial class TextureManager
encoder.Get()->CreateNewFrame(encoderFrame.GetAddressOf(), propertyBag.GetAddressOf()).ThrowOnError();
cancellationToken.ThrowIfCancellationRequested();
+ // Opt-in to the WIC2 support for writing 32-bit Windows BMP files with an alpha channel
+ if (containerFormat == GUID.GUID_ContainerFormatBmp && !this.wicFactory2.IsEmpty())
+ propertyBag.Get()->Write("EnableV5Header32bppBGRA", true).ThrowOnError();
+
if (props is not null)
{
- var nprop = 0u;
- propertyBag.Get()->CountProperties(&nprop).ThrowOnError();
- for (var i = 0u; i < nprop; i++)
- {
- var pbag2 = default(PROPBAG2);
- var npropread = 0u;
- propertyBag.Get()->GetPropertyInfo(i, 1, &pbag2, &npropread).ThrowOnError();
- if (npropread == 0)
- continue;
- try
- {
- var propName = new string((char*)pbag2.pstrName);
- if (props.TryGetValue(propName, out var untypedValue))
- {
- VARIANT val;
- // Marshal calls VariantInit.
- Marshal.GetNativeVariantForObject(untypedValue, (nint)(&val));
- VariantChangeType(&val, &val, 0, pbag2.vt).ThrowOnError();
- propertyBag.Get()->Write(1, &pbag2, &val).ThrowOnError();
- VariantClear(&val);
- }
- }
- finally
- {
- CoTaskMemFree(pbag2.pstrName);
- }
- }
+ foreach (var (name, untypedValue) in props)
+ propertyBag.Get()->Write(name, untypedValue).ThrowOnError();
}
encoderFrame.Get()->Initialize(propertyBag).ThrowOnError();
@@ -442,6 +549,34 @@ internal sealed partial class TextureManager
encoderFrame.Get()->SetPixelFormat(&outPixelFormat).ThrowOnError();
encoderFrame.Get()->SetSize(checked((uint)specs.Width), checked((uint)specs.Height)).ThrowOnError();
+ using (var metaWriter = default(ComPtr))
+ {
+ if (encoderFrame.Get()->GetMetadataQueryWriter(metaWriter.GetAddressOf()).SUCCEEDED)
+ {
+ if (containerFormat == GUID.GUID_ContainerFormatPng)
+ {
+ // Set sRGB chunk
+ if (srgb)
+ {
+ _ = metaWriter.Get()->SetMetadataByName("/sRGB/RenderingIntent", (byte)0);
+ }
+ else
+ {
+ // add gAMA chunk with gamma 1.0
+ // gama value * 100,000 -- i.e. gamma 1.0
+ _ = metaWriter.Get()->SetMetadataByName("/sRGB/RenderingIntent", 100000U);
+
+ // remove sRGB chunk which is added by default.
+ _ = metaWriter.Get()->RemoveMetadataByName("/sRGB/RenderingIntent");
+ }
+ }
+ else
+ {
+ // Set EXIF Colorspace of sRGB
+ _ = metaWriter.Get()->SetMetadataByName("System.Image.ColorSpace", (ushort)0);
+ }
+ }
+ }
using var tempBitmap = default(ComPtr);
fixed (byte* pBytes = bytes)
@@ -480,61 +615,11 @@ internal sealed partial class TextureManager
encoder.Get()->Commit().ThrowOnError();
}
- ///
- /// Gets the corresponding from a containing a WIC pixel format.
- ///
- /// The WIC pixel format.
- /// The corresponding , or if
- /// unavailable.
- private static DXGI_FORMAT GetCorrespondingDxgiFormat(Guid fmt) => 0 switch
+ private void ReleaseUnmanagedResource()
{
- // See https://github.com/microsoft/DirectXTex/wiki/WIC-I-O-Functions#savetowicmemory-savetowicfile
- _ when fmt == GUID.GUID_WICPixelFormat128bppRGBAFloat => DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT,
- _ when fmt == GUID.GUID_WICPixelFormat64bppRGBAHalf => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT,
- _ when fmt == GUID.GUID_WICPixelFormat64bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102XR => DXGI_FORMAT
- .DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA1010102 => DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat16bppBGRA5551 => DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat16bppBGR565 => DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppGrayFloat => DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT,
- _ when fmt == GUID.GUID_WICPixelFormat16bppGrayHalf => DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT,
- _ when fmt == GUID.GUID_WICPixelFormat16bppGray => DXGI_FORMAT.DXGI_FORMAT_R16_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat8bppGray => DXGI_FORMAT.DXGI_FORMAT_R8_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat8bppAlpha => DXGI_FORMAT.DXGI_FORMAT_A8_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppRGBA => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppBGRA => DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
- _ when fmt == GUID.GUID_WICPixelFormat32bppBGR => DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM,
- _ => DXGI_FORMAT.DXGI_FORMAT_UNKNOWN,
- };
-
- ///
- /// Gets the corresponding containing a WIC pixel format from a .
- ///
- /// The DXGI pixel format.
- /// The corresponding , or if unavailable.
- private static Guid GetCorrespondingWicPixelFormat(DXGI_FORMAT fmt) => fmt switch
- {
- // 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_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,
- DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM => GUID.GUID_WICPixelFormat32bppRGBA1010102,
- 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_WICPixelFormat32bppGrayFloat,
- DXGI_FORMAT.DXGI_FORMAT_R16_FLOAT => GUID.GUID_WICPixelFormat16bppGrayHalf,
- DXGI_FORMAT.DXGI_FORMAT_R16_UNORM => GUID.GUID_WICPixelFormat16bppGray,
- DXGI_FORMAT.DXGI_FORMAT_R8_UNORM => GUID.GUID_WICPixelFormat8bppGray,
- DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => GUID.GUID_WICPixelFormat8bppAlpha,
- DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM => GUID.GUID_WICPixelFormat32bppRGBA,
- DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => GUID.GUID_WICPixelFormat32bppBGRA,
- DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => GUID.GUID_WICPixelFormat32bppBGR,
- _ => Guid.Empty,
- };
-
- private void ReleaseUnmanagedResource() => this.wicFactory.Reset();
+ this.wicFactory.Reset();
+ this.wicFactory2.Reset();
+ }
private readonly struct ComponentEnumerable : IEnumerable>
where T : unmanaged, IWICComponentInfo.Interface
diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.cs b/Dalamud/Interface/Textures/Internal/TextureManager.cs
index 522167c55..921febe94 100644
--- a/Dalamud/Interface/Textures/Internal/TextureManager.cs
+++ b/Dalamud/Interface/Textures/Internal/TextureManager.cs
@@ -63,7 +63,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
this.wicManager = new(this);
}
- /// Gets the D3D11 Device used to create textures.
+ /// Gets the D3D11 Device used to create textures. Ownership is not transferred.
public unsafe ComPtr Device
{
get
@@ -80,15 +80,19 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
}
}
+ /// Gets a simpler drawer.
+ public SimpleDrawerImpl SimpleDrawer =>
+ this.simpleDrawer ?? throw new ObjectDisposedException(nameof(TextureManager));
+
/// Gets the shared texture manager.
public SharedTextureManager Shared =>
this.sharedTextureManager ??
- throw new ObjectDisposedException(nameof(this.sharedTextureManager));
+ throw new ObjectDisposedException(nameof(TextureManager));
/// Gets the WIC manager.
public WicManager Wic =>
this.wicManager ??
- throw new ObjectDisposedException(nameof(this.sharedTextureManager));
+ throw new ObjectDisposedException(nameof(TextureManager));
///
public void Dispose()
@@ -98,9 +102,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
this.disposing = true;
- this.drawsOneSquare?.Dispose();
- this.drawsOneSquare = null;
-
+ Interlocked.Exchange(ref this.simpleDrawer, null)?.Dispose();
Interlocked.Exchange(ref this.sharedTextureManager, null)?.Dispose();
Interlocked.Exchange(ref this.wicManager, null)?.Dispose();
}
@@ -201,7 +203,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
D3D11_FORMAT_SUPPORT supported;
if (this.Device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
return false;
-
+
const D3D11_FORMAT_SUPPORT required = D3D11_FORMAT_SUPPORT.D3D11_FORMAT_SUPPORT_TEXTURE2D;
return (supported & required) == required;
}
@@ -232,7 +234,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
var subrdata = new D3D11_SUBRESOURCE_DATA { pSysMem = dataPtr, SysMemPitch = (uint)specs.Pitch };
device.Get()->CreateTexture2D(&texd, &subrdata, texture.GetAddressOf()).ThrowOnError();
}
-
+
var viewDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC
{
Format = texd.Format,
@@ -264,7 +266,7 @@ internal sealed partial class TextureManager : IServiceType, IDisposable, ITextu
}
return this.NoThrottleCreateFromRaw(
- RawImageSpecification.From(buffer.Width, buffer.Height, dxgiFormat),
+ new(buffer.Width, buffer.Height, dxgiFormat),
buffer.RawData);
}
diff --git a/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs b/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs
new file mode 100644
index 000000000..8a0a17183
--- /dev/null
+++ b/Dalamud/Interface/Textures/Internal/ViewportTextureWrap.cs
@@ -0,0 +1,239 @@
+using System.Diagnostics;
+using System.Numerics;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Dalamud.Game;
+using Dalamud.Interface.Internal;
+using Dalamud.Utility;
+
+using ImGuiNET;
+
+using TerraFX.Interop.DirectX;
+using TerraFX.Interop.Windows;
+
+using NotSupportedException = System.NotSupportedException;
+
+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 uint viewportId;
+ private readonly bool beforeImGuiRender;
+ private readonly CancellationToken cancellationToken;
+ private readonly TaskCompletionSource firstUpdateTaskCompletionSource = new();
+
+ private D3D11_TEXTURE2D_DESC desc;
+ private ComPtr tex;
+ private ComPtr srv;
+ private ComPtr rtv;
+
+ private bool autoUpdate;
+ private bool disposed;
+
+ /// Initializes a new instance of the class.
+ /// The source viewport ID.
+ /// Capture before calling .
+ /// If true, automatically update the underlying texture.
+ /// The cancellation token.
+ public ViewportTextureWrap(
+ uint viewportId,
+ bool beforeImGuiRender,
+ bool autoUpdate,
+ CancellationToken cancellationToken)
+ {
+ this.viewportId = viewportId;
+ this.beforeImGuiRender = beforeImGuiRender;
+ this.autoUpdate = autoUpdate;
+ this.cancellationToken = cancellationToken;
+ }
+
+ /// Finalizes an instance of the class.
+ ~ViewportTextureWrap() => this.Dispose(false);
+
+ ///
+ public unsafe nint ImGuiHandle => (nint)this.srv.Get();
+
+ ///
+ public int Width => (int)this.desc.Width;
+
+ ///
+ public int Height => (int)this.desc.Height;
+
+ /// Gets the task representing the first call.
+ public Task FirstUpdateTask => this.firstUpdateTaskCompletionSource.Task;
+
+ /// Updates the texture from the source viewport.
+ public unsafe void Update()
+ {
+ if (this.cancellationToken.IsCancellationRequested || this.disposed)
+ {
+ this.firstUpdateTaskCompletionSource.TrySetCanceled();
+ return;
+ }
+
+ try
+ {
+ ThreadSafety.AssertMainThread();
+
+ using var backBuffer = GetImGuiViewportBackBuffer(this.viewportId);
+ D3D11_TEXTURE2D_DESC newDesc;
+ backBuffer.Get()->GetDesc(&newDesc);
+
+ if (newDesc.SampleDesc.Count > 1)
+ throw new NotSupportedException("Multisampling is not expected");
+
+ using var device = default(ComPtr);
+ backBuffer.Get()->GetDevice(device.GetAddressOf());
+
+ using var context = default(ComPtr);
+ device.Get()->GetImmediateContext(context.GetAddressOf());
+
+ if (this.desc.Width != newDesc.Width
+ || this.desc.Height != newDesc.Height
+ || this.desc.Format != newDesc.Format)
+ {
+ var texDesc = new D3D11_TEXTURE2D_DESC
+ {
+ Width = newDesc.Width,
+ Height = newDesc.Height,
+ MipLevels = 1,
+ ArraySize = 1,
+ Format = newDesc.Format,
+ SampleDesc = new(1, 0),
+ Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT,
+ BindFlags = (uint)(D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE |
+ D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET),
+ CPUAccessFlags = 0u,
+ MiscFlags = 0u,
+ };
+
+ using var texTemp = default(ComPtr);
+ device.Get()->CreateTexture2D(&texDesc, null, texTemp.GetAddressOf()).ThrowOnError();
+
+ var rtvDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
+ texTemp,
+ D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
+ using var rtvTemp = default(ComPtr);
+ device.Get()->CreateRenderTargetView(
+ (ID3D11Resource*)texTemp.Get(),
+ &rtvDesc,
+ rtvTemp.GetAddressOf()).ThrowOnError();
+
+ var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
+ texTemp,
+ D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
+ using var srvTemp = default(ComPtr);
+ device.Get()->CreateShaderResourceView(
+ (ID3D11Resource*)texTemp.Get(),
+ &srvDesc,
+ srvTemp.GetAddressOf())
+ .ThrowOnError();
+
+ this.desc = newDesc;
+ srvTemp.Swap(ref this.srv);
+ rtvTemp.Swap(ref this.rtv);
+ texTemp.Swap(ref this.tex);
+ }
+
+ context.Get()->CopyResource((ID3D11Resource*)this.tex.Get(), (ID3D11Resource*)backBuffer.Get());
+ var rtvLocal = this.rtv.Get();
+ context.Get()->OMSetRenderTargets(1u, &rtvLocal, null);
+ Service.Get().SimpleDrawer.StripAlpha(context.Get());
+
+ var dummy = default(ID3D11RenderTargetView*);
+ context.Get()->OMSetRenderTargets(1u, &dummy, null);
+
+ this.firstUpdateTaskCompletionSource.TrySetResult(this);
+ }
+ catch (Exception e)
+ {
+ this.firstUpdateTaskCompletionSource.TrySetException(e);
+ }
+
+ if (this.autoUpdate)
+ {
+ Service.Get().RunOnTick(
+ () =>
+ {
+ if (this.beforeImGuiRender)
+ Service.Get().RunBeforeImGuiRender(this.Update);
+ else
+ Service.Get().RunAfterImGuiRender(this.Update);
+ },
+ cancellationToken: this.cancellationToken);
+ }
+ }
+
+ /// Queue the texture to be disposed once the frame ends.
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// Actually dispose the wrapped texture.
+ void IDeferredDisposable.RealDispose()
+ {
+ _ = this.FirstUpdateTask.Exception;
+ this.tex.Reset();
+ this.srv.Reset();
+ }
+
+ private static unsafe ComPtr GetImGuiViewportBackBuffer(uint viewportId)
+ {
+ ThreadSafety.AssertMainThread();
+ var viewports = ImGui.GetPlatformIO().Viewports;
+ var viewportIndex = 0;
+ for (; viewportIndex < viewports.Size; viewportIndex++)
+ {
+ if (viewports[viewportIndex].ID == viewportId)
+ break;
+ }
+
+ if (viewportIndex >= viewports.Size)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(viewportId),
+ viewportId,
+ "Could not find a viewport with the given ID.");
+ }
+
+ var texture = default(ComPtr);
+
+ Debug.Assert(viewports[0].ID == ImGui.GetMainViewport().ID, "ImGui has changed");
+ if (viewportId == viewports[0].ID)
+ {
+ var device = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device.Instance();
+ fixed (Guid* piid = &IID.IID_ID3D11Texture2D)
+ {
+ ((IDXGISwapChain*)device->SwapChain->DXGISwapChain)->GetBuffer(0, piid, (void**)texture.GetAddressOf())
+ .ThrowOnError();
+ }
+ }
+ else
+ {
+ // See: ImGui_Impl_DX11.ImGuiViewportDataDx11
+ var rud = (nint*)viewports[viewportIndex].RendererUserData;
+ if (rud == null || rud[0] == nint.Zero || rud[1] == nint.Zero)
+ throw new InvalidOperationException();
+
+ using var resource = default(ComPtr);
+ ((ID3D11RenderTargetView*)rud[viewportIndex])->GetResource(resource.GetAddressOf());
+ resource.As(&texture).ThrowOnError();
+ }
+
+ return texture;
+ }
+
+ private void Dispose(bool disposing)
+ {
+ this.disposed = true;
+ this.autoUpdate = false;
+ if (disposing)
+ Service.GetNullable()?.EnqueueDeferredDispose(this);
+ else
+ ((IDeferredDisposable)this).RealDispose();
+ }
+}
diff --git a/Dalamud/Plugin/Services/ITextureProvider.cs b/Dalamud/Plugin/Services/ITextureProvider.cs
index 854579bcd..79b2a7ad3 100644
--- a/Dalamud/Plugin/Services/ITextureProvider.cs
+++ b/Dalamud/Plugin/Services/ITextureProvider.cs
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
-using System.Numerics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -34,32 +33,47 @@ public partial interface ITextureProvider
///
/// The source texture wrap. The passed value may be disposed once this function returns,
/// without having to wait for the completion of the returned .
- /// The left top coordinates relative to the size of the source texture.
- /// The right bottom coordinates relative to the size of the source texture.
- /// The desired target format. Use 0 to use the source format.
+ /// The texture modification arguments.
/// Whether to leave non-disposed when the returned
/// completes.
/// The cancellation token.
/// A containing the copied texture on success. Dispose after use.
- ///
- /// Coordinates in and should be in range between 0 and 1.
- ///
- /// Supported values for may not necessarily match
- /// .
- ///
+ /// This function may throw an exception.
Task CreateFromExistingTextureAsync(
IDalamudTextureWrap wrap,
- Vector2 uv0,
- Vector2 uv1,
- int dxgiFormat = 0,
+ ExistingTextureModificationArgs args = default,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default);
+ /// Creates a texture from the game screen, before rendering Dalamud.
+ /// If true, automatically update the underlying texture.
+ /// The cancellation token.
+ /// A containing the copied texture on success. Dispose after use.
+ /// This function may throw an exception.
+ Task CreateFromGameScreen(
+ bool autoUpdate = false,
+ CancellationToken cancellationToken = default);
+
+ /// Creates a texture from the game screen, before rendering Dalamud.
+ /// The viewport ID.
+ /// If true, automatically update the underlying texture.
+ /// The cancellation token.
+ /// A containing the copied texture on success. Dispose after use.
+ ///
+ /// Use ImGui.GetMainViewport().ID to capture the game screen with Dalamud rendered.
+ /// This function may throw an exception.
+ ///
+ Task CreateFromImGuiViewport(
+ uint viewportId,
+ bool autoUpdate = false,
+ CancellationToken cancellationToken = default);
+
/// Gets a texture from the given bytes, trying to interpret it as a .tex file or other well-known image
/// files, such as .png.
/// The bytes to load.
/// The cancellation token.
/// A containing the loaded texture on success. Dispose after use.
+ /// This function may throw an exception.
Task CreateFromImageAsync(
ReadOnlyMemory bytes,
CancellationToken cancellationToken = default);
@@ -70,8 +84,10 @@ public partial interface ITextureProvider
/// Whether to leave the stream open once the task completes, sucessfully or not.
/// The cancellation token.
/// A containing the loaded texture on success. Dispose after use.
- /// will be closed or not only according to ;
- /// is irrelevant in closing the stream.
+ ///
+ /// will be closed or not only according to ;
+ /// is irrelevant in closing the stream.
+ /// This function may throw an exception.
Task CreateFromImageAsync(
Stream stream,
bool leaveOpen = false,
@@ -81,6 +97,7 @@ public partial interface ITextureProvider
/// The specifications for the raw bitmap.
/// The bytes to load.
/// The texture loaded from the supplied raw bitmap. Dispose after use.
+ /// This function may throw an exception.
IDalamudTextureWrap CreateFromRaw(
RawImageSpecification specs,
ReadOnlySpan bytes);
@@ -90,6 +107,7 @@ public partial interface ITextureProvider
/// The bytes to load.
/// The cancellation token.
/// A containing the loaded texture on success. Dispose after use.
+ /// This function may throw an exception.
Task CreateFromRawAsync(
RawImageSpecification specs,
ReadOnlyMemory bytes,
@@ -101,8 +119,11 @@ public partial interface ITextureProvider
/// Whether to leave the stream open once the task completes, sucessfully or not.
/// The cancellation token.
/// A containing the loaded texture on success. Dispose after use.
- /// will be closed or not only according to ;
- /// is irrelevant in closing the stream.
+ ///
+ /// will be closed or not only according to ;
+ /// is irrelevant in closing the stream.
+ /// This function may throw an exception.
+ ///
Task CreateFromRawAsync(
RawImageSpecification specs,
Stream stream,
@@ -115,12 +136,14 @@ public partial interface ITextureProvider
///
/// The texture to obtain a handle to.
/// A texture wrap that can be used to render the texture. Dispose after use.
+ /// This function may throw an exception.
IDalamudTextureWrap CreateFromTexFile(TexFile file);
/// Get a texture handle for the specified Lumina .
/// The texture to obtain a handle to.
/// The cancellation token.
/// A texture wrap that can be used to render the texture. Dispose after use.
+ /// This function may throw an exception.
Task CreateFromTexFileAsync(
TexFile file,
CancellationToken cancellationToken = default);
@@ -128,13 +151,14 @@ public partial interface ITextureProvider
/// Gets the supported bitmap decoders.
/// The supported bitmap decoders.
///
- /// The following functions support the files of the container types pointed by yielded values.
+ /// The following functions support the files of the container types pointed by yielded values.
///
+ /// This function may throw an exception.
///
IEnumerable GetSupportedImageDecoderInfos();
@@ -145,32 +169,39 @@ public partial interface ITextureProvider
///
+ /// This function may throw an exception.
///
IEnumerable GetSupportedImageEncoderInfos();
/// Gets a shared texture corresponding to the given game resource icon specifier.
/// A game icon specifier.
/// The shared texture that you may use to obtain the loaded texture wrap and load states.
- /// This function is under the effect of .
+ ///
+ /// This function is under the effect of .
+ /// This function does not throw exceptions.
///
ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup);
/// Gets a shared texture corresponding to the given path to a game resource.
/// A path to a game resource.
/// The shared texture that you may use to obtain the loaded texture wrap and load states.
- /// This function is under the effect of .
+ ///
+ /// This function is under the effect of .
+ /// This function does not throw exceptions.
///
ISharedImmediateTexture GetFromGame(string path);
/// Gets a shared texture corresponding to the given file on the filesystem.
/// A path to a file on the filesystem.
/// The shared texture that you may use to obtain the loaded texture wrap and load states.
+ /// This function does not throw exceptions.
ISharedImmediateTexture GetFromFile(string path);
/// Gets a shared texture corresponding to the given file of the assembly manifest resources.
/// The assembly containing manifest resources.
/// The case-sensitive name of the manifest resource being requested.
/// The shared texture that you may use to obtain the loaded texture wrap and load states.
+ /// This function does not throw exceptions.
ISharedImmediateTexture GetFromManifestResource(Assembly assembly, string name);
/// Get a path for a specific icon's .tex file.
@@ -185,14 +216,12 @@ public partial interface ITextureProvider
/// The icon lookup.
/// The resolved path.
/// true if the corresponding file exists and has been set.
+ /// This function does not throw exceptions.
bool TryGetIconPath(in GameIconLookup lookup, [NotNullWhen(true)] out string? path);
/// Gets the raw data of a texture wrap.
/// The source texture wrap.
- /// The left top coordinates relative to the size of the source texture.
- /// The right bottom coordinates relative to the size of the source texture.
- /// The desired target format.
- /// If 0 (unknown) is passed, then the format will not be converted.
+ /// The texture modification arguments.
/// Whether to leave non-disposed when the returned
/// completes.
/// The cancellation token.
@@ -200,16 +229,11 @@ public partial interface ITextureProvider
///
/// The length of the returned RawData may not match
/// * .
- /// If is ,
- /// is , and is 0,
- /// then the source data will be returned.
- /// This function can fail.
+ /// This function may throw an exception.
///
Task<(RawImageSpecification Specification, byte[] RawData)> GetRawDataFromExistingTextureAsync(
IDalamudTextureWrap wrap,
- Vector2 uv0,
- Vector2 uv1,
- int dxgiFormat = 0,
+ ExistingTextureModificationArgs args = default,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default);
@@ -217,9 +241,7 @@ public partial interface ITextureProvider
/// The texture wrap to save.
/// The container GUID, obtained from .
/// The stream to save to.
- /// Properties to pass to the encoder. See
- /// Microsoft
- /// Learn for available parameters.
+ /// Properties to pass to the encoder. See remarks for valid values.
/// Whether to leave non-disposed when the returned
/// completes.
/// Whether to leave open when the returned
@@ -228,6 +250,15 @@ public partial interface ITextureProvider
/// A task representing the save process.
///
/// must not be disposed until the task finishes.
+ /// See the following webpages for the valid values for per
+ /// .
+ ///
+ /// This function may throw an exception.
///
Task SaveToStreamAsync(
IDalamudTextureWrap wrap,
@@ -242,15 +273,22 @@ public partial interface ITextureProvider
/// The texture wrap to save.
/// The container GUID, obtained from .
/// The target file path. The target file will be overwritten if it exist.
- /// Properties to pass to the encoder. See
- /// Microsoft
- /// Learn for available parameters.
+ /// Properties to pass to the encoder. See remarks for valid values.
/// Whether to leave non-disposed when the returned
/// completes.
/// The cancellation token.
/// A task representing the save process.
///
/// must not be disposed until the task finishes.
+ /// See the following webpages for the valid values for per
+ /// .
+ ///
+ /// This function may throw an exception.
///
Task SaveToFileAsync(
IDalamudTextureWrap wrap,
@@ -266,11 +304,13 @@ public partial interface ITextureProvider
///
/// The DXGI format.
/// true if supported.
+ /// This function does not throw exceptions.
bool IsDxgiFormatSupported(int dxgiFormat);
/// Determines whether the system supports the given DXGI format for use with
/// .
/// The DXGI format.
/// true if supported.
+ /// This function does not throw exceptions.
bool IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat);
}
diff --git a/Dalamud/Plugin/Services/RawImageSpecification.cs b/Dalamud/Plugin/Services/RawImageSpecification.cs
index 4d0aa2e9e..7c17ee9fb 100644
--- a/Dalamud/Plugin/Services/RawImageSpecification.cs
+++ b/Dalamud/Plugin/Services/RawImageSpecification.cs
@@ -1,25 +1,53 @@
-using System.Diagnostics.CodeAnalysis;
-
using TerraFX.Interop.DirectX;
namespace Dalamud.Plugin.Services;
-///
-/// Describes a raw image.
-///
-/// The width of the image.
-/// The height of the image.
-/// The pitch of the image in bytes. The value may not always exactly match
-/// * bytesPerPixelFromDxgiFormat.
-/// The format of the image. See DXGI_FORMAT.
-[SuppressMessage(
- "StyleCop.CSharp.NamingRules",
- "SA1313:Parameter names should begin with lower-case letter",
- Justification = "no")]
-public readonly record struct RawImageSpecification(int Width, int Height, int Pitch, int DxgiFormat)
+/// Describes a raw image.
+public record struct RawImageSpecification
{
private const string FormatNotSupportedMessage = $"{nameof(DxgiFormat)} is not supported.";
+ /// Initializes a new instance of the class.
+ /// The width of the raw image.
+ /// The height of the raw image.
+ /// The DXGI format of the raw image.
+ /// The pitch of the raw image in bytes.
+ /// Specify -1 to calculate it from other parameters.
+ public RawImageSpecification(int width, int height, int dxgiFormat, int pitch = -1)
+ {
+ if (pitch < 0)
+ {
+ if (!GetFormatInfo((DXGI_FORMAT)dxgiFormat, out var bitsPerPixel, out var isBlockCompression))
+ throw new NotSupportedException(FormatNotSupportedMessage);
+
+ pitch = isBlockCompression
+ ? Math.Max(1, (width + 3) / 4) * 2 * bitsPerPixel
+ : ((width * bitsPerPixel) + 7) / 8;
+ }
+
+ this.Width = width;
+ this.Height = height;
+ this.Pitch = pitch;
+ this.DxgiFormat = dxgiFormat;
+ }
+
+ /// Gets or sets the width of the raw image.
+ public int Width { get; set; }
+
+ /// Gets or sets the height of the raw image.
+ public int Height { get; set; }
+
+ /// Gets or sets the pitch of the raw image in bytes.
+ /// The value may not always exactly match
+ /// * bytesPerPixelFromDxgiFormat.
+ ///
+ public int Pitch { get; set; }
+
+ /// Gets or sets the format of the raw image.
+ /// See
+ /// DXGI_FORMAT.
+ public int DxgiFormat { get; set; }
+
/// Gets the number of bits per pixel.
/// Thrown if is not supported.
public int BitsPerPixel =>
@@ -27,27 +55,6 @@ public readonly record struct RawImageSpecification(int Width, int Height, int P
? bitsPerPixel
: throw new NotSupportedException(FormatNotSupportedMessage);
- ///
- /// Creates a new instance of record using the given resolution and pixel
- /// format. Pitch will be automatically calculated.
- ///
- /// The width.
- /// The height.
- /// The format.
- /// The new instance.
- /// Thrown if is not supported.
- public static RawImageSpecification From(int width, int height, int format)
- {
- if (!GetFormatInfo((DXGI_FORMAT)format, out var bitsPerPixel, out var isBlockCompression))
- throw new NotSupportedException(FormatNotSupportedMessage);
-
- var pitch = isBlockCompression
- ? Math.Max(1, (width + 3) / 4) * 2 * bitsPerPixel
- : ((width * bitsPerPixel) + 7) / 8;
-
- return new(width, height, pitch, format);
- }
-
///
/// Creates a new instance of record using the given resolution,
/// in B8G8R8A8(BGRA32) UNorm pixel format.
@@ -56,7 +63,7 @@ public readonly record struct RawImageSpecification(int Width, int Height, int P
/// The height.
/// The new instance.
public static RawImageSpecification Bgra32(int width, int height) =>
- new(width, height, width * 4, (int)DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM);
+ new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, width * 4);
///
/// Creates a new instance of record using the given resolution,
@@ -66,7 +73,7 @@ public readonly record struct RawImageSpecification(int Width, int Height, int P
/// The height.
/// The new instance.
public static RawImageSpecification Rgba32(int width, int height) =>
- new(width, height, width * 4, (int)DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM);
+ new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM, width * 4);
///
/// Creates a new instance of record using the given resolution,
@@ -76,7 +83,7 @@ public readonly record struct RawImageSpecification(int Width, int Height, int P
/// The height.
/// The new instance.
public static RawImageSpecification A8(int width, int height) =>
- new(width, height, width, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM);
+ new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_A8_UNORM, width);
private static bool GetFormatInfo(DXGI_FORMAT format, out int bitsPerPixel, out bool isBlockCompression)
{
diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs
index a9293eb6d..e51788c0b 100644
--- a/Dalamud/Storage/Assets/DalamudAssetManager.cs
+++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs
@@ -83,6 +83,9 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
///
public IDalamudTextureWrap Empty4X4 => this.GetDalamudTextureWrap(DalamudAsset.Empty4X4);
+ ///
+ public IDalamudTextureWrap White4X4 => this.GetDalamudTextureWrap(DalamudAsset.White4X4);
+
///
public void Dispose()
{
diff --git a/Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs b/Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs
index 99253411b..4fba51a2f 100644
--- a/Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs
+++ b/Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs
@@ -2,25 +2,21 @@
using SharpDX.DXGI;
+using TerraFX.Interop.DirectX;
+
namespace Dalamud.Storage.Assets;
-///
-/// Provide raw texture data directly.
-///
+/// Provide raw texture data directly.
[AttributeUsage(AttributeTargets.Field)]
internal class DalamudAssetRawTextureAttribute : Attribute
{
- ///
- /// Initializes a new instance of the class.
- ///
+ /// Initializes a new instance of the class.
/// The width.
- /// The pitch.
/// The height.
/// The format.
- public DalamudAssetRawTextureAttribute(int width, int pitch, int height, Format format)
- {
- this.Specification = new(width, height, pitch, (int)format);
- }
+ /// The pitch.
+ public DalamudAssetRawTextureAttribute(int width, int height, DXGI_FORMAT format, int pitch) =>
+ this.Specification = new(width, height, (int)format, pitch);
///
/// Gets the specification.
diff --git a/Dalamud/Storage/Assets/IDalamudAssetManager.cs b/Dalamud/Storage/Assets/IDalamudAssetManager.cs
index 643eef18c..f402c9f93 100644
--- a/Dalamud/Storage/Assets/IDalamudAssetManager.cs
+++ b/Dalamud/Storage/Assets/IDalamudAssetManager.cs
@@ -23,6 +23,11 @@ public interface IDalamudAssetManager
///
IDalamudTextureWrap Empty4X4 { get; }
+ ///
+ /// Gets the shared texture wrap for .
+ ///
+ IDalamudTextureWrap White4X4 { get; }
+
///
/// Gets whether the stream for the asset is instantly available.
///
diff --git a/Dalamud/Utility/TerraFxCom/IPropertyBag2Extensions.cs b/Dalamud/Utility/TerraFxCom/IPropertyBag2Extensions.cs
new file mode 100644
index 000000000..cfb11ae41
--- /dev/null
+++ b/Dalamud/Utility/TerraFxCom/IPropertyBag2Extensions.cs
@@ -0,0 +1,83 @@
+using System.Runtime.InteropServices;
+
+using TerraFX.Interop.Windows;
+
+using static TerraFX.Interop.Windows.Windows;
+
+namespace Dalamud.Utility.TerraFxCom;
+
+/// Utilities for and its derivatives.
+internal static unsafe partial class TerraFxComInterfaceExtensions
+{
+ /// Calls .
+ /// The property bag.
+ /// The name of the item to be interpreted as a VARIANT.
+ /// The new value, to be interpreted as a .
+ /// Return value from .
+ public static HRESULT Write(ref this IPropertyBag2 obj, string name, object? value)
+ {
+ VARIANT varValue;
+ // Marshal calls VariantInit.
+ Marshal.GetNativeVariantForObject(value, (nint)(&varValue));
+ try
+ {
+ fixed (char* pName = name)
+ {
+ var option = new PROPBAG2 { pstrName = (ushort*)pName };
+ return obj.Write(1, &option, &varValue);
+ }
+ }
+ finally
+ {
+ VariantClear(&varValue);
+ }
+ }
+
+ /// Calls .
+ /// The object.
+ /// The name of the metadata.
+ /// The new value, to be interpreted as a .
+ /// Return value from .
+ public static HRESULT SetMetadataByName(ref this IWICMetadataQueryWriter obj, string name, object? value)
+ {
+ VARIANT varValue;
+ // Marshal calls VariantInit.
+ Marshal.GetNativeVariantForObject(value, (nint)(&varValue));
+ try
+ {
+ PROPVARIANT propVarValue;
+ var propVarRes = VariantToPropVariant(&varValue, &propVarValue);
+ if (propVarRes < 0)
+ return propVarRes;
+
+ try
+ {
+ fixed (char* pName = name)
+ return obj.SetMetadataByName((ushort*)pName, &propVarValue);
+ }
+ finally
+ {
+ _ = PropVariantClear(&propVarValue);
+ }
+ }
+ finally
+ {
+ _ = VariantClear(&varValue);
+ }
+ }
+
+ /// Calls .
+ /// The object.
+ /// The name of the metadata.
+ /// Return value from .
+ public static HRESULT RemoveMetadataByName(ref this IWICMetadataQueryWriter obj, string name)
+ {
+ fixed (char* pName = name)
+ return obj.RemoveMetadataByName((ushort*)pName);
+ }
+
+ [LibraryImport("propsys.dll")]
+ private static partial int VariantToPropVariant(
+ void* pVarIn,
+ void* pPropVarOut);
+}