Compare commits

..

1 commit

Author SHA1 Message Date
8e5ad95b8d
dirty rnd: MiniShader utility, inspired by Pictomancy 2025-07-07 17:10:10 +02:00
13 changed files with 556 additions and 343 deletions

View file

@ -115,51 +115,18 @@ public static class MinSizeModeExt
}; };
} }
public enum ResolutionScalingMode public enum ResolutionScalingMode : byte
{ {
Point = 0, Fast = 0,
Linear = 1, FSR = 1,
FSR = 2, DLSS = 2
DLSS = 3
} }
public static class ResolutionScalingModeExt public static class ResolutionScalingModeExt
{ {
public static ResolutionScalingMode FromXIVSysConf(uint mode) => mode switch
{
0 => ResolutionScalingMode.FSR,
1 => ResolutionScalingMode.DLSS,
_ => ResolutionScalingMode.Linear
};
public static uint ToXIVSysConf(this ResolutionScalingMode mode) => mode switch
{
ResolutionScalingMode.FSR => 0,
ResolutionScalingMode.DLSS => 1,
_ => 0
};
public static ResolutionScalingMode FromXIVGFX(byte mode) => mode switch
{
0 => ResolutionScalingMode.Linear,
1 => ResolutionScalingMode.FSR,
2 => ResolutionScalingMode.DLSS,
_ => ResolutionScalingMode.Linear
};
public static byte ToXIVGFX(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.Point => "Pixelated", ResolutionScalingMode.Fast => "Fast Pixelation",
ResolutionScalingMode.Linear => "Blurry",
ResolutionScalingMode.FSR => "AMD FSR", ResolutionScalingMode.FSR => "AMD FSR",
ResolutionScalingMode.DLSS => "NVIDIA DLSS", ResolutionScalingMode.DLSS => "NVIDIA DLSS",
_ => mode.ToString(), _ => mode.ToString(),
@ -167,25 +134,11 @@ public static class ResolutionScalingModeExt
public static string? ToHumanInfoString(this ResolutionScalingMode mode) => mode switch public static string? ToHumanInfoString(this ResolutionScalingMode mode) => mode switch
{ {
ResolutionScalingMode.Point => "Lowest quality option which results in pixelation.", ResolutionScalingMode.Fast => "Lowest quality option which results in blurry 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
}; };
public static bool IsBroken(this ResolutionScalingMode mode) => mode switch public static bool IsUnsupported(this ResolutionScalingMode mode) => mode == ResolutionScalingMode.DLSS;
{
ResolutionScalingMode.DLSS => !Service.DebugConfig.IsDebug,
_ => false
};
public static unsafe bool IsSupported(this ResolutionScalingMode mode) => mode switch
{
ResolutionScalingMode.DLSS => PostEffectManagerEx.Instance()->DLSS != null,
_ => true,
};
public static ResolutionScalingMode ToSupported(this ResolutionScalingMode mode) =>
!mode.IsSupported() ? ResolutionScalingMode.FSR : mode;
} }

View file

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>0x0ade</Authors> <Authors>0x0ade</Authors>
<Company></Company> <Company></Company>
<Version>0.4.2.1</Version> <Version>0.4.1.0</Version>
<Description></Description> <Description></Description>
<Copyright></Copyright> <Copyright></Copyright>
<PackageProjectUrl></PackageProjectUrl> <PackageProjectUrl></PackageProjectUrl>
@ -29,6 +29,15 @@
<DalamudLibPath Condition="$(DALAMUD_HOME) != ''">$(DALAMUD_HOME)/</DalamudLibPath> <DalamudLibPath Condition="$(DALAMUD_HOME) != ''">$(DALAMUD_HOME)/</DalamudLibPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="Shaders\Example.hlsl" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Shaders\Blit.hlsl" />
<EmbeddedResource Include="Shaders\Example.hlsl" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DalamudPackager" Version="12.0.0" /> <PackageReference Include="DalamudPackager" Version="12.0.0" />
<Reference Include="FFXIVClientStructs"> <Reference Include="FFXIVClientStructs">
@ -75,7 +84,9 @@
<HintPath>$(DalamudLibPath)TerraFX.Interop.Windows.dll</HintPath> <HintPath>$(DalamudLibPath)TerraFX.Interop.Windows.dll</HintPath>
<Private>false</Private> <Private>false</Private>
</Reference> </Reference>
<PackageReference Include="SharpDX.D3DCompiler" Version="4.2.0" />
<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

@ -1,4 +1,5 @@
using Dalamud.Game.Config; using CustomResolution.Shaders;
using Dalamud.Game.Config;
using Dalamud.Hooking; using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
@ -6,12 +7,12 @@ using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FloppyUtils; using FloppyUtils;
using SharpDX; using SharpDX;
using System; using System;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static System.Net.Mime.MediaTypeNames;
using Device = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device; using Device = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device;
using SDX11 = SharpDX.Direct3D11;
namespace CustomResolution; namespace CustomResolution;
@ -21,55 +22,28 @@ 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<ImmediateProcessCommands> _immediateProcessCommandsHook; private Hook<ICDX11ProcessCommands> _icdx11ProcessCommandsHook;
private Hook<DDX11PostTick> _ddx11PostTickHook; private Hook<DDX11PostTick> _ddx11PostTickHook;
private Hook<TaskRenderGraphicsRender> _taskRenderGraphicsRenderHook; private Hook<TaskRenderGraphicsRender> _taskRenderGraphicsRenderHook;
private Hook<PrepareTexture> _prepareTextureHook; private Hook<PrepareTexture> _prepareTextureHook;
private Hook<ImmediateBindPSSRVs> _immediateBindPSSRVsHook; private Hook<ImmediatePreparePS> _immediatePreparePSHook;
private Hook<ImmediateBindCSSRVs> _immediateBindCSSRVsHook; private Hook<ImmediatePrepareCS> _immediatePrepareCSHook;
private Hook<CreateTexture2D> _createTexture2DHook; private Hook<CreateTexture2D> _createTexture2DHook;
private Hook<Draw> _drawHook;
private PostEffectManagerEx* _postEffectManager;
private SDX11.DeviceContext _d3dctx;
private SDX11.Device _d3ddev;
private SDX11.SamplerState _samplerMips;
private SDX11.SamplerState _samplerPoint;
private SDX11.SamplerState _samplerLinear;
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; private uint _gameplayDrawCount = 0;
private bool _drawIsGameplay = false;
private ResolutionScalingMode? _setConfigGraphicsRezoType; private BlitShader _blitShader;
public GameSizeState() public GameSizeState()
{ {
var pfx = PostEffectManagerEx.Instance(); _blitShader = new();
Service.PluginLog.Debug($"PostEffectManager: 0x{(long) (nint) pfx:X16}");
Service.PluginLog.Info($"DLSS: {(pfx->DLSS != null ? "Available" : "Unavailable")}");
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);
samplerDesc.Filter = SDX11.Filter.MinMagLinearMipPoint;
_samplerLinear = 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??
@ -80,6 +54,8 @@ 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.
@ -111,11 +87,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...
*/ */
_immediateProcessCommandsHook = Service.GameInteropProvider.HookFromAddress<ImmediateProcessCommands>( _icdx11ProcessCommandsHook = Service.GameInteropProvider.HookFromAddress<ICDX11ProcessCommands>(
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"),
ImmediateProcessCommandsDetour ICDX11ProcessCommandsDetour
); );
_immediateProcessCommandsHook.Enable(); _icdx11ProcessCommandsHook.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"),
@ -153,25 +129,33 @@ public unsafe class GameSizeState : IDisposable
/* The shader resource view of GameplayTexture gets read by these two. /* The shader resource view of GameplayTexture gets read by these two.
*/ */
_immediateBindPSSRVsHook = Service.GameInteropProvider.HookFromAddress<ImmediateBindPSSRVs>( _immediatePreparePSHook = Service.GameInteropProvider.HookFromAddress<ImmediatePreparePS>(
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"), 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 ImmediatePreparePSDetour
); );
_immediateBindPSSRVsHook.Enable(); _immediatePreparePSHook.Enable();
_immediateBindCSSRVsHook = Service.GameInteropProvider.HookFromAddress<ImmediateBindCSSRVs>( _immediatePrepareCSHook = Service.GameInteropProvider.HookFromAddress<ImmediatePrepareCS>(
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"), 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 ImmediatePrepareCSDetour
); );
_immediateBindCSSRVsHook.Enable(); _immediatePrepareCSHook.Enable();
/* And because all that isn't bad enough: /* Hook ID3D11Device funcs based on their vtable indices,
* Hook CreateTexture2D to forcibly set the mipmap generation flag. * because FFXIV might not set some flags we would like to have set on the textures.,
* and because aaaaaaaaaaaaaaaaaaaaaaaaaaaaa
*/ */
ID3D11Device* d3ddev = null;
dev->D3D11DeviceContext->GetDevice(&d3ddev);
_createTexture2DHook = Service.GameInteropProvider.HookFromAddress<CreateTexture2D>( _createTexture2DHook = Service.GameInteropProvider.HookFromAddress<CreateTexture2D>(
((ID3D11Device*) _d3ddev.NativePointer)->lpVtbl[5], d3ddev->lpVtbl[5],
CreateTexture2DDetour CreateTexture2DDetour
); );
_createTexture2DHook.Enable(); _createTexture2DHook.Enable();
_drawHook = Service.GameInteropProvider.HookFromAddress<Draw>(
dev->D3D11DeviceContext->lpVtbl[13],
DrawDetour
);
_drawHook.Enable();
} }
public bool ConfigDynRezo { get; private set; } public bool ConfigDynRezo { get; private set; }
@ -193,7 +177,7 @@ public unsafe class GameSizeState : IDisposable
private delegate void RTMRegenAfterResize(); private delegate void RTMRegenAfterResize();
// [UnmanagedFunctionPointer(CallingConvention.FastCall)] // [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void ImmediateProcessCommands(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count); private delegate void ICDX11ProcessCommands(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count);
// [UnmanagedFunctionPointer(CallingConvention.FastCall)] // [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void DDX11PostTick(DeviceEx* dev); private delegate void DDX11PostTick(DeviceEx* dev);
@ -205,55 +189,41 @@ public unsafe class GameSizeState : IDisposable
private delegate ulong PrepareTexture(Texture* tex, uint* size, byte mips, uint format); private delegate ulong PrepareTexture(Texture* tex, uint* size, byte mips, uint format);
// [UnmanagedFunctionPointer(CallingConvention.FastCall)] // [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void ImmediateBindPSSRVs(ImmediateContextEx* im, PixelShader* ps, ImmediateSRV* data); private delegate void ImmediatePreparePS(ImmediateContextEx* im, PixelShader* ps, ImmediateBlitResource* data);
// [UnmanagedFunctionPointer(CallingConvention.FastCall)] // [UnmanagedFunctionPointer(CallingConvention.FastCall)]
private delegate void ImmediateBindCSSRVs(ImmediateContextEx* im, Shader* cs, ImmediateSRV* data); private delegate void ImmediatePrepareCS(ImmediateContextEx* im, Shader* cs, ImmediateBlitResource* data);
private delegate int CreateTexture2D(ID3D11Device* d3ddev, D3D11_TEXTURE2D_DESC* desc, D3D11_SUBRESOURCE_DATA* initialData, ID3D11Texture2D** tex); 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 startVertexPosition);
public void Dispose() public void Dispose()
{ {
_rtmApplyScalingHook.Dispose(); _rtmApplyScalingHook.Dispose();
_rtmDestroyAfterResizeHook.Dispose(); _rtmDestroyAfterResizeHook.Dispose();
_rtmRegenAfterResizeHook.Dispose(); _rtmRegenAfterResizeHook.Dispose();
_immediateProcessCommandsHook.Dispose(); _icdx11ProcessCommandsHook.Dispose();
_ddx11PostTickHook.Dispose(); _ddx11PostTickHook.Dispose();
_taskRenderGraphicsRenderHook.Dispose(); _taskRenderGraphicsRenderHook.Dispose();
_prepareTextureHook.Dispose(); _prepareTextureHook.Dispose();
_immediateBindPSSRVsHook.Dispose(); _immediatePreparePSHook.Dispose();
_immediateBindCSSRVsHook.Dispose(); _immediatePrepareCSHook.Dispose();
_createTexture2DHook.Dispose(); _createTexture2DHook.Dispose();
lock (_renderLock) _drawHook.Dispose();
{ _blitShader.Dispose();
_samplerLinear.Dispose();
_samplerPoint.Dispose();
_samplerMips.Dispose();
}
_d3ddev.Dispose();
Service.Framework.RunOnFrameworkThread(Update); Service.Framework.RunOnFrameworkThread(Update);
} }
public void SetConfigGraphicsRezoType(ResolutionScalingMode mode)
{
_setConfigGraphicsRezoType = mode;
}
public void Update() public void Update()
{ {
_dbg.Clear(); _dbg.Clear();
ref var cfg = ref Service.Config._.Game; ref var cfg = ref Service.Config._.Game;
if (_setConfigGraphicsRezoType is { } setType)
{
_setConfigGraphicsRezoType = null;
Service.GameConfig.System.Set(SystemConfigOption.GraphicsRezoUpscaleType.ToString(), setType.ToXIVSysConf());
}
ConfigDynRezo = Service.GameConfig.System.GetUInt(SystemConfigOption.DynamicRezoType.ToString()) != 0U; ConfigDynRezo = Service.GameConfig.System.GetUInt(SystemConfigOption.DynamicRezoType.ToString()) != 0U;
// GameConfig starts with FSR at 0; GraphicsConfig starts with FSR at 1 // GameConfig starts with FSR at 0; GraphicsConfig starts with FSR at 1
ConfigGraphicsRezoType = ResolutionScalingModeExt.FromXIVSysConf(Service.GameConfig.System.GetUInt(SystemConfigOption.GraphicsRezoUpscaleType.ToString())); ConfigGraphicsRezoType = (ResolutionScalingMode) (Service.GameConfig.System.GetUInt(SystemConfigOption.GraphicsRezoUpscaleType.ToString()) + 1);
ConfigGraphicsRezoScale = Service.GameConfig.System.GetUInt(SystemConfigOption.GraphicsRezoScale.ToString()) / 100f; ConfigGraphicsRezoScale = Service.GameConfig.System.GetUInt(SystemConfigOption.GraphicsRezoScale.ToString()) / 100f;
var dev = (DeviceEx*) Device.Instance(); var dev = (DeviceEx*) Device.Instance();
@ -262,8 +232,14 @@ public unsafe class GameSizeState : IDisposable
if (Service.DebugConfig.IsDebug) if (Service.DebugConfig.IsDebug)
{ {
_dbg.Append(@$"DR !{gfx->DynamicRezoEnable} +{gfx->DynamicRezoEnableBeyond1} _{gfx->DynamicRezoEnableCutScene} ?{gfx->DynamicRezoEnableUnkx47} ?{gfx->DynamicRezoEnableUnkx48} _dbg.Append(@$"RTMApplyScaling 0x{(_rtmApplyScalingHook.IsDisposed ? null : _rtmApplyScalingHook.Address):X16}
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}
GFX 0x{(long) (nint) gfx:X16}
RTM 0x{(long) (nint) rtm:X16}
RTM {rtm->Resolution_Width} x {rtm->Resolution_Height} RTM {rtm->Resolution_Width} x {rtm->Resolution_Height}
RTM H {rtm->DynamicResolutionActualTargetHeight} {rtm->DynamicResolutionTargetHeight} {rtm->DynamicResolutionMaximumHeight} {rtm->DynamicResolutionMinimumHeight} RTM H {rtm->DynamicResolutionActualTargetHeight} {rtm->DynamicResolutionTargetHeight} {rtm->DynamicResolutionMaximumHeight} {rtm->DynamicResolutionMinimumHeight}
RTM S {rtm->GraphicsRezoScalePrev} {rtm->GraphicsRezoScaleGlassX} {rtm->GraphicsRezoScaleGlassY} {rtm->GraphicsRezoScaleGlassX} {rtm->GraphicsRezoScaleGlassY} RTM S {rtm->GraphicsRezoScalePrev} {rtm->GraphicsRezoScaleGlassX} {rtm->GraphicsRezoScaleGlassY} {rtm->GraphicsRezoScaleGlassX} {rtm->GraphicsRezoScaleGlassY}
@ -280,12 +256,12 @@ 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 = cfg.Scale <= 1f ? Service.Config._.ResolutionScalingMode.ToXIVGFX() : ResolutionScalingMode.Linear.ToXIVGFX(); gfx->GraphicsRezoUpscaleType = (byte) (cfg.Scale <= 1f ? Service.Config._.ResolutionScalingMode : ResolutionScalingMode.Fast);
rtm->DynamicResolutionMinimumHeight = rtm->DynamicResolutionMaximumHeight; rtm->DynamicResolutionMinimumHeight = rtm->DynamicResolutionMaximumHeight;
} }
else else
{ {
gfx->GraphicsRezoUpscaleType = ConfigGraphicsRezoType.ToSupported().ToXIVGFX(); gfx->GraphicsRezoUpscaleType = (byte) ConfigGraphicsRezoType;
} }
_wasEnabled = enabled; _wasEnabled = enabled;
} }
@ -293,16 +269,16 @@ 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->GameplayTextureUnk3; var tex = rtm->GameplayTexture;
if (tex->AllocatedWidth != widthS || tex->AllocatedHeight != heightS) if (tex->AllocatedWidth != widthS || tex->AllocatedHeight != heightS)
{ {
if (!unloading) if (!unloading)
{ {
Service.PluginLog.Debug($"Regenerating dirty RTM - locking ImmediateContextDX11.ProcessCommands");
lock (_renderLock) lock (_renderLock)
{ {
Service.PluginLog.Debug($"Regenerating dirty RTM - locked ImmediateContextDX11.ProcessCommands"); Service.PluginLog.Debug($"Regenerating dirty RTM - locking ImmediateContextDX11.ProcessCommands");
dev->RequestResolutionChange = 1; dev->RequestResolutionChange = 1;
RTMDestroyAfterResizeDetour(); RTMDestroyAfterResizeDetour();
RTMRegenAfterResizeDetour(); RTMRegenAfterResizeDetour();
@ -320,7 +296,7 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
(rtm->Resolution_Width != widthS || rtm->Resolution_Height != heightS)) (rtm->Resolution_Width != widthS || rtm->Resolution_Height != heightS))
{ {
// Can also be forced via dev->RequestResolutionChange, but while cleaner on paper, it trips up ReShade. // Can also be forced via dev->RequestResolutionChange, but while cleaner on paper, it trips up ReShade.
Service.PluginLog.Debug($"_forceUpdateRTM -> 1 - expected {widthS} x {heightS}, got {rtm->Resolution_Width} x {rtm->Resolution_Height}"); Service.PluginLog.Debug("_forceUpdateRTM -> 1");
_forceUpdateRTM = ForceUpdateRTMState._1_FakeInv; _forceUpdateRTM = ForceUpdateRTMState._1_FakeInv;
} }
@ -358,14 +334,12 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
private static void GetScaledWidthHeight(uint width, uint height, float scale, out uint widthS, out uint heightS) private static void GetScaledWidthHeight(uint width, uint height, float scale, out uint widthS, out uint heightS)
{ {
heightS = (uint) MathF.Round(height * scale, MidpointRounding.AwayFromZero); heightS = (uint) MathF.Round(height * scale);
widthS = (width * heightS) / height; widthS = (width * heightS) / height;
} }
private void RTMApplyScalingDetour(RenderTargetManagerEx* rtm, uint* size, byte unk1) private void RTMApplyScalingDetour(RenderTargetManagerEx* rtm, uint* size, byte unk1)
{ {
Debug.Assert(rtm == RenderTargetManager.Instance());
ref var cfg = ref Service.Config._.Game; ref var cfg = ref Service.Config._.Game;
if (Service.Plugin.Unloading || !cfg.IsEnabled) if (Service.Plugin.Unloading || !cfg.IsEnabled)
{ {
@ -375,7 +349,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 = (byte) (cfg.Scale > 1f ? 1 : 0); gfx->DynamicRezoEnableBeyond1 = 1;
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);
@ -396,7 +370,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: {rv} 0x{(long) (nint) rtm->GameplayTextureUnk1->D3D11Texture2D:X16} 0x{(long) (nint) rtm->GameplayTextureUnk3->D3D11Texture2D:X16}"); Service.PluginLog.Debug($"After: {rv} 0x{(long) (nint) rtm->GameplayTexture->D3D11Texture2D:X16}");
return rv; return rv;
} }
@ -415,17 +389,16 @@ 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->GameplayTextureUnk1->D3D11Texture2D:X16} 0x{(long) (nint) rtm->GameplayTextureUnk3->D3D11Texture2D:X16}"); Service.PluginLog.Debug($"After: 0x{(long) (nint) rtm->GameplayTexture->D3D11Texture2D:X16}");
} }
private void ImmediateProcessCommandsDetour(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count) private void ICDX11ProcessCommandsDetour(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count)
{ {
_drawCount = 0; _gameplayDrawCount = 0;
// Service.PluginLog.Debug("--- ImmediateProcessCommands ---");
lock (_renderLock) lock (_renderLock)
{ {
_immediateProcessCommandsHook.OriginalDisposeSafe(ctx, cmds, count); _icdx11ProcessCommandsHook.OriginalDisposeSafe(ctx, cmds, count);
} }
} }
@ -453,16 +426,21 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
private ulong PrepareTextureDetour(Texture* tex, uint* size, byte mips, uint format) private ulong PrepareTextureDetour(Texture* tex, uint* size, byte mips, uint format)
{ {
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance(); var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
if (tex != rtm->GameplayTextureUnk1 && tex != rtm->GameplayTextureUnk3) if (tex != rtm->GameplayTexture)
{ {
return _prepareTextureHook.OriginalDisposeSafe(tex, size, mips, format); return _prepareTextureHook.OriginalDisposeSafe(tex, size, mips, format);
} }
ref var cfg = ref Service.Config._; ref var cfg = ref Service.Config._.Game;
if (!Service.Plugin.Unloading && cfg.Game.IsEnabled && cfg.Game.Scale > 2f) // TODO: MathF.Log
mips = 1;
if (!Service.Plugin.Unloading && cfg.IsEnabled)
{ {
mips = (byte) MathF.Ceiling(MathF.Log2(cfg.Game.Scale)); for (var s = cfg.Scale; s > 2f; s /= 2f)
{
mips++;
}
} }
Service.PluginLog.Debug($"Preparing gameplay texture 0x{(long) (nint) tex:X16} with {mips} mips, currently 0x{(long) (nint) tex->D3D11Texture2D:X16}"); Service.PluginLog.Debug($"Preparing gameplay texture 0x{(long) (nint) tex:X16} with {mips} mips, currently 0x{(long) (nint) tex->D3D11Texture2D:X16}");
@ -471,93 +449,54 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
return rv; return rv;
} }
private void ImmediateBindPSCSSRVsDetourCommon( private void ImmediatePreparePSDetour(ImmediateContextEx* im, PixelShader* ps, ImmediateBlitResource* data)
bool isPS,
SDX11.CommonShaderStage stage,
ImmediateContextEx* im, PixelShader* ps, ImmediateSRV* data
)
{ {
Debug.Assert(im == Device.Instance()->ImmediateContext); //Service.PluginLog.Debug($"ImmediatePreparePS 0x{(long) im:X16} 0x{(long) ps:X16} 0x{(long) unk3:X16}");
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance(); var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
bool called = false; var tex = rtm->GameplayTexture;
if (ps->Shader.SamplerCount >= 1 && data[0].Texture == tex)
// 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}"); _gameplayDrawCount++;
var tex = data[0].Texture; // Service.PluginLog.Debug($"ImmediatePreparePS #{_gameplayDrawCount} 0x{(long) im:X16} 0x{(long) ps:X16} 0x{(long) data[0].Unk1:X16} 0x{(long) (nint) data[0].Texture:X16} 0x{data[0].Unk2:X8} 0x{data[0].Unk3:X8}");
Orig(); var gfx = (GraphicsConfigEx*) GraphicsConfig.Instance();
if ((gfx->GraphicsRezoUpscaleType == (byte) ResolutionScalingMode.FSR && gfx->GraphicsRezoScale < 1f) ||
SDX11.SamplerState? sampler = null; gfx->GraphicsRezoUpscaleType == (byte) ResolutionScalingMode.DLSS)
if (Service.Config._.Game.IsEnabled && Service.Config._.ResolutionScalingMode == ResolutionScalingMode.Point)
{ {
sampler = _samplerPoint; _drawIsGameplay = _gameplayDrawCount == 1;
}
else if (tex->MipLevel != 1)
{
sampler = _samplerMips;
_d3dctx.GenerateMips(CppObject.FromPointer<SDX11.ShaderResourceView>((nint) tex->D3D11ShaderResourceView));
}
else if (Service.Config._.Game.IsEnabled && Service.Config._.ResolutionScalingMode == ResolutionScalingMode.Linear)
{
sampler = _samplerLinear;
}
if (sampler is not null)
{
stage.SetSampler(0, sampler);
if (!isPS)
{
_d3dctx.PixelShader.SetSampler(0, sampler);
}
}
}
Orig();
void Orig()
{
if (called)
{
return;
}
called = true;
_drawCount++;
if (isPS)
{
_immediateBindPSSRVsHook.OriginalDisposeSafe(im, ps, data);
} }
else else
{ {
_immediateBindCSSRVsHook.OriginalDisposeSafe(im, (Shader*) ps, data); _drawIsGameplay = _gameplayDrawCount == 3;
} }
} }
_immediatePreparePSHook.OriginalDisposeSafe(im, ps, data);
} }
private void ImmediateBindPSSRVsDetour(ImmediateContextEx* im, PixelShader* ps, ImmediateSRV* data) private void ImmediatePrepareCSDetour(ImmediateContextEx* im, Shader* cs, ImmediateBlitResource* data)
{ {
ImmediateBindPSCSSRVsDetourCommon(true, _d3dctx.PixelShader, im, ps, data); //Service.PluginLog.Debug($"ImmediatePrepareCS 0x{(long) im:X16} 0x{(long) cs:X16} 0x{(long) unk3:X16}");
}
private void ImmediateBindCSSRVsDetour(ImmediateContextEx* im, Shader* cs, ImmediateSRV* data) var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
{ if (((PixelShader*) cs)->Shader.SamplerCount >= 1 && data[0].Texture == rtm->GameplayTexture)
ImmediateBindPSCSSRVsDetourCommon(false, _d3dctx.ComputeShader, im, (PixelShader*) cs, data); {
_gameplayDrawCount++;
}
_immediatePrepareCSHook.OriginalDisposeSafe(im, cs, data);
} }
private int CreateTexture2DDetour(ID3D11Device* d3ddev, D3D11_TEXTURE2D_DESC* desc, D3D11_SUBRESOURCE_DATA* initialData, ID3D11Texture2D** tex) private int CreateTexture2DDetour(ID3D11Device* d3ddev, D3D11_TEXTURE2D_DESC* desc, D3D11_SUBRESOURCE_DATA* initialData, ID3D11Texture2D** tex)
{ {
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance(); var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
if (tex != &rtm->GameplayTextureUnk1->D3D11Texture2D && tex != &rtm->GameplayTextureUnk3->D3D11Texture2D) if (tex != &rtm->GameplayTexture->D3D11Texture2D)
{ {
return _createTexture2DHook.OriginalDisposeSafe(d3ddev, desc, initialData, tex); return _createTexture2DHook.OriginalDisposeSafe(d3ddev, desc, initialData, tex);
} }
if (rtm->GameplayTextureUnk1->MipLevel != 1) if (rtm->GameplayTexture->MipLevel != 1)
{ {
desc->MiscFlags |= (uint) D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_GENERATE_MIPS; desc->MiscFlags |= (uint) D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_GENERATE_MIPS;
} }
@ -565,6 +504,41 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr
return _createTexture2DHook.OriginalDisposeSafe(d3ddev, desc, initialData, tex); return _createTexture2DHook.OriginalDisposeSafe(d3ddev, desc, initialData, tex);
} }
private void DrawDetour(ID3D11DeviceContext* d3dctx, uint vertexCount, uint startVertexPosition)
{
ref var cfg = ref Service.Config._.Game;
if (!_drawIsGameplay)
{
_drawHook.OriginalDisposeSafe(d3dctx, vertexCount, startVertexPosition);
return;
}
_drawIsGameplay = false;
if (!cfg.IsEnabled)
{
_drawHook.OriginalDisposeSafe(d3dctx, vertexCount, startVertexPosition);
return;
}
var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance();
SharpDX.Direct3D11.SamplerStateDescription description = SharpDX.Direct3D11.SamplerStateDescription.Default();
description.Filter = SharpDX.Direct3D11.Filter.MinMagMipLinear;
using SharpDX.Direct3D11.SamplerState sampler = new(_blitShader.Device, description);
_blitShader.Ctx.PixelShader.SetSampler(0, sampler);
/*
_blitShader.Draw(
CppObject.FromPointer<SharpDX.Direct3D11.ShaderResourceView>((nint) rtm->GameplayTexture->D3D11ShaderResourceView)
);
*/
_blitShader.Ctx.GenerateMips(CppObject.FromPointer<SharpDX.Direct3D11.ShaderResourceView>((nint) rtm->GameplayTexture->D3D11ShaderResourceView));
_drawHook.OriginalDisposeSafe(d3dctx, vertexCount, startVertexPosition);
}
private enum ForceUpdateRTMState private enum ForceUpdateRTMState
{ {
_0_Idle, _0_Idle,
@ -603,6 +577,7 @@ 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;
@ -624,8 +599,7 @@ public unsafe struct RenderTargetManagerEx
[FieldOffset(0)] [FieldOffset(0)]
public RenderTargetManager _; public RenderTargetManager _;
// Title screen: Gets blitted FROM after actual gameplay texture. Actual purpose unknown. // Gets blitted FROM after actual gameplay texture. Actual purpose unknown.
// In-game outdoors: Gets blitted to backbuffer.
[FieldOffset(0x68)] [FieldOffset(0x68)]
public Texture* GameplayTextureUnk1; public Texture* GameplayTextureUnk1;
@ -633,10 +607,9 @@ public unsafe struct RenderTargetManagerEx
[FieldOffset(0x100)] [FieldOffset(0x100)]
public Texture* GameplayTextureUnk2; public Texture* GameplayTextureUnk2;
// Title screen: Gets blitted to backbuffer. // Gets blitted to backbuffer.
// In-game outdoors: Totally unknown.
[FieldOffset(0x258)] [FieldOffset(0x258)]
public Texture* GameplayTextureUnk3; public Texture* GameplayTexture;
[FieldOffset(0x428)] [FieldOffset(0x428)]
public uint Resolution_Width; public uint Resolution_Width;
@ -665,48 +638,19 @@ public unsafe struct RenderTargetManagerEx
} }
[StructLayout(LayoutKind.Explicit, Size = 0x18)] [StructLayout(LayoutKind.Explicit, Size = 0x18)]
public unsafe struct ImmediateSRV public unsafe struct ImmediateBlitResource
{ {
// D3D11ShaderResourceView must be at 0x58 + (GlobalIndex % 3) * 8 // D3D11ShaderResourceView must be at 0x58 + (GlobalIndex % 3) * 8
// Smells like something swapchain-adjacent, but have yet to analyze anything with != null. // Smells like something swapchain-adjacent, but have yet to analyze anything with != null.
[FieldOffset(0x0)] [FieldOffset(0x0)]
public nint Unkx0; public nint Unk1;
[FieldOffset(0x8)] [FieldOffset(0x8)]
public Texture* Texture; public Texture* Texture;
[FieldOffset(0x10)] [FieldOffset(0x10)]
public uint Unkx10; public uint Unk2;
[FieldOffset(0x14)] [FieldOffset(0x14)]
public uint Unkx14; public uint Unk3;
} }
// There's currently no effort to C#-ify the PostEffectManager findings in CS.
[StructLayout(LayoutKind.Explicit)]
public unsafe struct PostEffectManagerEx
{
private static PostEffectManagerEx* _instance;
public static PostEffectManagerEx* Instance()
{
if (Service.SigScanner is null)
{
return null;
}
var code = Service.SigScanner.ScanText("48 83 EC ?? 48 8B 05 ?? ?? ?? ?? 49 8B F9 49 8B F0 4C 8B F2 48 8B 98 ?? ?? 00 00 48 85 DB");
var found = Service.SigScanner.ResolveRelativeAddress(code + 4 + 7, *(int*) (code + 4 + 3));
return _instance = (PostEffectManagerEx*) *(nint*) found;
}
/* Identifiable by being used for DLSS initialization when the upscale type == 2,
* and containing NVSDK NGX capability param attribs at *+0x158 since 2024?
* The function setting it up is very similar to NVIDIA's sample code,
* but in case of emergency, CTRL+F NVSDK_NGX_D3D11_GetCapabilityParameters (always exported?)
* and the two magic strings "SuperSampling.NeedsUpdatedDriver" and "SuperSampling.Available"
*/
[FieldOffset(0x4220)]
public void* DLSS;
}

