sgejegksng
This commit is contained in:
@@ -665,6 +665,12 @@ void VulkanEngine::draw()
|
||||
_asyncLoader->pump_main_thread(*_sceneManager);
|
||||
}
|
||||
|
||||
// Apply any completed async pipeline rebuilds before using pipelines this frame.
|
||||
if (_pipelineManager)
|
||||
{
|
||||
_pipelineManager->pump_main_thread();
|
||||
}
|
||||
|
||||
_sceneManager->update_scene();
|
||||
|
||||
// Update IBL based on camera position and user-defined reflection volumes.
|
||||
|
||||
@@ -16,10 +16,14 @@ PipelineManager::~PipelineManager()
|
||||
void PipelineManager::init(EngineContext *ctx)
|
||||
{
|
||||
_context = ctx;
|
||||
start_worker();
|
||||
}
|
||||
|
||||
void PipelineManager::cleanup()
|
||||
{
|
||||
// Stop async worker first so no background thread touches the device/context.
|
||||
stop_worker();
|
||||
|
||||
for (auto &kv: _graphicsPipelines)
|
||||
{
|
||||
destroyGraphics(kv.second);
|
||||
@@ -82,6 +86,10 @@ void PipelineManager::hotReloadChanged()
|
||||
{
|
||||
if (!_context || !_context->getDevice()) return;
|
||||
|
||||
// Discover pipelines whose shaders changed and enqueue async rebuild jobs.
|
||||
std::vector<ReloadJob> to_enqueue;
|
||||
to_enqueue.reserve(_graphicsPipelines.size());
|
||||
|
||||
for (auto &kv: _graphicsPipelines)
|
||||
{
|
||||
auto &rec = kv.second;
|
||||
@@ -102,14 +110,14 @@ void PipelineManager::hotReloadChanged()
|
||||
if (needReload)
|
||||
{
|
||||
GraphicsPipelineRecord fresh = rec;
|
||||
// Do not touch existing pipeline here; async worker will build into a fresh record.
|
||||
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);
|
||||
}
|
||||
|
||||
ReloadJob job{};
|
||||
job.name = kv.first;
|
||||
job.record = std::move(fresh);
|
||||
to_enqueue.push_back(std::move(job));
|
||||
}
|
||||
}
|
||||
catch (const std::exception &)
|
||||
@@ -117,6 +125,66 @@ void PipelineManager::hotReloadChanged()
|
||||
// ignore hot-reload errors to avoid spamming
|
||||
}
|
||||
}
|
||||
|
||||
if (to_enqueue.empty()) return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
for (auto &job : to_enqueue)
|
||||
{
|
||||
// Avoid duplicate enqueues while a previous rebuild for this pipeline is in-flight.
|
||||
if (_inflight.find(job.name) != _inflight.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_inflight.insert(job.name);
|
||||
_pending_jobs.push_back(std::move(job));
|
||||
}
|
||||
}
|
||||
_jobs_cv.notify_all();
|
||||
}
|
||||
|
||||
void PipelineManager::pump_main_thread()
|
||||
{
|
||||
// Move completed jobs to a local queue so we don't hold the mutex while doing Vulkan work.
|
||||
std::deque<ReloadJob> completed;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
if (_completed_jobs.empty()) return;
|
||||
completed.swap(_completed_jobs);
|
||||
}
|
||||
|
||||
if (!_context || !_context->getDevice()) return;
|
||||
|
||||
std::vector<std::string> finished_names;
|
||||
finished_names.reserve(completed.size());
|
||||
|
||||
for (auto &job : completed)
|
||||
{
|
||||
auto it = _graphicsPipelines.find(job.name);
|
||||
if (it == _graphicsPipelines.end())
|
||||
{
|
||||
// Pipeline was unregistered while the job was in flight; just destroy the newly built pipeline.
|
||||
destroyGraphics(job.record);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Replace existing pipeline with the freshly built one.
|
||||
destroyGraphics(it->second);
|
||||
it->second = std::move(job.record);
|
||||
fmt::println("Reloaded graphics pipeline '{}' (async)", job.name);
|
||||
}
|
||||
finished_names.push_back(job.name);
|
||||
}
|
||||
|
||||
// Clear in-flight markers after commit so new reloads can be enqueued.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
for (const auto &name : finished_names)
|
||||
{
|
||||
_inflight.erase(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineManager::debug_get_graphics(std::vector<GraphicsPipelineDebugInfo> &out) const
|
||||
@@ -220,6 +288,95 @@ void PipelineManager::destroyGraphics(GraphicsPipelineRecord &rec)
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineManager::start_worker()
|
||||
{
|
||||
bool expected = false;
|
||||
if (!_running.compare_exchange_strong(expected, true))
|
||||
{
|
||||
// Already running.
|
||||
return;
|
||||
}
|
||||
|
||||
_worker = std::thread(&PipelineManager::worker_loop, this);
|
||||
}
|
||||
|
||||
void PipelineManager::stop_worker()
|
||||
{
|
||||
bool expected = true;
|
||||
if (!_running.compare_exchange_strong(expected, false))
|
||||
{
|
||||
// Was not running.
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
_pending_jobs.clear();
|
||||
_completed_jobs.clear();
|
||||
_inflight.clear();
|
||||
}
|
||||
_jobs_cv.notify_all();
|
||||
|
||||
if (_worker.joinable())
|
||||
{
|
||||
_worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineManager::worker_loop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ReloadJob job;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_jobs_mutex);
|
||||
_jobs_cv.wait(lock, [this]() {
|
||||
return !_running.load(std::memory_order_acquire) || !_pending_jobs.empty();
|
||||
});
|
||||
|
||||
if (!_running.load(std::memory_order_acquire) && _pending_jobs.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pending_jobs.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
job = std::move(_pending_jobs.front());
|
||||
_pending_jobs.pop_front();
|
||||
}
|
||||
|
||||
if (!_context || !_context->getDevice())
|
||||
{
|
||||
// Context/device went away; drop job and exit.
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
_inflight.erase(job.name);
|
||||
return;
|
||||
}
|
||||
|
||||
GraphicsPipelineRecord rec = job.record;
|
||||
bool ok = buildGraphics(rec);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
if (ok)
|
||||
{
|
||||
ReloadJob completed;
|
||||
completed.name = job.name;
|
||||
completed.record = std::move(rec);
|
||||
_completed_jobs.push_back(std::move(completed));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow future hotReloadChanged calls to enqueue another attempt.
|
||||
_inflight.erase(job.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Compute forwarding API ---
|
||||
bool PipelineManager::createComputePipeline(const std::string &name, const ComputePipelineCreateInfo &info)
|
||||
{
|
||||
|
||||
@@ -4,11 +4,17 @@
|
||||
#include <render/pipelines.h>
|
||||
#include <compute/vk_compute.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
class EngineContext;
|
||||
|
||||
@@ -97,9 +103,12 @@ public:
|
||||
// Convenience to interop with MaterialInstance
|
||||
bool getMaterialPipeline(const std::string &name, MaterialPipeline &out) const;
|
||||
|
||||
// Rebuild pipelines whose shaders changed on disk
|
||||
// Rebuild pipelines whose shaders changed on disk (enqueue async rebuild jobs)
|
||||
void hotReloadChanged();
|
||||
|
||||
// Apply any completed async rebuilds on the main thread.
|
||||
void pump_main_thread();
|
||||
|
||||
// Debug helpers (graphics only)
|
||||
struct GraphicsPipelineDebugInfo
|
||||
{
|
||||
@@ -125,7 +134,29 @@ private:
|
||||
EngineContext *_context = nullptr;
|
||||
std::unordered_map<std::string, GraphicsPipelineRecord> _graphicsPipelines;
|
||||
|
||||
// --- Async hot-reload state ---
|
||||
struct ReloadJob
|
||||
{
|
||||
std::string name;
|
||||
GraphicsPipelineRecord record;
|
||||
};
|
||||
|
||||
std::atomic<bool> _running{false};
|
||||
std::thread _worker;
|
||||
|
||||
std::mutex _jobs_mutex;
|
||||
std::condition_variable _jobs_cv;
|
||||
std::deque<ReloadJob> _pending_jobs;
|
||||
std::deque<ReloadJob> _completed_jobs;
|
||||
std::unordered_set<std::string> _inflight;
|
||||
|
||||
bool buildGraphics(GraphicsPipelineRecord &rec) const;
|
||||
|
||||
void destroyGraphics(GraphicsPipelineRecord &rec);
|
||||
|
||||
void start_worker();
|
||||
|
||||
void stop_worker();
|
||||
|
||||
void worker_loop();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user