From 00b83b4196b060987f34cd55286aa4f55c02530d Mon Sep 17 00:00:00 2001
From: Stenzek <stenzek@gmail.com>
Date: Tue, 16 Apr 2019 00:34:52 +1000
Subject: [PATCH] ShaderCache: Utilize pipeline cache data where possible

---
 Source/Core/VideoCommon/GXPipelineTypes.h |  10 +
 Source/Core/VideoCommon/ShaderCache.cpp   | 234 ++++++++++++++++------
 Source/Core/VideoCommon/ShaderCache.h     |  21 +-
 3 files changed, 194 insertions(+), 71 deletions(-)

diff --git a/Source/Core/VideoCommon/GXPipelineTypes.h b/Source/Core/VideoCommon/GXPipelineTypes.h
index 8414b1af7f..f20b8edac6 100644
--- a/Source/Core/VideoCommon/GXPipelineTypes.h
+++ b/Source/Core/VideoCommon/GXPipelineTypes.h
@@ -99,6 +99,16 @@ struct SerializedGXPipelineUid
   u32 depth_state_bits;
   u32 blending_state_bits;
 };
+struct SerializedGXUberPipelineUid
+{
+  PortableVertexDeclaration vertex_decl;
+  UberShader::VertexShaderUid vs_uid;
+  GeometryShaderUid gs_uid;
+  UberShader::PixelShaderUid ps_uid;
+  u32 rasterization_state_bits;
+  u32 depth_state_bits;
+  u32 blending_state_bits;
+};
 #pragma pack(pop)
 
 }  // namespace VideoCommon
diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp
index 10bec245ef..e1712ec8f3 100644
--- a/Source/Core/VideoCommon/ShaderCache.cpp
+++ b/Source/Core/VideoCommon/ShaderCache.cpp
@@ -25,8 +25,7 @@ namespace VideoCommon
 ShaderCache::ShaderCache() = default;
 ShaderCache::~ShaderCache()
 {
-  ClearShaderCaches();
-  ClearPipelineCaches();
+  ClearCaches();
 }
 
 bool ShaderCache::Initialize()
@@ -48,7 +47,7 @@ void ShaderCache::InitializeShaderCache()
   // Load shader and UID caches.
   if (g_ActiveConfig.bShaderCache && m_api_type != APIType::Nothing)
   {
-    LoadShaderCaches();
+    LoadCaches();
     LoadPipelineUIDCache();
   }
 
@@ -69,11 +68,10 @@ void ShaderCache::Reload()
 {
   WaitForAsyncCompiler();
   ClosePipelineUIDCache();
-  InvalidateCachedPipelines();
-  ClearShaderCaches();
+  ClearCaches();
 
   if (g_ActiveConfig.bShaderCache)
-    LoadShaderCaches();
+    LoadCaches();
 
   // Switch to the precompiling shader configuration while we rebuild.
   m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderPrecompilerThreads());
@@ -140,7 +138,7 @@ const AbstractPipeline* ShaderCache::GetUberPipelineForUid(const GXUberPipelineU
     return it->second.first.get();
 
   std::unique_ptr<AbstractPipeline> pipeline;
-  std::optional<AbstractPipelineConfig> pipeline_config = GetGXUberPipelineConfig(uid);
+  std::optional<AbstractPipelineConfig> pipeline_config = GetGXPipelineConfig(uid);
   if (pipeline_config)
     pipeline = g_renderer->CreatePipeline(*pipeline_config);
   return InsertGXUberPipeline(uid, std::move(pipeline));
@@ -176,8 +174,34 @@ void ShaderCache::WaitForAsyncCompiler()
   }
 }
 
