Implement pipeline/shader cache for Vulkan, to avoid shader compile stutters on second and subsequent runs.

The raw pipeline cache got pretty large. Instead, store IDs like GL.

There's still a disabled option to store the pipeline cache objects.
This commit is contained in:
Henrik Rydgård 2018-03-13 23:22:21 +01:00
parent 2d33d526b8
commit 614cabb115
16 changed files with 365 additions and 41 deletions

View File

@ -937,17 +937,6 @@ void VulkanContext::DestroyDevice() {
device_ = nullptr; device_ = nullptr;
} }
VkPipelineCache VulkanContext::CreatePipelineCache() {
VkPipelineCache cache;
VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
pc.pInitialData = nullptr;
pc.initialDataSize = 0;
pc.flags = 0;
VkResult res = vkCreatePipelineCache(device_, &pc, nullptr, &cache);
assert(VK_SUCCESS == res);
return cache;
}
bool VulkanContext::CreateShaderModule(const std::vector<uint32_t> &spirv, VkShaderModule *shaderModule) { bool VulkanContext::CreateShaderModule(const std::vector<uint32_t> &spirv, VkShaderModule *shaderModule) {
VkShaderModuleCreateInfo sm{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; VkShaderModuleCreateInfo sm{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
sm.pCode = spirv.data(); sm.pCode = spirv.data();

View File

@ -142,8 +142,6 @@ public:
VulkanDeleteList &Delete() { return globalDeleteList_; } VulkanDeleteList &Delete() { return globalDeleteList_; }
VkPipelineCache CreatePipelineCache();
// The parameters are whatever the chosen window system wants. // The parameters are whatever the chosen window system wants.
void InitSurface(WindowSystem winsys, void *data1, void *data2, int width = -1, int height = -1); void InitSurface(WindowSystem winsys, void *data1, void *data2, int width = -1, int height = -1);
void ReinitSurface(int width = -1, int height = -1); void ReinitSurface(int width = -1, int height = -1);

View File

@ -88,6 +88,8 @@ public:
return numDrawCalls; return numDrawCalls;
} }
VertexDecoder *GetVertexDecoder(u32 vtype);
protected: protected:
virtual void ClearTrackedVertexArrays() {} virtual void ClearTrackedVertexArrays() {}
@ -106,8 +108,6 @@ protected:
bool ApplyShaderBlending(); bool ApplyShaderBlending();
VertexDecoder *GetVertexDecoder(u32 vtype);
inline int IndexSize(u32 vtype) const { inline int IndexSize(u32 vtype) const {
const u32 indexType = (vtype & GE_VTYPE_IDX_MASK); const u32 indexType = (vtype & GE_VTYPE_IDX_MASK);
if (indexType == GE_VTYPE_IDX_16BIT) { if (indexType == GE_VTYPE_IDX_16BIT) {

View File

@ -75,6 +75,21 @@ void DecVtxFormat::ComputeID() {
id = uvfmt | (c0fmt << 4) | (c1fmt << 8) | (nrmfmt << 12) | (posfmt << 16); id = uvfmt | (c0fmt << 4) | (c1fmt << 8) | (nrmfmt << 12) | (posfmt << 16);
} }
void DecVtxFormat::InitializeFromID(uint32_t id) {
this->id = id;
uvfmt = (id & 0xF);
c0fmt = ((id >> 4) & 0xF);
c1fmt = ((id >> 8) & 0xF);
nrmfmt = ((id >> 12) & 0xF);
posfmt = ((id >> 16) & 0xF);
uvoff = 0;
c0off = uvoff + DecFmtSize(uvfmt);
c1off = c0off + DecFmtSize(c0fmt);
nrmoff = c1off + DecFmtSize(c1fmt);
posoff = nrmoff + DecFmtSize(nrmfmt);
stride = posoff + DecFmtSize(posfmt);
}
void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBound, u16 *indexUpperBound) { void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBound, u16 *indexUpperBound) {
// Find index bounds. Could cache this in display lists. // Find index bounds. Could cache this in display lists.
// Also, this could be greatly sped up with SSE2/NEON, although rarely a bottleneck. // Also, this could be greatly sped up with SSE2/NEON, although rarely a bottleneck.

View File

@ -72,10 +72,11 @@ struct DecVtxFormat {
u8 c1fmt; u8 c1off; u8 c1fmt; u8 c1off;
u8 nrmfmt; u8 nrmoff; u8 nrmfmt; u8 nrmoff;
u8 posfmt; u8 posoff; u8 posfmt; u8 posoff;
short stride; u8 stride;
uint32_t id; uint32_t id;
void ComputeID(); void ComputeID();
void InitializeFromID(uint32_t id);
}; };
void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBound, u16 *indexUpperBound); void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBound, u16 *indexUpperBound);

View File

@ -109,7 +109,7 @@ void DrawEngineGLES::DeviceRestore() {
void DrawEngineGLES::InitDeviceObjects() { void DrawEngineGLES::InitDeviceObjects() {
for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) { for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) {
frameData_[i].pushVertex = new GLPushBuffer(render_, GL_ARRAY_BUFFER, 1024 * 1024); frameData_[i].pushVertex = new GLPushBuffer(render_, GL_ARRAY_BUFFER, 256 * 1024);
frameData_[i].pushIndex = new GLPushBuffer(render_, GL_ELEMENT_ARRAY_BUFFER, 256 * 1024); frameData_[i].pushIndex = new GLPushBuffer(render_, GL_ELEMENT_ARRAY_BUFFER, 256 * 1024);
render_->RegisterPushBuffer(i, frameData_[i].pushVertex); render_->RegisterPushBuffer(i, frameData_[i].pushVertex);

View File

@ -419,7 +419,7 @@ void GPU_GLES::BeginFrame() {
GPUCommon::BeginFrame(); GPUCommon::BeginFrame();
// Save the cache from time to time. TODO: How often? // Save the cache from time to time. TODO: How often? We save on exit, so shouldn't need to do this all that often.
if (!shaderCachePath_.empty() && (gpuStats.numFlips & 4095) == 0) { if (!shaderCachePath_.empty() && (gpuStats.numFlips & 4095) == 0) {
shaderManagerGL_->Save(shaderCachePath_); shaderManagerGL_->Save(shaderCachePath_);
} }

View File

@ -255,9 +255,7 @@ void DrawEngineVulkan::BeginFrame() {
// TODO: How can we make this nicer... // TODO: How can we make this nicer...
((TessellationDataTransferVulkan *)tessDataTransfer)->SetPushBuffer(frame->pushUBO); ((TessellationDataTransferVulkan *)tessDataTransfer)->SetPushBuffer(frame->pushUBO);
// TODO : Find a better place to do this.
if (!nullTexture_) { if (!nullTexture_) {
ILOG("INIT : Creating null texture");
VkCommandBuffer cmdInit = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::INIT_COMMANDBUFFER); VkCommandBuffer cmdInit = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::INIT_COMMANDBUFFER);
nullTexture_ = new VulkanTexture(vulkan_, textureCache_->GetAllocator()); nullTexture_ = new VulkanTexture(vulkan_, textureCache_->GetAllocator());
int w = 8; int w = 8;

View File

@ -29,6 +29,7 @@
#include "Core/Config.h" #include "Core/Config.h"
#include "Core/Reporting.h" #include "Core/Reporting.h"
#include "Core/System.h" #include "Core/System.h"
#include "Core/ELF/ParamSFO.h"
#include "GPU/GPUState.h" #include "GPU/GPUState.h"
#include "GPU/ge_constants.h" #include "GPU/ge_constants.h"
@ -94,9 +95,57 @@ GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
if (vulkan_->GetFeaturesEnabled().wideLines) { if (vulkan_->GetFeaturesEnabled().wideLines) {
drawEngine_.SetLineWidth(PSP_CoreParameter().renderWidth / 480.0f); drawEngine_.SetLineWidth(PSP_CoreParameter().renderWidth / 480.0f);
} }
// Load shader cache.
std::string discID = g_paramSFO.GetDiscID();
if (discID.size()) {
File::CreateFullPath(GetSysDirectory(DIRECTORY_APP_CACHE));
shaderCachePath_ = GetSysDirectory(DIRECTORY_APP_CACHE) + "/" + discID + ".vkshadercache";
LoadCache(shaderCachePath_);
}
}
void GPU_Vulkan::LoadCache(std::string filename) {
PSP_SetLoading("Loading shader cache...");
// Actually precompiled by IsReady() since we're single-threaded.
FILE *f = File::OpenCFile(filename, "rb");
if (!f)
return;
// First compile shaders to SPIR-V, then load the pipeline cache and recreate the pipelines.
// It's when recreating the pipelines that the pipeline cache is useful - in the ideal case,
// it can just memcpy the finished shader binaries out of the pipeline cache file.
bool result = shaderManagerVulkan_->LoadCache(f);
if (result) {
VkRenderPass renderPass = g_Config.iRenderingMode == FB_BUFFERED_MODE ?
(VkRenderPass)draw_->GetNativeObject(Draw::NativeObject::FRAMEBUFFER_RENDERPASS) :
(VkRenderPass)draw_->GetNativeObject(Draw::NativeObject::BACKBUFFER_RENDERPASS);
result = pipelineManager_->LoadCache(f, false, shaderManagerVulkan_, &drawEngine_, drawEngine_.GetPipelineLayout(), renderPass);
}
fclose(f);
if (!result) {
WARN_LOG(G3D, "Bad Vulkan pipeline cache");
// Bad cache file for this GPU/Driver/etc. Delete it.
File::Delete(filename);
} else {
INFO_LOG(G3D, "Loaded Vulkan pipeline cache.");
}
}
void GPU_Vulkan::SaveCache(std::string filename) {
FILE *f = File::OpenCFile(filename, "wb");
if (!f)
return;
shaderManagerVulkan_->SaveCache(f);
pipelineManager_->SaveCache(f, false, shaderManagerVulkan_);
INFO_LOG(G3D, "Saved Vulkan pipeline cache");
fclose(f);
} }
GPU_Vulkan::~GPU_Vulkan() { GPU_Vulkan::~GPU_Vulkan() {
SaveCache(shaderCachePath_);
// Note: We save the cache in DeviceLost
DestroyDeviceObjects(); DestroyDeviceObjects();
framebufferManagerVulkan_->DestroyAllFBOs(); framebufferManagerVulkan_->DestroyAllFBOs();
vulkan2D_.Shutdown(); vulkan2D_.Shutdown();
@ -407,6 +456,9 @@ void GPU_Vulkan::DestroyDeviceObjects() {
} }
void GPU_Vulkan::DeviceLost() { void GPU_Vulkan::DeviceLost() {
if (!shaderCachePath_.empty()) {
SaveCache(shaderCachePath_);
}
DestroyDeviceObjects(); DestroyDeviceObjects();
framebufferManagerVulkan_->DeviceLost(); framebufferManagerVulkan_->DeviceLost();
vulkan2D_.DeviceLost(); vulkan2D_.DeviceLost();

View File

@ -80,6 +80,9 @@ private:
void InitDeviceObjects(); void InitDeviceObjects();
void DestroyDeviceObjects(); void DestroyDeviceObjects();
void LoadCache(std::string filename);
void SaveCache(std::string filename);
VulkanContext *vulkan_; VulkanContext *vulkan_;
FramebufferManagerVulkan *framebufferManagerVulkan_; FramebufferManagerVulkan *framebufferManagerVulkan_;
TextureCacheVulkan *textureCacheVulkan_; TextureCacheVulkan *textureCacheVulkan_;
@ -100,4 +103,6 @@ private:
}; };
FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]{}; FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]{};
std::string shaderCachePath_;
}; };

View File

@ -1,4 +1,5 @@
#include <cstring> #include <cstring>
#include <memory>
#include "profiler/profiler.h" #include "profiler/profiler.h"
@ -8,9 +9,10 @@
#include "GPU/Vulkan/VulkanUtil.h" #include "GPU/Vulkan/VulkanUtil.h"
#include "GPU/Vulkan/PipelineManagerVulkan.h" #include "GPU/Vulkan/PipelineManagerVulkan.h"
#include "GPU/Vulkan/ShaderManagerVulkan.h" #include "GPU/Vulkan/ShaderManagerVulkan.h"
#include "GPU/Common/DrawEngineCommon.h"
PipelineManagerVulkan::PipelineManagerVulkan(VulkanContext *vulkan) : vulkan_(vulkan), pipelines_(256) { PipelineManagerVulkan::PipelineManagerVulkan(VulkanContext *vulkan) : vulkan_(vulkan), pipelines_(256) {
pipelineCache_ = vulkan->CreatePipelineCache(); // The pipeline cache is created on demand (or explicitly through Load).
} }
PipelineManagerVulkan::~PipelineManagerVulkan() { PipelineManagerVulkan::~PipelineManagerVulkan() {
@ -40,7 +42,7 @@ void PipelineManagerVulkan::DeviceLost() {
void PipelineManagerVulkan::DeviceRestore(VulkanContext *vulkan) { void PipelineManagerVulkan::DeviceRestore(VulkanContext *vulkan) {
vulkan_ = vulkan; vulkan_ = vulkan;
pipelineCache_ = vulkan->CreatePipelineCache(); // The pipeline cache is created on demand.
} }
struct DeclTypeInfo { struct DeclTypeInfo {
@ -97,7 +99,7 @@ static int SetupVertexAttribs(VkVertexInputAttributeDescription attrs[], const D
return count; return count;
} }
static int SetupVertexAttribsPretransformed(VkVertexInputAttributeDescription attrs[], const DecVtxFormat &decFmt) { static int SetupVertexAttribsPretransformed(VkVertexInputAttributeDescription attrs[]) {
int count = 0; int count = 0;
VertexAttribSetup(&attrs[count++], DEC_FLOAT_4, 0, PspAttributeLocation::POSITION); VertexAttribSetup(&attrs[count++], DEC_FLOAT_4, 0, PspAttributeLocation::POSITION);
VertexAttribSetup(&attrs[count++], DEC_FLOAT_3, 16, PspAttributeLocation::TEXCOORD); VertexAttribSetup(&attrs[count++], DEC_FLOAT_3, 16, PspAttributeLocation::TEXCOORD);
@ -223,7 +225,7 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip
attributeCount = SetupVertexAttribs(attrs, *decFmt); attributeCount = SetupVertexAttribs(attrs, *decFmt);
vertexStride = decFmt->stride; vertexStride = decFmt->stride;
} else { } else {
attributeCount = SetupVertexAttribsPretransformed(attrs, *decFmt); attributeCount = SetupVertexAttribsPretransformed(attrs);
vertexStride = 36; vertexStride = 36;
} }
@ -293,6 +295,12 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip
} }
VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VkPipelineLayout layout, VkRenderPass renderPass, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform) { VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VkPipelineLayout layout, VkRenderPass renderPass, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform) {
if (!pipelineCache_) {
VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
VkResult res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
assert(VK_SUCCESS == res);
}
VulkanPipelineKey key{}; VulkanPipelineKey key{};
_assert_msg_(G3D, renderPass, "Can't create a pipeline with a null renderpass"); _assert_msg_(G3D, renderPass, "Can't create a pipeline with a null renderpass");
@ -301,7 +309,7 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VkPipelineLayout layo
key.useHWTransform = useHwTransform; key.useHWTransform = useHwTransform;
key.vShader = vs->GetModule(); key.vShader = vs->GetModule();
key.fShader = fs->GetModule(); key.fShader = fs->GetModule();
key.vtxDecId = useHwTransform ? decFmt->id : 0; key.vtxFmtId = useHwTransform ? decFmt->id : 0;
auto iter = pipelines_.Get(key); auto iter = pipelines_.Get(key);
if (iter) if (iter)
@ -459,9 +467,9 @@ std::string PipelineManagerVulkan::DebugGetObjectString(std::string id, DebugSha
if (pipelineKey.useHWTransform) { if (pipelineKey.useHWTransform) {
str << "HWX "; str << "HWX ";
} }
if (pipelineKey.vtxDecId) { if (pipelineKey.vtxFmtId) {
str << "V("; str << "V(";
str << StringFromFormat("%08x", pipelineKey.vtxDecId); // TODO: Format nicer. str << StringFromFormat("%08x", pipelineKey.vtxFmtId); // TODO: Format nicer.
str << ") "; str << ") ";
} else { } else {
str << "SWX "; str << "SWX ";
@ -492,3 +500,145 @@ void PipelineManagerVulkan::SetLineWidth(float lineWidth) {
} }
}); });
} }
// For some reason this struct is only defined in the spec, not in the headers.
struct VkPipelineCacheHeader {
uint32_t headerSize;
VkPipelineCacheHeaderVersion version;
uint32_t vendorId;
uint32_t deviceId;
uint8_t uuid[VK_UUID_SIZE];
};
void PipelineManagerVulkan::SaveCache(FILE *file, bool saveRawPipelineCache, ShaderManagerVulkan *shaderManager) {
size_t dataSize = 0;
uint32_t size;
if (saveRawPipelineCache) {
VkResult result = vkGetPipelineCacheData(vulkan_->GetDevice(), pipelineCache_, &dataSize, nullptr);
uint32_t size = (uint32_t)dataSize;
if (result != VK_SUCCESS) {
size = 0;
fwrite(&size, sizeof(size), 1, file);
return;
}
std::unique_ptr<uint8_t[]> buffer(new uint8_t[dataSize]);
vkGetPipelineCacheData(vulkan_->GetDevice(), pipelineCache_, &dataSize, buffer.get());
size = (uint32_t)dataSize;
fwrite(&size, sizeof(size), 1, file);
fwrite(buffer.get(), 1, size, file);
NOTICE_LOG(G3D, "Saved Vulkan pipeline cache (%d bytes).", (int)size);
}
size_t seekPosOnFailure = ftell(file);
// Write the number of pipelines.
size = (uint32_t)pipelines_.size();
fwrite(&size, sizeof(size), 1, file);
bool failed = false;
int count = 0;
pipelines_.Iterate([&](const VulkanPipelineKey &pkey, VulkanPipeline *value) {
if (failed)
return;
VulkanVertexShader *vshader = shaderManager->GetVertexShaderFromModule(pkey.vShader);
VulkanFragmentShader *fshader = shaderManager->GetFragmentShaderFromModule(pkey.fShader);
if (!vshader || !fshader) {
failed = true;
return;
}
StoredVulkanPipelineKey key{};
key.raster = pkey.raster;
key.useHWTransform = pkey.useHWTransform;
key.fShaderID = fshader->GetID();
key.vShaderID = vshader->GetID();
if (key.useHWTransform) {
// NOTE: This is not a vtype, but a decoded vertex format.
key.vtxFmtId = pkey.vtxFmtId;
}
fwrite(&key, sizeof(key), 1, file);
count++;
});
if (failed) {
ERROR_LOG(G3D, "Failed to write pipeline cache, some shader was missing");
// Write a zero in the right place so it doesn't try to load the pipelines next time.
size = 0;
fseek(file, (long)seekPosOnFailure, SEEK_SET);
fwrite(&size, sizeof(size), 1, file);
return;
}
NOTICE_LOG(G3D, "Saved Vulkan pipeline ID cache (%d pipelines).", (int)count);
}
bool PipelineManagerVulkan::LoadCache(FILE *file, bool loadRawPipelineCache, ShaderManagerVulkan *shaderManager, DrawEngineCommon *drawEngine, VkPipelineLayout layout, VkRenderPass renderPass) {
uint32_t size = 0;
if (loadRawPipelineCache) {
fread(&size, sizeof(size), 1, file);
if (!size) {
WARN_LOG(G3D, "Zero-sized Vulkan pipeline cache.");
return true;
}
std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
fread(buffer.get(), 1, size, file);
// Verify header.
VkPipelineCacheHeader *header = (VkPipelineCacheHeader *)buffer.get();
if (header->version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE) {
// Bad header, don't do anything.
WARN_LOG(G3D, "Bad Vulkan pipeline cache header - ignoring");
return false;
}
if (0 != memcmp(header->uuid, vulkan_->GetPhysicalDeviceProperties().pipelineCacheUUID, VK_UUID_SIZE)) {
// Wrong hardware/driver/etc.
WARN_LOG(G3D, "Bad Vulkan pipeline cache UUID - ignoring");
return false;
}
VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
pc.pInitialData = buffer.get();
pc.initialDataSize = size;
pc.flags = 0;
VkPipelineCache cache;
VkResult res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &cache);
if (res != VK_SUCCESS) {
return false;
}
if (!pipelineCache_) {
pipelineCache_ = cache;
} else {
vkMergePipelineCaches(vulkan_->GetDevice(), pipelineCache_, 1, &cache);
}
} else {
if (!pipelineCache_) {
VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
VkResult res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
}
NOTICE_LOG(G3D, "Loaded Vulkan pipeline cache (%d bytes).", (int)size);
}
// Read the number of pipelines.
fread(&size, sizeof(size), 1, file);
NOTICE_LOG(G3D, "Creating %d pipelines...", size);
bool failed = false;
for (uint32_t i = 0; i < size; i++) {
if (failed) {
continue;
}
StoredVulkanPipelineKey key;
fread(&key, sizeof(key), 1, file);
VulkanVertexShader *vs = shaderManager->GetVertexShaderFromID(key.vShaderID);
VulkanFragmentShader *fs = shaderManager->GetFragmentShaderFromID(key.fShaderID);
if (!vs || !fs) {
failed = true;
ERROR_LOG(G3D, "Failed to find vs or fs in of pipeline %d in cache", (int)i);
continue;
}
DecVtxFormat fmt;
fmt.InitializeFromID(key.vtxFmtId);
GetOrCreatePipeline(layout, renderPass, key.raster,
key.useHWTransform ? &fmt : 0,
vs, fs, key.useHWTransform);
}
NOTICE_LOG(G3D, "Recreated Vulkan pipeline cache (%d pipelines).", (int)size);
return true;
}

