diff --git a/CMakeLists.txt b/CMakeLists.txt index 53ed229334..f5c253123c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,6 @@ find_package(efsw CONFIG REQUIRED) find_package(Tracy CONFIG REQUIRED) find_package(directx-headers CONFIG REQUIRED) add_subdirectory(${CMAKE_SOURCE_DIR}/cmake/Streamline) -include(XeSS-SDK) find_path(DETOURS_INCLUDE_DIRS "detours/detours.h") find_library(DETOURS_LIBRARY detours REQUIRED) @@ -235,6 +234,27 @@ if(CLANG_FORMAT_PATH) ) endif() +# ####################################################################################################################### +# # HLSL additional include directories for VS intellisense +# ####################################################################################################################### + +set(HLSL_INCLUDE_DIRS ${FEATURE_SHADER_DIRS} "package/Shaders") + +set(HLSL_INCLUDE_JSON "") +foreach(dir IN LISTS HLSL_INCLUDE_DIRS) + if(HLSL_INCLUDE_JSON STREQUAL "") + set(HLSL_INCLUDE_JSON " \"${dir}\"") + else() + set(HLSL_INCLUDE_JSON "${HLSL_INCLUDE_JSON},\n \"${dir}\"") + endif() +endforeach() + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/shadertoolsconfig.json.in" + "${CMAKE_CURRENT_SOURCE_DIR}/shadertoolsconfig.json" + @ONLY +) + # ####################################################################################################################### # # Shader validation config generation # ####################################################################################################################### @@ -826,9 +846,11 @@ set(AIO_PACKAGE "${DIST_PATH}/${PROJECT_NAME}_AIO-${UTC_NOW}.7z") add_custom_command( OUTPUT ${AIO_PACKAGE} DEPENDS ${CORE_SOURCES} + COMMAND ${CMAKE_COMMAND} -E make_directory ${AIO_DIR} COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} --prefix ${AIO_DIR} - COMMAND ${CMAKE_COMMAND} -E tar cfv ${AIO_PACKAGE} --format=7zip -- . - WORKING_DIRECTORY ${AIO_DIR} + COMMAND + ${CMAKE_COMMAND} -E chdir ${AIO_DIR} ${CMAKE_COMMAND} -E tar cfv + ${AIO_PACKAGE} --format=7zip -- . COMMENT "Creating AIO zip package (manual)" ) add_custom_target("Package-AIO-Manual" DEPENDS ${AIO_PACKAGE}) diff --git a/README.md b/README.md index 2e99b79750..5c9cd88434 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ In Launch Application Menu, use the following settings: Specifically, the Modded Code includes: - Skyrim (and its variants) -- Hardware drivers to enable additional functionality provided via proprietary SDKs, such as [Nvidia DLSS](https://developer.nvidia.com/rtx/dlss/get-started), [AMD FidelityFX FSR3](https://gpuopen.com/fidelityfx-super-resolution-3/), and [Intel XeSS](https://github.com/intel/xess) +- Hardware drivers to enable additional functionality provided via proprietary SDKs, such as [Nvidia DLSS](https://developer.nvidia.com/rtx/dlss/get-started) and [AMD FidelityFX FSR3](https://gpuopen.com/fidelityfx-super-resolution-3/) The Modding Libraries include: diff --git a/cmake/XeSS-SDK.cmake b/cmake/XeSS-SDK.cmake deleted file mode 100644 index 9a67982d24..0000000000 --- a/cmake/XeSS-SDK.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# XeSS SDK Configuration -# This file configures the Intel XeSS SDK integration for the project - -# XeSS is dynamically loaded at runtime, so we don't need to link against static libraries -# The XeSS DLL (libxess.dll) should be placed in the Data/SKSE/Plugins/XeSS directory - -# Find XeSS headers installed by vcpkg port -find_path(INTEL_XESS_INCLUDE_DIRS "xess/xess.h") - -if(INTEL_XESS_INCLUDE_DIRS) - message(STATUS "XeSS SDK headers found via vcpkg at ${INTEL_XESS_INCLUDE_DIRS}") - target_include_directories( - ${PROJECT_NAME} - PRIVATE - ${INTEL_XESS_INCLUDE_DIRS} - ) -else() - message(WARNING "XeSS SDK headers not found - XeSS compilation may fail") - message(STATUS "Make sure intel-xess is installed via vcpkg") -endif() - -# Link required D3D12 libraries for interop -target_link_libraries( - ${PROJECT_NAME} - PRIVATE - d3d12.lib - dxgi.lib -) - -# Add preprocessor definition to enable XeSS support -target_compile_definitions( - ${PROJECT_NAME} - PRIVATE - XESS_SUPPORT=1 -) \ No newline at end of file diff --git a/cmake/ports/intel-xess/portfile.cmake b/cmake/ports/intel-xess/portfile.cmake deleted file mode 100644 index 3b878f5b7b..0000000000 --- a/cmake/ports/intel-xess/portfile.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# Intel XeSS SDK - headers only -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO intel/xess - REF v2.1.0 - SHA512 6129abf9a271c366e8d04f2676ec8f39858cd8e1530b0178911a0c5e1c616db56bc6c577aa3cec2d63f23310cedb658f5e7b463469bb467482bb40af59ed155a - HEAD_REF main -) - -# Install only the necessary header files (not the entire repo) -set(XESS_HEADERS_SOURCE ${SOURCE_PATH}/inc/xess) -file(INSTALL ${XESS_HEADERS_SOURCE} DESTINATION ${CURRENT_PACKAGES_DIR}/include) - -# Install copyright -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") \ No newline at end of file diff --git a/cmake/ports/intel-xess/vcpkg.json b/cmake/ports/intel-xess/vcpkg.json deleted file mode 100644 index 0d047fbd76..0000000000 --- a/cmake/ports/intel-xess/vcpkg.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "intel-xess", - "version": "2.1.0", - "port-version": 1, - "description": "Intel Xe Super Sampling (XeSS) SDK - AI-based upscaling technology (headers only)", - "homepage": "https://github.com/intel/xess", - "supports": "windows" -} diff --git a/cmake/shadertoolsconfig.json.in b/cmake/shadertoolsconfig.json.in new file mode 100644 index 0000000000..b9a589d719 --- /dev/null +++ b/cmake/shadertoolsconfig.json.in @@ -0,0 +1,8 @@ +{ + "root": true, + "hlsl.preprocessorDefinitions": { + }, + "hlsl.additionalIncludeDirectories": [ +@HLSL_INCLUDE_JSON@ + ] +} \ No newline at end of file diff --git a/features/Hair Specular/Shaders/Hair/Hair.hlsli b/features/Hair Specular/Shaders/Hair/Hair.hlsli index 40ec13e532..38d0a58b77 100644 --- a/features/Hair Specular/Shaders/Hair/Hair.hlsli +++ b/features/Hair Specular/Shaders/Hair/Hair.hlsli @@ -326,6 +326,9 @@ namespace Hair float3 GetHairDynamicCubemapSpecularIrradiance(float2 uv, float2 ScreenUV, float3 T, float3 N, float3 VN, float3 V, float glossiness, float3 specLobePrim, float3 specLobeSec) # endif { + if (SharedData::hairSpecularSettings.HairMode == 1) { + return 0; + } float3 SpecularIrradiance = 0; float3 N1 = N; float3 N2 = N; diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index a866435751..144ad827c6 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -289,7 +289,8 @@ VS_OUTPUT main(VS_INPUT input) vsout.LandBlendWeights2.w = 1 - saturate(0.000375600968 * (9625.59961 - length(gridOffset))); vsout.LandBlendWeights2.xyz = input.LandBlendWeights2.xyz; # elif defined(PROJECTED_UV) && !defined(SKINNED) - vsout.TexProj = TextureProj[eyeIndex][2].xyz; + float3x3 texProjWorld3x3 = float3x3(World[eyeIndex][0].xyz, World[eyeIndex][1].xyz, World[eyeIndex][2].xyz); + vsout.TexProj = mul(texProjWorld3x3, TextureProj[eyeIndex][2].xyz); # endif # if defined(EYE) @@ -2098,9 +2099,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif hairT = Hair::ReorientTangent(hairT, worldNormal); - if (SharedData::hairSpecularSettings.Enabled && SharedData::hairSpecularSettings.EnableTangentShift) { - float3 shiftedNormal = Hair::ShiftWorldNormal(hairT, worldNormal, 0, uv); - screenSpaceNormal = normalize(FrameBuffer::WorldToView(shiftedNormal, false, eyeIndex)); + if (SharedData::hairSpecularSettings.Enabled) { + if (SharedData::hairSpecularSettings.EnableTangentShift && SharedData::hairSpecularSettings.HairMode != 1) { + float3 shiftedNormal = Hair::ShiftWorldNormal(hairT, worldNormal, 0, uv); + screenSpaceNormal = normalize(FrameBuffer::WorldToView(shiftedNormal, false, eyeIndex)); + } } float3 transmissionColor = 0; @@ -2721,6 +2724,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(HAIR) && defined(CS_HAIR) if (SharedData::hairSpecularSettings.Enabled && SharedData::hairSpecularSettings.HairMode == 1) ambientNormal = normalize(viewDirection - hairT * dot(viewDirection, hairT)); + screenSpaceNormal = normalize(FrameBuffer::WorldToView(ambientNormal, false, eyeIndex)); # endif float3 directionalAmbientColor = max(0, mul(DirectionalAmbient, float4(ambientNormal, 1.0))); diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index 40aa95dfd8..23c7f083aa 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -10,14 +10,22 @@ static constexpr uint MAX_LIGHTS = 1024; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( LightLimitFix::Settings, EnableContactShadows, - EnableLightsVisualisation, LightsVisualisationMode) void LightLimitFix::DrawSettings() { auto shaderCache = globals::shaderCache; - if (ImGui::TreeNodeEx("Light Limit Visualization", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text(std::format("Clustered Light Count : {}", lightCount).c_str()); + + ImGui::TreePop(); + } + + /////////////////////////////// + ImGui::SeparatorText("Debug"); + + if (ImGui::TreeNode("Light Limit Visualization")) { ImGui::Checkbox("Enable Lights Visualisation", &settings.EnableLightsVisualisation); if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("Enables visualization of the light limit\n"); @@ -43,12 +51,6 @@ void LightLimitFix::DrawSettings() ImGui::TreePop(); } - - if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text(std::format("Clustered Light Count : {}", lightCount).c_str()); - - ImGui::TreePop(); - } } LightLimitFix::PerFrame LightLimitFix::GetCommonBufferData() @@ -563,4 +565,4 @@ void LightLimitFix::Hooks::BSWaterShader_SetupGeometry::thunk(RE::BSShader* This auto& singleton = globals::features::lightLimitFix; singleton.BSLightingShader_SetupGeometry_Before(Pass); singleton.BSLightingShader_SetupGeometry_After(Pass); -}; \ No newline at end of file +}; diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index c7e122b1b6..140564eca4 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -1091,7 +1091,7 @@ bool Upscaling::IsUpscalingActive() { auto method = GetUpscaleMethod(); - // Only consider vendor upscalers (FSR/XeSS/DLSS) as "active" when the + // Only consider vendor upscalers (FSR/DLSS) as "active" when the // selected method actually produces a downscale. If the renderer is // currently running at 1:1 (no downscale) then depth-buffer culling and // other VR-sensitive behavior can remain enabled. diff --git a/src/Features/Upscaling.h b/src/Features/Upscaling.h index 220e902801..c5056a68c1 100644 --- a/src/Features/Upscaling.h +++ b/src/Features/Upscaling.h @@ -9,7 +9,7 @@ #include /** - * @brief Provides upscaling functionality including DLSS, FSR, XeSS and TAA. + * @brief Provides upscaling functionality including DLSS, FSR and TAA. * * This feature handles various upscaling methods and frame generation technologies * to improve performance while maintaining visual quality. @@ -30,7 +30,6 @@ struct Upscaling : Feature "Advanced upscaling and frame generation technologies for improved performance", { "DLSS (Deep Learning Super Sampling) support", "FSR (FidelityFX Super Resolution) support", - "XeSS (Intel Xe Super Sampling) support", "TAA (Temporal Anti-Aliasing) support", "Frame generation for supported systems" } }; diff --git a/src/Features/VR.cpp b/src/Features/VR.cpp index b0b4f2d3b8..7ebec819f8 100644 --- a/src/Features/VR.cpp +++ b/src/Features/VR.cpp @@ -131,7 +131,7 @@ void VR::PostPostLoad() void VR::DataLoaded() { // Initialize occlusion culling based on settings, but force-disable if an external - // upscaler is active (FSR/XeSS/DLSS) since upscalers may modify the depth buffer. + // upscaler is active (FSR/DLSS) since upscalers may modify the depth buffer. bool desired = settings.EnableDepthBufferCullingExterior; UpdateDepthBufferCulling(desired); @@ -590,7 +590,7 @@ namespace // If an upscaler is active that rewrites or repurposes the depth buffer, // depth-buffer-culling must be disabled to avoid incorrect occlusion tests // (which are especially problematic in VR). Query the Upscaling feature - // to see whether we're running FSR, XeSS or DLSS. + // to see whether we're running FSR or DLSS. // Determine if an external upscaler is active by reading the numeric // setting value directly. Avoid referencing Upscaling types here to // prevent header/type collisions in this translation unit. @@ -603,7 +603,7 @@ namespace ImGui::Checkbox("Enable Depth Buffer Culling in Exteriors", &settings.EnableDepthBufferCullingExterior); if (upscalingActive) { if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Disabled while an external upscaler is active (FSR/XeSS/DLSS) because upscalers may modify depth.\nThis prevents incorrect occlusion in VR."); + ImGui::Text("Disabled while an external upscaler is active (FSR/DLSS) because upscalers may modify depth.\nThis prevents incorrect occlusion in VR."); } ImGui::EndDisabled(); } else { @@ -618,7 +618,7 @@ namespace ImGui::Checkbox("Enable Depth Buffer Culling in Interiors", &settings.EnableDepthBufferCullingInterior); if (upscalingActive) { if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Disabled while an external upscaler is active (FSR/XeSS/DLSS) because upscalers may modify depth.\nThis prevents incorrect occlusion in VR."); + ImGui::Text("Disabled while an external upscaler is active (FSR/DLSS) because upscalers may modify depth.\nThis prevents incorrect occlusion in VR."); } ImGui::EndDisabled(); } else { diff --git a/src/Menu.cpp b/src/Menu.cpp index 99dee1ccfb..6cbbd0bbda 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -141,6 +141,9 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( SkipCompilationKey, EffectToggleKey, OverlayToggleKey, + ShaderBlockPrevKey, + ShaderBlockNextKey, + EnableShaderBlocking, FirstTimeSetupCompleted, Theme, SelectedThemePreset) @@ -487,7 +490,9 @@ void Menu::DrawGeneralSettings() .settingToggleKey = settingToggleKey, .settingsEffectsToggle = settingsEffectsToggle, .settingSkipCompilationKey = settingSkipCompilationKey, - .settingOverlayToggleKey = settingOverlayToggleKey + .settingOverlayToggleKey = settingOverlayToggleKey, + .settingShaderBlockPrevKey = settingShaderBlockPrevKey, + .settingShaderBlockNextKey = settingShaderBlockNextKey }; // Render settings using extracted component @@ -507,7 +512,7 @@ void Menu::DrawAdvancedSettings() { // Render advanced settings using extracted component AdvancedSettingsRenderer::RenderAdvancedSettings( - []() { globals::truePBR->DrawSettings(); }, + [this]() { globals::truePBR->DrawSettings(); }, [this]() { DrawDisableAtBootSettings(); }); } @@ -516,49 +521,49 @@ void Menu::DrawDisableAtBootSettings() auto state = globals::state; auto& disabledFeatures = state->GetDisabledFeatures(); - if (ImGui::CollapsingHeader("Disable at Boot", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) { - ImGui::Text( - "Select features to disable at boot. " - "This is the same as deleting a feature.ini file. " - "Restart will be required to reenable."); - - if (ImGui::CollapsingHeader("Special Features")) { - // Prepare a sorted list of special feature names - std::vector specialFeatureNames; - for (const auto& [featureName, _] : state->specialFeatures) { - specialFeatureNames.push_back(featureName); - } - std::sort(specialFeatureNames.begin(), specialFeatureNames.end()); + ImGui::Text( + "Select features to disable at boot. " + "This is the same as deleting a feature.ini file. " + "Restart will be required to reenable."); - // Display sorted special features - for (const auto& featureName : specialFeatureNames) { - // Check if the feature is currently disabled - bool isDisabled = disabledFeatures.contains(featureName) && disabledFeatures[featureName]; + ImGui::Spacing(); - // Create a checkbox for each feature - if (ImGui::Checkbox(featureName.c_str(), &isDisabled)) { - // Update the disabledFeatures map based on user interaction - disabledFeatures[featureName] = isDisabled; - } + if (ImGui::CollapsingHeader("Special Features", ImGuiTreeNodeFlags_DefaultOpen)) { + // Prepare a sorted list of special feature names + std::vector specialFeatureNames; + for (const auto& [featureName, _] : state->specialFeatures) { + specialFeatureNames.push_back(featureName); + } + std::sort(specialFeatureNames.begin(), specialFeatureNames.end()); + + // Display sorted special features + for (const auto& featureName : specialFeatureNames) { + // Check if the feature is currently disabled + bool isDisabled = disabledFeatures.contains(featureName) && disabledFeatures[featureName]; + + // Create a checkbox for each feature + if (ImGui::Checkbox(featureName.c_str(), &isDisabled)) { + // Update the disabledFeatures map based on user interaction + disabledFeatures[featureName] = isDisabled; } } + } - if (ImGui::CollapsingHeader("Features")) { - // Prepare a sorted list of feature pointers - auto featureList = Feature::GetFeatureList(); - std::sort(featureList.begin(), featureList.end(), [](Feature* a, Feature* b) { - return a->GetShortName() < b->GetShortName(); - }); - - // Display sorted features - for (auto* feature : featureList) { - const std::string featureName = feature->GetShortName(); - bool isDisabled = disabledFeatures.contains(featureName) && disabledFeatures[featureName]; - - if (ImGui::Checkbox(featureName.c_str(), &isDisabled)) { - // Update the disabledFeatures map based on user interaction - disabledFeatures[featureName] = isDisabled; - } + if (ImGui::CollapsingHeader("Features", ImGuiTreeNodeFlags_DefaultOpen)) { + // Prepare a sorted list of feature pointers + auto featureList = Feature::GetFeatureList(); + std::sort(featureList.begin(), featureList.end(), [](Feature* a, Feature* b) { + return a->GetShortName() < b->GetShortName(); + }); + + // Display sorted features + for (auto* feature : featureList) { + const std::string featureName = feature->GetShortName(); + bool isDisabled = disabledFeatures.contains(featureName) && disabledFeatures[featureName]; + + if (ImGui::Checkbox(featureName.c_str(), &isDisabled)) { + // Update the disabledFeatures map based on user interaction + disabledFeatures[featureName] = isDisabled; } } } @@ -673,16 +678,24 @@ void Menu::ProcessInputEventQueue() std::function action; }; auto shaderCache = globals::shaderCache; - auto devMode = globals::state->IsDeveloperMode(); HotkeyAction hotkeyActions[] = { { &settings.ToggleKey, &settingToggleKey, [this](uint32_t key) { settings.ToggleKey = key; settingToggleKey = false; } }, { &settings.SkipCompilationKey, &settingSkipCompilationKey, [this](uint32_t key) { settings.SkipCompilationKey = key; settingSkipCompilationKey = false; } }, { &settings.EffectToggleKey, &settingsEffectsToggle, [this](uint32_t key) { settings.EffectToggleKey = key; settingsEffectsToggle = false; } }, { &settings.OverlayToggleKey, &settingOverlayToggleKey, [this](uint32_t key) { settings.OverlayToggleKey = key; settingOverlayToggleKey = false; } }, + { &settings.ShaderBlockPrevKey, &settingShaderBlockPrevKey, [this](uint32_t key) { settings.ShaderBlockPrevKey = key; settingShaderBlockPrevKey = false; } }, + { &settings.ShaderBlockNextKey, &settingShaderBlockNextKey, [this](uint32_t key) { settings.ShaderBlockNextKey = key; settingShaderBlockNextKey = false; } }, }; bool handled = false; for (auto& h : hotkeyActions) { if (*(h.settingFlag)) { + // During first-time setup, don't capture Enter or Escape as hotkeys + // These keys are reserved for closing the dialog + if (HomePageRenderer::ShouldShowFirstTimeSetup() && (key == VK_RETURN || key == VK_ESCAPE)) { + *(h.settingFlag) = false; // Cancel hotkey capture mode + handled = true; + break; + } h.action(key); handled = true; break; @@ -698,8 +711,8 @@ void Menu::ProcessInputEventQueue() { settings.ToggleKey, [this]() { IsEnabled = !IsEnabled; } }, { settings.SkipCompilationKey, [shaderCache]() { shaderCache->backgroundCompilation = true; } }, { settings.EffectToggleKey, [shaderCache]() { shaderCache->SetEnabled(!shaderCache->IsEnabled()); } }, - { priorShaderKey, [shaderCache, devMode]() { if (devMode) shaderCache->IterateShaderBlock(); } }, - { nextShaderKey, [shaderCache, devMode]() { if (devMode) shaderCache->IterateShaderBlock(false); } }, + { settings.ShaderBlockPrevKey, [this, shaderCache]() { if (settings.EnableShaderBlocking) shaderCache->IterateShaderBlock(); } }, + { settings.ShaderBlockNextKey, [this, shaderCache]() { if (settings.EnableShaderBlocking) shaderCache->IterateShaderBlock(false); } }, { settings.OverlayToggleKey, []() { Menu::GetSingleton()->overlayVisible = !Menu::GetSingleton()->overlayVisible; } }, diff --git a/src/Menu.h b/src/Menu.h index 14576f64b2..d9470072bb 100644 --- a/src/Menu.h +++ b/src/Menu.h @@ -134,8 +134,8 @@ class Menu bool settingSkipCompilationKey = false; bool settingsEffectsToggle = false; bool settingOverlayToggleKey = false; - uint32_t priorShaderKey = VK_PRIOR; // used for blocking shaders in debugging - uint32_t nextShaderKey = VK_NEXT; // used for blocking shaders in debugging + bool settingShaderBlockPrevKey = false; // Debug: capture shader block prev key + bool settingShaderBlockNextKey = false; // Debug: capture shader block next key // Font caching (made public for ThemeManager and OverlayRenderer access) // Marked mutable because they're cache fields that may be updated from const methods @@ -365,6 +365,9 @@ class Menu uint32_t SkipCompilationKey = VK_ESCAPE; uint32_t EffectToggleKey = VK_MULTIPLY; // toggle all effects uint32_t OverlayToggleKey = VK_F10; // Global overlay toggle key for all overlays + uint32_t ShaderBlockPrevKey = VK_PRIOR; // Debug: cycle backward through shaders (PageUp) + uint32_t ShaderBlockNextKey = VK_NEXT; // Debug: cycle forward through shaders (PageDown) + bool EnableShaderBlocking = false; // Enable shader blocking hotkeys for debugging bool FirstTimeSetupCompleted = false; // Track if first-time setup has been completed ThemeSettings Theme; std::string SelectedThemePreset = ""; // Currently selected theme preset (empty = custom/user theme) diff --git a/src/Menu/AdvancedSettingsRenderer.cpp b/src/Menu/AdvancedSettingsRenderer.cpp index 5f69781cbb..b83fcd5d15 100644 --- a/src/Menu/AdvancedSettingsRenderer.cpp +++ b/src/Menu/AdvancedSettingsRenderer.cpp @@ -8,210 +8,224 @@ #include "FeatureIssues.h" #include "Features/PerformanceOverlay/ABTesting/ABTesting.h" +#include "Fonts.h" #include "Globals.h" #include "Menu.h" #include "ShaderCache.h" #include "State.h" #include "TruePBR.h" #include "Util.h" +#include "Utils/Format.h" #include "Utils/UI.h" void AdvancedSettingsRenderer::RenderAdvancedSettings( const std::function& drawTruePBRSettings, const std::function& drawDisableAtBootSettings) { - RenderAdvancedSection(); - RenderShaderReplacementSection(); - - // TruePBR settings - drawTruePBRSettings(); - - // Disable at boot settings - drawDisableAtBootSettings(); - - RenderShaderDebugSection(); - RenderDeveloperSection(); -} - -void AdvancedSettingsRenderer::RenderAdvancedSection() -{ - auto shaderCache = globals::shaderCache; - - if (ImGui::CollapsingHeader("Advanced", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) { - // Dump Shaders option - bool useDump = shaderCache->IsDump(); - if (ImGui::Checkbox("Dump Shaders", &useDump)) { - shaderCache->SetDump(useDump); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Dump shaders at startup. This should be used only when reversing shaders. Normal users don't need this."); + // Use TabBar system - tabs sorted alphabetically + if (ImGui::BeginTabBar("##AdvancedSettingsTabs", ImGuiTabBarFlags_None)) { + // Developer Tab + if (MenuFonts::BeginTabItemWithFont("Developer", Menu::FontRole::Subheading)) { + if (ImGui::BeginChild("##DeveloperContent", ImVec2(0, 0), false)) { + RenderDeveloperSection(); + } + ImGui::EndChild(); + ImGui::EndTabItem(); } - // Log Level selection - spdlog::level::level_enum logLevel = globals::state->GetLogLevel(); - const char* items[] = { - "trace", - "debug", - "info", - "warn", - "err", - "critical", - "off" - }; - static int item_current = static_cast(logLevel); - if (ImGui::Combo("Log Level", &item_current, items, IM_ARRAYSIZE(items))) { - ImGui::SameLine(); - globals::state->SetLogLevel(static_cast(item_current)); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Log level. Trace is most verbose. Default is info."); + // Disable at Boot Tab + if (MenuFonts::BeginTabItemWithFont("Disable at Boot", Menu::FontRole::Subheading)) { + if (ImGui::BeginChild("##DisableAtBootContent", ImVec2(0, 0), false)) { + RenderDisableAtBootSection(drawDisableAtBootSettings); + } + ImGui::EndChild(); + ImGui::EndTabItem(); } - // Shader Defines input - auto& shaderDefines = globals::state->shaderDefinesString; - if (ImGui::InputText("Shader Defines", &shaderDefines)) { - globals::state->SetDefines(shaderDefines); - } - if (ImGui::IsItemDeactivatedAfterEdit() || (ImGui::IsItemActive() && - (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)) || - ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_KeypadEnter))))) { - globals::state->SetDefines(shaderDefines); - shaderCache->Clear(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Defines for Shader Compiler. Semicolon \";\" separated. Clear with space. Rebuild shaders after making change. Compute Shaders require a restart to recompile."); + // Logging Tab + if (MenuFonts::BeginTabItemWithFont("Logging", Menu::FontRole::Subheading)) { + if (ImGui::BeginChild("##LoggingContent", ImVec2(0, 0), false)) { + RenderLoggingSection(); + } + ImGui::EndChild(); + ImGui::EndTabItem(); } - ImGui::Spacing(); - - // Compiler Thread controls - ImGui::SliderInt("Compiler Threads", &shaderCache->compilationThreadCount, 1, static_cast(std::thread::hardware_concurrency())); - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Number of threads to use to compile shaders. " - "The more threads the faster compilation will finish but may make the system unresponsive. "); + // PBR Settings Tab + if (MenuFonts::BeginTabItemWithFont("PBR Settings", Menu::FontRole::Subheading)) { + if (ImGui::BeginChild("##PBRSettingsContent", ImVec2(0, 0), false)) { + RenderPBRSection(drawTruePBRSettings); + } + ImGui::EndChild(); + ImGui::EndTabItem(); } - ImGui::SliderInt("Background Compiler Threads", &shaderCache->backgroundCompilationThreadCount, 1, static_cast(std::thread::hardware_concurrency())); - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Number of threads to use to compile shaders while playing game. " - "This is activated if the startup compilation is skipped. " - "The more threads the faster compilation will finish but may make the system unresponsive. "); + + // Shader Debug Tab + if (MenuFonts::BeginTabItemWithFont("Shader Debug", Menu::FontRole::Subheading)) { + if (ImGui::BeginChild("##ShaderDebugContent", ImVec2(0, 0), false)) { + RenderShaderDebugSection(); + } + ImGui::EndChild(); + ImGui::EndTabItem(); } - // A/B Testing settings - auto* abTestingManager = ABTestingManager::GetSingleton(); - abTestingManager->DrawSettingsUI(); + ImGui::EndTabBar(); + } +} + +void AdvancedSettingsRenderer::RenderLoggingSection() +{ + auto shaderCache = globals::shaderCache; - // File Watcher option - bool useFileWatcher = shaderCache->UseFileWatcher(); - if (ImGui::Checkbox("Enable File Watcher", &useFileWatcher)) { - shaderCache->SetFileWatcher(useFileWatcher); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Automatically recompile shaders on file change. " - "Intended for developing."); - } + // Log Level selection + spdlog::level::level_enum logLevel = globals::state->GetLogLevel(); + const char* items[] = { + "trace", + "debug", + "info", + "warn", + "err", + "critical", + "off" + }; + static int item_current = static_cast(logLevel); + if (ImGui::Combo("Log Level", &item_current, items, IM_ARRAYSIZE(items))) { + ImGui::SameLine(); + globals::state->SetLogLevel(static_cast(item_current)); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Log level. Trace is most verbose. Default is info."); + } - // Dump Ini Settings button - if (ImGui::Button("Dump Ini Settings", { -1, 0 })) { - Util::DumpSettingsOptions(); - } + // Shader Defines input + auto& shaderDefines = globals::state->shaderDefinesString; + if (ImGui::InputText("Shader Defines", &shaderDefines)) { + globals::state->SetDefines(shaderDefines); + } + if (ImGui::IsItemDeactivatedAfterEdit() || (ImGui::IsItemActive() && + (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)) || + ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_KeypadEnter))))) { + globals::state->SetDefines(shaderDefines); + shaderCache->Clear(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Defines for Shader Compiler. Semicolon \";\" separated. Clear with space. Rebuild shaders after making change. Compute Shaders require a restart to recompile."); + } - // Clear Shader Cache button - if (ImGui::Button("Clear Shader Cache", { -1, 0 })) { - shaderCache->Clear(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Clear all compiled shaders from memory. Forces recompilation of all shaders on next use."); - } + ImGui::Spacing(); - // Debug addresses section - if (ImGui::TreeNodeEx("Addresses")) { - auto Renderer = globals::game::renderer; - auto BSShaderAccumulator = *globals::game::currentAccumulator.get(); - auto RendererShadowState = globals::game::shadowState; - ADDRESS_NODE(Renderer) - ADDRESS_NODE(BSShaderAccumulator) - ADDRESS_NODE(RendererShadowState) - ImGui::TreePop(); - } + // Compiler Thread controls + ImGui::SliderInt("Compiler Threads", &shaderCache->compilationThreadCount, 1, static_cast(std::thread::hardware_concurrency())); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Number of threads to use to compile shaders. " + "The more threads the faster compilation will finish but may make the system unresponsive. "); + } + ImGui::SliderInt("Background Compiler Threads", &shaderCache->backgroundCompilationThreadCount, 1, static_cast(std::thread::hardware_concurrency())); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Number of threads to use to compile shaders while playing game. " + "This is activated if the startup compilation is skipped. " + "The more threads the faster compilation will finish but may make the system unresponsive. "); + } - // Statistics section - if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text(std::format("Shader Compiler : {}", shaderCache->GetShaderStatsString()).c_str()); - ImGui::TreePop(); - } + // A/B Testing settings + auto* abTestingManager = ABTestingManager::GetSingleton(); + abTestingManager->DrawSettingsUI(); - // Frame annotations toggle - ImGui::Checkbox("Frame Annotations", &globals::state->frameAnnotations); - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Enable detailed frame annotations for debugging render passes and draw calls."); - } + // Dump Ini Settings button + if (ImGui::Button("Dump Ini Settings", { -1, 0 })) { + Util::DumpSettingsOptions(); } } -void AdvancedSettingsRenderer::RenderShaderReplacementSection() +void AdvancedSettingsRenderer::RenderShaderDebugSection() { - if (ImGui::CollapsingHeader("Replace Original Shaders", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) { - auto state = globals::state; - if (ImGui::BeginTable("##ReplaceToggles", 3, ImGuiTableFlags_SizingStretchSame)) { - globals::state->ForEachShaderTypeWithIndex([&](auto type, int classIndex) { - ImGui::TableNextColumn(); - - if (!(SIE::ShaderCache::IsSupportedShader(type) || state->IsDeveloperMode())) { - ImGui::BeginDisabled(); - ImGui::Checkbox(std::format("{}", magic_enum::enum_name(type)).c_str(), &state->enabledClasses[classIndex]); - ImGui::EndDisabled(); - } else - ImGui::Checkbox(std::format("{}", magic_enum::enum_name(type)).c_str(), &state->enabledClasses[classIndex]); - }); - if (state->IsDeveloperMode()) { - ImGui::Checkbox("Vertex", &state->enableVShaders); - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Replace Vertex Shaders. " - "When false, will disable the custom Vertex Shaders for the types above. " - "For developers to test whether CS shaders match vanilla behavior. "); - } + auto shaderCache = globals::shaderCache; + auto state = globals::state; - ImGui::Checkbox("Pixel", &state->enablePShaders); - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Replace Pixel Shaders. " - "When false, will disable the custom Pixel Shaders for the types above. " - "For developers to test whether CS shaders match vanilla behavior. "); - } + // Dump Shaders option + bool useDump = shaderCache->IsDump(); + if (ImGui::Checkbox("Dump Shaders", &useDump)) { + shaderCache->SetDump(useDump); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Dump shaders at startup. This should be used only when reversing shaders. Normal users don't need this."); + } - ImGui::Checkbox("Compute", &state->enableCShaders); - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Replace Compute Shaders. " - "When false, will disable the custom Compute Shaders for the types above. " - "For developers to test whether CS shaders match vanilla behavior. "); - } + // Clear Shader Cache button + if (ImGui::Button("Clear Shader Cache", { -1, 0 })) { + shaderCache->Clear(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Clear all compiled shaders from memory. Forces recompilation of all shaders on next use."); + } + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + // Shader Replacement section + Util::DrawSectionHeader("Replace Original Shaders"); + + if (ImGui::BeginTable("##ReplaceToggles", 3, ImGuiTableFlags_SizingStretchSame)) { + globals::state->ForEachShaderTypeWithIndex([&](auto type, int classIndex) { + ImGui::TableNextColumn(); + + if (!(SIE::ShaderCache::IsSupportedShader(type) || state->IsDeveloperMode())) { + ImGui::BeginDisabled(); + ImGui::Checkbox(std::format("{}", magic_enum::enum_name(type)).c_str(), &state->enabledClasses[classIndex]); + ImGui::EndDisabled(); + } else + ImGui::Checkbox(std::format("{}", magic_enum::enum_name(type)).c_str(), &state->enabledClasses[classIndex]); + }); + if (state->IsDeveloperMode()) { + ImGui::Checkbox("Vertex", &state->enableVShaders); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Replace Vertex Shaders. " + "When false, will disable the custom Vertex Shaders for the types above. " + "For developers to test whether CS shaders match vanilla behavior. "); + } + + ImGui::Checkbox("Pixel", &state->enablePShaders); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Replace Pixel Shaders. " + "When false, will disable the custom Pixel Shaders for the types above. " + "For developers to test whether CS shaders match vanilla behavior. "); + } + + ImGui::Checkbox("Compute", &state->enableCShaders); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Replace Compute Shaders. " + "When false, will disable the custom Compute Shaders for the types above. " + "For developers to test whether CS shaders match vanilla behavior. "); } - ImGui::EndTable(); } + ImGui::EndTable(); } -} - -void AdvancedSettingsRenderer::RenderShaderDebugSection() -{ - auto shaderCache = globals::shaderCache; + // Only show shader blocking section in developer mode if (!globals::state->IsDeveloperMode()) { return; } + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + // Show blocked shader status as a regular section if (!shaderCache->blockedKey.empty()) { - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.2f, 0.1f, 0.1f, 0.8f)); - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); - ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f); - - if (ImGui::CollapsingHeader("Currently Blocked Shader", ImGuiTreeNodeFlags_DefaultOpen)) { + // Create a visually distinct box for the blocked shader info with rounded corners and border + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); + ImVec4 blockedBgColor = Util::Colors::GetError(); + blockedBgColor.w = 0.15f; // Semi-transparent background + ImGui::PushStyleColor(ImGuiCol_ChildBg, blockedBgColor); + + if (ImGui::BeginChild("##BlockedShaderInfo", ImVec2(0, 0), true, ImGuiChildFlags_AutoResizeY)) { ImGui::TextColored(Util::Colors::GetError(), "Shader Blocking Active"); ImGui::SameLine(); if (ImGui::SmallButton("Stop Blocking##Section")) { @@ -224,20 +238,18 @@ void AdvancedSettingsRenderer::RenderShaderDebugSection() auto activeShaders = shaderCache->GetActiveShaders(); for (const auto& shader : activeShaders) { if (shader.key == shaderCache->blockedKey) { - ImGui::Text("Type: %s | Class: %s | Descriptor: 0x%X", - magic_enum::enum_name(shader.shaderType).data(), - magic_enum::enum_name(shader.shaderClass).data(), - shader.descriptor); - - // Add copy button with full information including disk cache - ImGui::SameLine(); - ImGui::PushID("copy_blocked_shader"); - if (ImGui::SmallButton("Copy Info")) { - // Convert wstring to string for display + ImGui::Text("Type: %s", magic_enum::enum_name(shader.shaderType).data()); + ImGui::Text("Class: %s", magic_enum::enum_name(shader.shaderClass).data()); + ImGui::Text("Descriptor: 0x%X", shader.descriptor); + + // Add button to copy shader info to clipboard + ImGui::PushID(shader.key.c_str()); + if (ImGui::SmallButton("Copy Info##BlockedShader")) { std::string diskPathStr; - diskPathStr.resize(shader.diskPath.size()); - std::transform(shader.diskPath.begin(), shader.diskPath.end(), diskPathStr.begin(), - [](wchar_t c) { return static_cast(c); }); + diskPathStr.reserve(shader.diskPath.size()); + for (wchar_t wc : shader.diskPath) { + diskPathStr += static_cast(wc); + } std::string fullInfo = std::format("Type: {}\nClass: {}\nDescriptor: 0x{:X}\nKey: {}\nCache Path: {}", magic_enum::enum_name(shader.shaderType).data(), @@ -258,10 +270,61 @@ void AdvancedSettingsRenderer::RenderShaderDebugSection() } } } + ImGui::EndChild(); ImGui::PopStyleVar(); // ChildRounding ImGui::PopStyleVar(); // WindowBorderSize - ImGui::PopStyleColor(); // WindowBg + ImGui::PopStyleColor(); // ChildBg + } + + // Shader Debug section + if (ImGui::CollapsingHeader("Shader Debug")) { + auto menu = globals::menu; + auto& menuSettings = menu->GetSettings(); + auto& themeSettings = menuSettings.Theme; + + if (ImGui::Checkbox("Enable Shader Blocking", &menuSettings.EnableShaderBlocking)) { + // Setting saved automatically on next save + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Enables hotkeys to cycle through and block individual shaders for debugging purposes."); + } + + if (menuSettings.EnableShaderBlocking) { + ImGui::Indent(); + + // Shader Block Previous Key + if (menu->settingShaderBlockPrevKey) { + ImGui::Text("Press any key for Shader Block Previous..."); + } else { + ImGui::AlignTextToFramePadding(); + ImGui::Text("Block Previous:"); + ImGui::SameLine(); + ImGui::AlignTextToFramePadding(); + ImGui::TextColored(themeSettings.StatusPalette.CurrentHotkey, "%s", Util::Input::KeyIdToString(menuSettings.ShaderBlockPrevKey)); + ImGui::SameLine(); + if (ImGui::Button("Change##ShaderBlockPrev")) { + menu->settingShaderBlockPrevKey = true; + } + } + + // Shader Block Next Key + if (menu->settingShaderBlockNextKey) { + ImGui::Text("Press any key for Shader Block Next..."); + } else { + ImGui::AlignTextToFramePadding(); + ImGui::Text("Block Next:"); + ImGui::SameLine(); + ImGui::AlignTextToFramePadding(); + ImGui::TextColored(themeSettings.StatusPalette.CurrentHotkey, "%s", Util::Input::KeyIdToString(menuSettings.ShaderBlockNextKey)); + ImGui::SameLine(); + if (ImGui::Button("Change##ShaderBlockNext")) { + menu->settingShaderBlockNextKey = true; + } + } + + ImGui::Unindent(); + } } // Active shaders list @@ -270,25 +333,22 @@ void AdvancedSettingsRenderer::RenderShaderDebugSection() if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text( "List of shaders that have been used in recent frames. " - "Use PAGEUP/PAGEDOWN to cycle through and block shaders for debugging. " + "Enable Shader Blocking above to use hotkeys to cycle through and block shaders for debugging. " "Shaders not used for ~1 second are removed from this list."); } // Get fresh active shaders data for accurate count and table auto activeShaders = shaderCache->GetActiveShaders(); - ImGui::Text("Total Active: %zu", activeShaders.size()); - - // Calculate total draw calls for percentage calculation uint32_t totalDrawCalls = 0; for (const auto& shader : activeShaders) { totalDrawCalls += shader.drawCalls; } - // Filter controls (now handled by ShowFilteredStringTableCustom) + // Static variables to maintain table filter state static char filterText[256] = ""; - static int searchColumn = 0; // 0 = All Columns, 1 = Type, 2 = Class, 3 = Descriptor, 4 = Draw Calls, 5 = Key - - // Create shader rows for the table utility (simplified - no filter data needed) + static int searchColumn = 0; // 0 = All Columns, 1 = Type, 2 = Class, 3 = Descriptor, 4 = Draw Calls, 5 = Key + static size_t sortColumn = 4; // Default sort by Frame % (draw calls) + static bool sortAscending = false; // Descending by default (highest usage first) // Create shader rows for the table utility (simplified - no filter data needed) struct ShaderRow { SIE::ShaderCache::ActiveShaderInfo shader; @@ -308,10 +368,10 @@ void AdvancedSettingsRenderer::RenderShaderDebugSection() { "Class", "Shader class", [](const ShaderRow& row) { return std::string(magic_enum::enum_name(row.shader.shaderClass)); } }, - { "Descriptor", "Shader descriptor hash", [](const ShaderRow& row) { + { "Descriptor", "Shader descriptor", [](const ShaderRow& row) { return std::format("0x{:X}", row.shader.descriptor); } }, - { "Frame %", "Percentage of total draw calls in current frame", [](const ShaderRow& row) { + { "Frame %", "Percentage of draw calls this frame", [](const ShaderRow& row) { float percentage = Util::CalculatePercentage(static_cast(row.shader.drawCalls), static_cast(row.totalDrawCalls)); return Util::FormatPercent(percentage); } }, @@ -323,23 +383,21 @@ void AdvancedSettingsRenderer::RenderShaderDebugSection() // Row click callbacks auto onRowLeftClick = [shaderCache](const ShaderRow& row) { if (row.shader.key == shaderCache->blockedKey) { - // Clicking on already blocked shader - unblock it shaderCache->DisableShaderBlocking(); } else { - // Clicking on different shader - block it + // Block this shader - use IterateShaderBlock to find and block it + // Or set blockedKey directly (simpler for click-to-block) shaderCache->blockedKey = row.shader.key; - shaderCache->blockedKeyIndex = 0; - shaderCache->blockedIDs.clear(); - logger::debug("Manually blocking shader: {}", row.shader.key); + logger::info("Blocking shader: {}", row.shader.key); } }; auto onRowRightClick = [shaderCache](const ShaderRow& row) { - // Convert wstring to string for display std::string diskPathStr; - diskPathStr.resize(row.shader.diskPath.size()); - std::transform(row.shader.diskPath.begin(), row.shader.diskPath.end(), diskPathStr.begin(), - [](wchar_t c) { return static_cast(c); }); + diskPathStr.reserve(row.shader.diskPath.size()); + for (wchar_t wc : row.shader.diskPath) { + diskPathStr += static_cast(wc); + } std::string fullInfo = std::format("Type: {}\nClass: {}\nDescriptor: 0x{:X}\nKey: {}\nCache Path: {}", magic_enum::enum_name(row.shader.shaderType).data(), @@ -349,7 +407,6 @@ void AdvancedSettingsRenderer::RenderShaderDebugSection() diskPathStr); ImGui::SetClipboardText(fullInfo.c_str()); }; - auto getRowTooltip = [shaderCache](const ShaderRow& row) { std::string clickAction = (row.shader.key == shaderCache->blockedKey) ? "Left-click to unblock this shader" : "Left-click to block this shader"; @@ -417,39 +474,79 @@ void AdvancedSettingsRenderer::RenderShaderDebugSection() { Util::TableInputEventType::ContextMenu, onRowRightClick, "Copy Info", 1 } }; - // Define function to get row text color (highlight blocked shaders) - auto getRowTextColor = [shaderCache](const ShaderRow& row) -> ImVec4 { - if (row.shader.key == shaderCache->blockedKey) { - // Use theme error color for blocked shader text - return Util::Colors::GetError(); - } - return ImVec4(0, 0, 0, 0); // Default text color for normal rows - }; - - // Use the new interactive table + // Render the table with all configurations Util::ShowInteractiveTable( - "ActiveShadersTable", + "##ActiveShadersTable", columns, shaderRows, - 3, // Default sort column (Frame %) - false, // Default descending (for "hot" shaders) + sortColumn, + sortAscending, sorters, filterState, inputEvents, - getRowTooltip, - nullptr, // No background color - getRowTextColor); // Pass the new text color function + getRowTooltip); - // Update the filter text back to the char array + // Update static variables with modified filter state strncpy_s(filterText, filterState.filterText.c_str(), sizeof(filterText) - 1); + filterText[sizeof(filterText) - 1] = '\0'; searchColumn = filterState.searchColumn; } } +void AdvancedSettingsRenderer::RenderPBRSection(const std::function& drawTruePBRSettings) +{ + drawTruePBRSettings(); +} + +void AdvancedSettingsRenderer::RenderDisableAtBootSection(const std::function& drawDisableAtBootSettings) +{ + drawDisableAtBootSettings(); +} + void AdvancedSettingsRenderer::RenderDeveloperSection() { + auto shaderCache = globals::shaderCache; + + // File Watcher option (moved from Advanced/Logging) + bool useFileWatcher = shaderCache->UseFileWatcher(); + if (ImGui::Checkbox("Enable File Watcher", &useFileWatcher)) { + shaderCache->SetFileWatcher(useFileWatcher); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Automatically recompile shaders on file change. " + "Intended for developing."); + } + + // Debug addresses section (moved from Advanced/Logging) + if (ImGui::TreeNodeEx("Addresses")) { + auto Renderer = globals::game::renderer; + auto BSShaderAccumulator = *globals::game::currentAccumulator.get(); + auto RendererShadowState = globals::game::shadowState; + ADDRESS_NODE(Renderer) + ADDRESS_NODE(BSShaderAccumulator) + ADDRESS_NODE(RendererShadowState) + ImGui::TreePop(); + } + + // Statistics section (moved from Advanced/Logging) + if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text(std::format("Shader Compiler : {}", shaderCache->GetShaderStatsString()).c_str()); + ImGui::TreePop(); + } + + // Frame annotations toggle (moved from Advanced/Logging) + ImGui::Checkbox("Frame Annotations", &globals::state->frameAnnotations); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Enable detailed frame annotations for debugging render passes and draw calls."); + } + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + // Developer Mode Testing Section if (globals::state->IsDeveloperMode()) { FeatureIssues::Test::DrawDeveloperModeTestingUI(); } -} \ No newline at end of file +} diff --git a/src/Menu/AdvancedSettingsRenderer.h b/src/Menu/AdvancedSettingsRenderer.h index a6bd66a2bd..7a1cefca2c 100644 --- a/src/Menu/AdvancedSettingsRenderer.h +++ b/src/Menu/AdvancedSettingsRenderer.h @@ -1,6 +1,7 @@ #pragma once #include +#include // Forward declaration class Menu; @@ -13,8 +14,9 @@ class AdvancedSettingsRenderer const std::function& drawDisableAtBootSettings); private: - static void RenderAdvancedSection(); - static void RenderShaderReplacementSection(); + static void RenderLoggingSection(); static void RenderShaderDebugSection(); + static void RenderPBRSection(const std::function& drawTruePBRSettings); + static void RenderDisableAtBootSection(const std::function& drawDisableAtBootSettings); static void RenderDeveloperSection(); }; \ No newline at end of file diff --git a/src/Menu/FeatureListRenderer.cpp b/src/Menu/FeatureListRenderer.cpp index 41d110cda5..b3a5800db7 100644 --- a/src/Menu/FeatureListRenderer.cpp +++ b/src/Menu/FeatureListRenderer.cpp @@ -446,10 +446,18 @@ void FeatureListRenderer::DrawMenuVisitor::RenderFeatureSettingsTab(Feature* fea } if (!isDisabled && isLoaded) { - ImVec2 childSize = ImGui::GetWindowSize(); + // Position button in screen coordinates so it stays fixed in viewport when scrolling + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 windowSize = ImGui::GetWindowSize(); + float scrollbarWidth = ImGui::GetScrollMaxY() > 0 ? ImGui::GetStyle().ScrollbarSize : 0.0f; + float iconDimension = ImGui::GetFrameHeight() * 1.2f; ImVec2 iconSize = ImVec2(iconDimension, iconDimension); - ImGui::SetCursorPos(ImVec2(childSize.x - iconSize.x - 10.0f, childSize.y - iconSize.y - 10.0f)); + float padding = 10.0f; + ImVec2 buttonPos = ImVec2( + windowPos.x + windowSize.x - iconSize.x - padding - scrollbarWidth, + windowPos.y + windowSize.y - iconSize.y - padding); + ImGui::SetCursorScreenPos(buttonPos); auto& theme = globals::menu->GetTheme().Palette; ImVec4 iconColor = theme.Text; iconColor.w *= 0.7f; diff --git a/src/Menu/SettingsTabRenderer.h b/src/Menu/SettingsTabRenderer.h index 82fd6289bd..2247a0c6cb 100644 --- a/src/Menu/SettingsTabRenderer.h +++ b/src/Menu/SettingsTabRenderer.h @@ -15,6 +15,8 @@ class SettingsTabRenderer bool& settingsEffectsToggle; bool& settingSkipCompilationKey; bool& settingOverlayToggleKey; + bool& settingShaderBlockPrevKey; // Debug: shader block previous key + bool& settingShaderBlockNextKey; // Debug: shader block next key }; static void RenderGeneralSettings( diff --git a/src/TruePBR.cpp b/src/TruePBR.cpp index 2d1b65f084..86afc11300 100644 --- a/src/TruePBR.cpp +++ b/src/TruePBR.cpp @@ -7,6 +7,7 @@ #include "Hooks.h" #include "ShaderCache.h" #include "State.h" +#include "Util.h" NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( GlintParameters, @@ -107,14 +108,8 @@ void TruePBR::DrawSettings() { if (ImGui::CollapsingHeader("PBR", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) { if (ImGui::TreeNodeEx("Texture Set Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginCombo("Texture Set", selectedPbrTextureSetName.c_str())) { - for (auto& [textureSetName, textureSet] : pbrTextureSets) { - if (ImGui::Selectable(textureSetName.c_str(), textureSetName == selectedPbrTextureSetName)) { - selectedPbrTextureSetName = textureSetName; - selectedPbrTextureSet = &textureSet; - } - } - ImGui::EndCombo(); + if (Util::SearchableCombo("Texture Set", selectedPbrTextureSetName, pbrTextureSets)) { + selectedPbrTextureSet = &pbrTextureSets[selectedPbrTextureSetName]; } if (selectedPbrTextureSet != nullptr) { @@ -200,14 +195,8 @@ void TruePBR::DrawSettings() } if (ImGui::TreeNodeEx("Material Object Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginCombo("Material Object", selectedPbrMaterialObjectName.c_str())) { - for (auto& [materialObjectName, materialObject] : pbrMaterialObjects) { - if (ImGui::Selectable(materialObjectName.c_str(), materialObjectName == selectedPbrMaterialObjectName)) { - selectedPbrMaterialObjectName = materialObjectName; - selectedPbrMaterialObject = &materialObject; - } - } - ImGui::EndCombo(); + if (Util::SearchableCombo("Material Object", selectedPbrMaterialObjectName, pbrMaterialObjects)) { + selectedPbrMaterialObject = &pbrMaterialObjects[selectedPbrMaterialObjectName]; } if (selectedPbrMaterialObject != nullptr) { diff --git a/src/TruePBR.h b/src/TruePBR.h index d6963fe5ce..2ead82c6e4 100644 --- a/src/TruePBR.h +++ b/src/TruePBR.h @@ -1,5 +1,7 @@ #pragma once +#include + struct GlintParameters { bool enabled = false; diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 76b7e1b48b..5ddb981d30 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -773,6 +773,43 @@ namespace Util displayName.find(query) != std::string::npos; } + bool StringMatchesSearch(const std::string& text, const std::string& searchQuery) + { + if (searchQuery.empty()) + return true; + + std::string lowerText = text; + std::string lowerQuery = searchQuery; + + // Convert all to lowercase for case-insensitive search + std::transform(lowerText.begin(), lowerText.end(), lowerText.begin(), ::tolower); + std::transform(lowerQuery.begin(), lowerQuery.end(), lowerQuery.begin(), ::tolower); + + return lowerText.find(lowerQuery) != std::string::npos; + } + + void DrawSearchIcon(const ImVec2& position, float size, float alpha) + { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + ImVec2 center = ImVec2(position.x + size * 0.46f, position.y + size * 0.5f); + float radius = size * 0.3f; + + // Use themed text color with reduced alpha for search icon + auto& theme = globals::menu->GetTheme().Palette; + ImVec4 iconColor = theme.Text; + iconColor.w *= alpha; // Apply alpha multiplier for subtler appearance + ImU32 placeholderColor = ImGui::GetColorU32(iconColor); + + // Draw circle + drawList->AddCircle(center, radius, placeholderColor, 12, 2.2f); + + // Draw handle + ImVec2 handleStart = ImVec2(center.x + radius * 0.81f, center.y + radius * 0.81f); + ImVec2 handleEnd = ImVec2(handleStart.x + size * 0.29f, handleStart.y + size * 0.29f); + drawList->AddLine(handleStart, handleEnd, placeholderColor, 2.1f); + } + void DrawFeatureSearchBar(std::string& searchString, float availableWidth) { ImGui::PushID("FeatureSearchBar"); @@ -812,26 +849,9 @@ namespace Util searchString = buffer; } - // Draw a simple search icon (magnifying glass shape) + // Draw search icon using the reusable function ImVec2 iconPos = ImVec2(cursorPos.x + 8.0f, cursorPos.y + (frameHeight - iconSize) * 0.5f); - ImDrawList* drawList = ImGui::GetWindowDrawList(); - - ImVec2 center = ImVec2(iconPos.x + iconSize * 0.46f, iconPos.y + iconSize * 0.5f); - float radius = iconSize * 0.3f; - - // Use themed text color with reduced alpha for search icon - auto& theme = globals::menu->GetTheme().Palette; - ImVec4 iconColor = theme.Text; - iconColor.w *= 0.7f; // Reduce alpha for subtler appearance - ImU32 placeholderColor = ImGui::GetColorU32(iconColor); - - // Draw circle - drawList->AddCircle(center, radius, placeholderColor, 12, 2.2f); - - // Draw handle - ImVec2 handleStart = ImVec2(center.x + radius * 0.81f, center.y + radius * 0.81f); - ImVec2 handleEnd = ImVec2(handleStart.x + iconSize * 0.29f, handleStart.y + iconSize * 0.29f); - drawList->AddLine(handleStart, handleEnd, placeholderColor, 2.1f); + DrawSearchIcon(iconPos, iconSize, 0.7f); ImGui::PopStyleVar(2); ImGui::PopStyleColor(5); diff --git a/src/Utils/UI.h b/src/Utils/UI.h index f190a801db..cd267ec055 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -608,6 +608,22 @@ namespace Util */ bool FeatureMatchesSearch(Feature* feat, const std::string& searchQuery); + /** + * @brief Generic case-insensitive string matching for search functionality. + * @param text The text to search in + * @param searchQuery The search query string + * @return True if the text matches the search query (case-insensitive) + */ + bool StringMatchesSearch(const std::string& text, const std::string& searchQuery); + + /** + * @brief Draws a search icon (magnifying glass) at the specified position. + * @param position The screen position where the icon should be drawn + * @param size The size of the icon (default: 20.0f) + * @param alpha Alpha multiplier for the icon color (default: 0.7f for subtle appearance) + */ + void DrawSearchIcon(const ImVec2& position, float size = 20.0f, float alpha = 0.7f); + /** * @brief Draws the feature search bar with magnifying glass icon. * @param searchString Reference to the search string to modify @@ -743,6 +759,73 @@ namespace Util const char* KeyIdToString(uint32_t key); } + /** + * @brief Renders a searchable combo box with case-insensitive filtering + * + * Provides a reusable ImGui combo box with built-in search functionality. + * When opened, automatically focuses a search input that filters items as you type. + * The search is case-insensitive and clears automatically on selection or close. + * + * @tparam T The value type stored in the map + * @param label The label for the combo box + * @param selectedName Reference to the currently selected item's name (will be updated on selection) + * @param itemMap The map of items to display (key = item name, value = item data) + * @return true if a new item was selected, false otherwise + * + * @note Uses a static search buffer, so only one SearchableCombo should be open at a time + * + * @example + * @code + * std::unordered_map myItems; + * std::string selectedName; + * MyData* selectedItem = nullptr; + * + * if (Util::SearchableCombo("Choose Item", selectedName, myItems)) { + * selectedItem = &myItems[selectedName]; + * } + * @endcode + */ + template + bool SearchableCombo(const char* label, std::string& selectedName, std::unordered_map& itemMap) + { + bool valueChanged = false; + static std::unordered_map searchBuffers; + + std::string comboId = std::string(label); + auto& searchBuffer = searchBuffers[comboId]; + + if (ImGui::BeginCombo(label, selectedName.c_str())) { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(24.0f, ImGui::GetStyle().FramePadding.y)); + ImGui::InputText("##search", searchBuffer, IM_ARRAYSIZE(searchBuffer)); + ImGui::PopStyleVar(); + ImVec2 iconPos = ImVec2(ImGui::GetItemRectMin().x + 5.0f, ImGui::GetItemRectMin().y + (ImGui::GetItemRectSize().y - 16.0f) * 0.5f); + DrawSearchIcon(iconPos, 16.0f, 0.5f); + + ImGui::Separator(); + + // Filter and display items + for (auto& [itemName, item] : itemMap) { + // Simple case-insensitive search + if (searchBuffer[0] == '\0' || + std::search(itemName.begin(), itemName.end(), searchBuffer, searchBuffer + strlen(searchBuffer), + [](char a, char b) { return std::tolower(a) == std::tolower(b); }) != itemName.end()) { + if (ImGui::Selectable(itemName.c_str(), itemName == selectedName)) { + selectedName = itemName; + valueChanged = true; + searchBuffer[0] = '\0'; // Clear search on selection + } + } + } + + ImGui::EndCombo(); + } else { + // Reset search when combo is closed + searchBuffer[0] = '\0'; + } + + return valueChanged; + } + /** * @brief Renders a table cell with automatic text highlighting and optional tooltip/fallback. * Convenience function for table cell renderers that combines text rendering with highlighting, @@ -755,6 +838,7 @@ namespace Util * @param enableWrapping Whether to enable text wrapping for multi-line content (default: true) * @param textColor Optional text color override (default: use default text color) */ + inline void RenderTableCell(const std::string& text, const std::string& filterText, const std::string& tooltipText = "", const char* fallbackText = nullptr, ImVec4 highlightColor = ImVec4(1.0f, 1.0f, 0.0f, 1.0f), bool enableWrapping = true, diff --git a/vcpkg.json b/vcpkg.json index 70ed4f8da6..1b7f1b3dd2 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -21,7 +21,6 @@ "name": "imgui", "features": ["dx11-binding", "win32-binding", "docking-experimental"] }, - "intel-xess", "magic-enum", "detours", "nlohmann-json",