diff --git a/Build Release.bat b/BuildRelease.bat similarity index 100% rename from Build Release.bat rename to BuildRelease.bat diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f9944003c..a9d015ad37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,11 +77,10 @@ endif() # ####################################################################################################################### # # Automatic deployment # ####################################################################################################################### - file(GLOB FEATURE_PATHS LIST_DIRECTORIES true ${CMAKE_SOURCE_DIR}/features/*) # Automatic deployment to CommunityShaders output directory. -if (AUTO_PLUGIN_DEPLOYMENT) +if(AUTO_PLUGIN_DEPLOYMENT) foreach(DEPLOY_TARGET $ENV{CommunityShadersOutputDir}) message("Copying package folder with dll/pdb with all features to ${DEPLOY_TARGET}") add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD @@ -89,13 +88,16 @@ if (AUTO_PLUGIN_DEPLOYMENT) COMMAND ${CMAKE_COMMAND} -E copy $ "${DEPLOY_TARGET}/SKSE/Plugins/" COMMAND ${CMAKE_COMMAND} -E copy $ "${DEPLOY_TARGET}/SKSE/Plugins/" ) + foreach(FEATURE_PATH ${FEATURE_PATHS}) + message("Copying ${FEATURE_PATH} to ${DEPLOY_TARGET}") add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${FEATURE_PATH} "${DEPLOY_TARGET}" ) endforeach() endforeach() - if (NOT DEFINED ENV{CommunityShadersOutputDir}) + + if(NOT DEFINED ENV{CommunityShadersOutputDir}) message("When using AUTO_PLUGIN_DEPLOYMENT option, you need to set environment variable 'CommunityShadersOutputDir'") endif() endif() diff --git a/README.md b/README.md index 6db1f52cef..a4171d417a 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,17 @@ SKSE core plugin for community-driven advanced graphics modifications. ## Requirements -- [CMake](https://cmake.org/) - - Add this to your `PATH` -- [PowerShell](https://github.com/PowerShell/PowerShell/releases/latest) -- [Vcpkg](https://github.com/microsoft/vcpkg) - - Add the environment variable `VCPKG_ROOT` with the value as the path to the folder containing vcpkg +- Any terminal of your choice (e.g., PowerShell) - [Visual Studio Community 2022](https://visualstudio.microsoft.com/) - Desktop development with C++ -- [CommonLibSSENG](https://github.com/alandtse/commonlibvr/tree/ng) - - Add this as as an environment variable `CommonLibSSEPath` +- [CMake](https://cmake.org/) + - Edit the `PATH` environment variable and add the cmake.exe install path as a new value + - Instructions for finding and editing the `PATH` environment variable can be found [here](https://www.java.com/en/download/help/path.html) +- [Git](https://git-scm.com/downloads) + - Edit the `PATH` environment variable and add the Git.exe install path as a new value +- [Vcpkg](https://github.com/microsoft/vcpkg) + - Install vcpkg using the directions in vcpkg's [Quick Start Guide](https://github.com/microsoft/vcpkg#quick-start-windows) + - After install, add a new environment variable named `VCPKG_ROOT` with the value as the path to the folder containing vcpkg ## User Requirements @@ -29,17 +31,13 @@ SKSE core plugin for community-driven advanced graphics modifications. - Run `cmake` - Close the cmd window -## Building +## Clone and Build +Open terminal (e.g., PowerShell) and run the following commands: ``` -git clone https://github.com/doodlum/skyrim-community-shaders.git +git clone https://github.com/doodlum/skyrim-community-shaders.git --recursive cd skyrim-community-shaders -# pull commonlib /extern to override the path settings -git submodule update --init --recursive -#configure cmake -cmake -S . --preset=ALL -#build dll -cmake --build build --config Release +.\BuildRelease.bat ``` ## License diff --git a/features/Grass Collision/Shaders/Features/GrassCollision.ini b/features/Grass Collision/Shaders/Features/GrassCollision.ini index 312d7ff985..7c4d5a2a34 100644 --- a/features/Grass Collision/Shaders/Features/GrassCollision.ini +++ b/features/Grass Collision/Shaders/Features/GrassCollision.ini @@ -1,2 +1,2 @@ [Info] -Version = 1-1-0 \ No newline at end of file +Version = 1-2-0 \ No newline at end of file diff --git a/features/Grass Collision/Shaders/GrassCollision/GrassCollision.hlsli b/features/Grass Collision/Shaders/GrassCollision/GrassCollision.hlsli index 8d0e9f44da..648491a0ce 100644 --- a/features/Grass Collision/Shaders/GrassCollision/GrassCollision.hlsli +++ b/features/Grass Collision/Shaders/GrassCollision/GrassCollision.hlsli @@ -6,6 +6,7 @@ cbuffer GrassCollisionPerFrame : register(b5) bool EnableGrassCollision; float RadiusMultiplier; float DisplacementMultiplier; + float maxDistance; } struct StructuredCollision @@ -21,10 +22,10 @@ float3 GetDisplacedPosition(float3 position, float alpha, uint eyeIndex = 0) float3 worldPosition = mul(World[eyeIndex], float4(position, 1)).xyz; float3 displacement = 0; - // Player bound culling + // Player bound culling and distance from player culling { float dist = distance(boundCentre[eyeIndex].xyz, worldPosition); - if (dist > boundRadius) { + if ((dist > boundRadius) && (dist > maxDistance)) { return 0; } } diff --git a/features/Subsurface Scattering/Features/SubsurfaceScattering.ini b/features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini similarity index 100% rename from features/Subsurface Scattering/Features/SubsurfaceScattering.ini rename to features/Subsurface Scattering/Shaders/Features/SubsurfaceScattering.ini diff --git a/features/Subsurface Scattering/SubsurfaceScattering.hlsli b/features/Subsurface Scattering/Shaders/SubsurfaceScattering.hlsli similarity index 100% rename from features/Subsurface Scattering/SubsurfaceScattering.hlsli rename to features/Subsurface Scattering/Shaders/SubsurfaceScattering.hlsli diff --git a/features/Subsurface Scattering/SubsurfaceScattering/SeparableSSS.hlsli b/features/Subsurface Scattering/Shaders/SubsurfaceScattering/SeparableSSS.hlsli similarity index 100% rename from features/Subsurface Scattering/SubsurfaceScattering/SeparableSSS.hlsli rename to features/Subsurface Scattering/Shaders/SubsurfaceScattering/SeparableSSS.hlsli diff --git a/features/Subsurface Scattering/SubsurfaceScattering/SeparableSSSCS.hlsl b/features/Subsurface Scattering/Shaders/SubsurfaceScattering/SeparableSSSCS.hlsl similarity index 100% rename from features/Subsurface Scattering/SubsurfaceScattering/SeparableSSSCS.hlsl rename to features/Subsurface Scattering/Shaders/SubsurfaceScattering/SeparableSSSCS.hlsl diff --git a/src/Feature.cpp b/src/Feature.cpp index 74fe17086d..f685ee1682 100644 --- a/src/Feature.cpp +++ b/src/Feature.cpp @@ -86,7 +86,8 @@ const std::vector& Feature::GetFeatureList() GrassLighting::GetSingleton(), GrassCollision::GetSingleton(), ExtendedMaterials::GetSingleton(), - LightLimitFix::GetSingleton() + LightLimitFix::GetSingleton(), + SubsurfaceScattering::GetSingleton() }; return REL::Module::IsVR() ? featuresVR : features; diff --git a/src/Features/DistantTreeLighting.cpp b/src/Features/DistantTreeLighting.cpp index 068c5abb69..35201f304c 100644 --- a/src/Features/DistantTreeLighting.cpp +++ b/src/Features/DistantTreeLighting.cpp @@ -13,35 +13,64 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( void DistantTreeLighting::DrawSettings() { if (ImGui::TreeNodeEx("Complex Tree LOD", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped( - "Enables advanced lighting simulation on tree LOD.\n" - "Requires DynDOLOD.\n" - "See https://dyndolod.info/ for more information."); ImGui::Checkbox("Enable Complex Tree LOD", (bool*)&settings.EnableComplexTreeLOD); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables advanced lighting simulation on tree LOD.\n"); + ImGui::Text("Requires DynDOLOD.\n"); + ImGui::Text("See https://dyndolod.info/ for more information."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Lights", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped("Fix for trees not being affected by sunlight scale."); ImGui::Checkbox("Enable Directional Light Fix", (bool*)&settings.EnableDirLightFix); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Fix for trees not being affected by sunlight scale."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Effects", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped( - "Soft lighting controls how evenly lit an object is.\n" - "Back lighting illuminates the back face of an object.\n" - "Combined to model the transport of light through the surface."); - ImGui::SliderFloat("Subsurface Scattering Amount", &settings.SubsurfaceScatteringAmount, 0.0f, 1.0f); + ImGui::SliderFloat("SSS Amount", &settings.SubsurfaceScatteringAmount, 0.0f, 1.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Subsurface Scattering (SSS) amount\n"); + ImGui::Text("Soft lighting controls how evenly lit an object is.\n"); + ImGui::Text("Back lighting illuminates the back face of an object.\n"); + ImGui::Text("Combined to model the transport of light through the surface."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Vanilla", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped("Darkens lighting relative fog strength."); ImGui::SliderFloat("Fog Dimmer Amount", &settings.FogDimmerAmount, 0.0f, 1.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Darkens lighting relative fog strength."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::TreePop(); } diff --git a/src/Features/ExtendedMaterials.cpp b/src/Features/ExtendedMaterials.cpp index faed6bf608..ccc9c608b6 100644 --- a/src/Features/ExtendedMaterials.cpp +++ b/src/Features/ExtendedMaterials.cpp @@ -14,59 +14,147 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( ShadowsStartFade, ShadowsEndFade) +void ExtendedMaterials::DataLoaded() +{ + if (&settings.EnableTerrain) { + if (auto bLandSpecular = RE::INISettingCollection::GetSingleton()->GetSetting("bLandSpecular:Landscape"); bLandSpecular) { + if (!bLandSpecular->data.b) { + logger::info("[CPM] Changing bLandSpecular from {} to {} to support Terrain Parallax", bLandSpecular->data.b, true); + bLandSpecular->data.b = true; + } + } + } +} + void ExtendedMaterials::DrawSettings() { if (ImGui::TreeNodeEx("Complex Material", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped( - "Enables support for the Complex Material specification which makes use of the environment mask.\n" - "This includes parallax, as well as more realistic metals and specular reflections.\n" - "May lead to some warped textures on modded content which have an invalid alpha channel in their environment mask."); ImGui::Checkbox("Enable Complex Material", (bool*)&settings.EnableComplexMaterial); - + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables support for the Complex Material specification which makes use of the environment mask.\n"); + ImGui::Text("This includes parallax, as well as more realistic metals and specular reflections.\n"); + ImGui::Text("May lead to some warped textures on modded content which have an invalid alpha channel in their environment mask."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Contact Refinement Parallax Mapping", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped("Enables parallax on standard meshes made for parallax."); ImGui::Checkbox("Enable Parallax", (bool*)&settings.EnableParallax); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables parallax on standard meshes made for parallax."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + if (ImGui::Checkbox("Enable Terrain", (bool*)&settings.EnableTerrain)) { + if (settings.EnableTerrain) { + DataLoaded(); + } + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables terrain parallax using the alpha channel of each landscape texture.\n"); + ImGui::Text("Therefore, all landscape textures must support parallax for this effect to work properly."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped( - "Enables terrain parallax using the alpha channel of each landscape texture.\n" - "Therefore, all landscape textures must support parallax for this effect to work properly."); - ImGui::Checkbox("Enable Terrain", (bool*)&settings.EnableTerrain); - - ImGui::TextWrapped( - "Doubles the sample count and approximates the intersection point using Parallax Occlusion Mapping.\n" - "Significantly improves the quality and removes aliasing.\n" - "TAA or the Skyrim Upscaler is recommended when using this option due to CRPM artifacts."); ImGui::Checkbox("Enable High Quality", (bool*)&settings.EnableHighQuality); - - ImGui::TextWrapped("The furthest distance from the camera which uses parallax."); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Doubles the sample count and approximates the intersection point using Parallax Occlusion Mapping.\n"); + ImGui::Text("Significantly improves the quality and removes aliasing.\n"); + ImGui::Text("TAA or the Skyrim Upscaler is recommended when using this option due to CRPM artifacts."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Spacing(); + ImGui::Spacing(); ImGui::SliderInt("Max Distance", (int*)&settings.MaxDistance, 0, 4096); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("The furthest distance from the camera which uses parallax."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("The percentage of the max distance which uses CRPM."); ImGui::SliderFloat("CRPM Range", &settings.CRPMRange, 0.0f, 1.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("The percentage of the max distance which uses Contact Refinement Parallax Mapping (CRPM)."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped( - "The range that parallax blends from POM to bump mapping, and bump mapping to nothing.\n" - "This value should be set as low as possible due to the performance impact of blending POM and relief mapping."); ImGui::SliderFloat("Blend Range", &settings.BlendRange, 0.0f, settings.CRPMRange); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("The range that parallax blends from Parallax Occlusion Mapping (POM) to bump mapping, and bump mapping to nothing.\n"); + ImGui::Text("This value should be set as low as possible due to the performance impact of blending POM and relief mapping."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("The range between the highest and lowest point a surface can be offset by."); ImGui::SliderFloat("Height", &settings.Height, 0, 0.2f); - + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("The range between the highest and lowest point a surface can be offset by."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Approximate Soft Shadows", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped( - "Enables cheap soft shadows when using parallax.\n" - "This applies to all directional and point lights."); ImGui::Checkbox("Enable Shadows", (bool*)&settings.EnableShadows); - - ImGui::TextWrapped("Modifying the start and end fade can improve performance and hide obvious texture tiling."); - ImGui::SliderInt("Shadows Start Fade", (int*)&settings.ShadowsStartFade, 0, settings.ShadowsEndFade); - ImGui::SliderInt("Shadows End Fade", (int*)&settings.ShadowsEndFade, settings.ShadowsStartFade, 4096); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables cheap soft shadows when using parallax.\n"); + ImGui::Text("This applies to all directional and point lights."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::TextWrapped("Modifying the shadow start and end fade can improve performance and hide obvious texture tiling."); + ImGui::SliderInt("Start Fade", (int*)&settings.ShadowsStartFade, 0, settings.ShadowsEndFade); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Distance shadows start to fade."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::SliderInt("End Fade", (int*)&settings.ShadowsEndFade, settings.ShadowsStartFade, 4096); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Distance shadows finish fading."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::TreePop(); } diff --git a/src/Features/ExtendedMaterials.h b/src/Features/ExtendedMaterials.h index 14f1836c7b..0fb448d8c0 100644 --- a/src/Features/ExtendedMaterials.h +++ b/src/Features/ExtendedMaterials.h @@ -47,6 +47,8 @@ struct ExtendedMaterials : Feature virtual void SetupResources(); virtual inline void Reset() {} + void DataLoaded(); + virtual void DrawSettings(); void ModifyLighting(const RE::BSShader* shader, const uint32_t descriptor); diff --git a/src/Features/GrassCollision.cpp b/src/Features/GrassCollision.cpp index 487a58e1e9..7bdada71a9 100644 --- a/src/Features/GrassCollision.cpp +++ b/src/Features/GrassCollision.cpp @@ -17,17 +17,62 @@ enum class GrassShaderTechniques void GrassCollision::DrawSettings() { if (ImGui::TreeNodeEx("Grass Collision", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped("Allows player collision to modify grass position."); - ImGui::Checkbox("Enable Grass Collision", (bool*)&settings.EnableGrassCollision); - ImGui::TextWrapped("Distance from collision centres to apply collision"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Allows player collision to modify grass position."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Spacing(); ImGui::SliderFloat("Radius Multiplier", &settings.RadiusMultiplier, 0.0f, 8.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Distance from collision centres to apply collision."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::SliderFloat("Max Distance from Player", &settings.maxDistance, 0.0f, 1500.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Distance from player to apply collision (NPCs). 0 to disable NPC collisions."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("Strength of each collision on grass position."); ImGui::SliderFloat("Displacement Multiplier", &settings.DisplacementMultiplier, 0.0f, 32.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Strength of each collision on grass position."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + if (ImGui::SliderInt("Calculation Frame Interval", (int*)&settings.frameInterval, 0, 30)) { + if (settings.frameInterval) // increment so mod math works (e.g., skip 1 frame means frame % 2). + settings.frameInterval++; + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("How many frames to skip before calculating positions again. 0 means calculate every frame (most smooth/costly)."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::TreePop(); } + if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text(std::format("Active/Total Actors : {}/{}", activeActorCount, totalActorCount).c_str()); + ImGui::Text(std::format("Total Collisions : {}", currentCollisionCount).c_str()); + ImGui::TreePop(); + } } static bool GetShapeBound(RE::NiAVObject* a_node, RE::NiPoint3& centerPos, float& radius) @@ -112,38 +157,67 @@ void GrassCollision::UpdateCollisions() { auto state = RE::BSGraphics::RendererShadowState::GetSingleton(); - std::uint32_t currentCollisionCount = 0; - - std::vector collisionsData{}; - - if (auto player = RE::PlayerCharacter::GetSingleton()) { - if (auto root = player->Get3D(false)) { - auto position = player->GetPosition(); - RE::BSVisit::TraverseScenegraphCollision(root, [&](RE::bhkNiCollisionObject* a_object) -> RE::BSVisit::BSVisitControl { - RE::NiPoint3 centerPos; - float radius; - if (GetShapeBound(a_object, centerPos, radius)) { - radius *= settings.RadiusMultiplier; - CollisionSData data{}; - RE::NiPoint3 eyePosition{}; - for (int eyeIndex = 0; eyeIndex < eyeCount; eyeIndex++) { - if (!REL::Module::IsVR()) { - eyePosition = state->GetRuntimeData().posAdjust.getEye(); - } else - eyePosition = state->GetVRRuntimeData().posAdjust.getEye(eyeIndex); - data.centre[eyeIndex].x = centerPos.x - eyePosition.x; - data.centre[eyeIndex].y = centerPos.y - eyePosition.y; - data.centre[eyeIndex].z = centerPos.z - eyePosition.z; + auto frameCount = RE::BSGraphics::State::GetSingleton()->uiFrameCount; + + if (settings.frameInterval == 0 || frameCount % settings.frameInterval == 0) { // only calculate actor positions on some frames + currentCollisionCount = 0; + totalActorCount = 0; + activeActorCount = 0; + actorList.clear(); + collisionsData.clear(); + // actor query code from po3 under MIT + // https://github.com/powerof3/PapyrusExtenderSSE/blob/7a73b47bc87331bec4e16f5f42f2dbc98b66c3a7/include/Papyrus/Functions/Faction.h#L24C7-L46 + if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists && settings.maxDistance > 0.0f) { + std::vector*> actors; + actors.push_back(&processLists->highActorHandles); // high actors are in combat or doing something interesting + for (auto array : actors) { + for (auto& actorHandle : *array) { + auto actorPtr = actorHandle.get(); + if (actorPtr && actorPtr.get() && actorPtr.get()->Is3DLoaded()) { + actorList.push_back(actorPtr.get()); + totalActorCount++; } - data.radius = radius; - currentCollisionCount++; - collisionsData.push_back(data); } - return RE::BSVisit::BSVisitControl::kContinue; - }); + } + } + + RE::NiPoint3 playerPosition; + if (auto player = RE::PlayerCharacter::GetSingleton()) { + actorList.push_back(player); + playerPosition = player->GetPosition(); } - } + for (const auto actor : actorList) { + if (auto root = actor->Get3D(false)) { + if (playerPosition.GetDistance(actor->GetPosition()) > settings.maxDistance) { // npc too far so skip + continue; + } + activeActorCount++; + RE::BSVisit::TraverseScenegraphCollision(root, [&](RE::bhkNiCollisionObject* a_object) -> RE::BSVisit::BSVisitControl { + RE::NiPoint3 centerPos; + float radius; + if (GetShapeBound(a_object, centerPos, radius)) { + radius *= settings.RadiusMultiplier; + CollisionSData data{}; + RE::NiPoint3 eyePosition{}; + for (int eyeIndex = 0; eyeIndex < eyeCount; eyeIndex++) { + if (!REL::Module::IsVR()) { + eyePosition = state->GetRuntimeData().posAdjust.getEye(); + } else + eyePosition = state->GetVRRuntimeData().posAdjust.getEye(eyeIndex); + data.centre[eyeIndex].x = centerPos.x - eyePosition.x; + data.centre[eyeIndex].y = centerPos.y - eyePosition.y; + data.centre[eyeIndex].z = centerPos.z - eyePosition.z; + } + data.radius = radius; + currentCollisionCount++; + collisionsData.push_back(data); + } + return RE::BSVisit::BSVisitControl::kContinue; + }); + } + } + } if (!currentCollisionCount) { CollisionSData data{}; ZeroMemory(&data, sizeof(data)); @@ -151,7 +225,6 @@ void GrassCollision::UpdateCollisions() currentCollisionCount = 1; } - static std::uint32_t colllisionCount = 0; bool collisionCountChanged = currentCollisionCount != colllisionCount; if (!collisions || collisionCountChanged) { diff --git a/src/Features/GrassCollision.h b/src/Features/GrassCollision.h index 6b1a92d12f..ffc955e6ea 100644 --- a/src/Features/GrassCollision.h +++ b/src/Features/GrassCollision.h @@ -19,6 +19,8 @@ struct GrassCollision : Feature std::uint32_t EnableGrassCollision = 1; float RadiusMultiplier = 2; float DisplacementMultiplier = 16; + float maxDistance = 1000.0; + std::uint32_t frameInterval = 0; }; struct alignas(16) PerFrame @@ -26,6 +28,7 @@ struct GrassCollision : Feature Vector4 boundCentre[2]; float boundRadius; Settings Settings; + float pad01[2]; }; struct CollisionSData @@ -35,6 +38,12 @@ struct GrassCollision : Feature }; std::unique_ptr collisions = nullptr; + std::uint32_t totalActorCount = 0; + std::uint32_t activeActorCount = 0; + std::uint32_t currentCollisionCount = 0; + std::vector actorList{}; + std::vector collisionsData{}; + std::uint32_t colllisionCount = 0; Settings settings; diff --git a/src/Features/GrassLighting.cpp b/src/Features/GrassLighting.cpp index d844a35db2..b8ed967154 100644 --- a/src/Features/GrassLighting.cpp +++ b/src/Features/GrassLighting.cpp @@ -20,35 +20,78 @@ enum class GrassShaderTechniques void GrassLighting::DrawSettings() { if (ImGui::TreeNodeEx("Complex Grass", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped( - "Specular highlights for complex grass.\n" - "Functions the same as on other objects."); + ImGui::TextWrapped("Specular highlights for complex grass"); ImGui::SliderFloat("Glossiness", &settings.Glossiness, 1.0f, 100.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Specular highlight glossiness."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::SliderFloat("Specular Strength", &settings.SpecularStrength, 0.0f, 1.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Specular highlight strength."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Effects", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped( - "Soft lighting controls how evenly lit an object is.\n" - "Back lighting illuminates the back face of an object.\n" - "Combined to model the transport of light through the surface."); - ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); - ImGui::SliderFloat("Subsurface Scattering Amount", &settings.SubsurfaceScatteringAmount, 0.0f, 2.0f); + ImGui::SliderFloat("SSS Amount", &settings.SubsurfaceScatteringAmount, 0.0f, 2.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Subsurface Scattering (SSS) amount\n"); + ImGui::Text("Soft lighting controls how evenly lit an object is.\n"); + ImGui::Text("Back lighting illuminates the back face of an object.\n"); + ImGui::Text("Combined to model the transport of light through the surface."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Lighting", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped("Fix for grass not being affected by sunlight scale."); ImGui::Checkbox("Enable Directional Light Fix", (bool*)&settings.EnableDirLightFix); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Fix for grass not being affected by sunlight scale."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("Darkens the grass textures to look better with the new lighting"); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::TextWrapped("Grass Color"); ImGui::SliderFloat("Brightness", &settings.Brightness, 0.0f, 1.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Darkens the grass textures to look better with the new lighting"); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("Boosts the vibrancy of textures"); ImGui::SliderFloat("Saturation", &settings.Saturation, 1.0f, 2.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Boosts the vibrancy of textures"); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::TreePop(); } diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index 84cd95fdee..7ca9fa6be2 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -26,39 +26,127 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( void LightLimitFix::DrawSettings() { - if (ImGui::TreeNodeEx("Miscellaneous", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped("All lights cast small shadows. Performance impact."); - ImGui::Checkbox("Enable Contact Shadows", &settings.EnableContactShadows); + if (ImGui::TreeNodeEx("Particle Lights", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Checkbox("Enable Particle Lights", &settings.EnableParticleLights); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables Particle Lights."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("Torches and light spells will cast shadows in first-person. Performance impact."); - ImGui::Checkbox("Enable First-Person Shadows", &settings.EnableFirstPersonShadows); + ImGui::Checkbox("Enable Culling", &settings.EnableParticleLightsCulling); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Significantly improves performance by not rendering empty textures. Only disable if you are encountering issues."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("- Visualise the light limit; Red when the \"strict\" light limit is reached (portal-strict lights).\n - Visualise the number of strict lights. \n - Visualise the number of clustered lights."); - ImGui::Checkbox("Enable Lights Visualisation", &settings.EnableLightsVisualisation); + ImGui::Checkbox("Enable Detection", &settings.EnableParticleLightsDetection); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Adds particle lights to the player light level, so that NPCs can detect them for stealth and gameplay."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - { - static const char* comboOptions[] = { "Light Limit", "Strict Lights Count", "Clustered Lights Count" }; - ImGui::Combo("Lights Visualisation Mode", (int*)&settings.LightsVisualisationMode, comboOptions, 3); + ImGui::Checkbox("Enable Optimization", &settings.EnableParticleLightsOptimization); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Merges vertices which are close enough to each other to significantly improve performance."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::SliderInt("Optimisation Cluster Radius", (int*)&settings.ParticleLightsOptimisationClusterRadius, 1, 64); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Radius to use for clustering lights."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); + + ImGui::TextWrapped("Particle Lights Color"); + ImGui::SliderFloat("Brightness", &settings.ParticleLightsBrightness, 0.0, 1.0, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Particle light brightness."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::SliderFloat("Saturation", &settings.ParticleLightsSaturation, 1.0, 2.0, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Particle light saturation."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } - if (ImGui::TreeNodeEx("Particle Lights", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Checkbox("Enable Particle Lights", &settings.EnableParticleLights); - ImGui::TextWrapped("Significantly improves performance by not rendering empty textures. Only disable if you are encountering issues."); - ImGui::Checkbox("Enable Culling", &settings.EnableParticleLightsCulling); + if (ImGui::TreeNodeEx("Shadows", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Checkbox("Enable Contact Shadows", &settings.EnableContactShadows); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("All lights cast small shadows. Performance impact."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("Adds particle lights to the player light level, so that NPCs can detect them for stealth and gameplay."); - ImGui::Checkbox("Enable Detection", &settings.EnableParticleLightsDetection); + ImGui::Checkbox("Enable First-Person Shadows", &settings.EnableFirstPersonShadows); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Torches and light spells will cast shadows in first-person. Performance impact."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::SliderFloat("Brightness", &settings.ParticleLightsBrightness, 0.0, 1.0, "%.2f"); - ImGui::SliderFloat("Saturation", &settings.ParticleLightsSaturation, 1.0, 2.0, "%.2f"); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::TreePop(); + } - ImGui::TextWrapped("Merges vertices which are close enough to each other to significantly improve performance."); - ImGui::Checkbox("Enable Optimization", &settings.EnableParticleLightsOptimization); - ImGui::SliderInt("Optimisation Cluster Radius", (int*)&settings.ParticleLightsOptimisationClusterRadius, 1, 64); + if (ImGui::TreeNodeEx("Light Limit Visualization", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Checkbox("Enable Lights Visualisation", &settings.EnableLightsVisualisation); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables visualization of the light limit\n"); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + { + static const char* comboOptions[] = { "Light Limit", "Strict Lights Count", "Clustered Lights Count" }; + ImGui::Combo("Lights Visualisation Mode", (int*)&settings.LightsVisualisationMode, comboOptions, 3); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text(" - Visualise the light limit. Red when the \"strict\" light limit is reached (portal-strict lights).\n"); + ImGui::Text(" - Visualise the number of strict lights. \n"); + ImGui::Text(" - Visualise the number of clustered lights."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } diff --git a/src/Features/ScreenSpaceShadows.cpp b/src/Features/ScreenSpaceShadows.cpp index 0bbc973e0c..c82e752391 100644 --- a/src/Features/ScreenSpaceShadows.cpp +++ b/src/Features/ScreenSpaceShadows.cpp @@ -21,32 +21,109 @@ void ScreenSpaceShadows::DrawSettings() { if (ImGui::TreeNodeEx("General", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Checkbox("Enable Screen-Space Shadows", &enabled); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables screen-space shadows."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } - ImGui::TextWrapped("Controls the accuracy of traced shadows."); ImGui::SliderInt("Max Samples", (int*)&settings.MaxSamples, 1, 512); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Controls the accuracy of traced shadows."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Blur Filter", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::SliderFloat("Blur Radius", &settings.BlurRadius, 0, 1); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Blur radius."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::SliderFloat("Blur Depth Dropoff", &settings.BlurDropoff, 0.001f, 0.1f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Blur depth dropoff."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Near Shadows", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::SliderFloat("Near Distance", &settings.NearDistance, 0, 128); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Near Shadow Distance."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::SliderFloat("Near Thickness", &settings.NearThickness, 0, 128); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Near Shadow Thickness."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::SliderFloat("Near Hardness", &settings.NearHardness, 0, 64); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Near Shadow Hardness."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Far Shadows", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::SliderFloat("Far Distance Scale", &settings.FarDistanceScale, 0, 1); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Far Shadow Distance Scale."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::SliderFloat("Far Thickness Scale", &settings.FarThicknessScale, 0, 1); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Far Shadow Thickness Scale."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::SliderFloat("Far Hardness", &settings.FarHardness, 0, 64); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Far Shadow Hardness."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::TreePop(); } diff --git a/src/Features/SubsurfaceScattering.cpp b/src/Features/SubsurfaceScattering.cpp index ce75aae1f9..59b93a2492 100644 --- a/src/Features/SubsurfaceScattering.cpp +++ b/src/Features/SubsurfaceScattering.cpp @@ -25,8 +25,7 @@ void SubsurfaceScattering::Draw(const RE::BSShader* shader, const uint32_t) D3D11_TEXTURE2D_DESC texDesc{}; snowSwapTexture.texture->GetDesc(&texDesc); - if ((texDesc.BindFlags | D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_RENDER_TARGET) != texDesc.BindFlags) - { + if ((texDesc.BindFlags | D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_RENDER_TARGET) != texDesc.BindFlags) { logger::info("missing access!"); } colorTextureTemp = new Texture2D(texDesc); @@ -37,13 +36,11 @@ void SubsurfaceScattering::Draw(const RE::BSShader* shader, const uint32_t) colorTextureTemp->CreateSRV(srvDesc); colorTextureTemp2->CreateSRV(srvDesc); - D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {}; snowSwapTexture.RTV->GetDesc(&rtvDesc); colorTextureTemp->CreateRTV(rtvDesc); colorTextureTemp2->CreateRTV(rtvDesc); - D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; snowSwapTexture.UAV->GetDesc(&uavDesc); colorTextureTemp->CreateUAV(uavDesc); @@ -81,7 +78,7 @@ void SubsurfaceScattering::Draw(const RE::BSShader* shader, const uint32_t) samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; DX::ThrowIfFailed(device->CreateSamplerState(&samplerDesc, &linearSampler)); - + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; DX::ThrowIfFailed(device->CreateSamplerState(&samplerDesc, &pointSampler)); } @@ -113,8 +110,7 @@ void SubsurfaceScattering::Draw(const RE::BSShader* shader, const uint32_t) context->OMGetBlendState(&blendState, blendFactor, &sampleMask); - if (!mappedBlendStates.contains(blendState)) - { + if (!mappedBlendStates.contains(blendState)) { if (!modifiedBlendStates.contains(blendState)) { D3D11_BLEND_DESC blendDesc; blendState->GetDesc(&blendDesc); @@ -176,7 +172,8 @@ void SubsurfaceScattering::DrawDeferred() data.DynamicRes.w = 1.0f / data.DynamicRes.y; auto shadowState = RE::BSGraphics::RendererShadowState::GetSingleton(); - auto cameraData = shadowState->GetRuntimeData().cameraData.getEye(); + auto cameraData = !REL::Module::IsVR() ? shadowState->GetRuntimeData().cameraData.getEye() : + shadowState->GetVRRuntimeData().cameraData.getEye(); data.SSSS_FOVY = atan(1.0f / cameraData.projMat.m[0][0]) * 2.0f * (180.0f / 3.14159265359f); data.CameraData.x = accumulator->kCamera->GetRuntimeData2().viewFrustum.fFar; diff --git a/src/Features/WaterBlending.cpp b/src/Features/WaterBlending.cpp index 1a0947735f..0d688a7495 100644 --- a/src/Features/WaterBlending.cpp +++ b/src/Features/WaterBlending.cpp @@ -9,14 +9,44 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( void WaterBlending::DrawSettings() { - if (ImGui::TreeNodeEx("General", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::TreeNodeEx("Water Blending", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Checkbox("Enable Water Blending", (bool*)&settings.EnableWaterBlending); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables blending water into the terrain and objects on the water's surface."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::SliderFloat("Water Blend Range", &settings.WaterBlendRange, 0, 3); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Water Blend Range."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::Spacing(); + ImGui::Spacing(); ImGui::Checkbox("Enable Water Blending SSR", (bool*)&settings.EnableWaterBlendingSSR); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Enables blending screen-space reflections (SSR) as they are faded out near where the terrain touches large water sources."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::SliderFloat("SSR Blend Range", &settings.SSRBlendRange, 0, 3); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Screen-space Reflection (SSR) Blend Range."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } ImGui::TreePop(); } diff --git a/src/Menu.cpp b/src/Menu.cpp index 8f9f85bcb0..2a180b4745 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -334,6 +334,24 @@ void Menu::DrawSettings() ImGui::EndTooltip(); } + if (shaderCache.GetFailedTasks()) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button("Toggle Error Message", { -1, 0 })) { + shaderCache.ToggleErrorMessages(); + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text( + "Hide or show the shader failure message. " + "Your installation is broken and will likely see errors in game. " + "Please double check you have updated all features and that your load order is correct. " + "See CommunityShaders.log for details and check the NexusMods page or Discord server. "); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + } ImGui::EndTable(); } @@ -457,6 +475,22 @@ void Menu::DrawSettings() ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } + ImGui::Spacing(); + ImGui::SliderInt("Compiler Threads", &shaderCache.compilationThreadCount, 1, static_cast(std::thread::hardware_concurrency())); + if (ImGui::IsItemHovered()) { + 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. "); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text(std::format("Shader Compiler : {}", shaderCache.GetShaderStatsString()).c_str()); + ImGui::TreePop(); + } } if (ImGui::CollapsingHeader("Replace Original Shaders", ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) { @@ -539,7 +573,10 @@ void Menu::DrawOverlay() compiledShaders = shaderCache.GetCompletedTasks(); totalShaders = shaderCache.GetTotalTasks(); - if (compiledShaders != totalShaders) { + auto failed = shaderCache.GetFailedTasks(); + auto hide = shaderCache.IsHideErrors(); + auto stats = shaderCache.GetShaderStatsString(); + if (shaderCache.IsCompiling()) { ImGui::SetNextWindowBgAlpha(1); ImGui::SetNextWindowPos(ImVec2(10, 10)); if (!ImGui::Begin("ShaderCompilationInfo", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)) { @@ -547,8 +584,19 @@ void Menu::DrawOverlay() return; } - ImGui::Text("Compiling Shaders: %d / %d", compiledShaders, totalShaders); + ImGui::Text(fmt::format("Compiling Shaders: {}", stats).c_str()); + + ImGui::End(); + } else if (failed && !hide) { + ImGui::SetNextWindowBgAlpha(1); + ImGui::SetNextWindowPos(ImVec2(10, 10)); + if (!ImGui::Begin("ShaderCompilationInfo", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)) { + ImGui::End(); + return; + } + ImGui::Text("ERROR: %d shaders failed to compile. Check installation and CommunityShaders.log", failed, totalShaders); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); ImGui::End(); } diff --git a/src/ShaderCache.cpp b/src/ShaderCache.cpp index cb5f276665..b2309eff8d 100644 --- a/src/ShaderCache.cpp +++ b/src/ShaderCache.cpp @@ -17,6 +17,8 @@ namespace SIE { namespace SShaderCache { + static void GetShaderDefines(RE::BSShader::Type, uint32_t, D3D_SHADER_MACRO*); + static std::string GetShaderString(ShaderClass, const RE::BSShader&, uint32_t, bool = false); constexpr const char* VertexShaderProfile = "vs_5_0"; constexpr const char* PixelShaderProfile = "ps_5_0"; constexpr const char* ComputeShaderProfile = "cs_5_0"; @@ -896,9 +898,13 @@ namespace SIE return it->second; } - static std::string MergeDefinesString(const std::array& defines) + static std::string MergeDefinesString(std::array& defines, bool a_sort = false) { std::string result; + if (a_sort) + std::sort(std::begin(defines), std::end(defines), [](const D3D_SHADER_MACRO& a, const D3D_SHADER_MACRO& b) { + return a.Name > b.Name; + }); for (const auto& def : defines) { if (def.Name != nullptr) { result += def.Name; @@ -908,6 +914,8 @@ namespace SIE } result += ' '; } else { + if (a_sort) // sometimes the sort messes up so null entries get interspersed + continue; break; } } @@ -1052,12 +1060,56 @@ namespace SIE return std::format(L"Data/ShaderCache/{}/{:X}.cso", std::wstring(name.begin(), name.end()), descriptor); } - static ID3DBlob* CompileShader(ShaderClass shaderClass, const RE::BSShader& shader, - uint32_t descriptor, bool useDiskCache) + static std::string GetShaderString(ShaderClass shaderClass, const RE::BSShader& shader, uint32_t descriptor, bool hashkey) { + auto sourceShaderFile = shader.fxpFilename; + std::array defines{}; + SIE::SShaderCache::GetShaderDefines(shader.shaderType.get(), descriptor, &defines[0]); + std::string result; + if (hashkey) // generate hashkey so don't include descriptor + result = fmt::format("{}:{}:{}", sourceShaderFile, magic_enum::enum_name(shaderClass), SIE::SShaderCache::MergeDefinesString(defines, true)); + else + result = fmt::format("{}:{}:{:X}:{}", sourceShaderFile, magic_enum::enum_name(shaderClass), descriptor, SIE::SShaderCache::MergeDefinesString(defines, true)); + return result; + } + + static ID3DBlob* CompileShader(ShaderClass shaderClass, const RE::BSShader& shader, uint32_t descriptor, bool useDiskCache) + { + ID3DBlob* shaderBlob = nullptr; + + // check hashmap + auto& cache = ShaderCache::Instance(); + if (shaderBlob = cache.GetCompletedShader(shaderClass, shader, descriptor); shaderBlob) { + // already compiled before + logger::debug("Shader already compiled; using cache: {}", SShaderCache::GetShaderString(shaderClass, shader, descriptor)); + cache.IncCacheHitTasks(); + return shaderBlob; + } const auto type = shader.shaderType.get(); - const std::wstring path = GetShaderPath(shader.fxpFilename); + // check diskcache + auto diskPath = GetDiskPath(shader.fxpFilename, descriptor, shaderClass); + + if (!shaderBlob && useDiskCache && std::filesystem::exists(diskPath)) { + shaderBlob = nullptr; + if (FAILED(D3DReadFileToBlob(diskPath.c_str(), &shaderBlob))) { + logger::error("Failed to load {} shader {}::{}", magic_enum::enum_name(shaderClass), magic_enum::enum_name(type), descriptor); + + if (shaderBlob != nullptr) { + shaderBlob->Release(); + } + } else { + std::string str; + std::transform(diskPath.begin(), diskPath.end(), std::back_inserter(str), [](wchar_t c) { + return (char)c; + }); + logger::debug("Loaded shader from {}", str); + cache.AddCompletedShader(shaderClass, shader, descriptor, shaderBlob); + return shaderBlob; + } + } + + // prepare preprocessor defines std::array defines{}; auto lastIndex = 0; if (shaderClass == ShaderClass::Vertex) { @@ -1079,30 +1131,10 @@ namespace SIE defines[lastIndex] = { nullptr, nullptr }; // do final entry GetShaderDefines(type, descriptor, &defines[lastIndex]); - logger::debug("{}, {}", descriptor, MergeDefinesString(defines)); - - auto diskPath = GetDiskPath(shader.fxpFilename, descriptor, shaderClass); - - if (useDiskCache && std::filesystem::exists(diskPath)) { - ID3DBlob* shaderBlob = nullptr; - if (FAILED(D3DReadFileToBlob(diskPath.c_str(), &shaderBlob))) { - logger::error("Failed to load {} shader {}::{}", magic_enum::enum_name(shaderClass), magic_enum::enum_name(type), descriptor); + logger::debug("Defines set for {}:{}:{:X} to {}", magic_enum::enum_name(type), magic_enum::enum_name(shaderClass), descriptor, MergeDefinesString(defines)); - if (shaderBlob != nullptr) { - shaderBlob->Release(); - } - } else { - std::string str; - ; - std::transform(diskPath.begin(), diskPath.end(), std::back_inserter(str), [](wchar_t c) { - return (char)c; - }); - logger::debug("Loaded shader from {}", str); - return shaderBlob; - } - } - - ID3DBlob* shaderBlob = nullptr; + // compile shaders + const std::wstring path = GetShaderPath(shader.fxpFilename); ID3DBlob* errorBlob = nullptr; const uint32_t flags = D3DCOMPILE_OPTIMIZATION_LEVEL3; const HRESULT compileResult = D3DCompileFromFile(path.c_str(), defines.data(), D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", @@ -1122,11 +1154,12 @@ namespace SIE shaderBlob->Release(); } + cache.AddCompletedShader(shaderClass, shader, descriptor, nullptr); return nullptr; } - logger::debug("Compiled {} shader {}::{}", magic_enum::enum_name(shaderClass), - magic_enum::enum_name(type), descriptor); + logger::debug("Compiled shader {}:{}:{:X}", magic_enum::enum_name(type), magic_enum::enum_name(shaderClass), descriptor); + // strip debug info ID3DBlob* strippedShaderBlob = nullptr; const uint32_t stripFlags = D3DCOMPILER_STRIP_DEBUG_INFO | @@ -1138,6 +1171,7 @@ namespace SIE std::swap(shaderBlob, strippedShaderBlob); strippedShaderBlob->Release(); + // save shader to disk if (useDiskCache) { auto directoryPath = std::format("Data/ShaderCache/{}", shader.fxpFilename); if (!std::filesystem::is_directory(directoryPath)) { @@ -1151,21 +1185,19 @@ namespace SIE const HRESULT saveResult = D3DWriteBlobToFile(shaderBlob, diskPath.c_str(), true); if (FAILED(saveResult)) { std::string str; - ; std::transform(diskPath.begin(), diskPath.end(), std::back_inserter(str), [](wchar_t c) { return (char)c; }); logger::error("Failed to save shader to {}", str); } else { std::string str; - ; std::transform(diskPath.begin(), diskPath.end(), std::back_inserter(str), [](wchar_t c) { return (char)c; }); logger::debug("Saved shader to {}", str); } } - + cache.AddCompletedShader(shaderClass, shader, descriptor, shaderBlob); return shaderBlob; } @@ -1370,6 +1402,61 @@ namespace SIE } compilationSet.Clear(); + std::unique_lock lock{ mapMutex }; + shaderMap.clear(); + } + + bool ShaderCache::AddCompletedShader(ShaderClass shaderClass, const RE::BSShader& shader, uint32_t descriptor, ID3DBlob* a_blob) + { + auto key = SIE::SShaderCache::GetShaderString(shaderClass, shader, descriptor, true); + auto status = a_blob ? ShaderCompilationTask::Status::Completed : ShaderCompilationTask::Status::Failed; + std::unique_lock lock{ mapMutex }; + logger::debug("Adding {} shader to map: {}", magic_enum ::enum_name(status), key); + shaderMap.insert_or_assign(key, std::pair(a_blob, status)); + return (bool)a_blob; + } + + ID3DBlob* ShaderCache::GetCompletedShader(const std::string a_key) + { + std::scoped_lock lock{ mapMutex }; + if (!shaderMap.empty() && shaderMap.contains(a_key)) { + auto status = shaderMap.at(a_key).second; + if (status != ShaderCompilationTask::Status::Pending) + return shaderMap.at(a_key).first; + } + return nullptr; + } + + ID3DBlob* ShaderCache::GetCompletedShader(ShaderClass shaderClass, const RE::BSShader& shader, + uint32_t descriptor) + { + auto key = SIE::SShaderCache::GetShaderString(shaderClass, shader, descriptor, true); + return GetCompletedShader(key); + } + + ID3DBlob* ShaderCache::GetCompletedShader(const ShaderCompilationTask& a_task) + { + auto key = a_task.GetString(); + return GetCompletedShader(key); + } + + ShaderCompilationTask::Status ShaderCache::GetShaderStatus(const std::string a_key) + { + std::scoped_lock lock{ mapMutex }; + if (!shaderMap.empty() && shaderMap.contains(a_key)) { + return shaderMap.at(a_key).second; + } + return ShaderCompilationTask::Status::Pending; + } + + std::string ShaderCache::GetShaderStatsString() + { + return compilationSet.GetStatsString(); + } + + bool ShaderCache::IsCompiling() + { + return compilationSet.totalTasks && compilationSet.completedTasks + compilationSet.failedTasks < compilationSet.totalTasks; } bool ShaderCache::IsEnabled() const @@ -1414,7 +1501,7 @@ namespace SIE void ShaderCache::DeleteDiskCache() { - std::lock_guard lock(compilationSet.mutex); + std::lock_guard lock(compilationSet.compilationMutex); try { std::filesystem::remove_all(L"Data/ShaderCache"); logger::info("Deleted disk cache"); @@ -1459,7 +1546,7 @@ namespace SIE ShaderCache::ShaderCache() { - static const auto compilationThreadCount = static_cast(std::thread::hardware_concurrency()); + logger::debug("ShaderCache initialized with {} compiler threads", compilationThreadCount); for (size_t threadIndex = 0; threadIndex < compilationThreadCount; ++threadIndex) { compilationThreads.push_back(std::jthread(&ShaderCache::ProcessCompilationSet, this)); } @@ -1523,15 +1610,37 @@ namespace SIE return nullptr; } + uint64_t ShaderCache::GetCachedHitTasks() + { + return compilationSet.cacheHitTasks; + } uint64_t ShaderCache::GetCompletedTasks() { return compilationSet.completedTasks; } + uint64_t ShaderCache::GetFailedTasks() + { + return compilationSet.failedTasks; + } uint64_t ShaderCache::GetTotalTasks() { return compilationSet.totalTasks; } + void ShaderCache::IncCacheHitTasks() + { + compilationSet.cacheHitTasks++; + } + + bool ShaderCache::IsHideErrors() + { + return hideError; + } + + void ShaderCache::ToggleErrorMessages() + { + hideError = !hideError; + } void ShaderCache::ProcessCompilationSet() { @@ -1565,6 +1674,11 @@ namespace SIE (static_cast(shaderClass) << 60); } + std::string ShaderCompilationTask::GetString() const + { + return SIE::SShaderCache::GetShaderString(shaderClass, shader, descriptor, true); + } + bool ShaderCompilationTask::operator==(const ShaderCompilationTask& other) const { return GetId() == other.GetId(); @@ -1572,9 +1686,11 @@ namespace SIE ShaderCompilationTask CompilationSet::WaitTake() { - std::unique_lock lock(mutex); + std::unique_lock lock(compilationMutex); conditionVariable.wait(lock, [this]() { return !availableTasks.empty(); }); - + 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(); tasksInProgress.insert(std::move(node)); @@ -1583,9 +1699,10 @@ namespace SIE void CompilationSet::Add(const ShaderCompilationTask& task) { - std::unique_lock lock(mutex); + std::unique_lock lock(compilationMutex); auto inProgressIt = tasksInProgress.find(task); - if (inProgressIt == tasksInProgress.end()) { + auto processedIt = processedTasks.find(task); + if (inProgressIt == tasksInProgress.end() && processedIt == processedTasks.end() && !ShaderCache::Instance().GetCompletedShader(task)) { auto [availableIt, wasAdded] = availableTasks.insert(task); lock.unlock(); if (wasAdded) { @@ -1597,17 +1714,67 @@ namespace SIE void CompilationSet::Complete(const ShaderCompilationTask& task) { - std::unique_lock lock(mutex); + auto& cache = ShaderCache::Instance(); + auto key = task.GetString(); + auto shaderBlob = cache.GetCompletedShader(task); + if (shaderBlob) { + logger::debug("Compiling Task succeeded: {}", key); + completedTasks++; + } else { + logger::debug("Compiling Task failed: {}", key); + failedTasks++; + } + auto now = high_resolution_clock::now(); + totalMs += duration_cast(now - lastCalculation).count(); + lastCalculation = now; + std::unique_lock lock(compilationMutex); + processedTasks.insert(task); tasksInProgress.erase(task); - completedTasks++; } void CompilationSet::Clear() { - std::lock_guard lock(mutex); + std::lock_guard lock(compilationMutex); availableTasks.clear(); tasksInProgress.clear(); + processedTasks.clear(); totalTasks = 0; completedTasks = 0; + failedTasks = 0; + cacheHitTasks = 0; + lastReset = high_resolution_clock::now(); + lastCalculation = high_resolution_clock::now(); + totalMs = (double)duration_cast(lastReset - lastReset).count(); + } + + std::string CompilationSet::GetHumanTime(double a_totalms) + { + int milliseconds = (int)a_totalms; + int seconds = milliseconds / 1000; + milliseconds %= 1000; + int minutes = seconds / 60; + seconds %= 60; + int hours = minutes / 60; + minutes %= 60; + + return fmt::format("{:02}:{:02}:{:02}", hours, minutes, seconds); + } + + double CompilationSet::GetEta() + { + auto rate = completedTasks / totalMs; + auto remaining = (int)totalTasks - completedTasks - failedTasks; + return remaining / rate; + } + + std::string CompilationSet::GetStatsString() + { + return fmt::format("{}/{} (successful/total)\tfailed: {}\tcachehits: {}\nElapsed/Estimated Time: {}/{}", + (std::uint64_t)completedTasks, + (std::uint64_t)totalTasks, + (std::uint64_t)failedTasks, + (std::uint64_t)cacheHitTasks, + GetHumanTime(totalMs), + GetHumanTime(GetEta() + totalMs)); } } \ No newline at end of file diff --git a/src/ShaderCache.h b/src/ShaderCache.h index 915bbd6db4..be7bedb102 100644 --- a/src/ShaderCache.h +++ b/src/ShaderCache.h @@ -2,12 +2,15 @@ #include +#include #include #include #include static constexpr REL::Version SHADER_CACHE_VERSION = { 0, 0, 0, 11 }; +using namespace std::chrono; + namespace SIE { enum class ShaderClass @@ -21,11 +24,18 @@ namespace SIE class ShaderCompilationTask { public: + enum Status + { + Pending, + Failed, + Completed + }; ShaderCompilationTask(ShaderClass shaderClass, const RE::BSShader& shader, uint32_t descriptor); void Perform() const; size_t GetId() const; + std::string GetString() const; bool operator==(const ShaderCompilationTask& other) const; @@ -54,14 +64,23 @@ namespace SIE void Add(const ShaderCompilationTask& task); void Complete(const ShaderCompilationTask& task); void Clear(); + std::string GetHumanTime(double a_totalms); + double GetEta(); + std::string GetStatsString(); std::atomic completedTasks = 0; std::atomic totalTasks = 0; - std::mutex mutex; + std::atomic failedTasks = 0; + std::atomic cacheHitTasks = 0; // number of compiles of a previously seen shader combo + std::mutex compilationMutex; private: std::unordered_set availableTasks; std::unordered_set tasksInProgress; + std::unordered_set processedTasks; // completed or failed std::condition_variable 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(); }; class ShaderCache @@ -92,6 +111,7 @@ namespace SIE return IsSupportedShader(shader.shaderType.get()); } + bool IsCompiling(); bool IsEnabled() const; void SetEnabled(bool value); bool IsAsync() const; @@ -107,6 +127,13 @@ namespace SIE void Clear(); + bool AddCompletedShader(ShaderClass shaderClass, const RE::BSShader& shader, uint32_t descriptor, ID3DBlob* a_blob); + ID3DBlob* GetCompletedShader(const std::string a_key); + ID3DBlob* GetCompletedShader(const SIE::ShaderCompilationTask& a_task); + ID3DBlob* GetCompletedShader(ShaderClass shaderClass, const RE::BSShader& shader, uint32_t descriptor); + ShaderCompilationTask::Status GetShaderStatus(const std::string a_key); + std::string GetShaderStatsString(); + RE::BSGraphics::VertexShader* GetVertexShader(const RE::BSShader& shader, uint32_t descriptor); RE::BSGraphics::PixelShader* GetPixelShader(const RE::BSShader& shader, uint32_t descriptor); @@ -116,8 +143,15 @@ namespace SIE RE::BSGraphics::PixelShader* MakeAndAddPixelShader(const RE::BSShader& shader, uint32_t descriptor); + uint64_t GetCachedHitTasks(); uint64_t GetCompletedTasks(); + uint64_t GetFailedTasks(); uint64_t GetTotalTasks(); + void IncCacheHitTasks(); + void ToggleErrorMessages(); + bool IsHideErrors(); + + int32_t compilationThreadCount = std::max(static_cast(std::thread::hardware_concurrency()) - 1, 1); private: ShaderCache(); @@ -136,10 +170,13 @@ namespace SIE bool isDiskCache = false; bool isAsync = true; bool isDump = false; + bool hideError = false; eastl::vector compilationThreads; std::mutex vertexShadersMutex; std::mutex pixelShadersMutex; CompilationSet compilationSet; + std::unordered_map> shaderMap{}; + std::mutex mapMutex; }; } diff --git a/src/State.cpp b/src/State.cpp index 87131fb41e..0be24b445c 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -94,9 +94,11 @@ void State::Load() if (advanced["Log Level"].is_number_integer()) { logLevel = static_cast((int)advanced["Log Level"]); //logLevel = static_cast(max(spdlog::level::trace, min(spdlog::level::off, (int)advanced["Log Level"]))); - if (advanced["Shader Defines"].is_string()) - SetDefines(advanced["Shader Defines"]); } + if (advanced["Shader Defines"].is_string()) + 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 (settings["General"].is_object()) { @@ -149,6 +151,7 @@ void State::Save() advanced["Dump Shaders"] = shaderCache.IsDump(); advanced["Log Level"] = logLevel; advanced["Shader Defines"] = shaderDefinesString; + advanced["Compiler Threads"] = shaderCache.compilationThreadCount; settings["Advanced"] = advanced; json general; diff --git a/src/XSEPlugin.cpp b/src/XSEPlugin.cpp index a2671bce3f..0c30f81bd2 100644 --- a/src/XSEPlugin.cpp +++ b/src/XSEPlugin.cpp @@ -98,9 +98,6 @@ void MessageHandler(SKSE::MessagingInterface::Message* message) shaderCache.SetAsync(true); shaderCache.SetDiskCache(true); shaderCache.SetDump(false); - - State::GetSingleton()->Load(); - shaderCache.ValidateDiskCache(); if (LightLimitFix::GetSingleton()->loaded) { @@ -123,7 +120,7 @@ void MessageHandler(SKSE::MessagingInterface::Message* message) auto& shaderCache = SIE::ShaderCache::Instance(); - while (shaderCache.GetCompletedTasks() != shaderCache.GetTotalTasks()) { + while (shaderCache.IsCompiling()) { std::this_thread::sleep_for(100ms); } @@ -133,6 +130,8 @@ void MessageHandler(SKSE::MessagingInterface::Message* message) if (LightLimitFix::GetSingleton()->loaded) LightLimitFix::GetSingleton()->DataLoaded(); + if (ExtendedMaterials::GetSingleton()->loaded) + ExtendedMaterials::GetSingleton()->DataLoaded(); } break;