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
@@ -13,6 +13,8 @@
|
|||||||
#include "Common/GPU/Vulkan/VulkanContext.h"
|
#include "Common/GPU/Vulkan/VulkanContext.h"
|
||||||
#include "Common/GPU/Vulkan/VulkanDebug.h"
|
#include "Common/GPU/Vulkan/VulkanDebug.h"
|
||||||
#include "Common/StringUtils.h"
|
#include "Common/StringUtils.h"
|
||||||
|
#include "Common/File/FileUtil.h"
|
||||||
|
#include "ext/xxhash.h"
|
||||||
|
|
||||||
#ifdef USE_CRT_DBG
|
#ifdef USE_CRT_DBG
|
||||||
#undef new
|
#undef new
|
||||||
@@ -1778,8 +1780,180 @@ EShLanguage FindLanguage(const VkShaderStageFlagBits shader_type) {
|
|||||||
|
|
||||||
// Compile a given string containing GLSL into SPV for use by VK
|
// Compile a given string containing GLSL into SPV for use by VK
|
||||||
// Return value of false means an error was encountered.
|
// Return value of false means an error was encountered.
|
||||||
|
// Bump when glslang or its options change, so that nothing it compiled differently is used.
|
||||||
|
static const uint32_t SPIRV_CACHE_VERSION = 2;
|
||||||
|
static const uint32_t SPIRV_CACHE_MAGIC = 0x43565053; // "SPVC"
|
||||||
|
static const uint32_t SPIRV_MAGIC = 0x07230203;
|
||||||
|
|
||||||
|
struct SPIRVCacheHeader {
|
||||||
|
uint32_t magic;
|
||||||
|
uint32_t version;
|
||||||
|
uint32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SPIRVCacheEntryHeader {
|
||||||
|
uint32_t keyHash;
|
||||||
|
uint32_t keyLength;
|
||||||
|
uint32_t numWords;
|
||||||
|
uint32_t checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
SPIRVCache g_spirvCache;
|
||||||
|
|
||||||
|
SPIRVCache::Key SPIRVCache::MakeKey(VkShaderStageFlagBits stage, GLSLVariant variant, const char *source) {
|
||||||
|
const size_t length = strlen(source);
|
||||||
|
const uint32_t seed = (SPIRV_CACHE_VERSION << 16) | ((uint32_t)stage << 4) | (uint32_t)variant;
|
||||||
|
return Key{ XXH32(source, length, seed), (uint32_t)length };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SPIRVCache::Lookup(VkShaderStageFlagBits stage, GLSLVariant variant, const char *source, std::vector<uint32_t> *spirv) {
|
||||||
|
const Key key = MakeKey(stage, variant, source);
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
LoadIfNeededLocked();
|
||||||
|
auto iter = entries_.find(key);
|
||||||
|
if (iter == entries_.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
iter->second.used = true;
|
||||||
|
*spirv = iter->second.spirv;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPIRVCache::Insert(VkShaderStageFlagBits stage, GLSLVariant variant, const char *source, const std::vector<uint32_t> &spirv) {
|
||||||
|
const Key key = MakeKey(stage, variant, source);
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
Entry &entry = entries_[key];
|
||||||
|
entry.spirv = spirv;
|
||||||
|
entry.used = true;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPIRVCache::Clear() {
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
entries_.clear();
|
||||||
|
dirty_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SPIRVCache::Read(FILE *f) {
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
return ReadLocked(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bad entry is dropped rather than failing the whole read: it's only a cache, and the shader
|
||||||
|
// gets compiled again. A truncated file keeps what came before the damage.
|
||||||
|
bool SPIRVCache::ReadLocked(FILE *f) {
|
||||||
|
entries_.clear();
|
||||||
|
dirty_ = false;
|
||||||
|
SPIRVCacheHeader header{};
|
||||||
|
if (fread(&header, sizeof(header), 1, f) != 1 || header.magic != SPIRV_CACHE_MAGIC) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (header.version != SPIRV_CACHE_VERSION) {
|
||||||
|
INFO_LOG(Log::G3D, "SPIR-V cache version %d, expected %d - starting over", header.version, SPIRV_CACHE_VERSION);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < header.count; i++) {
|
||||||
|
SPIRVCacheEntryHeader entryHeader{};
|
||||||
|
if (fread(&entryHeader, sizeof(entryHeader), 1, f) != 1 || entryHeader.numWords == 0 || entryHeader.numWords > 1024 * 1024) {
|
||||||
|
WARN_LOG(Log::G3D, "SPIR-V cache truncated or damaged after %d entries", i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<uint32_t> spirv(entryHeader.numWords);
|
||||||
|
if (fread(spirv.data(), sizeof(uint32_t), spirv.size(), f) != spirv.size()) {
|
||||||
|
WARN_LOG(Log::G3D, "SPIR-V cache truncated after %d entries", i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (spirv[0] != SPIRV_MAGIC || (uint32_t)XXH3_64bits(spirv.data(), spirv.size() * sizeof(uint32_t)) != entryHeader.checksum) {
|
||||||
|
WARN_LOG(Log::G3D, "Bad entry in SPIR-V cache, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entries_[Key{ entryHeader.keyHash, entryHeader.keyLength }].spirv = std::move(spirv);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SPIRVCache::Write(FILE *f, bool onlyUsed) {
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
SPIRVCacheHeader header{ SPIRV_CACHE_MAGIC, SPIRV_CACHE_VERSION, 0 };
|
||||||
|
for (const auto &[key, entry] : entries_) {
|
||||||
|
if (entry.used || !onlyUsed) {
|
||||||
|
header.count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ok = fwrite(&header, sizeof(header), 1, f) == 1;
|
||||||
|
for (const auto &[key, entry] : entries_) {
|
||||||
|
if (!ok) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!entry.used && onlyUsed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SPIRVCacheEntryHeader entryHeader{};
|
||||||
|
entryHeader.keyHash = key.hash;
|
||||||
|
entryHeader.keyLength = key.length;
|
||||||
|
entryHeader.numWords = (uint32_t)entry.spirv.size();
|
||||||
|
entryHeader.checksum = (uint32_t)XXH3_64bits(entry.spirv.data(), entry.spirv.size() * sizeof(uint32_t));
|
||||||
|
ok = fwrite(&entryHeader, sizeof(entryHeader), 1, f) == 1 &&
|
||||||
|
fwrite(entry.spirv.data(), sizeof(uint32_t), entry.spirv.size(), f) == entry.spirv.size();
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
dirty_ = false;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPIRVCache::SetPath(const Path &path, int maxEntries) {
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
path_ = path;
|
||||||
|
maxEntries_ = maxEntries;
|
||||||
|
loaded_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPIRVCache::LoadIfNeededLocked() {
|
||||||
|
if (loaded_ || path_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loaded_ = true;
|
||||||
|
FILE *f = File::OpenCFile(path_, "rb");
|
||||||
|
if (f) {
|
||||||
|
ReadLocked(f);
|
||||||
|
fclose(f);
|
||||||
|
INFO_LOG(Log::G3D, "Loaded %d shaders from the SPIR-V cache", (int)entries_.size());
|
||||||
|
}
|
||||||
|
// Entries for shaders that have since changed pile up, so start over once there are a lot of
|
||||||
|
// them. A few shaders compiling once more doesn't matter.
|
||||||
|
if (maxEntries_ > 0 && (int)entries_.size() >= maxEntries_) {
|
||||||
|
INFO_LOG(Log::G3D, "SPIR-V cache has %d entries, flushing it", (int)entries_.size());
|
||||||
|
entries_.clear();
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPIRVCache::SaveIfDirty() {
|
||||||
|
Path path;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
if (!dirty_ || path_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
path = path_;
|
||||||
|
}
|
||||||
|
FILE *f = File::OpenCFile(path, "wb");
|
||||||
|
if (!f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// A small fixed set of shaders, so keep everything - including ones this run didn't need.
|
||||||
|
if (!Write(f, false)) {
|
||||||
|
ERROR_LOG(Log::G3D, "Failed to write the SPIR-V cache, disk full?");
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant,
|
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant,
|
||||||
std::vector<unsigned int> &spirv, std::string *errorMessage) {
|
std::vector<unsigned int> &spirv, std::string *errorMessage, SPIRVCache *cache) {
|
||||||
|
if (cache && cache->Lookup(shader_type, variant, sourceCode, &spirv)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
glslang::TProgram program;
|
glslang::TProgram program;
|
||||||
const char *shaderStrings[1];
|
const char *shaderStrings[1];
|
||||||
@@ -1848,6 +2022,9 @@ bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode,
|
|||||||
options.optimizeSize = false;
|
options.optimizeSize = false;
|
||||||
options.generateDebugInfo = false;
|
options.generateDebugInfo = false;
|
||||||
glslang::GlslangToSpv(*program.getIntermediate(stage), spirv, &options);
|
glslang::GlslangToSpv(*program.getIntermediate(stage), spirv, &options);
|
||||||
|
if (cache && !spirv.empty()) {
|
||||||
|
cache->Insert(shader_type, variant, sourceCode, spirv);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,11 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "Common/Common.h"
|
#include "Common/Common.h"
|
||||||
#include "Common/Log.h"
|
#include "Common/Log.h"
|
||||||
|
#include "Common/File/Path.h"
|
||||||
#include "Common/GPU/MiscTypes.h"
|
#include "Common/GPU/MiscTypes.h"
|
||||||
#include "Common/GPU/Vulkan/VulkanLoader.h"
|
#include "Common/GPU/Vulkan/VulkanLoader.h"
|
||||||
#include "Common/GPU/Vulkan/VulkanDebug.h"
|
#include "Common/GPU/Vulkan/VulkanDebug.h"
|
||||||
@@ -613,7 +615,57 @@ enum class GLSLVariant {
|
|||||||
GLES300,
|
GLES300,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant, std::vector<uint32_t> &spirv, std::string *errorMessage);
|
// Compiled SPIR-V, keyed on the GLSL source, stage and variant, so that a shader compiled in an earlier
|
||||||
|
// run doesn't have to go through glslang again. Thread safe.
|
||||||
|
class SPIRVCache {
|
||||||
|
public:
|
||||||
|
bool Lookup(VkShaderStageFlagBits stage, GLSLVariant variant, const char *source, std::vector<uint32_t> *spirv);
|
||||||
|
void Insert(VkShaderStageFlagBits stage, GLSLVariant variant, const char *source, const std::vector<uint32_t> &spirv);
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
// For a cache stored inside another file. Read replaces the contents; Write can skip entries that
|
||||||
|
// haven't been looked up or inserted since, so that ones nothing uses anymore age out.
|
||||||
|
bool Read(FILE *f);
|
||||||
|
bool Write(FILE *f, bool onlyUsed);
|
||||||
|
|
||||||
|
// For a cache with a file of its own, loaded on the first lookup. If it has grown to maxEntries,
|
||||||
|
// it's flushed on load and starts over.
|
||||||
|
void SetPath(const Path &path, int maxEntries);
|
||||||
|
void SaveIfDirty();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The source length along with the hash makes a collision, which would hand a shader the wrong
|
||||||
|
// SPIR-V, far less likely than a 32-bit hash alone.
|
||||||
|
struct Key {
|
||||||
|
uint32_t hash;
|
||||||
|
uint32_t length;
|
||||||
|
bool operator==(const Key &other) const { return hash == other.hash && length == other.length; }
|
||||||
|
};
|
||||||
|
struct KeyHash {
|
||||||
|
size_t operator()(const Key &key) const { return key.hash; }
|
||||||
|
};
|
||||||
|
struct Entry {
|
||||||
|
std::vector<uint32_t> spirv;
|
||||||
|
bool used = false;
|
||||||
|
};
|
||||||
|
static Key MakeKey(VkShaderStageFlagBits stage, GLSLVariant variant, const char *source);
|
||||||
|
bool ReadLocked(FILE *f);
|
||||||
|
void LoadIfNeededLocked();
|
||||||
|
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::unordered_map<Key, Entry, KeyHash> entries_;
|
||||||
|
Path path_;
|
||||||
|
int maxEntries_ = 0;
|
||||||
|
bool loaded_ = false;
|
||||||
|
bool dirty_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// For thin3d's and other fixed shaders. Game shaders use a cache of their own, stored with the rest of
|
||||||
|
// the game's shader cache.
|
||||||
|
extern SPIRVCache g_spirvCache;
|
||||||
|
|
||||||
|
// With a cache, a shader found there skips glslang, and a newly compiled one is added to it.
|
||||||
|
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant, std::vector<uint32_t> &spirv, std::string *errorMessage, SPIRVCache *cache = nullptr);
|
||||||
|
|
||||||
const char *VulkanColorSpaceToString(VkColorSpaceKHR colorSpace);
|
const char *VulkanColorSpaceToString(VkColorSpaceKHR colorSpace);
|
||||||
const char *VulkanFormatToString(VkFormat format);
|
const char *VulkanFormatToString(VkFormat format);
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, const uint8_t *data, size_t
|
|||||||
source_ = (const char *)data;
|
source_ = (const char *)data;
|
||||||
std::vector<uint32_t> spirv;
|
std::vector<uint32_t> spirv;
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
if (!GLSLtoSPV(vkstage_, source_.c_str(), GLSLVariant::VULKAN, spirv, &errorMessage)) {
|
if (!GLSLtoSPV(vkstage_, source_.c_str(), GLSLVariant::VULKAN, spirv, &errorMessage, &g_spirvCache)) {
|
||||||
WARN_LOG(Log::G3D, "Shader compile to module failed (%s): %s", tag_.c_str(), errorMessage.c_str());
|
WARN_LOG(Log::G3D, "Shader compile to module failed (%s): %s", tag_.c_str(), errorMessage.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,9 @@ void GPU_Vulkan::SaveCache(const Path &filename) {
|
|||||||
pipelineManager_->SavePipelineCache(f, false, shaderManagerVulkan_, draw_);
|
pipelineManager_->SavePipelineCache(f, false, shaderManagerVulkan_, draw_);
|
||||||
INFO_LOG(Log::G3D, "Saved Vulkan pipeline cache");
|
INFO_LOG(Log::G3D, "Saved Vulkan pipeline cache");
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
|
// And the fixed shaders the GPU compiled along the way.
|
||||||
|
g_spirvCache.SaveIfDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
GPU_Vulkan::~GPU_Vulkan() {
|
GPU_Vulkan::~GPU_Vulkan() {
|
||||||
|
|||||||
@@ -40,14 +40,14 @@
|
|||||||
// takes time here, and makes this worthy of parallelization, is GLSLtoSPV.
|
// takes time here, and makes this worthy of parallelization, is GLSLtoSPV.
|
||||||
// Takes ownership over tag.
|
// Takes ownership over tag.
|
||||||
// This always returns something, checking the return value for null is not meaningful.
|
// 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 = [=] {
|
auto compile = [=] {
|
||||||
PROFILE_THIS_SCOPE("shadercomp");
|
PROFILE_THIS_SCOPE("shadercomp");
|
||||||
|
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
std::vector<uint32_t> spirv;
|
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 (!errorMessage.empty()) {
|
||||||
if (success) {
|
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) {
|
: vulkan_(vulkan), id_(id), flags_(flags) {
|
||||||
_assert_(!id.is_invalid());
|
_assert_(!id.is_invalid());
|
||||||
source_ = code;
|
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);
|
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) {
|
: vulkan_(vulkan), useHWTransform_(useHWTransform), flags_(flags), id_(id) {
|
||||||
_assert_(!id.is_invalid());
|
_assert_(!id.is_invalid());
|
||||||
source_ = code;
|
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);
|
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_));
|
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_));
|
||||||
|
|
||||||
const bool useHWTransform = VSID.Bit(VS_BIT_USE_HW_TRANSFORM);
|
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);
|
vsCache_.Insert(VSID, vs);
|
||||||
return vs;
|
return vs;
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ const VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromID(FShader
|
|||||||
_assert_msg_(success, "FS gen error: %s", genErrorString.c_str());
|
_assert_msg_(success, "FS gen error: %s", genErrorString.c_str());
|
||||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_));
|
_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);
|
fsCache_.Insert(FSID, fs);
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ enum class VulkanCacheDetectFlags {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#define CACHE_HEADER_MAGIC 0xff51f420
|
#define CACHE_HEADER_MAGIC 0xff51f420
|
||||||
#define CACHE_VERSION 59
|
#define CACHE_VERSION 60
|
||||||
|
|
||||||
struct VulkanCacheHeader {
|
struct VulkanCacheHeader {
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
@@ -413,6 +413,12 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) {
|
|||||||
gstate_c.useFlagsChanged = false;
|
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;
|
int failCount = 0;
|
||||||
|
|
||||||
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
|
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_));
|
_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.
|
// Don't add the new shader if already compiled - though this should no longer happen.
|
||||||
if (!vsCache_.ContainsKey(id)) {
|
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);
|
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_));
|
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_));
|
||||||
if (!fsCache_.ContainsKey(id)) {
|
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);
|
fsCache_.Insert(id, fs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,6 +484,8 @@ void ShaderManagerVulkan::SaveCache(FILE *f, DrawEngineVulkan *drawEngine) {
|
|||||||
header.numFragmentShaders = (int)fsCache_.size();
|
header.numFragmentShaders = (int)fsCache_.size();
|
||||||
header.unused_numGeometryShaders = 0;
|
header.unused_numGeometryShaders = 0;
|
||||||
bool writeFailed = fwrite(&header, sizeof(header), 1, f) != 1;
|
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) {
|
vsCache_.Iterate([&](const VShaderID &id, VulkanVertexShader *vs) {
|
||||||
writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1;
|
writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class VulkanPushPool;
|
|||||||
|
|
||||||
class VulkanFragmentShader {
|
class VulkanFragmentShader {
|
||||||
public:
|
public:
|
||||||
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code);
|
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code, SPIRVCache *cache);
|
||||||
~VulkanFragmentShader();
|
~VulkanFragmentShader();
|
||||||
|
|
||||||
const std::string &source() const { return source_; }
|
const std::string &source() const { return source_; }
|
||||||
@@ -63,7 +63,7 @@ protected:
|
|||||||
|
|
||||||
class VulkanVertexShader {
|
class VulkanVertexShader {
|
||||||
public:
|
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();
|
~VulkanVertexShader();
|
||||||
|
|
||||||
const std::string &source() const { return source_; }
|
const std::string &source() const { return source_; }
|
||||||
@@ -145,6 +145,9 @@ private:
|
|||||||
|
|
||||||
char *codeBuffer_;
|
char *codeBuffer_;
|
||||||
|
|
||||||
|
// The SPIR-V of this game's shaders, saved with the rest of its shader cache.
|
||||||
|
SPIRVCache spirvCache_;
|
||||||
|
|
||||||
uint64_t uboAlignment_;
|
uint64_t uboAlignment_;
|
||||||
|
|
||||||
Uniforms *uniforms_;
|
Uniforms *uniforms_;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ void InitVulkanCreateInfoFromConfig(VulkanContext::CreateInfo *info) {
|
|||||||
|
|
||||||
VkShaderModule CompileShaderModule(VulkanContext *vulkan, VkShaderStageFlagBits stage, const char *code, std::string *error) {
|
VkShaderModule CompileShaderModule(VulkanContext *vulkan, VkShaderStageFlagBits stage, const char *code, std::string *error) {
|
||||||
std::vector<uint32_t> spirv;
|
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 (!error->empty()) {
|
||||||
if (success) {
|
if (success) {
|
||||||
ERROR_LOG(Log::G3D, "Warnings in shader compilation!");
|
ERROR_LOG(Log::G3D, "Warnings in shader compilation!");
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
#include "Common/Render/Text/draw_text.h"
|
#include "Common/Render/Text/draw_text.h"
|
||||||
#include "Common/GPU/OpenGL/GLFeatures.h"
|
#include "Common/GPU/OpenGL/GLFeatures.h"
|
||||||
#include "Common/GPU/thin3d.h"
|
#include "Common/GPU/thin3d.h"
|
||||||
|
#include "Common/GPU/Vulkan/VulkanContext.h"
|
||||||
#include "Common/UI/UI.h"
|
#include "Common/UI/UI.h"
|
||||||
#include "Common/UI/Screen.h"
|
#include "Common/UI/Screen.h"
|
||||||
#include "Common/UI/ScreenManager.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));
|
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();
|
ApplyAchievementsHostOverride();
|
||||||
|
|
||||||
g_screenManager = new ScreenManager();
|
g_screenManager = new ScreenManager();
|
||||||
@@ -966,6 +977,10 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) {
|
|||||||
ImGui_ImplThin3d_CreateDeviceObjects(g_draw);
|
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");
|
INFO_LOG(Log::System, "NativeInitGraphics completed");
|
||||||
|
|
||||||
@@ -1027,6 +1042,10 @@ bool CreateGlobalPipelines() {
|
|||||||
void NativeShutdownGraphics(GraphicsContext *graphicsContext) {
|
void NativeShutdownGraphics(GraphicsContext *graphicsContext) {
|
||||||
INFO_LOG(Log::System, "NativeShutdownGraphics begin");
|
INFO_LOG(Log::System, "NativeShutdownGraphics begin");
|
||||||
|
|
||||||
|
#if !PPSSPP_PLATFORM(UWP)
|
||||||
|
g_spirvCache.SaveIfDirty();
|
||||||
|
#endif
|
||||||
|
|
||||||
graphicsContext->NotifyEmuThreadExit();
|
graphicsContext->NotifyEmuThreadExit();
|
||||||
|
|
||||||
if (g_screenManager) {
|
if (g_screenManager) {
|
||||||
|
|||||||
Reference in new issue
Block a user