+template <typename SerializedUidType, typename UidType>
+static void SerializePipelineUid(const UidType& uid, SerializedUidType& serialized_uid)
+{
+  // Convert to disk format. Ensure all padding bytes are zero.
+  std::memset(&serialized_uid, 0, sizeof(serialized_uid));
+  serialized_uid.vertex_decl = uid.vertex_format->GetVertexDeclaration();
+  serialized_uid.vs_uid = uid.vs_uid;
+  serialized_uid.gs_uid = uid.gs_uid;
+  serialized_uid.ps_uid = uid.ps_uid;
+  serialized_uid.rasterization_state_bits = uid.rasterization_state.hex;
+  serialized_uid.depth_state_bits = uid.depth_state.hex;
+  serialized_uid.blending_state_bits = uid.blending_state.hex;
+}
+
+template <typename UidType, typename SerializedUidType>
+static void UnserializePipelineUid(const SerializedUidType& uid, UidType& real_uid)
+{
+  real_uid.vertex_format = VertexLoaderManager::GetOrCreateMatchingFormat(uid.vertex_decl);
+  real_uid.vs_uid = uid.vs_uid;
+  real_uid.gs_uid = uid.gs_uid;
+  real_uid.ps_uid = uid.ps_uid;
+  real_uid.rasterization_state.hex = uid.rasterization_state_bits;
+  real_uid.depth_state.hex = uid.depth_state_bits;
+  real_uid.blending_state.hex = uid.blending_state_bits;
+}
+
 template <ShaderStage stage, typename K, typename T>