View file

@ -0,0 +1,184 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Common.Lua;
using MonoMod.Utils;
using Newtonsoft.Json;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Xml.Linq;
using TerraFX.Interop.DirectX;
using SDXDevice = SharpDX.Direct3D11.Device;
using XIVDevice = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device;
namespace CustomResolution;
public unsafe class MiniShader : IDisposable
{
public MiniShader(string name)
{
Name = name;
var asm = Assembly.GetExecutingAssembly();
var path = $"{nameof(CustomResolution)}.Shaders.{name}.hlsl";
using (var stream = asm.GetManifestResourceStream(path) ?? throw new FileNotFoundException(path))
using (var streamReader = new StreamReader(stream))
{
Source = streamReader.ReadToEnd();
}
var dev = (DeviceEx*) XIVDevice.Instance();
Ctx = CppObject.FromPointer<DeviceContext>((nint) dev->D3D11DeviceContext);
Device = Ctx.Device;
VS = new(Device, ShaderBytecode.Compile(Source, "vs", "vs_5_0"));
PS = new(Device, ShaderBytecode.Compile(Source, "ps", "ps_5_0"));
}
public string Name { get; private set; }
public string Source { get; private set; }
public DeviceContext Ctx { get; private set; }
public SDXDevice Device { get; private set; }
public SharpDX.Direct3D11.VertexShader VS { get; private set; }
public SharpDX.Direct3D11.PixelShader PS { get; private set; }
public virtual void Dispose()
{
PS.Dispose();
VS.Dispose();
Device.Dispose();
}
}
public class MiniShaderConstants<T> : IDisposable where T : unmanaged
{
private readonly MiniShader _shader;
private T _value;
public MiniShaderConstants(MiniShader shader)
{
_shader = shader;
Buffer = new(
_shader.Device,
Math.Max(16, Marshal.SizeOf<T>()),
ResourceUsage.Default,
BindFlags.ConstantBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
Marshal.SizeOf<T>()
);
}
public SharpDX.Direct3D11.Buffer Buffer { get; private set; }
public ref T Value => ref _value;
public void Dispose()
{
Buffer.Dispose();
}
public void Update()
{
Set(Value);
}
public void Set(T data)
{
_value = data;
_shader.Ctx.UpdateSubresource(ref data, Buffer);
}
}
public class MiniShaderState : IDisposable
{
private List<Action> _undos = [];
public MiniShaderState()
{
}
public void Dispose()
{
foreach (var undo in _undos)
{
undo();
}
}
public void Set<TOwner, TValue>(TOwner owner, string name, TValue value)
{
var prev = ReflCache<TOwner>.Get<TValue>(owner, name);
_undos.Add(() => ReflCache<TOwner>.Set(owner, name, prev));
ReflCache<TOwner>.Set(owner, name, value);
}
public void SetShader<T>(CommonShaderStage<T> stage, T? shader) where T : DeviceChild
{
var prev = stage.Get();
_undos.Add(() => stage.Set(prev));
stage.Set(shader!);
}
public void SetConstantBuffer<T>(CommonShaderStage<T> stage, int slot, SharpDX.Direct3D11.Buffer buffer) where T : DeviceChild
{
var prev = stage.GetConstantBuffers(slot, 1);
_undos.Add(() => stage.SetConstantBuffer(slot, prev[0]));
stage.SetConstantBuffer(slot, buffer);
}
public void SetShaderResource<T>(CommonShaderStage<T> stage, int slot, ShaderResourceView srv) where T : DeviceChild
{
var prev = stage.GetShaderResources(slot, 1);
_undos.Add(() => stage.SetShaderResource(slot, prev[0]));
stage.SetShaderResource(slot, srv);
}
private static class ReflCache<TOwner>
{
private static readonly Type _tOwner = typeof(TOwner);
private static readonly Dictionary<string, PropertyInfo?> _props = [];
private static PropertyInfo? GetProperty(string name)
{
if (_props.TryGetValue(name, out var prop))
{
return prop;
}
return _props[name] = _tOwner.GetProperty(name);
}
public static TValue Get<TValue>(TOwner owner, string name)
{
if (GetProperty(name) is { } prop)
{
return (TValue) prop.GetValue(owner)!;
}
return default!;
}
public static void Set<TValue>(TOwner owner, string name, TValue value)
{
if (GetProperty(name) is { } prop)
{
prop.SetValue(owner, value);
return;
}
}
}
}

