Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/FidelityFX.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "FidelityFX.h"
#include "Utils/FileSystem.h"

#include "State.h"
#include "Upscaling.h"
Expand All @@ -8,6 +9,9 @@

ffxFunctions ffxModule;

// Define the static member
std::vector<std::pair<std::string, std::string>> FidelityFX::dllVersions = {};

FfxResource ffxGetResource(ID3D11Resource* dx11Resource,
[[maybe_unused]] wchar_t const* ffxResName,
FfxResourceStates state /*=FFX_RESOURCE_STATE_COMPUTE_READ*/)
Expand All @@ -28,7 +32,12 @@ FfxResource ffxGetResource(ID3D11Resource* dx11Resource,

void FidelityFX::LoadFFX()
{
module = LoadLibrary(L"Data\\SKSE\\Plugins\\FidelityFX\\amd_fidelityfx_dx12.dll");
std::wstring dllPath = std::wstring(FidelityFX::PluginDir) + L"\\amd_fidelityfx_dx12.dll";
module = LoadLibrary(dllPath.c_str());

// Cache all DLL versions in the FidelityFX directory
std::filesystem::path pluginDir = std::filesystem::path(FidelityFX::PluginDir);
FidelityFX::dllVersions = Util::EnumerateDllVersions(pluginDir);

if (module)
ffxLoadFunctions(&ffxModule, module);
Expand All @@ -48,7 +57,10 @@ void FidelityFX::SetupFrameGeneration()
createBackend.device = swapChain->d3d12Device.get();

if (ffx::CreateContext(frameGenContext, nullptr, createFg, createBackend) != ffx::ReturnCode::Ok) {
featureFSR3FG = false;
logger::critical("[FidelityFX] Failed to create frame generation context!");
} else {
featureFSR3FG = true;
}
}

Expand Down Expand Up @@ -184,6 +196,9 @@ void FidelityFX::Present(bool a_useFrameGeneration)
}

frameID++;

// Set isFrameGenActive based on whether FSR3 frame generation is enabled
isFrameGenActive = a_useFrameGeneration;
}

