Merge pull request #1923 from Soreepeong/imguiscene-inside

Take ImGuiScene into Dalamud
This commit is contained in:
goat 2025-04-03 21:43:40 +02:00 committed by GitHub
commit 8213a29ef8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 4112 additions and 181 deletions

6
.gitmodules vendored
View file

@ -1,6 +1,3 @@
[submodule "lib/ImGuiScene"]
path = lib/ImGuiScene
url = https://github.com/goatcorp/ImGuiScene
[submodule "lib/FFXIVClientStructs"]
path = lib/FFXIVClientStructs
url = https://github.com/aers/FFXIVClientStructs
@ -10,6 +7,9 @@
[submodule "lib/TsudaKageyu-minhook"]
path = lib/TsudaKageyu-minhook
url = https://github.com/TsudaKageyu/minhook.git
[submodule "lib/ImGui.NET"]
path = lib/ImGui.NET
url = https://github.com/goatcorp/ImGui.NET.git
[submodule "lib/cimgui"]
path = lib/cimgui
url = https://github.com/goatcorp/gc-cimgui

View file

@ -117,6 +117,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
config.NoLoadThirdPartyPlugins = json.value("NoLoadThirdPartyPlugins", config.NoLoadThirdPartyPlugins);
config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
config.BootDebugDirectX = json.value("BootDebugDirectX", config.BootDebugDirectX);
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
config.BootDisableFallbackConsole = json.value("BootDisableFallbackConsole", config.BootDisableFallbackConsole);
config.BootWaitMessageBox = json.value("BootWaitMessageBox", config.BootWaitMessageBox);

View file