View File

@ -42,7 +42,7 @@ struct VulkanPipelineKey {
VkRenderPass renderPass; VkRenderPass renderPass;
VkShaderModule vShader; VkShaderModule vShader;
VkShaderModule fShader; VkShaderModule fShader;
uint32_t vtxDecId; uint32_t vtxFmtId;
bool useHWTransform; bool useHWTransform;
void ToString(std::string *str) const { void ToString(std::string *str) const {
@ -54,6 +54,14 @@ struct VulkanPipelineKey {
} }
}; };
struct StoredVulkanPipelineKey {
VulkanPipelineRasterStateKey raster;
VShaderID vShaderID;
FShaderID fShaderID;
uint32_t vtxFmtId;
bool useHWTransform;
};
enum PipelineFlags { enum PipelineFlags {
PIPELINE_FLAG_USES_BASE_UB = (1 << 0), PIPELINE_FLAG_USES_BASE_UB = (1 << 0),
PIPELINE_FLAG_USES_LIGHT_UB = (1 << 1), PIPELINE_FLAG_USES_LIGHT_UB = (1 << 1),
@ -74,6 +82,8 @@ struct VulkanPipeline {
class VulkanContext; class VulkanContext;
class VulkanVertexShader; class VulkanVertexShader;
class VulkanFragmentShader; class VulkanFragmentShader;
class ShaderManagerVulkan;
class DrawEngineCommon;
class PipelineManagerVulkan { class PipelineManagerVulkan {
public: public:
@ -93,9 +103,13 @@ public:
std::string DebugGetObjectString(std::string id, DebugShaderType type, DebugShaderStringType stringType); std::string DebugGetObjectString(std::string id, DebugShaderType type, DebugShaderStringType stringType);
std::vector<std::string> DebugGetObjectIDs(DebugShaderType type); std::vector<std::string> DebugGetObjectIDs(DebugShaderType type);
// Saves data for faster creation next time.
void SaveCache(FILE *file, bool saveRawPipelineCache, ShaderManagerVulkan *shaderManager);
bool LoadCache(FILE *file, bool loadRawPipelineCache, ShaderManagerVulkan *shaderManager, DrawEngineCommon *drawEngine, VkPipelineLayout layout, VkRenderPass renderPass);
private: private:
DenseHashMap<VulkanPipelineKey, VulkanPipeline *, nullptr> pipelines_; DenseHashMap<VulkanPipelineKey, VulkanPipeline *, nullptr> pipelines_;
VkPipelineCache pipelineCache_; VkPipelineCache pipelineCache_ = VK_NULL_HANDLE;
VulkanContext *vulkan_; VulkanContext *vulkan_;
float lineWidth_ = 1.0f; float lineWidth_ = 1.0f;
}; };

View File

@ -40,8 +40,8 @@
#include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h" #include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h"
#include "GPU/Vulkan/VertexShaderGeneratorVulkan.h" #include "GPU/Vulkan/VertexShaderGeneratorVulkan.h"
VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code, bool useHWTransform) VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code)
: vulkan_(vulkan), id_(id), failed_(false), useHWTransform_(useHWTransform), module_(0) { : vulkan_(vulkan), id_(id), failed_(false), module_(0) {
PROFILE_THIS_SCOPE("shadercomp"); PROFILE_THIS_SCOPE("shadercomp");
source_ = code; source_ = code;
@ -98,7 +98,7 @@ std::string VulkanFragmentShader::GetShaderString(DebugShaderStringType type) co
} }
} }
VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, const char *code, int vertType, bool useHWTransform, bool usesLighting) VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, const char *code, bool useHWTransform, bool usesLighting)
: vulkan_(vulkan), id_(id), failed_(false), useHWTransform_(useHWTransform), module_(VK_NULL_HANDLE), usesLighting_(usesLighting) { : vulkan_(vulkan), id_(id), failed_(false), useHWTransform_(useHWTransform), module_(VK_NULL_HANDLE), usesLighting_(usesLighting) {
PROFILE_THIS_SCOPE("shadercomp"); PROFILE_THIS_SCOPE("shadercomp");
source_ = code; source_ = code;
@ -254,7 +254,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader
// Vertex shader not in cache. Let's compile it. // Vertex shader not in cache. Let's compile it.
bool usesLighting; bool usesLighting;
GenerateVulkanGLSLVertexShader(VSID, codeBuffer_, &usesLighting); GenerateVulkanGLSLVertexShader(VSID, codeBuffer_, &usesLighting);
vs = new VulkanVertexShader(vulkan_, VSID, codeBuffer_, vertType, useHWTransform, usesLighting); vs = new VulkanVertexShader(vulkan_, VSID, codeBuffer_, useHWTransform, usesLighting);
vsCache_.Insert(VSID, vs); vsCache_.Insert(VSID, vs);
} }
lastVSID_ = VSID; lastVSID_ = VSID;
@ -263,7 +263,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader
if (!fs) { if (!fs) {
// Fragment shader not in cache. Let's compile it. // Fragment shader not in cache. Let's compile it.
GenerateVulkanGLSLFragmentShader(FSID, codeBuffer_); GenerateVulkanGLSLFragmentShader(FSID, codeBuffer_);
fs = new VulkanFragmentShader(vulkan_, FSID, codeBuffer_, useHWTransform); fs = new VulkanFragmentShader(vulkan_, FSID, codeBuffer_);
fsCache_.Insert(FSID, fs); fsCache_.Insert(FSID, fs);
} }
@ -323,3 +323,89 @@ std::string ShaderManagerVulkan::DebugGetShaderString(std::string id, DebugShade
return "N/A"; return "N/A";
} }
} }
VulkanVertexShader *ShaderManagerVulkan::GetVertexShaderFromModule(VkShaderModule module) {
VulkanVertexShader *vs = nullptr;
vsCache_.Iterate([&](const VShaderID &id, VulkanVertexShader *shader) {
if (shader->GetModule() == module)
vs = shader;
});
return vs;
}
VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderModule module) {
VulkanFragmentShader *fs = nullptr;
fsCache_.Iterate([&](const FShaderID &id, VulkanFragmentShader *shader) {
if (shader->GetModule() == module)
fs = shader;
});
return fs;
}
// 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.
#define CACHE_HEADER_MAGIC 0xff51f420
#define CACHE_VERSION 5
struct VulkanCacheHeader {
uint32_t magic;
uint32_t version;
uint32_t featureFlags;
uint32_t reserved;
int numVertexShaders;
int numFragmentShaders;
};
bool ShaderManagerVulkan::LoadCache(FILE *f) {
VulkanCacheHeader header{};
fread(&header, sizeof(header), 1, f);
if (header.magic != CACHE_HEADER_MAGIC)
return false;
if (header.version != CACHE_VERSION)
return false;
if (header.featureFlags != gstate_c.featureFlags)
return false;
for (int i = 0; i < header.numVertexShaders; i++) {
VShaderID id;
fread(&id, sizeof(id), 1, f);
bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM);
bool usesLighting;
GenerateVulkanGLSLVertexShader(id, codeBuffer_, &usesLighting);
VulkanVertexShader *vs = new VulkanVertexShader(vulkan_, id, codeBuffer_, useHWTransform, usesLighting);
vsCache_.Insert(id, vs);
}
for (int i = 0; i < header.numFragmentShaders; i++) {
FShaderID id;
fread(&id, sizeof(id), 1, f);
GenerateVulkanGLSLFragmentShader(id, codeBuffer_);
VulkanFragmentShader *fs = new VulkanFragmentShader(vulkan_, id, codeBuffer_);
fsCache_.Insert(id, fs);
}
NOTICE_LOG(G3D, "Loaded %d vertex and %d fragment shaders", header.numVertexShaders, header.numFragmentShaders);
return true;
}
void ShaderManagerVulkan::SaveCache(FILE *f) {
VulkanCacheHeader header{};
header.magic = CACHE_HEADER_MAGIC;
header.version = CACHE_VERSION;
header.featureFlags = gstate_c.featureFlags;
header.reserved = 0;
header.numVertexShaders = (int)vsCache_.size();
header.numFragmentShaders = (int)fsCache_.size();
fwrite(&header, sizeof(header), 1, f);
vsCache_.Iterate([&](const VShaderID &id, VulkanVertexShader *vs) {
fwrite(&id, sizeof(id), 1, f);
});
fsCache_.Iterate([&](const FShaderID &id, VulkanFragmentShader *fs) {
fwrite(&id, sizeof(id), 1, f);
});
NOTICE_LOG(G3D, "Saved %d vertex and %d fragment shaders", header.numVertexShaders, header.numFragmentShaders);
}

