Add ITextureProvider.CreateFromExistingTextureAsync

This commit is contained in:
Soreepeong 2024-03-02 01:41:11 +09:00
parent c86be31255
commit 1ae11440aa
8 changed files with 612 additions and 22 deletions

View file

@ -70,6 +70,8 @@ internal class InterfaceManager : IDisposable, IServiceType
[ServiceManager.ServiceDependency]
private readonly DalamudIme dalamudIme = Service<DalamudIme>.Get();
private readonly ConcurrentQueue<Action> runBeforePresent = new();
private readonly SwapChainVtableResolver address = new();
private readonly Hook<SetCursorDelegate> setCursorHook;
private RawDX11Scene? scene;
@ -283,6 +285,10 @@ internal class InterfaceManager : IDisposable, IServiceType
this.deferredDisposeDisposables.Add(locked);
}
/// <summary>Queues an action to be run before Present call.</summary>
/// <param name="action">The action.</param>
public void RunBeforePresent(Action action) => this.runBeforePresent.Enqueue(action);
/// <summary>
/// Get video memory information.
/// </summary>
@ -520,6 +526,9 @@ internal class InterfaceManager : IDisposable, IServiceType
if (!this.dalamudAtlas!.HasBuiltAtlas)
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
while (this.runBeforePresent.TryDequeue(out var action))
action.InvokeSafely();
if (this.address.IsReshade)
{
var pRes = this.presentHook!.Original(swapChain, syncInterval, presentFlags);

View file

@ -0,0 +1,484 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using ImGuiNET;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
internal sealed partial class TextureManager
{
private DrawsOneSquare? drawsOneSquare;
/// <inheritdoc/>
bool ITextureProvider.IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat) =>
this.IsDxgiFormatSupportedForCreateFromExistingTextureAsync((DXGI_FORMAT)dxgiFormat);
/// <inheritdoc cref="ITextureProvider.IsDxgiFormatSupportedForCreateFromExistingTextureAsync"/>
public bool IsDxgiFormatSupportedForCreateFromExistingTextureAsync(DXGI_FORMAT dxgiFormat)
{
if (this.interfaceManager.Scene is not { } scene)
{
_ = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
}
var format = (Format)dxgiFormat;
var support = scene.Device.CheckFormatSupport(format);
return (support & FormatSupport.RenderTarget) != 0
&& (support & FormatSupport.Texture2D) != 0;
}
/// <inheritdoc/>
Task<IDalamudTextureWrap> ITextureProvider.CreateFromExistingTextureAsync(
IDalamudTextureWrap wrap,
Vector2 uv0,
Vector2 uv1,
int dxgiFormat,
CancellationToken cancellationToken) =>
this.CreateFromExistingTextureAsync(wrap, uv0, uv1, (DXGI_FORMAT)dxgiFormat, cancellationToken);
/// <inheritdoc cref="ITextureProvider.CreateFromExistingTextureAsync"/>
public Task<IDalamudTextureWrap> CreateFromExistingTextureAsync(
IDalamudTextureWrap wrap,
Vector2 uv0,
Vector2 uv1,
DXGI_FORMAT format,
CancellationToken cancellationToken = default)
{
var wrapCopy = wrap.CreateWrapSharingLowLevelResource();
return this.textureLoadThrottler.LoadTextureAsync(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
ct =>
{
var tcs = new TaskCompletionSource<IDalamudTextureWrap>();
this.interfaceManager.RunBeforePresent(
() =>
{
try
{
ct.ThrowIfCancellationRequested();
unsafe
{
using var tex = new ComPtr<ID3D11Texture2D>(
this.NoThrottleCreateFromExistingTextureCore(
wrapCopy,
uv0,
uv1,
format,
false));
using var device = default(ComPtr<ID3D11Device>);
tex.Get()->GetDevice(device.GetAddressOf());
using var srv = default(ComPtr<ID3D11ShaderResourceView>);
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
tex,
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
device.Get()->CreateShaderResourceView(
(ID3D11Resource*)tex.Get(),
&srvDesc,
srv.GetAddressOf())
.ThrowOnError();
var desc = default(D3D11_TEXTURE2D_DESC);
tex.Get()->GetDesc(&desc);
tcs.SetResult(
new UnknownTextureWrap(
(IUnknown*)srv.Get(),
(int)desc.Width,
(int)desc.Height,
true));
}
}
catch (Exception e)
{
tcs.SetException(e);
}
});
return tcs.Task;
},
cancellationToken)
.ContinueWith(
r =>
{
wrapCopy.Dispose();
return r;
},
default(CancellationToken))
.Unwrap();
}
private unsafe ID3D11Texture2D* NoThrottleCreateFromExistingTextureCore(
IDalamudTextureWrap wrap,
Vector2 uv0,
Vector2 uv1,
DXGI_FORMAT format,
bool enableCpuRead)
{
ThreadSafety.AssertMainThread();
using var resUnk = new ComPtr<IUnknown>((IUnknown*)wrap.ImGuiHandle);
using var texSrv = default(ComPtr<ID3D11ShaderResourceView>);
resUnk.As(&texSrv).ThrowOnError();
using var device = default(ComPtr<ID3D11Device>);
texSrv.Get()->GetDevice(device.GetAddressOf());
using var deviceContext = default(ComPtr<ID3D11DeviceContext>);
device.Get()->GetImmediateContext(deviceContext.GetAddressOf());
using var tex2D = default(ComPtr<ID3D11Texture2D>);
using (var texRes = default(ComPtr<ID3D11Resource>))
{
texSrv.Get()->GetResource(texRes.GetAddressOf());
texRes.As(&tex2D).ThrowOnError();
}
var texDesc = default(D3D11_TEXTURE2D_DESC);
tex2D.Get()->GetDesc(&texDesc);
using var tex2DCopyTemp = default(ComPtr<ID3D11Texture2D>);
var tex2DCopyTempDesc = new D3D11_TEXTURE2D_DESC
{
Width = checked((uint)MathF.Round((uv1.X - uv0.X) * wrap.Width)),
Height = checked((uint)MathF.Round((uv1.Y - uv0.Y) * wrap.Height)),
MipLevels = 1,
ArraySize = 1,
Format = 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,
};
device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
using (var rtvCopyTemp = default(ComPtr<ID3D11RenderTargetView>))
{
var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
tex2DCopyTemp,
D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
device.Get()->CreateRenderTargetView(
(ID3D11Resource*)tex2DCopyTemp.Get(),
&rtvCopyTempDesc,
rtvCopyTemp.GetAddressOf()).ThrowOnError();
this.drawsOneSquare ??= new();
this.drawsOneSquare.Setup(device.Get());
deviceContext.Get()->OMSetRenderTargets(1u, rtvCopyTemp.GetAddressOf(), null);
this.drawsOneSquare.Draw(
deviceContext.Get(),
texSrv.Get(),
(int)tex2DCopyTempDesc.Width,
(int)tex2DCopyTempDesc.Height,
uv0,
uv1);
deviceContext.Get()->OMSetRenderTargets(0, null, null);
}
if (!enableCpuRead)
{
tex2DCopyTemp.Get()->AddRef();
return tex2DCopyTemp.Get();
}
using var tex2DTarget = default(ComPtr<ID3D11Texture2D>);
var tex2DTargetDesc = tex2DCopyTempDesc with
{
Usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC,
BindFlags = 0u,
CPUAccessFlags = (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ,
};
device.Get()->CreateTexture2D(&tex2DTargetDesc, null, tex2DTarget.GetAddressOf()).ThrowOnError();
deviceContext.Get()->CopyResource((ID3D11Resource*)tex2DTarget.Get(), (ID3D11Resource*)tex2DCopyTemp.Get());
tex2DTarget.Get()->AddRef();
return tex2DTarget.Get();
}
[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<ID3D11SamplerState> sampler;
private ComPtr<ID3D11VertexShader> vertexShader;
private ComPtr<ID3D11PixelShader> pixelShader;
private ComPtr<ID3D11InputLayout> inputLayout;
private ComPtr<ID3D11Buffer> vertexConstantBuffer;
private ComPtr<ID3D11BlendState> blendState;
private ComPtr<ID3D11RasterizerState> rasterizerState;
private ComPtr<ID3D11Buffer> vertexBufferFill;
private ComPtr<ID3D11Buffer> vertexBufferMutable;
private ComPtr<ID3D11Buffer> 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>(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<byte>.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<byte>.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<byte>.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<byte>.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<ImDrawVert>(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);
}
}
}

View file

@ -26,6 +26,8 @@ using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
@ -36,7 +38,7 @@ namespace Dalamud.Interface.Internal;
[ResolveVia<ITextureProvider>]
[ResolveVia<ITextureSubstitutionProvider>]
#pragma warning restore SA1015
internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvider, ITextureSubstitutionProvider
internal sealed partial class TextureManager : IServiceType, IDisposable, ITextureProvider, ITextureSubstitutionProvider
{
private const int PathLookupLruCount = 8192;
@ -109,6 +111,9 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
ReleaseSelfReferences(this.manifestResourceTextures);
this.lookupToPath.Clear();
this.drawsOneSquare?.Dispose();
this.drawsOneSquare = null;
return;
static void ReleaseSelfReferences<T>(ConcurrentDictionary<T, SharedImmediateTexture> dict)
@ -302,7 +307,11 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
cancellationToken);
/// <inheritdoc/>
public bool IsDxgiFormatSupported(int dxgiFormat)
bool ITextureProvider.IsDxgiFormatSupported(int dxgiFormat) =>
this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
/// <inheritdoc cref="ITextureProvider.IsDxgiFormatSupported"/>
public bool IsDxgiFormatSupported(DXGI_FORMAT dxgiFormat)
{
if (this.interfaceManager.Scene is not { } scene)
{
@ -310,7 +319,9 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
}
return scene.Device.CheckFormatSupport((Format)dxgiFormat).HasFlag(FormatSupport.Texture2D);
var format = (Format)dxgiFormat;
var support = scene.Device.CheckFormatSupport(format);
return (support & FormatSupport.Texture2D) != 0;
}
/// <inheritdoc/>
@ -522,7 +533,8 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
var buffer = file.TextureBuffer;
var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false);
if (conversion != TexFile.DxgiFormatConversion.NoConversion || !this.IsDxgiFormatSupported(dxgiFormat))
if (conversion != TexFile.DxgiFormatConversion.NoConversion ||
!this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat))
{
dxgiFormat = (int)Format.B8G8R8A8_UNorm;
buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8);

