mirror of
https://github.com/libretro/ppsspp.git
synced 2024-12-02 14:16:53 +00:00
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:
parent
2d33d526b8
commit
614cabb115
@ -937,17 +937,6 @@ void VulkanContext::DestroyDevice() {
|
||||
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) {
|
||||
VkShaderModuleCreateInfo sm{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
|
||||
sm.pCode = spirv.data();
|
||||
|
@ -142,8 +142,6 @@ public:
|
||||
|
||||
VulkanDeleteList &Delete() { return globalDeleteList_; }
|
||||
|
||||
VkPipelineCache CreatePipelineCache();
|
||||
|
||||
// The parameters are whatever the chosen window system wants.
|
||||
void InitSurface(WindowSystem winsys, void *data1, void *data2, int width = -1, int height = -1);
|
||||
void ReinitSurface(int width = -1, int height = -1);
|
||||
|
@ -88,6 +88,8 @@ public:
|
||||
return numDrawCalls;
|
||||
}
|
||||
|
||||
VertexDecoder *GetVertexDecoder(u32 vtype);
|
||||
|
||||
protected:
|
||||
virtual void ClearTrackedVertexArrays() {}
|
||||
|
||||
@ -106,8 +108,6 @@ protected:
|
||||
|
||||
bool ApplyShaderBlending();
|
||||
|
||||
VertexDecoder *GetVertexDecoder(u32 vtype);
|
||||
|
||||
inline int IndexSize(u32 vtype) const {
|
||||
const u32 indexType = (vtype & GE_VTYPE_IDX_MASK);
|
||||
if (indexType == GE_VTYPE_IDX_16BIT) {
|
||||
|
@ -75,6 +75,21 @@ void DecVtxFormat::ComputeID() {
|
||||
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) {
|
||||
// Find index bounds. Could cache this in display lists.
|
||||
// Also, this could be greatly sped up with SSE2/NEON, although rarely a bottleneck.
|
||||
|
@ -72,10 +72,11 @@ struct DecVtxFormat {
|
||||
u8 c1fmt; u8 c1off;
|
||||
u8 nrmfmt; u8 nrmoff;
|
||||
u8 posfmt; u8 posoff;
|
||||
short stride;
|
||||
u8 stride;
|
||||
|
||||
uint32_t id;
|
||||
void ComputeID();
|
||||
void InitializeFromID(uint32_t id);
|
||||
};
|
||||
|
||||
void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBound, u16 *indexUpperBound);
|
||||
|
@ -109,7 +109,7 @@ void DrawEngineGLES::DeviceRestore() {
|
||||
|
||||
void DrawEngineGLES::InitDeviceObjects() {
|
||||
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);
|
||||
|
||||
render_->RegisterPushBuffer(i, frameData_[i].pushVertex);
|
||||
|
@ -419,7 +419,7 @@ void GPU_GLES::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) {
|
||||
shaderManagerGL_->Save(shaderCachePath_);
|
||||
}
|
||||
|
@ -255,9 +255,7 @@ void DrawEngineVulkan::BeginFrame() {
|
||||
// TODO: How can we make this nicer...
|
||||
((TessellationDataTransferVulkan *)tessDataTransfer)->SetPushBuffer(frame->pushUBO);
|
||||
|
||||
// TODO : Find a better place to do this.
|
||||
if (!nullTexture_) {
|
||||
ILOG("INIT : Creating null texture");
|
||||
VkCommandBuffer cmdInit = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::INIT_COMMANDBUFFER);
|
||||
nullTexture_ = new VulkanTexture(vulkan_, textureCache_->GetAllocator());
|
||||
int w = 8;
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "Core/Config.h"
|
||||
#include "Core/Reporting.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/ELF/ParamSFO.h"
|
||||
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/ge_constants.h"
|
||||
@ -94,9 +95,57 @@ GPU_Vulkan::GPU_Vulkan(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
|
||||
if (vulkan_->GetFeaturesEnabled().wideLines) {
|
||||
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() {
|
||||
SaveCache(shaderCachePath_);
|
||||
// Note: We save the cache in DeviceLost
|
||||
DestroyDeviceObjects();
|
||||
framebufferManagerVulkan_->DestroyAllFBOs();
|
||||
vulkan2D_.Shutdown();
|
||||
@ -407,6 +456,9 @@ void GPU_Vulkan::DestroyDeviceObjects() {
|
||||
}
|
||||
|
||||
void GPU_Vulkan::DeviceLost() {
|
||||
if (!shaderCachePath_.empty()) {
|
||||
SaveCache(shaderCachePath_);
|
||||
}
|
||||
DestroyDeviceObjects();
|
||||
framebufferManagerVulkan_->DeviceLost();
|
||||
vulkan2D_.DeviceLost();
|
||||
|
@ -80,6 +80,9 @@ private:
|
||||
void InitDeviceObjects();
|
||||
void DestroyDeviceObjects();
|
||||
|
||||
void LoadCache(std::string filename);
|
||||
void SaveCache(std::string filename);
|
||||
|
||||
VulkanContext *vulkan_;
|
||||
FramebufferManagerVulkan *framebufferManagerVulkan_;
|
||||
TextureCacheVulkan *textureCacheVulkan_;
|
||||
@ -100,4 +103,6 @@ private:
|
||||
};
|
||||
|
||||
FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]{};
|
||||
|
||||
std::string shaderCachePath_;
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "profiler/profiler.h"
|
||||
|
||||
@ -8,9 +9,10 @@
|
||||
#include "GPU/Vulkan/VulkanUtil.h"
|
||||
#include "GPU/Vulkan/PipelineManagerVulkan.h"
|
||||
#include "GPU/Vulkan/ShaderManagerVulkan.h"
|
||||
#include "GPU/Common/DrawEngineCommon.h"
|
||||
|
||||
PipelineManagerVulkan::PipelineManagerVulkan(VulkanContext *vulkan) : vulkan_(vulkan), pipelines_(256) {
|
||||
pipelineCache_ = vulkan->CreatePipelineCache();
|
||||
// The pipeline cache is created on demand (or explicitly through Load).
|
||||
}
|
||||
|
||||
PipelineManagerVulkan::~PipelineManagerVulkan() {
|
||||
@ -40,7 +42,7 @@ void PipelineManagerVulkan::DeviceLost() {
|
||||
|
||||
void PipelineManagerVulkan::DeviceRestore(VulkanContext *vulkan) {
|
||||
vulkan_ = vulkan;
|
||||
pipelineCache_ = vulkan->CreatePipelineCache();
|
||||
// The pipeline cache is created on demand.
|
||||
}
|
||||
|
||||
struct DeclTypeInfo {
|
||||
@ -97,7 +99,7 @@ static int SetupVertexAttribs(VkVertexInputAttributeDescription attrs[], const D
|
||||
return count;
|
||||
}
|
||||
|
||||
static int SetupVertexAttribsPretransformed(VkVertexInputAttributeDescription attrs[], const DecVtxFormat &decFmt) {
|
||||
static int SetupVertexAttribsPretransformed(VkVertexInputAttributeDescription attrs[]) {
|
||||
int count = 0;
|
||||
VertexAttribSetup(&attrs[count++], DEC_FLOAT_4, 0, PspAttributeLocation::POSITION);
|
||||
VertexAttribSetup(&attrs[count++], DEC_FLOAT_3, 16, PspAttributeLocation::TEXCOORD);
|
||||
@ -223,7 +225,7 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip
|
||||
attributeCount = SetupVertexAttribs(attrs, *decFmt);
|
||||
vertexStride = decFmt->stride;
|
||||
} else {
|
||||
attributeCount = SetupVertexAttribsPretransformed(attrs, *decFmt);
|
||||
attributeCount = SetupVertexAttribsPretransformed(attrs);
|
||||
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) {
|
||||
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{};
|
||||
_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.vShader = vs->GetModule();
|
||||
key.fShader = fs->GetModule();
|
||||
key.vtxDecId = useHwTransform ? decFmt->id : 0;
|
||||
key.vtxFmtId = useHwTransform ? decFmt->id : 0;
|
||||
|
||||
auto iter = pipelines_.Get(key);
|
||||
if (iter)
|
||||
@ -459,9 +467,9 @@ std::string PipelineManagerVulkan::DebugGetObjectString(std::string id, DebugSha
|
||||
if (pipelineKey.useHWTransform) {
|
||||
str << "HWX ";
|
||||
}
|
||||
if (pipelineKey.vtxDecId) {
|
||||
if (pipelineKey.vtxFmtId) {
|
||||
str << "V(";
|
||||
str << StringFromFormat("%08x", pipelineKey.vtxDecId); // TODO: Format nicer.
|
||||
str << StringFromFormat("%08x", pipelineKey.vtxFmtId); // TODO: Format nicer.
|
||||
str << ") ";
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ struct VulkanPipelineKey {
|
||||
VkRenderPass renderPass;
|
||||
VkShaderModule vShader;
|
||||
VkShaderModule fShader;
|
||||
uint32_t vtxDecId;
|
||||
uint32_t vtxFmtId;
|
||||
bool useHWTransform;
|
||||
|
||||
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 {
|
||||
PIPELINE_FLAG_USES_BASE_UB = (1 << 0),
|
||||
PIPELINE_FLAG_USES_LIGHT_UB = (1 << 1),
|
||||
@ -74,6 +82,8 @@ struct VulkanPipeline {
|
||||
class VulkanContext;
|
||||
class VulkanVertexShader;
|
||||
class VulkanFragmentShader;
|
||||
class ShaderManagerVulkan;
|
||||
class DrawEngineCommon;
|
||||
|
||||
class PipelineManagerVulkan {
|
||||
public:
|
||||
@ -93,9 +103,13 @@ public:
|
||||
std::string DebugGetObjectString(std::string id, DebugShaderType type, DebugShaderStringType stringType);
|
||||
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:
|
||||
DenseHashMap<VulkanPipelineKey, VulkanPipeline *, nullptr> pipelines_;
|
||||
VkPipelineCache pipelineCache_;
|
||||
VkPipelineCache pipelineCache_ = VK_NULL_HANDLE;
|
||||
VulkanContext *vulkan_;
|
||||
float lineWidth_ = 1.0f;
|
||||
};
|
||||
|
@ -40,8 +40,8 @@
|
||||
#include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h"
|
||||
#include "GPU/Vulkan/VertexShaderGeneratorVulkan.h"
|
||||
|
||||
VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code, bool useHWTransform)
|
||||
: vulkan_(vulkan), id_(id), failed_(false), useHWTransform_(useHWTransform), module_(0) {
|
||||
VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code)
|
||||
: vulkan_(vulkan), id_(id), failed_(false), module_(0) {
|
||||
PROFILE_THIS_SCOPE("shadercomp");
|
||||
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) {
|
||||
PROFILE_THIS_SCOPE("shadercomp");
|
||||
source_ = code;
|
||||
@ -254,7 +254,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader
|
||||
// Vertex shader not in cache. Let's compile it.
|
||||
bool 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);
|
||||
}
|
||||
lastVSID_ = VSID;
|
||||
@ -263,7 +263,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader
|
||||
if (!fs) {
|
||||
// Fragment shader not in cache. Let's compile it.
|
||||
GenerateVulkanGLSLFragmentShader(FSID, codeBuffer_);
|
||||
fs = new VulkanFragmentShader(vulkan_, FSID, codeBuffer_, useHWTransform);
|
||||
fs = new VulkanFragmentShader(vulkan_, FSID, codeBuffer_);
|
||||
fsCache_.Insert(FSID, fs);
|
||||
}
|
||||
|
||||
@ -323,3 +323,89 @@ std::string ShaderManagerVulkan::DebugGetShaderString(std::string id, DebugShade
|
||||
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);
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "Common/Hashmaps.h"
|
||||
#include "Common/Vulkan/VulkanMemory.h"
|
||||
@ -33,16 +35,16 @@ class VulkanPushBuffer;
|
||||
|
||||
class VulkanFragmentShader {
|
||||
public:
|
||||
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code, bool useHWTransform);
|
||||
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code);
|
||||
~VulkanFragmentShader();
|
||||
|
||||
const std::string &source() const { return source_; }
|
||||
|
||||
bool Failed() const { return failed_; }
|
||||
bool UseHWTransform() const { return useHWTransform_; }
|
||||
|
||||
std::string GetShaderString(DebugShaderStringType type) const;
|
||||
VkShaderModule GetModule() const { return module_; }
|
||||
const FShaderID &GetID() { return id_; }
|
||||
|
||||
protected:
|
||||
VkShaderModule module_;
|
||||
@ -50,13 +52,12 @@ protected:
|
||||
VulkanContext *vulkan_;
|
||||
std::string source_;
|
||||
bool failed_;
|
||||
bool useHWTransform_;
|
||||
FShaderID id_;
|
||||
};
|
||||
|
||||
class VulkanVertexShader {
|
||||
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();
|
||||
|
||||
const std::string &source() const { return source_; }
|
||||
@ -69,6 +70,7 @@ public:
|
||||
|
||||
std::string GetShaderString(DebugShaderStringType type) const;
|
||||
VkShaderModule GetModule() const { return module_; }
|
||||
const VShaderID &GetID() { return id_; }
|
||||
|
||||
protected:
|
||||
VkShaderModule module_;
|
||||
@ -98,6 +100,12 @@ public:
|
||||
int GetNumVertexShaders() const { return (int)vsCache_.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::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType);
|
||||
|
||||
@ -115,6 +123,9 @@ public:
|
||||
return dest->PushAligned(&ub_lights, sizeof(ub_lights), uboAlignment_, buf);
|
||||
}
|
||||
|
||||
bool LoadCache(FILE *f);
|
||||
void SaveCache(FILE *f);
|
||||
|
||||
private:
|
||||
void Clear();
|
||||
|
||||
|
@ -62,7 +62,10 @@ void Vulkan2D::DestroyDeviceObjects() {
|
||||
}
|
||||
|
||||
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] = {};
|
||||
// Texture.
|
||||
bindings[0].descriptorCount = 1;
|
||||
@ -80,7 +83,7 @@ void Vulkan2D::InitDeviceObjects() {
|
||||
VkDescriptorSetLayoutCreateInfo dsl = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
|
||||
dsl.bindingCount = 2;
|
||||
dsl.pBindings = bindings;
|
||||
VkResult res = vkCreateDescriptorSetLayout(device, &dsl, nullptr, &descriptorSetLayout_);
|
||||
res = vkCreateDescriptorSetLayout(device, &dsl, nullptr, &descriptorSetLayout_);
|
||||
assert(VK_SUCCESS == res);
|
||||
|
||||
VkDescriptorPoolSize dpTypes[1];
|
||||
|
@ -773,7 +773,9 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
|
||||
res = vkCreatePipelineLayout(device_, &pl, nullptr, &pipelineLayout_);
|
||||
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user