#include #include #include #include #include #include PipelineManager::~PipelineManager() { cleanup(); } void PipelineManager::init(EngineContext *ctx) { _context = ctx; } void PipelineManager::cleanup() { for (auto &kv: _graphicsPipelines) { destroyGraphics(kv.second); } _graphicsPipelines.clear(); _context = nullptr; } bool PipelineManager::registerGraphics(const std::string &name, const GraphicsPipelineCreateInfo &info) { if (! _context || !_context->getDevice()) return false; auto it = _graphicsPipelines.find(name); if (it != _graphicsPipelines.end()) { fmt::println("Graphics pipeline '{}' already exists", name); return false; } GraphicsPipelineRecord rec{}; rec.spec = info; if (!buildGraphics(rec)) { destroyGraphics(rec); return false; } _graphicsPipelines.emplace(name, std::move(rec)); return true; } void PipelineManager::unregisterGraphics(const std::string &name) { auto it = _graphicsPipelines.find(name); if (it == _graphicsPipelines.end()) return; destroyGraphics(it->second); _graphicsPipelines.erase(it); } bool PipelineManager::getGraphics(const std::string &name, VkPipeline &pipeline, VkPipelineLayout &layout) const { auto it = _graphicsPipelines.find(name); if (it == _graphicsPipelines.end()) return false; pipeline = it->second.pipeline; layout = it->second.layout; return pipeline != VK_NULL_HANDLE && layout != VK_NULL_HANDLE; } bool PipelineManager::getMaterialPipeline(const std::string &name, MaterialPipeline &out) const { VkPipeline p{}; VkPipelineLayout l{}; if (!getGraphics(name, p, l)) return false; out.pipeline = p; out.layout = l; return true; } void PipelineManager::hotReloadChanged() { if (!_context || !_context->getDevice()) return; for (auto &kv: _graphicsPipelines) { auto &rec = kv.second; try { bool needReload = false; if (!rec.spec.vertexShaderPath.empty()) { auto t = std::filesystem::last_write_time(rec.spec.vertexShaderPath); if (rec.vertTime != std::filesystem::file_time_type{} && t != rec.vertTime) needReload = true; } if (!rec.spec.fragmentShaderPath.empty()) { auto t = std::filesystem::last_write_time(rec.spec.fragmentShaderPath); if (rec.fragTime != std::filesystem::file_time_type{} && t != rec.fragTime) needReload = true; } if (needReload) { GraphicsPipelineRecord fresh = rec; fresh.pipeline = VK_NULL_HANDLE; fresh.layout = VK_NULL_HANDLE; if (buildGraphics(fresh)) { destroyGraphics(rec); rec = std::move(fresh); fmt::println("Reloaded graphics pipeline '{}'", kv.first); } } } catch (const std::exception &) { // ignore hot-reload errors to avoid spamming } } } void PipelineManager::debug_get_graphics(std::vector &out) const { out.clear(); out.reserve(_graphicsPipelines.size()); for (const auto &kv : _graphicsPipelines) { const auto &rec = kv.second; GraphicsPipelineDebugInfo info{}; info.name = kv.first; info.vertexShaderPath = rec.spec.vertexShaderPath; info.fragmentShaderPath = rec.spec.fragmentShaderPath; info.valid = (rec.pipeline != VK_NULL_HANDLE) && (rec.layout != VK_NULL_HANDLE); out.push_back(std::move(info)); } } bool PipelineManager::buildGraphics(GraphicsPipelineRecord &rec) const { VkShaderModule vert = VK_NULL_HANDLE; VkShaderModule frag = VK_NULL_HANDLE; if (!rec.spec.vertexShaderPath.empty()) { if (!vkutil::load_shader_module(rec.spec.vertexShaderPath.c_str(), _context->getDevice()->device(), &vert)) { fmt::println("Failed to load vertex shader: {}", rec.spec.vertexShaderPath); return false; } } if (!rec.spec.fragmentShaderPath.empty()) { if (!vkutil::load_shader_module(rec.spec.fragmentShaderPath.c_str(), _context->getDevice()->device(), &frag)) { if (vert != VK_NULL_HANDLE) vkDestroyShaderModule(_context->getDevice()->device(), vert, nullptr); fmt::println("Failed to load fragment shader: {}", rec.spec.fragmentShaderPath); return false; } } VkPipelineLayoutCreateInfo layoutInfo = vkinit::pipeline_layout_create_info(); layoutInfo.setLayoutCount = static_cast(rec.spec.setLayouts.size()); layoutInfo.pSetLayouts = rec.spec.setLayouts.empty() ? nullptr : rec.spec.setLayouts.data(); layoutInfo.pushConstantRangeCount = static_cast(rec.spec.pushConstants.size()); layoutInfo.pPushConstantRanges = rec.spec.pushConstants.empty() ? nullptr : rec.spec.pushConstants.data(); VK_CHECK(vkCreatePipelineLayout(_context->getDevice()->device(), &layoutInfo, nullptr, &rec.layout)); PipelineBuilder builder; if (vert != VK_NULL_HANDLE || frag != VK_NULL_HANDLE) { builder.set_shaders(vert, frag); } if (rec.spec.configure) rec.spec.configure(builder); builder._pipelineLayout = rec.layout; rec.pipeline = builder.build_pipeline(_context->getDevice()->device()); if (vert != VK_NULL_HANDLE) vkDestroyShaderModule(_context->getDevice()->device(), vert, nullptr); if (frag != VK_NULL_HANDLE) vkDestroyShaderModule(_context->getDevice()->device(), frag, nullptr); if (rec.pipeline == VK_NULL_HANDLE) { vkDestroyPipelineLayout(_context->getDevice()->device(), rec.layout, nullptr); rec.layout = VK_NULL_HANDLE; return false; } // Record timestamps for hot reload try { if (!rec.spec.vertexShaderPath.empty()) rec.vertTime = std::filesystem::last_write_time(rec.spec.vertexShaderPath); if (!rec.spec.fragmentShaderPath.empty()) rec.fragTime = std::filesystem::last_write_time(rec.spec.fragmentShaderPath); } catch (const std::exception &) { // ignore timestamp errors } return true; } void PipelineManager::destroyGraphics(GraphicsPipelineRecord &rec) { if (!_context || !_context->getDevice()) return; if (rec.pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(_context->getDevice()->device(), rec.pipeline, nullptr); rec.pipeline = VK_NULL_HANDLE; } if (rec.layout != VK_NULL_HANDLE) { vkDestroyPipelineLayout(_context->getDevice()->device(), rec.layout, nullptr); rec.layout = VK_NULL_HANDLE; } } // --- Compute forwarding API --- bool PipelineManager::createComputePipeline(const std::string &name, const ComputePipelineCreateInfo &info) { if (!_context || !_context->compute) return false; return _context->compute->registerPipeline(name, info); } void PipelineManager::destroyComputePipeline(const std::string &name) { if (!_context || !_context->compute) return; _context->compute->unregisterPipeline(name); } bool PipelineManager::hasComputePipeline(const std::string &name) const { if (!_context || !_context->compute) return false; return _context->compute->hasPipeline(name); } void PipelineManager::dispatchCompute(VkCommandBuffer cmd, const std::string &name, const ComputeDispatchInfo &info) { if (!_context || !_context->compute) return; _context->compute->dispatch(cmd, name, info); } void PipelineManager::dispatchComputeImmediate(const std::string &name, const ComputeDispatchInfo &info) { if (!_context || !_context->compute) return; _context->compute->dispatchImmediate(name, info); } bool PipelineManager::createComputeInstance(const std::string &instanceName, const std::string &pipelineName) { if (!_context || !_context->compute) return false; return _context->compute->createInstance(instanceName, pipelineName); } void PipelineManager::destroyComputeInstance(const std::string &instanceName) { if (!_context || !_context->compute) return; _context->compute->destroyInstance(instanceName); } bool PipelineManager::setComputeInstanceStorageImage(const std::string &instanceName, uint32_t binding, VkImageView view, VkImageLayout layout) { if (!_context || !_context->compute) return false; return _context->compute->setInstanceStorageImage(instanceName, binding, view, layout); } bool PipelineManager::setComputeInstanceSampledImage(const std::string &instanceName, uint32_t binding, VkImageView view, VkSampler sampler, VkImageLayout layout) { if (!_context || !_context->compute) return false; return _context->compute->setInstanceSampledImage(instanceName, binding, view, sampler, layout); } bool PipelineManager::setComputeInstanceBuffer(const std::string &instanceName, uint32_t binding, VkBuffer buffer, VkDeviceSize size, VkDescriptorType type, VkDeviceSize offset) { if (!_context || !_context->compute) return false; return _context->compute->setInstanceBuffer(instanceName, binding, buffer, size, type, offset); } AllocatedImage PipelineManager::createAndBindComputeStorageImage(const std::string &instanceName, uint32_t binding, VkExtent3D extent, VkFormat format, VkImageLayout layout, VkImageUsageFlags usage) { if (!_context || !_context->compute) return {}; return _context->compute->createAndBindStorageImage(instanceName, binding, extent, format, layout, usage); } AllocatedBuffer PipelineManager::createAndBindComputeStorageBuffer(const std::string &instanceName, uint32_t binding, VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage memUsage) { if (!_context || !_context->compute) return {}; return _context->compute->createAndBindStorageBuffer(instanceName, binding, size, usage, memUsage); } void PipelineManager::dispatchComputeInstance(VkCommandBuffer cmd, const std::string &instanceName, const ComputeDispatchInfo &info) { if (!_context || !_context->compute) return; _context->compute->dispatchInstance(cmd, instanceName, info); }