perf: add Vulkan pipeline cache persistence for faster startup

Create a VkPipelineCache at device init, loaded from disk if available.
All 65 pipeline creation calls across 19 renderer files now use the
shared cache. On shutdown, the cache is serialized to disk so subsequent
launches skip redundant shader compilation.

Cache path: ~/.local/share/wowee/pipeline_cache.bin (Linux),
~/Library/Caches/wowee/ (macOS), %APPDATA%\wowee\ (Windows).
Stale/corrupt caches are handled gracefully (fallback to empty cache).
This commit is contained in:
Kelsi 2026-03-24 09:47:03 -07:00
parent c18720f0f0
commit c8c01f8ac0
23 changed files with 192 additions and 72 deletions

View file

@ -74,6 +74,7 @@ public:
uint32_t getGraphicsQueueFamily() const { return graphicsQueueFamily; } uint32_t getGraphicsQueueFamily() const { return graphicsQueueFamily; }
VmaAllocator getAllocator() const { return allocator; } VmaAllocator getAllocator() const { return allocator; }
VkSurfaceKHR getSurface() const { return surface; } VkSurfaceKHR getSurface() const { return surface; }
VkPipelineCache getPipelineCache() const { return pipelineCache_; }
VkSwapchainKHR getSwapchain() const { return swapchain; } VkSwapchainKHR getSwapchain() const { return swapchain; }
VkFormat getSwapchainFormat() const { return swapchainFormat; } VkFormat getSwapchainFormat() const { return swapchainFormat; }
@ -130,6 +131,8 @@ private:
void destroySwapchain(); void destroySwapchain();
bool createCommandPools(); bool createCommandPools();
bool createSyncObjects(); bool createSyncObjects();
bool createPipelineCache();
void savePipelineCache();
bool createImGuiResources(); bool createImGuiResources();
void destroyImGuiResources(); void destroyImGuiResources();
@ -144,6 +147,9 @@ private:
VkDevice device = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE;
VmaAllocator allocator = VK_NULL_HANDLE; VmaAllocator allocator = VK_NULL_HANDLE;
// Pipeline cache (persisted to disk for faster startup)
VkPipelineCache pipelineCache_ = VK_NULL_HANDLE;
VkQueue graphicsQueue = VK_NULL_HANDLE; VkQueue graphicsQueue = VK_NULL_HANDLE;
VkQueue presentQueue = VK_NULL_HANDLE; VkQueue presentQueue = VK_NULL_HANDLE;
uint32_t graphicsQueueFamily = 0; uint32_t graphicsQueueFamily = 0;

View file

@ -75,8 +75,8 @@ public:
// Dynamic state // Dynamic state
PipelineBuilder& setDynamicStates(const std::vector<VkDynamicState>& states); PipelineBuilder& setDynamicStates(const std::vector<VkDynamicState>& states);
// Build the pipeline // Build the pipeline (pass a VkPipelineCache for faster creation)
VkPipeline build(VkDevice device) const; VkPipeline build(VkDevice device, VkPipelineCache cache = VK_NULL_HANDLE) const;
// Common blend states // Common blend states
static VkPipelineColorBlendAttachmentState blendDisabled(); static VkPipelineColorBlendAttachmentState blendDisabled();

View file

