mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Vulkan: Cache compiled SPIR-V so shaders skip glslang on later runs
GLSLtoSPV takes an optional SPIRVCache, keyed on a 32-bit hash of the source, stage and variant, plus the source length. A changed shader simply misses. thin3d's shaders and the other fixed ones use a global cache in PSP/SYSTEM/CACHE/vulkan_spirv.cache, loaded on first use and saved after graphics init, when a game's cache is saved, and at shutdown; it's flushed once it reaches 32 entries, about twice what a session compiles, so outdated ones don't pile up. Game shaders keep theirs in the .vkshadercache, ahead of the shader IDs so that the compiles on load find it (version 60), and only what the session used is saved. A cold glslang costs about 40ms before its first shader here, and 0.3-0.9ms per shader after that. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
3970fa5c62
commit
8b4e93021e
8 files changed
+279
-17
No files matched your search
@@ -171,6 +171,9 @@ void GPU_Vulkan::SaveCache(const Path &filename) {
|
||||
pipelineManager_->SavePipelineCache(f, false, shaderManagerVulkan_, draw_);
|
||||
INFO_LOG(Log::G3D, "Saved Vulkan pipeline cache");
|
||||
fclose(f);
|
||||
|
||||
// And the fixed shaders the GPU compiled along the way.
|
||||
g_spirvCache.SaveIfDirty();
|
||||
}
|
||||
|
||||
GPU_Vulkan::~GPU_Vulkan() {
|
||||
|
||||
@@ -40,14 +40,14 @@
|
||||
// takes time here, and makes this worthy of parallelization, is GLSLtoSPV.
|
||||
// Takes ownership over tag.
|
||||
// This always returns something, checking the return value for null is not meaningful.
|
||||
static Promise<VkShaderModule> *CompileShaderModuleAsync(VulkanContext *vulkan, VkShaderStageFlagBits stage, const char *code, std::string *tag) {
|
||||
static Promise<VkShaderModule> *CompileShaderModuleAsync(VulkanContext *vulkan, VkShaderStageFlagBits stage, const char *code, std::string *tag, SPIRVCache *cache) {
|
||||
auto compile = [=] {
|
||||
PROFILE_THIS_SCOPE("shadercomp");
|
||||
|
||||
std::string errorMessage;
|
||||
std::vector<uint32_t> spirv;
|
||||
|
||||
bool success = GLSLtoSPV(stage, code, GLSLVariant::VULKAN, spirv, &errorMessage);
|
||||
bool success = GLSLtoSPV(stage, code, GLSLVariant::VULKAN, spirv, &errorMessage, cache);
|
||||
|
||||
if (!errorMessage.empty()) {
|
||||
if (success) {
|
||||
@@ -102,11 +102,11 @@ static Promise<VkShaderModule> *CompileShaderModuleAsync(VulkanContext *vulkan,
|
||||
}
|
||||
}
|
||||
|
||||
VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code)
|
||||
VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code, SPIRVCache *cache)
|
||||
: vulkan_(vulkan), id_(id), flags_(flags) {
|
||||
_assert_(!id.is_invalid());
|
||||
source_ = code;
|
||||
module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_FRAGMENT_BIT, source_.c_str(), new std::string(id.Description()));
|
||||
module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_FRAGMENT_BIT, source_.c_str(), new std::string(id.Description()), cache);
|
||||
VERBOSE_LOG(Log::G3D, "Compiled fragment shader:\n%s\n", (const char *)code);
|
||||
}
|
||||
|
||||
@@ -133,11 +133,11 @@ std::string VulkanFragmentShader::GetShaderString(DebugShaderStringType type) co
|
||||
}
|
||||
}
|
||||
|
||||
VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, VertexShaderFlags flags, const char *code, bool useHWTransform)
|
||||
VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, VertexShaderFlags flags, const char *code, bool useHWTransform, SPIRVCache *cache)
|
||||
: vulkan_(vulkan), useHWTransform_(useHWTransform), flags_(flags), id_(id) {
|
||||
_assert_(!id.is_invalid());
|
||||
source_ = code;
|
||||
module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_VERTEX_BIT, source_.c_str(), new std::string(id.Description()));
|
||||
module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_VERTEX_BIT, source_.c_str(), new std::string(id.Description()), cache);
|
||||
VERBOSE_LOG(Log::G3D, "Compiled vertex shader:\n%s\n", (const char *)code);
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ const VulkanVertexShader *ShaderManagerVulkan::GetVertexShaderFromID(VShaderID V
|
||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_));
|
||||
|
||||
const bool useHWTransform = VSID.Bit(VS_BIT_USE_HW_TRANSFORM);
|
||||
vs = new VulkanVertexShader(vulkan, VSID, flags, codeBuffer_, useHWTransform);
|
||||
vs = new VulkanVertexShader(vulkan, VSID, flags, codeBuffer_, useHWTransform, &spirvCache_);
|
||||
vsCache_.Insert(VSID, vs);
|
||||
return vs;
|
||||
}
|
||||
@@ -264,7 +264,7 @@ const VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromID(FShader
|
||||
_assert_msg_(success, "FS gen error: %s", genErrorString.c_str());
|
||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_));
|
||||
|
||||
fs = new VulkanFragmentShader(vulkan, FSID, flags, codeBuffer_);
|
||||
fs = new VulkanFragmentShader(vulkan, FSID, flags, codeBuffer_, &spirvCache_);
|
||||
fsCache_.Insert(FSID, fs);
|
||||
return fs;
|
||||
}
|
||||
@@ -369,7 +369,7 @@ enum class VulkanCacheDetectFlags {
|
||||
};
|
||||
|
||||
#define CACHE_HEADER_MAGIC 0xff51f420
|
||||
#define CACHE_VERSION 59
|
||||
#define CACHE_VERSION 60
|
||||
|
||||
struct VulkanCacheHeader {
|
||||
uint32_t magic;
|
||||
@@ -413,6 +413,12 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) {
|
||||
gstate_c.useFlagsChanged = false;
|
||||
}
|
||||
|
||||
// The SPIR-V comes first, so that compiling the shaders below mostly finds it there.
|
||||
if (!spirvCache_.Read(f)) {
|
||||
ERROR_LOG(Log::G3D, "Vulkan shader cache: SPIR-V missing or damaged");
|
||||
return false;
|
||||
}
|
||||
|
||||
int failCount = 0;
|
||||
|
||||
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
|
||||
@@ -436,7 +442,7 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) {
|
||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_));
|
||||
// Don't add the new shader if already compiled - though this should no longer happen.
|
||||
if (!vsCache_.ContainsKey(id)) {
|
||||
VulkanVertexShader *vs = new VulkanVertexShader(vulkan, id, flags, codeBuffer_, useHWTransform);
|
||||
VulkanVertexShader *vs = new VulkanVertexShader(vulkan, id, flags, codeBuffer_, useHWTransform, &spirvCache_);
|
||||
vsCache_.Insert(id, vs);
|
||||
}
|
||||
}
|
||||
@@ -459,7 +465,7 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) {
|
||||
}
|
||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_));
|
||||
if (!fsCache_.ContainsKey(id)) {
|
||||
VulkanFragmentShader *fs = new VulkanFragmentShader(vulkan, id, flags, codeBuffer_);
|
||||
VulkanFragmentShader *fs = new VulkanFragmentShader(vulkan, id, flags, codeBuffer_, &spirvCache_);
|
||||
fsCache_.Insert(id, fs);
|
||||
}
|
||||
}
|
||||
@@ -478,6 +484,8 @@ void ShaderManagerVulkan::SaveCache(FILE *f, DrawEngineVulkan *drawEngine) {
|
||||
header.numFragmentShaders = (int)fsCache_.size();
|
||||
header.unused_numGeometryShaders = 0;
|
||||
bool writeFailed = fwrite(&header, sizeof(header), 1, f) != 1;
|
||||
// Only what this run compiled or used, so that SPIR-V of shaders that have since changed ages out.
|
||||
writeFailed = writeFailed || !spirvCache_.Write(f, true);
|
||||
vsCache_.Iterate([&](const VShaderID &id, VulkanVertexShader *vs) {
|
||||
writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1;
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ class VulkanPushPool;
|
||||
|
||||
class VulkanFragmentShader {
|
||||
public:
|
||||
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code);
|
||||
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code, SPIRVCache *cache);
|
||||
~VulkanFragmentShader();
|
||||
|
||||
const std::string &source() const { return source_; }
|
||||
@@ -63,7 +63,7 @@ protected:
|
||||
|
||||
class VulkanVertexShader {
|
||||
public:
|
||||
VulkanVertexShader(VulkanContext *vulkan, VShaderID id, VertexShaderFlags flags, const char *code, bool useHWTransform);
|
||||
VulkanVertexShader(VulkanContext *vulkan, VShaderID id, VertexShaderFlags flags, const char *code, bool useHWTransform, SPIRVCache *cache);
|
||||
~VulkanVertexShader();
|
||||
|
||||
const std::string &source() const { return source_; }
|
||||
@@ -145,6 +145,9 @@ private:
|
||||
|
||||
char *codeBuffer_;
|
||||
|
||||
// The SPIR-V of this game's shaders, saved with the rest of its shader cache.
|
||||
SPIRVCache spirvCache_;
|
||||
|
||||
uint64_t uboAlignment_;
|
||||
|
||||
Uniforms *uniforms_;
|
||||
|
||||
@@ -84,7 +84,7 @@ void InitVulkanCreateInfoFromConfig(VulkanContext::CreateInfo *info) {
|
||||
|
||||
VkShaderModule CompileShaderModule(VulkanContext *vulkan, VkShaderStageFlagBits stage, const char *code, std::string *error) {
|
||||
std::vector<uint32_t> spirv;
|
||||
bool success = GLSLtoSPV(stage, code, GLSLVariant::VULKAN, spirv, error);
|
||||
bool success = GLSLtoSPV(stage, code, GLSLVariant::VULKAN, spirv, error, &g_spirvCache);
|
||||
if (!error->empty()) {
|
||||
if (success) {
|
||||
ERROR_LOG(Log::G3D, "Warnings in shader compilation!");
|
||||
|
||||
Reference in new issue
Block a user