Add FSR 4.1.1 support

This commit is contained in:
FakeMichau
2026-06-22 16:30:04 +02:00
parent 2fa2ce9ace
commit abc6ecba08
21 changed files with 409 additions and 85 deletions
+7 -5
View File
@@ -604,14 +604,16 @@ UseReactiveMaskForTransparency=auto
; 0.0 - 0.9 - Default (auto) is 0.45
DlssReactiveMaskBias=auto
; Forces Opti to try to use FSR4 in case the GPU detection says otherwise
; true or false - Default (auto) is false
Fsr4ForceCapable=auto
; Forces Opti to try to use a specific FSR 4 model
; Don't fool yourself when trying to force FP8 on actually unsupported GPUs, driver has a FSR 3 fallback
; 0 = No override | 1 = FP8 | 2 = INT8
; From 0 to 2 - Default (auto) is 0
Fsr4ForceModel=auto
; Select FSR4 model to use
; Select internal FSR4 preset to use
; 0 = For FSR Native AA, 1 = Ultra Quality/Quality, 2 = Balanced, 3 = Performance, 4 = DRS, 5 = Ultra Performance
; From 0 to 5 - Default (auto) is game's default
Fsr4Model=auto
Fsr4Preset=auto
; Enable FSR4 Watermark
; true or false - Default (auto) is false
+10 -5
View File
@@ -262,12 +262,17 @@ bool Config::Reload(std::filesystem::path iniPath)
FfxFGIndex.set_from_config(readInt("FSR", "FGIndex"));
FsrUseMaskForTransparency.set_from_config(readBool("FSR", "UseReactiveMaskForTransparency"));
DlssReactiveMaskBias.set_from_config(readFloat("FSR", "DlssReactiveMaskBias"));
Fsr4ForceCapable.set_from_config(readBool("FSR", "Fsr4ForceCapable"));
if (auto v = readEnum<FSR4Support>("FSR", "Fsr4ForceModel"))
Fsr4ForceModel.set_from_config(*v);
else
Fsr4ForceModel.reset();
Fsr4EnableWatermark.set_from_config(readBool("FSR", "Fsr4EnableWatermark"));
Fsr4DoNotLoadAmdxc64.set_from_config(readBool("FSR", "Fsr4DoNotLoadAmdxc64"));
if (auto setting = readInt("FSR", "Fsr4Model"); setting.has_value() && setting >= 0 && setting <= 5)
Fsr4Model.set_from_config(setting);
if (auto setting = readInt("FSR", "Fsr4Preset"); setting.has_value() && setting >= 0 && setting <= 5)
Fsr4Preset.set_from_config(setting);
FsrNonLinearColorSpace.set_from_config(readBool("FSR", "FsrNonLinearColorSpace"));
FsrNonLinearPQ.set_from_config(readBool("FSR", "FsrNonLinearPQ"));
@@ -1052,8 +1057,8 @@ bool Config::SaveIni()
GetBoolValue(Instance()->FsrUseMaskForTransparency.value_for_config()).c_str());
ini.SetValue("FSR", "DlssReactiveMaskBias",
GetFloatValue(Instance()->DlssReactiveMaskBias.value_for_config()).c_str());
ini.SetValue("FSR", "Fsr4ForceCapable", GetBoolValue(Instance()->Fsr4ForceCapable.value_for_config()).c_str());
ini.SetValue("FSR", "Fsr4Model", GetIntValue(Instance()->Fsr4Model.value_for_config()).c_str());
ini.SetValue("FSR", "Fsr4ForceModel", GetIntValue(Instance()->Fsr4ForceModel.value_for_config()).c_str());
ini.SetValue("FSR", "Fsr4Preset", GetIntValue(Instance()->Fsr4Preset.value_for_config()).c_str());
ini.SetValue("FSR", "Fsr4EnableWatermark",
GetBoolValue(Instance()->Fsr4EnableWatermark.value_for_config()).c_str());
ini.SetValue("FSR", "Fsr4DoNotLoadAmdxc64",
+2 -2
View File
@@ -436,8 +436,8 @@ class Config
CustomOptional<float> FsrMinDisOccAcc { -0.333f };
// FSR4
CustomOptional<bool> Fsr4ForceCapable { false };
CustomOptional<uint32_t, NoDefault> Fsr4Model;
CustomOptional<FSR4Support> Fsr4ForceModel { FSR4Support::None };
CustomOptional<uint32_t, NoDefault> Fsr4Preset;
CustomOptional<bool> Fsr4EnableWatermark { false };
CustomOptional<bool> Fsr4DoNotLoadAmdxc64 { false };
+1 -1
View File
@@ -36,7 +36,7 @@ std::string ApiUpscalerInputName(ApiUpscalerInput upscaler)
std::string UpscalerDisplayName(Upscaler upscaler, API api)
{
bool fsr4Capable = IdentifyGpu::getPrimaryGpu().fsr4Capable;
bool fsr4Capable = IdentifyGpu::getPrimaryGpu().fsr4Support != FSR4Support::None;
switch (upscaler)
{
+8
View File
@@ -91,6 +91,14 @@ enum class SharpenShader
LocalContrastDepthAware
};
enum class FSR4Support : uint8_t
{
None = 0,
FP8 = 1,
INT8 = 2,
Count
};
std::string ApiUpscalerInputName(ApiUpscalerInput upscaler);
std::string UpscalerDisplayName(Upscaler upscaler, API api = API::NotSelected);
+2 -1
View File
@@ -60,6 +60,7 @@ enum class WorkingMode : uint32_t
enum class PostCode : uint32_t
{
SlPluginsAlreadyInMemory,
TryingFsr4Fp8OnUnsupported,
_
};
@@ -241,7 +242,7 @@ class State
std::vector<uint64_t> ffxUpscalerVersionIds {};
std::vector<const char*> ffxFGVersionNames {};
std::vector<uint64_t> ffxFGVersionIds {};
std::optional<uint32_t> currentFsr4Model {};
std::optional<uint32_t> currentFsr4Preset {};
// Linux checks
bool isRunningOnLinux = false;
+14
View File
@@ -1719,6 +1719,20 @@ DWORD WINAPI getGpuInfo(LPVOID hModuleVoid)
if (primaryGpu.vendorId == VendorId::AMD)
Amdxc64Hooks::Init();
if (Config::Instance()->Fsr4ForceModel.value_or_default() == FSR4Support::INT8)
{
// We need spoofing hooks for FFX but want to avoid spoofing for the rest of the game
if (!Config::Instance()->DxgiSpoofing.value_or_default())
{
std::wstring wname = string_to_wstring(primaryGpu.name);
Config::Instance()->SpoofedVendorId.set_volatile_value(primaryGpu.vendorId);
Config::Instance()->SpoofedDeviceId.set_volatile_value(primaryGpu.deviceId);
Config::Instance()->SpoofedGPUName.set_volatile_value(wname);
}
Config::Instance()->DxgiSpoofing.set_volatile_value(true);
}
// If DX12 already loaded then grab the full GPU info right away
if (hModuleVoid)
IdentifyGpu::updateD3d12Capabilities();
+41 -4
View File
@@ -7,6 +7,7 @@ PFN_getModelBlob FSR4ModelSelection::o_getModelBlobSDK = nullptr;
PFN_getModelBlob FSR4ModelSelection::o_getModelBlobDriver = nullptr;
PFN_createModel FSR4ModelSelection::o_createModelSDK = nullptr;
PFN_createModel FSR4ModelSelection::o_createModelDriver = nullptr;
PFN_createModel2 FSR4ModelSelection::o_createModelDriver2 = nullptr;
uint32_t getCorrectedPreset(uint32_t preset)
{
@@ -24,12 +25,12 @@ uint32_t getCorrectedPreset(uint32_t preset)
correctedPreset = 1;
}
if (Config::Instance()->Fsr4Model.has_value())
if (Config::Instance()->Fsr4Preset.has_value())
{
correctedPreset = Config::Instance()->Fsr4Model.value();
correctedPreset = Config::Instance()->Fsr4Preset.value();
}
State::Instance().currentFsr4Model = correctedPreset;
State::Instance().currentFsr4Preset = correctedPreset;
return correctedPreset;
}
@@ -78,6 +79,17 @@ uint64_t FSR4ModelSelection::hkcreateModelDriver(void* context, uint32_t preset)
return result;
}
uint64_t FSR4ModelSelection::hkcreateModelDriver2(void* context, uint32_t preset, void** model)
{
LOG_FUNC();
preset = getCorrectedPreset(preset);
auto result = o_createModelDriver2(context, preset, model);
return result;
}
void FSR4ModelSelection::Hook(HMODULE module, FSR4Source source)
{
if (module == nullptr)
@@ -238,7 +250,32 @@ void FSR4ModelSelection::Hook(HMODULE module, FSR4Source source)
}
}
if (!o_createModelDriver && !o_getModelBlobDriver && source == FSR4Source::DriverDll)
if (!o_createModelDriver && !o_createModelDriver2 && source == FSR4Source::DriverDll)
{
// From amdxcffx64 2.3.0
const char* pattern = "48 8B C4 48 89 58 ? 55 56 57 41 54 41 55 41 56 41 57 48 8D A8 ? ? ? ? 48 81 EC ? ? ? ? "
"0F 29 70 ? 0F 29 78 ? 48 8B 05 ? ? ? ? 48 33 C4 48 89 85 ? ? ? ? 4D 8B E8 8B FA 48 8B";
o_createModelDriver2 = (PFN_createModel2) scanner::GetAddress(module, pattern);
if (o_createModelDriver2)
{
LOG_DEBUG("Hooking model selection, o_createModelDriver2: {:X}", (uintptr_t) o_createModelDriver2);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&) o_createModelDriver2, hkcreateModelDriver2);
auto detourResult = DetourTransactionCommit();
if (detourResult != NO_ERROR)
{
LOG_ERROR("Failed to attach detour: {:X}", detourResult);
o_createModelDriver2 = nullptr;
}
}
}
if (!o_createModelDriver && !o_getModelBlobDriver && !o_createModelDriver2 && source == FSR4Source::DriverDll)
LOG_ERROR("Couldn't hook model selection from the driver dll");
else if (!o_createModelSDK && !o_getModelBlobSDK && source == FSR4Source::SDK)
LOG_ERROR("Couldn't hook model selection from the SDK dll");
+3
View File
@@ -4,6 +4,7 @@
typedef uint64_t (*PFN_getModelBlob)(uint32_t preset, uint64_t unknown, uint64_t* source, uint64_t* size);
typedef uint64_t (*PFN_createModel)(void* context, uint32_t preset);
typedef uint64_t (*PFN_createModel2)(void* context, uint32_t preset, void** model);
enum class FSR4Source
{
@@ -21,6 +22,8 @@ class FSR4ModelSelection
static uint64_t hkcreateModelDriver(void* context, uint32_t preset);
static PFN_createModel o_createModelSDK;
static PFN_createModel o_createModelDriver;
static uint64_t hkcreateModelDriver2(void* context, uint32_t preset, void** model);
static PFN_createModel2 o_createModelDriver2;
public:
static void Hook(HMODULE module, FSR4Source source);
+52 -1
View File
@@ -2,6 +2,7 @@
#include "FSR4Upgrade.h"
#include <proxies/FfxApi_Proxy.h>
#include <misc/IdentifyGpu.h>
struct ffxProviderInterface
{
@@ -39,6 +40,21 @@ HRESULT STDMETHODCALLTYPE AmdExtFfxApi::UpdateFfxApiProvider(void* pData, uint32
if (effectType >= FFXStructType::Unknown)
effect = "???";
static std::optional<bool> sdkSupportsInt8;
static std::optional<bool> amdxcffx64SupportsInt8;
if (effectType == FFXStructType::Upscaling && !sdkSupportsInt8.has_value())
{
wchar_t sdkDllPath[MAX_PATH] = { 0 };
GetModuleFileNameW(callerModule, sdkDllPath, MAX_PATH);
Util::version_t sdkVersion;
Util::GetFileVersion(sdkDllPath, nullptr, &sdkVersion);
sdkSupportsInt8 = sdkVersion >= Util::version_t(4, 1, 1, 0);
IdentifyGpu::updateInt8Support(sdkSupportsInt8, amdxcffx64SupportsInt8);
}
if (o_UpdateFfxApiProvider == nullptr)
{
FSR4Upgrade::moduleAmdxcffx64 = nullptr;
@@ -77,10 +93,23 @@ HRESULT STDMETHODCALLTYPE AmdExtFfxApi::UpdateFfxApiProvider(void* pData, uint32
if (FSR4Upgrade::moduleAmdxcffx64)
{
FSR4ModelSelection::Hook(FSR4Upgrade::moduleAmdxcffx64, FSR4Source::DriverDll);
wchar_t driverDllPath[MAX_PATH] = { 0 };
GetModuleFileNameW(FSR4Upgrade::moduleAmdxcffx64, driverDllPath, MAX_PATH);
Util::version_t amdxcffx64Version;
Util::GetFileVersion(driverDllPath, &amdxcffx64Version);
amdxcffx64SupportsInt8 = amdxcffx64Version >= Util::version_t(2, 3, 0, 0);
IdentifyGpu::updateInt8Support(sdkSupportsInt8, amdxcffx64SupportsInt8);
}
else
{
LOG_WARN("Failed to load amdxcffx64.dll");
amdxcffx64SupportsInt8 = false;
IdentifyGpu::updateInt8Support(sdkSupportsInt8, amdxcffx64SupportsInt8);
return E_NOINTERFACE;
}
@@ -96,6 +125,12 @@ HRESULT STDMETHODCALLTYPE AmdExtFfxApi::UpdateFfxApiProvider(void* pData, uint32
}
}
// Prevents the use of FP8 FG on unsupported cards
// As a consequence, can't use MLFG on RDNA4 when forcing INT8
// IdentifyGpu would need to have a field for pre-spoofed fsr4 support
if (effectType == FFXStructType::FG && fsr4Support != FSR4Support::FP8)
return E_NOINTERFACE;
// Result 0x80004002 (E_NOINTERFACE) basically means that amdxcffx64 doesn't have a provider for that effect
if ((effectType == FFXStructType::FG || effectType == FFXStructType::Upscaling ||
effectType == FFXStructType::SwapchainDX12) &&
@@ -133,16 +168,25 @@ HRESULT STDMETHODCALLTYPE AmdExtFfxApi::UpdateFfxApiProvider(void* pData, uint32
HRESULT STDMETHODCALLTYPE AmdExtD3DShaderIntrinsics::GetInfo(void* ShaderIntrinsicsInfo)
{
if (!State::Instance().isRunningOnLinux && Amdxc64Hooks::o_amdExtD3DShaderIntrinsics)
return Amdxc64Hooks::o_amdExtD3DShaderIntrinsics->GetInfo(ShaderIntrinsicsInfo);
LOG_FUNC();
return S_OK;
}
HRESULT STDMETHODCALLTYPE AmdExtD3DShaderIntrinsics::CheckSupport(AmdExtD3DShaderIntrinsicsSupport intrinsic)
{
if (!State::Instance().isRunningOnLinux && Amdxc64Hooks::o_amdExtD3DShaderIntrinsics)
return Amdxc64Hooks::o_amdExtD3DShaderIntrinsics->CheckSupport(intrinsic);
LOG_TRACE(": {}", magic_enum::enum_name(intrinsic));
return S_OK;
}
HRESULT STDMETHODCALLTYPE AmdExtD3DShaderIntrinsics::Enable()
{
if (!State::Instance().isRunningOnLinux && Amdxc64Hooks::o_amdExtD3DShaderIntrinsics)
return Amdxc64Hooks::o_amdExtD3DShaderIntrinsics->Enable();
LOG_FUNC();
return S_OK;
}
@@ -156,7 +200,11 @@ HRESULT STDMETHODCALLTYPE AmdExtD3DDevice8::GetWaveMatrixProperties(uint64_t* co
waveMatrixProperties->nSize = 16;
waveMatrixProperties->kSize = 16;
waveMatrixProperties->aType = fp8;
if (fsr4Support == FSR4Support::FP8)
waveMatrixProperties->aType = fp8;
else
waveMatrixProperties->aType = float16; // Just anything to fail the checks
waveMatrixProperties->bType = fp8;
waveMatrixProperties->cType = float32;
@@ -164,5 +212,8 @@ HRESULT STDMETHODCALLTYPE AmdExtD3DDevice8::GetWaveMatrixProperties(uint64_t* co
waveMatrixProperties->saturatingAccumulation = false;
// TODO: fill out the rest when improving AmdExtD3DDevice8 support
*count = 1;
return S_OK;
}
+4
View File
@@ -4,6 +4,8 @@
struct AmdExtFfxApi : public IAmdExtFfxApi
{
FSR4Support fsr4Support {};
PFN_UpdateFfxApiProvider o_UpdateFfxApiProvider = nullptr;
PFN_UpdateFfxApiProviderEx o_UpdateFfxApiProviderEx = nullptr;
@@ -34,6 +36,8 @@ struct AmdExtD3DShaderIntrinsics : public IAmdExtD3DShaderIntrinsics
struct AmdExtD3DDevice8 : public IAmdExtD3DDevice8
{
FSR4Support fsr4Support {};
STUB(1)
STUB(2)
STUB(3)
+26 -3
View File
@@ -12,6 +12,10 @@
struct AmdExtD3DFactory : public IAmdExtD3DFactory
{
bool linux = false;
FSR4Support fsr4Support {};
bool fsr4ForcedSupport = false;
HRESULT STDMETHODCALLTYPE CreateInterface(IUnknown* pOuter, REFIID riid, void** ppvObject) override
{
if (riid == __uuidof(IAmdExtD3DShaderIntrinsics))
@@ -21,14 +25,21 @@ struct AmdExtD3DFactory : public IAmdExtD3DFactory
*ppvObject = Amdxc64Hooks::amdExtD3DShaderIntrinsics;
Amdxc64Hooks::o_amdExtD3DFactory->CreateInterface(pOuter, riid,
(void**) &Amdxc64Hooks::o_amdExtD3DShaderIntrinsics);
LOG_INFO("Custom IAmdExtD3DShaderIntrinsics queried, returning custom AmdExtD3DShaderIntrinsics");
return S_OK;
}
else if (riid == __uuidof(IAmdExtD3DDevice8))
// TODO: Implementation too incomplete to always enable it
else if (riid == __uuidof(IAmdExtD3DDevice8) && (linux || fsr4ForcedSupport))
{
if (Amdxc64Hooks::amdExtD3DDevice8 == nullptr)
{
Amdxc64Hooks::amdExtD3DDevice8 = new AmdExtD3DDevice8();
Amdxc64Hooks::amdExtD3DDevice8->fsr4Support = fsr4Support;
}
*ppvObject = Amdxc64Hooks::amdExtD3DDevice8;
@@ -93,6 +104,9 @@ void Amdxc64Hooks::Init()
if (moduleAmdxc64 != nullptr)
{
// Pin the dll so that our hooks stay valid, mainly for Linux
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"amdxc64.dll", &moduleAmdxc64);
LOG_INFO("amdxc64.dll loaded");
o_AmdExtD3DCreateInterface = (PFN_AmdExtD3DCreateInterface) KernelBaseProxy::GetProcAddress_()(
moduleAmdxc64, "AmdExtD3DCreateInterface");
@@ -120,14 +134,20 @@ void Amdxc64Hooks::Init()
HRESULT STDMETHODCALLTYPE Amdxc64Hooks::hkAmdExtD3DCreateInterface(IUnknown* pOuter, REFIID riid, void** ppvObject)
{
const bool runFsr4Upgrade = IdentifyGpu::getPrimaryGpu().fsr4Capable;
const auto primaryGpu = IdentifyGpu::getPrimaryGpu();
const bool runFsr4Upgrade = primaryGpu.fsr4Support != FSR4Support::None;
// Proton bleeding edge ships amdxc64 that is missing some required functions
if (runFsr4Upgrade && riid == __uuidof(IAmdExtD3DFactory) && State::Instance().isRunningOnLinux)
if (runFsr4Upgrade && riid == __uuidof(IAmdExtD3DFactory))
{
// Required for the custom AmdExtFfxApi, lack of it triggers visual glitches
if (amdExtD3DFactory == nullptr)
{
amdExtD3DFactory = new AmdExtD3DFactory();
amdExtD3DFactory->linux = State::Instance().isRunningOnLinux;
amdExtD3DFactory->fsr4Support = primaryGpu.fsr4Support;
amdExtD3DFactory->fsr4ForcedSupport = primaryGpu.fsr4ForcedSupport;
}
*ppvObject = amdExtD3DFactory;
@@ -142,7 +162,10 @@ HRESULT STDMETHODCALLTYPE Amdxc64Hooks::hkAmdExtD3DCreateInterface(IUnknown* pOu
else if (runFsr4Upgrade && riid == __uuidof(IAmdExtFfxApi))
{
if (amdExtFfxApi == nullptr)
{
amdExtFfxApi = new AmdExtFfxApi();
amdExtFfxApi->fsr4Support = primaryGpu.fsr4Support;
}
// Return custom one
*ppvObject = amdExtFfxApi;
+1
View File
@@ -135,6 +135,7 @@ class Amdxc64Hooks
inline static AmdExtD3DDevice8* amdExtD3DDevice8 = nullptr;
inline static AmdExtD3DShaderIntrinsics* amdExtD3DShaderIntrinsics = nullptr;
inline static AmdExtD3DShaderIntrinsics* o_amdExtD3DShaderIntrinsics = nullptr;
inline static AmdExtD3DFactory* amdExtD3DFactory = nullptr;
inline static AmdExtD3DFactory* o_amdExtD3DFactory = nullptr;
inline static AmdExtFfxApi* amdExtFfxApi = nullptr;
+46 -17
View File
@@ -19,7 +19,7 @@ static NTSTATUS hkD3DKMTQueryAdapterInfo(const D3DKMT_QUERYADAPTERINFO* data)
auto result = o_D3DKMTQueryAdapterInfo(data);
// LOG_INFO("Adapter into type: {}", (uint32_t)data->Type);
if (data->Type == KMTQAITYPE_WDDM_2_7_CAPS)
if (data->Type == KMTQAITYPE_WDDM_2_7_CAPS && Config::Instance()->SpoofHAGS.value_or_default())
{
LOG_INFO("Spoofing HAGS 2.7");
@@ -36,7 +36,7 @@ static NTSTATUS hkD3DKMTQueryAdapterInfo(const D3DKMT_QUERYADAPTERINFO* data)
return 0;
}
else if (data->Type == KMTQAITYPE_WDDM_2_9_CAPS)
else if (data->Type == KMTQAITYPE_WDDM_2_9_CAPS && Config::Instance()->SpoofHAGS.value_or_default())
{
LOG_INFO("Spoofing HAGS 2.9");
@@ -51,6 +51,38 @@ static NTSTATUS hkD3DKMTQueryAdapterInfo(const D3DKMT_QUERYADAPTERINFO* data)
d3dkmt_wddm_2_9_caps->HwSchEnabled = 1;
return 0;
}
else if (data->Type == KMTQAITYPE_UMDRIVERPRIVATE && data->PrivateDriverDataSize == 608 &&
Util::WhoIsTheCaller(_ReturnAddress()).starts_with("amd"))
{
LOG_DEBUG("Likely FSR 4 GPU Check");
auto primaryGpu = IdentifyGpu::getPrimaryGpu();
auto amd_gpu_info = static_cast<uint32_t*>(data->pPrivateDriverData);
if (primaryGpu.fsr4ForcedSupport || State::Instance().isRunningOnLinux)
{
// Values as per https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/addrlib/src/amdgpu_asic_addr.h
// Seem to be asic family and asic revision
if (primaryGpu.fsr4Support == FSR4Support::INT8)
{
amd_gpu_info[12] = 145; // gfx11
amd_gpu_info[13] = 1;
return 1;
}
else if (primaryGpu.fsr4Support == FSR4Support::FP8)
{
// Based on 9070xt
amd_gpu_info[12] = 152; // gfx12
amd_gpu_info[13] = 81;
return 1;
}
}
return result;
}
return result;
}
@@ -60,24 +92,21 @@ static void hookGdi32()
{
LOG_FUNC();
if (Config::Instance()->SpoofHAGS.value_or_default())
o_D3DKMTQueryAdapterInfo =
reinterpret_cast<PFN_D3DKMTQueryAdapterInfo>(DetourFindFunction("gdi32.dll", "D3DKMTQueryAdapterInfo"));
if (o_D3DKMTQueryAdapterInfo != nullptr)
{
o_D3DKMTQueryAdapterInfo =
reinterpret_cast<PFN_D3DKMTQueryAdapterInfo>(DetourFindFunction("gdi32.dll", "D3DKMTQueryAdapterInfo"));
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
if (o_D3DKMTQueryAdapterInfo != nullptr)
DetourAttach(&(PVOID&) o_D3DKMTQueryAdapterInfo, hkD3DKMTQueryAdapterInfo);
auto detourResult = DetourTransactionCommit();
if (detourResult != NO_ERROR)
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&) o_D3DKMTQueryAdapterInfo, hkD3DKMTQueryAdapterInfo);
auto detourResult = DetourTransactionCommit();
if (detourResult != NO_ERROR)
{
LOG_ERROR("DetourTransactionCommit error: {:X}", detourResult);
o_D3DKMTQueryAdapterInfo = nullptr;
}
LOG_ERROR("DetourTransactionCommit error: {:X}", detourResult);
o_D3DKMTQueryAdapterInfo = nullptr;
}
}
}
+41 -3
View File
@@ -108,11 +108,17 @@ FARPROC WINAPI KernelHooks::hk_K32_GetProcAddress(HMODULE hModule, LPCSTR lpProc
// 2nd check is amdxcffx64.dll trying to queue amdxc64 but amdxc64 not being loaded.
// Also skip the internal call of amdxc64
if (lpProcName != nullptr && (hModule == amdxc64Mark || hModule == nullptr) &&
lstrcmpA(lpProcName, "AmdExtD3DCreateInterface") == 0 && IdentifyGpu::getPrimaryGpu().fsr4Capable &&
lstrcmpA(lpProcName, "AmdExtD3DCreateInterface") == 0 &&
IdentifyGpu::getPrimaryGpu().fsr4Support != FSR4Support::None &&
Util::GetCallerModule(_ReturnAddress()) != KernelBaseProxy::GetModuleHandleW_()(L"amdxc64.dll"))
{
LOG_TRACE("Giving hkAmdExtD3DCreateInterface");
return (FARPROC) &Amdxc64Hooks::hkAmdExtD3DCreateInterface;
}
else if (hModule == amdxc64Mark)
{
return o_K32_GetProcAddress(KernelBaseProxy::GetModuleHandleW_()(L"amdxc64.dll"), lpProcName);
}
return o_K32_GetProcAddress(hModule, lpProcName);
}
@@ -135,12 +141,16 @@ HMODULE WINAPI KernelHooks::hk_K32_GetModuleHandleA(LPCSTR lpModuleName)
// Therefore it should be safe for us to return a custom implementation when it's not loaded
// This can get removed if Proton starts to ship amdxc64
// For system with amdxc64 loaded - we should've caught the load and hooked it
// For systems without - we provide that app with a fake handle and track the usage that way
auto original = o_K32_GetModuleHandleA(lpModuleName);
auto primaryGpu = IdentifyGpu::getPrimaryGpu();
if (original == nullptr && primaryGpu.fsr4Capable)
if (original == nullptr && primaryGpu.fsr4Support != FSR4Support::None)
{
LOG_INFO("amdxc64.dll is not loaded, giving a fake HMODULE");
LOG_INFO("giving a fake HMODULE for amdxc64.dll");
return amdxc64Mark;
}
@@ -151,6 +161,34 @@ HMODULE WINAPI KernelHooks::hk_K32_GetModuleHandleA(LPCSTR lpModuleName)
return o_K32_GetModuleHandleA(lpModuleName);
}
VALIDATE_HOOK(hk_K32_GetModuleHandleW, Kernel32Proxy::PFN_GetModuleHandleW)
HMODULE WINAPI KernelHooks::hk_K32_GetModuleHandleW(LPCWSTR lpModuleName)
{
if (lpModuleName != NULL)
{
if (wcscmp(lpModuleName, L"amdxc64.dll") == 0)
{
LOG_TRACE("amdxc64.dll call");
// See comments in hk_K32_GetModuleHandleA
auto original = o_K32_GetModuleHandleW(lpModuleName);
auto primaryGpu = IdentifyGpu::getPrimaryGpu();
if (original == nullptr && primaryGpu.fsr4Support != FSR4Support::None)
{
LOG_INFO("giving a fake HMODULE for amdxc64.dll");
return amdxc64Mark;
}
return original;
}
}
return o_K32_GetModuleHandleW(lpModuleName);
}
VALIDATE_HOOK(hk_K32_GetModuleHandleExA, Kernel32Proxy::PFN_GetModuleHandleExA)
BOOL WINAPI KernelHooks::hk_K32_GetModuleHandleExA(DWORD dwFlags, LPCSTR lpModuleName, HMODULE* phModule)
{
+7 -2
View File
@@ -28,6 +28,7 @@ class KernelHooks
inline static Kernel32Proxy::PFN_LoadLibraryExW o_K32_LoadLibraryExW = nullptr;
inline static Kernel32Proxy::PFN_GetProcAddress o_K32_GetProcAddress = nullptr;
inline static Kernel32Proxy::PFN_GetModuleHandleA o_K32_GetModuleHandleA = nullptr;
inline static Kernel32Proxy::PFN_GetModuleHandleW o_K32_GetModuleHandleW = nullptr;
inline static Kernel32Proxy::PFN_GetModuleHandleExA o_K32_GetModuleHandleExA = nullptr;
inline static Kernel32Proxy::PFN_GetModuleHandleExW o_K32_GetModuleHandleExW = nullptr;
inline static Kernel32Proxy::PFN_GetFileAttributesW o_K32_GetFileAttributesW = nullptr;
@@ -42,10 +43,9 @@ class KernelHooks
inline static KernelBaseProxy::PFN_LoadLibraryExW o_KB_LoadLibraryExW = nullptr;
inline static KernelBaseProxy::PFN_GetProcAddress o_KB_GetProcAddress = nullptr;
static constexpr HMODULE amdxc64Mark = HMODULE(0xFFFFFFFF13372137);
static FARPROC WINAPI hk_K32_GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
static HMODULE WINAPI hk_K32_GetModuleHandleA(LPCSTR lpModuleName);
static HMODULE WINAPI hk_K32_GetModuleHandleW(LPCWSTR lpModuleName);
static BOOL WINAPI hk_K32_GetModuleHandleExA(DWORD dwFlags, LPCSTR lpModuleName, HMODULE* phModule);
static BOOL WINAPI hk_K32_GetModuleHandleExW(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE* phModule);
static FARPROC WINAPI hk_KB_GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
@@ -67,6 +67,8 @@ class KernelHooks
static inline std::mutex hookMutexBase;
public:
static constexpr HMODULE amdxc64Mark = HMODULE(0xFFFFFFFF13372137);
static void Hook()
{
std::lock_guard<std::mutex> lock(hookMutex32);
@@ -79,6 +81,9 @@ class KernelHooks
if (o_K32_GetModuleHandleA == nullptr)
o_K32_GetModuleHandleA = Kernel32Proxy::Hook_GetModuleHandleA(hk_K32_GetModuleHandleA);
if (o_K32_GetModuleHandleW == nullptr)
o_K32_GetModuleHandleW = Kernel32Proxy::Hook_GetModuleHandleW(hk_K32_GetModuleHandleW);
#ifdef LOW_LATENCY_INPUTS
if (o_K32_GetModuleHandleExA == nullptr)
o_K32_GetModuleHandleExA = Kernel32Proxy::Hook_GetModuleHandleExA(hk_K32_GetModuleHandleExA);
+1 -1
View File
@@ -622,7 +622,7 @@ static Upscaler GetUpscalerBackend()
if (NVNGXProxy::IsDx12Inited() && primaryGpu.dlssCapable)
upscaler = Upscaler::DLSS;
if (primaryGpu.fsr4Capable)
if (primaryGpu.fsr4Support != FSR4Support::None)
upscaler = Upscaler::FFX;
if (Config::Instance()->Dx12Upscaler.has_value())
+19 -12
View File
@@ -1412,6 +1412,14 @@ void MenuCommon::UpdateVersionAndStartupNotifications(RenderMenuContext& ctx)
ImGui::InsertNotification(notification);
}
if (state.postCodes & PostCode::TryingFsr4Fp8OnUnsupported)
{
ImGuiToast notification { ImGuiToastType::Warning, 10000 };
notification.setTitle("Silly goose detected");
notification.setContent("FSR 4 FP8 only works on AMD");
ImGui::InsertNotification(notification);
}
state.postDone = true;
}
@@ -2565,8 +2573,8 @@ void MenuCommon::RenderActiveUpscalerSettings(RenderMenuContext& ctx)
// Conversion from 0 -> 6 into nullopt + 0 -> 5 is required
uint32_t configModes = 0;
if (config->Fsr4Model.has_value())
configModes = config->Fsr4Model.value_or(0) + 1;
if (config->Fsr4Preset.has_value())
configModes = config->Fsr4Preset.value_or(0) + 1;
if (configModes < 0 || configModes >= models.size())
configModes = 0;
@@ -2575,24 +2583,23 @@ void MenuCommon::RenderActiveUpscalerSettings(RenderMenuContext& ctx)
if (ImGui::BeginTable("nonLinear", 2, ImGuiTableFlags_SizingStretchProp))
{
ImGui::TableNextColumn();
if (ImGui::BeginCombo("Models", selectedModel))
if (ImGui::BeginCombo("Presets", selectedModel))
{
for (int n = 0; n < models.size(); n++)
{
uint32_t selection = 0;
if (config->Fsr4Model.has_value())
selection = config->Fsr4Model.value_or(0) + 1;
if (config->Fsr4Preset.has_value())
selection = config->Fsr4Preset.value_or(0) + 1;
if (ImGui::Selectable(models[n], selection == n))
{
if (n < 1)
config->Fsr4Model.reset();
config->Fsr4Preset.reset();
else
config->Fsr4Model = n - 1;
config->Fsr4Preset = n - 1;
state.newBackend = currentBackend;
MARK_ALL_BACKENDS_CHANGED();
@@ -2615,8 +2622,8 @@ void MenuCommon::RenderActiveUpscalerSettings(RenderMenuContext& ctx)
ImGui::TableNextColumn();
if (state.currentFsr4Model.has_value())
ImGui::Text("Current model: %d", state.currentFsr4Model.value());
if (state.currentFsr4Preset.has_value())
ImGui::Text("Current preset: %d", state.currentFsr4Preset.value());
else
ImGui::Text("Failed to hook");
@@ -4330,8 +4337,8 @@ void MenuCommon::RenderFrameGenerationRuntimeSettings(RenderMenuContext& ctx)
// Hide to reduce confusion, config is still read
bool isUnrealEngine = State::Instance().NVNGX_Engine == NVSDK_NGX_ENGINE_TYPE_UNREAL ||
State::Instance().gameQuirks & GameQuirk::ForceUnrealEngine;
if (!primaryGpu.dlssCapable && !primaryGpu.fsr4Capable && !primaryGpu.usesVkd3dProton &&
!isUnrealEngine)
if (!primaryGpu.dlssCapable && primaryGpu.fsr4Support == FSR4Support::None &&
!primaryGpu.usesVkd3dProton && !isUnrealEngine)
{
if (bool makeDepthCopy = config->MakeDepthCopy.value_or_default();
ImGui::Checkbox("Fix broken visuals", &makeDepthCopy))
+110 -26
View File
@@ -1,11 +1,12 @@
#include "pch.h"
#include "IdentifyGpu.h"
#include <magic_enum.hpp>
#include <include/device_info/device_info.hpp>
#include <proxies/Dxgi_Proxy.h>
#include <proxies/D3d12_Proxy.h>
#include "nvapi/NvApiTypes.h"
#include <magic_enum.hpp>
#include <hooks/Amdxc64_Hooks.h>
using Microsoft::WRL::ComPtr;
@@ -180,8 +181,6 @@ std::vector<GpuInformation> IdentifyGpu::checkGpuInfo()
{
queryNvapi(gpuInfo);
}
SAFE_RELEASE(gpuInfo.d3d12device);
}
return localCachedInfo;
@@ -376,14 +375,18 @@ void IdentifyGpu::updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3
{
LUID luid;
bool usesVkd3dProton = false;
bool fsr4Capable = false;
FSR4Support fsr4Support = FSR4Support::None;
bool fsr4ForcedSupport = false;
};
std::vector<D3d12Result> results;
for (auto& gpuInfo : cache)
{
if (gpuInfo.vendorId != VendorId::AMD && !gpuInfo.usesDxvk)
if (gpuInfo.vendorId != VendorId::AMD && !gpuInfo.usesDxvk &&
Config::Instance()->Fsr4ForceModel.value_or_default() == FSR4Support::None)
{
continue;
}
ComPtr<IDXGIFactory4> factory;
if (FAILED(DxgiProxy::CreateDxgiFactory_()(__uuidof(factory), (IDXGIFactory**) factory.GetAddressOf())))
@@ -401,7 +404,7 @@ void IdentifyGpu::updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3
// D3D12 device is needed to be able to query amdxc and check for vkd3d-proton
ScopedCreatingD3DDevice scopedCreating {};
ScopedSkipVulkanHooks skipVulkanHooks {};
ID3D12Device* localDevice = nullptr;
ComPtr<ID3D12Device> localDevice;
auto createResult = pD3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&localDevice));
if (SUCCEEDED(createResult) && localDevice)
@@ -413,14 +416,17 @@ void IdentifyGpu::updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3
if (localDevice && SUCCEEDED(localDevice->QueryInterface(IID_PPV_ARGS(&vkd3dInterop))))
res.usesVkd3dProton = true;
// Kinda questionable, may need to reconsider
if (Config::Instance()->Fsr4ForceModel.value_or_default() != FSR4Support::None)
{
res.fsr4Support = Config::Instance()->Fsr4ForceModel.value_or_default();
res.fsr4ForcedSupport = true;
}
if (gpuInfo.vendorId == VendorId::AMD)
{
// Kinda questionable, may need to reconsider
if (Config::Instance()->Fsr4ForceCapable.value_or_default())
res.fsr4Capable = true;
// Query vkd3d-proton for extensions it's using to look for the required one for FSR 4
if (!res.fsr4Capable && res.usesVkd3dProton)
if (res.fsr4Support == FSR4Support::None && res.usesVkd3dProton)
{
UINT extensionCount = 0;
@@ -436,7 +442,7 @@ void IdentifyGpu::updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3
// Only RDNA4+
if (!strcmp("VK_EXT_shader_float8", exts[i]))
{
res.fsr4Capable = true;
res.fsr4Support = FSR4Support::FP8;
break;
}
}
@@ -447,14 +453,14 @@ void IdentifyGpu::updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3
// Pre-RDNA4 GPUs on Linux can support FSR 4 but require a special envvar
// check for the envvar and assume everything else is also setup for FSR 4 to work on those
// cards
if (!res.fsr4Capable)
if (res.fsr4Support == FSR4Support::None)
{
const char* envvar = getenv("DXIL_SPIRV_CONFIG");
if (envvar && strstr(envvar, "wmma_rdna3_workaround"))
res.fsr4Capable = true;
res.fsr4Support = FSR4Support::FP8;
}
if (!res.fsr4Capable)
if (res.fsr4Support == FSR4Support::None)
{
auto moduleAmdxc64 = KernelBaseProxy::GetModuleHandleW_()(L"amdxc64.dll");
@@ -471,29 +477,50 @@ void IdentifyGpu::updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3
// Query amdxc for a specific intrinsics support, FSR 4 checks more but hopefully this one
// is enough amdxc on Windows hates vkd3d-proton's device, on Linux it's fine
if (!res.fsr4Capable && localDevice &&
(State::Instance().isRunningOnLinux || !res.usesVkd3dProton) && AmdExtD3DCreateInterface)
if (localDevice && (State::Instance().isRunningOnLinux || !res.usesVkd3dProton) &&
AmdExtD3DCreateInterface)
{
if (SUCCEEDED(AmdExtD3DCreateInterface(localDevice, IID_PPV_ARGS(&amdExtD3DFactory))))
if (SUCCEEDED(AmdExtD3DCreateInterface(localDevice.Get(), IID_PPV_ARGS(&amdExtD3DFactory))))
{
ComPtr<IAmdExtD3DShaderIntrinsics> amdExtD3DShaderIntrinsics = nullptr;
if (amdExtD3DFactory && SUCCEEDED(amdExtD3DFactory->CreateInterface(
localDevice, IID_PPV_ARGS(&amdExtD3DShaderIntrinsics))))
if (amdExtD3DFactory &&
SUCCEEDED(amdExtD3DFactory->CreateInterface(
localDevice.Get(), IID_PPV_ARGS(&amdExtD3DShaderIntrinsics))))
{
HRESULT float8support = amdExtD3DShaderIntrinsics->CheckSupport(
AmdExtD3DShaderIntrinsicsSupport_Float8Conversion);
res.fsr4Capable = float8support == S_OK;
if (float8support == S_OK)
res.fsr4Support = FSR4Support::FP8;
}
}
}
}
// Check for native INT8 support
if (res.fsr4Support == FSR4Support::None)
{
device_info::AdapterId adapterId(gpuInfo.vendorId, gpuInfo.deviceId, gpuInfo.revisionId);
auto cardInfo = device_info::GetCardInfo(adapterId);
if (cardInfo.has_value())
{
if (cardInfo.value().generation == device_info::HwGeneration::kGfx11)
res.fsr4Support = FSR4Support::INT8;
// if (cardInfo.value().generation == device_info::HwGeneration::kGfx11_5)
//{
// res.fsr4Support = FSR4Support::INT8;
// res.fsr4ForcedSupport = true;
// }
}
}
// TODO: could now try to ask amdxcffx for FSR 4 and see if it returns it
// but our FSR 4 upgrade code call this function so it gets complicated
}
localDevice->Release();
results.push_back(res);
}
}
@@ -508,7 +535,8 @@ void IdentifyGpu::updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3
if (IsEqualLUID(gpuInfo.luid, res.luid))
{
gpuInfo.usesVkd3dProton = res.usesVkd3dProton;
gpuInfo.fsr4Capable = res.fsr4Capable;
gpuInfo.fsr4Support = res.fsr4Support;
gpuInfo.fsr4ForcedSupport = res.fsr4ForcedSupport;
break;
}
}
@@ -522,12 +550,68 @@ void IdentifyGpu::updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3
for (auto& gpu : detectedGpus)
{
std::string fsr4Support = "unknown";
if (gpu.fsr4Support == FSR4Support::None)
fsr4Support = "false";
else if (gpu.fsr4Support == FSR4Support::FP8)
fsr4Support = "fp8";
else if (gpu.fsr4Support == FSR4Support::INT8)
fsr4Support = "int8";
if (gpu.fsr4ForcedSupport)
fsr4Support += " (forced)";
gpus += std::format("{}{}\n", indent, gpu.name);
gpus += std::format("{} vendorId: {:X}, deviceId: {:X}, VRAM: {}MB\n", indent, (uint32_t) gpu.vendorId,
gpu.deviceId, gpu.dedicatedVramInBytes / (1024 * 1024));
gpus += std::format("{} dxvk: {}, vkd3d-proton: {}\n", indent, gpu.usesDxvk, gpu.usesVkd3dProton);
gpus += std::format("{} Upscaler support - fsr4: {}, dlss: {}\n", indent, gpu.fsr4Capable, gpu.dlssCapable);
gpus += std::format("{} Upscaler support - fsr4: {}, dlss: {}\n", indent, fsr4Support, gpu.dlssCapable);
}
spdlog::info(gpus);
}
auto primaryGpu = !detectedGpus.empty() ? detectedGpus.front() : GpuInformation {};
if (primaryGpu.vendorId != VendorId::AMD && primaryGpu.fsr4ForcedSupport &&
primaryGpu.fsr4Support == FSR4Support::FP8)
{
State::Instance().postCodes |= PostCode::TryingFsr4Fp8OnUnsupported;
std::scoped_lock lock(mutex);
for (auto& gpuInfo : cache)
{
gpuInfo.fsr4Support = FSR4Support::None;
gpuInfo.fsr4ForcedSupport = false;
}
}
// This means that the user didn't want to force support but we decided that we want to force it
// We need spoofing on but if it's off then this will be too late for most cases to enable it
// But if we only force it for AMD GPUs then that's fine
if (Config::Instance()->Fsr4ForceModel.value_or_default() == FSR4Support::None && primaryGpu.fsr4ForcedSupport)
{
//// We need spoofing hooks for FFX but want to avoid spoofing for the rest of the game
// if (!Config::Instance()->DxgiSpoofing.value_or_default())
//{
// Config::Instance()->SpoofedVendorId.set_volatile_value(primaryGpu.vendorId);
// Config::Instance()->SpoofedDeviceId.set_volatile_value(primaryGpu.deviceId);
// }
// Config::Instance()->DxgiSpoofing.set_volatile_value(true);
}
}
void IdentifyGpu::updateInt8Support(std::optional<bool>& sdkSupportsInt8, std::optional<bool>& amdxcffx64SupportsInt8)
{
if (amdxcffx64SupportsInt8 && !amdxcffx64SupportsInt8.value() && sdkSupportsInt8 && !sdkSupportsInt8.value())
{
LOG_DEBUG("Neither driver nor the sdk supports FSR 4 Int 8, disabling");
std::scoped_lock lock(mutex);
for (auto& gpuInfo : cache)
{
gpuInfo.fsr4Support = FSR4Support::None;
gpuInfo.fsr4ForcedSupport = false;
}
}
}
+3 -2
View File
@@ -61,8 +61,8 @@ struct GpuInformation
std::filesystem::path driverStore {};
// AMD
bool fsr4Capable = false;
ID3D12Device* d3d12device = nullptr;
bool fsr4ForcedSupport = false;
FSR4Support fsr4Support {};
device_info::HwGeneration amdHwGeneration = device_info::HwGeneration::kUndefinedGeneration;
// Nvidia
@@ -93,4 +93,5 @@ class IdentifyGpu
static std::vector<GpuInformation> getAllGpus();
static GpuInformation getPrimaryGpu();
static void updateD3d12Capabilities(D3d12Proxy::PFN_D3D12CreateDevice o_D3D12CreateDevice = nullptr);
static void updateInt8Support(std::optional<bool>& sdkSupportsInt8, std::optional<bool>& amdxcffx64SupportsInt8);
};
+11
View File
@@ -187,6 +187,17 @@ HRESULT DxgiSpoofing::hkGetDesc1(IDXGIAdapter1* This, DXGI_ADAPTER_DESC1* pDesc)
LOG_DEBUG("spoofing");
#endif
}
if (Util::WhoIsTheCaller(_ReturnAddress()).starts_with("amdxcffx64"))
{
const auto primaryGpu = IdentifyGpu::getPrimaryGpu();
if (primaryGpu.fsr4ForcedSupport && primaryGpu.fsr4Support == FSR4Support::INT8)
{
LOG_TRACE("Spoofing vendor AMD for amdxcffx64");
pDesc->VendorId = VendorId::AMD;
}
}
}
AttachToAdapter(This);