mirror of
https://github.com/Toastconcern/FrameStudio.git
synced 2026-10-28 23:03:21 +01:00
Frame Studio - Gaussian splats (PLY, SPZ v2-v4), PLY vertex colours, panorama skies - Copy, cut, paste and duplicate for objects, splats and world items - Walkable-area rebake moved off the UI thread; viewport one frame sooner; refresh-locked pacing (FS_PACE=timer for the old one) - Fixed: bundle thumbnail use-after-free crash, auto-save deleting environments, saves into scratch copies, edits lost on non-ASCII paths, unopenable manifests with inf/nan, bundle save data loss, opening a home marking it changed, gizmo NaN, long paths, many malformed-file crashes - Updater verifies sha256, journals the swap and never leaves the exe missing Frame Home - Seats turn you to face their way - Draw batching (Hand Tutorials up to ~20% less GPU time), cut-out EQUAL pass, late head-pose read before submit - Proper queue-family hand-over to the compositor layer; layer descriptor generations and seqlocked slots; structural SPIR-V check - Fixed: hangs/crashes on damaged .fenv files and shaders, render-pass validation error, sync hazard in the foveation smoothing pass, unverified update downloads, half-done installs Tests: tests_loco/env_share/spv_check wired into CMake (FRAME_TESTS) and CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsaXGU27gsrXjDoFtyFJP9
95 lines
3.3 KiB
C++
95 lines
3.3 KiB
C++
#pragma once
|
|
// A small persistent thread pool for per-frame work split across cores: the CPU animation's skinning and the
|
|
// renderer's vertex rebuilds. forEach(n, fn) runs fn(i) for every i < n on the pool and the calling thread, and
|
|
// returns when all are done. Items are handed out one at a time (they differ a lot in size). Not re-entrant: a
|
|
// forEach inside fn runs serially.
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <cstddef>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace par {
|
|
|
|
class Pool {
|
|
public:
|
|
static Pool& get() { static Pool p; return p; }
|
|
|
|
void forEach(size_t n, const std::function<void(size_t)>& fn) {
|
|
if (n == 0) return;
|
|
if (n == 1 || threads_.empty() || inside_) { for (size_t i = 0; i < n; ++i) fn(i); return; }
|
|
std::unique_lock<std::mutex> busy(run_); // one forEach at a time (from any thread)
|
|
{
|
|
std::lock_guard<std::mutex> g(m_);
|
|
fn_ = &fn; n_ = n; next_.store(0); left_ = threads_.size();
|
|
++gen_;
|
|
}
|
|
cv_.notify_all();
|
|
work();
|
|
std::unique_lock<std::mutex> g(m_);
|
|
done_.wait(g, [&] { return left_ == 0; });
|
|
fn_ = nullptr;
|
|
// An item that threw (a damaged file's bad_alloc, a parser's throw): rethrown here, once every thread is done
|
|
// with fn. Thrown on a pool thread it ended the process; on this one it left the pool running a dead fn.
|
|
if (error_) { std::exception_ptr e = error_; error_ = nullptr; g.unlock(); std::rethrow_exception(e); }
|
|
}
|
|
|
|
~Pool() {
|
|
{ std::lock_guard<std::mutex> g(m_); stop_ = true; ++gen_; }
|
|
cv_.notify_all();
|
|
for (auto& t : threads_) t.join();
|
|
}
|
|
|
|
private:
|
|
Pool() {
|
|
const unsigned hw = std::thread::hardware_concurrency();
|
|
const unsigned n = hw > 2 ? std::min(hw - 2, 6u) : 0; // leave a core for the render thread's other work
|
|
for (unsigned i = 0; i < n; ++i) threads_.emplace_back([this] { loop(); });
|
|
}
|
|
void work() {
|
|
inside_ = true;
|
|
for (size_t i; (i = next_.fetch_add(1)) < n_;) {
|
|
try {
|
|
(*fn_)(i);
|
|
} catch (...) {
|
|
std::lock_guard<std::mutex> g(m_);
|
|
if (!error_) error_ = std::current_exception();
|
|
}
|
|
}
|
|
inside_ = false;
|
|
}
|
|
void loop() {
|
|
uint64_t seen = 0;
|
|
for (;;) {
|
|
{
|
|
std::unique_lock<std::mutex> g(m_);
|
|
cv_.wait(g, [&] { return gen_ != seen; });
|
|
seen = gen_;
|
|
if (stop_) return;
|
|
}
|
|
work();
|
|
std::lock_guard<std::mutex> g(m_);
|
|
if (--left_ == 0) done_.notify_one();
|
|
}
|
|
}
|
|
|
|
std::vector<std::thread> threads_;
|
|
std::mutex m_, run_;
|
|
std::condition_variable cv_, done_;
|
|
const std::function<void(size_t)>* fn_ = nullptr;
|
|
size_t n_ = 0, left_ = 0;
|
|
std::atomic<size_t> next_{0};
|
|
uint64_t gen_ = 0;
|
|
bool stop_ = false;
|
|
std::exception_ptr error_; // the first item of this forEach that threw (under m_)
|
|
static inline thread_local bool inside_ = false;
|
|
};
|
|
|
|
inline void forEach(size_t n, const std::function<void(size_t)>& fn) { Pool::get().forEach(n, fn); }
|
|
|
|
} // namespace par
|