mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-07 05:03:35 +02:00
The pipeline debug listing printed the color blend factors in the alpha slot, which is doubly unhelpful since that branch is only taken when the alpha factors differ from the defaults. CompileShaderModuleAsync takes ownership of the tag but only deleted it on the success path, leaking it whenever GLSLtoSPV failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SGz54K3ZXa2Qzyc3aMEYyY
493 lines
17 KiB
C++
493 lines
17 KiB
C++
// Copyright (c) 2015- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#ifdef _WIN32
|
|
//#define SHADERLOG
|
|
#endif
|
|
|
|
#include "Common/LogReporting.h"
|
|
#include "Common/Profiler/Profiler.h"
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Common/MemoryUtil.h"
|
|
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/GPU/Vulkan/VulkanContext.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Common/GPU/Vulkan/VulkanMemory.h"
|
|
|
|
#include "GPU/GPUState.h"
|
|
#include "GPU/Common/FragmentShaderGenerator.h"
|
|
#include "GPU/Common/VertexShaderGenerator.h"
|
|
#include "GPU/Vulkan/ShaderManagerVulkan.h"
|
|
#include "GPU/Vulkan/DrawEngineVulkan.h"
|
|
|
|
// Most drivers treat vkCreateShaderModule as pretty much a memcpy. What actually
|
|
// 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) {
|
|
auto compile = [=] {
|
|
PROFILE_THIS_SCOPE("shadercomp");
|
|
|
|
std::string errorMessage;
|
|
std::vector<uint32_t> spirv;
|
|
|
|
bool success = GLSLtoSPV(stage, code, GLSLVariant::VULKAN, spirv, &errorMessage);
|
|
|
|
if (!errorMessage.empty()) {
|
|
if (success) {
|
|
ERROR_LOG(Log::G3D, "Warnings in shader compilation!");
|
|
} else {
|
|
ERROR_LOG(Log::G3D, "Error in shader compilation!");
|
|
}
|
|
std::string numberedSource = LineNumberString(code);
|
|
ERROR_LOG(Log::G3D, "Messages: %s", errorMessage.c_str());
|
|
ERROR_LOG(Log::G3D, "Shader source:\n%s", numberedSource.c_str());
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
OutputDebugStringA("Error messages:\n");
|
|
OutputDebugStringA(errorMessage.c_str());
|
|
OutputDebugStringA(numberedSource.c_str());
|
|
#endif
|
|
Reporting::ReportMessage("Vulkan error in shader compilation: info: %s / code: %s", errorMessage.c_str(), code);
|
|
}
|
|
|
|
VkShaderModule shaderModule = VK_NULL_HANDLE;
|
|
if (success) {
|
|
const char *createTag = tag ? tag->c_str() : nullptr;
|
|
if (!createTag) {
|
|
switch (stage) {
|
|
case VK_SHADER_STAGE_VERTEX_BIT: createTag = "game_vertex"; break;
|
|
case VK_SHADER_STAGE_FRAGMENT_BIT: createTag = "game_fragment"; break;
|
|
case VK_SHADER_STAGE_COMPUTE_BIT: createTag = "game_compute"; break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
success = vulkan->CreateShaderModule(spirv, &shaderModule, createTag);
|
|
#ifdef SHADERLOG
|
|
OutputDebugStringA("OK");
|
|
#endif
|
|
}
|
|
// We take ownership of the tag, so delete it whether we got here through success or failure.
|
|
delete tag;
|
|
return shaderModule;
|
|
};
|
|
|
|
#if defined(_DEBUG)
|
|
// Don't parallelize in debug mode, pathological behavior due to mutex locks in allocator which is HEAVILY used by glslang.
|
|
bool singleThreaded = true;
|
|
#else
|
|
bool singleThreaded = false;
|
|
#endif
|
|
|
|
if (singleThreaded) {
|
|
return Promise<VkShaderModule>::AlreadyDone(compile());
|
|
} else {
|
|
return Promise<VkShaderModule>::Spawn(&g_threadManager, compile, TaskType::DEDICATED_THREAD);
|
|
}
|
|
}
|
|
|
|
VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code)
|
|
: 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()));
|
|
VERBOSE_LOG(Log::G3D, "Compiled fragment shader:\n%s\n", (const char *)code);
|
|
}
|
|
|
|
VulkanFragmentShader::~VulkanFragmentShader() {
|
|
if (module_) {
|
|
VkShaderModule shaderModule = module_->BlockUntilReady();
|
|
if (shaderModule) {
|
|
vulkan_->Delete().QueueDeleteShaderModule(shaderModule);
|
|
}
|
|
vulkan_->Delete().QueueCallback([module = module_](VulkanContext *vulkan) {
|
|
delete module;
|
|
});
|
|
}
|
|
}
|
|
|
|
std::string VulkanFragmentShader::GetShaderString(DebugShaderStringType type) const {
|
|
switch (type) {
|
|
case SHADER_STRING_SOURCE_CODE:
|
|
return source_;
|
|
case SHADER_STRING_SHORT_DESC:
|
|
return id_.Description();
|
|
default:
|
|
return "N/A";
|
|
}
|
|
}
|
|
|
|
VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, VertexShaderFlags flags, const char *code, bool useHWTransform)
|
|
: 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()));
|
|
VERBOSE_LOG(Log::G3D, "Compiled vertex shader:\n%s\n", (const char *)code);
|
|
}
|
|
|
|
VulkanVertexShader::~VulkanVertexShader() {
|
|
if (module_) {
|
|
VkShaderModule shaderModule = module_->BlockUntilReady();
|
|
if (shaderModule) {
|
|
vulkan_->Delete().QueueDeleteShaderModule(shaderModule);
|
|
}
|
|
vulkan_->Delete().QueueCallback([module = module_](VulkanContext *vulkan) {
|
|
delete module;
|
|
});
|
|
}
|
|
}
|
|
|
|
std::string VulkanVertexShader::GetShaderString(DebugShaderStringType type) const {
|
|
switch (type) {
|
|
case SHADER_STRING_SOURCE_CODE:
|
|
return source_;
|
|
case SHADER_STRING_SHORT_DESC:
|
|
return id_.Description();
|
|
default:
|
|
return "N/A";
|
|
}
|
|
}
|
|
|
|
static constexpr size_t CODE_BUFFER_SIZE = 32768;
|
|
|
|
ShaderManagerVulkan::ShaderManagerVulkan(Draw::DrawContext *draw)
|
|
: ShaderManagerCommon(draw), compat_(GLSL_VULKAN), fsCache_(16), vsCache_(16) {
|
|
codeBuffer_ = new char[CODE_BUFFER_SIZE];
|
|
VulkanContext *vulkan = (VulkanContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT);
|
|
uboAlignment_ = vulkan->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment;
|
|
|
|
uniforms_ = (Uniforms *)AllocateAlignedMemory(sizeof(Uniforms), 16);
|
|
_assert_(uniforms_);
|
|
|
|
static_assert(sizeof(uniforms_->ub_base) <= 512, "ub_base grew too big");
|
|
static_assert(sizeof(uniforms_->ub_lights) <= 512, "ub_lights grew too big");
|
|
}
|
|
|
|
ShaderManagerVulkan::~ShaderManagerVulkan() {
|
|
FreeAlignedMemory(uniforms_);
|
|
Clear();
|
|
delete[] codeBuffer_;
|
|
}
|
|
|
|
void ShaderManagerVulkan::DeviceLost() {
|
|
Clear();
|
|
draw_ = nullptr;
|
|
}
|
|
|
|
void ShaderManagerVulkan::DeviceRestore(Draw::DrawContext *draw) {
|
|
VulkanContext *vulkan = (VulkanContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT);
|
|
draw_ = draw;
|
|
uboAlignment_ = vulkan->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment;
|
|
}
|
|
|
|
void ShaderManagerVulkan::Clear() {
|
|
fsCache_.Iterate([&](const FShaderID &key, VulkanFragmentShader *shader) {
|
|
delete shader;
|
|
});
|
|
vsCache_.Iterate([&](const VShaderID &key, VulkanVertexShader *shader) {
|
|
delete shader;
|
|
});
|
|
fsCache_.Clear();
|
|
vsCache_.Clear();
|
|
lastFSID_.set_invalid();
|
|
lastVSID_.set_invalid();
|
|
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
|
|
}
|
|
|
|
void ShaderManagerVulkan::ClearShaders() {
|
|
Clear();
|
|
lastFSID_.set_invalid();
|
|
lastVSID_.set_invalid();
|
|
gstate_c.Dirty(DIRTY_ALL_UNIFORMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
|
|
}
|
|
|
|
uint64_t ShaderManagerVulkan::UpdateUniforms(bool useBufferedRendering, bool pixelMapped) {
|
|
uint64_t dirty = gstate_c.GetDirtyUniforms();
|
|
if (dirty != 0) {
|
|
if (dirty & DIRTY_BASE_UNIFORMS)
|
|
BaseUpdateUniforms(&uniforms_->ub_base, dirty, useBufferedRendering, pixelMapped);
|
|
if (dirty & DIRTY_LIGHT_UNIFORMS)
|
|
LightUpdateUniforms(&uniforms_->ub_lights, dirty);
|
|
}
|
|
gstate_c.CleanUniforms();
|
|
return dirty;
|
|
}
|
|
|
|
const VulkanVertexShader *ShaderManagerVulkan::GetVertexShaderFromID(VShaderID VSID) {
|
|
VulkanVertexShader *vs = vsCache_.GetOrNull(VSID);
|
|
if (vs) {
|
|
return vs;
|
|
}
|
|
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
|
|
// Vertex shader not in cache. Let's compile it.
|
|
std::string genErrorString;
|
|
uint64_t uniformMask = 0; // Not used
|
|
uint32_t attributeMask = 0; // Not used
|
|
VertexShaderFlags flags{};
|
|
bool success = GenerateVertexShader(VSID, codeBuffer_, compat_, draw_->GetBugs(), &attributeMask, &uniformMask, &flags, &genErrorString);
|
|
_assert_msg_(success, "VS gen error: %s", genErrorString.c_str());
|
|
_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);
|
|
vsCache_.Insert(VSID, vs);
|
|
return vs;
|
|
}
|
|
|
|
const VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromID(FShaderID FSID) {
|
|
VulkanFragmentShader *fs = fsCache_.GetOrNull(FSID);
|
|
if (fs) {
|
|
return fs;
|
|
}
|
|
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
|
|
// Fragment shader not in cache. Let's compile it.
|
|
std::string genErrorString;
|
|
uint64_t uniformMask = 0; // Not used
|
|
FragmentShaderFlags flags{};
|
|
bool success = GenerateFragmentShader(FSID, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString);
|
|
_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_);
|
|
fsCache_.Insert(FSID, fs);
|
|
return fs;
|
|
}
|
|
|
|
void ShaderManagerVulkan::GetShaderIDs(int prim, u32 vertexType, VShaderID *vshader, FShaderID *fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, ClipInfoFlags clipInfoFlags) {
|
|
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
|
|
|
|
bool recomputedVS = false;
|
|
VShaderID VSID;
|
|
if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) {
|
|
gstate_c.Clean(DIRTY_VERTEXSHADER_STATE);
|
|
ComputeVertexShaderID(&VSID, vertexType, useHWTransform, clipInfoFlags);
|
|
lastVSID_ = VSID;
|
|
*vshader = VSID;
|
|
recomputedVS = true;
|
|
} else {
|
|
VSID = lastVSID_;
|
|
*vshader = lastVSID_;
|
|
}
|
|
|
|
FShaderID FSID;
|
|
VulkanFragmentShader *fs = nullptr;
|
|
bool recomputedFS = false;
|
|
if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) {
|
|
gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE);
|
|
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), clipInfoFlags);
|
|
lastFSID_ = FSID;
|
|
*fshader = FSID;
|
|
recomputedFS = true;
|
|
} else {
|
|
FSID = lastFSID_;
|
|
*fshader = lastFSID_;
|
|
}
|
|
|
|
// If you hit these, look at recomputedVS and recomputedFS to determine if it's a dirty-flag problem
|
|
// or an ID generation problem (if any of them are false, it's a dirty-flag problem).
|
|
_dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE));
|
|
_dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
|
|
_dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD));
|
|
_dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP));
|
|
|
|
_dbg_assert_msg_(VSID.Bit(VS_BIT_USE_HW_TRANSFORM) == useHWTransform, "Bad vshader ID was computed");
|
|
}
|
|
|
|
std::vector<std::string> ShaderManagerVulkan::DebugGetShaderIDs(DebugShaderType type) {
|
|
std::vector<uint64_t> ids;
|
|
switch (type) {
|
|
case SHADER_TYPE_VERTEX:
|
|
vsCache_.Iterate([&](const VShaderID &id, VulkanVertexShader *shader) {
|
|
ids.push_back(id.ToUint64());
|
|
});
|
|
break;
|
|
case SHADER_TYPE_FRAGMENT:
|
|
fsCache_.Iterate([&](const FShaderID &id, VulkanFragmentShader *shader) {
|
|
ids.push_back(id.ToUint64());
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ToSortedDebugShaderIdVec(ids);
|
|
}
|
|
|
|
std::string ShaderManagerVulkan::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) {
|
|
ShaderID shaderId;
|
|
shaderId.FromString(id);
|
|
switch (type) {
|
|
case SHADER_TYPE_VERTEX:
|
|
{
|
|
VulkanVertexShader *vs;
|
|
if (vsCache_.Get(VShaderID(shaderId), &vs)) {
|
|
return vs ? vs->GetShaderString(stringType) : "null (bad)";
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
case SHADER_TYPE_FRAGMENT:
|
|
{
|
|
VulkanFragmentShader *fs;
|
|
if (fsCache_.Get(FShaderID(shaderId), &fs)) {
|
|
return fs ? fs->GetShaderString(stringType) : "null (bad)";
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
default:
|
|
return "N/A";
|
|
}
|
|
}
|
|
|
|
// Shader cache.
|
|
//
|
|
// We simply store the IDs of the shaders used during gameplay. On next startup of
|
|
// the same game, we simply compile all the shaders from the start, so we don't have to
|
|
// compile them on the fly later. We also store the Vulkan pipeline cache, so if it contains
|
|
// pipelines compiled from SPIR-V matching these shaders, pipeline creation will be practically
|
|
// instantaneous.
|
|
|
|
enum class VulkanCacheDetectFlags {
|
|
EQUAL_DEPTH = 1,
|
|
};
|
|
|
|
#define CACHE_HEADER_MAGIC 0xff51f420
|
|
#define CACHE_VERSION 59
|
|
|
|
struct VulkanCacheHeader {
|
|
uint32_t magic;
|
|
uint32_t version;
|
|
uint32_t useFlags;
|
|
uint32_t detectFlags;
|
|
int numVertexShaders;
|
|
int numFragmentShaders;
|
|
int unused_numGeometryShaders; // Always 0, kept so the file format stays compatible.
|
|
};
|
|
|
|
bool ShaderManagerVulkan::LoadCacheFlags(FILE *f, DrawEngineVulkan *drawEngine) {
|
|
VulkanCacheHeader header{};
|
|
int64_t pos = File::Ftell(f);
|
|
bool success = fread(&header, sizeof(header), 1, f) == 1;
|
|
// We'll read it again later, this is just to check the flags.
|
|
success = success && File::Fseek(f, pos, SEEK_SET) == 0;
|
|
if (!success || header.magic != CACHE_HEADER_MAGIC) {
|
|
WARN_LOG(Log::G3D, "Shader cache magic mismatch");
|
|
return false;
|
|
}
|
|
if (header.version != CACHE_VERSION) {
|
|
WARN_LOG(Log::G3D, "Shader cache version mismatch, %d, expected %d", header.version, CACHE_VERSION);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ShaderManagerVulkan::LoadCache(FILE *f) {
|
|
VulkanCacheHeader header{};
|
|
bool success = fread(&header, sizeof(header), 1, f) == 1;
|
|
// We don't need to validate magic/version again, done in LoadCacheFlags().
|
|
|
|
if (header.useFlags != gstate_c.GetUseFlags()) {
|
|
// This can simply be a result of sawExactEqualDepth_ having been flipped to true in the previous run.
|
|
// Let's just keep going.
|
|
WARN_LOG(Log::G3D, "Shader cache useFlags mismatch, %08x, expected %08x", header.useFlags, gstate_c.GetUseFlags());
|
|
} else {
|
|
// We're compiling shaders now, so they haven't changed anymore.
|
|
gstate_c.useFlagsChanged = false;
|
|
}
|
|
|
|
int failCount = 0;
|
|
|
|
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
|
|
for (int i = 0; i < header.numVertexShaders; i++) {
|
|
VShaderID id;
|
|
if (fread(&id, sizeof(id), 1, f) != 1) {
|
|
ERROR_LOG(Log::G3D, "Vulkan shader cache truncated (in VertexShaders)");
|
|
return false;
|
|
}
|
|
bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM);
|
|
std::string genErrorString;
|
|
uint32_t attributeMask = 0;
|
|
uint64_t uniformMask = 0;
|
|
VertexShaderFlags flags;
|
|
if (!GenerateVertexShader(id, codeBuffer_, compat_, draw_->GetBugs(), &attributeMask, &uniformMask, &flags, &genErrorString)) {
|
|
ERROR_LOG(Log::G3D, "Failed to generate vertex shader during cache load");
|
|
// We just ignore this one and carry on.
|
|
failCount++;
|
|
continue;
|
|
}
|
|
_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);
|
|
vsCache_.Insert(id, vs);
|
|
}
|
|
}
|
|
uint32_t vendorID = vulkan->GetPhysicalDeviceProperties().properties.vendorID;
|
|
|
|
for (int i = 0; i < header.numFragmentShaders; i++) {
|
|
FShaderID id;
|
|
if (fread(&id, sizeof(id), 1, f) != 1) {
|
|
ERROR_LOG(Log::G3D, "Vulkan shader cache truncated (in FragmentShaders)");
|
|
return false;
|
|
}
|
|
std::string genErrorString;
|
|
uint64_t uniformMask = 0;
|
|
FragmentShaderFlags flags;
|
|
if (!GenerateFragmentShader(id, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString)) {
|
|
ERROR_LOG(Log::G3D, "Failed to generate fragment shader during cache load");
|
|
// We just ignore this one and carry on.
|
|
failCount++;
|
|
continue;
|
|
}
|
|
_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_);
|
|
fsCache_.Insert(id, fs);
|
|
}
|
|
}
|
|
|
|
NOTICE_LOG(Log::G3D, "ShaderCache: Loaded %d vertex and %d fragment shaders (failed %d)", header.numVertexShaders, header.numFragmentShaders, failCount);
|
|
return true;
|
|
}
|
|
|
|
void ShaderManagerVulkan::SaveCache(FILE *f, DrawEngineVulkan *drawEngine) {
|
|
VulkanCacheHeader header{};
|
|
header.magic = CACHE_HEADER_MAGIC;
|
|
header.version = CACHE_VERSION;
|
|
header.useFlags = gstate_c.GetUseFlags();
|
|
header.detectFlags = 0;
|
|
header.numVertexShaders = (int)vsCache_.size();
|
|
header.numFragmentShaders = (int)fsCache_.size();
|
|
header.unused_numGeometryShaders = 0;
|
|
bool writeFailed = fwrite(&header, sizeof(header), 1, f) != 1;
|
|
vsCache_.Iterate([&](const VShaderID &id, VulkanVertexShader *vs) {
|
|
writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1;
|
|
});
|
|
fsCache_.Iterate([&](const FShaderID &id, VulkanFragmentShader *fs) {
|
|
writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1;
|
|
});
|
|
if (writeFailed) {
|
|
ERROR_LOG(Log::G3D, "Failed to write Vulkan shader cache, disk full?");
|
|
} else {
|
|
NOTICE_LOG(Log::G3D, "Saved %d vertex and %d fragment shaders", header.numVertexShaders, header.numFragmentShaders);
|
|
}
|
|
}
|