diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f9944003..b90ad2c1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ include(XSEPlugin) # ####################################################################################################################### # # Find dependencies # ####################################################################################################################### +find_path(BSHOSHANY_THREAD_POOL_INCLUDE_DIRS "BS_thread_pool.hpp") find_package(magic_enum CONFIG REQUIRED) find_package(xbyak CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) @@ -37,6 +38,7 @@ find_package(pystring CONFIG REQUIRED) target_include_directories( ${PROJECT_NAME} PRIVATE + ${BSHOSHANY_THREAD_POOL_INCLUDE_DIRS} ${CLIB_UTIL_INCLUDE_DIRS} ) diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index 7ca9fa6be..e9e1320cf 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -677,7 +677,7 @@ void LightLimitFix::UpdateLights() if (settings.EnableFirstPersonShadows) { if (auto playerCamera = RE::PlayerCamera::GetSingleton()) { - if (playerCamera->IsInFirstPerson()) { + if (playerCamera->IsInFirstPerson() || REL::Module::IsVR()) { if (auto player = RE::PlayerCharacter::GetSingleton()) { firstPersonLight = player->GetInfoRuntimeData().firstPersonLight.get(); thirdPersonLight = player->GetInfoRuntimeData().thirdPersonLight.get(); diff --git a/src/Menu.cpp b/src/Menu.cpp index 9882781f1..e8da18cd0 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -14,6 +14,7 @@ #include "Features/WaterBlending.h" #define SETTING_MENU_TOGGLEKEY "Toggle Key" +#define SETTING_MENU_SKIPKEY "Skip Compilation Key" #define SETTING_MENU_FONTSCALE "Font Scale" void SetupImGuiStyle() @@ -77,6 +78,9 @@ void Menu::Load(json& o_json) if (o_json[SETTING_MENU_TOGGLEKEY].is_number_unsigned()) { toggleKey = o_json[SETTING_MENU_TOGGLEKEY]; } + if (o_json[SETTING_MENU_SKIPKEY].is_number_unsigned()) { + skipCompilationKey = o_json[SETTING_MENU_SKIPKEY]; + } if (o_json[SETTING_MENU_FONTSCALE].is_number_float()) { fontScale = o_json[SETTING_MENU_FONTSCALE]; } @@ -86,6 +90,7 @@ void Menu::Save(json& o_json) { json menu; menu[SETTING_MENU_TOGGLEKEY] = toggleKey; + menu[SETTING_MENU_SKIPKEY] = skipCompilationKey; menu[SETTING_MENU_FONTSCALE] = fontScale; o_json["Menu"] = menu; @@ -211,11 +216,18 @@ RE::BSEventNotifyControl Menu::ProcessEvent(RE::InputEvent* const* a_event, RE:: switch (button->device.get()) { case RE::INPUT_DEVICE::kKeyboard: if (!button->IsPressed()) { + logger::trace("Detected key code {} ({})", KeyIdToString(key), key); if (settingToggleKey) { toggleKey = key; settingToggleKey = false; + } else if (settingSkipCompilationKey) { + skipCompilationKey = key; + settingSkipCompilationKey = false; } else if (key == toggleKey) { IsEnabled = !IsEnabled; + } else if (key == skipCompilationKey) { + auto& shaderCache = SIE::ShaderCache::Instance(); + shaderCache.backgroundCompilation = true; } } @@ -412,10 +424,25 @@ void Menu::DrawSettings() ImGui::AlignTextToFramePadding(); ImGui::SameLine(); - if (ImGui::Button("Change")) { + if (ImGui::Button("Change##toggle")) { settingToggleKey = true; } } + if (settingSkipCompilationKey) { + ImGui::Text("Press any key to set as Skip Compilation Key..."); + } else { + ImGui::AlignTextToFramePadding(); + ImGui::Text("Skip Compilation Key:"); + ImGui::SameLine(); + ImGui::AlignTextToFramePadding(); + ImGui::TextColored(ImVec4(1, 1, 0, 1), "%s", KeyIdToString(skipCompilationKey)); + + ImGui::AlignTextToFramePadding(); + ImGui::SameLine(); + if (ImGui::Button("Change##skip")) { + settingSkipCompilationKey = true; + } + } if (ImGui::SliderFloat("Font Scale", &fontScale, -2.f, 2.f, "%.2f")) { float trueScale = exp2(fontScale); @@ -479,9 +506,19 @@ void Menu::DrawSettings() ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); ImGui::Text( - "Number of threads to compile shaders with. " - "The more threads the faster compilation will finish but may make the system unresponsive. " - "This should only be changed between restarts. "); + "Number of threads to use to compile shaders. " + "The more threads the faster compilation will finish but may make the system unresponsive. "); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::SliderInt("Background Compiler Threads", &shaderCache.backgroundCompilationThreadCount, 1, static_cast(std::thread::hardware_concurrency())); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + 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. "); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } @@ -575,7 +612,9 @@ void Menu::DrawOverlay() auto failed = shaderCache.GetFailedTasks(); auto hide = shaderCache.IsHideErrors(); - auto progressTitle = fmt::format("Compiling Shaders: {}", shaderCache.GetShaderStatsString(!state->IsDeveloperMode()).c_str()); + auto progressTitle = fmt::format("{}Compiling Shaders: {}", + shaderCache.backgroundCompilation ? "Background " : "", + shaderCache.GetShaderStatsString(!state->IsDeveloperMode()).c_str()); auto percent = (float)compiledShaders / (float)totalShaders; auto progressOverlay = fmt::format("{}/{} ({:2.1f}%)", compiledShaders, totalShaders, 100 * percent); if (shaderCache.IsCompiling()) { @@ -587,6 +626,13 @@ void Menu::DrawOverlay() } ImGui::TextUnformatted(progressTitle.c_str()); ImGui::ProgressBar(percent, ImVec2(0.0f, 0.0f), progressOverlay.c_str()); + if (!shaderCache.backgroundCompilation && shaderCache.menuLoaded) { + auto skipShadersText = fmt::format( + "Press {} to proceed without completing shader compilation. " + "WARNING: Uncompiled shaders will have visual errors or cause stuttering when loading.", + KeyIdToString(skipCompilationKey)); + ImGui::TextUnformatted(skipShadersText.c_str()); + } ImGui::End(); } else if (failed && !hide) { diff --git a/src/Menu.h b/src/Menu.h index 15a353933..053329e39 100644 --- a/src/Menu.h +++ b/src/Menu.h @@ -27,8 +27,9 @@ class Menu : public RE::BSTEventSink private: uint32_t toggleKey = VK_END; + uint32_t skipCompilationKey = VK_ESCAPE; bool settingToggleKey = false; - + bool settingSkipCompilationKey = false; float fontScale = 0.f; // exponential Menu() {} diff --git a/src/ShaderCache.cpp b/src/ShaderCache.cpp index be0535253..ab32ff528 100644 --- a/src/ShaderCache.cpp +++ b/src/ShaderCache.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "Features/ExtendedMaterials.h" @@ -1501,7 +1502,7 @@ namespace SIE void ShaderCache::DeleteDiskCache() { - std::lock_guard lock(compilationSet.compilationMutex); + std::scoped_lock lock{ compilationSet.compilationMutex }; try { std::filesystem::remove_all(L"Data/ShaderCache"); logger::info("Deleted disk cache"); @@ -1546,10 +1547,8 @@ namespace SIE ShaderCache::ShaderCache() { - logger::debug("ShaderCache initialized with {} compiler threads", compilationThreadCount); - for (size_t threadIndex = 0; threadIndex < compilationThreadCount; ++threadIndex) { - compilationThreads.push_back(std::jthread(&ShaderCache::ProcessCompilationSet, this)); - } + logger::debug("ShaderCache initialized with {} compiler threads", (int)compilationThreadCount); + compilationPool.push_task(&ShaderCache::ManageCompilationSet, this, ssource.get_token()); } RE::BSGraphics::VertexShader* ShaderCache::MakeAndAddVertexShader(const RE::BSShader& shader, @@ -1642,16 +1641,24 @@ namespace SIE hideError = !hideError; } - void ShaderCache::ProcessCompilationSet() + void ShaderCache::ManageCompilationSet(std::stop_token stoken) { - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); - while (true) { - const auto& task = compilationSet.WaitTake(); - task.Perform(); - compilationSet.Complete(task); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); + while (!stoken.stop_requested()) { + const auto& task = compilationSet.WaitTake(stoken); + if (!task.has_value()) + break; // exit because thread told to end + compilationPool.push_task(&ShaderCache::ProcessCompilationSet, this, stoken, task.value()); } } + void ShaderCache::ProcessCompilationSet(std::stop_token stoken, SIE::ShaderCompilationTask task) + { + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); + task.Perform(); + compilationSet.Complete(task); + } + ShaderCompilationTask::ShaderCompilationTask(ShaderClass aShaderClass, const RE::BSShader& aShader, uint32_t aDescriptor) : @@ -1684,15 +1691,24 @@ namespace SIE return GetId() == other.GetId(); } - ShaderCompilationTask CompilationSet::WaitTake() + std::optional CompilationSet::WaitTake(std::stop_token stoken) { std::unique_lock lock(compilationMutex); - conditionVariable.wait(lock, [this]() { return !availableTasks.empty(); }); + auto& shaderCache = ShaderCache::Instance(); + if (!conditionVariable.wait( + lock, stoken, + [this, &shaderCache]() { return !availableTasks.empty() && + // check against all tasks in queue to trickle the work. It cannot be the active tasks count because the thread pool itself is maximum. + (int)shaderCache.compilationPool.get_tasks_total() <= + (!shaderCache.backgroundCompilation ? shaderCache.compilationThreadCount : shaderCache.backgroundCompilationThreadCount); })) { + /*Woke up because of a stop request. */ + return std::nullopt; + } if (!ShaderCache::Instance().IsCompiling()) { // we just got woken up because there's a task, start clock lastCalculation = lastReset = high_resolution_clock::now(); } auto node = availableTasks.extract(availableTasks.begin()); - auto task = node.value(); + auto& task = node.value(); tasksInProgress.insert(std::move(node)); return task; } @@ -1727,14 +1743,15 @@ namespace SIE auto now = high_resolution_clock::now(); totalMs += duration_cast(now - lastCalculation).count(); lastCalculation = now; - std::unique_lock lock(compilationMutex); + std::scoped_lock lock(compilationMutex); processedTasks.insert(task); tasksInProgress.erase(task); + conditionVariable.notify_one(); } void CompilationSet::Clear() { - std::lock_guard lock(compilationMutex); + std::scoped_lock lock(compilationMutex); availableTasks.clear(); tasksInProgress.clear(); processedTasks.clear(); @@ -1763,8 +1780,8 @@ namespace SIE double CompilationSet::GetEta() { auto rate = completedTasks / totalMs; - auto remaining = (int)totalTasks - completedTasks - failedTasks; - return remaining / rate; + auto remaining = totalTasks - completedTasks - failedTasks; + return std::max(remaining / rate, 0.0); } std::string CompilationSet::GetStatsString(bool a_timeOnly) diff --git a/src/ShaderCache.h b/src/ShaderCache.h index 1e09d08b4..a85165851 100644 --- a/src/ShaderCache.h +++ b/src/ShaderCache.h @@ -2,6 +2,7 @@ #include +#include "BS_thread_pool.hpp" #include #include #include @@ -60,7 +61,7 @@ namespace SIE class CompilationSet { public: - ShaderCompilationTask WaitTake(); + std::optional WaitTake(std::stop_token stoken); void Add(const ShaderCompilationTask& task); void Complete(const ShaderCompilationTask& task); void Clear(); @@ -77,7 +78,7 @@ namespace SIE std::unordered_set availableTasks; std::unordered_set tasksInProgress; std::unordered_set processedTasks; // completed or failed - std::condition_variable conditionVariable; + std::condition_variable_any conditionVariable; std::chrono::steady_clock::time_point lastReset = high_resolution_clock::now(); std::chrono::steady_clock::time_point lastCalculation = high_resolution_clock::now(); double totalMs = (double)duration_cast(lastReset - lastReset).count(); @@ -124,7 +125,6 @@ namespace SIE void DeleteDiskCache(); void ValidateDiskCache(); void WriteDiskCacheInfo(); - void Clear(); bool AddCompletedShader(ShaderClass shaderClass, const RE::BSShader& shader, uint32_t descriptor, ID3DBlob* a_blob); @@ -152,10 +152,15 @@ namespace SIE bool IsHideErrors(); int32_t compilationThreadCount = std::max(static_cast(std::thread::hardware_concurrency()) - 1, 1); + int32_t backgroundCompilationThreadCount = std::max(static_cast(std::thread::hardware_concurrency()) / 2, 1); + BS::thread_pool compilationPool{}; + bool backgroundCompilation = false; + bool menuLoaded = false; private: ShaderCache(); - void ProcessCompilationSet(); + void ManageCompilationSet(std::stop_token stoken); + void ProcessCompilationSet(std::stop_token stoken, SIE::ShaderCompilationTask task); ~ShaderCache(); @@ -172,7 +177,7 @@ namespace SIE bool isDump = false; bool hideError = false; - eastl::vector compilationThreads; + std::stop_source ssource; std::mutex vertexShadersMutex; std::mutex pixelShadersMutex; CompilationSet compilationSet; diff --git a/src/State.cpp b/src/State.cpp index 142f39b3a..f81983a5b 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -92,6 +92,8 @@ void State::Load() SetDefines(advanced["Shader Defines"]); if (advanced["Compiler Threads"].is_number_integer()) shaderCache.compilationThreadCount = std::clamp(advanced["Compiler Threads"].get(), 1, static_cast(std::thread::hardware_concurrency())); + if (advanced["Background Compiler Threads"].is_number_integer()) + shaderCache.backgroundCompilationThreadCount = std::clamp(advanced["Background Compiler Threads"].get(), 1, static_cast(std::thread::hardware_concurrency())); } if (settings["General"].is_object()) { @@ -145,6 +147,7 @@ void State::Save() advanced["Log Level"] = logLevel; advanced["Shader Defines"] = shaderDefinesString; advanced["Compiler Threads"] = shaderCache.compilationThreadCount; + advanced["Background Compiler Threads"] = shaderCache.backgroundCompilationThreadCount; settings["Advanced"] = advanced; json general; diff --git a/src/XSEPlugin.cpp b/src/XSEPlugin.cpp index 6f4da8b28..53e5fe3dd 100644 --- a/src/XSEPlugin.cpp +++ b/src/XSEPlugin.cpp @@ -115,8 +115,8 @@ void MessageHandler(SKSE::MessagingInterface::Message* message) RE::BSInputDeviceManager::GetSingleton()->AddEventSink(Menu::GetSingleton()); auto& shaderCache = SIE::ShaderCache::Instance(); - - while (shaderCache.IsCompiling()) { + shaderCache.menuLoaded = true; + while (shaderCache.IsCompiling() && !shaderCache.backgroundCompilation) { std::this_thread::sleep_for(100ms); } diff --git a/vcpkg.json b/vcpkg.json index 8870f8cd3..fd89635f8 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -4,6 +4,7 @@ "description": "", "license": "MIT", "dependencies": [ + "bshoshany-thread-pool", "fmt", "directxtk", "rapidcsv",