From 46a44f17c65c79ad834d35d18e22d8f1519b891f Mon Sep 17 00:00:00 2001 From: ColdBomb1 Date: Wed, 6 May 2026 20:03:05 -0400 Subject: [PATCH 1/2] fix(upscaling): guard OpenComposite conflict --- src/Features/Upscaling.cpp | 372 +++++++++++++++++++++++++++++++++++-- src/Features/Upscaling.h | 15 ++ 2 files changed, 376 insertions(+), 11 deletions(-) diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index 9e1988d70f..e4534d52c4 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -10,10 +10,14 @@ #include "Utils/UI.h" #include #include +#include #include #include +#include #include +#include #include +#include NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( Upscaling::Settings, @@ -127,6 +131,228 @@ namespace texture = nullptr; } + struct OpenCompositeSettingValue + { + bool value = false; + std::string configPath; + }; + + struct OpenCompositeUpscalingSettings + { + OpenCompositeSettingValue dlssEnabled; + OpenCompositeSettingValue fsrEnabled; + OpenCompositeSettingValue dlaaEnabled; + OpenCompositeSettingValue fsrNativeAA; + OpenCompositeSettingValue fsr3PostAAEnabled; + }; + + struct DetectedOpenCompositeUpscalingBlocker + { + bool active = false; + std::string settingName; + std::string configPath; + }; + + std::string_view TrimAsciiWhitespace(std::string_view value) + { + while (!value.empty() && std::isspace(static_cast(value.front()))) + value.remove_prefix(1); + while (!value.empty() && std::isspace(static_cast(value.back()))) + value.remove_suffix(1); + return value; + } + + std::string ToLowerAscii(std::string_view value) + { + std::string result(value); + std::ranges::transform(result, result.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return result; + } + + bool TryParseOpenCompositeBool(std::string value, bool& outValue) + { + value = ToLowerAscii(TrimAsciiWhitespace(value)); + if (value == "true" || value == "on" || value == "enabled" || value == "1") { + outValue = true; + return true; + } + if (value == "false" || value == "off" || value == "disabled" || value == "0") { + outValue = false; + return true; + } + return false; + } + + std::string PathToDisplayString(const std::filesystem::path& path) + { + return path.string(); + } + + void AddUniquePath(std::vector& paths, const std::filesystem::path& path) + { + if (path.empty()) + return; + + auto normalized = path.lexically_normal().wstring(); + std::ranges::transform(normalized, normalized.begin(), [](wchar_t c) { + return static_cast(std::towlower(c)); + }); + + const bool alreadyAdded = std::ranges::any_of(paths, [&](const std::filesystem::path& existing) { + auto existingNormalized = existing.lexically_normal().wstring(); + std::ranges::transform(existingNormalized, existingNormalized.begin(), [](wchar_t c) { + return static_cast(std::towlower(c)); + }); + return existingNormalized == normalized; + }); + if (!alreadyAdded) + paths.push_back(path); + } + + std::filesystem::path GetCurrentDirectoryPath() + { + std::wstring buffer(MAX_PATH, L'\0'); + const DWORD length = GetCurrentDirectoryW(static_cast(buffer.size()), buffer.data()); + if (length == 0) + return {}; + + if (length >= buffer.size()) { + buffer.resize(length + 1); + const DWORD retryLength = GetCurrentDirectoryW(static_cast(buffer.size()), buffer.data()); + if (retryLength == 0 || retryLength >= buffer.size()) + return {}; + buffer.resize(retryLength); + } else { + buffer.resize(length); + } + + return std::filesystem::path(buffer); + } + + std::filesystem::path GetLoadedOpenVRDirectory() + { + HMODULE openVRModule = GetModuleHandleW(L"openvr_api.dll"); + if (!openVRModule) + return {}; + + std::wstring buffer(MAX_PATH, L'\0'); + const DWORD length = GetModuleFileNameW(openVRModule, buffer.data(), static_cast(buffer.size())); + if (length == 0 || length >= buffer.size()) + return {}; + + buffer.resize(length); + return std::filesystem::path(buffer).parent_path(); + } + + bool ShouldProbeOpenCompositeConfig() + { + return globals::game::isVR; + } + + std::vector GetOpenCompositeConfigCandidates() + { + std::vector candidates; + + const auto loadedOpenVRDirectory = GetLoadedOpenVRDirectory(); + if (!loadedOpenVRDirectory.empty()) + AddUniquePath(candidates, loadedOpenVRDirectory / L"opencomposite.ini"); + + const auto currentDirectory = GetCurrentDirectoryPath(); + if (!currentDirectory.empty()) { + AddUniquePath(candidates, currentDirectory / L"opencomposite.ini"); + AddUniquePath(candidates, currentDirectory / L"opencomposite_ext.ini"); + } + + return candidates; + } + + bool TryReadIniBoolSetting(const CSimpleIniA& ini, const char* key, bool& outValue) + { + auto tryReadSection = [&](const char* section) { + const char* rawValue = ini.GetValue(section, key, nullptr); + return rawValue && TryParseOpenCompositeBool(rawValue, outValue); + }; + + if (tryReadSection("")) + return true; + + CSimpleIniA::TNamesDepend sections; + ini.GetAllSections(sections); + for (const auto& section : sections) { + if (section.pItem && tryReadSection(section.pItem)) + return true; + } + + return false; + } + + void UpdateOpenCompositeSettingValue(OpenCompositeSettingValue& setting, const CSimpleIniA& ini, const char* key, const std::filesystem::path& path) + { + bool parsedValue = false; + if (!TryReadIniBoolSetting(ini, key, parsedValue)) + return; + + setting.value = parsedValue; + setting.configPath = PathToDisplayString(path); + } + + OpenCompositeUpscalingSettings ReadOpenCompositeUpscalingSettings() + { + OpenCompositeUpscalingSettings settings; + + std::error_code ec; + for (const auto& path : GetOpenCompositeConfigCandidates()) { + if (!std::filesystem::exists(path, ec)) + continue; + ec.clear(); + + CSimpleIniA ini; + ini.SetUnicode(); + const SI_Error rc = ini.LoadFile(path.c_str()); + if (rc < 0) { + logger::warn("[Upscaling] Failed to read Open Composite config '{}': {}", PathToDisplayString(path), rc); + continue; + } + + UpdateOpenCompositeSettingValue(settings.dlssEnabled, ini, "dlssEnabled", path); + UpdateOpenCompositeSettingValue(settings.fsrEnabled, ini, "fsrEnabled", path); + UpdateOpenCompositeSettingValue(settings.dlaaEnabled, ini, "dlaaEnabled", path); + UpdateOpenCompositeSettingValue(settings.fsrNativeAA, ini, "fsrNativeAA", path); + UpdateOpenCompositeSettingValue(settings.fsr3PostAAEnabled, ini, "fsr3PostAAEnabled", path); + } + + return settings; + } + + DetectedOpenCompositeUpscalingBlocker FindOpenCompositeUpscalingBlocker() + { + DetectedOpenCompositeUpscalingBlocker blocker; + if (!ShouldProbeOpenCompositeConfig()) + return blocker; + + const auto settings = ReadOpenCompositeUpscalingSettings(); + auto setBlocker = [&](const char* settingName, const OpenCompositeSettingValue& setting) { + blocker.active = true; + blocker.settingName = settingName; + blocker.configPath = setting.configPath; + }; + + if (settings.dlaaEnabled.value) + setBlocker("dlaaEnabled", settings.dlaaEnabled); + else if (settings.fsrNativeAA.value) + setBlocker("fsrNativeAA", settings.fsrNativeAA); + else if (settings.fsr3PostAAEnabled.value) + setBlocker("fsr3PostAAEnabled", settings.fsr3PostAAEnabled); + else if (settings.dlssEnabled.value) + setBlocker("dlssEnabled", settings.dlssEnabled); + else if (settings.fsrEnabled.value) + setBlocker("fsrEnabled", settings.fsrEnabled); + + return blocker; + } + float ClampPeripheryTAACenterBlendFeather(float value) { if (!std::isfinite(value)) @@ -623,6 +849,9 @@ void Upscaling::DrawSettings() const bool runtimeFsr4AutoEligible = fidelityFX.IsRuntimeUpscalerAutoEligible(); const bool runtimeFsr4Available = fidelityFX.IsRuntimeUpscalerAvailable(); const bool featureDLSS = streamline.featureDLSS; + ApplyOpenCompositeUpscalingBlocker(); + const auto& openCompositeBlocker = GetOpenCompositeUpscalingBlocker(); + const bool openCompositeBlocksUpscaling = openCompositeBlocker.active; uint32_t* currentUpscaleMode = &settings.upscaleMethod; if (!featureDLSS) @@ -632,18 +861,21 @@ void Upscaling::DrawSettings() settings.fsr4RuntimeEnable; std::vector upscaleChoices = { - { UpscaleMethod::kNONE, false, "None" }, - { UpscaleMethod::kTAA, false, "TAA" }, - { UpscaleMethod::kFSR, false, "AMD FSR 3.1.5" } + { UpscaleMethod::kNONE, false, "None" } }; - if (runtimeFsr4Available) - upscaleChoices.push_back({ UpscaleMethod::kFSR, true, "AMD FSR 4" }); - else if (requestedRuntimeFsr4BeforeMethodSelection) - upscaleChoices.push_back({ UpscaleMethod::kFSR, true, "AMD FSR 4 (Unavailable)" }); + if (!openCompositeBlocksUpscaling) { + upscaleChoices.push_back({ UpscaleMethod::kTAA, false, "TAA" }); + upscaleChoices.push_back({ UpscaleMethod::kFSR, false, "AMD FSR 3.1.5" }); - if (featureDLSS) - upscaleChoices.push_back({ UpscaleMethod::kDLSS, false, "NVIDIA DLSS" }); + if (runtimeFsr4Available) + upscaleChoices.push_back({ UpscaleMethod::kFSR, true, "AMD FSR 4" }); + else if (requestedRuntimeFsr4BeforeMethodSelection) + upscaleChoices.push_back({ UpscaleMethod::kFSR, true, "AMD FSR 4 (Unavailable)" }); + + if (featureDLSS) + upscaleChoices.push_back({ UpscaleMethod::kDLSS, false, "NVIDIA DLSS" }); + } auto matchesCurrentChoice = [&](const UpscaleUiChoice& choice) { if (static_cast(choice.method) != *currentUpscaleMode) @@ -670,10 +902,18 @@ void Upscaling::DrawSettings() } const char* currentMethodLabel = upscaleChoices[methodUiIndex].label; + if (openCompositeBlocksUpscaling) + ImGui::BeginDisabled(); const bool methodChanged = ImGui::SliderInt("Method", &methodUiIndex, 0, static_cast(upscaleChoices.size() - 1), currentMethodLabel); + if (openCompositeBlocksUpscaling) + ImGui::EndDisabled(); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::TextUnformatted("Selects the upscaling backend."); - ImGui::TextUnformatted("Range: choose between TAA, DLSS, FSR 3.1.5, Runtime FSR 4, or None."); + if (openCompositeBlocksUpscaling) { + ImGui::Text("Locked to None while Open Composite has %s=true.", openCompositeBlocker.settingName.c_str()); + } else { + ImGui::TextUnformatted("Selects the upscaling backend."); + ImGui::TextUnformatted("Range: choose between TAA, DLSS, FSR 3.1.5, Runtime FSR 4, or None."); + } } methodUiIndex = std::clamp(methodUiIndex, 0, static_cast(upscaleChoices.size() - 1)); const auto& selectedUpscaleChoice = upscaleChoices[methodUiIndex]; @@ -683,6 +923,21 @@ void Upscaling::DrawSettings() if (selectedUpscaleChoice.method == UpscaleMethod::kFSR) settings.fsr4RuntimeEnable = selectedUpscaleChoice.useRuntimeFsr4; } + if (openCompositeBlocksUpscaling) { + ApplyOpenCompositeUpscalingBlocker(); + ImGui::PushStyleColor(ImGuiCol_Text, Util::Colors::GetWarning()); + if (openCompositeBlocker.configPath.empty()) { + ImGui::TextWrapped( + "Community Shaders Upscaling is locked to None because Open Composite has %s=true.", + openCompositeBlocker.settingName.c_str()); + } else { + ImGui::TextWrapped( + "Community Shaders Upscaling is locked to None because Open Composite has %s=true in %s.", + openCompositeBlocker.settingName.c_str(), + openCompositeBlocker.configPath.c_str()); + } + ImGui::PopStyleColor(); + } // Check the current upscale method auto upscaleMethod = GetUpscaleMethod(); @@ -1318,8 +1573,51 @@ void Upscaling::DrawSettings() } } +const Upscaling::OpenCompositeUpscalingBlocker& Upscaling::GetOpenCompositeUpscalingBlocker(bool a_forceRefresh) const +{ + const ULONGLONG now = GetTickCount64(); + + if (!a_forceRefresh && openCompositeUpscalingBlockerCacheValid) { + return openCompositeUpscalingBlocker; + } + + const auto detectedBlocker = FindOpenCompositeUpscalingBlocker(); + openCompositeUpscalingBlocker.active = detectedBlocker.active; + openCompositeUpscalingBlocker.settingName = detectedBlocker.settingName; + openCompositeUpscalingBlocker.configPath = detectedBlocker.configPath; + openCompositeUpscalingBlockerCacheValid = true; + openCompositeUpscalingBlockerLastRefresh = now; + + return openCompositeUpscalingBlocker; +} + +void Upscaling::ApplyOpenCompositeUpscalingBlocker(bool a_forceRefresh) +{ + const auto& blocker = GetOpenCompositeUpscalingBlocker(a_forceRefresh); + if (!blocker.active) + return; + + if (settings.upscaleMethod != static_cast(UpscaleMethod::kNONE) || + settings.upscaleMethodNoDLSS != static_cast(UpscaleMethod::kNONE)) { + if (blocker.configPath.empty()) { + logger::warn( + "[Upscaling] Forcing Community Shaders Upscaling to None because Open Composite has {}=true.", + blocker.settingName); + } else { + logger::warn( + "[Upscaling] Forcing Community Shaders Upscaling to None because Open Composite has {}=true in {}.", + blocker.settingName, + blocker.configPath); + } + } + + settings.upscaleMethod = static_cast(UpscaleMethod::kNONE); + settings.upscaleMethodNoDLSS = static_cast(UpscaleMethod::kNONE); +} + void Upscaling::SaveSettings(json& o_json) { + ApplyOpenCompositeUpscalingBlocker(true); SanitizeUpscalingSettings(settings); o_json = settings; if (!IsVRRuntimeActive()) { @@ -1348,6 +1646,7 @@ void Upscaling::LoadSettings(json& o_json) logger::warn("[Upscaling] Loaded upscaleMethodNoDLSS {} out of range, clamping to {}", settings.upscaleMethodNoDLSS, static_cast(UpscaleMethod::kFSR)); } SanitizeUpscalingSettings(settings); + ApplyOpenCompositeUpscalingBlocker(true); const float originalReflexFPSLimit = settings.reflexFPSLimit; if (!std::isfinite(settings.reflexFPSLimit)) { settings.reflexFPSLimit = 60.0f; @@ -1381,10 +1680,18 @@ void Upscaling::RestoreDefaultSettings() settings.reflexLowLatencyBoost = false; settings.reflexUseFPSLimit = false; SanitizeUpscalingSettings(settings); + ApplyOpenCompositeUpscalingBlocker(true); } void Upscaling::DataLoaded() { + ApplyOpenCompositeUpscalingBlocker(true); + const auto& blocker = GetOpenCompositeUpscalingBlocker(); + if (blocker.active) { + logger::warn("[Upscaling] Skipping data-loaded upscaling adjustments because Open Composite has {}=true.", blocker.settingName); + return; + } + // Fix screenshots fix from Engine Fixes RE::GetINISetting("bUseTAA:Display")->data.b = false; @@ -1433,6 +1740,13 @@ bool Upscaling::MenuOpenCloseEventHandler::Register() void Upscaling::Load() { + ApplyOpenCompositeUpscalingBlocker(true); + const auto& blocker = GetOpenCompositeUpscalingBlocker(); + if (blocker.active) { + logger::warn("[Upscaling] Skipping D3D11 device hook because Open Composite has {}=true.", blocker.settingName); + return; + } + *(uintptr_t*)&ptrD3D11CreateDeviceAndSwapChainUpscaling = SKSE::PatchIAT(hk_D3D11CreateDeviceAndSwapChainUpscaling, "d3d11.dll", "D3D11CreateDeviceAndSwapChain"); } @@ -1450,6 +1764,13 @@ struct BSImageSpace_Init_FXAA }; void Upscaling::PostPostLoad() { + ApplyOpenCompositeUpscalingBlocker(true); + const auto& blocker = GetOpenCompositeUpscalingBlocker(); + if (blocker.active) { + logger::warn("[Upscaling] Skipping upscaling render hooks because Open Composite has {}=true.", blocker.settingName); + return; + } + bool isGOG = !GetModuleHandle(L"steam_api64.dll"); stl::detour_thunk(REL::RelocationID(79947, 82084)); @@ -1481,6 +1802,9 @@ void Upscaling::PostPostLoad() Upscaling::UpscaleMethod Upscaling::GetUpscaleMethod() const { + if (GetOpenCompositeUpscalingBlocker().active) + return UpscaleMethod::kNONE; + if (streamline.featureDLSS) return (UpscaleMethod)settings.upscaleMethod; return (UpscaleMethod)settings.upscaleMethodNoDLSS; @@ -3751,6 +4075,13 @@ void Upscaling::ConfigureUpscaling(RE::BSGraphics::State* a_viewport) void Upscaling::SetupResources() { + ApplyOpenCompositeUpscalingBlocker(true); + const auto& blocker = GetOpenCompositeUpscalingBlocker(); + if (blocker.active) { + logger::warn("[Upscaling] Skipping upscaling resource setup because Open Composite has {}=true.", blocker.settingName); + return; + } + QueryPerformanceFrequency(&qpf); auto renderer = globals::game::renderer; @@ -4221,6 +4552,25 @@ float Upscaling::GetFrameGenerationFrameTime() const // Unified interface methods void Upscaling::LoadUpscalingSDKs() { + ApplyOpenCompositeUpscalingBlocker(true); + const auto& blocker = GetOpenCompositeUpscalingBlocker(); + if (blocker.active) { + if (!openCompositeUpscalingBackendSkipLogged) { + if (blocker.configPath.empty()) { + logger::warn( + "[Upscaling] Skipping Community Shaders Streamline/FidelityFX backend initialization because Open Composite has {}=true.", + blocker.settingName); + } else { + logger::warn( + "[Upscaling] Skipping Community Shaders Streamline/FidelityFX backend initialization because Open Composite has {}=true in {}.", + blocker.settingName, + blocker.configPath); + } + openCompositeUpscalingBackendSkipLogged = true; + } + return; + } + // Initialize upscaling SDK components during plugin startup // This ensures all SDKs are available before any D3D device creation streamline.LoadInterposer(); diff --git a/src/Features/Upscaling.h b/src/Features/Upscaling.h index 8ab5bb23de..21379c07a7 100644 --- a/src/Features/Upscaling.h +++ b/src/Features/Upscaling.h @@ -477,6 +477,21 @@ struct Upscaling : Feature IDXGISwapChain* GetProxySwapChain(); private: + struct OpenCompositeUpscalingBlocker + { + bool active = false; + std::string settingName; + std::string configPath; + }; + + const OpenCompositeUpscalingBlocker& GetOpenCompositeUpscalingBlocker(bool a_forceRefresh = false) const; + void ApplyOpenCompositeUpscalingBlocker(bool a_forceRefresh = false); + + mutable OpenCompositeUpscalingBlocker openCompositeUpscalingBlocker; + mutable bool openCompositeUpscalingBlockerCacheValid = false; + mutable ULONGLONG openCompositeUpscalingBlockerLastRefresh = 0; + bool openCompositeUpscalingBackendSkipLogged = false; + struct Main_UpdateJitter { static void thunk(RE::BSGraphics::State* a_state); From fbd3a23f309a7a32d404e16f348d74ac789b3319 Mon Sep 17 00:00:00 2001 From: ColdBomb1 Date: Wed, 6 May 2026 20:15:37 -0400 Subject: [PATCH 2/2] fix(ci): locate VS 2026 toolchain --- .github/actions/setup-build-environment/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-build-environment/action.yaml b/.github/actions/setup-build-environment/action.yaml index 0e6879603d..1db2e0daff 100644 --- a/.github/actions/setup-build-environment/action.yaml +++ b/.github/actions/setup-build-environment/action.yaml @@ -51,7 +51,7 @@ runs: uses: ilammy/msvc-dev-cmd@v1 with: arch: x64 - vsversion: ${{ inputs.vs-version == '2026' && '18.0' || '17.0' }} + vsversion: ${{ inputs.vs-version == '2026' && '18' || '17.0' }} - name: Get MSVC version id: msvc_version