Skip to content

Commit

Permalink
Merge pull request #1973 from Soreepeong/reshade-addon
Browse files Browse the repository at this point in the history
add back DXGISwapChain::on_present hook as default for now
  • Loading branch information
goaaats authored Jul 25, 2024
2 parents 79971fc + f077710 commit 9298e85
Show file tree
Hide file tree
Showing 9 changed files with 399 additions and 188 deletions.
2 changes: 1 addition & 1 deletion Dalamud/Configuration/Internal/DalamudConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ public string EffectiveLanguage
public bool WindowIsImmersive { get; set; } = false;

/// <summary>Gets or sets the mode specifying how to handle ReShade.</summary>
public ReShadeHandlingMode ReShadeHandlingMode { get; set; } = ReShadeHandlingMode.ReShadeAddon;
public ReShadeHandlingMode ReShadeHandlingMode { get; set; } = ReShadeHandlingMode.Default;

/// <summary>Gets or sets the swap chain hook mode.</summary>
public SwapChainHelper.HookMode SwapChainHookMode { get; set; } = SwapChainHelper.HookMode.ByteCode;
Expand Down
9 changes: 9 additions & 0 deletions Dalamud/Hooking/Internal/ObjectVTableHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ public ObjectVTableHook(void* ppVtbl, int numMethods)
/// <summary>Gets the span view of overriden vtable.</summary>
public ReadOnlySpan<nint> OverridenVTableSpan => this.vtblOverriden.AsSpan();

/// <summary>Gets the address of the pointer to the vtable.</summary>
public nint Address => (nint)this.ppVtbl;

/// <summary>Gets the address of the original vtable.</summary>
public nint OriginalVTableAddress => (nint)this.pVtblOriginal;

/// <summary>Gets the address of the overriden vtable.</summary>
public nint OverridenVTableAddress => (nint)Unsafe.AsPointer(ref this.vtblOverriden[0]);

