diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp index ad138cc754..d63533b7f4 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp @@ -446,10 +446,14 @@ void ObjectCache::ClearPipelineCache() m_compute_pipeline_objects.clear(); } -std::string ObjectCache::GetDiskCacheFileName(const char* type) +std::string ObjectCache::GetDiskCacheFileName(const char* type, bool include_gameid, + bool include_host_config) { - return StringFromFormat("%svulkan-%s-%s.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(), - SConfig::GetInstance().GetGameID().c_str(), type); + return StringFromFormat( + "%svulkan-%s%s%s%s%s.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(), type, + include_gameid ? "-" : "", include_gameid ? SConfig::GetInstance().GetGameID().c_str() : "", + include_host_config ? "-" : "", + include_host_config ? g_ActiveConfig.GetHostConfigFilename().c_str() : ""); } class PipelineCacheReadCallback : public LinearDiskCacheReader @@ -475,7 +479,10 @@ public: bool ObjectCache::CreatePipelineCache() { - m_pipeline_cache_filename = GetDiskCacheFileName("pipeline"); + // Vulkan pipeline caches can be shared between games for shader compile time reduction. + // This assumes that drivers don't create all pipelines in the cache on load time, only + // when a lookup occurs that matches a pipeline (or pipeline data) in the cache. + m_pipeline_cache_filename = GetDiskCacheFileName("pipeline", false, true); VkPipelineCacheCreateInfo info = { VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType @@ -498,7 +505,7 @@ bool ObjectCache::LoadPipelineCache() { // We have to keep the pipeline cache file name around since when we save it // we delete the old one, by which time the game's unique ID is already cleared. - m_pipeline_cache_filename = GetDiskCacheFileName("pipeline"); + m_pipeline_cache_filename = GetDiskCacheFileName("pipeline", false, true); std::vector disk_data; LinearDiskCache disk_cache; @@ -664,15 +671,15 @@ struct ShaderCacheReader : public LinearDiskCacheReader void ObjectCache::LoadShaderCaches() { ShaderCacheReader vs_reader(m_vs_cache.shader_map); - m_vs_cache.disk_cache.OpenAndRead(GetDiskCacheFileName("vs"), vs_reader); + m_vs_cache.disk_cache.OpenAndRead(GetDiskCacheFileName("vs", true, true), vs_reader); ShaderCacheReader ps_reader(m_ps_cache.shader_map); - m_ps_cache.disk_cache.OpenAndRead(GetDiskCacheFileName("ps"), ps_reader); + m_ps_cache.disk_cache.OpenAndRead(GetDiskCacheFileName("ps", true, true), ps_reader); if (g_vulkan_context->SupportsGeometryShaders()) { ShaderCacheReader gs_reader(m_gs_cache.shader_map); - m_gs_cache.disk_cache.OpenAndRead(GetDiskCacheFileName("gs"), gs_reader); + m_gs_cache.disk_cache.OpenAndRead(GetDiskCacheFileName("gs", true, true), gs_reader); } SETSTAT(stats.numPixelShadersCreated, static_cast(m_ps_cache.shader_map.size())); @@ -684,6 +691,7 @@ void ObjectCache::LoadShaderCaches() template static void DestroyShaderCache(T& cache) { + cache.disk_cache.Sync(); cache.disk_cache.Close(); for (const auto& it : cache.shader_map) { @@ -825,6 +833,23 @@ void ObjectCache::RecompileSharedShaders() PanicAlert("Failed to recompile shared shaders."); } +void ObjectCache::ReloadShaderAndPipelineCaches() +{ + SavePipelineCache(); + DestroyShaderCaches(); + DestroyPipelineCache(); + + if (g_ActiveConfig.bShaderCache) + { + LoadShaderCaches(); + LoadPipelineCache(); + } + else + { + CreatePipelineCache(); + } +} + bool ObjectCache::CreateDescriptorSetLayouts() { static const VkDescriptorSetLayoutBinding ubo_set_bindings[] = { diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.h b/Source/Core/VideoBackends/Vulkan/ObjectCache.h index 8d7f4f6cc7..58476f16ab 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.h +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.h @@ -154,13 +154,16 @@ public: // Recompile shared shaders, call when stereo mode changes. void RecompileSharedShaders(); + // Reload pipeline cache. This will destroy all pipelines. + void ReloadShaderAndPipelineCaches(); + // Shared shader accessors VkShaderModule GetScreenQuadVertexShader() const { return m_screen_quad_vertex_shader; } VkShaderModule GetPassthroughVertexShader() const { return m_passthrough_vertex_shader; } VkShaderModule GetScreenQuadGeometryShader() const { return m_screen_quad_geometry_shader; } VkShaderModule GetPassthroughGeometryShader() const { return m_passthrough_geometry_shader; } // Gets the filename of the specified type of cache object (e.g. vertex shader, pipeline). - std::string GetDiskCacheFileName(const char* type); + std::string GetDiskCacheFileName(const char* type, bool include_gameid, bool include_host_config); private: bool CreatePipelineCache(); diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 9a26df8548..e2690ab253 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -114,7 +114,7 @@ bool Renderer::Initialize() } // Ensure all pipelines previously used by the game have been created. - StateTracker::GetInstance()->LoadPipelineUIDCache(); + StateTracker::GetInstance()->ReloadPipelineUIDCache(); // Initialize post processing. m_post_processor = std::make_unique(); @@ -1126,13 +1126,11 @@ void Renderer::CheckForSurfaceChange() void Renderer::CheckForConfigChanges() { // Save the video config so we can compare against to determine which settings have changed. - u32 old_multisamples = g_ActiveConfig.iMultisamples; + u32 old_host_bits = g_ActiveConfig.GetHostConfigBits(); int old_anisotropy = g_ActiveConfig.iMaxAnisotropy; - int old_stereo_mode = g_ActiveConfig.iStereoMode; int old_aspect_ratio = g_ActiveConfig.iAspectRatio; int old_efb_scale = g_ActiveConfig.iEFBScale; bool old_force_filtering = g_ActiveConfig.bForceFiltering; - bool old_ssaa = g_ActiveConfig.bSSAA; bool old_use_xfb = g_ActiveConfig.bUseXFB; bool old_use_realxfb = g_ActiveConfig.bUseRealXFB; @@ -1142,11 +1140,9 @@ void Renderer::CheckForConfigChanges() UpdateActiveConfig(); // Determine which (if any) settings have changed. - bool msaa_changed = old_multisamples != g_ActiveConfig.iMultisamples; - bool ssaa_changed = old_ssaa != g_ActiveConfig.bSSAA; + bool host_bits_changed = old_host_bits != g_ActiveConfig.GetHostConfigBits(); bool anisotropy_changed = old_anisotropy != g_ActiveConfig.iMaxAnisotropy; bool force_texture_filtering_changed = old_force_filtering != g_ActiveConfig.bForceFiltering; - bool stereo_changed = old_stereo_mode != g_ActiveConfig.iStereoMode; bool efb_scale_changed = old_efb_scale != g_ActiveConfig.iEFBScale; bool aspect_changed = old_aspect_ratio != g_ActiveConfig.iAspectRatio; bool use_xfb_changed = old_use_xfb != g_ActiveConfig.bUseXFB; @@ -1164,23 +1160,21 @@ void Renderer::CheckForConfigChanges() // MSAA samples changed, we need to recreate the EFB render pass. // If the stereoscopy mode changed, we need to recreate the buffers as well. - if (msaa_changed || stereo_changed) + // SSAA changed on/off, we have to recompile shaders. + // Changing stereoscopy from off<->on also requires shaders to be recompiled. + if (host_bits_changed) { + OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL); g_command_buffer_mgr->WaitForGPUIdle(); FramebufferManager::GetInstance()->RecreateRenderPass(); FramebufferManager::GetInstance()->ResizeEFBTextures(); BindEFBToStateTracker(); - } - - // SSAA changed on/off, we can leave the buffers/render pass, but have to recompile shaders. - // Changing stereoscopy from off<->on also requires shaders to be recompiled. - if (msaa_changed || ssaa_changed || stereo_changed) - { - g_command_buffer_mgr->WaitForGPUIdle(); RecompileShaders(); FramebufferManager::GetInstance()->RecompileShaders(); + g_object_cache->ReloadShaderAndPipelineCaches(); g_object_cache->RecompileSharedShaders(); - StateTracker::GetInstance()->LoadPipelineUIDCache(); + StateTracker::GetInstance()->InvalidateShaderPointers(); + StateTracker::GetInstance()->ReloadPipelineUIDCache(); } // For vsync, we need to change the present mode, which means recreating the swap chain. diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp index 7299e981a9..01b6065f3c 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp @@ -115,7 +115,20 @@ bool StateTracker::Initialize() return true; } -void StateTracker::LoadPipelineUIDCache() +void StateTracker::InvalidateShaderPointers() +{ + // Clear UIDs, forcing a false match next time. + m_vs_uid = {}; + m_gs_uid = {}; + m_ps_uid = {}; + + // Invalidate shader pointers. + m_pipeline_state.vs = VK_NULL_HANDLE; + m_pipeline_state.gs = VK_NULL_HANDLE; + m_pipeline_state.ps = VK_NULL_HANDLE; +} + +void StateTracker::ReloadPipelineUIDCache() { class PipelineInserter final : public LinearDiskCacheReader { @@ -130,7 +143,8 @@ void StateTracker::LoadPipelineUIDCache() StateTracker* this_ptr; }; - std::string filename = g_object_cache->GetDiskCacheFileName("pipeline-uid"); + // UID caches don't contain any host state, so use a single uid cache per gameid. + std::string filename = g_object_cache->GetDiskCacheFileName("pipeline-uid", true, false); PipelineInserter inserter(this); // OpenAndRead calls Close() first, which will flush all data to disk when reloading. diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.h b/Source/Core/VideoBackends/Vulkan/StateTracker.h index 0b16d52661..ef09608fe8 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.h +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.h @@ -117,7 +117,10 @@ public: bool IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const; // Reloads the UID cache, ensuring all pipelines used by the game so far have been created. - void LoadPipelineUIDCache(); + void ReloadPipelineUIDCache(); + + // Clears shader pointers, ensuring that now-deleted modules are not used. + void InvalidateShaderPointers(); private: // Serialized version of PipelineInfo, used when loading/saving the pipeline UID cache.