void FidelityFX::CreateFSRResources()
Expand Down
10 changes: 10 additions & 0 deletions src/FidelityFX.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
class FidelityFX
{
public:
static constexpr const wchar_t* PluginDir = L"Data\\SKSE\\Plugins\\FidelityFX";

static FidelityFX* GetSingleton()
{
static FidelityFX singleton;
Expand All @@ -29,6 +31,14 @@ class FidelityFX

FfxFsr3Context fsrContext;

bool featureFSR3FG = false; // whether enabled

// Track if FidelityFX is currently being used for frame generation
bool isFrameGenActive = false;

// Cached DLL version info for FidelityFX plugin directory
static std::vector<std::pair<std::string, std::string>> dllVersions;

void LoadFFX();

void SetupFrameGeneration();
Expand Down
85 changes: 80 additions & 5 deletions src/Streamline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,67 @@

void LoggingCallback(sl::LogType type, const char* msg)
{
// Remove trailing newlines from the raw message
std::string rawMsg(msg);
while (!rawMsg.empty() && (rawMsg.back() == '\n' || rawMsg.back() == '\r'))
rawMsg.pop_back();

// Remove leading bracketed metadata
const char* p = msg;
while (*p == '[') {
const char* close = strchr(p, ']');
if (!close)
break;
p = close + 1;
// Skip whitespace after each bracketed section
while (*p == ' ' || *p == '\t') ++p;
}
// Now p points to the first non-bracketed section (file/line info or message)
std::string cleanMsg(p);
// Trim leading/trailing whitespace and newlines
size_t start = cleanMsg.find_first_not_of(" \t\r\n");
size_t end = cleanMsg.find_last_not_of(" \t\r\n");
if (start != std::string::npos && end != std::string::npos)
cleanMsg = cleanMsg.substr(start, end - start + 1);
else
cleanMsg.clear();

// If the cleaned message is empty or only bracketed tokens, log the raw message
bool onlyBrackets = true;
for (char c : cleanMsg) {
if (c != '[' && c != ']' && c != ' ' && c != '\t') {
onlyBrackets = false;
break;
}
}
if (cleanMsg.empty() || onlyBrackets) {
logger::info("[StreamlineSDK:RAW] {}", rawMsg);
return;
}

// Use a clear prefix
const char* prefix = "[StreamlineSDK]";
switch (type) {
case sl::LogType::eInfo:
logger::info("{}", msg);
logger::info("{} {}", prefix, cleanMsg);
break;
case sl::LogType::eWarn:
logger::warn("{}", msg);
logger::warn("{} {}", prefix, cleanMsg);
break;
case sl::LogType::eError:
logger::error("{}", msg);
logger::error("{} {}", prefix, cleanMsg);
break;
}
}

std::vector<std::pair<std::string, std::string>> Streamline::dllVersions = {};

void Streamline::LoadInterposer()
{
triedInitialization = true;

interposer = LoadLibraryW(L"Data/SKSE/Plugins/Streamline/sl.interposer.dll");
std::wstring interposerPath = std::wstring(Streamline::PluginDir) + L"\\sl.interposer.dll";
interposer = LoadLibraryW(interposerPath.c_str());
if (interposer == nullptr) {
DWORD errorCode = GetLastError();
logger::info("[Streamline] Failed to load interposer: Error Code {0:x}", errorCode);
Expand All @@ -39,6 +82,23 @@ void Streamline::LoadInterposer()
logger::info("[Streamline] Interposer loaded at address: {0:p}", static_cast<void*>(interposer));
}

// Dynamically log all DLL versions in the Streamline plugin directory
std::filesystem::path pluginDir = std::filesystem::path(Streamline::PluginDir);
Streamline::dllVersions.clear();
for (const auto& entry : std::filesystem::directory_iterator(pluginDir)) {
if (entry.is_regular_file() && entry.path().extension() == L".dll") {
const auto& path = entry.path();
auto version = Util::GetDllVersion(path.c_str());
auto name = path.filename().string();
std::string versionStr = version ? Util::GetFormattedVersion(*version) : "Unknown";
Streamline::dllVersions.emplace_back(name, versionStr);
if (version)
logger::info("[Streamline] {} version: {}", name, versionStr);
else
logger::info("[Streamline] {} version: Unknown", name);
}
}

logger::info("[Streamline] Initializing Streamline");

sl::Preferences pref;
Expand All @@ -49,7 +109,19 @@ void Streamline::LoadInterposer()
pref.featuresToLoad = REL::Module::IsVR() ? featuresToLoadVR : featuresToLoad;
pref.numFeaturesToLoad = _countof(featuresToLoad);

pref.logLevel = sl::LogLevel::eOff;
// Set log level from settings
switch (globals::upscaling->settings.streamlineLogLevel) {
case 2:
pref.logLevel = sl::LogLevel::eVerbose;
break;
case 1:
pref.logLevel = sl::LogLevel::eDefault;
break;
case 0:
default:
pref.logLevel = sl::LogLevel::eOff;
break;
}
pref.logMessageCallback = LoggingCallback;
pref.showConsole = false;

Expand Down Expand Up @@ -321,6 +393,9 @@ void Streamline::Present()
static auto currentFrameGenerationMode = sl::DLSSGMode::eOff;
auto frameGenerationMode = upscaling->settings.frameGenerationMode ? sl::DLSSGMode::eOn : sl::DLSSGMode::eOff;

// Set isFrameGenActive based on whether DLSS-G frame generation is enabled
isFrameGenActive = (frameGenerationMode == sl::DLSSGMode::eOn);

if (currentFrameGenerationMode != frameGenerationMode) {
currentFrameGenerationMode = frameGenerationMode;

Expand Down
8 changes: 8 additions & 0 deletions src/Streamline.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ void DestroyDLSSResources();
class Streamline
{
public:
static constexpr const wchar_t* PluginDir = L"Data\\SKSE\\Plugins\\Streamline";

static Streamline* GetSingleton()
{
static Streamline singleton;
Expand All @@ -108,6 +110,9 @@ class Streamline
bool featureDLSSG = false;
bool featureReflex = false;

// Track if Streamline is currently being used for frame generation
bool isFrameGenActive = false;

sl::ViewportHandle viewport{ 0 };

HMODULE interposer = NULL;
Expand Down Expand Up @@ -152,6 +157,9 @@ class Streamline
decltype(&CreateDXGIFactory1) slCreateDXGIFactory1{};
decltype(&D3D11CreateDeviceAndSwapChain) slD3D11CreateDeviceAndSwapChain{};

// Cached DLL version info for Streamline plugin directory
static std::vector<std::pair<std::string, std::string>> dllVersions;

void LoadInterposer();

void CheckFeatures(IDXGIAdapter* a_adapter);
Expand Down
128 changes: 92 additions & 36 deletions src/Upscaling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "DX12SwapChain.h"
#include "Hooks.h"
#include "State.h"
#include <Windows.h>
#include <reshade/reshade.hpp>

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
Expand All @@ -14,7 +15,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
dlssPreset,
frameLimitMode,
frameGenerationMode,
frameGenerationForceEnable);
frameGenerationForceEnable,
streamlineLogLevel);

void Upscaling::DrawSettings()
{
Expand Down Expand Up @@ -88,54 +90,108 @@ void Upscaling::DrawSettings()
}

if (!globals::game::isVR) {
if (ImGui::TreeNodeEx("Frame Generation", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text("Frame Generation interpolates real frames with generated ones for a smoother experience");
ImGui::Text("Uses AMD FSR 3.1 Frame Generation and NVIDIA DLSS Frame Generation technology");
ImGui::Text("Requires a D3D11 to D3D12 proxy which can create compatibility issues");
ImGui::Text("Toggling this setting requires a restart to work correctly");

bool onlyRequiresRestart = true;
bool frameGenAvailable = d3d12Interop && ((globals::streamline && globals::streamline->featureDLSSG) ||
(globals::fidelityFX && globals::fidelityFX->featureFSR3FG));
if (frameGenAvailable) {
if (ImGui::TreeNodeEx("Frame Generation", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text("Frame Generation interpolates real frames with generated ones for a smoother experience");
ImGui::Text("Uses AMD FSR 3.1 Frame Generation and NVIDIA DLSS Frame Generation technology");
if (globals::streamline && globals::streamline->featureDLSSG)
ImGui::Text("NVIDIA DLSS-G Frame Generation is available.");
if (globals::fidelityFX && globals::fidelityFX->featureFSR3FG)
ImGui::Text("AMD FSR 3.1 Frame Generation is available.");
ImGui::Text("Requires a D3D11 to D3D12 proxy which can create compatibility issues");
ImGui::Text("Toggling this setting requires a restart to work correctly");

bool onlyRequiresRestart = true;

if (!isWindowed) {
ImGui::Text("Warning: Requires windowed mode");
onlyRequiresRestart = false;
}

if (!isWindowed) {
ImGui::Text("Warning: Requires windowed mode");
onlyRequiresRestart = false;
}
if (lowRefreshRate && !settings.frameGenerationForceEnable) {
ImGui::Text("Warning: Requires a high refresh rate monitor or Force Enable Frame Generation");
onlyRequiresRestart = false;
}

if (lowRefreshRate && !settings.frameGenerationForceEnable) {
ImGui::Text("Warning: Requires a high refresh rate monitor or Force Enable Frame Generation");
onlyRequiresRestart = false;
}
if (streamlineMissing) {
ImGui::Text("Warning: Streamline is not loaded");
onlyRequiresRestart = false;
}

if (streamlineMissing) {
ImGui::Text("Warning: amd_fidelityfx_dx12.dll is not loaded");
onlyRequiresRestart = false;
}
if (fidelityFXMissing) {
ImGui::Text("Warning: amd_fidelityfx_dx12.dll is not loaded");
onlyRequiresRestart = false;
}

if (fidelityFXMissing) {
ImGui::Text("Warning: Streamline is not loaded");
onlyRequiresRestart = false;
}
if (onlyRequiresRestart && settings.frameGenerationMode && !d3d12Interop)
ImGui::Text("Warning: Requires restart");

if (onlyRequiresRestart && settings.frameGenerationMode && !d3d12Interop)
ImGui::Text("Warning: Requires restart");
std::string backendLabel =
(globals::streamline && globals::streamline->isFrameGenActive) ? "DLSSG" :
(globals::fidelityFX && globals::fidelityFX->isFrameGenActive) ? "FSR3" :
"None";
std::string enabledLabel = "Enabled (" + backendLabel + ")";
const char* toggleModes[] = { "Disabled", "Enabled" };
const char* toggleModesFG[] = { "Disabled", enabledLabel.c_str() };

const char* toggleModes[] = { "Disabled", "Enabled" };
ImGui::SliderInt("Frame Generation", (int*)&settings.frameGenerationMode, 0, 1, toggleModesFG[settings.frameGenerationMode]);

ImGui::SliderInt("Frame Generation", (int*)&settings.frameGenerationMode, 0, 1, std::format("{}", toggleModes[settings.frameGenerationMode]).c_str());
if (!d3d12Interop)
ImGui::BeginDisabled();

if (!d3d12Interop)
ImGui::BeginDisabled();
ImGui::SliderInt("Frame Limit (Variable Refresh Rate)", (int*)&settings.frameLimitMode, 0, 1, std::format("{}", toggleModes[settings.frameLimitMode]).c_str());

ImGui::SliderInt("Frame Limit (Variable Refresh Rate)", (int*)&settings.frameLimitMode, 0, 1, std::format("{}", toggleModes[settings.frameLimitMode]).c_str());
if (!d3d12Interop)
ImGui::EndDisabled();

if (!d3d12Interop)
ImGui::EndDisabled();
ImGui::Text("Allows frame generation to function on low refresh rate monitors");
ImGui::SliderInt("Force Enable Frame Generation", (int*)&settings.frameGenerationForceEnable, 0, 1, std::format("{}", toggleModes[settings.frameGenerationForceEnable]).c_str());

ImGui::Text("Allows frame generation to function on low refresh rate monitors");
ImGui::SliderInt("Force Enable Frame Generation", (int*)&settings.frameGenerationForceEnable, 0, 1, std::format("{}", toggleModes[settings.frameGenerationForceEnable]).c_str());
ImGui::TreePop();
}
} else {
if (ImGui::TreeNodeEx("Frame Generation", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text("Frame Generation is not available on your system.\nThis requires either NVIDIA DLSS-G or AMD FSR 3.1 Frame Generation support and D3D12 interop.");
ImGui::TreePop();
}
}
}

ImGui::TreePop();
if (ImGui::TreeNodeEx("Backend Diagnostics")) {
// Streamline log level selection
const char* logLevels[] = { "Off", "Default", "Verbose" };
int logLevelIdx = static_cast<int>(settings.streamlineLogLevel);
if (ImGui::Combo("Streamline Logging", &logLevelIdx, logLevels, IM_ARRAYSIZE(logLevels))) {
settings.streamlineLogLevel = static_cast<uint>(logLevelIdx);
}
ImGui::TextUnformatted("Changing this requires a restart to take effect.");
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Streamline logging controls the verbosity of NVIDIA Streamline backend logs. Useful for debugging issues with DLSS/DLSS-G.");
}
ImGui::Separator();
// FidelityFX section
if (ImGui::Selectable("AMD FidelityFX DLLs (click to open folder)")) {
ShellExecuteW(nullptr, L"open", FidelityFX::PluginDir, nullptr, nullptr, SW_SHOWNORMAL);
}
std::vector<std::string> headers = { "DLL Name", "Version" };
std::vector<std::vector<std::string>> ffRows;
for (const auto& [name, version] : FidelityFX::dllVersions)
ffRows.push_back({ name, version });
std::vector<Util::TableSortFunc> ffSorters = { nullptr, Util::VersionSortComparator };
Util::ShowSortedStringTable("ffx_dll_versions", headers, ffRows, 0, true, ffSorters);

// Streamline section
if (ImGui::Selectable("NVIDIA Streamline DLLs (click to open folder)")) {
ShellExecuteW(nullptr, L"open", Streamline::PluginDir, nullptr, nullptr, SW_SHOWNORMAL);
}
std::vector<std::vector<std::string>> slRows;
for (const auto& [name, version] : Streamline::dllVersions)
slRows.push_back({ name, version });
std::vector<Util::TableSortFunc> slSorters = { nullptr, Util::VersionSortComparator };
Util::ShowSortedStringTable("sl_dll_versions", headers, slRows, 0, true, slSorters);
ImGui::TreePop();
}
}

Expand Down
Loading