/// <summary>Disables the hook.</summary>
public void Disable()
{
Expand Down
142 changes: 104 additions & 38 deletions Dalamud/Interface/Internal/InterfaceManager.AsHook.cs
Original file line number Diff line number Diff line change
@@ -1,75 +1,141 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

using Dalamud.Utility;

using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;

namespace Dalamud.Interface.Internal;

/// <summary>
/// This class manages interaction with the ImGui interface.
/// </summary>
internal partial class InterfaceManager
internal unsafe partial class InterfaceManager
{
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
// NOTE: Do not use HRESULT as return value type. It appears that .NET marshaller thinks HRESULT needs to be still
// treated as a type that does not fit into RAX.

/// <summary>Delegate for <c>DXGISwapChain::on_present(UINT flags, const DXGI_PRESENT_PARAMETERS *params)</c> in
/// <c>dxgi_swapchain.cpp</c>.</summary>
/// <param name="swapChain">Pointer to an instance of <c>DXGISwapChain</c>, which happens to be an
/// <see cref="IDXGISwapChain"/>.</param>
/// <param name="flags">An integer value that contains swap-chain presentation options. These options are defined by
/// the <c>DXGI_PRESENT</c> constants.</param>
/// <param name="presentParams">Optional; DXGI present parameters.</param>
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void ReShadeDxgiSwapChainPresentDelegate(
ReShadeDxgiSwapChain* swapChain,
uint flags,
DXGI_PRESENT_PARAMETERS* presentParams);

/// <summary>Delegate for <see cref="IDXGISwapChain.Present"/>.
/// <a href="https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-present">Microsoft
/// Learn</a>.</summary>
/// <param name="swapChain">Pointer to an instance of <see cref="IDXGISwapChain"/>.</param>
/// <param name="syncInterval">An integer that specifies how to synchronize presentation of a frame with the
/// vertical blank.</param>
/// <param name="flags">An integer value that contains swap-chain presentation options. These options are defined by
/// the <c>DXGI_PRESENT</c> constants.</param>
/// <returns>A <see cref="HRESULT"/> representing the result of the operation.</returns>
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate int DxgiSwapChainPresentDelegate(IDXGISwapChain* swapChain, uint syncInterval, uint flags);

/// <summary>Detour function for <see cref="IDXGISwapChain.ResizeBuffers"/>.
/// <a href="https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-resizebuffers">
/// Microsoft Learn</a>.</summary>
/// <param name="swapChain">Pointer to an instance of <see cref="IDXGISwapChain"/>.</param>
/// <param name="bufferCount">The number of buffers in the swap chain (including all back and front buffers).
/// This number can be different from the number of buffers with which you created the swap chain. This number
/// can't be greater than <see cref="DXGI.DXGI_MAX_SWAP_CHAIN_BUFFERS"/>. Set this number to zero to preserve the
/// existing number of buffers in the swap chain. You can't specify less than two buffers for the flip presentation
/// model.</param>
/// <param name="width">The new width of the back buffer. If you specify zero, DXGI will use the width of the client
/// area of the target window. You can't specify the width as zero if you called the
/// <see cref="IDXGIFactory2.CreateSwapChainForComposition"/> method to create the swap chain for a composition
/// surface.</param>
/// <param name="height">The new height of the back buffer. If you specify zero, DXGI will use the height of the
/// client area of the target window. You can't specify the height as zero if you called the
/// <see cref="IDXGIFactory2.CreateSwapChainForComposition"/> method to create the swap chain for a composition
/// surface.</param>
/// <param name="newFormat">A DXGI_FORMAT-typed value for the new format of the back buffer. Set this value to
/// <see cref="DXGI_FORMAT.DXGI_FORMAT_UNKNOWN"/> to preserve the existing format of the back buffer. The flip
/// presentation model supports a more restricted set of formats than the bit-block transfer (bitblt) model.</param>
/// <param name="swapChainFlags">A combination of <see cref="DXGI_SWAP_CHAIN_FLAG"/>-typed values that are combined
/// by using a bitwise OR operation. The resulting value specifies options for swap-chain behavior.</param>
/// <returns>A <see cref="HRESULT"/> representing the result of the operation.</returns>
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate int ResizeBuffersDelegate(
IDXGISwapChain* swapChain,
uint bufferCount,
uint width,
uint height,
DXGI_FORMAT newFormat,
uint swapChainFlags);

private void ReShadeDxgiSwapChainOnPresentDetour(
ReShadeDxgiSwapChain* swapChain,
uint flags,
DXGI_PRESENT_PARAMETERS* presentParams)
{
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);

Debug.Assert(this.dxgiPresentHook is not null, "How did PresentDetour get called when presentHook is null?");
Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already");

if (this.scene == null)
this.InitScene(swapChain);

Debug.Assert(this.scene is not null, "InitScene did not set the scene field, but did not throw an exception.");
Debug.Assert(
this.reShadeDxgiSwapChainPresentHook is not null,
"this.reShadeDxgiSwapChainPresentHook is not null");

if (!this.dalamudAtlas!.HasBuiltAtlas)
{
if (this.dalamudAtlas.BuildTask.Exception != null)
{
// TODO: Can we do something more user-friendly here? Unload instead?
Log.Error(this.dalamudAtlas.BuildTask.Exception, "Failed to initialize Dalamud base fonts");
Util.Fatal("Failed to initialize Dalamud base fonts.\nPlease report this error.", "Dalamud");
}
if (this.RenderDalamudCheckAndInitialize(swapChain->AsIDxgiSwapChain(), flags) is { } activeScene)
this.RenderDalamudDraw(activeScene);

return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
}
this.reShadeDxgiSwapChainPresentHook!.Original(swapChain, flags, presentParams);

this.CumulativePresentCalls++;
this.IsMainThreadInPresent = true;
// Upstream call to system IDXGISwapChain::Present will be called by ReShade.
}

while (this.runBeforeImGuiRender.TryDequeue(out var action))
action.InvokeSafely();
private int DxgiSwapChainPresentDetour(IDXGISwapChain* swapChain, uint syncInterval, uint flags)
{
Debug.Assert(this.dxgiSwapChainPresentHook is not null, "this.dxgiSwapChainPresentHook is not null");

RenderImGui(this.scene!);
this.PostImGuiRender();
this.IsMainThreadInPresent = false;
if (this.RenderDalamudCheckAndInitialize(swapChain, flags) is { } activeScene)
this.RenderDalamudDraw(activeScene);

return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
return this.dxgiSwapChainPresentHook!.Original(swapChain, syncInterval, flags);
}

private IntPtr AsHookResizeBuffersDetour(
IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
private int AsHookDxgiSwapChainResizeBuffersDetour(
IDXGISwapChain* swapChain,
uint bufferCount,
uint width,
uint height,
DXGI_FORMAT newFormat,
uint swapChainFlags)
{
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
return this.dxgiSwapChainResizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);

#if DEBUG
Log.Verbose(
$"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}");
$"Calling resizebuffers swap@{(nint)swapChain:X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}");
#endif

this.ResizeBuffers?.InvokeSafely();

this.scene?.OnPreResize();

var ret = this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
if (ret.ToInt64() == 0x887A0001)
{
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);

return ret;
}

