aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/vulkan_common
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/vulkan_common')
-rw-r--r--src/video_core/vulkan_common/streamline.cpp466
-rw-r--r--src/video_core/vulkan_common/streamline.h63
-rw-r--r--src/video_core/vulkan_common/streamline_api.h36
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp46
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h7
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.cpp20
6 files changed, 638 insertions, 0 deletions
diff --git a/src/video_core/vulkan_common/streamline.cpp b/src/video_core/vulkan_common/streamline.cpp
new file mode 100644
index 0000000000..4f8fd796c1
--- /dev/null
+++ b/src/video_core/vulkan_common/streamline.cpp
@@ -0,0 +1,466 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// NVIDIA DLSS through NVIDIA Streamline, with Streamline's manual hooking.
+
+#include <algorithm>
+#include <array>
+#include <cstdlib>
+#include <filesystem>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/fs/fs_util.h"
+#include "common/fs/path_util.h"
+#include "common/logging.h"
+#include "common/settings.h"
+#include "video_core/vulkan_common/streamline.h"
+#include "video_core/vulkan_common/streamline_api.h"
+
+#include <sl_security.h>
+
+#include <softpub.h>
+#include <wintrust.h>
+
+namespace Vulkan::Streamline {
+namespace {
+
+// A custom engine names itself with its own project id; an application id from NVIDIA is only
+// needed without an engine type. Keep it fixed: the driver may key behaviour off it.
+constexpr const char* PROJECT_ID = "8d3e2f9a-6b41-4c7e-9a52-1f0c3b7d5e64";
+constexpr const wchar_t* INTERPOSER = L"sl.interposer.dll";
+constexpr const wchar_t* MODEL = L"nvngx_dlss.dll";
+// The first DLSS with preset K.
+constexpr u32 MIN_DLSS_MAJOR = 310;
+
+using Version = std::array<u32, 4>;
+
+struct State {
+ HMODULE module{};
+ Api api{};
+ std::wstring plugin_dir;
+ std::wstring model_dir;
+ std::vector<std::string> instance_extensions;
+ std::vector<std::string> device_extensions;
+ bool started{};
+ bool attached{};
+};
+
+State& GetState() {
+ static State state;
+ return state;
+}
+
+int LogLevel() {
+ const char* const value = std::getenv("EDEN_NX2_DLSS_LOG");
+ return value ? std::atoi(value) : 0;
+}
+
+void LogFromStreamline(sl::LogType type, const char* message) {
+ std::string_view text{message ? message : ""};
+ while (!text.empty() && (text.back() == '\n' || text.back() == '\r')) {
+ text.remove_suffix(1);
+ }
+ switch (type) {
+ case sl::LogType::eError:
+ LOG_ERROR(Render_Vulkan, "Streamline: {}", text);
+ break;
+ case sl::LogType::eWarn:
+ LOG_WARNING(Render_Vulkan, "Streamline: {}", text);
+ break;
+ default:
+ if (LogLevel() > 0) {
+ LOG_INFO(Render_Vulkan, "Streamline: {}", text);
+ }
+ break;
+ }
+}
+
+/// Where the user may put sl.interposer.dll: EDEN_STREAMLINE_DIR, the user folder's "streamline"
+/// folder, then a "streamline" folder next to the executable.
+std::vector<std::filesystem::path> SearchDirs() {
+ std::vector<std::filesystem::path> dirs;
+ if (const char* const dir = std::getenv("EDEN_STREAMLINE_DIR"); dir && *dir) {
+ dirs.emplace_back(dir);
+ }
+ dirs.push_back(UserFolder());
+ dirs.push_back(Common::FS::GetExeDirectory() / "streamline");
+ return dirs;
+}
+
+template <typename T>
+bool Resolve(HMODULE module, const char* name, T*& function) {
+ function = reinterpret_cast<T*>(GetProcAddress(module, name));
+ if (!function) {
+ LOG_ERROR(Render_Vulkan, "sl.interposer.dll has no {}", name);
+ }
+ return function != nullptr;
+}
+
+std::optional<Version> ReadVersion(const std::filesystem::path& path) {
+ DWORD handle = 0;
+ const DWORD size = GetFileVersionInfoSizeW(path.c_str(), &handle);
+ if (size == 0) {
+ return std::nullopt;
+ }
+ std::vector<u8> data(size);
+ VS_FIXEDFILEINFO* info{};
+ UINT length = 0;
+ if (!GetFileVersionInfoW(path.c_str(), 0, size, data.data()) ||
+ !VerQueryValueW(data.data(), L"\\", reinterpret_cast<void**>(&info), &length) || !info) {
+ return std::nullopt;
+ }
+ return Version{HIWORD(info->dwFileVersionMS), LOWORD(info->dwFileVersionMS),
+ HIWORD(info->dwFileVersionLS), LOWORD(info->dwFileVersionLS)};
+}
+
+std::string VersionText(const std::filesystem::path& path) {
+ const std::optional<Version> version = path.empty() ? std::nullopt : ReadVersion(path);
+ if (!version) {
+ return {};
+ }
+ const Version& v = *version;
+ return v[3] == 0 ? fmt::format("{}.{}.{}", v[0], v[1], v[2])
+ : fmt::format("{}.{}.{}.{}", v[0], v[1], v[2], v[3]);
+}
+
+/// The NVIDIA app keeps every DLSS it downloads under ProgramData; pick the newest.
+std::filesystem::path FindAppModel() {
+ const wchar_t* const program_data = _wgetenv(L"ProgramData");
+ if (!program_data) {
+ return {};
+ }
+ const std::filesystem::path root =
+ std::filesystem::path{program_data} / L"NVIDIA" / L"NGX" / L"models" / L"dlss" / L"versions";
+ std::filesystem::path best_path;
+ Version best{};
+ std::error_code ec;
+ for (const auto& version_dir : std::filesystem::directory_iterator(root, ec)) {
+ std::error_code files_ec;
+ for (const auto& file :
+ std::filesystem::directory_iterator(version_dir.path() / L"files", files_ec)) {
+ if (file.path().extension() != L".bin") {
+ continue;
+ }
+ const std::optional<Version> version = ReadVersion(file.path());
+ if (version && (*version)[0] >= MIN_DLSS_MAJOR && *version > best) {
+ best = *version;
+ best_path = file.path();
+ }
+ }
+ }
+ return best_path;
+}
+
+/// Copies the NVIDIA app's DLSS into our folder as nvngx_dlss.dll, the name Streamline loads.
+std::filesystem::path UseAppModel(const DlssFiles& files) {
+ const std::filesystem::path dir = UserFolder() / L"nvidia-app";
+ const std::filesystem::path target = dir / MODEL;
+ std::error_code ec;
+ const bool current = std::filesystem::is_regular_file(target, ec) &&
+ std::filesystem::file_size(target, ec) ==
+ std::filesystem::file_size(files.app_model, ec) &&
+ ReadVersion(target) == ReadVersion(files.app_model);
+ if (!current) {
+ if (!IsSignedByNvidia(files.app_model)) {
+ LOG_WARNING(Render_Vulkan, "The NVIDIA app's DLSS {} is not signed by NVIDIA",
+ Common::FS::PathToUTF8String(files.app_model));
+ return {};
+ }
+ std::filesystem::create_directories(dir, ec);
+ std::filesystem::copy_file(files.app_model, target,
+ std::filesystem::copy_options::overwrite_existing, ec);
+ if (ec) {
+ LOG_WARNING(Render_Vulkan, "Could not copy the NVIDIA app's DLSS to {}: {}",
+ Common::FS::PathToUTF8String(target), ec.message());
+ return {};
+ }
+ }
+ return dir;
+}
+
+bool Load(State& state, const std::filesystem::path& path) {
+ if (!IsStreamlineModule(path)) {
+ LOG_ERROR(Render_Vulkan, "{} is not signed by NVIDIA; not loading it",
+ Common::FS::PathToUTF8String(path));
+ return false;
+ }
+ state.module = LoadLibraryW(path.c_str());
+ if (!state.module) {
+ LOG_ERROR(Render_Vulkan, "Could not load {} (error {})", Common::FS::PathToUTF8String(path),
+ GetLastError());
+ return false;
+ }
+ Api& api = state.api;
+ const HMODULE m = state.module;
+ if (Resolve(m, "slInit", api.init) && Resolve(m, "slShutdown", api.shutdown) &&
+ Resolve(m, "slIsFeatureSupported", api.is_feature_supported) &&
+ Resolve(m, "slGetFeatureRequirements", api.get_feature_requirements) &&
+ Resolve(m, "slGetFeatureVersion", api.get_feature_version) &&
+ Resolve(m, "slGetFeatureFunction", api.get_feature_function) &&
+ Resolve(m, "slGetNewFrameToken", api.get_new_frame_token) &&
+ Resolve(m, "slSetConstants", api.set_constants) &&
+ Resolve(m, "slEvaluateFeature", api.evaluate_feature) &&
+ Resolve(m, "slFreeResources", api.free_resources) &&
+ Resolve(m, "slSetVulkanInfo", api.set_vulkan_info)) {
+ return true;
+ }
+ FreeLibrary(state.module);
+ state.module = nullptr;
+ state.api = {};
+ return false;
+}
+
+std::vector<std::string> Names(uint32_t count, const char** names) {
+ std::vector<std::string> result;
+ for (uint32_t i = 0; i < count; ++i) {
+ result.emplace_back(names[i]);
+ }
+ return result;
+}
+
+} // Anonymous namespace
+
+std::filesystem::path UserFolder() {
+ return Common::FS::GetEdenPath(Common::FS::EdenPath::StreamlineDir);
+}
+
+bool IsStreamlineModule(const std::filesystem::path& path) {
+ return sl::security::verifyEmbeddedSignature(path.c_str());
+}
+
+bool IsSignedByNvidia(const std::filesystem::path& path) {
+ const std::wstring file_path = std::filesystem::absolute(path).wstring();
+ WINTRUST_FILE_INFO file{};
+ file.cbStruct = sizeof(file);
+ file.pcwszFilePath = file_path.c_str();
+ WINTRUST_DATA data{};
+ data.cbStruct = sizeof(data);
+ data.dwUIChoice = WTD_UI_NONE;
+ data.fdwRevocationChecks = WTD_REVOKE_NONE;
+ data.dwUnionChoice = WTD_CHOICE_FILE;
+ data.pFile = &file;
+ data.dwStateAction = WTD_STATEACTION_VERIFY;
+ data.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
+ GUID action = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ bool valid = WinVerifyTrust(nullptr, &action, &data) == ERROR_SUCCESS;
+ if (valid) {
+ CRYPT_PROVIDER_DATA* const provider = WTHelperProvDataFromStateData(data.hWVTStateData);
+ CRYPT_PROVIDER_SGNR* const signer =
+ provider ? WTHelperGetProvSignerFromChain(provider, 0, FALSE, 0) : nullptr;
+ CRYPT_PROVIDER_CERT* const cert = signer ? WTHelperGetProvCertFromChain(signer, 0) : nullptr;
+ wchar_t name[128]{};
+ valid = cert &&
+ CertGetNameStringW(cert->pCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, name,
+ static_cast<DWORD>(std::size(name))) > 1 &&
+ std::wstring_view{name} == L"NVIDIA Corporation";
+ }
+ data.dwStateAction = WTD_STATEACTION_CLOSE;
+ WinVerifyTrust(nullptr, &action, &data);
+ return valid;
+}
+
+DlssFiles FindDlssFiles() {
+ DlssFiles files;
+ files.folder = UserFolder();
+ std::error_code ec;
+ for (const std::filesystem::path& dir : SearchDirs()) {
+ if (std::filesystem::is_regular_file(dir / INTERPOSER, ec)) {
+ files.folder = dir;
+ files.interposer = dir / INTERPOSER;
+ break;
+ }
+ }
+ if (std::filesystem::is_regular_file(files.folder / MODEL, ec)) {
+ files.model = files.folder / MODEL;
+ }
+ files.app_model = FindAppModel();
+ files.interposer_version = VersionText(files.interposer);
+ files.model_version = VersionText(files.model);
+ files.app_model_version = VersionText(files.app_model);
+ return files;
+}
+
+bool Start() {
+ State& state = GetState();
+ if (state.started) {
+ return true;
+ }
+ if (!Settings::values.nvidia_dlss.GetValue()) {
+ return false;
+ }
+ if (!state.module) {
+ const DlssFiles files = FindDlssFiles();
+ if (files.interposer.empty()) {
+ LOG_WARNING(Render_Vulkan,
+ "NVIDIA DLSS is on, but sl.interposer.dll (NVIDIA Streamline) is not in "
+ "{} (the Graphics settings can download it); using FSR 3.1",
+ Common::FS::PathToUTF8String(files.folder));
+ return false;
+ }
+ if (!Load(state, files.interposer)) {
+ return false;
+ }
+ state.plugin_dir = files.folder.wstring();
+ state.model_dir.clear();
+ if (files.model.empty() && !files.app_model.empty()) {
+ if (const std::filesystem::path dir = UseAppModel(files); !dir.empty()) {
+ state.model_dir = dir.wstring();
+ LOG_INFO(Render_Vulkan, "Using the NVIDIA app's DLSS {}", files.app_model_version);
+ }
+ } else if (files.model.empty()) {
+ LOG_WARNING(Render_Vulkan, "No nvngx_dlss.dll in {} and no DLSS from the NVIDIA app",
+ Common::FS::PathToUTF8String(files.folder));
+ }
+ }
+
+ // Streamline's plugins come from the loader's folder or the NVIDIA app's downloads (OTA); the
+ // DLSS model from the loader's folder, else the NVIDIA app's copy.
+ const wchar_t* plugin_paths[] = {state.plugin_dir.c_str(), state.model_dir.c_str()};
+ const sl::Feature features[] = {sl::kFeatureDLSS};
+ sl::Preferences preferences{};
+ preferences.logLevel = LogLevel() >= 2 ? sl::LogLevel::eVerbose : sl::LogLevel::eDefault;
+ preferences.logMessageCallback = LogFromStreamline;
+ preferences.pathsToPlugins = plugin_paths;
+ preferences.numPathsToPlugins = state.model_dir.empty() ? 1 : 2;
+ preferences.flags = sl::PreferenceFlags::eDisableCLStateTracking |
+ sl::PreferenceFlags::eUseManualHooking | sl::PreferenceFlags::eAllowOTA |
+ sl::PreferenceFlags::eLoadDownloadedPlugins;
+ preferences.featuresToLoad = features;
+ preferences.numFeaturesToLoad = 1;
+ preferences.engine = sl::EngineType::eCustom;
+ preferences.engineVersion = "1.0";
+ preferences.projectId = PROJECT_ID;
+ preferences.renderAPI = sl::RenderAPI::eVulkan;
+ const Api& api = state.api;
+ if (const sl::Result result = api.init(preferences, sl::kSDKVersion);
+ result != sl::Result::eOk) {
+ LOG_ERROR(Render_Vulkan, "NVIDIA Streamline failed to start (result {}); using FSR 3.1",
+ static_cast<int>(result));
+ return false;
+ }
+ sl::FeatureRequirements requirements{};
+ if (const sl::Result result = api.get_feature_requirements(sl::kFeatureDLSS, requirements);
+ result != sl::Result::eOk) {
+ LOG_WARNING(Render_Vulkan,
+ "Streamline found no DLSS plugin (result {}); put sl.dlss.dll next to "
+ "sl.interposer.dll or install the NVIDIA app. Using FSR 3.1",
+ static_cast<int>(result));
+ api.shutdown();
+ return false;
+ }
+ if (requirements.vkNumComputeQueuesRequired != 0 ||
+ requirements.vkNumGraphicsQueuesRequired != 0) {
+ LOG_WARNING(Render_Vulkan, "This DLSS wants extra Vulkan queues; using FSR 3.1");
+ api.shutdown();
+ return false;
+ }
+ state.instance_extensions =
+ Names(requirements.vkNumInstanceExtensions, requirements.vkInstanceExtensions);
+ state.device_extensions =
+ Names(requirements.vkNumDeviceExtensions, requirements.vkDeviceExtensions);
+ // The KHR and EXT buffer device address extensions can't both be enabled.
+ const auto& device_extensions = state.device_extensions;
+ if (std::ranges::find(device_extensions, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) !=
+ device_extensions.end()) {
+ std::erase(state.device_extensions, VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
+ }
+ sl::FeatureVersion version{};
+ api.get_feature_version(sl::kFeatureDLSS, version);
+ LOG_INFO(Render_Vulkan, "NVIDIA Streamline {} started from {} (DLSS {})",
+ version.versionSL.toStr(),
+ Common::FS::PathToUTF8String(std::filesystem::path{state.plugin_dir}),
+ version.versionNGX.toStr());
+ state.started = true;
+ return true;
+}
+
+bool WantsDevice() {
+ const State& state = GetState();
+ return state.started && !state.attached;
+}
+
+std::vector<std::string> InstanceExtensions() {
+ const State& state = GetState();
+ return state.started ? state.instance_extensions : std::vector<std::string>{};
+}
+
+std::vector<std::string> DeviceExtensions() {
+ const State& state = GetState();
+ return state.started ? state.device_extensions : std::vector<std::string>{};
+}
+
+bool AttachDevice(VkInstance instance, VkPhysicalDevice physical, VkDevice device,
+ u32 graphics_family, vk::DeviceDispatch& dld) {
+ State& state = GetState();
+ if (!WantsDevice()) {
+ return false;
+ }
+ sl::VulkanInfo info{};
+ info.device = device;
+ info.instance = instance;
+ info.physicalDevice = physical;
+ info.computeQueueFamily = graphics_family;
+ info.graphicsQueueFamily = graphics_family;
+ if (const sl::Result result = state.api.set_vulkan_info(info); result != sl::Result::eOk) {
+ LOG_ERROR(Render_Vulkan, "Streamline did not take the Vulkan device (result {})",
+ static_cast<int>(result));
+ Stop();
+ return false;
+ }
+ sl::AdapterInfo adapter{};
+ adapter.vkPhysicalDevice = physical;
+ if (const sl::Result result = state.api.is_feature_supported(sl::kFeatureDLSS, adapter);
+ result != sl::Result::eOk) {
+ LOG_WARNING(Render_Vulkan,
+ "NVIDIA DLSS is not supported on this GPU or driver (Streamline result {}); "
+ "using FSR 3.1",
+ static_cast<int>(result));
+ Stop();
+ return false;
+ }
+ // Manual hooking: Streamline does its per-frame work in these.
+ const auto hook = [&](auto& function, const char* name) {
+ if (const FARPROC proxy = GetProcAddress(state.module, name)) {
+ function = reinterpret_cast<std::remove_reference_t<decltype(function)>>(proxy);
+ } else {
+ LOG_WARNING(Render_Vulkan, "sl.interposer.dll has no {}", name);
+ }
+ };
+ hook(dld.vkCreateSwapchainKHR, "vkCreateSwapchainKHR");
+ hook(dld.vkDestroySwapchainKHR, "vkDestroySwapchainKHR");
+ hook(dld.vkGetSwapchainImagesKHR, "vkGetSwapchainImagesKHR");
+ hook(dld.vkAcquireNextImageKHR, "vkAcquireNextImageKHR");
+ hook(dld.vkQueuePresentKHR, "vkQueuePresentKHR");
+ hook(dld.vkDeviceWaitIdle, "vkDeviceWaitIdle");
+ state.attached = true;
+ LOG_INFO(Render_Vulkan, "NVIDIA DLSS is available through Streamline");
+ return true;
+}
+
+bool IsDlssAvailable() {
+ const State& state = GetState();
+ return state.started && state.attached;
+}
+
+void Stop() {
+ State& state = GetState();
+ if (!state.started) {
+ return;
+ }
+ const sl::Result result = state.api.shutdown();
+ state.started = false;
+ state.attached = false;
+ LOG_INFO(Render_Vulkan, "NVIDIA Streamline stopped (result {})", static_cast<int>(result));
+}
+
+const Api& GetApi() {
+ return GetState().api;
+}
+
+} // namespace Vulkan::Streamline
diff --git a/src/video_core/vulkan_common/streamline.h b/src/video_core/vulkan_common/streamline.h
new file mode 100644
index 0000000000..47e55f7150
--- /dev/null
+++ b/src/video_core/vulkan_common/streamline.h
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// NVIDIA DLSS through NVIDIA Streamline. Only Streamline's MIT-licensed
+// headers are built in: NVIDIA's DLLs are loaded at run time from the user's PC, and none ship
+// with the emulator.
+
+#pragma once
+
+#include <filesystem>
+#include <string>
+#include <vector>
+
+#include "common/common_types.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan::Streamline {
+
+/// NVIDIA's files DLSS needs, as found on this PC. Paths are empty when missing.
+struct DlssFiles {
+ std::filesystem::path folder; ///< Where the loader is, or where it should go
+ std::filesystem::path interposer; ///< sl.interposer.dll (Streamline's loader)
+ std::filesystem::path model; ///< nvngx_dlss.dll next to the loader
+ std::filesystem::path app_model; ///< Newest DLSS the NVIDIA app downloaded
+ std::string interposer_version;
+ std::string model_version;
+ std::string app_model_version;
+};
+
+[[nodiscard]] DlssFiles FindDlssFiles();
+
+/// The user folder's "streamline" folder.
+[[nodiscard]] std::filesystem::path UserFolder();
+
+/// Streamline's own modules carry a second, Streamline-only signature.
+[[nodiscard]] bool IsStreamlineModule(const std::filesystem::path& path);
+
+/// A valid Authenticode signature from NVIDIA Corporation (DLSS models).
+[[nodiscard]] bool IsSignedByNvidia(const std::filesystem::path& path);
+
+/// When the NVIDIA DLSS setting is on, loads the user's sl.interposer.dll (checking NVIDIA's
+/// signature) and starts Streamline for DLSS. Call before creating the renderer's instance.
+bool Start();
+
+/// Whether Streamline is running and waiting for the renderer's device.
+[[nodiscard]] bool WantsDevice();
+
+/// Instance and device extensions DLSS asks for (empty when Streamline isn't running).
+[[nodiscard]] std::vector<std::string> InstanceExtensions();
+[[nodiscard]] std::vector<std::string> DeviceExtensions();
+
+/// Hands the renderer's device to Streamline and routes the swapchain calls through it, as
+/// Streamline's manual hooking needs. Stops Streamline and returns false when DLSS can't run.
+bool AttachDevice(VkInstance instance, VkPhysicalDevice physical, VkDevice device,
+ u32 graphics_family, vk::DeviceDispatch& dld);
+
+/// Whether DLSS can run on the attached device.
+[[nodiscard]] bool IsDlssAvailable();
+
+/// Shuts Streamline down; call before the device is destroyed.
+void Stop();
+
+} // namespace Vulkan::Streamline
diff --git a/src/video_core/vulkan_common/streamline_api.h b/src/video_core/vulkan_common/streamline_api.h
new file mode 100644
index 0000000000..0a82c1dd7d
--- /dev/null
+++ b/src/video_core/vulkan_common/streamline_api.h
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Streamline's API as loaded from the user's sl.interposer.dll. Only for the
+// Streamline sources: it pulls in <windows.h> and Streamline's (MIT-licensed) headers.
+
+#pragma once
+
+#include <windows.h>
+
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+#include <sl.h>
+#include <sl_dlss.h>
+#include <sl_helpers_vk.h>
+
+namespace Vulkan::Streamline {
+
+struct Api {
+ PFun_slInit* init{};
+ PFun_slShutdown* shutdown{};
+ PFun_slIsFeatureSupported* is_feature_supported{};
+ PFun_slGetFeatureRequirements* get_feature_requirements{};
+ PFun_slGetFeatureVersion* get_feature_version{};
+ PFun_slGetFeatureFunction* get_feature_function{};
+ PFun_slGetNewFrameToken* get_new_frame_token{};
+ PFun_slSetConstants* set_constants{};
+ PFun_slEvaluateFeature* evaluate_feature{};
+ PFun_slFreeResources* free_resources{};
+ PFun_slSetVulkanInfo* set_vulkan_info{};
+};
+
+/// The loaded functions; only valid while IsDlssAvailable().
+[[nodiscard]] const Api& GetApi();
+
+} // namespace Vulkan::Streamline
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 01e13cb384..2499f46f87 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -32,6 +32,9 @@
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/gpu_logging/gpu_logging.h"
+#ifdef EGLANTINE_STREAMLINE
+#include "video_core/vulkan_common/streamline.h"
+#endif
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
#include <adrenotools/bcenabler.h>
@@ -743,6 +746,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
RemoveExtensionFeature(extensions.descriptor_buffer, features.descriptor_buffer,
VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME);
}
+#ifdef EGLANTINE_STREAMLINE
+ // NVIDIA's DLSS needs buffer device addresses: keep the feature enabled for
+ // it, but leave Eglantine's own buffers as they are without descriptor buffers.
+ const bool dlss_wants_device_address =
+ streamline_extensions && features.buffer_device_address.bufferDeviceAddress;
+ if (!extensions.descriptor_buffer && dlss_wants_device_address) {
+ extensions.buffer_device_address = false;
+ } else
+#endif
if (!extensions.descriptor_buffer) {
RemoveExtensionFeature(extensions.buffer_device_address, features.buffer_device_address,
VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
@@ -753,6 +765,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
graphics_queue = logical.GetQueue(graphics_family);
present_queue = logical.GetQueue(present_family);
+#ifdef EGLANTINE_STREAMLINE
+ if (streamline_extensions) {
+ streamline_attached =
+ Streamline::AttachDevice(instance, physical, *logical, graphics_family, dld);
+ } else if (Streamline::WantsDevice()) {
+ Streamline::Stop();
+ }
+#endif
+
VmaVulkanFunctions functions{};
functions.vkGetInstanceProcAddr = dld.vkGetInstanceProcAddr;
functions.vkGetDeviceProcAddr = dld.vkGetDeviceProcAddr;
@@ -806,6 +827,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
Device::~Device() {
+#ifdef EGLANTINE_STREAMLINE
+ if (streamline_attached) {
+ Streamline::Stop();
+ }
+#endif
SaveStaticPipelineCache();
ShutdownGPULogging();
for (auto& pools : separate_pools) {
@@ -1123,6 +1149,26 @@ bool Device::GetSuitability(bool requires_swapchain) {
extensions.robustness_2 = false;
}
+#ifdef EGLANTINE_STREAMLINE
+ // The extensions NVIDIA's DLSS asks for, where the device has them all.
+ if (Streamline::WantsDevice()) {
+ const std::vector<std::string> wanted = Streamline::DeviceExtensions();
+ // Streamline may ask for no extensions at all.
+ const bool all = std::ranges::all_of(
+ wanted, [&](const std::string& name) { return supported_extensions.contains(name); });
+ std::string names;
+ for (const std::string& name : wanted) {
+ if (all) {
+ loaded_extensions.insert(name);
+ }
+ names += (names.empty() ? "" : ", ") + name;
+ }
+ streamline_extensions = all;
+ LOG_INFO(Render_Vulkan, "NVIDIA DLSS device extensions: {} ({})",
+ all ? "enabled" : "unavailable", names.empty() ? "none" : names);
+ }
+#endif
+
#undef FEATURE_EXTENSION
#undef EXTENSION
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index edc091318c..3115f4547c 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -289,6 +289,11 @@ public:
return physical;
}
+ /// Returns the instance the device was created from.
+ VkInstance GetInstance() const {
+ return instance;
+ }
+
VkPipelineCache StaticPipelineCache() const noexcept {
return *static_pipeline_cache;
}
@@ -1326,6 +1331,8 @@ private:
// Telemetry parameters
std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions.
std::set<std::string, std::less<>> loaded_extensions; ///< Loaded Vulkan extensions.
+ bool streamline_extensions{}; ///< NVIDIA DLSS's extensions are loaded.
+ bool streamline_attached{}; ///< Streamline has this device.
std::vector<size_t> valid_heap_memory; ///< Heaps used.
size_t main_heap{}; ///< Largest device-local heap, where images live.
u64 heap_size_limit{}; ///< EDEN_NX2_VRAM_LIMIT_MIB for device-local heaps, 0 if unset.
diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp
index 7f3d8ed41f..3ab924e37f 100644
--- a/src/video_core/vulkan_common/vulkan_instance.cpp
+++ b/src/video_core/vulkan_common/vulkan_instance.cpp
@@ -18,6 +18,11 @@
#include "core/frontend/emu_window.h"
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
+#ifdef EGLANTINE_STREAMLINE
+#include <algorithm>
+#include <string>
+#include "video_core/vulkan_common/streamline.h"
+#endif
namespace Vulkan {
namespace {
@@ -82,6 +87,21 @@ namespace {
#endif
if (enable_validation && AreExtensionsSupported(dld, properties, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}))
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+#ifdef EGLANTINE_STREAMLINE
+ // What NVIDIA's DLSS asks for through Streamline, where available. Static: the returned
+ // pointers must outlive this call.
+ static std::vector<std::string> streamline;
+ streamline = Streamline::InstanceExtensions();
+ for (const std::string& name : streamline) {
+ const bool available = std::ranges::any_of(properties, [&](const VkExtensionProperties& p) {
+ return name == p.extensionName;
+ });
+ const bool listed = std::ranges::any_of(extensions, [&](const char* e) { return name == e; });
+ if (available && !listed) {
+ extensions.push_back(name.c_str());
+ }
+ }
+#endif
return extensions;
}