@ -55,6 +55,7 @@ struct DalamudStartInfo {
bool NoLoadThirdPartyPlugins;
std::string BootLogPath;
bool BootDebugDirectX = false;
bool BootShowConsole = false;
bool BootDisableFallbackConsole = false;
WaitMessageboxFlags BootWaitMessageBox = WaitMessageboxFlags::None;

View file

@ -1,6 +1,10 @@
#include "pch.h"
#include <d3d11.h>
#include <dxgi1_3.h>
#include "DalamudStartInfo.h"
#include "hooks.h"
#include "logging.h"
#include "utils.h"
#include "veh.h"
@ -90,6 +94,69 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
if (g_startInfo.BootDebugDirectX) {
logging::I("Enabling DirectX Debugging.");
const auto hD3D11 = GetModuleHandleW(L"d3d11.dll");
const auto hDXGI = GetModuleHandleW(L"dxgi.dll");
const auto pfnD3D11CreateDevice = static_cast<decltype(&D3D11CreateDevice)>(
hD3D11 ? static_cast<void*>(GetProcAddress(hD3D11, "D3D11CreateDevice")) : nullptr);
if (pfnD3D11CreateDevice) {
static hooks::direct_hook<decltype(D3D11CreateDevice)> s_hookD3D11CreateDevice(
"d3d11.dll!D3D11CreateDevice",
pfnD3D11CreateDevice);
s_hookD3D11CreateDevice.set_detour([](
IDXGIAdapter* pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
const D3D_FEATURE_LEVEL* pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
ID3D11Device** ppDevice,
D3D_FEATURE_LEVEL* pFeatureLevel,
ID3D11DeviceContext** ppImmediateContext
) -> HRESULT {
return s_hookD3D11CreateDevice.call_original(
pAdapter,
DriverType,
Software,
(Flags & ~D3D11_CREATE_DEVICE_PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY) | D3D11_CREATE_DEVICE_DEBUG,
pFeatureLevels,
FeatureLevels,
SDKVersion,
ppDevice,
pFeatureLevel,
ppImmediateContext);
});
} else {
logging::W("Could not find d3d11!D3D11CreateDevice.");
}
const auto pfnCreateDXGIFactory = static_cast<decltype(&CreateDXGIFactory)>(
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory")) : nullptr);
const auto pfnCreateDXGIFactory1 = static_cast<decltype(&CreateDXGIFactory1)>(
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory1")) : nullptr);
static const auto pfnCreateDXGIFactory2 = static_cast<decltype(&CreateDXGIFactory2)>(
hDXGI ? static_cast<void*>(GetProcAddress(hDXGI, "CreateDXGIFactory2")) : nullptr);
if (pfnCreateDXGIFactory2) {
static hooks::direct_hook<decltype(CreateDXGIFactory)> s_hookCreateDXGIFactory(
"dxgi.dll!CreateDXGIFactory",
pfnCreateDXGIFactory);
static hooks::direct_hook<decltype(CreateDXGIFactory1)> s_hookCreateDXGIFactory1(
"dxgi.dll!CreateDXGIFactory1",
pfnCreateDXGIFactory1);
s_hookCreateDXGIFactory.set_detour([](REFIID riid, _COM_Outptr_ void **ppFactory) -> HRESULT {
return pfnCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, riid, ppFactory);
});
s_hookCreateDXGIFactory1.set_detour([](REFIID riid, _COM_Outptr_ void **ppFactory) -> HRESULT {
return pfnCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, riid, ppFactory);
});
} else {
logging::W("Could not find dxgi!CreateDXGIFactory2.");
}
}
if (minHookLoaded) {
logging::I("Applying fixes...");
xivfixes::apply_all(true);

View file

@ -101,6 +101,11 @@ public record DalamudStartInfo
/// </summary>
public bool BootShowConsole { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enable D3D11 and DXGI debugging if possible.
/// </summary>
public bool BootDebugDirectX { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the fallback console should be shown, if needed.
/// </summary>

View file

@ -38,13 +38,7 @@
<ProjectReference Include="..\Dalamud\Dalamud.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj">
<ProjectReference Include="..\lib\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>

View file

@ -91,6 +91,7 @@ namespace Dalamud.Injector
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
// Remove already handled arguments
args.Remove("--debug-directx");
args.Remove("--console");
args.Remove("--msgbox1");
args.Remove("--msgbox2");
@ -464,6 +465,7 @@ namespace Dalamud.Injector
startInfo.LogName ??= string.Empty;
// Set boot defaults
startInfo.BootDebugDirectX = args.Contains("--debug-directx");
startInfo.BootShowConsole = args.Contains("--console");
startInfo.BootEnableEtw = args.Contains("--etw");
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);

View file

@ -32,12 +32,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Tes
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGuiScene", "lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj", "{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.CorePlugin", "Dalamud.CorePlugin\Dalamud.CorePlugin.csproj", "{4AFDB34A-7467-4D41-B067-53BC4101D9D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs", "lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj", "{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}"
@ -68,6 +62,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimplot", "external\cimplot
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimguizmo", "external\cimguizmo\cimguizmo.vcxproj", "{F258347D-31BE-4605-98CE-40E43BDF6F9D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGui.NET-472", "lib\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{A0A3C0AC-18D9-4C74-8CFC-14E53512846D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -98,18 +94,6 @@ Global
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|x64
@ -154,6 +138,10 @@ Global
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Debug|Any CPU.Build.0 = Debug|x64
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.ActiveCfg = Release|x64
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.Build.0 = Release|x64
{A0A3C0AC-18D9-4C74-8CFC-14E53512846D}.Debug|Any CPU.ActiveCfg = Debug|x64
{A0A3C0AC-18D9-4C74-8CFC-14E53512846D}.Debug|Any CPU.Build.0 = Debug|x64
{A0A3C0AC-18D9-4C74-8CFC-14E53512846D}.Release|Any CPU.ActiveCfg = Release|x64
{A0A3C0AC-18D9-4C74-8CFC-14E53512846D}.Release|Any CPU.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -161,9 +149,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{5B832F73-5F54-4ADC-870F-D0095EF72C9A} = {19775C83-7117-4A5F-AA00-18889F46A490}
{8874326B-E755-4D13-90B4-59AB263A3E6B} = {19775C83-7117-4A5F-AA00-18889F46A490}
{0483026E-C6CE-4B1A-AA68-46544C08140B} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944}
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
{E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
@ -175,6 +160,7 @@ Global
{8BBACF2D-7AB8-4610-A115-0E363D35C291} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{F258347D-31BE-4605-98CE-40E43BDF6F9D} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{A0A3C0AC-18D9-4C74-8CFC-14E53512846D} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}

View file

@ -70,6 +70,12 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MinSharp" Version="1.0.4" />
<PackageReference Include="PInvoke.DwmApi" Version="0.7.104" />
<PackageReference Include="PInvoke.Kernel32" Version="0.7.104" />
<PackageReference Include="PInvoke.User32" Version="0.7.104" />
<PackageReference Include="PInvoke.Win32" Version="0.7.104" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
@ -87,13 +93,19 @@
<PackageReference Include="System.Resources.Extensions" Version="8.0.0" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-frag.hlsl.bytes">
<LogicalName>imgui-frag.hlsl.bytes</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-vertex.hlsl.bytes">
<LogicalName>imgui-vertex.hlsl.bytes</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dalamud.Common\Dalamud.Common.csproj" />
<ProjectReference Include="..\lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj" />
<ProjectReference Include="..\lib\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
<ProjectReference Include="..\lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj" />
</ItemGroup>
<ItemGroup>

View file

@ -32,7 +32,7 @@ internal partial class DragDropManager : IInternalDisposableService, IDragDropMa
Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync()
.ContinueWith(t =>
{
this.windowHandlePtr = t.Result.Manager.WindowHandlePtr;
this.windowHandlePtr = t.Result.Manager.GameWindowHandle;
this.Enable();
});
}

View file

@ -0,0 +1,247 @@
using System.Diagnostics.CodeAnalysis;
using Dalamud.Interface.ImGuiBackend.Helpers;
using Dalamud.Interface.ImGuiBackend.InputHandler;
using Dalamud.Interface.ImGuiBackend.Renderers;
using Dalamud.Utility;
using ImGuiNET;
using ImGuizmoNET;
using ImPlotNET;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.ImGuiBackend;
/// <summary>
/// Backend for ImGui, using <see cref="Dx11Renderer"/> and <see cref="Win32InputHandler"/>.
/// </summary>
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed/using scopes")]
internal sealed unsafe class Dx11Win32Backend : IWin32Backend
{
private readonly Dx11Renderer imguiRenderer;
private readonly Win32InputHandler imguiInput;
private ComPtr<IDXGISwapChain> swapChainPossiblyWrapped;
private ComPtr<IDXGISwapChain> swapChain;
private ComPtr<ID3D11Device> device;
private ComPtr<ID3D11DeviceContext> deviceContext;
private int targetWidth;
private int targetHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Dx11Win32Backend"/> class.
/// </summary>
/// <param name="swapChain">The pointer to an instance of <see cref="IDXGISwapChain"/>. The reference is copied.</param>
public Dx11Win32Backend(IDXGISwapChain* swapChain)
{
try
{
this.swapChainPossiblyWrapped = new(swapChain);
this.swapChain = new(swapChain);
fixed (ComPtr<IDXGISwapChain>* ppSwapChain = &this.swapChain)
ReShadePeeler.PeelSwapChain(ppSwapChain);
fixed (Guid* guid = &IID.IID_ID3D11Device)
fixed (ID3D11Device** pp = &this.device.GetPinnableReference())
this.swapChain.Get()->GetDevice(guid, (void**)pp).ThrowOnError();
fixed (ID3D11DeviceContext** pp = &this.deviceContext.GetPinnableReference())
this.device.Get()->GetImmediateContext(pp);
using var buffer = default(ComPtr<ID3D11Resource>);
fixed (Guid* guid = &IID.IID_ID3D11Resource)
this.swapChain.Get()->GetBuffer(0, guid, (void**)buffer.GetAddressOf()).ThrowOnError();
var desc = default(DXGI_SWAP_CHAIN_DESC);
this.swapChain.Get()->GetDesc(&desc).ThrowOnError();
this.targetWidth = (int)desc.BufferDesc.Width;
this.targetHeight = (int)desc.BufferDesc.Height;
this.WindowHandle = desc.OutputWindow;
var ctx = ImGui.CreateContext();
ImGuizmo.SetImGuiContext(ctx);
ImPlot.SetImGuiContext(ctx);
ImPlot.CreateContext();
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.ViewportsEnable;
this.imguiRenderer = new(this.SwapChain, this.Device, this.DeviceContext);
this.imguiInput = new(this.WindowHandle);
}
catch
{
this.Dispose();
throw;
}
}
/// <summary>
/// Finalizes an instance of the <see cref="Dx11Win32Backend"/> class.
/// </summary>
~Dx11Win32Backend() => this.ReleaseUnmanagedResources();
/// <inheritdoc/>
public event IImGuiBackend.BuildUiDelegate? BuildUi;
/// <inheritdoc/>
public event IImGuiBackend.NewInputFrameDelegate? NewInputFrame;
/// <inheritdoc/>
public event IImGuiBackend.NewRenderFrameDelegate? NewRenderFrame;
/// <inheritdoc/>
public bool UpdateCursor
{
get => this.imguiInput.UpdateCursor;
set => this.imguiInput.UpdateCursor = value;
}
/// <inheritdoc/>
public string? IniPath
{
get => this.imguiInput.IniPath;
set => this.imguiInput.IniPath = value;
}
/// <inheritdoc/>
public IImGuiInputHandler InputHandler => this.imguiInput;
/// <inheritdoc/>
public IImGuiRenderer Renderer => this.imguiRenderer;
/// <summary>
/// Gets the pointer to an instance of <see cref="IDXGISwapChain"/>.
/// </summary>
public IDXGISwapChain* SwapChain => this.swapChain;
/// <summary>
/// Gets the pointer to an instance of <see cref="ID3D11Device"/>.
/// </summary>
public ID3D11Device* Device => this.device;
/// <summary>
/// Gets the pointer to an instance of <see cref="ID3D11Device"/>, in <see cref="nint"/>.
/// </summary>
public nint DeviceHandle => (nint)this.device.Get();
/// <summary>
/// Gets the pointer to an instance of <see cref="ID3D11DeviceContext"/>.
/// </summary>
public ID3D11DeviceContext* DeviceContext => this.deviceContext;
/// <summary>
/// Gets the window handle.
/// </summary>
public HWND WindowHandle { get; }
/// <inheritdoc/>
public void Dispose()
{
this.ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
/// <inheritdoc/>
public nint? ProcessWndProcW(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam) =>
this.imguiInput.ProcessWndProcW(hWnd, msg, wParam, lParam);
/// <inheritdoc/>
public void Render()
{
this.imguiRenderer.OnNewFrame();
this.NewRenderFrame?.Invoke();
this.imguiInput.NewFrame(this.targetWidth, this.targetHeight);
this.NewInputFrame?.Invoke();
ImGui.NewFrame();
ImGuizmo.BeginFrame();
this.BuildUi?.Invoke();
ImGui.Render();
this.imguiRenderer.RenderDrawData(ImGui.GetDrawData());
ImGui.UpdatePlatformWindows();
ImGui.RenderPlatformWindowsDefault();
}
/// <inheritdoc/>
public void OnPreResize() => this.imguiRenderer.OnPreResize();
/// <inheritdoc/>
public void OnPostResize(int newWidth, int newHeight)
{
this.imguiRenderer.OnPostResize(newWidth, newHeight);
this.targetWidth = newWidth;
this.targetHeight = newHeight;
}
/// <inheritdoc/>
public void InvalidateFonts() => this.imguiRenderer.RebuildFontTexture();
/// <inheritdoc/>
public bool IsImGuiCursor(nint cursorHandle) => this.imguiInput.IsImGuiCursor(cursorHandle);
/// <inheritdoc/>
public bool IsAttachedToPresentationTarget(nint targetHandle) =>
AreIUnknownEqual(this.swapChain.Get(), (IUnknown*)targetHandle)
|| AreIUnknownEqual(this.swapChainPossiblyWrapped.Get(), (IUnknown*)targetHandle);
/// <inheritdoc/>
public bool IsMainViewportFullScreen()
{
BOOL fullscreen;
this.swapChain.Get()->GetFullscreenState(&fullscreen, null);
return fullscreen;
}
private static bool AreIUnknownEqual<T1, T2>(T1* punk1, T2* punk2)
where T1 : unmanaged, IUnknown.Interface
where T2 : unmanaged, IUnknown.Interface
{
// https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void)
// For any given COM object (also known as a COM component), a specific query for the IUnknown interface on any
// of the object's interfaces must always return the same pointer value.
if (punk1 is null || punk2 is null)
return false;
fixed (Guid* iid = &IID.IID_IUnknown)
{
using var u1 = default(ComPtr<IUnknown>);
if (punk1->QueryInterface(iid, (void**)u1.GetAddressOf()).FAILED)
return false;
using var u2 = default(ComPtr<IUnknown>);
if (punk2->QueryInterface(iid, (void**)u2.GetAddressOf()).FAILED)
return false;
return u1.Get() == u2.Get();
}
}
private void ReleaseUnmanagedResources()
{
if (this.device.IsEmpty())
return;
this.imguiRenderer.Dispose();
this.imguiInput.Dispose();
ImGui.DestroyContext();
this.swapChain.Dispose();
this.deviceContext.Dispose();
this.device.Dispose();
this.swapChainPossiblyWrapped.Dispose();
}
}

View file

@ -0,0 +1,655 @@
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.ImGuiBackend.Helpers.D3D11;
/// <summary>
/// Captures states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
internal unsafe struct D3D11DeviceContextStateBackup : IDisposable
{
private InputAssemblerState inputAssemblerState;
private RasterizerState rasterizerState;
private OutputMergerState outputMergerState;
private VertexShaderState vertexShaderState;
private HullShaderState hullShaderState;
private DomainShaderState domainShaderState;
private GeometryShaderState geometryShaderState;
private PixelShaderState pixelShaderState;
private ComputeShaderState computeShaderState;
/// <summary>
/// Initializes a new instance of the <see cref="D3D11DeviceContextStateBackup"/> struct,
/// by capturing all states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
/// <param name="featureLevel">The feature level.</param>
/// <param name="ctx">The device context.</param>
public D3D11DeviceContextStateBackup(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
{
this.inputAssemblerState = InputAssemblerState.From(ctx);
this.rasterizerState = RasterizerState.From(ctx);
this.outputMergerState = OutputMergerState.From(featureLevel, ctx);
this.vertexShaderState = VertexShaderState.From(ctx);
this.hullShaderState = HullShaderState.From(ctx);
this.domainShaderState = DomainShaderState.From(ctx);
this.geometryShaderState = GeometryShaderState.From(ctx);
this.pixelShaderState = PixelShaderState.From(ctx);
this.computeShaderState = ComputeShaderState.From(featureLevel, ctx);
}
/// <inheritdoc/>
public void Dispose()
{
this.inputAssemblerState.Dispose();
this.rasterizerState.Dispose();
this.outputMergerState.Dispose();
this.vertexShaderState.Dispose();
this.hullShaderState.Dispose();
this.domainShaderState.Dispose();
this.geometryShaderState.Dispose();
this.pixelShaderState.Dispose();
this.computeShaderState.Dispose();
}
/// <summary>
/// Captures Input Assembler states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct InputAssemblerState : IDisposable
{
private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT;
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11InputLayout> layout;
private ComPtr<ID3D11Buffer> indexBuffer;
private DXGI_FORMAT indexFormat;
private uint indexOffset;
private D3D_PRIMITIVE_TOPOLOGY topology;
private fixed ulong buffers[BufferCount];
private fixed uint strides[BufferCount];
private fixed uint offsets[BufferCount];
/// <summary>
/// Creates a new instance of <see cref="InputAssemblerState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static InputAssemblerState From(ID3D11DeviceContext* ctx)
{
var state = default(InputAssemblerState);
state.context.Attach(ctx);
ctx->AddRef();
ctx->IAGetInputLayout(state.layout.GetAddressOf());
ctx->IAGetPrimitiveTopology(&state.topology);
ctx->IAGetIndexBuffer(state.indexBuffer.GetAddressOf(), &state.indexFormat, &state.indexOffset);
ctx->IAGetVertexBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers, state.strides, state.offsets);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (InputAssemblerState* pThis = &this)
{
ctx->IASetInputLayout(pThis->layout);
ctx->IASetPrimitiveTopology(pThis->topology);
ctx->IASetIndexBuffer(pThis->indexBuffer, pThis->indexFormat, pThis->indexOffset);
ctx->IASetVertexBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers, pThis->strides, pThis->offsets);
pThis->context.Dispose();
pThis->layout.Dispose();
pThis->indexBuffer.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
b.Dispose();
}
}
}
/// <summary>
/// Captures Rasterizer states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct RasterizerState : IDisposable
{
private const int Count = TerraFX.Interop.DirectX.D3D11.D3D11_VIEWPORT_AND_SCISSORRECT_MAX_INDEX;
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11RasterizerState> state;
private fixed byte viewports[24 * Count];
private fixed ulong scissorRects[16 * Count];
/// <summary>
/// Creates a new instance of <see cref="RasterizerState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static RasterizerState From(ID3D11DeviceContext* ctx)
{
var state = default(RasterizerState);
state.context.Attach(ctx);
ctx->AddRef();
ctx->RSGetState(state.state.GetAddressOf());
uint n = Count;
ctx->RSGetViewports(&n, (D3D11_VIEWPORT*)state.viewports);
n = Count;
ctx->RSGetScissorRects(&n, (RECT*)state.scissorRects);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (RasterizerState* pThis = &this)
{
ctx->RSSetState(pThis->state);
ctx->RSSetViewports(Count, (D3D11_VIEWPORT*)pThis->viewports);
ctx->RSSetScissorRects(Count, (RECT*)pThis->scissorRects);
pThis->context.Dispose();
pThis->state.Dispose();
}
}
}
/// <summary>
/// Captures Output Merger states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct OutputMergerState : IDisposable
{
private const int RtvCount = TerraFX.Interop.DirectX.D3D11.D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT;
private const int UavCountMax = TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT;
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11BlendState> blendState;
private fixed float blendFactor[4];
private uint sampleMask;
private uint stencilRef;
private ComPtr<ID3D11DepthStencilState> depthStencilState;
private fixed ulong rtvs[RtvCount]; // ID3D11RenderTargetView*[RtvCount]
private ComPtr<ID3D11DepthStencilView> dsv;
private fixed ulong uavs[UavCountMax]; // ID3D11UnorderedAccessView*[UavCount]
private int uavCount;
/// <summary>
/// Creates a new instance of <see cref="OutputMergerState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="featureLevel">The feature level.</param>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static OutputMergerState From(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
{
var state = default(OutputMergerState);
state.uavCount = featureLevel >= D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1
? TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT
: TerraFX.Interop.DirectX.D3D11.D3D11_PS_CS_UAV_REGISTER_COUNT;
state.context.Attach(ctx);
ctx->AddRef();
ctx->OMGetBlendState(state.blendState.GetAddressOf(), state.blendFactor, &state.sampleMask);
ctx->OMGetDepthStencilState(state.depthStencilState.GetAddressOf(), &state.stencilRef);
ctx->OMGetRenderTargetsAndUnorderedAccessViews(
RtvCount,
(ID3D11RenderTargetView**)state.rtvs,
state.dsv.GetAddressOf(),
0,
(uint)state.uavCount,
(ID3D11UnorderedAccessView**)state.uavs);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (OutputMergerState* pThis = &this)
{
ctx->OMSetBlendState(pThis->blendState, pThis->blendFactor, pThis->sampleMask);
ctx->OMSetDepthStencilState(pThis->depthStencilState, pThis->stencilRef);
var rtvc = (uint)RtvCount;
while (rtvc > 0 && pThis->rtvs[rtvc - 1] == 0)
rtvc--;
var uavlb = rtvc;
while (uavlb < this.uavCount && pThis->uavs[uavlb] == 0)
uavlb++;
var uavc = (uint)this.uavCount;
while (uavc > uavlb && pThis->uavs[uavc - 1] == 0)
uavlb--;
uavc -= uavlb;
ctx->OMSetRenderTargetsAndUnorderedAccessViews(
rtvc,
(ID3D11RenderTargetView**)pThis->rtvs,
pThis->dsv,
uavc == 0 ? 0 : uavlb,
uavc,
uavc == 0 ? null : (ID3D11UnorderedAccessView**)pThis->uavs,
null);
this.context.Reset();
this.blendState.Reset();
this.depthStencilState.Reset();
this.dsv.Reset();
foreach (ref var b in new Span<ComPtr<ID3D11RenderTargetView>>(pThis->rtvs, RtvCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11UnorderedAccessView>>(pThis->uavs, this.uavCount))
b.Dispose();
}
}
}
/// <summary>
/// Captures Vertex Shader states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct VertexShaderState : IDisposable
{
private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
private const int ClassInstanceCount = 256; // According to msdn
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11VertexShader> shader;
private fixed ulong insts[ClassInstanceCount];
private fixed ulong buffers[BufferCount];
private fixed ulong samplers[SamplerCount];
private fixed ulong resources[ResourceCount];
private uint instCount;
/// <summary>
/// Creates a new instance of <see cref="VertexShaderState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static VertexShaderState From(ID3D11DeviceContext* ctx)
{
var state = default(VertexShaderState);
state.context.Attach(ctx);
ctx->AddRef();
state.instCount = ClassInstanceCount;
ctx->VSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
ctx->VSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
ctx->VSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
ctx->VSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (VertexShaderState* pThis = &this)
{
ctx->VSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
ctx->VSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
ctx->VSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
ctx->VSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
b.Dispose();
pThis->context.Dispose();
pThis->shader.Dispose();
}
}
}
/// <summary>
/// Captures Hull Shader states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct HullShaderState : IDisposable
{
private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
private const int ClassInstanceCount = 256; // According to msdn
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11HullShader> shader;
private fixed ulong insts[ClassInstanceCount];
private fixed ulong buffers[BufferCount];
private fixed ulong samplers[SamplerCount];
private fixed ulong resources[ResourceCount];
private uint instCount;
/// <summary>
/// Creates a new instance of <see cref="HullShaderState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static HullShaderState From(ID3D11DeviceContext* ctx)
{
var state = default(HullShaderState);
state.context.Attach(ctx);
ctx->AddRef();
state.instCount = ClassInstanceCount;
ctx->HSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
ctx->HSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
ctx->HSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
ctx->HSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (HullShaderState* pThis = &this)
{
ctx->HSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
ctx->HSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
ctx->HSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
ctx->HSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
b.Dispose();
pThis->context.Dispose();
pThis->shader.Dispose();
}
}
}
/// <summary>
/// Captures Domain Shader states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DomainShaderState : IDisposable
{
private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
private const int ClassInstanceCount = 256; // According to msdn
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11DomainShader> shader;
private fixed ulong insts[ClassInstanceCount];
private fixed ulong buffers[BufferCount];
private fixed ulong samplers[SamplerCount];
private fixed ulong resources[ResourceCount];
private uint instCount;
/// <summary>
/// Creates a new instance of <see cref="DomainShaderState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static DomainShaderState From(ID3D11DeviceContext* ctx)
{
var state = default(DomainShaderState);
state.context.Attach(ctx);
ctx->AddRef();
state.instCount = ClassInstanceCount;
ctx->DSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
ctx->DSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
ctx->DSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
ctx->DSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (DomainShaderState* pThis = &this)
{
ctx->DSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
ctx->DSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
ctx->DSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
ctx->DSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
b.Dispose();
pThis->context.Dispose();
pThis->shader.Dispose();
}
}
}
/// <summary>
/// Captures Geometry Shader states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct GeometryShaderState : IDisposable
{
private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
private const int ClassInstanceCount = 256; // According to msdn
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11GeometryShader> shader;
private fixed ulong insts[ClassInstanceCount];
private fixed ulong buffers[BufferCount];
private fixed ulong samplers[SamplerCount];
private fixed ulong resources[ResourceCount];
private uint instCount;
/// <summary>
/// Creates a new instance of <see cref="GeometryShaderState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static GeometryShaderState From(ID3D11DeviceContext* ctx)
{
var state = default(GeometryShaderState);
state.context.Attach(ctx);
ctx->AddRef();
state.instCount = ClassInstanceCount;
ctx->GSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
ctx->GSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
ctx->GSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
ctx->GSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (GeometryShaderState* pThis = &this)
{
ctx->GSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
ctx->GSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
ctx->GSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
ctx->GSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
b.Dispose();
pThis->context.Dispose();
pThis->shader.Dispose();
}
}
}
/// <summary>
/// Captures Pixel Shader states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct PixelShaderState : IDisposable
{
private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
private const int ClassInstanceCount = 256; // According to msdn
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11PixelShader> shader;
private fixed ulong insts[ClassInstanceCount];
private fixed ulong buffers[BufferCount];
private fixed ulong samplers[SamplerCount];
private fixed ulong resources[ResourceCount];
private uint instCount;
/// <summary>
/// Creates a new instance of <see cref="PixelShaderState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static PixelShaderState From(ID3D11DeviceContext* ctx)
{
var state = default(PixelShaderState);
state.context.Attach(ctx);
ctx->AddRef();
state.instCount = ClassInstanceCount;
ctx->PSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
ctx->PSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
ctx->PSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
ctx->PSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (PixelShaderState* pThis = &this)
{
ctx->PSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
ctx->PSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
ctx->PSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
ctx->PSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
b.Dispose();
pThis->context.Dispose();
pThis->shader.Dispose();
}
}
}
/// <summary>
/// Captures Compute Shader states of a <see cref="ID3D11DeviceContext"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ComputeShaderState : IDisposable
{
private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
private const int InstanceCount = 256; // According to msdn
private const int UavCountMax = TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT;
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11ComputeShader> shader;
private fixed ulong insts[InstanceCount]; // ID3D11ClassInstance*[BufferCount]
private fixed ulong buffers[BufferCount]; // ID3D11Buffer*[BufferCount]
private fixed ulong samplers[SamplerCount]; // ID3D11SamplerState*[SamplerCount]
private fixed ulong resources[ResourceCount]; // ID3D11ShaderResourceView*[ResourceCount]
private fixed ulong uavs[UavCountMax]; // ID3D11UnorderedAccessView*[UavCountMax]
private uint instCount;
private int uavCount;
/// <summary>
/// Creates a new instance of <see cref="ComputeShaderState"/> from <paramref name="ctx"/>.
/// </summary>
/// <param name="featureLevel">The feature level.</param>
/// <param name="ctx">The device context.</param>
/// <returns>The captured state.</returns>
public static ComputeShaderState From(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
{
var state = default(ComputeShaderState);
state.uavCount = featureLevel >= D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1
? TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT
: TerraFX.Interop.DirectX.D3D11.D3D11_PS_CS_UAV_REGISTER_COUNT;
state.context.Attach(ctx);
ctx->AddRef();
state.instCount = InstanceCount;
ctx->CSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
ctx->CSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
ctx->CSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
ctx->CSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
ctx->CSGetUnorderedAccessViews(0, (uint)state.uavCount, (ID3D11UnorderedAccessView**)state.uavs);
return state;
}
/// <inheritdoc/>
public void Dispose()
{
var ctx = this.context.Get();
if (ctx is null)
return;
fixed (ComputeShaderState* pThis = &this)
{
ctx->CSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
ctx->CSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
ctx->CSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
ctx->CSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
ctx->CSSetUnorderedAccessViews(0, (uint)this.uavCount, (ID3D11UnorderedAccessView**)pThis->uavs, null);
foreach (ref var b in new Span<ComPtr<ID3D11Buffer>>(pThis->buffers, BufferCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11SamplerState>>(pThis->samplers, SamplerCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ShaderResourceView>>(pThis->resources, ResourceCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11ClassInstance>>(pThis->insts, (int)pThis->instCount))
b.Dispose();
foreach (ref var b in new Span<ComPtr<ID3D11UnorderedAccessView>>(pThis->uavs, this.uavCount))
b.Dispose();
pThis->context.Dispose();
pThis->shader.Dispose();
}
}
}
}

View file

@ -0,0 +1,26 @@
using System.Text;
using Dalamud.Utility;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.ImGuiBackend.Helpers.D3D11;
/// <summary>Utility extension methods for D3D11 objects.</summary>
internal static class Extensions
{
/// <summary>Sets the name for debugging.</summary>
/// <param name="child">D3D11 object.</param>
/// <param name="name">Debug name.</param>
/// <typeparam name="T">Object type.</typeparam>
public static unsafe void SetDebugName<T>(ref this T child, string name)
where T : unmanaged, ID3D11DeviceChild.Interface
{
var len = Encoding.UTF8.GetByteCount(name);
var buf = stackalloc byte[len + 1];
Encoding.UTF8.GetBytes(name, new(buf, len + 1));
buf[len] = 0;
fixed (Guid* pId = &DirectX.WKPDID_D3DDebugObjectName)
child.SetPrivateData(pId, (uint)(len + 1), buf).ThrowOnError();
}
}

View file

@ -0,0 +1,165 @@
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using ImGuiNET;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
namespace Dalamud.Interface.ImGuiBackend.Helpers;
/// <summary>
/// Helpers for using ImGui Viewports.
/// </summary>
internal static class ImGuiViewportHelpers
{
/// <summary>
/// Delegate to be called when a window should be created.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
public delegate void CreateWindowDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when a window should be destroyed.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
public delegate void DestroyWindowDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when a window should be resized.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <param name="size">Size of the new window.</param>
public delegate void SetWindowSizeDelegate(ImGuiViewportPtr viewport, Vector2 size);
/// <summary>
/// Delegate to be called when a window should be rendered.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <param name="v">Custom user-provided argument from <see cref="ImGui.RenderPlatformWindowsDefault()"/>.</param>
public delegate void RenderWindowDelegate(ImGuiViewportPtr viewport, nint v);
/// <summary>
/// Delegate to be called when buffers for the window should be swapped.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <param name="v">Custom user-provided argument from <see cref="ImGui.RenderPlatformWindowsDefault()"/>.</param>
public delegate void SwapBuffersDelegate(ImGuiViewportPtr viewport, nint v);
/// <summary>
/// Delegate to be called when the window should be showed.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
public delegate void ShowWindowDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when the window should be updated.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
public delegate void UpdateWindowDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when the window position is queried.
/// </summary>
/// <param name="returnStorage">The return value storage.</param>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <returns>Same value with <paramref name="returnStorage"/>.</returns>
public unsafe delegate Vector2* GetWindowPosDelegate(Vector2* returnStorage, ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when the window should be moved.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <param name="pos">The new position.</param>
public delegate void SetWindowPosDelegate(ImGuiViewportPtr viewport, Vector2 pos);
/// <summary>
/// Delegate to be called when the window size is queried.
/// </summary>
/// <param name="returnStorage">The return value storage.</param>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <returns>Same value with <paramref name="returnStorage"/>.</returns>
public unsafe delegate Vector2* GetWindowSizeDelegate(Vector2* returnStorage, ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when the window should be given focus.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
public delegate void SetWindowFocusDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when the window is focused.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <returns>Whether the window is focused.</returns>
public delegate bool GetWindowFocusDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when whether the window is minimized is queried.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <returns>Whether the window is minimized.</returns>
public delegate bool GetWindowMinimizedDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when the window title should be changed.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <param name="title">The new title.</param>
public delegate void SetWindowTitleDelegate(ImGuiViewportPtr viewport, string title);
/// <summary>
/// Delegate to be called when the window alpha should be changed.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <param name="alpha">The new alpha.</param>
public delegate void SetWindowAlphaDelegate(ImGuiViewportPtr viewport, float alpha);
/// <summary>
/// Delegate to be called when the IME input position should be changed.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <param name="pos">The new position.</param>
public delegate void SetImeInputPosDelegate(ImGuiViewportPtr viewport, Vector2 pos);
/// <summary>
/// Delegate to be called when the window's DPI scale value is queried.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
/// <returns>The DPI scale.</returns>
public delegate float GetWindowDpiScaleDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Delegate to be called when viewport is changed.
/// </summary>
/// <param name="viewport">An instance of <see cref="ImGuiViewportPtr"/>.</param>
public delegate void ChangedViewportDelegate(ImGuiViewportPtr viewport);
/// <summary>
/// Disables ImGui from disabling alpha for Viewport window backgrounds.
/// </summary>
public static unsafe void EnableViewportWindowBackgroundAlpha()
{
// TODO: patch imgui.cpp:6126, which disables background transparency for extra viewport windows
var offset = 0x00007FFB6ADA632C - 0x00007FFB6AD60000;
offset += Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(x => x.ModuleName == "cimgui.dll")
.BaseAddress;
var b = (byte*)offset;
uint old;
if (!VirtualProtect(b, 1, PAGE.PAGE_EXECUTE_READWRITE, &old))
{
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())
?? throw new InvalidOperationException($"{nameof(VirtualProtect)} failed.");
}
*b = 0xEB;
if (!VirtualProtect(b, 1, old, &old))
{
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())
?? throw new InvalidOperationException($"{nameof(VirtualProtect)} failed.");
}
}
}

View file

@ -0,0 +1,230 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
namespace Dalamud.Interface.ImGuiBackend.Helpers;
/// <summary>
/// Peels ReShade off stuff.
/// </summary>
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed blocks")]
internal static unsafe class ReShadePeeler
{
/// <summary>
/// Peels <see cref="IDXGISwapChain"/> if it is wrapped by ReShade.
/// </summary>
/// <param name="comptr">[inout] The COM pointer to an instance of <see cref="IDXGISwapChain"/>.</param>
/// <typeparam name="T">A COM type that is or extends <see cref="IDXGISwapChain"/>.</typeparam>
/// <returns><c>true</c> if peeled.</returns>
public static bool PeelSwapChain<T>(ComPtr<T>* comptr)
where T : unmanaged, IDXGISwapChain.Interface =>
PeelIUnknown(comptr, sizeof(IDXGISwapChain.Vtbl<IDXGISwapChain>));
private static bool PeelIUnknown<T>(ComPtr<T>* comptr, nint vtblSize)
where T : unmanaged, IUnknown.Interface
{
var changed = false;
while (comptr->Get() != null && IsReShadedComObject(comptr->Get()))
{
// Expectation: the pointer to the underlying object should come early after the overriden vtable.
for (nint i = 8; i <= 0x20; i += 8)
{
var ppObjectBehind = (nint)comptr->Get() + i;
// Is the thing directly pointed from the address an actual something in the memory?
if (!IsValidReadableMemoryAddress(ppObjectBehind, 8))
continue;
var pObjectBehind = *(nint*)ppObjectBehind;
// Is the address of vtable readable?
if (!IsValidReadableMemoryAddress(pObjectBehind, sizeof(nint)))
continue;
var pObjectBehindVtbl = *(nint*)pObjectBehind;
// Is the vtable itself readable?
if (!IsValidReadableMemoryAddress(pObjectBehindVtbl, vtblSize))
continue;
// Are individual functions in vtable executable?
var valid = true;
for (var j = 0; valid && j < vtblSize; j += sizeof(nint))
valid &= IsValidExecutableMemoryAddress(*(nint*)(pObjectBehindVtbl + j), 1);
if (!valid)
continue;
// Interpret the object as an IUnknown.
// Note that `using` is not used, and `Attach` is used. We do not alter the reference count yet.
var punk = default(ComPtr<IUnknown>);
punk.Attach((IUnknown*)pObjectBehind);
// Is the IUnknown object also the type we want?
using var comptr2 = default(ComPtr<T>);
if (punk.As(&comptr2).FAILED)
continue;
comptr2.Swap(comptr);
changed = true;
break;
}
if (!changed)
break;
}
return changed;
}
private static bool BelongsInReShadeDll(nint ptr)
{
foreach (ProcessModule processModule in Process.GetCurrentProcess().Modules)
{
if (ptr < processModule.BaseAddress)
continue;
var dosh = (IMAGE_DOS_HEADER*)processModule.BaseAddress;
var nth = (IMAGE_NT_HEADERS64*)(processModule.BaseAddress + dosh->e_lfanew);
if (ptr >= processModule.BaseAddress + nth->OptionalHeader.SizeOfImage)
continue;
fixed (byte* pfn0 = "CreateDXGIFactory"u8)
fixed (byte* pfn1 = "D2D1CreateDevice"u8)
fixed (byte* pfn2 = "D3D10CreateDevice"u8)
fixed (byte* pfn3 = "D3D11CreateDevice"u8)
fixed (byte* pfn4 = "D3D12CreateDevice"u8)
fixed (byte* pfn5 = "glBegin"u8)
fixed (byte* pfn6 = "vkCreateDevice"u8)
{
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == 0)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == 0)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == 0)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == 0)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == 0)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == 0)
continue;
if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == 0)
continue;
}
var fileInfo = FileVersionInfo.GetVersionInfo(processModule.FileName);
if (fileInfo.FileDescription == null)
continue;
if (!fileInfo.FileDescription.Contains("GShade") && !fileInfo.FileDescription.Contains("ReShade"))
continue;
return true;
}
return false;
}
private static bool IsReShadedComObject<T>(T* obj)
where T : unmanaged, IUnknown.Interface
{
if (!IsValidReadableMemoryAddress((nint)obj, sizeof(nint)))
return false;
try
{
var vtbl = (nint**)Marshal.ReadIntPtr((nint)obj);
if (!IsValidReadableMemoryAddress((nint)vtbl, sizeof(nint) * 3))
return false;
for (var i = 0; i < 3; i++)
{
var pfn = Marshal.ReadIntPtr((nint)(vtbl + i));
if (!IsValidExecutableMemoryAddress(pfn, 1))
return false;
if (!BelongsInReShadeDll(pfn))
return false;
}
return true;
}
catch
{
return false;
}
}
private static bool IsValidReadableMemoryAddress(nint p, nint size)
{
while (size > 0)
{
if (!IsValidUserspaceMemoryAddress(p))
return false;
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((void*)p, &mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION)) == 0)
return false;
if (mbi is not
{
State: MEM.MEM_COMMIT,
Protect: PAGE.PAGE_READONLY or PAGE.PAGE_READWRITE or PAGE.PAGE_EXECUTE_READ
or PAGE.PAGE_EXECUTE_READWRITE,
})
return false;
var regionSize = (nint)((mbi.RegionSize + 0xFFFUL) & ~0x1000UL);
var checkedSize = ((nint)mbi.BaseAddress + regionSize) - p;
size -= checkedSize;
p += checkedSize;
}
return true;
}
private static bool IsValidExecutableMemoryAddress(nint p, nint size)
{
while (size > 0)
{
if (!IsValidUserspaceMemoryAddress(p))
return false;
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((void*)p, &mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION)) == 0)
return false;
if (mbi is not
{
State: MEM.MEM_COMMIT,
Protect: PAGE.PAGE_EXECUTE or PAGE.PAGE_EXECUTE_READ or PAGE.PAGE_EXECUTE_READWRITE
or PAGE.PAGE_EXECUTE_WRITECOPY,
})
return false;
var regionSize = (nint)((mbi.RegionSize + 0xFFFUL) & ~0x1000UL);
var checkedSize = ((nint)mbi.BaseAddress + regionSize) - p;
size -= checkedSize;
p += checkedSize;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsValidUserspaceMemoryAddress(nint p)
{
// https://learn.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/virtual-address-spaces
// A 64-bit process on 64-bit Windows has a virtual address space within the 128-terabyte range
// 0x000'00000000 through 0x7FFF'FFFFFFFF.
return p >= 0x10000 && p <= unchecked((nint)0x7FFF_FFFFFFFFUL);
}
}

View file

@ -0,0 +1,72 @@
using Dalamud.Interface.ImGuiBackend.InputHandler;
using Dalamud.Interface.ImGuiBackend.Renderers;
namespace Dalamud.Interface.ImGuiBackend;
/// <summary>Backend for ImGui.</summary>
internal interface IImGuiBackend : IDisposable
{
/// <summary>Delegate to be called when ImGui should be used to layout now.</summary>
public delegate void BuildUiDelegate();
/// <summary>Delegate to be called on new input frame.</summary>
public delegate void NewInputFrameDelegate();
/// <summary>Delegaet to be called on new render frame.</summary>
public delegate void NewRenderFrameDelegate();
/// <summary>User methods invoked every ImGui frame to construct custom UIs.</summary>
event BuildUiDelegate? BuildUi;
/// <summary>User methods invoked every ImGui frame on handling inputs.</summary>
event NewInputFrameDelegate? NewInputFrame;
/// <summary>User methods invoked every ImGui frame on handling renders.</summary>
event NewRenderFrameDelegate? NewRenderFrame;
/// <summary>Gets or sets a value indicating whether the cursor should be overridden with the ImGui cursor.
/// </summary>
bool UpdateCursor { get; set; }
/// <summary>Gets or sets the path of ImGui configuration .ini file.</summary>
string? IniPath { get; set; }
/// <summary>Gets the device handle.</summary>
nint DeviceHandle { get; }
/// <summary>Gets the input handler.</summary>
IImGuiInputHandler InputHandler { get; }
/// <summary>Gets the renderer.</summary>
IImGuiRenderer Renderer { get; }
/// <summary>Performs a render cycle.</summary>
void Render();
/// <summary>Handles stuff before resizing happens.</summary>
void OnPreResize();
/// <summary>Handles stuff after resizing happens.</summary>
/// <param name="newWidth">The new width.</param>
/// <param name="newHeight">The new height.</param>
void OnPostResize(int newWidth, int newHeight);
/// <summary>Invalidates fonts immediately.</summary>
/// <remarks>Call this while handling <see cref="NewRenderFrame"/>.</remarks>
void InvalidateFonts();
/// <summary>Determines if <paramref name="cursorHandle"/> is owned by this.</summary>
/// <param name="cursorHandle">The cursor.</param>
/// <returns>Whether it is the case.</returns>
bool IsImGuiCursor(nint cursorHandle);
/// <summary>Determines if this instance of <see cref="IImGuiBackend"/> is rendering to
/// <paramref name="targetHandle"/>. </summary>
/// <param name="targetHandle">The present target handle.</param>
/// <returns>Whether it is the case.</returns>
bool IsAttachedToPresentationTarget(nint targetHandle);
/// <summary>Determines if the main viewport is full screen. </summary>
/// <returns>Whether it is the case.</returns>
bool IsMainViewportFullScreen();
}

View file

@ -0,0 +1,15 @@
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.ImGuiBackend;
/// <summary><see cref="IImGuiBackend"/> with Win32 support.</summary>
internal interface IWin32Backend : IImGuiBackend
{
/// <summary>Processes window messages.</summary>
/// <param name="hWnd">Handle of the window.</param>
/// <param name="msg">Type of window message.</param>
/// <param name="wParam">wParam.</param>
/// <param name="lParam">lParam.</param>
/// <returns>Return value.</returns>
public nint? ProcessWndProcW(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam);
}

View file

@ -0,0 +1,22 @@
namespace Dalamud.Interface.ImGuiBackend.InputHandler;
/// <summary>A simple shared public interface that all ImGui input implementations follows.</summary>
internal interface IImGuiInputHandler : IDisposable
{
/// <summary>Gets or sets a value indicating whether the cursor should be overridden with the ImGui cursor.
/// </summary>
public bool UpdateCursor { get; set; }
/// <summary>Gets or sets the path of ImGui configuration .ini file.</summary>
string? IniPath { get; set; }
/// <summary>Determines if <paramref name="cursorHandle"/> is owned by this.</summary>
/// <param name="cursorHandle">The cursor.</param>
/// <returns>Whether it is the case.</returns>
public bool IsImGuiCursor(nint cursorHandle);
/// <summary>Marks the beginning of a new frame.</summary>
/// <param name="width">The width of the new frame.</param>
/// <param name="height">The height of the new frame.</param>
void NewFrame(int width, int height);
}

View file

@ -0,0 +1,309 @@
using System.Runtime.CompilerServices;
using ImGuiNET;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.ImGuiBackend.InputHandler;
/// <summary>
/// An implementation of <see cref="IImGuiInputHandler"/>, using Win32 APIs.
/// </summary>
internal sealed partial class Win32InputHandler
{
/// <summary>
/// Maps a <see cref="VK"/> to <see cref="ImGuiKey"/>.
/// </summary>
/// <param name="key">The virtual key.</param>
/// <returns>The corresponding <see cref="ImGuiKey"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ImGuiKey VirtualKeyToImGuiKey(int key) => key switch
{
VK.VK_TAB => ImGuiKey.Tab,
VK.VK_LEFT => ImGuiKey.LeftArrow,
VK.VK_RIGHT => ImGuiKey.RightArrow,
VK.VK_UP => ImGuiKey.UpArrow,
VK.VK_DOWN => ImGuiKey.DownArrow,
VK.VK_PRIOR => ImGuiKey.PageUp,
VK.VK_NEXT => ImGuiKey.PageDown,
VK.VK_HOME => ImGuiKey.Home,
VK.VK_END => ImGuiKey.End,
VK.VK_INSERT => ImGuiKey.Insert,
VK.VK_DELETE => ImGuiKey.Delete,
VK.VK_BACK => ImGuiKey.Backspace,
VK.VK_SPACE => ImGuiKey.Space,
VK.VK_RETURN => ImGuiKey.Enter,
VK.VK_ESCAPE => ImGuiKey.Escape,
VK.VK_OEM_7 => ImGuiKey.Apostrophe,
VK.VK_OEM_COMMA => ImGuiKey.Comma,
VK.VK_OEM_MINUS => ImGuiKey.Minus,
VK.VK_OEM_PERIOD => ImGuiKey.Period,
VK.VK_OEM_2 => ImGuiKey.Slash,
VK.VK_OEM_1 => ImGuiKey.Semicolon,
VK.VK_OEM_PLUS => ImGuiKey.Equal,
VK.VK_OEM_4 => ImGuiKey.LeftBracket,
VK.VK_OEM_5 => ImGuiKey.Backslash,
VK.VK_OEM_6 => ImGuiKey.RightBracket,
VK.VK_OEM_3 => ImGuiKey.GraveAccent,
VK.VK_CAPITAL => ImGuiKey.CapsLock,
VK.VK_SCROLL => ImGuiKey.ScrollLock,
VK.VK_NUMLOCK => ImGuiKey.NumLock,
VK.VK_SNAPSHOT => ImGuiKey.PrintScreen,
VK.VK_PAUSE => ImGuiKey.Pause,
VK.VK_NUMPAD0 => ImGuiKey.Keypad0,
VK.VK_NUMPAD1 => ImGuiKey.Keypad1,
VK.VK_NUMPAD2 => ImGuiKey.Keypad2,
VK.VK_NUMPAD3 => ImGuiKey.Keypad3,
VK.VK_NUMPAD4 => ImGuiKey.Keypad4,
VK.VK_NUMPAD5 => ImGuiKey.Keypad5,
VK.VK_NUMPAD6 => ImGuiKey.Keypad6,
VK.VK_NUMPAD7 => ImGuiKey.Keypad7,
VK.VK_NUMPAD8 => ImGuiKey.Keypad8,
VK.VK_NUMPAD9 => ImGuiKey.Keypad9,
VK.VK_DECIMAL => ImGuiKey.KeypadDecimal,
VK.VK_DIVIDE => ImGuiKey.KeypadDivide,
VK.VK_MULTIPLY => ImGuiKey.KeypadMultiply,
VK.VK_SUBTRACT => ImGuiKey.KeypadSubtract,
VK.VK_ADD => ImGuiKey.KeypadAdd,
VK.VK_RETURN + 256 => ImGuiKey.KeypadEnter,
VK.VK_LSHIFT => ImGuiKey.LeftShift,
VK.VK_LCONTROL => ImGuiKey.LeftCtrl,
VK.VK_LMENU => ImGuiKey.LeftAlt,
VK.VK_LWIN => ImGuiKey.LeftSuper,
VK.VK_RSHIFT => ImGuiKey.RightShift,
VK.VK_RCONTROL => ImGuiKey.RightCtrl,
VK.VK_RMENU => ImGuiKey.RightAlt,
VK.VK_RWIN => ImGuiKey.RightSuper,
VK.VK_APPS => ImGuiKey.Menu,
'0' => ImGuiKey._0,
'1' => ImGuiKey._1,
'2' => ImGuiKey._2,
'3' => ImGuiKey._3,
'4' => ImGuiKey._4,
'5' => ImGuiKey._5,
'6' => ImGuiKey._6,
'7' => ImGuiKey._7,
'8' => ImGuiKey._8,
'9' => ImGuiKey._9,
'A' => ImGuiKey.A,
'B' => ImGuiKey.B,
'C' => ImGuiKey.C,
'D' => ImGuiKey.D,
'E' => ImGuiKey.E,
'F' => ImGuiKey.F,
'G' => ImGuiKey.G,
'H' => ImGuiKey.H,
'I' => ImGuiKey.I,
'J' => ImGuiKey.J,
'K' => ImGuiKey.K,
'L' => ImGuiKey.L,
'M' => ImGuiKey.M,
'N' => ImGuiKey.N,
'O' => ImGuiKey.O,
'P' => ImGuiKey.P,
'Q' => ImGuiKey.Q,
'R' => ImGuiKey.R,
'S' => ImGuiKey.S,
'T' => ImGuiKey.T,
'U' => ImGuiKey.U,
'V' => ImGuiKey.V,
'W' => ImGuiKey.W,
'X' => ImGuiKey.X,
'Y' => ImGuiKey.Y,
'Z' => ImGuiKey.Z,
VK.VK_F1 => ImGuiKey.F1,
VK.VK_F2 => ImGuiKey.F2,
VK.VK_F3 => ImGuiKey.F3,
VK.VK_F4 => ImGuiKey.F4,
VK.VK_F5 => ImGuiKey.F5,
VK.VK_F6 => ImGuiKey.F6,
VK.VK_F7 => ImGuiKey.F7,
VK.VK_F8 => ImGuiKey.F8,
VK.VK_F9 => ImGuiKey.F9,
VK.VK_F10 => ImGuiKey.F10,
VK.VK_F11 => ImGuiKey.F11,
VK.VK_F12 => ImGuiKey.F12,
_ => ImGuiKey.None,
};
/// <summary>
/// Maps a <see cref="ImGuiKey"/> to <see cref="VK"/>.
/// </summary>
/// <param name="key">The ImGui key.</param>
/// <returns>The corresponding <see cref="VK"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ImGuiKeyToVirtualKey(ImGuiKey key) => key switch
{
ImGuiKey.Tab => VK.VK_TAB,
ImGuiKey.LeftArrow => VK.VK_LEFT,
ImGuiKey.RightArrow => VK.VK_RIGHT,
ImGuiKey.UpArrow => VK.VK_UP,
ImGuiKey.DownArrow => VK.VK_DOWN,
ImGuiKey.PageUp => VK.VK_PRIOR,
ImGuiKey.PageDown => VK.VK_NEXT,
ImGuiKey.Home => VK.VK_HOME,
ImGuiKey.End => VK.VK_END,
ImGuiKey.Insert => VK.VK_INSERT,
ImGuiKey.Delete => VK.VK_DELETE,
ImGuiKey.Backspace => VK.VK_BACK,
ImGuiKey.Space => VK.VK_SPACE,
ImGuiKey.Enter => VK.VK_RETURN,
ImGuiKey.Escape => VK.VK_ESCAPE,
ImGuiKey.Apostrophe => VK.VK_OEM_7,
ImGuiKey.Comma => VK.VK_OEM_COMMA,
ImGuiKey.Minus => VK.VK_OEM_MINUS,
ImGuiKey.Period => VK.VK_OEM_PERIOD,
ImGuiKey.Slash => VK.VK_OEM_2,
ImGuiKey.Semicolon => VK.VK_OEM_1,
ImGuiKey.Equal => VK.VK_OEM_PLUS,
ImGuiKey.LeftBracket => VK.VK_OEM_4,
ImGuiKey.Backslash => VK.VK_OEM_5,
ImGuiKey.RightBracket => VK.VK_OEM_6,
ImGuiKey.GraveAccent => VK.VK_OEM_3,
ImGuiKey.CapsLock => VK.VK_CAPITAL,
ImGuiKey.ScrollLock => VK.VK_SCROLL,
ImGuiKey.NumLock => VK.VK_NUMLOCK,
ImGuiKey.PrintScreen => VK.VK_SNAPSHOT,
ImGuiKey.Pause => VK.VK_PAUSE,
ImGuiKey.Keypad0 => VK.VK_NUMPAD0,
ImGuiKey.Keypad1 => VK.VK_NUMPAD1,
ImGuiKey.Keypad2 => VK.VK_NUMPAD2,
ImGuiKey.Keypad3 => VK.VK_NUMPAD3,
ImGuiKey.Keypad4 => VK.VK_NUMPAD4,
ImGuiKey.Keypad5 => VK.VK_NUMPAD5,
ImGuiKey.Keypad6 => VK.VK_NUMPAD6,
ImGuiKey.Keypad7 => VK.VK_NUMPAD7,
ImGuiKey.Keypad8 => VK.VK_NUMPAD8,
ImGuiKey.Keypad9 => VK.VK_NUMPAD9,
ImGuiKey.KeypadDecimal => VK.VK_DECIMAL,
ImGuiKey.KeypadDivide => VK.VK_DIVIDE,
ImGuiKey.KeypadMultiply => VK.VK_MULTIPLY,
ImGuiKey.KeypadSubtract => VK.VK_SUBTRACT,
ImGuiKey.KeypadAdd => VK.VK_ADD,
ImGuiKey.KeypadEnter => VK.VK_RETURN + 256,
ImGuiKey.LeftShift => VK.VK_LSHIFT,
ImGuiKey.LeftCtrl => VK.VK_LCONTROL,
ImGuiKey.LeftAlt => VK.VK_LMENU,
ImGuiKey.LeftSuper => VK.VK_LWIN,
ImGuiKey.RightShift => VK.VK_RSHIFT,
ImGuiKey.RightCtrl => VK.VK_RCONTROL,
ImGuiKey.RightAlt => VK.VK_RMENU,
ImGuiKey.RightSuper => VK.VK_RWIN,
ImGuiKey.Menu => VK.VK_APPS,
ImGuiKey._0 => '0',
ImGuiKey._1 => '1',
ImGuiKey._2 => '2',
ImGuiKey._3 => '3',
ImGuiKey._4 => '4',
ImGuiKey._5 => '5',
ImGuiKey._6 => '6',
ImGuiKey._7 => '7',
ImGuiKey._8 => '8',
ImGuiKey._9 => '9',
ImGuiKey.A => 'A',
ImGuiKey.B => 'B',
ImGuiKey.C => 'C',
ImGuiKey.D => 'D',
ImGuiKey.E => 'E',
ImGuiKey.F => 'F',
ImGuiKey.G => 'G',
ImGuiKey.H => 'H',
ImGuiKey.I => 'I',
ImGuiKey.J => 'J',
ImGuiKey.K => 'K',
ImGuiKey.L => 'L',
ImGuiKey.M => 'M',
ImGuiKey.N => 'N',
ImGuiKey.O => 'O',
ImGuiKey.P => 'P',
ImGuiKey.Q => 'Q',
ImGuiKey.R => 'R',
ImGuiKey.S => 'S',
ImGuiKey.T => 'T',
ImGuiKey.U => 'U',
ImGuiKey.V => 'V',
ImGuiKey.W => 'W',
ImGuiKey.X => 'X',
ImGuiKey.Y => 'Y',
ImGuiKey.Z => 'Z',
ImGuiKey.F1 => VK.VK_F1,
ImGuiKey.F2 => VK.VK_F2,
ImGuiKey.F3 => VK.VK_F3,
ImGuiKey.F4 => VK.VK_F4,
ImGuiKey.F5 => VK.VK_F5,
ImGuiKey.F6 => VK.VK_F6,
ImGuiKey.F7 => VK.VK_F7,
ImGuiKey.F8 => VK.VK_F8,
ImGuiKey.F9 => VK.VK_F9,
ImGuiKey.F10 => VK.VK_F10,
ImGuiKey.F11 => VK.VK_F11,
ImGuiKey.F12 => VK.VK_F12,
_ => 0,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsGamepadKey(ImGuiKey key) => (int)key is >= 617 and <= 640;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsModKey(ImGuiKey key) =>
key is ImGuiKey.LeftShift
or ImGuiKey.RightShift
or ImGuiKey.ModShift
or ImGuiKey.LeftCtrl
or ImGuiKey.ModCtrl
or ImGuiKey.LeftAlt
or ImGuiKey.RightAlt
or ImGuiKey.ModAlt;
private static void AddKeyEvent(ImGuiKey key, bool down, int nativeKeycode, int nativeScancode = -1)
{
var io = ImGui.GetIO();
io.AddKeyEvent(key, down);
io.SetKeyEventNativeData(key, nativeKeycode, nativeScancode);
}
private static void UpdateKeyModifiers()
{
var io = ImGui.GetIO();
io.AddKeyEvent(ImGuiKey.ModCtrl, IsVkDown(VK.VK_CONTROL));
io.AddKeyEvent(ImGuiKey.ModShift, IsVkDown(VK.VK_SHIFT));
io.AddKeyEvent(ImGuiKey.ModAlt, IsVkDown(VK.VK_MENU));
io.AddKeyEvent(ImGuiKey.ModSuper, IsVkDown(VK.VK_APPS));
}
private static void UpAllKeys()
{
var io = ImGui.GetIO();
for (var i = (int)ImGuiKey.NamedKey_BEGIN; i < (int)ImGuiKey.NamedKey_END; i++)
io.AddKeyEvent((ImGuiKey)i, false);
}
private static void UpAllMouseButton()
{
var io = ImGui.GetIO();
for (var i = 0; i < io.MouseDown.Count; i++)
io.MouseDown[i] = false;
}
private static bool IsVkDown(int key) => (TerraFX.Interop.Windows.Windows.GetKeyState(key) & 0x8000) != 0;
private static int GetButton(uint msg, WPARAM wParam) => msg switch
{
WM.WM_LBUTTONUP or WM.WM_LBUTTONDOWN or WM.WM_LBUTTONDBLCLK => 0,
WM.WM_RBUTTONUP or WM.WM_RBUTTONDOWN or WM.WM_RBUTTONDBLCLK => 1,
WM.WM_MBUTTONUP or WM.WM_MBUTTONDOWN or WM.WM_MBUTTONDBLCLK => 2,
WM.WM_XBUTTONUP or WM.WM_XBUTTONDOWN or WM.WM_XBUTTONDBLCLK =>
TerraFX.Interop.Windows.Windows.GET_XBUTTON_WPARAM(wParam) == TerraFX.Interop.Windows.Windows.XBUTTON1 ? 3 : 4,
_ => 0,
};
private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle)
{
style = (int)(flags.HasFlag(ImGuiViewportFlags.NoDecoration) ? WS.WS_POPUP : WS.WS_OVERLAPPEDWINDOW);
exStyle =
(int)(flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon) ? WS.WS_EX_TOOLWINDOW : (uint)WS.WS_EX_APPWINDOW);
exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP;
if (flags.HasFlag(ImGuiViewportFlags.TopMost))
exStyle |= WS.WS_EX_TOPMOST;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,374 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Interface.ImGuiBackend.Helpers;
using Dalamud.Utility;
using ImGuiNET;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
namespace Dalamud.Interface.ImGuiBackend.Renderers;
/// <summary>
/// Deals with rendering ImGui using DirectX 11.
/// See https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_dx11.cpp for the original implementation.
/// </summary>
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed/using scopes")]
internal unsafe partial class Dx11Renderer
{
private class ViewportHandler : IDisposable
{
private readonly Dx11Renderer renderer;
[SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "Keeping reference alive")]
private readonly ImGuiViewportHelpers.CreateWindowDelegate cwd;
public ViewportHandler(Dx11Renderer renderer)
{
this.renderer = renderer;
var pio = ImGui.GetPlatformIO();
pio.Renderer_CreateWindow = Marshal.GetFunctionPointerForDelegate(this.cwd = this.OnCreateWindow);
pio.Renderer_DestroyWindow = (nint)(delegate* unmanaged<ImGuiViewportPtr, void>)&OnDestroyWindow;
pio.Renderer_SetWindowSize = (nint)(delegate* unmanaged<ImGuiViewportPtr, Vector2, void>)&OnSetWindowSize;
pio.Renderer_RenderWindow = (nint)(delegate* unmanaged<ImGuiViewportPtr, nint, void>)&OnRenderWindow;
pio.Renderer_SwapBuffers = (nint)(delegate* unmanaged<ImGuiViewportPtr, nint, void>)&OnSwapBuffers;
}
~ViewportHandler() => ReleaseUnmanagedResources();
public void Dispose()
{
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
private static void ReleaseUnmanagedResources()
{
var pio = ImGui.GetPlatformIO();
pio.Renderer_CreateWindow = nint.Zero;
pio.Renderer_DestroyWindow = nint.Zero;
pio.Renderer_SetWindowSize = nint.Zero;
pio.Renderer_RenderWindow = nint.Zero;
pio.Renderer_SwapBuffers = nint.Zero;
}
[UnmanagedCallersOnly]
private static void OnDestroyWindow(ImGuiViewportPtr viewport)
{
if (viewport.RendererUserData == nint.Zero)
return;
ViewportData.Attach(viewport.RendererUserData).Dispose();
viewport.RendererUserData = nint.Zero;
}
[UnmanagedCallersOnly]
private static void OnSetWindowSize(ImGuiViewportPtr viewport, Vector2 size) =>
ViewportData.Attach(viewport.RendererUserData).ResizeBuffers((int)size.X, (int)size.Y, true);
[UnmanagedCallersOnly]
private static void OnRenderWindow(ImGuiViewportPtr viewport, nint v) =>
ViewportData.Attach(viewport.RendererUserData).Draw(viewport.DrawData, true);
[UnmanagedCallersOnly]
private static void OnSwapBuffers(ImGuiViewportPtr viewport, nint v) =>
ViewportData.Attach(viewport.RendererUserData).PresentIfSwapChainAvailable();
private void OnCreateWindow(ImGuiViewportPtr viewport)
{
// PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*).
// Some backend will leave PlatformHandleRaw NULL, in which case we assume PlatformHandle will contain the HWND.
var hWnd = viewport.PlatformHandleRaw;
if (hWnd == 0)
hWnd = viewport.PlatformHandle;
try
{
viewport.RendererUserData = ViewportData.CreateDComposition(this.renderer, (HWND)hWnd).Handle;
}
catch
{
viewport.RendererUserData = ViewportData.Create(this.renderer, (HWND)hWnd).Handle;
}
}
}
private sealed class ViewportData : IDisposable
{
private readonly Dx11Renderer parent;
private GCHandle selfGcHandle;
private ComPtr<IDXGISwapChain> swapChain;
private ComPtr<ID3D11Texture2D> renderTarget;
private ComPtr<ID3D11RenderTargetView> renderTargetView;
private ComPtr<IDCompositionVisual> dcompVisual;
private ComPtr<IDCompositionTarget> dcompTarget;
private int width;
private int height;
public ViewportData(
Dx11Renderer parent,
IDXGISwapChain* swapChain,
int width,
int height,
IDCompositionVisual* dcompVisual,
IDCompositionTarget* dcompTarget)
{
this.parent = parent;
this.swapChain = new(swapChain);
this.width = width;
this.height = height;
if (dcompVisual is not null)
this.dcompVisual = new(dcompVisual);
if (dcompTarget is not null)
this.dcompTarget = new(dcompTarget);
this.selfGcHandle = GCHandle.Alloc(this);
}
public IDXGISwapChain* SwapChain => this.swapChain;
public nint Handle => GCHandle.ToIntPtr(this.selfGcHandle);
private DXGI_FORMAT RtvFormat => this.parent.rtvFormat;
public static ViewportData Attach(nint handle) =>
(ViewportData)GCHandle.FromIntPtr(handle).Target ?? throw new InvalidOperationException();
public static ViewportData Create(
Dx11Renderer renderer,
IDXGISwapChain* swapChain,
IDCompositionVisual* dcompVisual,
IDCompositionTarget* dcompTarget)
{
DXGI_SWAP_CHAIN_DESC desc;
swapChain->GetDesc(&desc).ThrowOnError();
return new(
renderer,
swapChain,
(int)desc.BufferDesc.Width,
(int)desc.BufferDesc.Height,
dcompVisual,
dcompTarget);
}
public static ViewportData CreateDComposition(Dx11Renderer renderer, HWND hWnd)
{
if (renderer.dcompDevice.IsEmpty())
throw new NotSupportedException();
var mvsd = default(DXGI_SWAP_CHAIN_DESC);
renderer.mainViewport.SwapChain->GetDesc(&mvsd).ThrowOnError();
using var dxgiFactory = default(ComPtr<IDXGIFactory4>);
fixed (Guid* piidFactory = &IID.IID_IDXGIFactory4)
{
#if DEBUG
DirectX.CreateDXGIFactory2(
DXGI.DXGI_CREATE_FACTORY_DEBUG,
piidFactory,
(void**)dxgiFactory.GetAddressOf()).ThrowOnError();
#else
DirectX.CreateDXGIFactory1(piidFactory, (void**)dxgiFactory.GetAddressOf()).ThrowOnError();
#endif
}
RECT rc;
if (!GetWindowRect(hWnd, &rc) || rc.right == rc.left || rc.bottom == rc.top)
rc = new(0, 0, 4, 4);
using var swapChain1 = default(ComPtr<IDXGISwapChain1>);
var sd1 = new DXGI_SWAP_CHAIN_DESC1
{
Width = (uint)(rc.right - rc.left),
Height = (uint)(rc.bottom - rc.top),
Format = renderer.rtvFormat,
Stereo = false,
SampleDesc = new(1, 0),
BufferUsage = DXGI.DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount = Math.Max(2u, mvsd.BufferCount),
Scaling = DXGI_SCALING.DXGI_SCALING_STRETCH,
SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
AlphaMode = DXGI_ALPHA_MODE.DXGI_ALPHA_MODE_PREMULTIPLIED,
Flags = 0,
};
dxgiFactory.Get()->CreateSwapChainForComposition(
(IUnknown*)renderer.device.Get(),
&sd1,
null,
swapChain1.GetAddressOf()).ThrowOnError();
if (ReShadePeeler.PeelSwapChain(&swapChain1))
{
swapChain1.Get()->ResizeBuffers(sd1.BufferCount, sd1.Width, sd1.Height, sd1.Format, sd1.Flags)
.ThrowOnError();
}
using var dcTarget = default(ComPtr<IDCompositionTarget>);
renderer.dcompDevice.Get()->CreateTargetForHwnd(hWnd, BOOL.TRUE, dcTarget.GetAddressOf());
using var dcVisual = default(ComPtr<IDCompositionVisual>);
renderer.dcompDevice.Get()->CreateVisual(dcVisual.GetAddressOf()).ThrowOnError();
dcVisual.Get()->SetContent((IUnknown*)swapChain1.Get()).ThrowOnError();
dcTarget.Get()->SetRoot(dcVisual).ThrowOnError();
renderer.dcompDevice.Get()->Commit().ThrowOnError();
using var swapChain = default(ComPtr<IDXGISwapChain>);
swapChain1.As(&swapChain).ThrowOnError();
return Create(renderer, swapChain, dcVisual, dcTarget);
}
public static ViewportData Create(Dx11Renderer renderer, HWND hWnd)
{
using var dxgiFactory = default(ComPtr<IDXGIFactory>);
fixed (Guid* piidFactory = &IID.IID_IDXGIFactory)
{
#if DEBUG
DirectX.CreateDXGIFactory2(
DXGI.DXGI_CREATE_FACTORY_DEBUG,
piidFactory,
(void**)dxgiFactory.GetAddressOf()).ThrowOnError();
#else
DirectX.CreateDXGIFactory(piidFactory, (void**)dxgiFactory.GetAddressOf()).ThrowOnError();
#endif
}
// Create swapchain
using var swapChain = default(ComPtr<IDXGISwapChain>);
var desc = new DXGI_SWAP_CHAIN_DESC
{
BufferDesc =
{
Format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
},
SampleDesc = new(1, 0),
BufferUsage = DXGI.DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount = 1,
OutputWindow = hWnd,
Windowed = true,
SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_DISCARD,
};
dxgiFactory.Get()->CreateSwapChain((IUnknown*)renderer.device.Get(), &desc, swapChain.GetAddressOf())
.ThrowOnError();
if (ReShadePeeler.PeelSwapChain(&swapChain))
{
swapChain.Get()->ResizeBuffers(
desc.BufferCount,
desc.BufferDesc.Width,
desc.BufferDesc.Height,
desc.BufferDesc.Format,
desc.Flags)
.ThrowOnError();
}
return Create(renderer, swapChain, null, null);
}
public void Dispose()
{
if (!this.selfGcHandle.IsAllocated)
return;
this.ResetBuffers();
this.dcompVisual.Reset();
this.dcompTarget.Reset();
this.swapChain.Reset();
this.selfGcHandle.Free();
}
public void Draw(ImDrawDataPtr drawData, bool clearRenderTarget)
{
if (this.width < 1 || this.height < 1)
return;
this.EnsureRenderTarget();
this.parent.RenderDrawDataInternal(this.renderTargetView, drawData, clearRenderTarget);
}
public void PresentIfSwapChainAvailable()
{
if (this.width < 1 || this.height < 1)
return;
if (!this.swapChain.IsEmpty())
this.swapChain.Get()->Present(0, 0).ThrowOnError();
}
public void ResetBuffers()
{
this.renderTargetView.Reset();
this.renderTarget.Reset();
}
public void ResizeBuffers(int newWidth, int newHeight, bool resizeSwapChain)
{
this.ResetBuffers();
this.width = newWidth;
this.height = newHeight;
if (this.width < 1 || this.height < 1)
return;
if (resizeSwapChain && !this.swapChain.IsEmpty())
{
DXGI_SWAP_CHAIN_DESC desc;
this.swapChain.Get()->GetDesc(&desc).ThrowOnError();
this.swapChain.Get()->ResizeBuffers(
desc.BufferCount,
(uint)newWidth,
(uint)newHeight,
DXGI_FORMAT.DXGI_FORMAT_UNKNOWN,
desc.Flags).ThrowOnError();
}
}
private void EnsureRenderTarget()
{
if (!this.renderTarget.IsEmpty() && !this.renderTargetView.IsEmpty())
return;
this.ResetBuffers();
fixed (ID3D11Texture2D** pprt = &this.renderTarget.GetPinnableReference())
fixed (ID3D11RenderTargetView** pprtv = &this.renderTargetView.GetPinnableReference())
{
if (this.swapChain.IsEmpty())
{
var desc = new D3D11_TEXTURE2D_DESC
{
Width = (uint)this.width,
Height = (uint)this.height,
MipLevels = 1,
ArraySize = 1,
Format = this.RtvFormat,
SampleDesc = new(1, 0),
Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT,
BindFlags = (uint)D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE,
CPUAccessFlags = 0,
MiscFlags = (uint)D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_SHARED_NTHANDLE,
};
this.parent.device.Get()->CreateTexture2D(&desc, null, pprt).ThrowOnError();
}
else
{
fixed (Guid* piid = &IID.IID_ID3D11Texture2D)
{
this.swapChain.Get()->GetBuffer(0u, piid, (void**)pprt)
.ThrowOnError();
}
}
this.parent.device.Get()->CreateRenderTargetView((ID3D11Resource*)*pprt, null, pprtv).ThrowOnError();
}
}
}
}

View file

@ -0,0 +1,664 @@
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Dalamud.Interface.ImGuiBackend.Helpers;
using Dalamud.Interface.ImGuiBackend.Helpers.D3D11;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Textures.TextureWraps.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Utility;
using ImGuiNET;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace Dalamud.Interface.ImGuiBackend.Renderers;
/// <summary>
/// Deals with rendering ImGui using DirectX 11.
/// See https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_dx11.cpp for the original implementation.
/// </summary>
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed/using scopes")]
internal unsafe partial class Dx11Renderer : IImGuiRenderer
{
private readonly List<IDalamudTextureWrap> fontTextures = new();
private readonly D3D_FEATURE_LEVEL featureLevel;
private readonly ViewportHandler viewportHandler;
private readonly nint renderNamePtr;
private readonly DXGI_FORMAT rtvFormat;
private readonly ViewportData mainViewport;
private bool releaseUnmanagedResourceCalled;
private ComPtr<ID3D11Device> device;
private ComPtr<ID3D11DeviceContext> context;
private ComPtr<ID3D11VertexShader> vertexShader;
private ComPtr<ID3D11PixelShader> pixelShader;
private ComPtr<ID3D11SamplerState> sampler;
private ComPtr<ID3D11InputLayout> inputLayout;
private ComPtr<ID3D11Buffer> vertexConstantBuffer;
private ComPtr<ID3D11BlendState> blendState;
private ComPtr<ID3D11RasterizerState> rasterizerState;
private ComPtr<ID3D11DepthStencilState> depthStencilState;
private ComPtr<ID3D11Buffer> vertexBuffer;
private ComPtr<ID3D11Buffer> indexBuffer;
private int vertexBufferSize;
private int indexBufferSize;
private ComPtr<IDCompositionDevice> dcompDevice;
/// <summary>
/// Initializes a new instance of the <see cref="Dx11Renderer"/> class.
/// </summary>
/// <param name="swapChain">The swap chain.</param>
/// <param name="device">A pointer to an instance of <see cref="ID3D11Device"/>.</param>
/// <param name="context">A pointer to an instance of <see cref="ID3D11DeviceContext"/>.</param>
public Dx11Renderer(IDXGISwapChain* swapChain, ID3D11Device* device, ID3D11DeviceContext* context)
{
var io = ImGui.GetIO();
if (ImGui.GetIO().NativePtr->BackendRendererName is not null)
throw new InvalidOperationException("ImGui backend renderer seems to be have been already attached.");
try
{
DXGI_SWAP_CHAIN_DESC desc;
swapChain->GetDesc(&desc).ThrowOnError();
this.rtvFormat = desc.BufferDesc.Format;
this.device = new(device);
this.context = new(context);
this.featureLevel = device->GetFeatureLevel();
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.RendererHasViewports;
this.renderNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_dx11_c#");
io.NativePtr->BackendRendererName = (byte*)this.renderNamePtr;
if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
{
try
{
fixed (IDCompositionDevice** pp = &this.dcompDevice.GetPinnableReference())
fixed (Guid* piidDCompositionDevice = &IID.IID_IDCompositionDevice)
DirectX.DCompositionCreateDevice(null, piidDCompositionDevice, (void**)pp).ThrowOnError();
ImGuiViewportHelpers.EnableViewportWindowBackgroundAlpha();
}
catch
{
// don't care; not using DComposition then
}
this.viewportHandler = new(this);
}
this.mainViewport = ViewportData.Create(this, swapChain, null, null);
ImGui.GetPlatformIO().Viewports[0].RendererUserData = this.mainViewport.Handle;
}
catch
{
this.ReleaseUnmanagedResources();
throw;
}
}
/// <summary>
/// Finalizes an instance of the <see cref="Dx11Renderer"/> class.
/// </summary>
~Dx11Renderer() => this.ReleaseUnmanagedResources();
/// <inheritdoc/>
public void Dispose()
{
this.ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
/// <inheritdoc/>
public void OnNewFrame()
{
this.EnsureDeviceObjects();
}
/// <inheritdoc/>
public void OnPreResize() => this.mainViewport.ResetBuffers();
/// <inheritdoc/>
public void OnPostResize(int width, int height) => this.mainViewport.ResizeBuffers(width, height, false);
/// <inheritdoc/>
public void RenderDrawData(ImDrawDataPtr drawData) =>
this.mainViewport.Draw(drawData, this.mainViewport.SwapChain == null);
/// <summary>
/// Rebuilds font texture.
/// </summary>
public void RebuildFontTexture()
{
foreach (var fontResourceView in this.fontTextures)
fontResourceView.Dispose();
this.fontTextures.Clear();
this.CreateFontsTexture();
}
/// <inheritdoc/>
public IDalamudTextureWrap CreateTexture2D(
ReadOnlySpan<byte> data,
RawImageSpecification specs,
bool cpuRead,
bool cpuWrite,
bool allowRenderTarget,
[CallerMemberName] string debugName = "")
{
if (cpuRead && cpuWrite)
throw new ArgumentException("cpuRead and cpuWrite cannot be set at the same time.");
var cpuaf = default(D3D11_CPU_ACCESS_FLAG);
if (cpuRead)
cpuaf |= D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ;
if (cpuWrite)
cpuaf |= D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE;
D3D11_USAGE usage;
if (cpuRead)
usage = D3D11_USAGE.D3D11_USAGE_STAGING;
else if (cpuWrite)
usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC;
else
usage = D3D11_USAGE.D3D11_USAGE_DEFAULT;
var texDesc = new D3D11_TEXTURE2D_DESC
{
Width = (uint)specs.Width,
Height = (uint)specs.Height,
MipLevels = 1,
ArraySize = 1,
Format = specs.Format,
SampleDesc = new(1, 0),
Usage = usage,
BindFlags = (uint)(D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE |
(allowRenderTarget ? D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET : 0)),
CPUAccessFlags = (uint)cpuaf,
MiscFlags = 0,
};
using var texture = default(ComPtr<ID3D11Texture2D>);
if (data.IsEmpty)
{
Marshal.ThrowExceptionForHR(this.device.Get()->CreateTexture2D(&texDesc, null, texture.GetAddressOf()));
}
else
{
fixed (void* dataPtr = data)
{
var subrdata = new D3D11_SUBRESOURCE_DATA { pSysMem = dataPtr, SysMemPitch = (uint)specs.Pitch };
Marshal.ThrowExceptionForHR(
this.device.Get()->CreateTexture2D(&texDesc, &subrdata, texture.GetAddressOf()));
}
}
texture.Get()->SetDebugName($"Texture:{debugName}:SRV");
using var srvTemp = default(ComPtr<ID3D11ShaderResourceView>);
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
texture,
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
this.device.Get()->CreateShaderResourceView((ID3D11Resource*)texture.Get(), &srvDesc, srvTemp.GetAddressOf())
.ThrowOnError();
srvTemp.Get()->SetDebugName($"Texture:{debugName}:SRV");
return new UnknownTextureWrap((IUnknown*)srvTemp.Get(), specs.Width, specs.Height, true);
}
private void RenderDrawDataInternal(
ID3D11RenderTargetView* renderTargetView,
ImDrawDataPtr drawData,
bool clearRenderTarget)
{
// Avoid rendering when minimized
if (drawData.DisplaySize.X <= 0 || drawData.DisplaySize.Y <= 0)
return;
using var oldState = new D3D11DeviceContextStateBackup(this.featureLevel, this.context.Get());
// Setup desired DX state
this.SetupRenderState(drawData);
this.context.Get()->OMSetRenderTargets(1, &renderTargetView, null);
if (clearRenderTarget)
{
var color = default(Vector4);
this.context.Get()->ClearRenderTargetView(renderTargetView, (float*)&color);
}
if (!drawData.Valid || drawData.CmdListsCount == 0)
return;
var cmdLists = new Span<ImDrawListPtr>(drawData.NativePtr->CmdLists, drawData.NativePtr->CmdListsCount);
// Create and grow vertex/index buffers if needed
if (this.vertexBufferSize < drawData.TotalVtxCount)
this.vertexBuffer.Dispose();
if (this.vertexBuffer.Get() is null)
{
this.vertexBufferSize = drawData.TotalVtxCount + 5000;
var desc = new D3D11_BUFFER_DESC(
(uint)(sizeof(ImDrawVert) * this.vertexBufferSize),
(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*);
this.device.Get()->CreateBuffer(&desc, null, &buffer).ThrowOnError();
this.vertexBuffer.Attach(buffer);
}
if (this.indexBufferSize < drawData.TotalIdxCount)
this.indexBuffer.Dispose();
if (this.indexBuffer.Get() is null)
{
this.indexBufferSize = drawData.TotalIdxCount + 5000;
var desc = new D3D11_BUFFER_DESC(
(uint)(sizeof(ushort) * this.indexBufferSize),
(uint)D3D11_BIND_FLAG.D3D11_BIND_INDEX_BUFFER,
D3D11_USAGE.D3D11_USAGE_DYNAMIC,
(uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE);
var buffer = default(ID3D11Buffer*);
this.device.Get()->CreateBuffer(&desc, null, &buffer).ThrowOnError();
this.indexBuffer.Attach(buffer);
}
// Upload vertex/index data into a single contiguous GPU buffer
try
{
var vertexData = default(D3D11_MAPPED_SUBRESOURCE);
var indexData = default(D3D11_MAPPED_SUBRESOURCE);
this.context.Get()->Map(
(ID3D11Resource*)this.vertexBuffer.Get(),
0,
D3D11_MAP.D3D11_MAP_WRITE_DISCARD,
0,
&vertexData).ThrowOnError();
this.context.Get()->Map(
(ID3D11Resource*)this.indexBuffer.Get(),
0,
D3D11_MAP.D3D11_MAP_WRITE_DISCARD,
0,
&indexData).ThrowOnError();
var targetVertices = new Span<ImDrawVert>(vertexData.pData, this.vertexBufferSize);
var targetIndices = new Span<ushort>(indexData.pData, this.indexBufferSize);
foreach (ref var cmdList in cmdLists)
{
var vertices = new ImVectorWrapper<ImDrawVert>(&cmdList.NativePtr->VtxBuffer);
var indices = new ImVectorWrapper<ushort>(&cmdList.NativePtr->IdxBuffer);
vertices.DataSpan.CopyTo(targetVertices);
indices.DataSpan.CopyTo(targetIndices);
targetVertices = targetVertices[vertices.Length..];
targetIndices = targetIndices[indices.Length..];
}
}
finally
{
this.context.Get()->Unmap((ID3D11Resource*)this.vertexBuffer.Get(), 0);
this.context.Get()->Unmap((ID3D11Resource*)this.indexBuffer.Get(), 0);
}
// Setup orthographic projection matrix into our constant buffer.
// Our visible imgui space lies from DisplayPos (LT) to DisplayPos+DisplaySize (RB).
// DisplayPos is (0,0) for single viewport apps.
try
{
var data = default(D3D11_MAPPED_SUBRESOURCE);
this.context.Get()->Map(
(ID3D11Resource*)this.vertexConstantBuffer.Get(),
0,
D3D11_MAP.D3D11_MAP_WRITE_DISCARD,
0,
&data).ThrowOnError();
*(Matrix4x4*)data.pData = Matrix4x4.CreateOrthographicOffCenter(
drawData.DisplayPos.X,
drawData.DisplayPos.X + drawData.DisplaySize.X,
drawData.DisplayPos.Y + drawData.DisplaySize.Y,
drawData.DisplayPos.Y,
1f,
0f);
}
finally
{
this.context.Get()->Unmap((ID3D11Resource*)this.vertexConstantBuffer.Get(), 0);
}
// Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them)
var vertexOffset = 0;
var indexOffset = 0;
var clipOff = new Vector4(drawData.DisplayPos, drawData.DisplayPos.X, drawData.DisplayPos.Y);
foreach (ref var cmdList in cmdLists)
{
var cmds = new ImVectorWrapper<ImDrawCmd>(&cmdList.NativePtr->CmdBuffer);
foreach (ref var cmd in cmds.DataSpan)
{
var clipV4 = cmd.ClipRect - clipOff;
var clipRect = new RECT((int)clipV4.X, (int)clipV4.Y, (int)clipV4.Z, (int)clipV4.W);
// Skip the draw if nothing would be visible
if (clipRect.left >= clipRect.right || clipRect.top >= clipRect.bottom)
continue;
this.context.Get()->RSSetScissorRects(1, &clipRect);
if (cmd.UserCallback == nint.Zero)
{
// Bind texture and draw
var srv = (ID3D11ShaderResourceView*)cmd.TextureId;
this.context.Get()->PSSetShader(this.pixelShader, null, 0);
this.context.Get()->PSSetSamplers(0, 1, this.sampler.GetAddressOf());
this.context.Get()->PSSetShaderResources(0, 1, &srv);
this.context.Get()->DrawIndexed(
cmd.ElemCount,
(uint)(cmd.IdxOffset + indexOffset),
(int)(cmd.VtxOffset + vertexOffset));
}
}
indexOffset += cmdList.IdxBuffer.Size;
vertexOffset += cmdList.VtxBuffer.Size;
}
}
/// <summary>
/// Builds fonts as necessary, and uploads the built data onto the GPU.<br />
/// No-op if it has already been done.
/// </summary>
private void CreateFontsTexture()
{
if (this.device.IsEmpty())
throw new ObjectDisposedException(nameof(Dx11Renderer));
if (this.fontTextures.Any())
return;
var io = ImGui.GetIO();
if (io.Fonts.Textures.Size == 0)
io.Fonts.Build();
for (int textureIndex = 0, textureCount = io.Fonts.Textures.Size;
textureIndex < textureCount;
textureIndex++)
{
// Build texture atlas
io.Fonts.GetTexDataAsRGBA32(
textureIndex,
out byte* fontPixels,
out var width,
out var height,
out var bytespp);
var tex = this.CreateTexture2D(
new(fontPixels, width * height * bytespp),
new(width, height, (int)DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM, width * bytespp),
false,
false,
false,
$"Font#{textureIndex}");
io.Fonts.SetTexID(textureIndex, tex.ImGuiHandle);
this.fontTextures.Add(tex);
}
io.Fonts.ClearTexData();
}
/// <summary>
/// Initializes the device context's render state to what we would use for rendering ImGui by default.
/// </summary>
/// <param name="drawData">The relevant ImGui draw data.</param>
private void SetupRenderState(ImDrawDataPtr drawData)
{
var ctx = this.context.Get();
ctx->IASetInputLayout(this.inputLayout);
var buffer = this.vertexBuffer.Get();
var stride = (uint)sizeof(ImDrawVert);
var offset = 0u;
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, drawData.DisplaySize.X, drawData.DisplaySize.Y);
ctx->RSSetState(this.rasterizerState);
ctx->RSSetViewports(1, &viewport);
var blendColor = default(Vector4);
ctx->OMSetBlendState(this.blendState, (float*)&blendColor, 0xffffffff);
ctx->OMSetDepthStencilState(this.depthStencilState, 0);
ctx->VSSetShader(this.vertexShader.Get(), null, 0);
buffer = this.vertexConstantBuffer.Get();
ctx->VSSetConstantBuffers(0, 1, &buffer);
// PS handled later
ctx->GSSetShader(null, null, 0);
ctx->HSSetShader(null, null, 0);
ctx->DSSetShader(null, null, 0);
ctx->CSSetShader(null, null, 0);
}
/// <summary>
/// Creates objects from the device as necessary.<br />
/// No-op if objects already are built.
/// </summary>
private void EnsureDeviceObjects()
{
if (this.device.IsEmpty())
throw new ObjectDisposedException(nameof(Dx11Renderer));
var assembly = Assembly.GetExecutingAssembly();
// Create the vertex shader
if (this.vertexShader.IsEmpty() || this.inputLayout.IsEmpty())
{
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)
{
this.device.Get()->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,
},
};
this.device.Get()->CreateInputLayout(ied, 3, pArray, (nuint)stream.Length, ppInputLayout)
.ThrowOnError();
}
ArrayPool<byte>.Shared.Return(array);
}
// 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())
this.device.Get()->CreatePixelShader(pArray, (nuint)stream.Length, null, ppShader).ThrowOnError();
ArrayPool<byte>.Shared.Return(array);
}
// Create the sampler state
if (this.sampler.IsEmpty())
{
var samplerDesc = new D3D11_SAMPLER_DESC
{
Filter = D3D11_FILTER.D3D11_FILTER_MIN_MAG_MIP_LINEAR,
AddressU = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
AddressV = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
AddressW = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
MipLODBias = 0,
MaxAnisotropy = 0,
ComparisonFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS,
MinLOD = 0,
MaxLOD = 0,
};
fixed (ID3D11SamplerState** ppSampler = &this.sampler.GetPinnableReference())
this.device.Get()->CreateSamplerState(&samplerDesc, ppSampler).ThrowOnError();
}
// 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_DYNAMIC,
(uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE);
fixed (ID3D11Buffer** ppBuffer = &this.vertexConstantBuffer.GetPinnableReference())
this.device.Get()->CreateBuffer(&bufferDesc, null, ppBuffer).ThrowOnError();
}
// 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())
this.device.Get()->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,
ScissorEnable = true,
DepthClipEnable = true,
};
fixed (ID3D11RasterizerState** ppRasterizerState = &this.rasterizerState.GetPinnableReference())
this.device.Get()->CreateRasterizerState(&rasterizerDesc, ppRasterizerState).ThrowOnError();
}
// Create the depth-stencil State
if (this.depthStencilState.IsEmpty())
{
var dsDesc = new D3D11_DEPTH_STENCIL_DESC
{
DepthEnable = false,
DepthWriteMask = D3D11_DEPTH_WRITE_MASK.D3D11_DEPTH_WRITE_MASK_ALL,
DepthFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS,
StencilEnable = false,
StencilReadMask = byte.MaxValue,
StencilWriteMask = byte.MaxValue,
FrontFace =
{
StencilFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP,
StencilDepthFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP,
StencilPassOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP,
StencilFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS,
},
BackFace =
{
StencilFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP,
StencilDepthFailOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP,
StencilPassOp = D3D11_STENCIL_OP.D3D11_STENCIL_OP_KEEP,
StencilFunc = D3D11_COMPARISON_FUNC.D3D11_COMPARISON_ALWAYS,
},
};
fixed (ID3D11DepthStencilState** ppDepthStencilState = &this.depthStencilState.GetPinnableReference())
this.device.Get()->CreateDepthStencilState(&dsDesc, ppDepthStencilState).ThrowOnError();
}
this.CreateFontsTexture();
}
private void ReleaseUnmanagedResources()
{
if (this.releaseUnmanagedResourceCalled)
return;
this.releaseUnmanagedResourceCalled = true;
this.mainViewport.Dispose();
ImGui.GetPlatformIO().Viewports[0].RendererUserData = nint.Zero;
ImGui.DestroyPlatformWindows();
this.viewportHandler.Dispose();
var io = ImGui.GetIO();
if (io.NativePtr->BackendRendererName == (void*)this.renderNamePtr)
io.NativePtr->BackendRendererName = null;
if (this.renderNamePtr != 0)
Marshal.FreeHGlobal(this.renderNamePtr);
foreach (var fontResourceView in this.fontTextures)
fontResourceView.Dispose();
foreach (var i in Enumerable.Range(0, io.Fonts.Textures.Size))
io.Fonts.SetTexID(i, nint.Zero);
this.device.Reset();
this.context.Reset();
this.vertexShader.Reset();
this.pixelShader.Reset();
this.sampler.Reset();
this.inputLayout.Reset();
this.vertexConstantBuffer.Reset();
this.blendState.Reset();
this.rasterizerState.Reset();
this.depthStencilState.Reset();
this.vertexBuffer.Reset();
this.indexBuffer.Reset();
this.dcompDevice.Reset();
}
}

