game: Implement mip gen and point scaling

This commit is contained in:
Jade Macho 2025-07-07 23:40:33 +02:00
parent 51f2cd8348
commit 50ad7cff36
Signed by: 0x0ade
GPG key ID: E1960710FE4FBEEF
8 changed files with 267 additions and 78 deletions

View file

@ -117,16 +117,27 @@ public static class MinSizeModeExt
public enum ResolutionScalingMode public enum ResolutionScalingMode
{ {
Fast = 0, Point = 0,
FSR = 1, Linear = 1,
DLSS = 2 FSR = 2,
DLSS = 3
} }
public static class ResolutionScalingModeExt public static class ResolutionScalingModeExt
{ {
public static byte ToXIV(this ResolutionScalingMode mode) => mode switch
{
ResolutionScalingMode.Point => 0,
ResolutionScalingMode.Linear => 0,
ResolutionScalingMode.FSR => 1,
ResolutionScalingMode.DLSS => 2,
_ => 0
};
public static string ToHumanNameString(this ResolutionScalingMode mode) => mode switch public static string ToHumanNameString(this ResolutionScalingMode mode) => mode switch
{ {
ResolutionScalingMode.Fast => "Fast Pixelation", ResolutionScalingMode.Point => "Pixelated",
ResolutionScalingMode.Linear => "Blurry",
ResolutionScalingMode.FSR => "AMD FSR", ResolutionScalingMode.FSR => "AMD FSR",
ResolutionScalingMode.DLSS => "NVIDIA DLSS", ResolutionScalingMode.DLSS => "NVIDIA DLSS",
_ => mode.ToString(), _ => mode.ToString(),
@ -134,7 +145,8 @@ public static class ResolutionScalingModeExt
public static string? ToHumanInfoString(this ResolutionScalingMode mode) => mode switch public static string? ToHumanInfoString(this ResolutionScalingMode mode) => mode switch
{ {
ResolutionScalingMode.Fast => "Lowest quality option which results in blurry pixelation.", ResolutionScalingMode.Point => "Lowest quality option which results in pixelation.",
ResolutionScalingMode.Linear => "Low quality option which results in blur.",
ResolutionScalingMode.FSR => "Upscaling which works on any GPU for low performance overhead.", ResolutionScalingMode.FSR => "Upscaling which works on any GPU for low performance overhead.",
ResolutionScalingMode.DLSS => "DLSS in FFXIV is buggy, even without any plugins.", ResolutionScalingMode.DLSS => "DLSS in FFXIV is buggy, even without any plugins.",
_ => null _ => null

View file

@ -19,6 +19,7 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<RootNamespace>CustomResolution</RootNamespace> <RootNamespace>CustomResolution</RootNamespace>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
@ -30,7 +31,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DalamudPackager" Version="12.0.0" /> <PackageReference Include="DalamudPackager" Version="12.0.0" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
<Reference Include="FFXIVClientStructs"> <Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath> <HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private> <Private>false</Private>
@ -71,9 +71,11 @@
<HintPath>$(DalamudLibPath)SharpDX.Mathematics.dll</HintPath> <HintPath>$(DalamudLibPath)SharpDX.Mathematics.dll</HintPath>
<Private>false</Private> <Private>false</Private>
</Reference> </Reference>
<PackageReference Include="SharpDX.D3DCompiler" Version="4.2.0" /> <Reference Include="TerraFX.Interop.Windows">
<HintPath>$(DalamudLibPath)TerraFX.Interop.Windows.dll</HintPath>
<Private>false</Private>
</Reference>
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" /> <PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageReference Include="SharpDX.Direct2D1" Version="4.2.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -6,6 +6,7 @@ using FloppyUtils;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows; using static TerraFX.Interop.Windows.Windows;
@ -421,13 +422,14 @@ public unsafe struct DeviceEx
public ref uint NewHeight => ref _.NewHeight; public ref uint NewHeight => ref _.NewHeight;
// C# doesn't let us use ptr types as generic arguments, oh well. // C# doesn't let us use ptr types as generic arguments, oh well.
public readonly ID3D11DeviceContext* D3D11DeviceContext => (ID3D11DeviceContext*) _.D3D11DeviceContext;
public readonly ImmediateContextEx* ImmediateContext => (ImmediateContextEx*) _.ImmediateContext; public readonly ImmediateContextEx* ImmediateContext => (ImmediateContextEx*) _.ImmediateContext;
} }
// ImmediateContextEx in FFXIVClientStructs is a bare minimum. // ImmediateContextEx in FFXIVClientStructs is a bare minimum.
// https://github.com/aers/FFXIVClientStructs/blob/ef5e9a5997671fb2c9a72cb9d57d841855f62085/FFXIVClientStructs/FFXIV/Client/Graphics/Kernel/ImmediateContext.cs // https://github.com/aers/FFXIVClientStructs/blob/ef5e9a5997671fb2c9a72cb9d57d841855f62085/FFXIVClientStructs/FFXIV/Client/Graphics/Kernel/ImmediateContext.cs
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public struct ImmediateContextEx public unsafe struct ImmediateContextEx
{ {
[FieldOffset(0)] [FieldOffset(0)]
public ImmediateContext _; public ImmediateContext _;

View file

@ -4,10 +4,13 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FloppyUtils; using FloppyUtils;
using SharpDX;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using TerraFX.Interop.Windows; using TerraFX.Interop.DirectX;
using Device = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device;
using SDX11 = SharpDX.Direct3D11;
namespace CustomResolution; namespace CustomResolution;
@ -17,17 +20,44 @@ public unsafe class GameSizeState : IDisposable
private Hook<RTMApplyScaling> _rtmApplyScalingHook; private Hook<RTMApplyScaling> _rtmApplyScalingHook;
private Hook<RTMDestroyAfterResize> _rtmDestroyAfterResizeHook; private Hook<RTMDestroyAfterResize> _rtmDestroyAfterResizeHook;
private Hook<RTMRegenAfterResize> _rtmRegenAfterResizeHook; private Hook<RTMRegenAfterResize> _rtmRegenAfterResizeHook;
private Hook<ICDX11ProcessCommands> _icdx11ProcessCommandsHook; private Hook<ImmediateProcessCommands> _immediateProcessCommandsHook;
private Hook<DDX11PostTick> _ddx11PostTickHook; private Hook<DDX11PostTick> _ddx11PostTickHook;
private Hook<TaskRenderGraphicsRender> _taskRenderGraphicsRenderHook; private Hook<TaskRenderGraphicsRender> _taskRenderGraphicsRenderHook;
private Hook<PrepareTexture> _prepareTextureHook;
private Hook<ImmediateBindPSSRVs> _immediateBindPSSRVsHook;
private Hook<ImmediateBindCSSRVs> _immediateBindCSSRVsHook;
private Hook<CreateTexture2D> _createTexture2DHook;
private SDX11.DeviceContext _d3dctx;
private SDX11.Device _d3ddev;
private SDX11.SamplerState _samplerMips;
private SDX11.SamplerState _samplerPoint;
private bool _wasEnabled = false; private bool _wasEnabled = false;
private object _renderLock = new(); private object _renderLock = new();
private ForceUpdateRTMState _forceUpdateRTM = ForceUpdateRTMState._0_Idle; private ForceUpdateRTMState _forceUpdateRTM = ForceUpdateRTMState._0_Idle;
private uint _drawCount;
public GameSizeState() public GameSizeState()
{ {
var dev = (DeviceEx*) Device.Instance();
_d3dctx = CppObject.FromPointer<SDX11.DeviceContext>((nint) dev->D3D11DeviceContext);
_d3ddev = _d3dctx.Device;
var samplerDesc = SDX11.SamplerStateDescription.Default();
samplerDesc.Filter = SDX11.Filter.MinMagMipLinear;
samplerDesc.AddressU = SDX11.TextureAddressMode.Mirror;
samplerDesc.AddressV = SDX11.TextureAddressMode.Mirror;
samplerDesc.AddressW = SDX11.TextureAddressMode.Mirror;
_samplerMips = new(_d3ddev, samplerDesc);
samplerDesc.Filter = SDX11.Filter.MinMagMipPoint;
samplerDesc.MinimumLod = 0;
samplerDesc.MaximumLod = 0;
_samplerPoint = new(_d3ddev, samplerDesc);
/* Writes DynamicResolutionTargetHeight to size[1] near the end, /* Writes DynamicResolutionTargetHeight to size[1] near the end,
* but if in (gpose || main menu), only scale < 1?? * but if in (gpose || main menu), only scale < 1??
*/ */
@ -37,8 +67,6 @@ public unsafe class GameSizeState : IDisposable
); );
_rtmApplyScalingHook.Enable(); _rtmApplyScalingHook.Enable();
var dev = (DeviceEx*) Device.Instance();
/* Device.RequestResolutionChange and other triggers run some callbacks, namely /* Device.RequestResolutionChange and other triggers run some callbacks, namely
* (at the time of 7.2) 0x40 for RTM destruction and 0x48 for RTM reconstruction, * (at the time of 7.2) 0x40 for RTM destruction and 0x48 for RTM reconstruction,
* registered in RTM init and run in PostTick. * registered in RTM init and run in PostTick.
@ -70,11 +98,11 @@ public unsafe class GameSizeState : IDisposable
* with some other race conditions regarding the fields we modify. * with some other race conditions regarding the fields we modify.
* Let's wrap it in a C# lock and call it a day... * Let's wrap it in a C# lock and call it a day...
*/ */
_icdx11ProcessCommandsHook = Service.GameInteropProvider.HookFromAddress<ICDX11ProcessCommands>( _immediateProcessCommandsHook = Service.GameInteropProvider.HookFromAddress<ImmediateProcessCommands>(
Service.SigScanner.ScanText("48 89 5C 24 10 48 89 6C 24 18 56 57 41 56 48 83 EC 30 48 8B 01 41 8B F0 48 8B EA"), Service.SigScanner.ScanText("48 89 5C 24 10 48 89 6C 24 18 56 57 41 56 48 83 EC 30 48 8B 01 41 8B F0 48 8B EA"),
ICDX11ProcessCommandsDetour ImmediateProcessCommandsDetour
); );
_icdx11ProcessCommandsHook.Enable(); _immediateProcessCommandsHook.Enable();
_ddx11PostTickHook = Service.GameInteropProvider.HookFromAddress<DDX11PostTick>( _ddx11PostTickHook = Service.GameInteropProvider.HookFromAddress<DDX11PostTick>(
Service.SigScanner.ScanText("48 89 5C 24 10 48 89 6C 24 18 48 89 74 24 20 57 41 54 41 55 41 56 41 57 B8 30 43 00 00 ?? ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? 00 00 8B 15"), Service.SigScanner.ScanText("48 89 5C 24 10 48 89 6C 24 18 48 89 74 24 20 57 41 54 41 55 41 56 41 57 B8 30 43 00 00 ?? ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? 00 00 8B 15"),
@ -100,6 +128,38 @@ public unsafe class GameSizeState : IDisposable
TaskRenderGraphicsRenderDetour TaskRenderGraphicsRenderDetour
); );
_taskRenderGraphicsRenderHook.Enable(); _taskRenderGraphicsRenderHook.Enable();
/* Texture preparation used in a lot of places, but most notable for
* writing to the mip level count of the gameplay texture and also updating the D3D11 ptrs.
*/
_prepareTextureHook = Service.GameInteropProvider.HookFromAddress<PrepareTexture>(
Service.SigScanner.ScanText("40 53 48 83 EC 20 48 8B 02 48 8B D9 48 89 41 38 48 8B 02 48 89 41 40"),
PrepareTextureDetour
);
_prepareTextureHook.Enable();
/* The shader resource view of GameplayTexture gets read by these two.
*/
_immediateBindPSSRVsHook = Service.GameInteropProvider.HookFromAddress<ImmediateBindPSSRVs>(
Service.SigScanner.ScanText("48 8B C4 4C 89 40 18 48 89 48 08 53 56 48 83 EC 68 44 8B 5A 44 48 8D B1 E8 03 00 00 4C 89 68 D8"),
ImmediateBindPSSRVsDetour
);
_immediateBindPSSRVsHook.Enable();
_immediateBindCSSRVsHook = Service.GameInteropProvider.HookFromAddress<ImmediateBindCSSRVs>(
Service.SigScanner.ScanText("48 8B C4 4C 89 40 18 48 89 48 08 53 56 48 83 EC 68 44 8B 5A 44 48 8D B1 E8 13 00 00 4C 89 68 D8"),
ImmediateBindCSSRVsDetour
);
_immediateBindCSSRVsHook.Enable();
/* And because all that isn't bad enough:
* Hook ID3D11Device funcs based on their vtable indices, to allow mipmap generation,
* and to allow changing the sampler state before drawing.
*/
_createTexture2DHook = Service.GameInteropProvider.HookFromAddress<CreateTexture2D>(
((ID3D11Device*) _d3ddev.NativePointer)->lpVtbl[5],
CreateTexture2DDetour
);
_createTexture2DHook.Enable();
} }
public bool ConfigDynRezo { get; private set; } public bool ConfigDynRezo { get; private set; }
@ -121,7 +181,7 @@ public unsafe class GameSizeState : IDisposable
private delegate void RTMRegenAfterResize(); private delegate void RTMRegenAfterResize();
// [UnmanagedFunctionPointer(CallingConvention.FastCall)] // [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void ICDX11ProcessCommands(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count); private delegate void ImmediateProcessCommands(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count);
// [UnmanagedFunctionPointer(CallingConvention.FastCall)] // [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void DDX11PostTick(DeviceEx* dev); private delegate void DDX11PostTick(DeviceEx* dev);
@ -129,14 +189,37 @@ public unsafe class GameSizeState : IDisposable
// [UnmanagedFunctionPointer(CallingConvention.FastCall)] // [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void TaskRenderGraphicsRender(); private delegate void TaskRenderGraphicsRender();
// [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate ulong PrepareTexture(Texture* tex, uint* size, byte mips, uint format);
// [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void ImmediateBindPSSRVs(ImmediateContextEx* im, PixelShader* ps, ImmediateSRV* data);
// [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void ImmediateBindCSSRVs(ImmediateContextEx* im, Shader* cs, ImmediateSRV* data);
private delegate int CreateTexture2D(ID3D11Device* d3ddev, D3D11_TEXTURE2D_DESC* desc, D3D11_SUBRESOURCE_DATA* initialData, ID3D11Texture2D** tex);
private delegate void Draw(ID3D11DeviceContext* d3dctx, uint vertexCount, uint startVertexLocation);
public void Dispose() public void Dispose()
{ {
_rtmApplyScalingHook.Dispose(); _rtmApplyScalingHook.Dispose();
_rtmDestroyAfterResizeHook.Dispose(); _rtmDestroyAfterResizeHook.Dispose();
_rtmRegenAfterResizeHook.Dispose(); _rtmRegenAfterResizeHook.Dispose();
_icdx11ProcessCommandsHook.Dispose(); _immediateProcessCommandsHook.Dispose();
_ddx11PostTickHook.Dispose(); _ddx11PostTickHook.Dispose();
_taskRenderGraphicsRenderHook.Dispose(); _taskRenderGraphicsRenderHook.Dispose();
_prepareTextureHook.Dispose();
_immediateBindPSSRVsHook.Dispose();
_immediateBindCSSRVsHook.Dispose();
_createTexture2DHook.Dispose();
lock (_renderLock)
{
_samplerPoint.Dispose();
_samplerMips.Dispose();
}
_d3ddev.Dispose();
Service.Framework.RunOnFrameworkThread(Update); Service.Framework.RunOnFrameworkThread(Update);
} }
@ -157,10 +240,7 @@ public unsafe class GameSizeState : IDisposable
if (Service.DebugConfig.IsDebug) if (Service.DebugConfig.IsDebug)
{ {
_dbg.Append(@$"RTMApplyScaling 0x{(_rtmApplyScalingHook.IsDisposed ? null : _rtmApplyScalingHook.Address):X16} _dbg.Append(@$"DR !{gfx->DynamicRezoEnable} +{gfx->DynamicRezoEnableBeyond1} _{gfx->DynamicRezoEnableCutScene} ?{gfx->DynamicRezoEnableUnkx47} ?{gfx->DynamicRezoEnableUnkx48}
RTMRegenAfterResize 0x{(_rtmRegenAfterResizeHook.IsDisposed ? null : _rtmRegenAfterResizeHook.Address):X16}
ICDX11ProcessCommands 0x{(_icdx11ProcessCommandsHook.IsDisposed ? null : _icdx11ProcessCommandsHook.Address):X16}
DR !{gfx->DynamicRezoEnable} +{gfx->DynamicRezoEnableBeyond1} _{gfx->DynamicRezoEnableCutScene} ?{gfx->DynamicRezoEnableUnkx47} ?{gfx->DynamicRezoEnableUnkx48}
GR x{gfx->GraphicsRezoScale} ?{gfx->GraphicsRezoUnk1} _{gfx->GraphicsRezoUpscaleType} ?{gfx->GraphicsRezoUnk2} GR x{gfx->GraphicsRezoScale} ?{gfx->GraphicsRezoUnk1} _{gfx->GraphicsRezoUpscaleType} ?{gfx->GraphicsRezoUnk2}
DEV 0x{(long) (nint) dev:X16} DEV 0x{(long) (nint) dev:X16}
GFX 0x{(long) (nint) gfx:X16} GFX 0x{(long) (nint) gfx:X16}
@ -181,7 +261,7 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
gfx->DynamicRezoEnable = (byte) (ConfigDynRezo || enabled ? 1 : 0); gfx->DynamicRezoEnable = (byte) (ConfigDynRezo || enabled ? 1 : 0);
if (enabled) if (enabled)
{ {
gfx->GraphicsRezoUpscaleType = (byte) (cfg.Scale <= 1f ? Service.Config._.ResolutionScalingMode : ResolutionScalingMode.Fast); gfx->GraphicsRezoUpscaleType = cfg.Scale <= 1f ? Service.Config._.ResolutionScalingMode.ToXIV() : ResolutionScalingMode.Linear.ToXIV();
rtm->DynamicResolutionMinimumHeight = rtm->DynamicResolutionMaximumHeight; rtm->DynamicResolutionMinimumHeight = rtm->DynamicResolutionMaximumHeight;
} }
else else
@ -194,9 +274,8 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
var scale = MathF.Max(1f, enabled ? cfg.Scale : 1f); var scale = MathF.Max(1f, enabled ? cfg.Scale : 1f);
GetScaledWidthHeight(dev->Width, dev->Height, scale, out var widthS, out var heightS); GetScaledWidthHeight(dev->Width, dev->Height, scale, out var widthS, out var heightS);
// TODO: Figure out a more consistent way to get to the currently allocated size.
// Check if the backing RTs are the expected size. // Check if the backing RTs are the expected size.
var tex = rtm->_.Unk20[0].Value; var tex = rtm->GameplayTextureUnk1;
if (tex->AllocatedWidth != widthS || tex->AllocatedHeight != heightS) if (tex->AllocatedWidth != widthS || tex->AllocatedHeight != heightS)
{ {
if (!unloading) if (!unloading)
@ -274,7 +353,7 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
var gfx = (GraphicsConfigEx*) GraphicsConfig.Instance(); var gfx = (GraphicsConfigEx*) GraphicsConfig.Instance();
var _DynamicRezoEnableBeyond1 = gfx->DynamicRezoEnableBeyond1; var _DynamicRezoEnableBeyond1 = gfx->DynamicRezoEnableBeyond1;
gfx->DynamicRezoEnableBeyond1 = 1; gfx->DynamicRezoEnableBeyond1 = (byte) (cfg.Scale > 1f ? 1 : 0);
Service.PluginLog.Debug($"Applying scaling, before: {size[0]} {size[1]} {unk1}"); Service.PluginLog.Debug($"Applying scaling, before: {size[0]} {size[1]} {unk1}");
_rtmApplyScalingHook.OriginalDisposeSafe(rtm, size, unk1); _rtmApplyScalingHook.OriginalDisposeSafe(rtm, size, unk1);
@ -295,7 +374,7 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
Service.PluginLog.Debug($"Destroying RTM resources"); Service.PluginLog.Debug($"Destroying RTM resources");
byte rv = _rtmDestroyAfterResizeHook.OriginalDisposeSafe(); byte rv = _rtmDestroyAfterResizeHook.OriginalDisposeSafe();
Service.PluginLog.Debug($"After: 0x{(long) (nint) rtm->_.Unk20[0].Value->D3D11Texture2D:X16}"); Service.PluginLog.Debug($"After: {rv} 0x{(long) (nint) rtm->GameplayTextureUnk1->D3D11Texture2D:X16} 0x{(long) (nint) rtm->GameplayTextureUnk3->D3D11Texture2D:X16}");
return rv; return rv;
} }
@ -314,14 +393,17 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
using var scale = new ScaleState(); using var scale = new ScaleState();
Service.PluginLog.Debug($"Regenerating RTM resources: {dev->Width} x {dev->Height}"); Service.PluginLog.Debug($"Regenerating RTM resources: {dev->Width} x {dev->Height}");
_rtmRegenAfterResizeHook.OriginalDisposeSafe(); _rtmRegenAfterResizeHook.OriginalDisposeSafe();
Service.PluginLog.Debug($"After: 0x{(long) (nint) rtm->_.Unk20[0].Value->D3D11Texture2D:X16}"); Service.PluginLog.Debug($"After: 0x{(long) (nint) rtm->GameplayTextureUnk1->D3D11Texture2D:X16} 0x{(long) (nint) rtm->GameplayTextureUnk3->D3D11Texture2D:X16}");
} }
private void ICDX11ProcessCommandsDetour(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count) private void ImmediateProcessCommandsDetour(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count)
{ {
_drawCount = 0;
// Service.PluginLog.Debug("--- ImmediateProcessCommands ---");
lock (_renderLock) lock (_renderLock)
{ {
_icdx11ProcessCommandsHook.OriginalDisposeSafe(ctx, cmds, count); _immediateProcessCommandsHook.OriginalDisposeSafe(ctx, cmds, count);
} }
} }
@ -346,6 +428,112 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
} }
} }
private ulong PrepareTextureDetour(Texture* tex, uint* size, byte mips, uint format)
{
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
if (tex != rtm->GameplayTextureUnk1 && tex != rtm->GameplayTextureUnk3)
{
return _prepareTextureHook.OriginalDisposeSafe(tex, size, mips, format);
}
ref var cfg = ref Service.Config._;
if (!Service.Plugin.Unloading && cfg.Game.IsEnabled && cfg.Game.Scale > 2f)
{
mips = (byte) MathF.Ceiling(MathF.Log2(cfg.Game.Scale));
}
Service.PluginLog.Debug($"Preparing gameplay texture 0x{(long) (nint) tex:X16} with {mips} mips, currently 0x{(long) (nint) tex->D3D11Texture2D:X16}");
var rv = _prepareTextureHook.OriginalDisposeSafe(tex, size, mips, format);
Service.PluginLog.Debug($"After: 0x{rv:X16} 0x{(long) (nint) tex->D3D11Texture2D:X16} 0x{(long) (nint) tex->D3D11ShaderResourceView:X16}");
return rv;
}
private void ImmediateBindPSCSSRVsDetourCommon(
bool isPS,
SDX11.CommonShaderStage stage,
ImmediateContextEx* im, PixelShader* ps, ImmediateSRV* data
)
{
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
bool called = false;
// TODO: Identifying by the shader might be much more reliable long-term.
if (ps->Shader.SamplerCount >= 1 && (data[0].Texture == rtm->GameplayTextureUnk1 || data[0].Texture == rtm->GameplayTextureUnk3))
{
// Service.PluginLog.Debug($"ImmediateBind{(isPS ? "PS" : "CS")}SRVs #{_drawCount} 0x{(long) (nint) ps}");
var tex = data[0].Texture;
Orig();
if (Service.Config._.Game.IsEnabled && Service.Config._.ResolutionScalingMode == ResolutionScalingMode.Point)
{
stage.SetSampler(0, _samplerPoint);
if (!isPS)
{
_d3dctx.PixelShader.SetSampler(0, _samplerPoint);
}
}
else if (tex->MipLevel != 1)
{
_d3dctx.PixelShader.SetSampler(0, _samplerMips);
if (!isPS)
{
_d3dctx.PixelShader.SetSampler(0, _samplerMips);
}
_d3dctx.GenerateMips(CppObject.FromPointer<SDX11.ShaderResourceView>((nint) tex->D3D11ShaderResourceView));
}
}
Orig();
void Orig()
{
if (called)
{
return;
}
called = true;
_drawCount++;
if (isPS)
{
_immediateBindPSSRVsHook.OriginalDisposeSafe(im, ps, data);
}
else
{
_immediateBindCSSRVsHook.OriginalDisposeSafe(im, (Shader*) ps, data);
}
}
}
private void ImmediateBindPSSRVsDetour(ImmediateContextEx* im, PixelShader* ps, ImmediateSRV* data)
{
ImmediateBindPSCSSRVsDetourCommon(true, _d3dctx.PixelShader, im, ps, data);
}
private void ImmediateBindCSSRVsDetour(ImmediateContextEx* im, Shader* cs, ImmediateSRV* data)
{
ImmediateBindPSCSSRVsDetourCommon(false, _d3dctx.ComputeShader, im, (PixelShader*) cs, data);
}
private int CreateTexture2DDetour(ID3D11Device* d3ddev, D3D11_TEXTURE2D_DESC* desc, D3D11_SUBRESOURCE_DATA* initialData, ID3D11Texture2D** tex)
{
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
if (tex != &rtm->GameplayTextureUnk1->D3D11Texture2D && tex != &rtm->GameplayTextureUnk3->D3D11Texture2D)
{
return _createTexture2DHook.OriginalDisposeSafe(d3ddev, desc, initialData, tex);
}
if (rtm->GameplayTextureUnk1->MipLevel != 1)
{
desc->MiscFlags |= (uint) D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_GENERATE_MIPS;
}
return _createTexture2DHook.OriginalDisposeSafe(d3ddev, desc, initialData, tex);
}
private enum ForceUpdateRTMState private enum ForceUpdateRTMState
{ {
_0_Idle, _0_Idle,
@ -384,7 +572,6 @@ public unsafe struct GraphicsConfigEx
[FieldOffset(0)] [FieldOffset(0)]
public GraphicsConfig _; public GraphicsConfig _;
// CRES_CLEAN never
public ref byte DynamicRezoEnable => ref _.DynamicRezoEnable; public ref byte DynamicRezoEnable => ref _.DynamicRezoEnable;
// Set to 0 in main menu and gpose, preventing scaling beyond 1. // Set to 0 in main menu and gpose, preventing scaling beyond 1.
public ref byte DynamicRezoEnableBeyond1 => ref RefPtr.For(ref DynamicRezoEnable).Offs<byte>(0x1).Ref; public ref byte DynamicRezoEnableBeyond1 => ref RefPtr.For(ref DynamicRezoEnable).Offs<byte>(0x1).Ref;
@ -406,6 +593,20 @@ public unsafe struct RenderTargetManagerEx
[FieldOffset(0)] [FieldOffset(0)]
public RenderTargetManager _; public RenderTargetManager _;
// Totle screen: Gets blitted FROM after actual gameplay texture. Actual purpose unknown.
// Im-game outdoors: Gets blitted to backbuffer.
[FieldOffset(0x68)]
public Texture* GameplayTextureUnk1;
// Gets blitted TO after actual gameplay texture, using Unk1. Actual purpose unknown.
[FieldOffset(0x100)]
public Texture* GameplayTextureUnk2;
// Title screen: Gets blitted to backbuffer.
// In-game outdoors: Totally unknown.
[FieldOffset(0x258)]
public Texture* GameplayTextureUnk3;
[FieldOffset(0x428)] [FieldOffset(0x428)]
public uint Resolution_Width; public uint Resolution_Width;
[FieldOffset(0x428 + 0x4)] [FieldOffset(0x428 + 0x4)]
@ -431,3 +632,21 @@ public unsafe struct RenderTargetManagerEx
[FieldOffset(0x70C + 4 * 4)] [FieldOffset(0x70C + 4 * 4)]
public float GraphicsRezoScaleUnk2; public float GraphicsRezoScaleUnk2;
} }
[StructLayout(LayoutKind.Explicit, Size = 0x18)]
public unsafe struct ImmediateSRV
{
// D3D11ShaderResourceView must be at 0x58 + (GlobalIndex % 3) * 8
// Smells like something swapchain-adjacent, but have yet to analyze anything with != null.
[FieldOffset(0x0)]
public nint Unkx0;
[FieldOffset(0x8)]
public Texture* Texture;
[FieldOffset(0x10)]
public uint Unkx10;
[FieldOffset(0x14)]
public uint Unkx14;
}

View file

@ -2,7 +2,6 @@
using Dalamud.Game.Config; using Dalamud.Game.Config;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using ImGuiNET; using ImGuiNET;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;

View file

@ -154,16 +154,7 @@ Changed via /gres");
DrawSizeTabShared(ref _.Game, "Game", scaleOnly: true); DrawSizeTabShared(ref _.Game, "Game", scaleOnly: true);
using (_.Game.Scale > 2f ? ImRaii.PushColor(ImGuiCol.Border, 0xff00ffff) : null) ImGui.InputFloat("Scale", ref _.Game.Scale, 0.01f, 0.1f, "%.3f", ImGuiInputTextFlags.None);
{
ImGui.InputFloat("Scale", ref _.Game.Scale, 0.01f, 0.1f, "%.3f", ImGuiInputTextFlags.None);
}
if (_.Game.Scale > 2f && ImGui.IsItemHovered())
{
ImGui.SetTooltip(@"Scaling larger than 2x currently doesn't improve antialiasing!
This is still being worked on before the v1.0 release.");
}
if (ImGui.BeginCombo("Graphics Upscaling", _.ResolutionScalingMode.ToHumanNameString())) if (ImGui.BeginCombo("Graphics Upscaling", _.ResolutionScalingMode.ToHumanNameString()))
{ {

View file

@ -8,27 +8,6 @@
"resolved": "12.0.0", "resolved": "12.0.0",
"contentHash": "J5TJLV3f16T/E2H2P17ClWjtfEBPpq3yxvqW46eN36JCm6wR+EaoaYkqG9Rm5sHqs3/nK/vKjWWyvEs/jhKoXw==" "contentHash": "J5TJLV3f16T/E2H2P17ClWjtfEBPpq3yxvqW46eN36JCm6wR+EaoaYkqG9Rm5sHqs3/nK/vKjWWyvEs/jhKoXw=="
}, },
"SharpDX.D3DCompiler": {
"type": "Direct",
"requested": "[4.2.0, )",
"resolved": "4.2.0",
"contentHash": "Rnsd6Ilp127xbXqhTit8WKFQUrXwWxqVGpglyWDNkIBCk0tWXNQEjrJpsl0KAObzyZaa33+EXAikLVt5fnd3GA==",
"dependencies": {
"NETStandard.Library": "1.6.1",
"SharpDX": "4.2.0"
}
},
"SharpDX.Direct2D1": {
"type": "Direct",
"requested": "[4.2.0, )",
"resolved": "4.2.0",
"contentHash": "Qs8LzDMaQf1u3KB8ArHu9pDv6itZ++QXs99a/bVAG+nKr0Hx5NG4mcN5vsfE0mVR2TkeHfeUm4PksRah6VUPtA==",
"dependencies": {
"NETStandard.Library": "1.6.1",
"SharpDX": "4.2.0",
"SharpDX.DXGI": "4.2.0"
}
},
"SharpDX.Direct3D11": { "SharpDX.Direct3D11": {
"type": "Direct", "type": "Direct",
"requested": "[4.2.0, )", "requested": "[4.2.0, )",
@ -40,12 +19,6 @@
"SharpDX.DXGI": "4.2.0" "SharpDX.DXGI": "4.2.0"
} }
}, },
"TerraFX.Interop.Windows": {
"type": "Direct",
"requested": "[10.0.22621.2, )",
"resolved": "10.0.22621.2",
"contentHash": "lORoYCoURS33Vi7PDvubRugLF2+l5v3rX2oVEqNhpBLjs3aZpqapRvTHPKVwsC+dGrsGuqJ/3yXuxVUb0vl3vg=="
},
"Microsoft.NETCore.Platforms": { "Microsoft.NETCore.Platforms": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.1.0", "resolved": "1.1.0",

View file

@ -23,15 +23,6 @@ everything where it was while still allowing you to switch between game scaling
is that this does NOT affect the screenshot resolution. is that this does NOT affect the screenshot resolution.
</details> </details>
<details><summary>I'm scaling by 3x or higher, but I'm still getting jagged edges!</summary>
The plugin currently only gives FFXIV a fake resolution to work with internally, but it doesn't ensure that the
extra pixels land on your screen. When scaling the display resolution, those will still be used for in-game or ReShade screenshots though.
Windows should perform basic filtering with 2x scaling (it might not work on some graphics cards?), and I haven't
heard back from anyone using this on Linux yet. But an universal fix for this is possible - it just requires more
time than I have on my hands right now.
</details>
<details><summary>I'm using DLSS, and my game looks pixelated / is small!</summary> <details><summary>I'm using DLSS, and my game looks pixelated / is small!</summary>
DLSS in FFXIV is bugged. No, I'm not joking: even without any plugins or mods loaded, it can glitch out when DLSS in FFXIV is bugged. No, I'm not joking: even without any plugins or mods loaded, it can glitch out when
simply resizing the window (most notably by rapidly pressing WinKey + Down and WinKey + Up). simply resizing the window (most notably by rapidly pressing WinKey + Down and WinKey + Up).