View File

@ -17,6 +17,8 @@
#pragma once #pragma once
#include <cstdio>
#include "base/basictypes.h" #include "base/basictypes.h"
#include "Common/Hashmaps.h" #include "Common/Hashmaps.h"
#include "Common/Vulkan/VulkanMemory.h" #include "Common/Vulkan/VulkanMemory.h"
@ -33,16 +35,16 @@ class VulkanPushBuffer;
class VulkanFragmentShader { class VulkanFragmentShader {
public: public:
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code, bool useHWTransform); VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code);
~VulkanFragmentShader(); ~VulkanFragmentShader();
const std::string &source() const { return source_; } const std::string &source() const { return source_; }
bool Failed() const { return failed_; } bool Failed() const { return failed_; }
bool UseHWTransform() const { return useHWTransform_; }
std::string GetShaderString(DebugShaderStringType type) const; std::string GetShaderString(DebugShaderStringType type) const;
VkShaderModule GetModule() const { return module_; } VkShaderModule GetModule() const { return module_; }
const FShaderID &GetID() { return id_; }
protected: protected:
VkShaderModule module_; VkShaderModule module_;
@ -50,13 +52,12 @@ protected:
VulkanContext *vulkan_; VulkanContext *vulkan_;
std::string source_; std::string source_;
bool failed_; bool failed_;
bool useHWTransform_;
FShaderID id_; FShaderID id_;
}; };
class VulkanVertexShader { class VulkanVertexShader {
public: public:
VulkanVertexShader(VulkanContext *vulkan, VShaderID id, const char *code, int vertType, bool useHWTransform, bool usesLighting); VulkanVertexShader(VulkanContext *vulkan, VShaderID id, const char *code, bool useHWTransform, bool usesLighting);
~VulkanVertexShader(); ~VulkanVertexShader();
const std::string &source() const { return source_; } const std::string &source() const { return source_; }
@ -69,6 +70,7 @@ public:
std::string GetShaderString(DebugShaderStringType type) const; std::string GetShaderString(DebugShaderStringType type) const;
VkShaderModule GetModule() const { return module_; } VkShaderModule GetModule() const { return module_; }
const VShaderID &GetID() { return id_; }
protected: protected:
VkShaderModule module_; VkShaderModule module_;
@ -98,6 +100,12 @@ public:
int GetNumVertexShaders() const { return (int)vsCache_.size(); } int GetNumVertexShaders() const { return (int)vsCache_.size(); }
int GetNumFragmentShaders() const { return (int)fsCache_.size(); } int GetNumFragmentShaders() const { return (int)fsCache_.size(); }
// Used for saving/loading the cache. Don't need to be particularly fast.
VulkanVertexShader *GetVertexShaderFromID(VShaderID id) { return vsCache_.Get(id); }
VulkanFragmentShader *GetFragmentShaderFromID(FShaderID id) { return fsCache_.Get(id); }
VulkanVertexShader *GetVertexShaderFromModule(VkShaderModule module);
VulkanFragmentShader *GetFragmentShaderFromModule(VkShaderModule module);
std::vector<std::string> DebugGetShaderIDs(DebugShaderType type); std::vector<std::string> DebugGetShaderIDs(DebugShaderType type);
std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType); std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType);
@ -115,6 +123,9 @@ public:
return dest->PushAligned(&ub_lights, sizeof(ub_lights), uboAlignment_, buf); return dest->PushAligned(&ub_lights, sizeof(ub_lights), uboAlignment_, buf);
} }
bool LoadCache(FILE *f);
void SaveCache(FILE *f);
private: private:
void Clear(); void Clear();