/// <summary>Represents <c>DXGISwapChain</c> in ReShade.</summary>
[StructLayout(LayoutKind.Sequential)]
private struct ReShadeDxgiSwapChain
{
// DXGISwapChain only implements IDXGISwapChain4. The only vtable should be that.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IDXGISwapChain* AsIDxgiSwapChain() => (IDXGISwapChain*)Unsafe.AsPointer(ref this);
}
}
55 changes: 17 additions & 38 deletions Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Diagnostics;

using Dalamud.Interface.Internal.ReShadeHandling;
using Dalamud.Utility;

Expand All @@ -11,9 +9,9 @@ namespace Dalamud.Interface.Internal;
/// <summary>
/// This class manages interaction with the ImGui interface.
/// </summary>
internal partial class InterfaceManager
internal unsafe partial class InterfaceManager
{
private unsafe void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
private void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
{
var swapChainNative = swapChain.GetNative<IDXGISwapChain>();
if (this.scene?.SwapChain.NativePointer != (nint)swapChainNative)
Expand All @@ -22,7 +20,7 @@ private unsafe void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInte
this.scene?.OnPreResize();
}

private unsafe void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
private void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
{
var swapChainNative = swapChain.GetNative<IDXGISwapChain>();
if (this.scene?.SwapChain.NativePointer != (nint)swapChainNative)
Expand All @@ -42,52 +40,33 @@ private void ReShadeAddonInterfaceOnPresent(
ReadOnlySpan<RECT> destRect,
ReadOnlySpan<RECT> dirtyRects)
{
var swapChainNative = swapChain.GetNative();

if (this.scene == null)
this.InitScene(swapChainNative);

if (this.scene?.SwapChain.NativePointer != swapChainNative)
return;

Debug.Assert(this.dalamudAtlas is not null, "this.dalamudAtlas is not null");

if (!this.dalamudAtlas!.HasBuiltAtlas)
{
if (this.dalamudAtlas.BuildTask.Exception != null)
{
// TODO: Can we do something more user-friendly here? Unload instead?
Log.Error(this.dalamudAtlas.BuildTask.Exception, "Failed to initialize Dalamud base fonts");
Util.Fatal("Failed to initialize Dalamud base fonts.\nPlease report this error.", "Dalamud");
}

return;
}
var swapChainNative = swapChain.GetNative<IDXGISwapChain>();

this.CumulativePresentCalls++;
this.IsMainThreadInPresent = true;
if (this.RenderDalamudCheckAndInitialize(swapChainNative, 0) is { } activeScene)
this.RenderDalamudDraw(activeScene);
}

while (this.runBeforeImGuiRender.TryDequeue(out var action))
action.InvokeSafely();
private void ReShadeAddonInterfaceOnReShadeOverlay(ref ReShadeAddonInterface.ApiObject runtime)
{
var swapChainNative = runtime.GetNative<IDXGISwapChain>();

RenderImGui(this.scene!);
this.PostImGuiRender();
this.IsMainThreadInPresent = false;
if (this.RenderDalamudCheckAndInitialize(swapChainNative, 0) is { } activeScene)
this.RenderDalamudDraw(activeScene);
}

private nint AsReShadeAddonResizeBuffersDetour(
nint swapChain,
private int AsReShadeAddonDxgiSwapChainResizeBuffersDetour(
IDXGISwapChain* swapChain,
uint bufferCount,
uint width,
uint height,
uint newFormat,
DXGI_FORMAT newFormat,
uint swapChainFlags)
{
// Hooked vtbl instead of registering ReShade event. This check is correct.
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
return this.dxgiSwapChainResizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);

this.ResizeBuffers?.InvokeSafely();
return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
return this.dxgiSwapChainResizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
}
}
Loading

0 comments on commit 9298e85

Please sign in to comment.