View file

@ -1,8 +1,8 @@
using Dalamud.Game.ClientState.Keys; using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Config; using Dalamud.Game.Config;
using Dalamud.Interface.ImGuiNotification;
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;
@ -17,9 +17,6 @@ public sealed unsafe class Plugin : IDalamudPlugin
private readonly List<Cmd> _cmds; private readonly List<Cmd> _cmds;
private bool _ignoreConfigChanges = false; private bool _ignoreConfigChanges = false;
private int _lagsIndex = 0;
private readonly bool[] _lagsAll = new bool[5];
public Plugin(IDalamudPluginInterface pluginInterface) public Plugin(IDalamudPluginInterface pluginInterface)
{ {
pluginInterface.Create<Service>(); pluginInterface.Create<Service>();
@ -58,9 +55,6 @@ public sealed unsafe class Plugin : IDalamudPlugin
public bool Unloading { get; private set; } = false; public bool Unloading { get; private set; } = false;
public int LagsTotal { get; private set; } = 0;
public bool Lag { get; private set; } = false;
public void Dispose() public void Dispose()
{ {
Unloading = true; Unloading = true;
@ -87,25 +81,10 @@ public sealed unsafe class Plugin : IDalamudPlugin
private void OnFrameworkUpdateForSize(IFramework framework, string type, ref ConfigurationV1.SizeConfig size) private void OnFrameworkUpdateForSize(IFramework framework, string type, ref ConfigurationV1.SizeConfig size)
{ {
if (Lag && Service.Config._.DisableOnHang && size.IsEnabled) if (Service.ClientState.IsLoggedIn && framework.UpdateDelta.TotalSeconds >= 1 && Service.Config._.DisableOnHang && size.IsEnabled)
{ {
Service.PluginLog.Warning($"Disabling because hang: {type}"); Service.PluginLog.Warning($"Disabling because hang: {type}");
if (Service.ClientState.IsLoggedIn) Service.PrintChat($"Lag detected, \"{type}\" custom resolution disabled.");
{
Service.PrintChat($"Lag detected, \"{type}\" custom resolution disabled.");
}
else
{
// An initial duration of 15 might seem absurd, but we're dealing with lag here!
Service.NotificationManager.AddNotification(new Notification()
{
Title = "CustomResolution",
Content = $"Lag detected, \"{type}\" custom resolution disabled.",
Type = NotificationType.Error,
InitialDuration = TimeSpan.FromSeconds(framework.UpdateDelta.TotalSeconds + 15),
Minimized = false
});
}
size.IsEnabled = false; size.IsEnabled = false;
Service.Config.Save(); Service.Config.Save();
} }
@ -136,18 +115,6 @@ public sealed unsafe class Plugin : IDalamudPlugin
private void OnFrameworkUpdate(IFramework framework) private void OnFrameworkUpdate(IFramework framework)
{ {
if (_lagsAll[_lagsIndex])
{
LagsTotal--;
}
if (_lagsAll[_lagsIndex] = framework.UpdateDelta.TotalSeconds > 1.0)
{
LagsTotal++;
}
_lagsIndex = (_lagsIndex + 1) % _lagsAll.Length;
Lag = LagsTotal == _lagsAll.Length;
lock (_disposeLock) lock (_disposeLock)
{ {
OnFrameworkUpdateForSize(framework, "Display", ref Service.Config._.Display); OnFrameworkUpdateForSize(framework, "Display", ref Service.Config._.Display);

View file

@ -1,7 +1,6 @@
using CustomResolution.Hooks; using CustomResolution.Hooks;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using Dalamud.Interface;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
@ -58,9 +57,6 @@ public sealed class Service
[PluginService] [PluginService]
public static IGameConfig GameConfig { get; private set; } = null!; public static IGameConfig GameConfig { get; private set; } = null!;
[PluginService]
public static INotificationManager NotificationManager { get; private set; } = null!;
public static void PrintChat(string msg) public static void PrintChat(string msg)
{ {
ChatGui.Print(new XivChatEntry ChatGui.Print(new XivChatEntry

View file

@ -0,0 +1,23 @@
Texture2D<float4> inputTexture : register(t0);
SamplerState TextureSampler;
struct VSOutput
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD;
};
VSOutput vs(uint id : SV_VertexID)
{
VSOutput output;
float2 uv = float2((id << 1) & 2, id & 2);
output.pos = float4(uv * 2 - 1, 0, 1);
output.uv = uv * float2(1, -1) + float2(0, 1);
return output;
}
float4 ps(VSOutput input) : SV_Target
{
float4 color = inputTexture.Sample(TextureSampler, input.uv);
return color;
}

View file

@ -0,0 +1,51 @@
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace CustomResolution.Shaders;
public class BlitShader : MiniShader
{
public BlitShader() : base("Blit")
{
}
public override void Dispose()
{
base.Dispose();
}
public void Draw(ShaderResourceView srv)
{
using MiniShaderState state = new();
state.SetShader(Ctx.VertexShader, VS);
state.SetShader(Ctx.PixelShader, PS);
state.SetShader(Ctx.GeometryShader, null);
state.Set(Ctx.InputAssembler, nameof(Ctx.InputAssembler.PrimitiveTopology), PrimitiveTopology.TriangleList);
var blendDesc = Ctx.OutputMerger.BlendState.Description;
blendDesc.RenderTarget[0].IsBlendEnabled = false;
using var blendState = new BlendState(Device, blendDesc);
state.Set(Ctx.OutputMerger, nameof(Ctx.OutputMerger.BlendState), blendState);
state.SetShaderResource(Ctx.PixelShader, 0, srv);
Ctx.Draw(3, 0);
}
[StructLayout(LayoutKind.Sequential)]
public struct TConstants
{
public float Time;
}
}

View file

@ -0,0 +1,26 @@
struct Constants
{
float time;
};
Constants k : register(b0);
struct VSOutput
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD;
};
VSOutput vs(uint id : SV_VertexID)
{
VSOutput output;
float2 uv = float2((id << 1) & 2, id & 2);
output.pos = float4(uv - 1, 0, 1);
output.uv = uv;
return output;
}
float4 ps(VSOutput input) : SV_Target
{
float4 color = float4(0.5, 0, frac(k.time), 0.5);
return color;
}

View file

@ -0,0 +1,64 @@
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace CustomResolution.Shaders;
public class ExampleShader : MiniShader
{
public ExampleShader() : base("Example")
{
Constants = new(this);
}
public MiniShaderConstants<TConstants> Constants { get; private set; }
public override void Dispose()
{
Constants.Dispose();
base.Dispose();
}
public void Draw()
{
using MiniShaderState state = new();
state.SetShader(Ctx.VertexShader, VS);
state.SetShader(Ctx.PixelShader, PS);
state.SetShader(Ctx.GeometryShader, null);
state.Set(Ctx.InputAssembler, nameof(Ctx.InputAssembler.PrimitiveTopology), PrimitiveTopology.TriangleList);
state.SetConstantBuffer(Ctx.PixelShader, 0, Constants.Buffer);
Ctx.PixelShader.SetConstantBuffer(0, Constants.Buffer);
var blendDesc = Ctx.OutputMerger.BlendState.Description;
blendDesc.RenderTarget[0].IsBlendEnabled = true;
blendDesc.RenderTarget[0].SourceBlend = BlendOption.SourceAlpha;
blendDesc.RenderTarget[0].DestinationBlend = BlendOption.InverseSourceAlpha;
blendDesc.RenderTarget[0].BlendOperation = BlendOperation.Add;
blendDesc.RenderTarget[0].SourceAlphaBlend = BlendOption.SourceAlpha;
blendDesc.RenderTarget[0].DestinationAlphaBlend = BlendOption.InverseSourceAlpha;
blendDesc.RenderTarget[0].AlphaBlendOperation = BlendOperation.Maximum;
blendDesc.RenderTarget[0].RenderTargetWriteMask = ColorWriteMaskFlags.All;
using var blendState = new BlendState(Device, blendDesc);
state.Set(Ctx.OutputMerger, nameof(Ctx.OutputMerger.BlendState), blendState);
Ctx.Draw(3, 0);
}
[StructLayout(LayoutKind.Sequential)]
public struct TConstants
{
public float Time;
}
}

View file

@ -125,22 +125,7 @@ Changed via /cres");
private void DrawGameTab() private void DrawGameTab()
{ {
var unsupportedSysScale = !Service.GameSize.ConfigGraphicsRezoType.IsSupported(); using var imTab = ImRaii.TabItem($"Gameplay##GameTab");
var blinkTime = (DateTime.UtcNow.Ticks % TimeSpan.TicksPerHour) / (float) TimeSpan.TicksPerSecond;
var blink = (blinkTime % 2) < 1;
if (unsupportedSysScale && blink)
{
ImGui.PushStyleColor(ImGuiCol.Border, 0xFF00FFFF);
}
using var imTab = ImRaii.TabItem($"Gameplay{(unsupportedSysScale ? " \uE0C0" : "")}##GameTab");
if (unsupportedSysScale && blink)
{
ImGui.PopStyleColor();
}
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
@ -162,21 +147,6 @@ Changed via /gres");
return; return;
} }
if (unsupportedSysScale)
{
using var border = ImRaii.PushColor(ImGuiCol.Border, 0xFF00FFFF);
if (ImGui.Button(@$"Your game system configuration is currently stuck on
an unsupported graphics upscaling option: {Service.GameSize.ConfigGraphicsRezoType.ToHumanNameString()}
This can happen after you've replaced your GPU,
or when your graphic driver has encountered problems.
Click here to reset to FSR and fix the scaling slider.##FixDLSS"))
{
Service.GameSize.SetConfigGraphicsRezoType(ResolutionScalingMode.FSR);
}
}
using (ImRaii.Disabled()) using (ImRaii.Disabled())
{ {
ImGui.InputInt2("Current size", ref _displayCurrentGameWH[0]); ImGui.InputInt2("Current size", ref _displayCurrentGameWH[0]);
@ -184,13 +154,22 @@ Click here to reset to FSR and fix the scaling slider.##FixDLSS"))
DrawSizeTabShared(ref _.Game, "Game", scaleOnly: true); DrawSizeTabShared(ref _.Game, "Game", scaleOnly: true);
ImGui.InputFloat("Scale", ref _.Game.Scale, 0.01f, 0.1f, "%.3f", ImGuiInputTextFlags.None); using (_.Game.Scale > 2f ? ImRaii.PushColor(ImGuiCol.Border, 0xff00ffff) : null)
{
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()))
{ {
foreach (var mode in Enum.GetValues<ResolutionScalingMode>()) foreach (var mode in Enum.GetValues<ResolutionScalingMode>())
{ {
if (ImGui.Selectable(mode.ToHumanNameString(), _.ResolutionScalingMode == mode, mode.IsBroken() ? ImGuiSelectableFlags.Disabled : ImGuiSelectableFlags.None)) if (ImGui.Selectable(mode.ToHumanNameString(), _.ResolutionScalingMode == mode, mode.IsUnsupported() ? ImGuiSelectableFlags.Disabled : ImGuiSelectableFlags.None))
{ {
_.ResolutionScalingMode = mode; _.ResolutionScalingMode = mode;
} }
@ -206,15 +185,11 @@ Click here to reset to FSR and fix the scaling slider.##FixDLSS"))
if (Service.DebugConfig.IsDebug) if (Service.DebugConfig.IsDebug)
{ {
if (ImGui.Button("(DEBUG) Set game config to DLSS"))
{
Service.GameSize.SetConfigGraphicsRezoType(ResolutionScalingMode.DLSS);
}
ImGui.Text(Service.GameSize.DebugInfo); ImGui.Text(Service.GameSize.DebugInfo);
} }
} }
private void DrawSizeTabShared(ref ConfigurationV1.SizeConfig size, string name, bool scaleOnly = false) private void DrawSizeTabShared(ref ConfigurationV1.SizeConfig size, string name, bool scaleOnly = false)
{ {
ImGui.Checkbox("Enabled", ref size.IsEnabled); ImGui.Checkbox("Enabled", ref size.IsEnabled);
@ -330,11 +305,11 @@ Not intended to be used with proper fullscreen.");
ImGui.EndCombo(); ImGui.EndCombo();
} }
ImGui.Checkbox("Disable on lag", ref _.DisableOnHang); ImGui.Checkbox("Game hang protection", ref _.DisableOnHang);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
ImGui.SetTooltip(@$"Automatically disables scaling if FPS goes below 1 FPS. ImGui.SetTooltip(@$"Automatically disables scaling if the game hangs for more than a second.
Leave this option enabled if you want this plugin to disable itself as a protection Leave this option enabled if you want this plugin to disable itself as a protection
in case the resolution changes unexpectedly, or in case the resolution goes too high. in case the resolution changes unexpectedly, or in case the resolution goes too high.
@ -342,16 +317,5 @@ in case the resolution changes unexpectedly, or in case the resolution goes too
In the absolute worst case, delete the following file to reset your settings: In the absolute worst case, delete the following file to reset your settings:
%%AppData%%\XIVLauncher\pluginConfigs\{Service.PluginName}.json"); %%AppData%%\XIVLauncher\pluginConfigs\{Service.PluginName}.json");
} }
#if DEBUG
var debug = Service.DebugConfig.IsDebug;
ImGui.Checkbox("DEBUG!", ref debug);
Service.DebugConfig.IsDebug = debug;
#endif
if (Service.DebugConfig.IsDebug)
{
ImGui.Text($"LAG: {Service.Plugin.LagsTotal} {Service.Plugin.Lag}");
}
} }
} }

View file

@ -8,6 +8,27 @@
"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, )",

View file

@ -23,6 +23,15 @@ 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).