View file

@ -0,0 +1,43 @@
using System.Runtime.CompilerServices;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using ImGuiNET;
namespace Dalamud.Interface.ImGuiBackend.Renderers;
/// <summary>A simple shared public interface that all ImGui render implementations follow.</summary>
internal interface IImGuiRenderer : IDisposable
{
/// <summary>Load an image from a span of bytes of specified format.</summary>
/// <param name="data">The data to load.</param>
/// <param name="specs">Texture specifications.</param>
/// <param name="cpuRead">Whether to support reading from CPU, while disabling reading from GPU.</param>
/// <param name="cpuWrite">Whether to support writing from CPU, while disabling writing from GPU.</param>
/// <param name="allowRenderTarget">Whether to allow rendering to this texture.</param>
/// <param name="debugName">Name for debugging.</param>
/// <returns>A texture, ready to use in ImGui.</returns>
IDalamudTextureWrap CreateTexture2D(
ReadOnlySpan<byte> data,
RawImageSpecification specs,
bool cpuRead,
bool cpuWrite,
bool allowRenderTarget,
[CallerMemberName] string debugName = "");
/// <summary>Notifies that the window is about to be resized.</summary>
void OnPreResize();
/// <summary>Notifies that the window has been resized.</summary>
/// <param name="width">The new window width.</param>
/// <param name="height">The new window height.</param>
void OnPostResize(int width, int height);
/// <summary>Marks the beginning of a new frame.</summary>
void OnNewFrame();
/// <summary>Renders the draw data.</summary>
/// <param name="drawData">The draw data.</param>
void RenderDrawData(ImDrawDataPtr drawData);
}