View file

@ -16,6 +16,8 @@ using Dalamud.Utility;
using ImGuiNET;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
/// <summary>
@ -41,6 +43,10 @@ internal class TexWidget : IDataWindowWidget
private Vector2 inputTexScale = Vector2.Zero;
private TextureManager textureManager = null!;
private string[]? supportedRenderTargetFormatNames;
private DXGI_FORMAT[]? supportedRenderTargetFormats;
private int renderTargetChoiceInt;
/// <inheritdoc/>
public string[]? CommandShortcuts { get; init; } = { "tex", "texture" };
@ -66,6 +72,8 @@ internal class TexWidget : IDataWindowWidget
this.inputManifestResourceAssemblyIndex = 0;
this.inputManifestResourceNameCandidates = null;
this.inputManifestResourceNameIndex = 0;
this.supportedRenderTargetFormats = null;
this.supportedRenderTargetFormatNames = null;
this.Ready = true;
}
@ -142,19 +150,63 @@ internal class TexWidget : IDataWindowWidget
ImGui.PopID();
}
TextureEntry? toRemove = null;
TextureEntry? toCopy = null;
ImGui.Dummy(new(ImGui.GetTextLineHeightWithSpacing()));
if (this.supportedRenderTargetFormats is null)
{
this.supportedRenderTargetFormatNames = null;
this.supportedRenderTargetFormats =
Enum.GetValues<DXGI_FORMAT>()
.Where(this.textureManager.IsDxgiFormatSupportedForCreateFromExistingTextureAsync)
.ToArray();
this.renderTargetChoiceInt = 0;
}
this.supportedRenderTargetFormatNames ??= this.supportedRenderTargetFormats.Select(Enum.GetName).ToArray();
ImGui.Combo(
"CropCopy Format",
ref this.renderTargetChoiceInt,
this.supportedRenderTargetFormatNames,
this.supportedRenderTargetFormatNames.Length);
Action? runLater = null;
foreach (var t in this.addedTextures)
{
ImGui.PushID(t.Id);
if (ImGui.CollapsingHeader($"Tex #{t.Id} {t}###header", ImGuiTreeNodeFlags.DefaultOpen))
{
if (ImGui.Button("X"))
toRemove = t;
{
runLater = () =>
{
t.Dispose();
this.addedTextures.Remove(t);
};
}
ImGui.SameLine();
if (ImGui.Button("Copy"))
toCopy = t;
if (ImGui.Button("Copy Reference"))
runLater = () => this.addedTextures.Add(t.CreateFromSharedLowLevelResource(this.textureManager));
ImGui.SameLine();
if (ImGui.Button("CropCopy"))
{
runLater = () =>
{
if (t.GetTexture(this.textureManager) is not { } source)
return;
if (this.supportedRenderTargetFormats is not { } supportedFormats)
return;
if (this.renderTargetChoiceInt < 0 || this.renderTargetChoiceInt >= supportedFormats.Length)
return;
var texTask = this.textureManager.CreateFromExistingTextureAsync(
source,
new(0.25f),
new(0.75f),
supportedFormats[this.renderTargetChoiceInt]);
this.addedTextures.Add(new() { Api10 = texTask });
};
}
try
{
@ -162,7 +214,7 @@ internal class TexWidget : IDataWindowWidget
{
var scale = new Vector2(tex.Width, tex.Height);
if (this.inputTexScale != Vector2.Zero)
scale = this.inputTexScale;
scale *= this.inputTexScale;
ImGui.Image(tex.ImGuiHandle, scale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol);
}
@ -180,16 +232,7 @@ internal class TexWidget : IDataWindowWidget
ImGui.PopID();
}
if (toRemove != null)
{
toRemove.Dispose();
this.addedTextures.Remove(toRemove);
}
if (toCopy != null)
{
this.addedTextures.Add(toCopy.CreateFromSharedLowLevelResource(this.textureManager));
}
runLater?.Invoke();
}
private unsafe void DrawLoadedTextures(ICollection<SharedImmediateTexture> textures)

View file

@ -18,6 +18,8 @@ using ImGuiNET;
using SharpDX.DXGI;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
/// <summary>
@ -580,7 +582,7 @@ internal sealed partial class FontAtlasFactory
var buf = Array.Empty<byte>();
try
{
var use4 = this.factory.TextureManager.IsDxgiFormatSupported((int)Format.B4G4R4A4_UNorm);
var use4 = this.factory.TextureManager.IsDxgiFormatSupported(DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM);
var bpp = use4 ? 2 : 4;
var width = this.NewImAtlas.TexWidth;
var height = this.NewImAtlas.TexHeight;

View file

@ -24,6 +24,8 @@ using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
/// <summary>
@ -353,7 +355,7 @@ internal sealed partial class FontAtlasFactory
var numPixels = texFile.Header.Width * texFile.Header.Height;
_ = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
var targetIsB4G4R4A4 = this.TextureManager.IsDxgiFormatSupported((int)Format.B4G4R4A4_UNorm);
var targetIsB4G4R4A4 = this.TextureManager.IsDxgiFormatSupported(DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM);
var bpp = targetIsB4G4R4A4 ? 2 : 4;
var buffer = ArrayPool<byte>.Shared.Rent(numPixels * bpp);
try

View file

@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@ -27,6 +28,28 @@ namespace Dalamud.Plugin.Services;
/// </remarks>
public partial interface ITextureProvider
{
/// <summary>Creates a texture from the given existing texture, cropping and converting pixel format as needed.
/// </summary>
/// <param name="wrap">The source texture wrap. The passed value may be disposed once this function returns,
/// without having to wait for the completion of the returned <see cref="Task{TResult}"/>.</param>
/// <param name="uv0">The left top coordinates relative to the size of the source texture.</param>
/// <param name="uv1">The right bottom coordinates relative to the size of the source texture.</param>
/// <param name="dxgiFormat">The desired target format.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TResult}"/> containing the copied texture on success. Dispose after use.</returns>
/// <remarks>
/// <para>Coordinates in <paramref name="uv0"/> and <paramref name="uv1"/> should be in range between 0 and 1.
/// </para>
/// <para>Supported values for <paramref name="dxgiFormat"/> may not necessarily match
/// <see cref="IsDxgiFormatSupported"/>.</para>
/// </remarks>
Task<IDalamudTextureWrap> CreateFromExistingTextureAsync(
IDalamudTextureWrap wrap,
Vector2 uv0,
Vector2 uv1,
int dxgiFormat,
CancellationToken cancellationToken = default);
/// <summary>Gets a texture from the given bytes, trying to interpret it as a .tex file or other well-known image
/// files, such as .png.</summary>
/// <param name="bytes">The bytes to load.</param>
@ -143,4 +166,10 @@ public partial interface ITextureProvider
/// <param name="dxgiFormat">The DXGI format.</param>
/// <returns><c>true</c> if supported.</returns>
bool IsDxgiFormatSupported(int dxgiFormat);
/// <summary>Determines whether the system supports the given DXGI format for use with
/// <see cref="CreateFromExistingTextureAsync"/>.</summary>
/// <param name="dxgiFormat">The DXGI format.</param>
/// <returns><c>true</c> if supported.</returns>
bool IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat);
}

View file

@ -692,12 +692,21 @@ public static class Util
/// Throws a corresponding exception if <see cref="HRESULT.FAILED"/> is true.
/// </summary>
/// <param name="hr">The result value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ThrowOnError(this HRESULT hr)
{
if (hr.FAILED)
Marshal.ThrowExceptionForHR(hr.Value);
}
/// <summary>Determines if the specified instance of <see cref="ComPtr{T}"/> points to null.</summary>
/// <param name="f">The pointer.</param>
/// <typeparam name="T">The COM interface type from TerraFX.</typeparam>
/// <returns><c>true</c> if not empty.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe bool IsEmpty<T>(in this ComPtr<T> f) where T : unmanaged, IUnknown.Interface =>
f.Get() is null;
/// <summary>
/// Calls <see cref="TaskCompletionSource.SetException(System.Exception)"/> if the task is incomplete.
/// </summary>