@ -90,7 +90,7 @@ bool Celestial::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -162,7 +162,7 @@ void Celestial::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -264,7 +264,7 @@ bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFram
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
}; };
opaquePipeline_ = buildCharPipeline(PipelineBuilder::blendDisabled(), true); opaquePipeline_ = buildCharPipeline(PipelineBuilder::blendDisabled(), true);
@ -2648,7 +2648,7 @@ bool CharacterRenderer::initializeShadow(VkRenderPass shadowRenderPass) {
.setLayout(shadowPipelineLayout_) .setLayout(shadowPipelineLayout_)
.setRenderPass(shadowRenderPass) .setRenderPass(shadowRenderPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();
@ -3315,7 +3315,7 @@ void CharacterRenderer::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
}; };
LOG_INFO("CharacterRenderer::recreatePipelines: renderPass=", (void*)mainPass, LOG_INFO("CharacterRenderer::recreatePipelines: renderPass=", (void*)mainPass,

View file

@ -101,7 +101,7 @@ bool ChargeEffect::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayo
.setLayout(ribbonPipelineLayout_) .setLayout(ribbonPipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -165,7 +165,7 @@ bool ChargeEffect::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayo
.setLayout(dustPipelineLayout_) .setLayout(dustPipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -314,7 +314,7 @@ void ChargeEffect::recreatePipelines() {
.setLayout(ribbonPipelineLayout_) .setLayout(ribbonPipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -360,7 +360,7 @@ void ChargeEffect::recreatePipelines() {
.setLayout(dustPipelineLayout_) .setLayout(dustPipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -83,7 +83,7 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -149,7 +149,7 @@ void Clouds::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -109,7 +109,7 @@ bool LensFlare::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayou
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
// Shader modules can be freed after pipeline creation // Shader modules can be freed after pipeline creation
vertModule.destroy(); vertModule.destroy();
@ -198,7 +198,7 @@ void LensFlare::recreatePipelines() {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -107,7 +107,7 @@ bool Lightning::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
.setLayout(boltPipelineLayout) .setLayout(boltPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -169,7 +169,7 @@ bool Lightning::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
.setLayout(flashPipelineLayout) .setLayout(flashPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -306,7 +306,7 @@ void Lightning::recreatePipelines() {
.setLayout(boltPipelineLayout) .setLayout(boltPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -344,7 +344,7 @@ void Lightning::recreatePipelines() {
.setLayout(flashPipelineLayout) .setLayout(flashPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -507,7 +507,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
}; };
opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true); opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true);
@ -542,7 +542,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
.setLayout(particlePipelineLayout_) .setLayout(particlePipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
}; };
particlePipeline_ = buildParticlePipeline(PipelineBuilder::blendAlpha()); particlePipeline_ = buildParticlePipeline(PipelineBuilder::blendAlpha());
@ -575,7 +575,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
.setLayout(smokePipelineLayout_) .setLayout(smokePipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
} }
// --- Build ribbon pipelines --- // --- Build ribbon pipelines ---
@ -617,7 +617,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
.setLayout(ribbonPipelineLayout_) .setLayout(ribbonPipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
}; };
ribbonPipeline_ = buildRibbonPipeline(PipelineBuilder::blendAlpha()); ribbonPipeline_ = buildRibbonPipeline(PipelineBuilder::blendAlpha());
@ -3228,7 +3228,7 @@ bool M2Renderer::initializeShadow(VkRenderPass shadowRenderPass) {
.setLayout(shadowPipelineLayout_) .setLayout(shadowPipelineLayout_)
.setRenderPass(shadowRenderPass) .setRenderPass(shadowRenderPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();
@ -5076,7 +5076,7 @@ void M2Renderer::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
}; };
opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true); opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true);
@ -5111,7 +5111,7 @@ void M2Renderer::recreatePipelines() {
.setLayout(particlePipelineLayout_) .setLayout(particlePipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
}; };
particlePipeline_ = buildParticlePipeline(PipelineBuilder::blendAlpha()); particlePipeline_ = buildParticlePipeline(PipelineBuilder::blendAlpha());
@ -5144,7 +5144,7 @@ void M2Renderer::recreatePipelines() {
.setLayout(smokePipelineLayout_) .setLayout(smokePipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
} }
// --- Ribbon pipelines --- // --- Ribbon pipelines ---
@ -5178,7 +5178,7 @@ void M2Renderer::recreatePipelines() {
.setLayout(ribbonPipelineLayout_) .setLayout(ribbonPipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
}; };
ribbonPipeline_ = buildRibbonPipeline(PipelineBuilder::blendAlpha()); ribbonPipeline_ = buildRibbonPipeline(PipelineBuilder::blendAlpha());

View file

@ -165,7 +165,7 @@ bool Minimap::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayout*
.setLayout(tilePipelineLayout) .setLayout(tilePipelineLayout)
.setRenderPass(compositeTarget->getRenderPass()) .setRenderPass(compositeTarget->getRenderPass())
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
vs.destroy(); vs.destroy();
fs.destroy(); fs.destroy();
@ -192,7 +192,7 @@ bool Minimap::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayout*
.setLayout(displayPipelineLayout) .setLayout(displayPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
vs.destroy(); vs.destroy();
fs.destroy(); fs.destroy();
@ -270,7 +270,7 @@ void Minimap::recreatePipelines() {
.setLayout(displayPipelineLayout) .setLayout(displayPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
vs.destroy(); vs.destroy();
fs.destroy(); fs.destroy();

View file

@ -92,7 +92,7 @@ bool MountDust::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -199,7 +199,7 @@ void MountDust::recreatePipelines() {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -114,7 +114,7 @@ bool QuestMarkerRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFr
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -233,7 +233,7 @@ void QuestMarkerRenderer::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass()) .setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx_->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -3820,7 +3820,7 @@ void Renderer::initSelectionCircle() {
.setLayout(selCirclePipelineLayout) .setLayout(selCirclePipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();
@ -3932,7 +3932,7 @@ void Renderer::initOverlayPipeline() {
.setLayout(overlayPipelineLayout) .setLayout(overlayPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx->getPipelineCache());
vertMod.destroy(); fragMod.destroy(); vertMod.destroy(); fragMod.destroy();
@ -4144,7 +4144,7 @@ bool Renderer::initFSRResources() {
.setLayout(fsr_.pipelineLayout) .setLayout(fsr_.pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx->getPipelineCache());
vertMod.destroy(); vertMod.destroy();
fragMod.destroy(); fragMod.destroy();
@ -4668,7 +4668,7 @@ bool Renderer::initFSR2Resources() {
.setLayout(fsr2_.sharpenPipelineLayout) .setLayout(fsr2_.sharpenPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx->getPipelineCache());
vertMod.destroy(); vertMod.destroy();
fragMod.destroy(); fragMod.destroy();
@ -5353,7 +5353,7 @@ bool Renderer::initFXAAResources() {
.setLayout(fxaa_.pipelineLayout) .setLayout(fxaa_.pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx->getPipelineCache());
vertMod.destroy(); vertMod.destroy();
fragMod.destroy(); fragMod.destroy();

View file

@ -81,7 +81,7 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
// Shader modules can be freed after pipeline creation // Shader modules can be freed after pipeline creation
vertModule.destroy(); vertModule.destroy();
@ -133,7 +133,7 @@ void Skybox::recreatePipelines() {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -91,7 +91,7 @@ bool StarField::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout)
.setMultisample(vkCtx->getMsaaSamples()) .setMultisample(vkCtx->getMsaaSamples())
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -164,7 +164,7 @@ void StarField::recreatePipelines() {
.setMultisample(vkCtx->getMsaaSamples()) .setMultisample(vkCtx->getMsaaSamples())
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -98,7 +98,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
.setLayout(ripplePipelineLayout) .setLayout(ripplePipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -142,7 +142,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
.setLayout(bubblePipelineLayout) .setLayout(bubblePipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -186,7 +186,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
.setLayout(insectPipelineLayout) .setLayout(insectPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -366,7 +366,7 @@ void SwimEffects::recreatePipelines() {
.setLayout(ripplePipelineLayout) .setLayout(ripplePipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -393,7 +393,7 @@ void SwimEffects::recreatePipelines() {
.setLayout(bubblePipelineLayout) .setLayout(bubblePipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -420,7 +420,7 @@ void SwimEffects::recreatePipelines() {
.setLayout(insectPipelineLayout) .setLayout(insectPipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -143,7 +143,7 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
if (!pipeline) { if (!pipeline) {
LOG_ERROR("TerrainRenderer: failed to create fill pipeline"); LOG_ERROR("TerrainRenderer: failed to create fill pipeline");
@ -165,7 +165,7 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
if (!wireframePipeline) { if (!wireframePipeline) {
LOG_WARNING("TerrainRenderer: wireframe pipeline not available"); LOG_WARNING("TerrainRenderer: wireframe pipeline not available");
@ -245,7 +245,7 @@ void TerrainRenderer::recreatePipelines() {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
if (!pipeline) { if (!pipeline) {
LOG_ERROR("TerrainRenderer::recreatePipelines: failed to create fill pipeline"); LOG_ERROR("TerrainRenderer::recreatePipelines: failed to create fill pipeline");
@ -264,7 +264,7 @@ void TerrainRenderer::recreatePipelines() {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
if (!wireframePipeline) { if (!wireframePipeline) {
LOG_WARNING("TerrainRenderer::recreatePipelines: wireframe pipeline not available"); LOG_WARNING("TerrainRenderer::recreatePipelines: wireframe pipeline not available");
@ -932,7 +932,7 @@ bool TerrainRenderer::initializeShadow(VkRenderPass shadowRenderPass) {
.setLayout(shadowPipelineLayout_) .setLayout(shadowPipelineLayout_)
.setRenderPass(shadowRenderPass) .setRenderPass(shadowRenderPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();

View file

@ -6,6 +6,9 @@
#include <imgui_impl_vulkan.h> #include <imgui_impl_vulkan.h>
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <filesystem>
#include <fstream>
#include <string>
namespace wowee { namespace wowee {
namespace rendering { namespace rendering {
@ -37,6 +40,10 @@ bool VkContext::initialize(SDL_Window* window) {
if (!createLogicalDevice()) return false; if (!createLogicalDevice()) return false;
if (!createAllocator()) return false; if (!createAllocator()) return false;
// Pipeline cache: try to load from disk, fall back to empty cache.
// Not fatal — if it fails we just skip caching.
createPipelineCache();
int w, h; int w, h;
SDL_Vulkan_GetDrawableSize(window, &w, &h); SDL_Vulkan_GetDrawableSize(window, &w, &h);
if (!createSwapchain(w, h)) return false; if (!createSwapchain(w, h)) return false;
@ -83,6 +90,13 @@ void VkContext::shutdown() {
if (immFence) { vkDestroyFence(device, immFence, nullptr); immFence = VK_NULL_HANDLE; } if (immFence) { vkDestroyFence(device, immFence, nullptr); immFence = VK_NULL_HANDLE; }
if (immCommandPool) { vkDestroyCommandPool(device, immCommandPool, nullptr); immCommandPool = VK_NULL_HANDLE; } if (immCommandPool) { vkDestroyCommandPool(device, immCommandPool, nullptr); immCommandPool = VK_NULL_HANDLE; }
// Persist pipeline cache to disk before tearing down the device.
savePipelineCache();
if (pipelineCache_) {
vkDestroyPipelineCache(device, pipelineCache_, nullptr);
pipelineCache_ = VK_NULL_HANDLE;
}
LOG_WARNING("VkContext::shutdown - destroySwapchain..."); LOG_WARNING("VkContext::shutdown - destroySwapchain...");
destroySwapchain(); destroySwapchain();
@ -267,6 +281,106 @@ bool VkContext::createAllocator() {
return true; return true;
} }
// ---------------------------------------------------------------------------
// Pipeline cache persistence
// ---------------------------------------------------------------------------
static std::string getPipelineCachePath() {
#ifdef _WIN32
if (const char* appdata = std::getenv("APPDATA"))
return std::string(appdata) + "\\wowee\\pipeline_cache.bin";
return ".\\pipeline_cache.bin";
#elif defined(__APPLE__)
if (const char* home = std::getenv("HOME"))
return std::string(home) + "/Library/Caches/wowee/pipeline_cache.bin";
return "./pipeline_cache.bin";
#else
if (const char* home = std::getenv("HOME"))
return std::string(home) + "/.local/share/wowee/pipeline_cache.bin";
return "./pipeline_cache.bin";
#endif
}
bool VkContext::createPipelineCache() {
std::string path = getPipelineCachePath();
// Try to load existing cache data from disk.
std::vector<char> cacheData;
{
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (file.is_open()) {
auto size = file.tellg();
if (size > 0) {
cacheData.resize(static_cast<size_t>(size));
file.seekg(0);
file.read(cacheData.data(), size);
if (!file) {
LOG_WARNING("Pipeline cache file read failed, starting with empty cache");
cacheData.clear();
}
}
}
}
VkPipelineCacheCreateInfo cacheCI{};
cacheCI.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
cacheCI.initialDataSize = cacheData.size();
cacheCI.pInitialData = cacheData.empty() ? nullptr : cacheData.data();
VkResult result = vkCreatePipelineCache(device, &cacheCI, nullptr, &pipelineCache_);
if (result != VK_SUCCESS) {
// If loading stale/corrupt data caused failure, retry with empty cache.
if (!cacheData.empty()) {
LOG_WARNING("Pipeline cache creation failed with saved data, retrying empty");
cacheCI.initialDataSize = 0;
cacheCI.pInitialData = nullptr;
result = vkCreatePipelineCache(device, &cacheCI, nullptr, &pipelineCache_);
}
if (result != VK_SUCCESS) {
LOG_WARNING("Pipeline cache creation failed — pipelines will not be cached");
pipelineCache_ = VK_NULL_HANDLE;
return false;
}
}
if (!cacheData.empty()) {
LOG_INFO("Pipeline cache loaded from disk (", cacheData.size(), " bytes)");
} else {
LOG_INFO("Pipeline cache created (empty)");
}
return true;
}
void VkContext::savePipelineCache() {
if (!pipelineCache_ || !device) return;
size_t dataSize = 0;
if (vkGetPipelineCacheData(device, pipelineCache_, &dataSize, nullptr) != VK_SUCCESS || dataSize == 0) {
LOG_WARNING("Failed to query pipeline cache size");
return;
}
std::vector<char> data(dataSize);
if (vkGetPipelineCacheData(device, pipelineCache_, &dataSize, data.data()) != VK_SUCCESS) {
LOG_WARNING("Failed to retrieve pipeline cache data");
return;
}
std::string path = getPipelineCachePath();
std::filesystem::create_directories(std::filesystem::path(path).parent_path());
std::ofstream file(path, std::ios::binary | std::ios::trunc);
if (!file.is_open()) {
LOG_WARNING("Failed to open pipeline cache file for writing: ", path);
return;
}
file.write(data.data(), static_cast<std::streamsize>(dataSize));
file.close();
LOG_INFO("Pipeline cache saved to disk (", dataSize, " bytes)");
}
bool VkContext::createSwapchain(int width, int height) { bool VkContext::createSwapchain(int width, int height) {
vkb::SwapchainBuilder swapchainBuilder{physicalDevice, device, surface}; vkb::SwapchainBuilder swapchainBuilder{physicalDevice, device, surface};

View file

@ -111,7 +111,7 @@ PipelineBuilder& PipelineBuilder::setDynamicStates(const std::vector<VkDynamicSt
return *this; return *this;
} }
VkPipeline PipelineBuilder::build(VkDevice device) const { VkPipeline PipelineBuilder::build(VkDevice device, VkPipelineCache cache) const {
// Vertex input // Vertex input
VkPipelineVertexInputStateCreateInfo vertexInput{}; VkPipelineVertexInputStateCreateInfo vertexInput{};
vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
@ -192,7 +192,7 @@ VkPipeline PipelineBuilder::build(VkDevice device) const {
pipelineInfo.subpass = subpass_; pipelineInfo.subpass = subpass_;
VkPipeline pipeline = VK_NULL_HANDLE; VkPipeline pipeline = VK_NULL_HANDLE;
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, if (vkCreateGraphicsPipelines(device, cache, 1, &pipelineInfo,
nullptr, &pipeline) != VK_SUCCESS) nullptr, &pipeline) != VK_SUCCESS)
{ {
LOG_ERROR("Failed to create graphics pipeline"); LOG_ERROR("Failed to create graphics pipeline");

View file

@ -193,7 +193,7 @@ bool WaterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLay
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();
@ -257,7 +257,7 @@ void WaterRenderer::recreatePipelines() {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();
@ -2092,7 +2092,7 @@ bool WaterRenderer::createWater1xPass(VkFormat colorFormat, VkFormat depthFormat
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(water1xRenderPass) .setRenderPass(water1xRenderPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();

View file

@ -85,7 +85,7 @@ bool Weather::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();
@ -165,7 +165,7 @@ void Weather::recreatePipelines() {
.setLayout(pipelineLayout) .setLayout(pipelineLayout)
.setRenderPass(vkCtx->getImGuiRenderPass()) .setRenderPass(vkCtx->getImGuiRenderPass())
.setDynamicStates(dynamicStates) .setDynamicStates(dynamicStates)
.build(device); .build(device, vkCtx->getPipelineCache());
vertModule.destroy(); vertModule.destroy();
fragModule.destroy(); fragModule.destroy();

View file

@ -183,7 +183,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx_->getPipelineCache());
if (!opaquePipeline_) { if (!opaquePipeline_) {
core::Logger::getInstance().error("WMORenderer: failed to create opaque pipeline"); core::Logger::getInstance().error("WMORenderer: failed to create opaque pipeline");
@ -205,7 +205,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx_->getPipelineCache());
if (!transparentPipeline_) { if (!transparentPipeline_) {
core::Logger::getInstance().warning("WMORenderer: transparent pipeline not available"); core::Logger::getInstance().warning("WMORenderer: transparent pipeline not available");
@ -224,7 +224,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx_->getPipelineCache());
// --- Build wireframe pipeline --- // --- Build wireframe pipeline ---
wireframePipeline_ = PipelineBuilder() wireframePipeline_ = PipelineBuilder()
@ -239,7 +239,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx_->getPipelineCache());
if (!wireframePipeline_) { if (!wireframePipeline_) {
core::Logger::getInstance().warning("WMORenderer: wireframe pipeline not available"); core::Logger::getInstance().warning("WMORenderer: wireframe pipeline not available");
@ -1679,7 +1679,7 @@ bool WMORenderer::initializeShadow(VkRenderPass shadowRenderPass) {
.setLayout(shadowPipelineLayout_) .setLayout(shadowPipelineLayout_)
.setRenderPass(shadowRenderPass) .setRenderPass(shadowRenderPass)
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
.build(device); .build(device, vkCtx_->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();
@ -3681,7 +3681,7 @@ void WMORenderer::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx_->getPipelineCache());
transparentPipeline_ = PipelineBuilder() transparentPipeline_ = PipelineBuilder()
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT), .setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
@ -3695,7 +3695,7 @@ void WMORenderer::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx_->getPipelineCache());
glassPipeline_ = PipelineBuilder() glassPipeline_ = PipelineBuilder()
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT), .setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
@ -3709,7 +3709,7 @@ void WMORenderer::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx_->getPipelineCache());
wireframePipeline_ = PipelineBuilder() wireframePipeline_ = PipelineBuilder()
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT), .setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
@ -3723,7 +3723,7 @@ void WMORenderer::recreatePipelines() {
.setLayout(pipelineLayout_) .setLayout(pipelineLayout_)
.setRenderPass(mainPass) .setRenderPass(mainPass)
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx_->getPipelineCache());
vertShader.destroy(); vertShader.destroy();
fragShader.destroy(); fragShader.destroy();

View file

@ -165,7 +165,7 @@ bool WorldMap::initialize(VkContext* ctx, pipeline::AssetManager* am) {
.setLayout(tilePipelineLayout) .setLayout(tilePipelineLayout)
.setRenderPass(compositeTarget->getRenderPass()) .setRenderPass(compositeTarget->getRenderPass())
.setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR })
.build(device); .build(device, vkCtx->getPipelineCache());
vs.destroy(); vs.destroy();
fs.destroy(); fs.destroy();