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:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-28 16:03:52 -06:00
1 parent 3970fa5c62
commit 8b4e93021e
8 files changed
+279 -17

No files matched your search

+19
View File
@@ -62,6 +62,7 @@
#include "Common/Render/Text/draw_text.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/GPU/thin3d.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/UI/UI.h"
#include "Common/UI/Screen.h"
#include "Common/UI/ScreenManager.h"
@@ -798,6 +799,16 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO
g_DownloadManager.SetCacheDir(GetSysDirectory(DIRECTORY_APP_CACHE));
#if !PPSSPP_PLATFORM(UWP)
// The SPIR-V of thin3d's shaders and other fixed ones. Only read on first use, so only by Vulkan.
if (g_Config.bShaderCache) {
File::CreateFullPath(GetSysDirectory(DIRECTORY_APP_CACHE));
// About twice what a session compiles (menu and a game: 10, a few more with post-processing
// or texture upscaling), so it's flushed once outdated entries have piled up.
g_spirvCache.SetPath(GetSysDirectory(DIRECTORY_APP_CACHE) / "vulkan_spirv.cache", 32);
}
#endif
ApplyAchievementsHostOverride();
g_screenManager = new ScreenManager();
@@ -966,6 +977,10 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) {
ImGui_ImplThin3d_CreateDeviceObjects(g_draw);
}
#if !PPSSPP_PLATFORM(UWP)
// Now, rather than only at shutdown: on mobile the app can be killed without one.
g_spirvCache.SaveIfDirty();
#endif
INFO_LOG(Log::System, "NativeInitGraphics completed");
@@ -1027,6 +1042,10 @@ bool CreateGlobalPipelines() {
void NativeShutdownGraphics(GraphicsContext *graphicsContext) {
INFO_LOG(Log::System, "NativeShutdownGraphics begin");
#if !PPSSPP_PLATFORM(UWP)
g_spirvCache.SaveIfDirty();
#endif
graphicsContext->NotifyEmuThreadExit();
if (g_screenManager) {