View File

@ -62,7 +62,10 @@ void Vulkan2D::DestroyDeviceObjects() {
} }
void Vulkan2D::InitDeviceObjects() { void Vulkan2D::InitDeviceObjects() {
pipelineCache_ = vulkan_->CreatePipelineCache(); VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
VkResult res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
assert(VK_SUCCESS == res);
VkDescriptorSetLayoutBinding bindings[2] = {}; VkDescriptorSetLayoutBinding bindings[2] = {};
// Texture. // Texture.
bindings[0].descriptorCount = 1; bindings[0].descriptorCount = 1;
@ -80,7 +83,7 @@ void Vulkan2D::InitDeviceObjects() {
VkDescriptorSetLayoutCreateInfo dsl = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; VkDescriptorSetLayoutCreateInfo dsl = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
dsl.bindingCount = 2; dsl.bindingCount = 2;
dsl.pBindings = bindings; dsl.pBindings = bindings;
VkResult res = vkCreateDescriptorSetLayout(device, &dsl, nullptr, &descriptorSetLayout_); res = vkCreateDescriptorSetLayout(device, &dsl, nullptr, &descriptorSetLayout_);
assert(VK_SUCCESS == res); assert(VK_SUCCESS == res);
VkDescriptorPoolSize dpTypes[1]; VkDescriptorPoolSize dpTypes[1];

View File

@ -773,7 +773,9 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
res = vkCreatePipelineLayout(device_, &pl, nullptr, &pipelineLayout_); res = vkCreatePipelineLayout(device_, &pl, nullptr, &pipelineLayout_);
assert(VK_SUCCESS == res); assert(VK_SUCCESS == res);
pipelineCache_ = vulkan_->CreatePipelineCache(); VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
assert(VK_SUCCESS == res);
renderManager_.SetSplitSubmit(splitSubmit); renderManager_.SetSplitSubmit(splitSubmit);