diff --git a/src/render/shadercache/README.md b/src/render/shadercache/README.md new file mode 100644 index 00000000..0c567dc0 --- /dev/null +++ b/src/render/shadercache/README.md @@ -0,0 +1,4 @@ +# Shader cache + +This folder contains a small background shader compile + cache skeleton. +Cache files are written to the configured cache directory as `.bin` and `.meta`. diff --git a/src/render/shadercache/ShaderCache.cpp b/src/render/shadercache/ShaderCache.cpp new file mode 100644 index 00000000..10db051d --- /dev/null +++ b/src/render/shadercache/ShaderCache.cpp @@ -0,0 +1,107 @@ +#include "ShaderCache.h" +#include +#include +#include + +using namespace std::chrono; +namespace fs = std::filesystem; + +ShaderCache::ShaderCache(const std::string& cacheDir) : cacheDir_(cacheDir) { + // ensure dir exists + try { + fs::create_directories(cacheDir_); + } catch (...) {} +} + +ShaderCache::~ShaderCache() = default; + +std::string ShaderCache::cacheDir() const { return cacheDir_; } + +void ShaderCache::loadFromDisk() { + std::lock_guard lk(m_); + try { + for (auto &p : fs::directory_iterator(cacheDir_)) { + if (!p.is_regular_file()) continue; + auto path = p.path(); + if (path.extension() == ".bin") { + try { + auto entry = readEntryFromFile(path); + map_.emplace(entry.key, std::move(entry)); + } catch (const std::exception& e) { + std::cerr << "[ShaderCache] error reading " << path << ": " << e.what() << std::endl; + } + } + } + std::cerr << "[ShaderCache] loaded " << map_.size() << " entries from disk\n"; + } catch (const std::exception& e) { + std::cerr << "[ShaderCache] loadFromDisk error: " << e.what() << std::endl; + } +} + +bool ShaderCache::tryGet(const std::string& key, std::vector& outBlob) { + std::lock_guard lk(m_); + auto it = map_.find(key); + if (it == map_.end()) return false; + outBlob = it->second.blob; + std::cerr << "[ShaderCache] cache hit: " << key << " (size=" << outBlob.size() << ")\n"; + return true; +} + +void ShaderCache::put(const std::string& key, const std::vector& blob, const std::string& gpuId) { + ShaderCacheEntry e; + e.key = key; + e.blob = blob; + e.gpu_identifier = gpuId; + e.timestamp = static_cast(duration_cast(system_clock::now().time_since_epoch()).count()); + + { + std::lock_guard lk(m_); + map_[key] = e; + } + persistEntryToDisk(e); + std::cerr << "[ShaderCache] put: " << key << " (size=" << blob.size() << ")\n"; +} + +void ShaderCache::persistEntryToDisk(const ShaderCacheEntry& e) { + try { + fs::create_directories(cacheDir_); + fs::path binPath = fs::path(cacheDir_) / (e.key + ".bin"); + fs::path metaPath = fs::path(cacheDir_) / (e.key + ".meta"); + + { + std::ofstream out(binPath, std::ios::binary); + out.write(reinterpret_cast(e.blob.data()), static_cast(e.blob.size())); + } + { + std::ofstream meta(metaPath); + meta << e.gpu_identifier << "\n" << e.timestamp << "\n"; + } + } catch (const std::exception& ex) { + std::cerr << "[ShaderCache] persist error: " << ex.what() << std::endl; + } +} + +ShaderCacheEntry ShaderCache::readEntryFromFile(const std::filesystem::path& p) { + ShaderCacheEntry e; + e.key = p.stem().string(); // filename without extension + // read blob + std::ifstream in(p, std::ios::binary | std::ios::ate); + if (!in) throw std::runtime_error("cannot open bin file"); + auto size = in.tellg(); + in.seekg(0); + e.blob.resize(static_cast(size)); + in.read(reinterpret_cast(e.blob.data()), size); + + // read metadata file + fs::path meta = p.parent_path() / (e.key + ".meta"); + if (fs::exists(meta)) { + std::ifstream m(meta); + if (m) { + std::getline(m, e.gpu_identifier); + std::string ts; + std::getline(m, ts); + if (!ts.empty()) e.timestamp = std::stoull(ts); + } + } + return e; +} diff --git a/src/render/shadercache/ShaderCache.h b/src/render/shadercache/ShaderCache.h new file mode 100644 index 00000000..c31d122b --- /dev/null +++ b/src/render/shadercache/ShaderCache.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include +#include +#include + +struct ShaderCacheEntry { + std::string key; + std::vector blob; + std::string gpu_identifier; + uint64_t timestamp; +}; + +class ShaderCache { +public: + explicit ShaderCache(const std::string& cacheDir); + ~ShaderCache(); + + // load persisted entries metadata (called at startup) + void loadFromDisk(); + + // try to fetch compiled blob (returns true if found and set outBlob) + bool tryGet(const std::string& key, std::vector& outBlob); + + // put compiled blob into cache and persist to disk + void put(const std::string& key, const std::vector& blob, const std::string& gpuId); + + // simple helper: returns cache dir + std::string cacheDir() const; + +private: + void persistEntryToDisk(const ShaderCacheEntry& e); + ShaderCacheEntry readEntryFromFile(const std::filesystem::path& p); + + std::string cacheDir_; + std::unordered_map map_; + std::mutex m_; +}; diff --git a/src/render/shadercache/ShaderCompileWorker.cpp b/src/render/shadercache/ShaderCompileWorker.cpp new file mode 100644 index 00000000..966061ce --- /dev/null +++ b/src/render/shadercache/ShaderCompileWorker.cpp @@ -0,0 +1,57 @@ +#include "ShaderCompileWorker.h" +#include +#include +#include + +// A tiny "fake" compile function to simulate compilation work. +// Replace this with actual renderer compile in the integration step. +static bool FakeCompileToBlob(const std::string& src, std::vector& out) { + // Simulate some work and produce a small blob + std::this_thread::sleep_for(std::chrono::milliseconds(150)); // simulate compile latency + out.assign(src.begin(), src.end()); // just copy source bytes as a stand-in + return true; +} + +ShaderCompileWorker::ShaderCompileWorker(ShaderCache& cache) : cache_(cache) {} +ShaderCompileWorker::~ShaderCompileWorker() { stop(); } + +void ShaderCompileWorker::start() { + stop_.store(false); + worker_ = std::thread([this]{ threadMain(); }); +} + +void ShaderCompileWorker::stop() { + stop_.store(true); + // push a dummy to wake up + queue_.push(CompileTask{}); + if (worker_.joinable()) worker_.join(); +} + +void ShaderCompileWorker::enqueue(CompileTask task) { + queue_.push(std::move(task)); +} + +void ShaderCompileWorker::threadMain() { + while (true) { + auto opt = queue_.pop_wait(stop_); + if (!opt.has_value()) break; + CompileTask task = std::move(*opt); + if (task.key.empty()) continue; // ignore wake-up dummy + + // Double-check cache to avoid duplicate work + std::vector existing; + if (cache_.tryGet(task.key, existing)) continue; + + std::vector compiled; + bool ok = FakeCompileToBlob(task.source, compiled); // TODO replace with real compile + + if (ok) { + cache_.put(task.key, compiled, task.gpuId); + std::cerr << "[ShaderWorker] compiled key=" << task.key << " size=" << compiled.size() << "\n"; + // Optionally: enqueue a main-thread action to create the GPU shader object from compiled blob. + // This must be executed on the render thread because many APIs require a valid context. + } else { + std::cerr << "[ShaderWorker] compile failed for key=" << task.key << "\n"; + } + } +} diff --git a/src/render/shadercache/ShaderCompileWorker.h b/src/render/shadercache/ShaderCompileWorker.h new file mode 100644 index 00000000..f7e709bc --- /dev/null +++ b/src/render/shadercache/ShaderCompileWorker.h @@ -0,0 +1,31 @@ +#pragma once +#include "ThreadSafeQueue.h" +#include "ShaderCache.h" +#include +#include +#include +#include + +struct CompileTask { + std::string key; // hash of source + options + std::string source; // shader source (or path to IR) + std::string gpuId; // GPU id used for this compile +}; + +class ShaderCompileWorker { +public: + explicit ShaderCompileWorker(ShaderCache& cache); + ~ShaderCompileWorker(); + + void start(); + void stop(); + + void enqueue(CompileTask task); + +private: + void threadMain(); + ThreadSafeQueue queue_; + std::thread worker_; + std::atomic stop_{false}; + ShaderCache& cache_; +}; diff --git a/src/render/shadercache/ThreadSafeQueue.h b/src/render/shadercache/ThreadSafeQueue.h new file mode 100644 index 00000000..e79f1bba --- /dev/null +++ b/src/render/shadercache/ThreadSafeQueue.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include + +template +class ThreadSafeQueue { +public: + ThreadSafeQueue() = default; + ~ThreadSafeQueue() = default; + + void push(T item) { + { + std::lock_guard lk(m_); + q_.push(std::move(item)); + } + cv_.notify_one(); + } + + std::optional pop_wait(std::atomic& stopFlag) { + std::unique_lock lk(m_); + cv_.wait(lk, [&]{ return stopFlag.load() || !q_.empty(); }); + if (stopFlag.load() && q_.empty()) return std::nullopt; + T it = std::move(q_.front()); + q_.pop(); + return it; + }size_t size() const { + std::lock_guard lk(m_); + return q_.size(); + } + +private: + mutable std::mutex m_; + std::condition_variable cv_; + std::queue q_; +};EOF