Skip to content

Commit 217607f

Browse files
authored
Add a Reflex FPS framerate limit slider (#108)
* Add an fps cap slider * Don't override the fps cap when it's disabled Fixes RTSS fps limiter in the reflex mode * Don't load nvapi early * Fix fps limit not disabling * Rename the config group
1 parent ae0035f commit 217607f

17 files changed

+506
-251
lines changed

OptiScaler/Config.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ bool Config::Reload(std::filesystem::path iniPath)
8989
FGRectHeight = readInt("FrameGen", "RectHeight");
9090
}
9191

92+
// Framerate
93+
{
94+
FramerateLimit = readFloat("Framerate", "FramerateLimit");
95+
}
96+
9297
// FSR
9398
{
9499
if (!FsrVerticalFov.has_value())
@@ -693,6 +698,11 @@ bool Config::SaveIni()
693698
ini.SetValue("FrameGen", "RectHeight", GetIntValue(Instance()->FGRectHeight).c_str());
694699
}
695700

701+
// Framerate
702+
{
703+
ini.SetValue("Framerate", "FramerateLimit", GetFloatValue(Instance()->FramerateLimit).c_str());
704+
}
705+
696706
// Output Scaling
697707
{
698708
ini.SetValue("OutputScaling", "Enabled", GetBoolValue(Instance()->OutputScalingEnabled).c_str());

OptiScaler/Config.h

+4
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ class Config
208208
std::optional<uint32_t> FN_LatencyFlexMode; // conservative - aggressive - reflex ids
209209
std::optional<uint32_t> FN_ForceReflex; // in-game - force disable - force enable
210210

211+
// framerate
212+
bool ReflexAvailable = false;
213+
std::optional<float> FramerateLimit;
214+
211215
// for realtime changes
212216
bool changeBackend = false;
213217
std::string newBackend = "";

OptiScaler/NVNGX_Proxy.h

+115-230
Large diffs are not rendered by default.

OptiScaler/OptiScaler.vcxproj

+5-2
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ copy $(SolutionDir)nvngx.ini $(SolutionDir)x64\Release\a\</Command>
209209
<ClInclude Include="bias\Bias_Dx12.h" />
210210
<ClInclude Include="bias\Bias_Dx11.h" />
211211
<ClInclude Include="bias\precompile\Bias_Shader.h" />
212-
<ClInclude Include="fakenvapi.h" />
212+
<ClInclude Include="nvapi\fakenvapi.h" />
213213
<ClInclude Include="format_transfer\FT_Common.h" />
214214
<ClInclude Include="format_transfer\FT_Dx12.h" />
215215
<ClInclude Include="FfxApi_Proxy.h" />
@@ -218,12 +218,14 @@ copy $(SolutionDir)nvngx.ini $(SolutionDir)x64\Release\a\</Command>
218218
<ClInclude Include="format_transfer\precompile\R8G8B8A8_Shader.h" />
219219
<ClInclude Include="hooks\HooksDx.h" />
220220
<ClInclude Include="hooks\HooksVk.h" />
221+
<ClInclude Include="nvapi\NvApiHooks.h" />
222+
<ClInclude Include="nvapi\NvApiTypes.h" />
221223
<ClInclude Include="output_scaling\OS_Common.h" />
222224
<ClInclude Include="output_scaling\OS_Dx12.h" />
223225
<ClInclude Include="output_scaling\precompile\BCDS_Shader.h" />
224226
<ClInclude Include="output_scaling\precompile\BCUS_Shader.h" />
225227
<ClInclude Include="bicubicscaling\precompile\FSR_Shader.h" />
226-
<ClInclude Include="nvapi\nvapi.h" />
228+
<ClInclude Include="nvapi\external\nvapi.h" />
227229
<ClInclude Include="backends\fsr2_212\FSR2Feature_212.h" />
228230
<ClInclude Include="backends\fsr2_212\FSR2Feature_Dx11On12_212.h" />
229231
<ClInclude Include="backends\fsr2_212\FSR2Feature_Dx12_212.h" />
@@ -268,6 +270,7 @@ copy $(SolutionDir)nvngx.ini $(SolutionDir)x64\Release\a\</Command>
268270
<ClInclude Include="rcas\RCAS_Common.h" />
269271
<ClInclude Include="rcas\RCAS_Dx11.h" />
270272
<ClInclude Include="rcas\RCAS_Dx12.h" />
273+
<ClInclude Include="nvapi\ReflexHooks.h" />
271274
<ClInclude Include="resource.h" />
272275
<ClInclude Include="Util.h" />
273276
<ClInclude Include="backends\xess\XeSSFeature_Dx11.h" />

OptiScaler/OptiScaler.vcxproj.filters

+11-2
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@
134134
<ClInclude Include="backends\dlss\DLSSFeature_Dx11.h">
135135
<Filter>Header Files</Filter>
136136
</ClInclude>
137-
<ClInclude Include="nvapi\nvapi.h">
137+
<ClInclude Include="nvapi\external\nvapi.h">
138138
<Filter>Header Files</Filter>
139139
</ClInclude>
140140
<ClInclude Include="backends\dlss\DLSSFeature_Vk.h">
@@ -269,7 +269,16 @@
269269
<ClInclude Include="XeSS_Proxy.h">
270270
<Filter>Header Files</Filter>
271271
</ClInclude>
272-
<ClInclude Include="fakenvapi.h">
272+
<ClInclude Include="nvapi\fakenvapi.h">
273+
<Filter>Header Files</Filter>
274+
</ClInclude>
275+
<ClInclude Include="nvapi\ReflexHooks.h">
276+
<Filter>Header Files</Filter>
277+
</ClInclude>
278+
<ClInclude Include="nvapi\NvApiHooks.h">
279+
<Filter>Header Files</Filter>
280+
</ClInclude>
281+
<ClInclude Include="nvapi\NvApiTypes.h">
273282
<Filter>Header Files</Filter>
274283
</ClInclude>
275284
</ItemGroup>

OptiScaler/backends/dlss/DLSSFeature.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,6 @@ DLSSFeature::DLSSFeature(unsigned int handleId, NVSDK_NGX_Parameter* InParameter
353353

354354
if (NVNGXProxy::NVNGXModule() != nullptr && !Config::Instance()->DE_Available)
355355
{
356-
HookNvApi();
357356
HookNgxApi(NVNGXProxy::NVNGXModule());
358357
}
359358

OptiScaler/dllmain.cpp

+50-12
Original file line numberDiff line numberDiff line change
@@ -234,14 +234,33 @@ inline static HMODULE LoadLibraryCheck(std::string lcaseLibName)
234234
}
235235

236236
// NvApi64.dll
237-
if (!isWorkingWithEnabler && Config::Instance()->OverrideNvapiDll.value_or(false) && CheckDllName(&lcaseLibName, &nvapiNames))
238-
{
239-
LOG_INFO("{0} call!", lcaseLibName);
237+
if (CheckDllName(&lcaseLibName, &nvapiNames)) {
238+
if (!isWorkingWithEnabler && Config::Instance()->OverrideNvapiDll.value_or(false))
239+
{
240+
LOG_INFO("{0} call!", lcaseLibName);
240241

241-
auto nvapi = LoadNvApi();
242+
LoadNvApi();
243+
auto nvapi = GetModuleHandleA(lcaseLibName.c_str());
242244

243-
if (nvapi != nullptr)
244-
return nvapi;
245+
// Nvapihooks intentionally won't load nvapi so have to make sure it's loaded
246+
if (nvapi != nullptr) {
247+
NvApiHooks::Hook(nvapi);
248+
return nvapi;
249+
}
250+
}
251+
else
252+
{
253+
auto nvapi = GetModuleHandleA(lcaseLibName.c_str());
254+
255+
// Try to load nvapi only from system32, like the original call would
256+
if (nvapi == nullptr)
257+
nvapi = o_LoadLibraryExA(lcaseLibName.c_str(), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
258+
259+
if (nvapi != nullptr)
260+
NvApiHooks::Hook(nvapi);
261+
262+
// AMD without nvapi override should fall through
263+
}
245264
}
246265

247266
// nvngx_dlss
@@ -331,14 +350,33 @@ inline static HMODULE LoadLibraryCheckW(std::wstring lcaseLibName)
331350
}
332351

333352
// NvApi64.dll
334-
if (!isWorkingWithEnabler && Config::Instance()->OverrideNvapiDll.value_or(false) && CheckDllNameW(&lcaseLibName, &nvapiNamesW))
335-
{
336-
LOG_INFO("{0} call!", lcaseLibNameA);
353+
if (CheckDllNameW(&lcaseLibName, &nvapiNamesW)) {
354+
if (!isWorkingWithEnabler && Config::Instance()->OverrideNvapiDll.value_or(false))
355+
{
356+
LOG_INFO("{0} call!", lcaseLibNameA);
337357

338-
auto nvapi = LoadNvApi();
358+
LoadNvApi();
359+
auto nvapi = GetModuleHandleW(lcaseLibName.c_str());
339360

340-
if (nvapi != nullptr)
341-
return nvapi;
361+
// Nvapihooks intentionally won't load nvapi so have to make sure it's loaded
362+
if (nvapi != nullptr) {
363+
NvApiHooks::Hook(nvapi);
364+
return nvapi;
365+
}
366+
}
367+
else
368+
{
369+
auto nvapi = GetModuleHandleW(lcaseLibName.c_str());
370+
371+
// Try to load nvapi only from system32, like the original call would
372+
if (nvapi == nullptr)
373+
nvapi = o_LoadLibraryExW(lcaseLibName.c_str(), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
374+
375+
if (nvapi != nullptr)
376+
NvApiHooks::Hook(nvapi);
377+
378+
// AMD without nvapi override should fall through
379+
}
342380
}
343381

344382
// Hooks

OptiScaler/hooks/HooksDx.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1751,6 +1751,8 @@ static HRESULT Present(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags
17511751
if (Config::Instance()->CurrentFeature != nullptr)
17521752
fgPresentedFrame = Config::Instance()->CurrentFeature->FrameCount();
17531753

1754+
ReflexHooks::update(FrameGen_Dx12::fgIsActive);
1755+
17541756
// release used objects
17551757
if (cq != nullptr)
17561758
cq->Release();

OptiScaler/hooks/HooksDx.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
#endif
2828

2929
#include "../FfxApi_Proxy.h"
30-
#include "../NVNGX_Proxy.h"
30+
#include "../nvapi/fakenvapi.h"
31+
#include "../nvapi/ReflexHooks.h"
3132
#include <dx12/ffx_api_dx12.h>
3233
#include <ffx_framegeneration.h>
3334

OptiScaler/imgui/imgui_common.cpp

+19-1
Original file line numberDiff line numberDiff line change
@@ -1344,7 +1344,7 @@ void ImGuiCommon::RenderMenu()
13441344
bool fpsLimitVsync = Config::Instance()->DE_FramerateLimitVsync.value_or(false);
13451345
if (ImGui::Checkbox("VSync", &fpsLimitVsync))
13461346
Config::Instance()->DE_FramerateLimitVsync = fpsLimitVsync;
1347-
ShowHelpMarker("Limit FPS to your monitor's refresh rate\nNot really vsync");
1347+
ShowHelpMarker("Limit FPS to your monitor's refresh rate");
13481348

13491349
if (Config::Instance()->DE_DynamicLimitAvailable.has_value() && Config::Instance()->DE_DynamicLimitAvailable.value() > 0)
13501350
{
@@ -1434,6 +1434,24 @@ void ImGuiCommon::RenderMenu()
14341434
}
14351435
}
14361436

1437+
// Reflex ---------------------
1438+
if (!Config::Instance()->DE_Available && Config::Instance()->ReflexAvailable)
1439+
{
1440+
ImGui::SeparatorText("Framerate");
1441+
1442+
// set inital value
1443+
if (_limitFps == INFINITY)
1444+
_limitFps = Config::Instance()->FramerateLimit.value_or(0);
1445+
1446+
ImGui::SliderFloat("FPS Limit", &_limitFps, 0, 200, "%.0f");
1447+
1448+
if (ImGui::Button("Apply Limit")) {
1449+
Config::Instance()->FramerateLimit = _limitFps;
1450+
}
1451+
1452+
ShowHelpMarker("Currently uses Reflex to limit FPS\nbe sure the game supports it and you have it enabled\non AMD cards you can use fakenvapi to substitute Reflex");
1453+
}
1454+
14371455
// OUTPUT SCALING -----------------------------
14381456
if (Config::Instance()->Api == NVNGX_DX12 || Config::Instance()->Api == NVNGX_DX11)
14391457
{

OptiScaler/imgui/imgui_common.h

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class ImGuiCommon
3434
// dlss enabler
3535
inline static int _deLimitFps = 500;
3636

37+
// reflex
38+
inline static float _limitFps = INFINITY;
39+
3740
// fsr3x
3841
inline static int _fsr3xIndex = -1;
3942

OptiScaler/nvapi/NvApiHooks.h

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#pragma once
2+
3+
#include "../pch.h"
4+
#include "NvApiTypes.h"
5+
#include "ReflexHooks.h"
6+
#include "fakenvapi.h"
7+
8+
class NvApiHooks {
9+
public:
10+
// NvAPI_GPU_GetArchInfo hooking based on Nukem's spoofing code here
11+
// https://github.com/Nukem9/dlssg-to-fsr3/blob/89ddc8c1cce4593fb420e633a06605c3c4b9c3cf/source/wrapper_generic/nvapi.cpp#L50
12+
13+
inline static NvApiTypes::PFN_NvApi_QueryInterface OriginalNvAPI_QueryInterface = nullptr;
14+
inline static NvApiTypes::PfnNvAPI_GPU_GetArchInfo OriginalNvAPI_GPU_GetArchInfo = nullptr;
15+
16+
inline static uint32_t __stdcall HookedNvAPI_GPU_GetArchInfo(void* GPUHandle, NV_GPU_ARCH_INFO* ArchInfo)
17+
{
18+
if (OriginalNvAPI_GPU_GetArchInfo)
19+
{
20+
const auto status = OriginalNvAPI_GPU_GetArchInfo(GPUHandle, ArchInfo);
21+
22+
if (status == 0 && ArchInfo)
23+
{
24+
LOG_DEBUG("From api arch: {0:X} impl: {1:X} rev: {2:X}!", ArchInfo->architecture, ArchInfo->implementation, ArchInfo->revision);
25+
26+
// for 16xx cards
27+
if (ArchInfo->architecture == NV_GPU_ARCHITECTURE_TU100 && ArchInfo->implementation > NV_GPU_ARCH_IMPLEMENTATION_TU106)
28+
{
29+
ArchInfo->implementation = NV_GPU_ARCH_IMPLEMENTATION_TU106;
30+
ArchInfo->implementation_id = NV_GPU_ARCH_IMPLEMENTATION_TU106;
31+
32+
LOG_INFO("Spoofed arch: {0:X} impl: {1:X} rev: {2:X}!", ArchInfo->architecture, ArchInfo->implementation, ArchInfo->revision);
33+
}
34+
//else if (ArchInfo->architecture < NV_GPU_ARCHITECTURE_TU100 && ArchInfo->architecture >= NV_GPU_ARCHITECTURE_GP100)
35+
//{
36+
// LOG_INFO("Spoofing below 16xx arch: {0:X} impl: {1:X} rev: {2:X}!", ArchInfo->architecture, ArchInfo->implementation, ArchInfo->revision);
37+
38+
// ArchInfo->architecture = NV_GPU_ARCHITECTURE_TU100;
39+
// ArchInfo->architecture_id = NV_GPU_ARCHITECTURE_TU100;
40+
// ArchInfo->implementation = NV_GPU_ARCH_IMPLEMENTATION_TU106;
41+
// ArchInfo->implementation_id = NV_GPU_ARCH_IMPLEMENTATION_TU106;
42+
43+
// LOG_INFO("Spoofed arch: {0:X} impl: {1:X} rev: {2:X}!", ArchInfo->architecture, ArchInfo->implementation, ArchInfo->revision);
44+
//}
45+
}
46+
47+
return status;
48+
}
49+
50+
return 0xFFFFFFFF;
51+
}
52+
53+
inline static void* __stdcall HookedNvAPI_QueryInterface(NvApiTypes::NV_INTERFACE InterfaceId)
54+
{
55+
switch (InterfaceId) {
56+
case NvApiTypes::NV_INTERFACE::D3D_SetSleepMode:
57+
case NvApiTypes::NV_INTERFACE::D3D_Sleep:
58+
case NvApiTypes::NV_INTERFACE::D3D_GetLatency:
59+
case NvApiTypes::NV_INTERFACE::D3D_SetLatencyMarker:
60+
case NvApiTypes::NV_INTERFACE::D3D12_SetAsyncFrameMarker:
61+
return ReflexHooks::hookReflex(OriginalNvAPI_QueryInterface, InterfaceId);
62+
default:
63+
ReflexHooks::hookReflex(OriginalNvAPI_QueryInterface, InterfaceId);
64+
}
65+
66+
const auto functionPointer = OriginalNvAPI_QueryInterface(InterfaceId);
67+
68+
if (functionPointer)
69+
{
70+
switch (InterfaceId) {
71+
case NvApiTypes::NV_INTERFACE::GPU_GetArchInfo:
72+
if (!Config::Instance()->DE_Available) {
73+
OriginalNvAPI_GPU_GetArchInfo = static_cast<NvApiTypes::PfnNvAPI_GPU_GetArchInfo>(functionPointer);
74+
return &HookedNvAPI_GPU_GetArchInfo;
75+
}
76+
break;
77+
}
78+
}
79+
80+
return functionPointer;
81+
}
82+
83+
// Requires HMODULE to make sure nvapi is loaded before calling this function
84+
inline static void Hook(HMODULE nvapiModule)
85+
{
86+
if (OriginalNvAPI_QueryInterface != nullptr)
87+
return;
88+
89+
if (nvapiModule == nullptr) {
90+
LOG_ERROR("Hook called with a nullptr nvapi module");
91+
return;
92+
}
93+
94+
LOG_DEBUG("Trying to hook NvApi");
95+
96+
OriginalNvAPI_QueryInterface = (NvApiTypes::PFN_NvApi_QueryInterface)GetProcAddress(nvapiModule, "nvapi_QueryInterface");
97+
98+
LOG_DEBUG("OriginalNvAPI_QueryInterface = {0:X}", (unsigned long long)OriginalNvAPI_QueryInterface);
99+
100+
if (OriginalNvAPI_QueryInterface != nullptr)
101+
{
102+
LOG_INFO("NvAPI_QueryInterface found, hooking!");
103+
fakenvapi::Init((fakenvapi::PFN_Fake_QueryInterface&)OriginalNvAPI_QueryInterface);
104+
105+
DetourTransactionBegin();
106+
DetourUpdateThread(GetCurrentThread());
107+
DetourAttach(&(PVOID&)OriginalNvAPI_QueryInterface, HookedNvAPI_QueryInterface);
108+
DetourTransactionCommit();
109+
}
110+
}
111+
112+
inline static void Unhook() {
113+
DetourTransactionBegin();
114+
DetourUpdateThread(GetCurrentThread());
115+
116+
if (OriginalNvAPI_QueryInterface != nullptr)
117+
{
118+
DetourDetach(&(PVOID&)OriginalNvAPI_QueryInterface, HookedNvAPI_QueryInterface);
119+
OriginalNvAPI_QueryInterface = nullptr;
120+
}
121+
122+
DetourTransactionCommit();
123+
}
124+
};

OptiScaler/nvapi/NvApiTypes.h

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include "../pch.h"
4+
#include <dxgi.h>
5+
#include <d3d12.h>
6+
#include "external/nvapi.h"
7+
8+
// Separate to break up a circular dependency
9+
class NvApiTypes {
10+
public:
11+
enum class NV_INTERFACE : uint32_t
12+
{
13+
GPU_GetArchInfo = 0xD8265D24,
14+
D3D12_SetRawScgPriority = 0x5DB3048A,
15+
D3D_SetSleepMode = 0xac1ca9e0,
16+
D3D_Sleep = 0x852cd1d2,
17+
D3D_GetLatency = 0x1a587f9c,
18+
D3D_SetLatencyMarker = 0xd9984c05,
19+
D3D12_SetAsyncFrameMarker = 0x13c98f73,
20+
};
21+
22+
typedef void* (__stdcall* PFN_NvApi_QueryInterface)(NV_INTERFACE InterfaceId);
23+
24+
using PfnNvAPI_GPU_GetArchInfo = uint32_t(__stdcall*)(void* GPUHandle, NV_GPU_ARCH_INFO* ArchInfo);
25+
};

0 commit comments

Comments
 (0)