From 7543d1437c2b050ec38c02dfb71cc2df5b2cb965 Mon Sep 17 00:00:00 2001 From: wilsjo2 <97138003+wilsjo2@users.noreply.github.com> Date: Sun, 13 Sep 2026 05:40:13 +1000 Subject: [PATCH] Bind active Streamline plugins and correct graphics resource handling Resolve DLSSG, Reflex and PCL functions after device selection so driver-selected plugins supply initialized entry points. Preserve the depth-plane SRV format and check the Vulkan framebuffer handle during teardown. Vulkan framebuffer guard incorporates y4my4my4m work (7b7220bb / 7c3b65dc). Validation: Release x64 build and installed-runtime Streamline active-plugin smoke test; Jedi depth-view diagnosis and NR gameplay retest recorded separately. --- OptiScaler/menu/menu_overlay_vk.cpp | 2 +- OptiScaler/proxies/Streamline_Proxy.h | 52 ++++++++-- OptiScaler/shaders/Shader_Dx12.cpp | 6 +- tests/streamline_active_plugin_smoke.cpp | 124 +++++++++++++++++++++++ 4 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 tests/streamline_active_plugin_smoke.cpp diff --git a/OptiScaler/menu/menu_overlay_vk.cpp b/OptiScaler/menu/menu_overlay_vk.cpp index 24fdd288..7e0d6ae5 100644 --- a/OptiScaler/menu/menu_overlay_vk.cpp +++ b/OptiScaler/menu/menu_overlay_vk.cpp @@ -480,7 +480,7 @@ void MenuOverlayVk::DestroyVulkanObjects(bool shutdown) fd->BackbufferView = VK_NULL_HANDLE; } - if (fd->BackbufferView != VK_NULL_HANDLE) + if (fd->Framebuffer != VK_NULL_HANDLE) { vkDestroyFramebuffer(_ImVulkan_Info.Device, fd->Framebuffer, VK_NULL_HANDLE); fd->Framebuffer = VK_NULL_HANDLE; diff --git a/OptiScaler/proxies/Streamline_Proxy.h b/OptiScaler/proxies/Streamline_Proxy.h index aa8ffe2b..e41dd15d 100644 --- a/OptiScaler/proxies/Streamline_Proxy.h +++ b/OptiScaler/proxies/Streamline_Proxy.h @@ -285,6 +285,48 @@ class StreamlineProxy return _slVersion; } + template + static bool ResolveActiveFeature(sl::Feature feature, const char* name, T& target, HMODULE& module, + bool required = true) + { + void* function = nullptr; + auto result = _slGetFeatureFunction(feature, name, function); + target = result == sl::Result::eOk ? reinterpret_cast(function) : nullptr; + if (target == nullptr) + { + if (required) + LOG_ERROR("Active Streamline function {} unavailable: {}", name, (int) result); + return !required; + } + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(function), &module); + return true; + } + + static sl::Result SetD3DDeviceAndBind(void* device) + { + auto result = _slSetD3DDevice(device); + if (result != sl::Result::eOk || !_isD3D12Requested) + return result; + + // OTA overrides may replace the bundled DLLs. Resolve only from the plugins + // actually initialized by this interposer, after the device has been set. + bool ok = true; + auto& state = State::Instance(); + ok &= ResolveActiveFeature(sl::kFeatureDLSS_G, "slDLSSGSetOptions", _slDLSSGSetOptions, state.optiSlDLSSG); + ok &= ResolveActiveFeature(sl::kFeatureDLSS_G, "slDLSSGGetState", _slDLSSGGetState, state.optiSlDLSSG); + ok &= ResolveActiveFeature(sl::kFeatureReflex, "slReflexGetState", _slReflexGetState, state.optiSlReflex); + ok &= ResolveActiveFeature(sl::kFeatureReflex, "slReflexSleep", _slReflexSleep, state.optiSlReflex); + ok &= ResolveActiveFeature(sl::kFeatureReflex, "slReflexSetOptions", _slReflexSetOptions, state.optiSlReflex); + ResolveActiveFeature(sl::kFeatureReflex, "slReflexSetCameraData", _slReflexSetCameraData, state.optiSlReflex, false); + ResolveActiveFeature(sl::kFeatureReflex, "slReflexGetPredictedCameraData", _slReflexGetPredictedCameraData, state.optiSlReflex, false); + ResolveActiveFeature(sl::kFeaturePCL, "slPCLGetState", _slPCLGetState, state.optiSlPCL, false); + ok &= ResolveActiveFeature(sl::kFeaturePCL, "slPCLSetMarker", _slPCLSetMarker, state.optiSlPCL); + ok &= ResolveActiveFeature(sl::kFeaturePCL, "slPCLSetOptions", _slPCLSetOptions, state.optiSlPCL); + LOG_INFO("Active Streamline feature binding: {}", ok); + return ok ? sl::Result::eOk : sl::Result::eErrorFeatureMissing; + } + static bool InitWithD3D12(ID3D12Device* device) { if (_isD3D12Inited) @@ -358,16 +400,13 @@ class StreamlineProxy State::DisableChecks(owner); } + _isD3D12Requested = true; auto initResult = StreamlineProxy::Init()(pref, sl::kSDKVersion); State::EnableChecks(owner); if (initResult == sl::Result::eOk) { - State::Instance().optiSlDLSSG = StreamlineProxy::HookStreamlineDLSSG(); - State::Instance().optiSlReflex = StreamlineProxy::HookStreamlineReflex(); - State::Instance().optiSlPCL = StreamlineProxy::HookStreamlinePCL(); - if (State::Instance().gameQuirks & GameQuirk::CreateSLOnThe2ndDevice) { // slSetD3DDevice moved to hkD3D12CreateDevice @@ -375,7 +414,7 @@ class StreamlineProxy } else { - auto result = _slSetD3DDevice(device); + auto result = SetD3DDeviceAndBind(device); if (result == sl::Result::eOk) { auto reflexConst = sl::ReflexOptions {}; @@ -451,7 +490,7 @@ class StreamlineProxy static PFN_slGetNativeInterface GetNativeInterface() { return _slGetNativeInterface; } static PFN_slGetFeatureFunction GetFeatureFunction() { return _slGetFeatureFunction; } static PFN_slGetNewFrameToken GetNewFrameToken() { return _slGetNewFrameToken; } - static PFN_slSetD3DDevice SetD3DDevice() { return _slSetD3DDevice; } + static PFN_slSetD3DDevice SetD3DDevice() { return &SetD3DDeviceAndBind; } static PFN_CreateDxgiFactory CreateDxgiFactory() { return _slCreateDxgiFactory; } static PFN_CreateDxgiFactory1 CreateDxgiFactory1() { return _slCreateDxgiFactory1; } static PFN_CreateDxgiFactory2 CreateDxgiFactory2() { return _slCreateDxgiFactory2; } @@ -475,6 +514,7 @@ class StreamlineProxy inline static bool _isInited = false; inline static bool _isD3D11Inited = false; inline static bool _isD3D12Inited = false; + inline static bool _isD3D12Requested = false; // Interposer inline static PFN_slInit _slInit = nullptr; diff --git a/OptiScaler/shaders/Shader_Dx12.cpp b/OptiScaler/shaders/Shader_Dx12.cpp index e8ab66d1..e9a94149 100644 --- a/OptiScaler/shaders/Shader_Dx12.cpp +++ b/OptiScaler/shaders/Shader_Dx12.cpp @@ -198,7 +198,11 @@ void Shader_Dx12::CreateShaderResourceView(ID3D12Device* device, ID3D12Resource* } D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; - srvDesc.Format = TranslateTypelessFormats((format != DXGI_FORMAT_UNKNOWN) ? format : desc.Format); + const auto viewFormat = (format != DXGI_FORMAT_UNKNOWN) ? format : desc.Format; + // NR's copied depth guide already has the depth-plane SRV format. The shared + // translator maps it back to a DSV format, which removes the device on SRV creation. + srvDesc.Format = viewFormat == DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS + ? viewFormat : TranslateTypelessFormats(viewFormat); srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; const UINT mipLevels = (desc.MipLevels) ? static_cast(desc.MipLevels) : static_cast(-1); diff --git a/tests/streamline_active_plugin_smoke.cpp b/tests/streamline_active_plugin_smoke.cpp new file mode 100644 index 00000000..3d515575 --- /dev/null +++ b/tests/streamline_active_plugin_smoke.cpp @@ -0,0 +1,124 @@ +// Installed-runtime regression for Streamline's active-plugin binding contract. +// Build: cl /std:c++20 /EHsc /I external/streamline tests/streamline_active_plugin_smoke.cpp /link d3d12.lib dxgi.lib +// Run: streamline_active_plugin_smoke.exe +// Runs without a game. It checks startup/plugin identity, not presentation or frame generation. +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool ok, const char* message) +{ + if (!ok) + throw std::runtime_error(message); +} + +template static T Export(HMODULE module, const char* name) +{ + auto result = reinterpret_cast(GetProcAddress(module, name)); + Require(result != nullptr, name); + return result; +} + +static void PrintModule(const char* label, void* function) +{ + HMODULE module = nullptr; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(function), &module); + wchar_t path[MAX_PATH] {}; + GetModuleFileNameW(module, path, MAX_PATH); + std::printf("%s: %ls\n", label, path); +} + +// Contain the old-path access violation to demonstrate the regression without launching a game. +static unsigned CallReflex(decltype(&slReflexSetOptions) function, const sl::ReflexOptions& options) +{ + __try { return static_cast(function(options)); } + __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) + { return EXCEPTION_ACCESS_VIOLATION; } +} + +int wmain(int argc, wchar_t** argv) +{ + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + try + { + Require(argc == 3, "Expected Streamline and game binary directories"); + const std::filesystem::path directory(argv[1]); + const auto interposer = LoadLibraryW((directory / L"sl.interposer.dll").c_str()); + Require(interposer != nullptr, "Load Streamline interposer"); + const auto init = Export(interposer, "slInit"); + const auto setDevice = Export(interposer, "slSetD3DDevice"); + const auto getFeature = Export(interposer, "slGetFeatureFunction"); + const auto shutdown = Export(interposer, "slShutdown"); + const sl::Feature features[] = { sl::kFeatureDLSS_G, sl::kFeatureReflex, sl::kFeaturePCL }; + const wchar_t* paths[] = { argv[1], argv[2] }; + sl::Preferences pref; + pref.applicationId = 0x0F71CA1E; + pref.featuresToLoad = features; + pref.numFeaturesToLoad = 3; + pref.pathsToPlugins = paths; + pref.numPathsToPlugins = 2; + pref.renderAPI = sl::RenderAPI::eD3D12; + pref.flags |= sl::PreferenceFlags::eUseManualHooking; + pref.flags |= sl::PreferenceFlags::eUseFrameBasedResourceTagging; + pref.flags |= sl::PreferenceFlags::eUseDXGIFactoryProxy; + pref.flags &= ~sl::PreferenceFlags::eAllowOTA; + pref.flags &= ~sl::PreferenceFlags::eLoadDownloadedPlugins; + Require(init(pref, sl::kSDKVersion) == sl::Result::eOk, "slInit"); + + // Mirror the previous binding order: the bundled plugin is loaded before device selection. + const auto bundled = LoadLibraryW((directory / L"sl.reflex.dll").c_str()); + const auto getPlugin = Export(bundled, "slGetPluginFunction"); + const auto stale = reinterpret_cast(getPlugin("slReflexSetOptions")); + Require(stale != nullptr, "Bundled Reflex export"); + ID3D12Device* device = nullptr; + Require(SUCCEEDED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device))), "D3D12 device"); + Require(setDevice(device) == sl::Result::eOk, "slSetD3DDevice"); + void* function = nullptr; + Require(getFeature(sl::kFeatureReflex, "slReflexSetOptions", function) == sl::Result::eOk && function, + "Active Reflex function"); + PrintModule("Bundled Reflex", reinterpret_cast(stale)); + PrintModule("Active Reflex", function); + sl::ReflexOptions options; + options.mode = sl::ReflexMode::eOff; + options.useMarkersToOptimize = false; + const auto activeReflex = reinterpret_cast(function); + const auto activeResult = CallReflex(activeReflex, options); + Require(activeResult == static_cast(sl::Result::eOk), "Active Reflex startup call"); + std::puts("PASS: active Reflex startup call"); + for (const auto& binding : { std::pair{sl::kFeatureDLSS_G, "slDLSSGSetOptions"}, + {sl::kFeatureDLSS_G, "slDLSSGGetState"}, + {sl::kFeatureReflex, "slReflexGetState"}, + {sl::kFeatureReflex, "slReflexSleep"}, + {sl::kFeaturePCL, "slPCLSetMarker"}, + {sl::kFeaturePCL, "slPCLSetOptions"} }) + { + function = nullptr; + Require(getFeature(binding.first, binding.second, function) == sl::Result::eOk && function, binding.second); + } + std::puts("PASS: all required DLSSG/Reflex/PCL functions resolved from active plugins"); + if (stale != activeReflex) + { + const auto staleResult = CallReflex(stale, options); + std::printf("Old bundled call result: 0x%08x\n", staleResult); + Require(staleResult == EXCEPTION_ACCESS_VIOLATION, "Expected old-path access violation"); + std::puts("PASS: reproduced original access violation; active-plugin binding avoids it"); + } + else + std::puts("No plugin replacement on this runtime: active and bundled functions match"); + shutdown(); + device->Release(); + return 0; + } + catch (const std::exception& error) + { + std::fprintf(stderr, "FAIL: %s\n", error.what()); + return 1; + } +}