GPUDevice: Use ObjectArchive for shader cache

pull/3797/merge
Stenzek 2 weeks ago
parent 79371c47a2
commit 5420d699e2
No known key found for this signature in database

@ -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

@ -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)

@ -124,6 +124,8 @@ protected:
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override;
u16 GetShaderCacheVersion() const override;
private:
using BlendStateMapKey = std::pair<u64, u32>;
struct BlendStateMapHash

@ -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)

@ -207,6 +207,8 @@ protected:
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override;
u16 GetShaderCacheVersion() const override;
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
bool CreatePipelineCache(const std::string& path, Error* error) override;
bool GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error) override;

@ -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<GPUShaderCacheKey> && std::is_trivially_copyable_v<GPUShaderCacheKey>);
static_assert(sizeof(GPUPipeline::GraphicsConfig::color_formats) ==
sizeof(GPUTextureFormat) * GPUDevice::MAX_RENDER_TARGETS);
std::unique_ptr<GPUDevice> g_gpu_device;
namespace {
@ -56,7 +63,6 @@ struct Locals
std::array<u8, SHA1Digest::DIGEST_SIZE> 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<const u8*>(&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<u32>(stage);
key.shader_language = static_cast<u32>(language);
MD5Digest digest;
digest.Update(shader_code.data(), static_cast<u32>(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<u32>(shader_code.length());
digest.Reset();
digest.Update(entry_point.data(), static_cast<u32>(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<const char*, static_cast<size_t>(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<u16>::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<GPUShader> GPUDevice::CreateShader(GPUShaderStage stage, GPUShad
return shader;
}
const GPUShaderCache::CacheIndexKey key = m_shader_cache.GetCacheKey(stage, language, source, entry_point);
std::optional<GPUShaderCache::ShaderBinary> binary = m_shader_cache.Lookup(key);
const GPUShaderCacheKey key = GetShaderCacheKey(stage, language, source, entry_point);
Error lookup_error;
std::optional<ObjectArchive::ObjectData> 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<GPUShader> 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<u8> 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<u32>(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;

@ -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 <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <vector>
@ -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<u8> data, Error* error);
virtual bool GetPipelineCacheData(DynamicHeapArray<u8>* 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;

@ -1,330 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// 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<typename A, typename B>
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<CacheIndexKey> && std::is_trivially_copyable_v<CacheIndexKey>,
"Cache key must be trivially copyable");
static_assert(std::is_standard_layout_v<CacheIndexEntry> && std::is_trivially_copyable_v<CacheIndexEntry>,
"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<size_t>((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<u32>(stage);
key.shader_language = static_cast<u32>(language);
MD5Digest digest;
digest.Update(shader_code.data(), static_cast<u32>(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<u32>(shader_code.length());
digest.Reset();
digest.Update(entry_point.data(), static_cast<u32>(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::ShaderBinary> GPUShaderCache::Lookup(const CacheIndexKey& key)
{
std::optional<ShaderBinary> 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<u8> 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<GPUShaderStage>(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<u32>(std::ftell(m_blob_file));
iter->compressed_size = static_cast<u32>(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<GPUShaderStage>(key.shader_type)));
m_index.erase(iter);
return false;
}
DEV_LOG("Cached compressed {} shader: {} -> {} bytes",
GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)), data_size, compress_buffer->size());
return true;
}

@ -1,83 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "common/heap_array.h"
#include "common/types.h"
#include <optional>
#include <string>
#include <string_view>
#include <vector>
enum class GPUShaderStage : u8;
enum class GPUShaderLanguage : u8;
class GPUShaderCache
{
public:
using ShaderBinary = DynamicHeapArray<u8>;
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<ShaderBinary> 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<CacheIndexEntry>;
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;
};

@ -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;
};

@ -344,6 +344,8 @@ protected:
std::optional<bool> 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;

@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// 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

@ -1,15 +1,17 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// 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)

@ -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<bool> 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;

@ -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<GPUShader> OpenGLDevice::CreateShaderFromSource(GPUShaderStage s
}
return std::unique_ptr<GPUShader>(
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<const OpenGLShader*>(plconfig.vertex_shader)->GetKey();
const GPUShaderCache::CacheIndexKey& fs_key = static_cast<const OpenGLShader*>(plconfig.fragment_shader)->GetKey();
const GPUShaderCache::CacheIndexKey* gs_key =
const GPUShaderCacheKey& vs_key = static_cast<const OpenGLShader*>(plconfig.vertex_shader)->GetKey();
const GPUShaderCacheKey& fs_key = static_cast<const OpenGLShader*>(plconfig.fragment_shader)->GetKey();
const GPUShaderCacheKey* gs_key =
plconfig.geometry_shader ? &static_cast<const OpenGLShader*>(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 ||

@ -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<GLuint> m_id;
bool m_compile_tried = false;

@ -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<const char*>(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<const char*>(glGetString(GL_VENDOR));
if (std::strcmp(gl_vendor, "ATI Technologies Inc.") == 0)
return false;
#endif
return true;
#else
return true;
#endif

@ -37,7 +37,6 @@
<ClInclude Include="dinput_source.h" />
<ClInclude Include="gpu_device.h" />
<ClInclude Include="gpu_framebuffer_manager.h" />
<ClInclude Include="gpu_shader_cache.h" />
<ClInclude Include="gpu_texture.h" />
<ClInclude Include="http_downloader.h" />
<ClInclude Include="imgui_gsvector.h" />
@ -157,7 +156,6 @@
<ClCompile Include="dinput_source.cpp" />
<ClCompile Include="elf_file.cpp" />
<ClCompile Include="gpu_device.cpp" />
<ClCompile Include="gpu_shader_cache.cpp" />
<ClCompile Include="gpu_texture.cpp" />
<ClCompile Include="http_downloader.cpp" />
<ClCompile Include="image.cpp" />

@ -44,7 +44,6 @@
<ClInclude Include="d3d12_stream_buffer.h" />
<ClInclude Include="d3d12_texture.h" />
<ClInclude Include="gpu_device.h" />
<ClInclude Include="gpu_shader_cache.h" />
<ClInclude Include="gpu_texture.h" />
<ClInclude Include="metal_device.h" />
<ClInclude Include="postprocessing_shader_glsl.h" />
@ -142,7 +141,6 @@
<ClCompile Include="d3d12_stream_buffer.cpp" />
<ClCompile Include="d3d12_texture.cpp" />
<ClCompile Include="gpu_device.cpp" />
<ClCompile Include="gpu_shader_cache.cpp" />
<ClCompile Include="gpu_texture.cpp" />
<ClCompile Include="postprocessing_shader_glsl.cpp" />
<ClCompile Include="d3d11_pipeline.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)

@ -241,6 +241,8 @@ protected:
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override;
u16 GetShaderCacheVersion() const override;
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
bool CreatePipelineCache(const std::string& path, Error* error) override;
bool GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error) override;

Loading…
Cancel
Save