View file

@ -119,13 +119,13 @@ internal unsafe partial class InterfaceManager
this.ResizeBuffers?.InvokeSafely();
this.scene?.OnPreResize();
this.backend?.OnPreResize();
var ret = this.dxgiSwapChainResizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
if (ret == DXGI.DXGI_ERROR_INVALID_CALL)
Log.Error("invalid call to resizeBuffers");
this.scene?.OnPostResize((int)width, (int)height);
this.backend?.OnPostResize((int)width, (int)height);
return ret;
}

View file

@ -14,23 +14,23 @@ internal unsafe partial class InterfaceManager
private void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
{
var swapChainNative = swapChain.GetNative<IDXGISwapChain>();
if (this.scene?.SwapChain.NativePointer != (nint)swapChainNative)
if (this.backend?.IsAttachedToPresentationTarget((nint)swapChainNative) is not true)
return;
this.scene?.OnPreResize();
this.backend?.OnPreResize();
}
private void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
{
var swapChainNative = swapChain.GetNative<IDXGISwapChain>();
if (this.scene?.SwapChain.NativePointer != (nint)swapChainNative)
if (this.backend?.IsAttachedToPresentationTarget((nint)swapChainNative) is not true)
return;
DXGI_SWAP_CHAIN_DESC desc;
if (swapChainNative->GetDesc(&desc).FAILED)
return;
this.scene?.OnPostResize((int)desc.BufferDesc.Width, (int)desc.BufferDesc.Height);
this.backend?.OnPostResize((int)desc.BufferDesc.Width, (int)desc.BufferDesc.Height);
}
private void ReShadeAddonInterfaceOnPresent(
@ -42,16 +42,16 @@ internal unsafe partial class InterfaceManager
{
var swapChainNative = swapChain.GetNative<IDXGISwapChain>();
if (this.RenderDalamudCheckAndInitialize(swapChainNative, 0) is { } activeScene)
this.RenderDalamudDraw(activeScene);
if (this.RenderDalamudCheckAndInitialize(swapChainNative, 0) is { } activebackend)
this.RenderDalamudDraw(activebackend);
}
private void ReShadeAddonInterfaceOnReShadeOverlay(ref ReShadeAddonInterface.ApiObject runtime)
{
var swapChainNative = runtime.GetNative<IDXGISwapChain>();
if (this.RenderDalamudCheckAndInitialize(swapChainNative, 0) is { } activeScene)
this.RenderDalamudDraw(activeScene);
if (this.RenderDalamudCheckAndInitialize(swapChainNative, 0) is { } activebackend)
this.RenderDalamudDraw(activebackend);
}
private int AsReShadeAddonDxgiSwapChainResizeBuffersDetour(

View file

@ -17,6 +17,7 @@ using Dalamud.Game.ClientState.Keys;
using Dalamud.Hooking;
using Dalamud.Hooking.Internal;
using Dalamud.Hooking.WndProcHook;
using Dalamud.Interface.ImGuiBackend;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification.Internal;
using Dalamud.Interface.Internal.Asserts;
@ -38,15 +39,13 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Environment;
using ImGuiNET;
using ImGuiScene;
using JetBrains.Annotations;
using PInvoke;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
// general dev notes, here because it's easiest
/*
@ -102,7 +101,7 @@ internal partial class InterfaceManager : IInternalDisposableService
private readonly AssertHandler assertHandler = new();
private RawDX11Scene? scene;
private IWin32Backend? backend;
private Hook<SetCursorDelegate>? setCursorHook;
private Hook<ReShadeDxgiSwapChainPresentDelegate>? reShadeDxgiSwapChainPresentHook;
@ -115,9 +114,9 @@ internal partial class InterfaceManager : IInternalDisposableService
private ILockedImFont? defaultFontResourceLock;
// can't access imgui IO before first present call
private bool lastWantCapture = false;
private HWND gameWindowHandle;
private bool lastWantCapture;
private bool isOverrideGameCursor = true;
private IntPtr gameWindowHandle;
[ServiceManager.ServiceConstructor]
private InterfaceManager()
@ -126,12 +125,12 @@ internal partial class InterfaceManager : IInternalDisposableService
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate IntPtr SetCursorDelegate(IntPtr hCursor);
private delegate nint SetCursorDelegate(nint hCursor);
/// <summary>
/// This event gets called each frame to facilitate ImGui drawing.
/// </summary>
public event RawDX11Scene.BuildUIDelegate? Draw;
public event IImGuiBackend.BuildUiDelegate? Draw;
/// <summary>
/// This event gets called when ResizeBuffers is called.
@ -199,36 +198,26 @@ internal partial class InterfaceManager : IInternalDisposableService
/// <summary>
/// Gets the DX11 scene.
/// </summary>
public RawDX11Scene? Scene => this.scene;
/// <summary>
/// Gets the D3D11 device instance.
/// </summary>
public SharpDX.Direct3D11.Device? Device => this.scene?.Device;
/// <summary>
/// Gets the address handle to the main process window.
/// </summary>
public IntPtr WindowHandlePtr => this.scene?.WindowHandlePtr ?? IntPtr.Zero;
public IImGuiBackend? Backend => this.backend;
/// <summary>
/// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor.
/// </summary>
public bool OverrideGameCursor
{
get => this.scene?.UpdateCursor ?? this.isOverrideGameCursor;
get => this.backend?.UpdateCursor ?? this.isOverrideGameCursor;
set
{
this.isOverrideGameCursor = value;
if (this.scene != null)
this.scene.UpdateCursor = value;
if (this.backend != null)
this.backend.UpdateCursor = value;
}
}
/// <summary>
/// Gets a value indicating whether the Dalamud interface ready to use.
/// </summary>
public bool IsReady => this.scene != null;
public bool IsReady => this.backend != null;
/// <summary>
/// Gets or sets a value indicating whether or not Draw events should be dispatched.
@ -242,20 +231,24 @@ internal partial class InterfaceManager : IInternalDisposableService
/// <summary>
/// Gets a value indicating the native handle of the game main window.
/// </summary>
public IntPtr GameWindowHandle
public unsafe HWND GameWindowHandle
{
get
{
if (this.gameWindowHandle == 0)
{
nint gwh = 0;
while ((gwh = NativeFunctions.FindWindowEx(0, gwh, "FFXIVGAME", 0)) != 0)
var gwh = default(HWND);
fixed (char* pClass = "FFXIVGAME")
{
_ = User32.GetWindowThreadProcessId(gwh, out var pid);
if (pid == Environment.ProcessId && User32.IsWindowVisible(gwh))
while ((gwh = FindWindowExW(default, gwh, (ushort*)pClass, default)) != default)
{
this.gameWindowHandle = gwh;
break;
uint pid;
_ = GetWindowThreadProcessId(gwh, &pid);
if (pid == Environment.ProcessId && IsWindowVisible(gwh))
{
this.gameWindowHandle = gwh;
break;
}
}
}
}
@ -324,7 +317,7 @@ internal partial class InterfaceManager : IInternalDisposableService
this.IconFontHandle = null;
Interlocked.Exchange(ref this.dalamudAtlas, null)?.Dispose();
Interlocked.Exchange(ref this.scene, null)?.Dispose();
Interlocked.Exchange(ref this.backend, null)?.Dispose();
return;
@ -459,27 +452,27 @@ internal partial class InterfaceManager : IInternalDisposableService
/// Get video memory information.
/// </summary>
/// <returns>The currently used video memory, or null if not available.</returns>
public (long Used, long Available)? GetD3dMemoryInfo()
public unsafe (long Used, long Available)? GetD3dMemoryInfo()
{
if (this.Device == null)
if (this.backend?.DeviceHandle is 0 or null)
return null;
try
{
var dxgiDev = this.Device.QueryInterfaceOrNull<SharpDX.DXGI.Device>();
var dxgiAdapter = dxgiDev?.Adapter.QueryInterfaceOrNull<SharpDX.DXGI.Adapter4>();
if (dxgiAdapter == null)
return null;
using var device = default(ComPtr<IDXGIDevice>);
using var adapter = default(ComPtr<IDXGIAdapter>);
using var adapter4 = default(ComPtr<IDXGIAdapter4>);
var memInfo = dxgiAdapter.QueryVideoMemoryInfo(0, SharpDX.DXGI.MemorySegmentGroup.Local);
return (memInfo.CurrentUsage, memInfo.CurrentReservation);
}
catch
{
// ignored
}
if (new ComPtr<IUnknown>((IUnknown*)this.backend.DeviceHandle).As(&device).FAILED)
return null;
return null;
if (device.Get()->GetAdapter(adapter.GetAddressOf()).FAILED)
return null;
if (adapter.As(&adapter4).FAILED)
return null;
var vmi = default(DXGI_QUERY_VIDEO_MEMORY_INFO);
adapter4.Get()->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP.DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &vmi);
return ((long)vmi.CurrentUsage, (long)vmi.CurrentReservation);
}
/// <summary>
@ -488,23 +481,23 @@ internal partial class InterfaceManager : IInternalDisposableService
/// </summary>
public void ClearStacks()
{
this.scene?.ClearStacksOnContext();
ImGuiHelpers.ClearStacksOnContext();
}
/// <summary>
/// Toggle Windows 11 immersive mode on the game window.
/// </summary>
/// <param name="enabled">Value.</param>
internal void SetImmersiveMode(bool enabled)
internal unsafe void SetImmersiveMode(bool enabled)
{
if (this.GameWindowHandle == 0)
throw new InvalidOperationException("Game window is not yet ready.");
var value = enabled ? 1 : 0;
((HRESULT)NativeFunctions.DwmSetWindowAttribute(
this.GameWindowHandle,
NativeFunctions.DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
ref value,
sizeof(int))).ThrowOnError();
var value = enabled ? 1u : 0u;
DwmSetWindowAttribute(
this.GameWindowHandle,
(uint)DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
&value,
sizeof(int)).ThrowOnError();
}
private static InterfaceManager WhenFontsReady()
@ -535,9 +528,9 @@ internal partial class InterfaceManager : IInternalDisposableService
/// and initializes ImGui for drawing.</summary>
/// <param name="swapChain">The swap chain to test and initialize ImGui with if conditions are met.</param>
/// <param name="flags">Flags passed to <see cref="IDXGISwapChain.Present"/>.</param>
/// <returns>An initialized instance of <see cref="RawDX11Scene"/>, or <c>null</c> if <paramref name="swapChain"/>
/// <returns>An initialized instance of <see cref="IDXGISwapChain"/>, or <c>null</c> if <paramref name="swapChain"/>
/// is not the main swap chain.</returns>
private unsafe RawDX11Scene? RenderDalamudCheckAndInitialize(IDXGISwapChain* swapChain, uint flags)
private unsafe IImGuiBackend? RenderDalamudCheckAndInitialize(IDXGISwapChain* swapChain, uint flags)
{
// Quoting ReShade dxgi_swapchain.cpp DXGISwapChain::on_present:
// > Some D3D11 games test presentation for timing and composition purposes
@ -550,7 +543,7 @@ internal partial class InterfaceManager : IInternalDisposableService
Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already");
var activeScene = this.scene ?? this.InitScene(swapChain);
var activeBackend = this.backend ?? this.InitBackend(swapChain);
if (!this.dalamudAtlas!.HasBuiltAtlas)
{
@ -564,12 +557,12 @@ internal partial class InterfaceManager : IInternalDisposableService
return null;
}
return activeScene;
return activeBackend;
}
/// <summary>Draws Dalamud to the given scene representing the ImGui context.</summary>
/// <param name="activeScene">The scene to draw to.</param>
private void RenderDalamudDraw(RawDX11Scene activeScene)
/// <param name="activeBackend">The scene to draw to.</param>
private void RenderDalamudDraw(IImGuiBackend activeBackend)
{
this.CumulativePresentCalls++;
this.IsMainThreadInPresent = true;
@ -582,7 +575,7 @@ internal partial class InterfaceManager : IInternalDisposableService
// Enable viewports if there are no issues.
var viewportsEnable = this.dalamudConfiguration.IsDisableViewport ||
activeScene.SwapChain.IsFullScreen ||
activeBackend.IsMainViewportFullScreen() ||
ImGui.GetPlatformIO().Monitors.Size == 1;
if (viewportsEnable)
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable;
@ -590,41 +583,48 @@ internal partial class InterfaceManager : IInternalDisposableService
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
// Call drawing functions, which in turn will call Draw event.
activeScene.Render();
activeBackend.Render();
this.PostImGuiRender();
this.IsMainThreadInPresent = false;
}
private unsafe RawDX11Scene InitScene(IDXGISwapChain* swapChain)
private unsafe IImGuiBackend InitBackend(IDXGISwapChain* swapChain)
{
RawDX11Scene newScene;
IWin32Backend newBackend;
using (Timings.Start("IM Scene Init"))
{
try
{
newBackend = new Dx11Win32Backend(swapChain);
this.assertHandler.Setup();
newScene = new RawDX11Scene((nint)swapChain);
}
catch (DllNotFoundException ex)
{
Service<InterfaceManagerWithScene>.ProvideException(ex);
Log.Error(ex, "Could not load ImGui dependencies.");
var res = User32.MessageBox(
IntPtr.Zero,
"Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?",
"Dalamud Error",
User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | User32.MessageBoxOptions.MB_ICONERROR);
if (res == User32.MessageBoxResult.IDYES)
fixed (void* lpText =
"Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?")
{
var psi = new ProcessStartInfo
fixed (void* lpCaption = "Dalamud Error")
{
FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe",
UseShellExecute = true,
};
Process.Start(psi);
var res = MessageBoxW(
default,
(ushort*)lpText,
(ushort*)lpCaption,
MB.MB_YESNO | MB.MB_TOPMOST | MB.MB_ICONERROR);
if (res == IDYES)
{
var psi = new ProcessStartInfo
{
FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe",
UseShellExecute = true,
};
Process.Start(psi);
}
}
}
Environment.Exit(-1);
@ -636,7 +636,8 @@ internal partial class InterfaceManager : IInternalDisposableService
var startInfo = Service<Dalamud>.Get().StartInfo;
var configuration = Service<DalamudConfiguration>.Get();
var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath)!, "dalamudUI.ini"));
var iniFileInfo = new FileInfo(
Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath)!, "dalamudUI.ini"));
try
{
@ -659,16 +660,18 @@ internal partial class InterfaceManager : IInternalDisposableService
Log.Error(ex, "Could not delete dalamudUI.ini");
}
newScene.UpdateCursor = this.isOverrideGameCursor;
newScene.ImGuiIniPath = iniFileInfo.FullName;
newScene.OnBuildUI += this.Display;
newScene.OnNewInputFrame += this.OnNewInputFrame;
newBackend.UpdateCursor = this.isOverrideGameCursor;
newBackend.IniPath = iniFileInfo.FullName;
newBackend.BuildUi += this.Display;
newBackend.NewInputFrame += this.OnNewInputFrame;
StyleModel.TransferOldModels();
if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name))
if (configuration.SavedStyles == null ||
configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name))
{
configuration.SavedStyles = new List<StyleModel> { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic };
configuration.SavedStyles = new List<StyleModel>
{ StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic };
configuration.ChosenStyle = StyleModelV1.DalamudStandard.Name;
}
else if (configuration.SavedStyles.Count == 1)
@ -724,16 +727,16 @@ internal partial class InterfaceManager : IInternalDisposableService
Log.Information("[IM] Scene & ImGui setup OK!");
}
this.scene = newScene;
this.backend = newBackend;
Service<InterfaceManagerWithScene>.Provide(new(this));
this.wndProcHookManager.PreWndProc += this.WndProcHookManagerOnPreWndProc;
return newScene;
return newBackend;
}
private unsafe void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
private void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
{
var r = this.scene?.ProcessWndProcW(args.Hwnd, (User32.WindowMessage)args.Message, args.WParam, args.LParam);
var r = this.backend?.ProcessWndProcW(args.Hwnd, args.Message, args.WParam, args.LParam);
if (r is not null)
args.SuppressWithValue(r.Value);
}
@ -790,7 +793,7 @@ internal partial class InterfaceManager : IInternalDisposableService
new()
{
SizePx = Service<FontAtlasFactory>.Get().DefaultFontSpec.SizePx,
GlyphRanges = new ushort[] { 0x20, 0x20, 0x00 },
GlyphRanges = [0x20, 0x20, 0x00],
})));
this.MonoFontHandle = (FontHandle)this.dalamudAtlas.NewDelegateFontHandle(
e => e.OnPreBuild(
@ -1055,13 +1058,13 @@ internal partial class InterfaceManager : IInternalDisposableService
this.dxgiSwapChainHook?.Enable();
}
private IntPtr SetCursorDetour(IntPtr hCursor)
private nint SetCursorDetour(nint hCursor)
{
if (this.lastWantCapture && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
return IntPtr.Zero;
if (this.lastWantCapture && (!this.backend?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
return default;
return this.setCursorHook?.IsDisposed is not false
? User32.SetCursor(new(hCursor, false)).DangerousGetHandle()
? SetCursor((HCURSOR)hCursor)
: this.setCursorHook.Original(hCursor);
}

View file

@ -9,7 +9,6 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using Dalamud.Logging.Internal;
@ -310,7 +309,7 @@ internal sealed partial class FontAtlasFactory
throw;
}
this.factory.SceneTask.ContinueWith(
this.factory.BackendTask.ContinueWith(
r =>
{
lock (this.syncRoot)
@ -318,8 +317,8 @@ internal sealed partial class FontAtlasFactory
if (this.disposed)
return;
r.Result.OnNewRenderFrame += this.ImGuiSceneOnNewRenderFrame;
this.disposables.Add(() => r.Result.OnNewRenderFrame -= this.ImGuiSceneOnNewRenderFrame);
r.Result.NewRenderFrame += this.ImGuiSceneOnNewRenderFrame;
this.disposables.Add(() => r.Result.NewRenderFrame -= this.ImGuiSceneOnNewRenderFrame);
}
if (this.AutoRebuildMode == FontAtlasAutoRebuildMode.OnNewFrame)
@ -735,7 +734,7 @@ internal sealed partial class FontAtlasFactory
foreach (var font in toolkit.Fonts)
toolkit.BuildLookupTable(font);
if (this.factory.SceneTask is { IsCompleted: false } sceneTask)
if (this.factory.BackendTask is { IsCompleted: false } backendTask)
{
Log.Verbose(
"[{name}:{functionname}] 0x{ptr:X}: await SceneTask (at {sw}ms)",
@ -743,7 +742,7 @@ internal sealed partial class FontAtlasFactory
nameof(this.RebuildFontsPrivateReal),
atlasPtr,
sw.ElapsedMilliseconds);
await sceneTask.ConfigureAwait(!isAsync);
await backendTask.ConfigureAwait(!isAsync);
}
#if VeryVerboseLog

View file

@ -10,6 +10,7 @@ using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.ImGuiBackend;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal;
using Dalamud.Interface.Textures.TextureWraps;
@ -19,8 +20,6 @@ using Dalamud.Utility;
using ImGuiNET;
using ImGuiScene;
using Lumina.Data.Files;
using TerraFX.Interop.DirectX;
@ -52,9 +51,9 @@ internal sealed partial class FontAtlasFactory
this.Framework = framework;
this.InterfaceManager = interfaceManager;
this.dalamudAssetManager = dalamudAssetManager;
this.SceneTask = Service<InterfaceManager.InterfaceManagerWithScene>
this.BackendTask = Service<InterfaceManager.InterfaceManagerWithScene>
.GetAsync()
.ContinueWith(r => r.Result.Manager.Scene);
.ContinueWith(r => r.Result.Manager.Backend);
var gffasInfo = Enum.GetValues<GameFontFamilyAndSize>()
.Select(
@ -141,7 +140,7 @@ internal sealed partial class FontAtlasFactory
/// <summary>
/// Gets the service instance of <see cref="InterfaceManager"/>.<br />
/// <see cref="Internal.InterfaceManager.Scene"/> may not yet be available.
/// <see cref="Internal.InterfaceManager.Backend"/> may not yet be available.
/// </summary>
public InterfaceManager InterfaceManager { get; }
@ -151,9 +150,9 @@ internal sealed partial class FontAtlasFactory
public TextureManager TextureManager => Service<TextureManager>.Get();
/// <summary>
/// Gets the async task for <see cref="RawDX11Scene"/> inside <see cref="InterfaceManager"/>.
/// Gets the async task for <see cref="IImGuiBackend"/> inside <see cref="InterfaceManager"/>.
/// </summary>
public Task<RawDX11Scene> SceneTask { get; }
public Task<IImGuiBackend> BackendTask { get; }
/// <summary>
/// Gets the default glyph ranges (glyph ranges of <see cref="GameFontFamilyAndSize.Axis12"/>).

View file

@ -60,7 +60,7 @@ internal sealed partial class TextureManager
/// <param name="device">The device.</param>
public void Setup(ID3D11Device* device)
{
var assembly = typeof(ImGuiScene.ImGui_Impl_DX11).Assembly;
var assembly = typeof(SimpleDrawerImpl).Assembly;
// Create the vertex shader
if (this.vertexShader.IsEmpty() || this.inputLayout.IsEmpty())

View file

@ -58,7 +58,7 @@ internal sealed partial class TextureManager
private unsafe TextureManager(InterfaceManager.InterfaceManagerWithScene withScene)
{
using var failsafe = new DisposeSafety.ScopedFinalizer();
failsafe.Add(this.device = new((ID3D11Device*)withScene.Manager.Device!.NativePointer));
failsafe.Add(this.device = new((ID3D11Device*)withScene.Manager.Backend!.DeviceHandle));
failsafe.Add(this.dynamicPriorityTextureLoader = new(Math.Max(1, Environment.ProcessorCount - 1)));
failsafe.Add(this.sharedTextureManager = new(this));
failsafe.Add(this.wicManager = new(this));

View file

@ -18,8 +18,6 @@ using ImGuiNET;
using Serilog;
using SharpDX.Direct3D11;
namespace Dalamud.Interface;
/// <summary>
@ -119,12 +117,22 @@ public interface IUiBuilder
/// <summary>
/// Gets the game's active Direct3D device.
/// </summary>
Device Device { get; }
// TODO: Remove it on API11/APIXI, and remove SharpDX/PInvoke/etc. dependency from Dalamud.
[Obsolete($"Use {nameof(DeviceHandle)} and wrap it using DirectX wrapper library of your choice.")]
SharpDX.Direct3D11.Device Device { get; }
/// <summary>
/// Gets the game's main window handle.
/// </summary>
IntPtr WindowHandlePtr { get; }
/// <summary>Gets the game's active Direct3D device.</summary>
/// <value>Pointer to the instance of IUnknown that the game is using and should be containing an ID3D11Device,
/// or 0 if it is not available yet.</value>
/// <remarks>Use
/// <a href="https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(q)">
/// QueryInterface</a> with IID of <c>IID_ID3D11Device</c> if you want to ensure that the interface type contained
/// within is indeed an instance of ID3D11Device.</remarks>
nint DeviceHandle { get; }
/// <summary>Gets the game's main window handle.</summary>
/// <value>HWND of the main game window, or 0 if it is not available yet.</value>
nint WindowHandlePtr { get; }
/// <summary>
/// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden.
@ -258,6 +266,8 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
private IFontHandle? monoFontHandle;
private IFontHandle? iconFontFixedWidthHandle;
private SharpDX.Direct3D11.Device? sdxDevice;
/// <summary>
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
/// You do not have to call this manually.
@ -414,15 +424,17 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
this.InterfaceManagerWithScene?.MonoFontHandle
?? throw new InvalidOperationException("Scene is not yet ready.")));
/// <summary>
/// Gets the game's active Direct3D device.
/// </summary>
public Device Device => this.InterfaceManagerWithScene!.Device!;
/// <inheritdoc/>
// TODO: Remove it on API11/APIXI, and remove SharpDX/PInvoke/etc. dependency from Dalamud.
[Obsolete($"Use {nameof(DeviceHandle)} and wrap it using DirectX wrapper library of your choice.")]
public SharpDX.Direct3D11.Device Device =>
this.sdxDevice ??= new(this.InterfaceManagerWithScene!.Backend!.DeviceHandle);
/// <summary>
/// Gets the game's main window handle.
/// </summary>
public IntPtr WindowHandlePtr => this.InterfaceManagerWithScene!.WindowHandlePtr;
/// <inheritdoc/>
public nint DeviceHandle => this.InterfaceManagerWithScene?.Backend?.DeviceHandle ?? 0;
/// <inheritdoc/>
public nint WindowHandlePtr => this.InterfaceManagerWithScene is { } imws ? imws.GameWindowHandle : 0;
/// <summary>
/// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden.

View file

@ -10,6 +10,7 @@ using System.Text.Unicode;
using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.ImGuiBackend.InputHandler;
using Dalamud.Interface.ImGuiSeStringRenderer;
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
using Dalamud.Interface.ManagedFontAtlas;
@ -17,14 +18,15 @@ using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET;
using ImGuiScene;
using VirtualKey = Dalamud.Game.ClientState.Keys.VirtualKey;
namespace Dalamud.Interface.Utility;
/// <summary>
/// Class containing various helper methods for use with ImGui inside Dalamud.
/// </summary>
public static class ImGuiHelpers
public static partial class ImGuiHelpers
{
/// <summary>
/// Gets the main viewport.
@ -111,7 +113,7 @@ public static class ImGuiHelpers
/// </summary>
/// <param name="size">The size of the indent.</param>
public static void ScaledIndent(float size) => ImGui.Indent(size * GlobalScale);
/// <summary>
/// Use a relative ImGui.SameLine() from your current cursor position, scaled by the Dalamud global scale.
/// </summary>
@ -286,7 +288,7 @@ public static class ImGuiHelpers
foreach (ref var kp in new Span<ImFontKerningPair>((void*)font->KerningPairs.Data, font->KerningPairs.Size))
kp.AdvanceXAdjustment = rounder(kp.AdvanceXAdjustment * scale);
foreach (ref var fkp in new Span<float>((void*)font->FrequentKerningPairs.Data, font->FrequentKerningPairs.Size))
fkp = rounder(fkp * scale);
}
@ -425,7 +427,7 @@ public static class ImGuiHelpers
/// <returns>The ImGuiKey that corresponds to this VirtualKey, or <c>ImGuiKey.None</c> otherwise.</returns>
public static ImGuiKey VirtualKeyToImGuiKey(VirtualKey key)
{
return ImGui_Input_Impl_Direct.VirtualKeyToImGuiKey((int)key);
return Win32InputHandler.VirtualKeyToImGuiKey((int)key);
}
/// <summary>
@ -435,7 +437,7 @@ public static class ImGuiHelpers
/// <returns>The VirtualKey that corresponds to this ImGuiKey, or <c>VirtualKey.NO_KEY</c> otherwise.</returns>
public static VirtualKey ImGuiKeyToVirtualKey(ImGuiKey key)
{
return (VirtualKey)ImGui_Input_Impl_Direct.ImGuiKeyToVirtualKey(key);
return (VirtualKey)Win32InputHandler.ImGuiKeyToVirtualKey(key);
}
/// <summary>
@ -535,7 +537,7 @@ public static class ImGuiHelpers
builder.BuildRanges(out var vec);
return new ReadOnlySpan<ushort>((void*)vec.Data, vec.Size).ToArray();
}
/// <inheritdoc cref="CreateImGuiRangesFrom(IEnumerable{UnicodeRange})"/>
public static ushort[] CreateImGuiRangesFrom(params UnicodeRange[] ranges)
=> CreateImGuiRangesFrom((IEnumerable<UnicodeRange>)ranges);
@ -618,7 +620,7 @@ public static class ImGuiHelpers
ImGuiNative.ImGuiInputTextCallbackData_InsertChars(data, 0, pBuf, pBuf + len);
ImGuiNative.ImGuiInputTextCallbackData_SelectAll(data);
}
/// <summary>
/// Finds the corresponding ImGui viewport ID for the given window handle.
/// </summary>
@ -639,6 +641,12 @@ public static class ImGuiHelpers
return -1;
}
/// <summary>
/// Clears the stack in the current ImGui context.
/// </summary>
[LibraryImport("cimgui", EntryPoint = "igCustom_ClearStacks")]
internal static partial void ClearStacksOnContext();
/// <summary>
/// Attempts to validate that <paramref name="fontPtr"/> is valid.
/// </summary>

View file

@ -1,10 +1,12 @@
using System.Runtime.CompilerServices;
using Dalamud.Interface.ImGuiBackend.Renderers;
using Dalamud.Memory;
using ImGuiScene;
using Lumina.Data.Files;
using TerraFX.Interop.DirectX;
namespace Dalamud.Utility;
/// <summary>
@ -13,7 +15,9 @@ namespace Dalamud.Utility;
public static class TexFileExtensions
{
/// <summary>
/// Returns the image data formatted for <see cref="RawDX11Scene.LoadImageRaw"/>.
/// Returns the image data formatted for <see cref="IImGuiRenderer.CreateTexture2D"/>,
/// using <see cref="DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM"/>.<br />
/// <b>Consider using <see cref="TexFile.ImageData"/> with <see cref="DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM"/>.</b>
/// </summary>
/// <param name="texFile">The TexFile to format.</param>
/// <returns>The formatted image data.</returns>

1
lib/ImGui.NET Submodule

@ -0,0 +1 @@
Subproject commit 98304cfd0bf86cf176732d60eb5dba6fc351f737

@ -1 +0,0 @@
Subproject commit d336b20a85ea48723a98681b18bdfe14a56a3403