sgejegksng

This commit is contained in:
2025-12-09 16:45:28 +09:00
parent f62ce93695
commit 4880e1a992
3 changed files with 201 additions and 7 deletions

View File

@@ -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.

View File

@@ -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)
{

View File

@@ -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();
};