From 5420d699e2ef0b72f96ca3647fa8177b84976a27 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 12 Sep 2026 23:59:34 +1000 Subject: [PATCH] GPUDevice: Use ObjectArchive for shader cache --- src/util/CMakeLists.txt | 2 - src/util/d3d11_device.cpp | 7 + src/util/d3d11_device.h | 2 + src/util/d3d12_device.cpp | 7 + src/util/d3d12_device.h | 2 + src/util/gpu_device.cpp | 99 ++++++++-- src/util/gpu_device.h | 13 +- src/util/gpu_shader_cache.cpp | 330 ---------------------------------- src/util/gpu_shader_cache.h | 83 --------- src/util/gpu_types.h | 12 ++ src/util/metal_device.h | 2 + src/util/metal_device.mm | 11 +- src/util/opengl_device.cpp | 18 +- src/util/opengl_device.h | 5 +- src/util/opengl_pipeline.cpp | 17 +- src/util/opengl_pipeline.h | 7 +- src/util/shadergen.cpp | 22 +-- src/util/util.vcxproj | 2 - src/util/util.vcxproj.filters | 2 - src/util/vulkan_device.cpp | 10 ++ src/util/vulkan_device.h | 2 + 21 files changed, 187 insertions(+), 468 deletions(-) delete mode 100644 src/util/gpu_shader_cache.cpp delete mode 100644 src/util/gpu_shader_cache.h diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 4173ad709..7c188705a 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -42,8 +42,6 @@ add_library(util gpu_device.cpp gpu_device.h gpu_framebuffer_manager.h - gpu_shader_cache.cpp - gpu_shader_cache.h gpu_texture.cpp gpu_texture.h gpu_types.h diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index 52a7064e5..e2b8ae1b8 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -209,6 +209,13 @@ void D3D11Device::SetFeatures(CreateFlags create_flags) SupportsTextureFormat(GPUTextureFormat::BC7)); } +u16 D3D11Device::GetShaderCacheVersion() const +{ + // Incorporate feature bits into the archive version so that device capability changes don't load the wrong shaders. + return Truncate16(m_render_api_version) | (BoolToUInt16(m_features.dual_source_blend) << 15) | + (BoolToUInt16(m_features.texture_buffers) << 14) | (BoolToUInt16(m_features.raster_order_views) << 13); +} + D3D11SwapChain::D3D11SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode) : GPUSwapChain(wi, vsync_mode) diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index 19febd25c..7bb323c5d 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -124,6 +124,8 @@ protected: std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; + u16 GetShaderCacheVersion() const override; + private: using BlendStateMapKey = std::pair; struct BlendStateMapHash diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index ce59a0257..29cf73d95 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -1376,6 +1376,13 @@ void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, CreateFlags creat SupportsTextureFormat(GPUTextureFormat::BC7)); } +u16 D3D12Device::GetShaderCacheVersion() const +{ + // Incorporate feature bits into the archive version so that device capability changes don't load the wrong shaders. + return Truncate16(m_render_api_version) | (BoolToUInt16(m_features.dual_source_blend) << 15) | + (BoolToUInt16(m_features.texture_buffers) << 14) | (BoolToUInt16(m_features.raster_order_views) << 13); +} + void D3D12Device::CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src, u32 src_x, u32 src_y, u32 src_layer, u32 src_level, u32 width, u32 height) diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index 4677c47ee..afa925719 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -207,6 +207,8 @@ protected: std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; + u16 GetShaderCacheVersion() const override; + bool ReadPipelineCache(DynamicHeapArray data, Error* error) override; bool CreatePipelineCache(const std::string& path, Error* error) override; bool GetPipelineCacheData(DynamicHeapArray* data, Error* error) override; diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 6ae3df09d..f2cf2f6ae 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -11,10 +11,12 @@ #include "shadergen.h" #include "common/assert.h" +#include "common/bitutils.h" #include "common/error.h" #include "common/file_system.h" #include "common/hash_combine.h" #include "common/log.h" +#include "common/md5_digest.h" #include "common/path.h" #include "common/scoped_guard.h" #include "common/sha1_digest.h" @@ -45,6 +47,11 @@ LOG_CHANNEL(GPUDevice); #include "vulkan_loader.h" #endif +static_assert(sizeof(GPUShaderCacheKey) == 48, "Shader cache key has no padding"); +static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v); +static_assert(sizeof(GPUPipeline::GraphicsConfig::color_formats) == + sizeof(GPUTextureFormat) * GPUDevice::MAX_RENDER_TARGETS); + std::unique_ptr g_gpu_device; namespace { @@ -56,7 +63,6 @@ struct Locals std::array pipeline_cache_hash; // Dynamic libraries - }; } // namespace @@ -375,6 +381,44 @@ const char* GPUDevice::ShaderLanguageToString(GPUShaderLanguage language) } } +ALWAYS_INLINE static ObjectArchive::KeySpan GetShaderCacheKeySpan(const GPUShaderCacheKey& key) +{ + return ObjectArchive::KeySpan(reinterpret_cast(&key), sizeof(key)); +} + +GPUShaderCacheKey GPUDevice::GetShaderCacheKey(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view shader_code, std::string_view entry_point) +{ + union + { + struct + { + u64 hash_low; + u64 hash_high; + }; + u8 hash[16]; + } h; + + GPUShaderCacheKey key = {}; + key.shader_type = static_cast(stage); + key.shader_language = static_cast(language); + + MD5Digest digest; + digest.Update(shader_code.data(), static_cast(shader_code.length())); + digest.Final(h.hash); + key.source_hash_low = h.hash_low; + key.source_hash_high = h.hash_high; + key.source_length = static_cast(shader_code.length()); + + digest.Reset(); + digest.Update(entry_point.data(), static_cast(entry_point.length())); + digest.Final(h.hash); + key.entry_point_low = h.hash_low; + key.entry_point_high = h.hash_high; + + return key; +} + const char* GPUDevice::VSyncModeToString(GPUVSyncMode mode) { static constexpr std::array(GPUVSyncMode::Count)> vsync_modes = {{ @@ -531,16 +575,24 @@ void GPUDevice::DestroyMainSwapChain() void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version) { + DebugAssert(version <= std::numeric_limits::max()); + const u16 backend_version = GetShaderCacheVersion(); + const u32 archive_version = (ZeroExtend32(backend_version) << 16) | ZeroExtend32(Truncate16(version)); + if (m_features.shader_cache && !base_path.empty()) { const std::string basename = GetShaderCacheBaseName("shaders"); const std::string filename = Path::Combine(base_path, basename); - if (!m_shader_cache.Open(filename.c_str(), m_render_api_version, version)) + + Error error; + bool was_invalidated = false; + if (!m_shader_cache.OpenPath(filename, archive_version, &error, &was_invalidated)) { - WARNING_LOG("Failed to open shader cache. Creating new cache."); - if (!m_shader_cache.Create()) - ERROR_LOG("Failed to create new shader cache."); + WARNING_LOG("Failed to open shader cache '{}': {}", Path::GetFileName(filename), error.GetDescription()); + } + if (was_invalidated) + { // Squish the pipeline cache too, it's going to be stale. if (m_features.pipeline_cache) { @@ -554,11 +606,6 @@ void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version) } } } - else - { - // Still need to set the version - GL needs it. - m_shader_cache.Open(std::string_view(), m_render_api_version, version); - } s_locals.pipeline_cache_path = {}; s_locals.pipeline_cache_size = 0; @@ -571,7 +618,7 @@ void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version) Path::Combine(base_path, TinyString::from_format("{}.bin", GetShaderCacheBaseName("pipelines"))); if (FileSystem::FileExists(s_locals.pipeline_cache_path.c_str())) { - if (OpenPipelineCache(s_locals.pipeline_cache_path, &error)) + if (OpenPipelineCache(s_locals.pipeline_cache_path, archive_version, &error)) return; WARNING_LOG("Failed to read pipeline cache '{}': {}", Path::GetFileName(s_locals.pipeline_cache_path), @@ -614,7 +661,7 @@ std::string GPUDevice::GetShaderCacheBaseName(std::string_view type) const return fmt::format("{}_{}{}", lower_api_name, type, debug_suffix); } -bool GPUDevice::OpenPipelineCache(const std::string& path, Error* error) +bool GPUDevice::OpenPipelineCache(const std::string& path, u32 version, Error* error) { CompressHelpers::OptionalByteBuffer data = CompressHelpers::DecompressFile(CompressHelpers::CompressType::Zstandard, path.c_str(), std::nullopt, error); @@ -790,8 +837,9 @@ std::unique_ptr GPUDevice::CreateShader(GPUShaderStage stage, GPUShad return shader; } - const GPUShaderCache::CacheIndexKey key = m_shader_cache.GetCacheKey(stage, language, source, entry_point); - std::optional binary = m_shader_cache.Lookup(key); + const GPUShaderCacheKey key = GetShaderCacheKey(stage, language, source, entry_point); + Error lookup_error; + std::optional binary = m_shader_cache.Lookup(GetShaderCacheKeySpan(key), &lookup_error); if (binary.has_value()) { shader = CreateShaderFromBinary(stage, binary->cspan(), error); @@ -799,20 +847,35 @@ std::unique_ptr GPUDevice::CreateShader(GPUShaderStage stage, GPUShad return shader; ERROR_LOG("Failed to create shader from binary (driver changed?). Clearing cache."); - m_shader_cache.Clear(); + Error clear_error; + if (!m_shader_cache.Clear(&clear_error)) + ERROR_LOG("Failed to clear shader cache: {}", clear_error.GetDescription()); binary.reset(); } + else if (lookup_error.GetDescription() != ObjectArchive::ERROR_DESCRIPTION_DOES_NOT_EXIST) [[unlikely]] + { + ERROR_LOG("Failed to read cached {} shader: {}", GPUShader::GetStageName(stage), lookup_error.GetDescription()); + + Error clear_error; + if (!m_shader_cache.Clear(&clear_error)) + ERROR_LOG("Failed to clear shader cache: {}", clear_error.GetDescription()); + } - GPUShaderCache::ShaderBinary new_binary; + DynamicHeapArray new_binary; shader = CreateShaderFromSource(stage, language, source, entry_point, &new_binary, error); if (!shader) return shader; // Don't insert empty shaders into the cache... - if (!new_binary.empty()) + if (!new_binary.empty() && m_shader_cache.IsOpen()) { - if (!m_shader_cache.Insert(key, new_binary.data(), static_cast(new_binary.size()))) + Error insert_error; + if (!m_shader_cache.Insert(GetShaderCacheKeySpan(key), new_binary.cspan(), ObjectArchive::CompressType::Zstandard, + &insert_error)) + { + ERROR_LOG("Failed to cache {} shader: {}", GPUShader::GetStageName(stage), insert_error.GetDescription()); m_shader_cache.Close(); + } } return shader; diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index b434998ef..4f048c750 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -3,8 +3,8 @@ #pragma once -#include "gpu_shader_cache.h" #include "gpu_texture.h" +#include "object_archive.h" #include "window_info.h" #include "common/bitfield.h" @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -612,7 +613,6 @@ public: static constexpr u32 UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024; static constexpr u32 SMALL_TEXTURE_BUFFER_SIZE = 16 * 1024 * 1024; static constexpr u32 LARGE_TEXTURE_BUFFER_SIZE = 64 * 1024 * 1024; - static_assert(sizeof(GPUPipeline::GraphicsConfig::color_formats) == sizeof(GPUTextureFormat) * MAX_RENDER_TARGETS); GPUDevice(); virtual ~GPUDevice(); @@ -626,6 +626,10 @@ public: /// Returns a string representing the specified language. static const char* ShaderLanguageToString(GPUShaderLanguage language); + /// Returns the cache key for a shader. + static GPUShaderCacheKey GetShaderCacheKey(GPUShaderStage stage, GPUShaderLanguage language, + std::string_view shader_code, std::string_view entry_point); + /// Returns a string representing the specified vsync mode. static const char* VSyncModeToString(GPUVSyncMode mode); @@ -873,7 +877,8 @@ protected: virtual void DestroyDevice() = 0; std::string GetShaderCacheBaseName(std::string_view type) const; - virtual bool OpenPipelineCache(const std::string& path, Error* error); + virtual u16 GetShaderCacheVersion() const = 0; + virtual bool OpenPipelineCache(const std::string& path, u32 version, Error* error); virtual bool CreatePipelineCache(const std::string& path, Error* error); virtual bool ReadPipelineCache(DynamicHeapArray data, Error* error); virtual bool GetPipelineCacheData(DynamicHeapArray* data, Error* error); @@ -913,7 +918,7 @@ protected: GPUSampler* m_nearest_sampler = nullptr; GPUSampler* m_linear_sampler = nullptr; - GPUShaderCache m_shader_cache; + ObjectArchive m_shader_cache; private: static constexpr u32 MAX_TEXTURE_POOL_SIZE = 125; diff --git a/src/util/gpu_shader_cache.cpp b/src/util/gpu_shader_cache.cpp deleted file mode 100644 index 4e3caf4a3..000000000 --- a/src/util/gpu_shader_cache.cpp +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: CC-BY-NC-ND-4.0 - -#include "gpu_shader_cache.h" -#include "gpu_device.h" - -#include "common/error.h" -#include "common/file_system.h" -#include "common/heap_array.h" -#include "common/log.h" -#include "common/md5_digest.h" -#include "common/path.h" - -#include "fmt/format.h" - -#include "compress_helpers.h" - -LOG_CHANNEL(GPUDevice); - -struct CacheFileHeader -{ - u32 signature; - u32 render_api_version; - u32 cache_version; -}; -static_assert(sizeof(CacheFileHeader) == 12, "Cache file header has no padding"); - -static constexpr u32 EXPECTED_SIGNATURE = 0x434B5544; // DUKC - -static constexpr size_t KEY_COPY_SIZE = offsetof(GPUShaderCache::CacheIndexKey, unused); - -template -ALWAYS_INLINE static int CompareEntries(const A& a, const B& b) -{ - // don't compare file fields when looking up - return std::memcmp(&a, &b, KEY_COPY_SIZE); -} - -GPUShaderCache::GPUShaderCache() -{ - static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v, - "Cache key must be trivially copyable"); - static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v, - "Cache entry must be trivially copyable"); - static_assert(offsetof(CacheIndexKey, shader_type) == offsetof(CacheIndexEntry, shader_type) && - offsetof(CacheIndexKey, shader_language) == offsetof(CacheIndexEntry, shader_language) && - offsetof(CacheIndexKey, source_hash_low) == offsetof(CacheIndexEntry, source_hash_low) && - offsetof(CacheIndexKey, source_hash_high) == offsetof(CacheIndexEntry, source_hash_high) && - offsetof(CacheIndexKey, entry_point_low) == offsetof(CacheIndexEntry, entry_point_low) && - offsetof(CacheIndexKey, entry_point_high) == offsetof(CacheIndexEntry, entry_point_high) && - offsetof(CacheIndexKey, source_length) == offsetof(CacheIndexEntry, source_length), - "Cache key and entry must have matching layout"); -} - -GPUShaderCache::~GPUShaderCache() -{ - Close(); -} - -bool GPUShaderCache::Open(std::string_view base_filename, u32 render_api_version, u32 cache_version) -{ - m_base_filename = base_filename; - m_render_api_version = render_api_version; - m_version = cache_version; - - if (base_filename.empty()) - return true; - - const std::string index_filename = fmt::format("{}.idx", m_base_filename); - const std::string blob_filename = fmt::format("{}.bin", m_base_filename); - return ReadExisting(index_filename, blob_filename); -} - -bool GPUShaderCache::Create() -{ - const std::string index_filename = fmt::format("{}.idx", m_base_filename); - const std::string blob_filename = fmt::format("{}.bin", m_base_filename); - return CreateNew(index_filename, blob_filename); -} - -void GPUShaderCache::Close() -{ - if (m_index_file) - { - std::fclose(m_index_file); - m_index_file = nullptr; - } - if (m_blob_file) - { - std::fclose(m_blob_file); - m_blob_file = nullptr; - } -} - -void GPUShaderCache::Clear() -{ - if (!IsOpen()) - return; - - Close(); - - WARNING_LOG("Clearing shader cache at {}.", Path::GetFileName(m_base_filename)); - - const std::string index_filename = fmt::format("{}.idx", m_base_filename); - const std::string blob_filename = fmt::format("{}.bin", m_base_filename); - CreateNew(index_filename, blob_filename); -} - -bool GPUShaderCache::CreateNew(const std::string& index_filename, const std::string& blob_filename) -{ - if (FileSystem::FileExists(index_filename.c_str())) - { - WARNING_LOG("Removing existing index file '{}'", Path::GetFileName(index_filename)); - FileSystem::DeleteFile(index_filename.c_str()); - } - if (FileSystem::FileExists(blob_filename.c_str())) - { - WARNING_LOG("Removing existing blob file '{}'", Path::GetFileName(blob_filename)); - FileSystem::DeleteFile(blob_filename.c_str()); - } - - m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb"); - if (!m_index_file) [[unlikely]] - { - ERROR_LOG("Failed to open index file '{}' for writing", Path::GetFileName(index_filename)); - return false; - } - - const CacheFileHeader file_header = { - .signature = EXPECTED_SIGNATURE, .render_api_version = m_render_api_version, .cache_version = m_version}; - if (std::fwrite(&file_header, sizeof(file_header), 1, m_index_file) != 1) [[unlikely]] - { - ERROR_LOG("Failed to write version to index file '{}'", Path::GetFileName(index_filename)); - std::fclose(m_index_file); - m_index_file = nullptr; - FileSystem::DeleteFile(index_filename.c_str()); - return false; - } - - m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b"); - if (!m_blob_file) [[unlikely]] - { - ERROR_LOG("Failed to open blob file '{}' for writing", Path::GetFileName(blob_filename)); - std::fclose(m_index_file); - m_index_file = nullptr; - FileSystem::DeleteFile(index_filename.c_str()); - return false; - } - - return true; -} - -bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std::string& blob_filename) -{ - m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b"); - if (!m_index_file) - { - // special case here: when there's a sharing violation (i.e. two instances running), - // we don't want to blow away the cache. so just continue without a cache. - if (errno == EACCES) - { - WARNING_LOG("Failed to open shader cache index with EACCES, are you running two instances?"); - return true; - } - - return false; - } - - CacheFileHeader file_header; - if (std::fread(&file_header, sizeof(file_header), 1, m_index_file) != 1 || - file_header.signature != EXPECTED_SIGNATURE || file_header.render_api_version != m_render_api_version || - file_header.cache_version != m_version) [[unlikely]] - { - ERROR_LOG("Bad file/data version in '{}'", Path::GetFileName(index_filename)); - std::fclose(m_index_file); - m_index_file = nullptr; - return false; - } - - m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b"); - if (!m_blob_file) [[unlikely]] - { - ERROR_LOG("Blob file '{}' is missing", Path::GetFileName(blob_filename)); - std::fclose(m_index_file); - m_index_file = nullptr; - return false; - } - - const s64 start_pos = FileSystem::FTell64(m_index_file); - s64 end_pos; - if (start_pos < 0 || !FileSystem::FSeek64(m_index_file, 0, SEEK_END, nullptr) || - (end_pos = FileSystem::FTell64(m_index_file)) < 0 || - !FileSystem::FSeek64(m_index_file, start_pos, SEEK_SET, nullptr) || - ((end_pos - start_pos) % sizeof(CacheIndexEntry)) != 0) [[unlikely]] - { - ERROR_LOG("Failed to seek in index file '{}'", Path::GetFileName(index_filename)); - std::fclose(m_blob_file); - m_blob_file = nullptr; - std::fclose(m_index_file); - m_index_file = nullptr; - return false; - } - - const size_t num_entries = static_cast((end_pos - start_pos) / sizeof(CacheIndexEntry)); - m_index.resize(num_entries); - - if (std::fread(m_index.data(), sizeof(CacheIndexEntry), num_entries, m_index_file) != num_entries) [[unlikely]] - { - ERROR_LOG("Failed to read entries from index file '{}'", Path::GetFileName(index_filename)); - m_index.clear(); - std::fclose(m_blob_file); - m_blob_file = nullptr; - std::fclose(m_index_file); - m_index_file = nullptr; - return false; - } - - // ensure we don't write before seeking - FileSystem::FSeek64(m_index_file, 0, SEEK_END); - - // the index won't be sorted initially, since the file is append only - std::ranges::sort(m_index, - [](const CacheIndexEntry& a, const CacheIndexEntry& b) { return (CompareEntries(a, b) < 0); }); - - DEV_LOG("Read {} entries from '{}'", m_index.size(), Path::GetFileName(index_filename)); - return true; -} - -GPUShaderCache::CacheIndexKey GPUShaderCache::GetCacheKey(GPUShaderStage stage, GPUShaderLanguage language, - std::string_view shader_code, std::string_view entry_point) -{ - union - { - struct - { - u64 hash_low; - u64 hash_high; - }; - u8 hash[16]; - } h; - - CacheIndexKey key; - key.shader_type = static_cast(stage); - key.shader_language = static_cast(language); - - MD5Digest digest; - digest.Update(shader_code.data(), static_cast(shader_code.length())); - digest.Final(h.hash); - key.source_hash_low = h.hash_low; - key.source_hash_high = h.hash_high; - key.source_length = static_cast(shader_code.length()); - - digest.Reset(); - digest.Update(entry_point.data(), static_cast(entry_point.length())); - digest.Final(h.hash); - key.entry_point_low = h.hash_low; - key.entry_point_high = h.hash_high; - - return key; -} - -std::optional GPUShaderCache::Lookup(const CacheIndexKey& key) -{ - std::optional ret; - - const auto iter = - std::lower_bound(m_index.begin(), m_index.end(), key, - [](const CacheIndexEntry& a, const CacheIndexKey& b) { return (CompareEntries(a, b) < 0); }); - if (iter != m_index.end() && CompareEntries(*iter, key) == 0) - { - DynamicHeapArray compressed_data(iter->compressed_size); - - if (std::fseek(m_blob_file, iter->file_offset, SEEK_SET) != 0 || - std::fread(compressed_data.data(), iter->compressed_size, 1, m_blob_file) != 1) [[unlikely]] - { - ERROR_LOG("Read {} byte {} shader from file failed", iter->compressed_size, - GPUShader::GetStageName(static_cast(key.shader_type))); - } - else - { - Error error; - ret = CompressHelpers::DecompressBuffer(CompressHelpers::CompressType::Zstandard, - CompressHelpers::OptionalByteBuffer(std::move(compressed_data)), - iter->uncompressed_size, &error); - if (!ret.has_value()) [[unlikely]] - ERROR_LOG("Failed to decompress shader: {}", error.GetDescription()); - } - } - - return ret; -} - -bool GPUShaderCache::Insert(const CacheIndexKey& key, const void* data, u32 data_size) -{ - Error error; - CompressHelpers::OptionalByteBuffer compress_buffer = - CompressHelpers::CompressToBuffer(CompressHelpers::CompressType::Zstandard, data, data_size, -1, &error); - if (!compress_buffer.has_value()) [[unlikely]] - { - ERROR_LOG("Failed to compress shader: {}", error.GetDescription()); - return false; - } - - if (!m_blob_file || std::fseek(m_blob_file, 0, SEEK_END) != 0) - return false; - - auto iter = - std::lower_bound(m_index.begin(), m_index.end(), key, - [](const CacheIndexEntry& a, const CacheIndexKey& b) { return (CompareEntries(a, b) < 0); }); - iter = m_index.emplace(iter); - std::memcpy(&(*iter), &key, KEY_COPY_SIZE); - iter->file_offset = static_cast(std::ftell(m_blob_file)); - iter->compressed_size = static_cast(compress_buffer->size()); - iter->uncompressed_size = data_size; - - if (std::fwrite(compress_buffer->data(), compress_buffer->size(), 1, m_blob_file) != 1 || - std::fflush(m_blob_file) != 0 || std::fwrite(&(*iter), sizeof(CacheIndexEntry), 1, m_index_file) != 1 || - std::fflush(m_index_file) != 0) [[unlikely]] - { - ERROR_LOG("Failed to write {} byte {} shader blob to file", data_size, - GPUShader::GetStageName(static_cast(key.shader_type))); - m_index.erase(iter); - return false; - } - - DEV_LOG("Cached compressed {} shader: {} -> {} bytes", - GPUShader::GetStageName(static_cast(key.shader_type)), data_size, compress_buffer->size()); - - return true; -} diff --git a/src/util/gpu_shader_cache.h b/src/util/gpu_shader_cache.h deleted file mode 100644 index 5b4c42782..000000000 --- a/src/util/gpu_shader_cache.h +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: CC-BY-NC-ND-4.0 - -#pragma once - -#include "common/heap_array.h" -#include "common/types.h" - -#include -#include -#include -#include - -enum class GPUShaderStage : u8; -enum class GPUShaderLanguage : u8; - -class GPUShaderCache -{ -public: - using ShaderBinary = DynamicHeapArray; - - struct CacheIndexKey - { - u32 shader_type; - u32 shader_language; - u64 source_hash_low; - u64 source_hash_high; - u64 entry_point_low; - u64 entry_point_high; - u32 source_length; - u32 unused; - }; - static_assert(sizeof(CacheIndexKey) == 48, "Cache index key has no padding"); - - GPUShaderCache(); - ~GPUShaderCache(); - - ALWAYS_INLINE const std::string& GetBaseFilename() const { return m_base_filename; } - ALWAYS_INLINE u32 GetVersion() const { return m_version; } - - bool IsOpen() const { return (m_index_file != nullptr); } - - bool Open(std::string_view base_filename, u32 render_api_version, u32 cache_version); - bool Create(); - void Close(); - - static CacheIndexKey GetCacheKey(GPUShaderStage stage, GPUShaderLanguage language, std::string_view shader_code, - std::string_view entry_point); - - std::optional Lookup(const CacheIndexKey& key); - bool Insert(const CacheIndexKey& key, const void* data, u32 data_size); - void Clear(); - -private: - struct CacheIndexEntry - { - u32 shader_type; - u32 shader_language; - u64 source_hash_low; - u64 source_hash_high; - u64 entry_point_low; - u64 entry_point_high; - u32 source_length; - u32 file_offset; - u32 compressed_size; - u32 uncompressed_size; - }; - static_assert(sizeof(CacheIndexEntry) == 56, "Cache index entry has no padding"); - - using CacheIndex = std::vector; - - bool CreateNew(const std::string& index_filename, const std::string& blob_filename); - bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); - - CacheIndex m_index; - - std::string m_base_filename; - u32 m_render_api_version = 0; - u32 m_version = 0; - - std::FILE* m_index_file = nullptr; - std::FILE* m_blob_file = nullptr; -}; diff --git a/src/util/gpu_types.h b/src/util/gpu_types.h index c8f812aa9..79258f1f3 100644 --- a/src/util/gpu_types.h +++ b/src/util/gpu_types.h @@ -119,3 +119,15 @@ enum class GPUPresentResult : u8 ExclusiveFullscreenLost, DeviceLost, }; + +struct GPUShaderCacheKey +{ + u32 shader_type; + u32 shader_language; + u64 source_hash_low; + u64 source_hash_high; + u64 entry_point_low; + u64 entry_point_high; + u32 source_length; + u32 unused; +}; diff --git a/src/util/metal_device.h b/src/util/metal_device.h index 3f1736b16..4859eb6f5 100644 --- a/src/util/metal_device.h +++ b/src/util/metal_device.h @@ -344,6 +344,8 @@ protected: std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; + u16 GetShaderCacheVersion() const override; + private: static constexpr u32 UNIFORM_BUFFER_ALIGNMENT = 256; static constexpr u8 NUM_TIMESTAMP_QUERIES = 3; diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 3cfc28270..51b969f8e 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "metal_device.h" #include "common/align.h" #include "common/assert.h" +#include "common/bitutils.h" #include "common/cocoa_tools.h" #include "common/error.h" #include "common/file_system.h" @@ -417,6 +418,14 @@ void MetalDevice::SetFeatures(CreateFlags create_flags) !HasCreateFlag(create_flags, CreateFlags::DisableCompressedTextures) && m_device.supportsBCTextureCompression; } +u16 MetalDevice::GetShaderCacheVersion() const +{ + // Incorporate feature bits into the archive version so that device capability changes don't load the wrong shaders. + const bool barriers = (!m_features.framebuffer_fetch && m_features.feedback_loops); + return (BoolToUInt16(m_features.dual_source_blend) << 15) | (BoolToUInt16(m_features.framebuffer_fetch) << 14) | + (BoolToUInt16(m_features.texture_buffers) << 13) | (BoolToUInt16(barriers) << 12); +} + bool MetalDevice::LoadShaders(Error* error) { @autoreleasepool diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp index fc0ce4093..dfbc46d25 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -1,15 +1,17 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "opengl_device.h" #include "opengl_pipeline.h" #include "opengl_stream_buffer.h" #include "opengl_texture.h" +#include "shadergen.h" #include "core/host.h" #include "common/align.h" #include "common/assert.h" +#include "common/bitutils.h" #include "common/error.h" #include "common/log.h" #include "common/string_util.h" @@ -515,6 +517,20 @@ bool OpenGLDevice::CheckFeatures(CreateFlags create_flags) return true; } +u16 OpenGLDevice::GetShaderCacheVersion() const +{ + // Incorporate feature bits into the archive version so that device capability changes don't load the wrong shaders. + const u32 glsl_version = ShaderGen::GetGLSLVersion(m_render_api); + const bool glsl_interface_blocks = ShaderGen::UseGLSLInterfaceBlocks(); + const bool glsl_binding_layout = ShaderGen::UseGLSLBindingLayout(); + DebugAssert(glsl_version <= ((1u << 10) - 1)); + + return Truncate16(glsl_version) | (BoolToUInt16(m_features.dual_source_blend) << 15) | + (BoolToUInt16(m_features.framebuffer_fetch) << 14) | (BoolToUInt16(m_features.texture_buffers) << 13) | + (BoolToUInt16(m_features.texture_buffers_emulated_with_ssbo) << 12) | + (BoolToUInt16(glsl_interface_blocks) << 11) | (BoolToUInt16(glsl_binding_layout) << 10); +} + void OpenGLDevice::DestroyDevice() { if (!m_gl_context) diff --git a/src/util/opengl_device.h b/src/util/opengl_device.h index 761e1cc4a..cf3e58929 100644 --- a/src/util/opengl_device.h +++ b/src/util/opengl_device.h @@ -5,7 +5,6 @@ #include "gpu_device.h" #include "gpu_framebuffer_manager.h" -#include "gpu_shader_cache.h" #include "opengl_context.h" #include "opengl_loader.h" #include "opengl_pipeline.h" @@ -151,7 +150,8 @@ protected: std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; - bool OpenPipelineCache(const std::string& path, Error* error) override; + u16 GetShaderCacheVersion() const override; + bool OpenPipelineCache(const std::string& path, u32 version, Error* error) override; bool CreatePipelineCache(const std::string& path, Error* error) override; bool ClosePipelineCache(const std::string& path, Error* error) override; @@ -240,6 +240,7 @@ private: FileSystem::POSIXLock m_pipeline_disk_cache_file_lock; #endif u32 m_pipeline_disk_cache_data_end = 0; + u32 m_pipeline_disk_cache_version = 0; bool m_pipeline_disk_cache_changed = false; bool m_disable_pbo = false; diff --git a/src/util/opengl_pipeline.cpp b/src/util/opengl_pipeline.cpp index 3172f20a7..2bdd2ab18 100644 --- a/src/util/opengl_pipeline.cpp +++ b/src/util/opengl_pipeline.cpp @@ -91,7 +91,7 @@ static void FillFooter(PipelineDiskCacheFooter* footer, u32 version) std::size(footer->driver_version)); } -OpenGLShader::OpenGLShader(GPUShaderStage stage, const GPUShaderCache::CacheIndexKey& key, std::string source) +OpenGLShader::OpenGLShader(GPUShaderStage stage, const GPUShaderCacheKey& key, std::string source) : GPUShader(stage), m_key(key), m_source(std::move(source)) { } @@ -221,7 +221,7 @@ std::unique_ptr OpenGLDevice::CreateShaderFromSource(GPUShaderStage s } return std::unique_ptr( - new OpenGLShader(stage, GPUShaderCache::GetCacheKey(stage, language, source, entry_point), std::string(source))); + new OpenGLShader(stage, GetShaderCacheKey(stage, language, source, entry_point), std::string(source))); } ////////////////////////////////////////////////////////////////////////// @@ -272,9 +272,9 @@ OpenGLPipeline::ProgramCacheKey OpenGLPipeline::GetProgramCacheKey(const Graphic { Assert(plconfig.input_layout.vertex_attributes.size() <= MAX_VERTEX_ATTRIBUTES); - const GPUShaderCache::CacheIndexKey& vs_key = static_cast(plconfig.vertex_shader)->GetKey(); - const GPUShaderCache::CacheIndexKey& fs_key = static_cast(plconfig.fragment_shader)->GetKey(); - const GPUShaderCache::CacheIndexKey* gs_key = + const GPUShaderCacheKey& vs_key = static_cast(plconfig.vertex_shader)->GetKey(); + const GPUShaderCacheKey& fs_key = static_cast(plconfig.fragment_shader)->GetKey(); + const GPUShaderCacheKey* gs_key = plconfig.geometry_shader ? &static_cast(plconfig.geometry_shader)->GetKey() : nullptr; ProgramCacheKey ret; @@ -805,9 +805,10 @@ void OpenGLDevice::SetPipeline(GPUPipeline* pipeline) } } -bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error) +bool OpenGLDevice::OpenPipelineCache(const std::string& path, u32 version, Error* error) { DebugAssert(!m_pipeline_disk_cache_file); + m_pipeline_disk_cache_version = version; auto fp = FileSystem::OpenManagedCFile(path.c_str(), "r+b", error); if (!fp) @@ -843,7 +844,7 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error) } PipelineDiskCacheFooter expected_footer; - FillFooter(&expected_footer, m_shader_cache.GetVersion()); + FillFooter(&expected_footer, version); if (file_footer.version != expected_footer.version || std::strncmp(file_footer.driver_vendor, expected_footer.driver_vendor, std::size(file_footer.driver_vendor)) != @@ -1126,7 +1127,7 @@ bool OpenGLDevice::ClosePipelineCache(const std::string& filename, Error* error) } PipelineDiskCacheFooter footer; - FillFooter(&footer, m_shader_cache.GetVersion()); + FillFooter(&footer, m_pipeline_disk_cache_version); footer.num_programs = count; if (std::fwrite(&footer, sizeof(footer), 1, m_pipeline_disk_cache_file) != 1 || diff --git a/src/util/opengl_pipeline.h b/src/util/opengl_pipeline.h index bceeb7130..b33832cb3 100644 --- a/src/util/opengl_pipeline.h +++ b/src/util/opengl_pipeline.h @@ -4,7 +4,6 @@ #pragma once #include "gpu_device.h" -#include "gpu_shader_cache.h" #include "opengl_loader.h" class OpenGLDevice; @@ -23,13 +22,13 @@ public: bool Compile(Error* error); ALWAYS_INLINE GLuint GetGLId() const { return m_id.value(); } - ALWAYS_INLINE const GPUShaderCache::CacheIndexKey& GetKey() const { return m_key; } + ALWAYS_INLINE const GPUShaderCacheKey& GetKey() const { return m_key; } ALWAYS_INLINE const std::string& GetSource() const { return m_source; } private: - OpenGLShader(GPUShaderStage stage, const GPUShaderCache::CacheIndexKey& key, std::string source); + OpenGLShader(GPUShaderStage stage, const GPUShaderCacheKey& key, std::string source); - GPUShaderCache::CacheIndexKey m_key; + GPUShaderCacheKey m_key; std::string m_source; std::optional m_id; bool m_compile_tried = false; diff --git a/src/util/shadergen.cpp b/src/util/shadergen.cpp index b82f61e85..0d91c9242 100644 --- a/src/util/shadergen.cpp +++ b/src/util/shadergen.cpp @@ -40,16 +40,6 @@ ShaderGen::ShaderGen(RenderAPI render_api, GPUShaderLanguage shader_language, bo m_use_glsl_interface_blocks = (shader_language == GPUShaderLanguage::GLSLVK); m_use_glsl_binding_layout = (shader_language == GPUShaderLanguage::GLSLVK); } - -#ifdef _WIN32 - if (m_shader_language == GPUShaderLanguage::GLSL) - { - // SSAA with interface blocks is broken on AMD's OpenGL driver. - const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); - if (std::strcmp(gl_vendor, "ATI Technologies Inc.") == 0) - m_use_glsl_interface_blocks = false; - } -#endif #else m_use_glsl_interface_blocks = true; m_use_glsl_binding_layout = true; @@ -86,7 +76,17 @@ GPUShaderLanguage ShaderGen::GetShaderLanguageForAPI(RenderAPI api) bool ShaderGen::UseGLSLInterfaceBlocks() { #ifdef ENABLE_OPENGL - return (GLAD_GL_ES_VERSION_3_2 || GLAD_GL_VERSION_3_2); + if (!GLAD_GL_ES_VERSION_3_2 && !GLAD_GL_VERSION_3_2) + return false; + +#ifdef _WIN32 + // SSAA with interface blocks is broken on AMD's OpenGL driver. + const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); + if (std::strcmp(gl_vendor, "ATI Technologies Inc.") == 0) + return false; +#endif + + return true; #else return true; #endif diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index e1a119044..1a32f8ae5 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -37,7 +37,6 @@ - @@ -157,7 +156,6 @@ - diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 290519989..4b0e98999 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -44,7 +44,6 @@ - @@ -142,7 +141,6 @@ - diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index 411cdbdfd..c75ba606b 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -2133,6 +2133,16 @@ void VulkanDevice::SetFeatures(CreateFlags create_flags, VkPhysicalDevice physic (!HasCreateFlag(create_flags, CreateFlags::DisableCompressedTextures) && vk_features.textureCompressionBC); } +u16 VulkanDevice::GetShaderCacheVersion() const +{ + // Incorporate feature bits into the archive version so that device capability changes don't load the wrong shaders. + DebugAssert(m_render_api_version <= ((1u << 10) - 1)); + return Truncate16(m_render_api_version) | (BoolToUInt16(m_features.dual_source_blend) << 15) | + (BoolToUInt16(m_features.framebuffer_fetch) << 14) | (BoolToUInt16(m_features.texture_buffers) << 13) | + (BoolToUInt16(m_features.texture_buffers_emulated_with_ssbo) << 12) | + (BoolToUInt16(m_features.feedback_loops) << 11) | (BoolToUInt16(m_features.raster_order_views) << 10); +} + void VulkanDevice::CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src, u32 src_x, u32 src_y, u32 src_layer, u32 src_level, u32 width, u32 height) diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index acbbf4b1d..cd1031df5 100644 --- a/src/util/vulkan_device.h +++ b/src/util/vulkan_device.h @@ -241,6 +241,8 @@ protected: std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; + u16 GetShaderCacheVersion() const override; + bool ReadPipelineCache(DynamicHeapArray data, Error* error) override; bool CreatePipelineCache(const std::string& path, Error* error) override; bool GetPipelineCacheData(DynamicHeapArray* data, Error* error) override;