diff --git a/include/rendering/vk_context.hpp b/include/rendering/vk_context.hpp index 654729b3..a9186439 100644 --- a/include/rendering/vk_context.hpp +++ b/include/rendering/vk_context.hpp @@ -74,6 +74,7 @@ public: uint32_t getGraphicsQueueFamily() const { return graphicsQueueFamily; } VmaAllocator getAllocator() const { return allocator; } VkSurfaceKHR getSurface() const { return surface; } + VkPipelineCache getPipelineCache() const { return pipelineCache_; } VkSwapchainKHR getSwapchain() const { return swapchain; } VkFormat getSwapchainFormat() const { return swapchainFormat; } @@ -130,6 +131,8 @@ private: void destroySwapchain(); bool createCommandPools(); bool createSyncObjects(); + bool createPipelineCache(); + void savePipelineCache(); bool createImGuiResources(); void destroyImGuiResources(); @@ -144,6 +147,9 @@ private: VkDevice device = 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 presentQueue = VK_NULL_HANDLE; uint32_t graphicsQueueFamily = 0; diff --git a/include/rendering/vk_pipeline.hpp b/include/rendering/vk_pipeline.hpp index ea0a3e10..e95337f8 100644 --- a/include/rendering/vk_pipeline.hpp +++ b/include/rendering/vk_pipeline.hpp @@ -75,8 +75,8 @@ public: // Dynamic state PipelineBuilder& setDynamicStates(const std::vector& states); - // Build the pipeline - VkPipeline build(VkDevice device) const; + // Build the pipeline (pass a VkPipelineCache for faster creation) + VkPipeline build(VkDevice device, VkPipelineCache cache = VK_NULL_HANDLE) const; // Common blend states static VkPipelineColorBlendAttachmentState blendDisabled(); diff --git a/src/rendering/celestial.cpp b/src/rendering/celestial.cpp index 798ac5d5..ad7804ba 100644 --- a/src/rendering/celestial.cpp +++ b/src/rendering/celestial.cpp @@ -90,7 +90,7 @@ bool Celestial::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) .setLayout(pipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -162,7 +162,7 @@ void Celestial::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 6b4e00b8..d709f0c9 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -264,7 +264,7 @@ bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFram .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); }; opaquePipeline_ = buildCharPipeline(PipelineBuilder::blendDisabled(), true); @@ -2648,7 +2648,7 @@ bool CharacterRenderer::initializeShadow(VkRenderPass shadowRenderPass) { .setLayout(shadowPipelineLayout_) .setRenderPass(shadowRenderPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertShader.destroy(); fragShader.destroy(); @@ -3315,7 +3315,7 @@ void CharacterRenderer::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); }; LOG_INFO("CharacterRenderer::recreatePipelines: renderPass=", (void*)mainPass, diff --git a/src/rendering/charge_effect.cpp b/src/rendering/charge_effect.cpp index d6fba4de..32a3b36d 100644 --- a/src/rendering/charge_effect.cpp +++ b/src/rendering/charge_effect.cpp @@ -101,7 +101,7 @@ bool ChargeEffect::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayo .setLayout(ribbonPipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -165,7 +165,7 @@ bool ChargeEffect::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayo .setLayout(dustPipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -314,7 +314,7 @@ void ChargeEffect::recreatePipelines() { .setLayout(ribbonPipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -360,7 +360,7 @@ void ChargeEffect::recreatePipelines() { .setLayout(dustPipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/clouds.cpp b/src/rendering/clouds.cpp index eb2a5a25..6b682850 100644 --- a/src/rendering/clouds.cpp +++ b/src/rendering/clouds.cpp @@ -83,7 +83,7 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) { .setLayout(pipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -149,7 +149,7 @@ void Clouds::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/lens_flare.cpp b/src/rendering/lens_flare.cpp index 3dd6b734..e9a9bb04 100644 --- a/src/rendering/lens_flare.cpp +++ b/src/rendering/lens_flare.cpp @@ -109,7 +109,7 @@ bool LensFlare::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayou .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); // Shader modules can be freed after pipeline creation vertModule.destroy(); @@ -198,7 +198,7 @@ void LensFlare::recreatePipelines() { .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/lightning.cpp b/src/rendering/lightning.cpp index 9dbd1b95..b7d28c1d 100644 --- a/src/rendering/lightning.cpp +++ b/src/rendering/lightning.cpp @@ -107,7 +107,7 @@ bool Lightning::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) .setLayout(boltPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -169,7 +169,7 @@ bool Lightning::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) .setLayout(flashPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -306,7 +306,7 @@ void Lightning::recreatePipelines() { .setLayout(boltPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -344,7 +344,7 @@ void Lightning::recreatePipelines() { .setLayout(flashPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 654717ab..fb5f0bb9 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -507,7 +507,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); }; opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true); @@ -542,7 +542,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout .setLayout(particlePipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); }; particlePipeline_ = buildParticlePipeline(PipelineBuilder::blendAlpha()); @@ -575,7 +575,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout .setLayout(smokePipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); } // --- Build ribbon pipelines --- @@ -617,7 +617,7 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout .setLayout(ribbonPipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); }; ribbonPipeline_ = buildRibbonPipeline(PipelineBuilder::blendAlpha()); @@ -3228,7 +3228,7 @@ bool M2Renderer::initializeShadow(VkRenderPass shadowRenderPass) { .setLayout(shadowPipelineLayout_) .setRenderPass(shadowRenderPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertShader.destroy(); fragShader.destroy(); @@ -5076,7 +5076,7 @@ void M2Renderer::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); }; opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true); @@ -5111,7 +5111,7 @@ void M2Renderer::recreatePipelines() { .setLayout(particlePipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); }; particlePipeline_ = buildParticlePipeline(PipelineBuilder::blendAlpha()); @@ -5144,7 +5144,7 @@ void M2Renderer::recreatePipelines() { .setLayout(smokePipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); } // --- Ribbon pipelines --- @@ -5178,7 +5178,7 @@ void M2Renderer::recreatePipelines() { .setLayout(ribbonPipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); }; ribbonPipeline_ = buildRibbonPipeline(PipelineBuilder::blendAlpha()); diff --git a/src/rendering/minimap.cpp b/src/rendering/minimap.cpp index cce494d9..7cccca2b 100644 --- a/src/rendering/minimap.cpp +++ b/src/rendering/minimap.cpp @@ -165,7 +165,7 @@ bool Minimap::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayout* .setLayout(tilePipelineLayout) .setRenderPass(compositeTarget->getRenderPass()) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); vs.destroy(); fs.destroy(); @@ -192,7 +192,7 @@ bool Minimap::initialize(VkContext* ctx, VkDescriptorSetLayout /*perFrameLayout* .setLayout(displayPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); vs.destroy(); fs.destroy(); @@ -270,7 +270,7 @@ void Minimap::recreatePipelines() { .setLayout(displayPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); vs.destroy(); fs.destroy(); diff --git a/src/rendering/mount_dust.cpp b/src/rendering/mount_dust.cpp index 5678f31c..560e8a42 100644 --- a/src/rendering/mount_dust.cpp +++ b/src/rendering/mount_dust.cpp @@ -92,7 +92,7 @@ bool MountDust::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -199,7 +199,7 @@ void MountDust::recreatePipelines() { .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/quest_marker_renderer.cpp b/src/rendering/quest_marker_renderer.cpp index b274a880..07498285 100644 --- a/src/rendering/quest_marker_renderer.cpp +++ b/src/rendering/quest_marker_renderer.cpp @@ -114,7 +114,7 @@ bool QuestMarkerRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFr .setLayout(pipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -233,7 +233,7 @@ void QuestMarkerRenderer::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(vkCtx_->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 7199273d..6e3d46c1 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -3820,7 +3820,7 @@ void Renderer::initSelectionCircle() { .setLayout(selCirclePipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertShader.destroy(); fragShader.destroy(); @@ -3932,7 +3932,7 @@ void Renderer::initOverlayPipeline() { .setLayout(overlayPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertMod.destroy(); fragMod.destroy(); @@ -4144,7 +4144,7 @@ bool Renderer::initFSRResources() { .setLayout(fsr_.pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertMod.destroy(); fragMod.destroy(); @@ -4668,7 +4668,7 @@ bool Renderer::initFSR2Resources() { .setLayout(fsr2_.sharpenPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertMod.destroy(); fragMod.destroy(); @@ -5353,7 +5353,7 @@ bool Renderer::initFXAAResources() { .setLayout(fxaa_.pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertMod.destroy(); fragMod.destroy(); diff --git a/src/rendering/skybox.cpp b/src/rendering/skybox.cpp index 3e0e7de6..1e08ac4f 100644 --- a/src/rendering/skybox.cpp +++ b/src/rendering/skybox.cpp @@ -81,7 +81,7 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) { .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); // Shader modules can be freed after pipeline creation vertModule.destroy(); @@ -133,7 +133,7 @@ void Skybox::recreatePipelines() { .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/starfield.cpp b/src/rendering/starfield.cpp index e472bc8d..b51d419b 100644 --- a/src/rendering/starfield.cpp +++ b/src/rendering/starfield.cpp @@ -91,7 +91,7 @@ bool StarField::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) .setMultisample(vkCtx->getMsaaSamples()) .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -164,7 +164,7 @@ void StarField::recreatePipelines() { .setMultisample(vkCtx->getMsaaSamples()) .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/swim_effects.cpp b/src/rendering/swim_effects.cpp index 9a7ad119..9bc4885a 100644 --- a/src/rendering/swim_effects.cpp +++ b/src/rendering/swim_effects.cpp @@ -98,7 +98,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou .setLayout(ripplePipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -142,7 +142,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou .setLayout(bubblePipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -186,7 +186,7 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou .setLayout(insectPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -366,7 +366,7 @@ void SwimEffects::recreatePipelines() { .setLayout(ripplePipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -393,7 +393,7 @@ void SwimEffects::recreatePipelines() { .setLayout(bubblePipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -420,7 +420,7 @@ void SwimEffects::recreatePipelines() { .setLayout(insectPipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/terrain_renderer.cpp b/src/rendering/terrain_renderer.cpp index 775881d3..7543e639 100644 --- a/src/rendering/terrain_renderer.cpp +++ b/src/rendering/terrain_renderer.cpp @@ -143,7 +143,7 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL .setLayout(pipelineLayout) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); if (!pipeline) { LOG_ERROR("TerrainRenderer: failed to create fill pipeline"); @@ -165,7 +165,7 @@ bool TerrainRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameL .setLayout(pipelineLayout) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); if (!wireframePipeline) { LOG_WARNING("TerrainRenderer: wireframe pipeline not available"); @@ -245,7 +245,7 @@ void TerrainRenderer::recreatePipelines() { .setLayout(pipelineLayout) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); if (!pipeline) { LOG_ERROR("TerrainRenderer::recreatePipelines: failed to create fill pipeline"); @@ -264,7 +264,7 @@ void TerrainRenderer::recreatePipelines() { .setLayout(pipelineLayout) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); if (!wireframePipeline) { LOG_WARNING("TerrainRenderer::recreatePipelines: wireframe pipeline not available"); @@ -932,7 +932,7 @@ bool TerrainRenderer::initializeShadow(VkRenderPass shadowRenderPass) { .setLayout(shadowPipelineLayout_) .setRenderPass(shadowRenderPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertShader.destroy(); fragShader.destroy(); diff --git a/src/rendering/vk_context.cpp b/src/rendering/vk_context.cpp index 51781a3c..aa223502 100644 --- a/src/rendering/vk_context.cpp +++ b/src/rendering/vk_context.cpp @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include namespace wowee { namespace rendering { @@ -37,6 +40,10 @@ bool VkContext::initialize(SDL_Window* window) { if (!createLogicalDevice()) 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; SDL_Vulkan_GetDrawableSize(window, &w, &h); if (!createSwapchain(w, h)) return false; @@ -83,6 +90,13 @@ void VkContext::shutdown() { if (immFence) { vkDestroyFence(device, immFence, nullptr); immFence = 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..."); destroySwapchain(); @@ -267,6 +281,106 @@ bool VkContext::createAllocator() { 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 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)); + 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 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(dataSize)); + file.close(); + + LOG_INFO("Pipeline cache saved to disk (", dataSize, " bytes)"); +} + bool VkContext::createSwapchain(int width, int height) { vkb::SwapchainBuilder swapchainBuilder{physicalDevice, device, surface}; diff --git a/src/rendering/vk_pipeline.cpp b/src/rendering/vk_pipeline.cpp index 4e565b07..4119d8c8 100644 --- a/src/rendering/vk_pipeline.cpp +++ b/src/rendering/vk_pipeline.cpp @@ -111,7 +111,7 @@ PipelineBuilder& PipelineBuilder::setDynamicStates(const std::vectorgetPipelineCache()); vertShader.destroy(); fragShader.destroy(); @@ -257,7 +257,7 @@ void WaterRenderer::recreatePipelines() { .setLayout(pipelineLayout) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertShader.destroy(); fragShader.destroy(); @@ -2092,7 +2092,7 @@ bool WaterRenderer::createWater1xPass(VkFormat colorFormat, VkFormat depthFormat .setLayout(pipelineLayout) .setRenderPass(water1xRenderPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertShader.destroy(); fragShader.destroy(); diff --git a/src/rendering/weather.cpp b/src/rendering/weather.cpp index 5dc525da..6f81aae0 100644 --- a/src/rendering/weather.cpp +++ b/src/rendering/weather.cpp @@ -85,7 +85,7 @@ bool Weather::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) { .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); @@ -165,7 +165,7 @@ void Weather::recreatePipelines() { .setLayout(pipelineLayout) .setRenderPass(vkCtx->getImGuiRenderPass()) .setDynamicStates(dynamicStates) - .build(device); + .build(device, vkCtx->getPipelineCache()); vertModule.destroy(); fragModule.destroy(); diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 0f8f6b76..68e7f7b3 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -183,7 +183,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx_->getPipelineCache()); if (!opaquePipeline_) { core::Logger::getInstance().error("WMORenderer: failed to create opaque pipeline"); @@ -205,7 +205,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx_->getPipelineCache()); if (!transparentPipeline_) { core::Logger::getInstance().warning("WMORenderer: transparent pipeline not available"); @@ -224,7 +224,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx_->getPipelineCache()); // --- Build wireframe pipeline --- wireframePipeline_ = PipelineBuilder() @@ -239,7 +239,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx_->getPipelineCache()); if (!wireframePipeline_) { core::Logger::getInstance().warning("WMORenderer: wireframe pipeline not available"); @@ -1679,7 +1679,7 @@ bool WMORenderer::initializeShadow(VkRenderPass shadowRenderPass) { .setLayout(shadowPipelineLayout_) .setRenderPass(shadowRenderPass) .setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertShader.destroy(); fragShader.destroy(); @@ -3681,7 +3681,7 @@ void WMORenderer::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx_->getPipelineCache()); transparentPipeline_ = PipelineBuilder() .setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT), @@ -3695,7 +3695,7 @@ void WMORenderer::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx_->getPipelineCache()); glassPipeline_ = PipelineBuilder() .setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT), @@ -3709,7 +3709,7 @@ void WMORenderer::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx_->getPipelineCache()); wireframePipeline_ = PipelineBuilder() .setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT), @@ -3723,7 +3723,7 @@ void WMORenderer::recreatePipelines() { .setLayout(pipelineLayout_) .setRenderPass(mainPass) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx_->getPipelineCache()); vertShader.destroy(); fragShader.destroy(); diff --git a/src/rendering/world_map.cpp b/src/rendering/world_map.cpp index 8fe955ca..03da7972 100644 --- a/src/rendering/world_map.cpp +++ b/src/rendering/world_map.cpp @@ -165,7 +165,7 @@ bool WorldMap::initialize(VkContext* ctx, pipeline::AssetManager* am) { .setLayout(tilePipelineLayout) .setRenderPass(compositeTarget->getRenderPass()) .setDynamicStates({ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }) - .build(device); + .build(device, vkCtx->getPipelineCache()); vs.destroy(); fs.destroy();