-static void LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid)
+void ShaderCache::LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid)
 {
   class CacheReader : public LinearDiskCacheReader<K, u8>
   {
@@ -219,38 +243,127 @@ static void LoadShaderCache(T& cache, APIType api_type, const char* type, bool i
 }
 
 template <typename T>
-static void ClearShaderCache(T& cache)
+void ShaderCache::ClearShaderCache(T& cache)
 {
   cache.disk_cache.Sync();
   cache.disk_cache.Close();
   cache.shader_map.clear();
 }
 
-void ShaderCache::LoadShaderCaches()
+template <typename KeyType, typename DiskKeyType, typename T>
+void ShaderCache::LoadPipelineCache(T& cache, LinearDiskCache<DiskKeyType, u8>& disk_cache,
+                                    APIType api_type, const char* type, bool include_gameid)
 {
-  // Ubershader caches, if present.
-  LoadShaderCache<ShaderStage::Vertex, UberShader::VertexShaderUid>(m_uber_vs_cache, m_api_type,
-                                                                    "uber-vs", false);
-  LoadShaderCache<ShaderStage::Pixel, UberShader::PixelShaderUid>(m_uber_ps_cache, m_api_type,
-                                                                  "uber-ps", false);
+  class CacheReader : public LinearDiskCacheReader<DiskKeyType, u8>
+  {
+  public:
+    CacheReader(ShaderCache* this_ptr_, T& cache_) : this_ptr(this_ptr_), cache(cache_) {}
+    bool AnyFailed() const { return failed; }
+    void Read(const DiskKeyType& key, const u8* value, u32 value_size)
+    {
+      KeyType real_uid;
+      UnserializePipelineUid(key, real_uid);
 
-  // We also share geometry shaders, as there aren't many variants.
-  if (m_host_config.backend_geometry_shaders)
-    LoadShaderCache<ShaderStage::Geometry, GeometryShaderUid>(m_gs_cache, m_api_type, "gs", false);
+      // Skip those which are already compiled.
+      if (failed || cache.find(real_uid) != cache.end())
+        return;
 
-  // Specialized shaders, gameid-specific.
-  LoadShaderCache<ShaderStage::Vertex, VertexShaderUid>(m_vs_cache, m_api_type, "specialized-vs",
-                                                        true);
-  LoadShaderCache<ShaderStage::Pixel, PixelShaderUid>(m_ps_cache, m_api_type, "specialized-ps",
-                                                      true);
+      auto config = this_ptr->GetGXPipelineConfig(real_uid);
+      if (!config)
+        return;
+
+      auto pipeline = g_renderer->CreatePipeline(*config, value, value_size);
+      if (!pipeline)
+      {
+        // If any of the pipelines fail to create, consider the cache stale.
+        failed = true;
+        return;
+      }
+
+      auto& entry = cache[real_uid];
+      entry.first = std::move(pipeline);
+      entry.second = false;
+    }
+
+  private:
+    ShaderCache* this_ptr;
+    T& cache;
+    bool failed = false;
+  };
+
+  std::string filename = GetDiskShaderCacheFileName(api_type, type, include_gameid, true);
+  CacheReader reader(this, cache);
+  u32 count = disk_cache.OpenAndRead(filename, reader);
+  INFO_LOG(VIDEO, "Loaded %u cached pipelines from %s", count, filename.c_str());
+
+  // If any of the pipelines in the cache failed to create, it's likely because of a change of
+  // driver version, or system configuration. In this case, when the UID cache picks up the pipeline
+  // later on, we'll write a duplicate entry to the pipeline cache. There's also no point in keeping
+  // the old cache data around, so discard and recreate the disk cache.
+  if (reader.AnyFailed())
+  {
+    WARN_LOG(VIDEO, "Failed to load one or more pipelines from cache '%s'. Discarding.",
+             filename.c_str());
+    disk_cache.Close();
+    File::Delete(filename);
+    disk_cache.OpenAndRead(filename, reader);
+  }
 }
 
-void ShaderCache::ClearShaderCaches()
+template <typename T, typename Y>
+void ShaderCache::ClearPipelineCache(T& cache, Y& disk_cache)
 {
+  disk_cache.Sync();
+  disk_cache.Close();
+
+  // Set the pending flag to false, and destroy the pipeline.
+  for (auto& it : cache)
+  {
+    it.second.first.reset();
+    it.second.second = false;
+  }
+}
+
+void ShaderCache::LoadCaches()
+{
+  // Ubershader caches, if present.
+  if (g_ActiveConfig.backend_info.bSupportsShaderBinaries)
+  {
+    LoadShaderCache<ShaderStage::Vertex, UberShader::VertexShaderUid>(m_uber_vs_cache, m_api_type,
+                                                                      "uber-vs", false);
+    LoadShaderCache<ShaderStage::Pixel, UberShader::PixelShaderUid>(m_uber_ps_cache, m_api_type,
+                                                                    "uber-ps", false);
+
+    // We also share geometry shaders, as there aren't many variants.
+    if (m_host_config.backend_geometry_shaders)
+      LoadShaderCache<ShaderStage::Geometry, GeometryShaderUid>(m_gs_cache, m_api_type, "gs",
+                                                                false);
+
+    // Specialized shaders, gameid-specific.
+    LoadShaderCache<ShaderStage::Vertex, VertexShaderUid>(m_vs_cache, m_api_type, "specialized-vs",
+                                                          true);
+    LoadShaderCache<ShaderStage::Pixel, PixelShaderUid>(m_ps_cache, m_api_type, "specialized-ps",
+                                                        true);
+  }
+
+  if (g_ActiveConfig.backend_info.bSupportsPipelineCacheData)
+  {
+    LoadPipelineCache<GXPipelineUid, SerializedGXPipelineUid>(
+        m_gx_pipeline_cache, m_gx_pipeline_disk_cache, m_api_type, "specialized-pipeline", true);
+    LoadPipelineCache<GXUberPipelineUid, SerializedGXUberPipelineUid>(
+        m_gx_uber_pipeline_cache, m_gx_uber_pipeline_disk_cache, m_api_type, "uber-pipeline",
+        false);
+  }
+}
+
+void ShaderCache::ClearCaches()
+{
+  ClearPipelineCache(m_gx_pipeline_cache, m_gx_pipeline_disk_cache);
   ClearShaderCache(m_vs_cache);
   ClearShaderCache(m_gs_cache);
   ClearShaderCache(m_ps_cache);
 
+  ClearPipelineCache(m_gx_uber_pipeline_cache, m_gx_uber_pipeline_disk_cache);
   ClearShaderCache(m_uber_vs_cache);
   ClearShaderCache(m_uber_ps_cache);
 
@@ -265,37 +378,16 @@ void ShaderCache::CompileMissingPipelines()
   // Queue all uids with a null pipeline for compilation.
   for (auto& it : m_gx_pipeline_cache)
   {
-    if (!it.second.second)
+    if (!it.second.first)
       QueuePipelineCompile(it.first, COMPILE_PRIORITY_SHADERCACHE_PIPELINE);
   }
   for (auto& it : m_gx_uber_pipeline_cache)
   {
-    if (!it.second.second)
+    if (!it.second.first)
       QueueUberPipelineCompile(it.first, COMPILE_PRIORITY_UBERSHADER_PIPELINE);
   }
 }
 
-void ShaderCache::InvalidateCachedPipelines()
-{
-  // Set the pending flag to false, and destroy the pipeline.
-  for (auto& it : m_gx_pipeline_cache)
-  {
-    it.second.first.reset();
-    it.second.second = false;
-  }
-  for (auto& it : m_gx_uber_pipeline_cache)
-  {
-    it.second.first.reset();
-    it.second.second = false;
-  }
-}
-
-void ShaderCache::ClearPipelineCaches()
-{
-  m_gx_pipeline_cache.clear();
-  m_gx_uber_pipeline_cache.clear();
-}
-
 std::unique_ptr<AbstractShader> ShaderCache::CompileVertexShader(const VertexShaderUid& uid) const
 {
   ShaderCode source_code = GenerateVertexShaderCode(m_api_type, m_host_config, uid.GetUidData());
@@ -505,7 +597,7 @@ std::optional<AbstractPipelineConfig> ShaderCache::GetGXPipelineConfig(const GXP
 }
 
 std::optional<AbstractPipelineConfig>
-ShaderCache::GetGXUberPipelineConfig(const GXUberPipelineUid& config)
+ShaderCache::GetGXPipelineConfig(const GXUberPipelineUid& config)
 {
   const AbstractShader* vs;
   auto vs_iter = m_uber_vs_cache.shader_map.find(config.vs_uid);
@@ -551,6 +643,18 @@ const AbstractPipeline* ShaderCache::InsertGXPipeline(const GXPipelineUid& confi
   if (!entry.first && pipeline)
     entry.first = std::move(pipeline);
 
+  if (g_ActiveConfig.bShaderCache)
+  {
+    auto cache_data = entry.first->GetCacheData();
+    if (!cache_data.empty())
+    {
+      SerializedGXPipelineUid disk_uid;
+      SerializePipelineUid(config, disk_uid);
+      m_gx_pipeline_disk_cache.Append(disk_uid, cache_data.data(),
+                                      static_cast<u32>(cache_data.size()));
+    }
+  }
+
   return entry.first.get();
 }
 
@@ -563,6 +667,18 @@ ShaderCache::InsertGXUberPipeline(const GXUberPipelineUid& config,
   if (!entry.first && pipeline)
     entry.first = std::move(pipeline);
 
+  if (g_ActiveConfig.bShaderCache)
+  {
+    auto cache_data = entry.first->GetCacheData();
+    if (!cache_data.empty())
+    {
+      SerializedGXUberPipelineUid disk_uid;
+      SerializePipelineUid(config, disk_uid);
+      m_gx_uber_pipeline_disk_cache.Append(disk_uid, cache_data.data(),
+                                           static_cast<u32>(cache_data.size()));
+    }
+  }
+
   return entry.first.get();
 }
 
@@ -648,14 +764,8 @@ void ShaderCache::ClosePipelineUIDCache()
 
 void ShaderCache::AddSerializedGXPipelineUID(const SerializedGXPipelineUid& uid)
 {
-  GXPipelineUid real_uid = {};
-  real_uid.vertex_format = VertexLoaderManager::GetOrCreateMatchingFormat(uid.vertex_decl);
-  real_uid.vs_uid = uid.vs_uid;
-  real_uid.gs_uid = uid.gs_uid;
-  real_uid.ps_uid = uid.ps_uid;
-  real_uid.rasterization_state.hex = uid.rasterization_state_bits;
-  real_uid.depth_state.hex = uid.depth_state_bits;
-  real_uid.blending_state.hex = uid.blending_state_bits;
+  GXPipelineUid real_uid;
+  UnserializePipelineUid(uid, real_uid);
 
   auto iter = m_gx_pipeline_cache.find(real_uid);
   if (iter != m_gx_pipeline_cache.end())
@@ -671,16 +781,8 @@ void ShaderCache::AppendGXPipelineUID(const GXPipelineUid& config)
   if (!m_gx_pipeline_uid_cache_file.IsOpen())
     return;
 
-  // Convert to disk format. Ensure all padding bytes are zero.
   SerializedGXPipelineUid disk_uid;
-  std::memset(&disk_uid, 0, sizeof(disk_uid));
-  disk_uid.vertex_decl = config.vertex_format->GetVertexDeclaration();
-  disk_uid.vs_uid = config.vs_uid;
-  disk_uid.gs_uid = config.gs_uid;
-  disk_uid.ps_uid = config.ps_uid;
-  disk_uid.rasterization_state_bits = config.rasterization_state.hex;
-  disk_uid.depth_state_bits = config.depth_state.hex;
-  disk_uid.blending_state_bits = config.blending_state.hex;
+  SerializePipelineUid(config, disk_uid);
   if (!m_gx_pipeline_uid_cache_file.WriteBytes(&disk_uid, sizeof(disk_uid)))
   {
     WARN_LOG(VIDEO, "Writing pipeline UID to cache failed, closing file.");
@@ -885,7 +987,7 @@ void ShaderCache::QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 pri
       // Check if all the stages required for this UberPipeline have been compiled.
       // If not, this work item becomes a no-op, and re-queues the UberPipeline for the next frame.
       if (SetStagesReady())
-        config = shader_cache->GetGXUberPipelineConfig(uid);
+        config = shader_cache->GetGXPipelineConfig(uid);
     }
 
     bool SetStagesReady()
diff --git a/Source/Core/VideoCommon/ShaderCache.h b/Source/Core/VideoCommon/ShaderCache.h
index 66caad93ac..16f6ca6f4a 100644
--- a/Source/Core/VideoCommon/ShaderCache.h
+++ b/Source/Core/VideoCommon/ShaderCache.h
@@ -111,13 +111,11 @@ private:
   static constexpr size_t NUM_PALETTE_CONVERSION_SHADERS = 3;
 
   void WaitForAsyncCompiler();
-  void LoadShaderCaches();
-  void ClearShaderCaches();
+  void LoadCaches();
+  void ClearCaches();
   void LoadPipelineUIDCache();
   void ClosePipelineUIDCache();
   void CompileMissingPipelines();
-  void InvalidateCachedPipelines();
-  void ClearPipelineCaches();
   void QueueUberShaderPipelines();
   bool CompileSharedPipelines();
 
@@ -149,7 +147,7 @@ private:
                       const RasterizationState& rasterization_state, const DepthState& depth_state,
                       const BlendingState& blending_state);
   std::optional<AbstractPipelineConfig> GetGXPipelineConfig(const GXPipelineUid& uid);
-  std::optional<AbstractPipelineConfig> GetGXUberPipelineConfig(const GXUberPipelineUid& uid);
+  std::optional<AbstractPipelineConfig> GetGXPipelineConfig(const GXUberPipelineUid& uid);
   const AbstractPipeline* InsertGXPipeline(const GXPipelineUid& config,
                                            std::unique_ptr<AbstractPipeline> pipeline);
   const AbstractPipeline* InsertGXUberPipeline(const GXUberPipelineUid& config,
@@ -165,6 +163,17 @@ private:
   void QueuePipelineCompile(const GXPipelineUid& uid, u32 priority);
   void QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 priority);
 
+  // Populating various caches.
+  template <ShaderStage stage, typename K, typename T>
+  void LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid);
+  template <typename T>
+  void ClearShaderCache(T& cache);
+  template <typename KeyType, typename DiskKeyType, typename T>
+  void LoadPipelineCache(T& cache, LinearDiskCache<DiskKeyType, u8>& disk_cache, APIType api_type,
+                         const char* type, bool include_gameid);
+  template <typename T, typename Y>
+  void ClearPipelineCache(T& cache, Y& disk_cache);
+
   // Priorities for compiling. The lower the value, the sooner the pipeline is compiled.
   // The shader cache is compiled last, as it is the least likely to be required. On demand
   // shaders are always compiled before pending ubershaders, as we want to use the ubershader
@@ -213,6 +222,8 @@ private:
   std::map<GXUberPipelineUid, std::pair<std::unique_ptr<AbstractPipeline>, bool>>
       m_gx_uber_pipeline_cache;
   File::IOFile m_gx_pipeline_uid_cache_file;
+  LinearDiskCache<SerializedGXPipelineUid, u8> m_gx_pipeline_disk_cache;
+  LinearDiskCache<SerializedGXUberPipelineUid, u8> m_gx_uber_pipeline_disk_cache;
 
   // EFB copy to VRAM/RAM pipelines
   std::map<TextureConversionShaderGen::TCShaderUid, std::unique_ptr<AbstractPipeline>>