This commit is contained in:
2026-08-15 19:58:39 +08:00
parent 8bfaeeb265
commit 26bbc29a16
29 changed files with 622 additions and 201 deletions
+5 -5
View File
@@ -348,7 +348,7 @@ inline nlohmann::json gallery_performance_capture_json(const Scene_Base& scene)
using namespace gallery_capture_detail;
const auto controller = scene.capture_state();
Json sessions = Json::array();
std::set<Render_Plan_Version> referenced_plans;
std::map<Render_Plan_Version, std::shared_ptr<const Render_Plan>> referenced_plans;
for (Capture_Session_Id id : scene.capture_sessions()) {
const auto session = scene.capture_session(id);
if (!session)
@@ -356,7 +356,7 @@ inline nlohmann::json gallery_performance_capture_json(const Scene_Base& scene)
Json frames = Json::array();
for (const auto& frame : session->frames) {
frames.push_back(frame_json(frame));
referenced_plans.insert(frame.snapshot->render_plan_version);
referenced_plans[frame.snapshot->render_plan_version] = frame.render_plan;
}
Json node_statistics = Json::array();
for (const auto& node : scene.node_statistics(id))
@@ -376,11 +376,11 @@ inline nlohmann::json gallery_performance_capture_json(const Scene_Base& scene)
});
}
if (const auto current = scene.render_plan_snapshot())
referenced_plans.insert(current->version);
referenced_plans[current->version] = current;
Json plans = Json::array();
std::shared_ptr<const Render_Plan> previous;
for (Render_Plan_Version version : referenced_plans) {
const auto plan = scene.find_render_plan(version);
for (const auto& [version, captured_plan] : referenced_plans) {
auto plan = captured_plan ? captured_plan : scene.find_render_plan(version);
if (!plan)
continue;
Json value = plan_json(scene, *plan);
+136 -80
View File
@@ -3,6 +3,7 @@
#include "common/Gallery_Scene_Interface.h"
#include "render_2D/Gallery_Scene2D.h"
#include "render_3D/Gallery_Scene3D.h"
#include <renderive/scheduling/Scheduler.hpp>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <atomic>
@@ -10,12 +11,13 @@
#include <cmath>
#include <condition_variable>
#include <cstdint>
#include <limits>
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <type_traits>
#include <utility>
namespace renderive::web {
@@ -123,26 +125,84 @@ std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene(std::uint64_t sessio
return make_gallery_scene_2d(session_id, std::move(case_id), mode, automatic_low_latency);
}
}
struct Gallery_Plot_Session::Impl {
struct Gallery_Plot_Session::Impl : std::enable_shared_from_this<Gallery_Plot_Session::Impl> {
using Clock = std::chrono::steady_clock;
class Automatic_Render_Scheduler final {
public:
static Automatic_Render_Scheduler& instance() {
static Automatic_Render_Scheduler scheduler;
return scheduler;
}
void arm(std::uint64_t session_id, std::weak_ptr<Impl> session, Clock::time_point deadline) {
std::lock_guard lock(mutex_);
if (const auto current = entries_.find(session_id); current != entries_.end()) {
schedule_.erase(current->second);
entries_.erase(current);
}
const auto iterator = schedule_.emplace(deadline, Entry{session_id, std::move(session)});
entries_.emplace(session_id, iterator);
condition_.notify_one();
}
void disarm(std::uint64_t session_id) noexcept {
std::lock_guard lock(mutex_);
const auto current = entries_.find(session_id);
if (current == entries_.end())
return;
schedule_.erase(current->second);
entries_.erase(current);
condition_.notify_one();
}
private:
struct Entry {
std::uint64_t session_id{};
std::weak_ptr<Impl> session;
};
using Schedule = std::multimap<Clock::time_point, Entry>;
Automatic_Render_Scheduler() : thread_([this](std::stop_token stop) { run(stop); }) {}
~Automatic_Render_Scheduler() {
thread_.request_stop();
condition_.notify_all();
}
void run(std::stop_token stop) noexcept {
std::unique_lock lock(mutex_);
while (!stop.stop_requested()) {
if (schedule_.empty()) {
condition_.wait(lock, [this, &stop] {
return stop.stop_requested() || !schedule_.empty();
});
continue;
}
const auto deadline = schedule_.begin()->first;
if (condition_.wait_until(lock, deadline) != std::cv_status::timeout)
continue;
if (schedule_.empty() || schedule_.begin()->first > Clock::now())
continue;
auto iterator = schedule_.begin();
Entry entry = std::move(iterator->second);
entries_.erase(entry.session_id);
schedule_.erase(iterator);
lock.unlock();
if (auto session = entry.session.lock())
session->automatic_render_due();
lock.lock();
}
}
std::mutex mutex_;
std::condition_variable condition_;
Schedule schedule_;
std::unordered_map<std::uint64_t, Schedule::iterator> entries_;
std::jthread thread_;
};
explicit Impl(bool enable_automatic_low_latency)
: automatic_low_latency(enable_automatic_low_latency),
session_id(next_session_id()) {}
: automatic_low_latency(enable_automatic_low_latency), session_id(next_session_id()) {}
~Impl() {
render_worker.request_stop();
scheduler_condition.notify_all();
if (automatic_low_latency)
Automatic_Render_Scheduler::instance().disarm(session_id);
}
static std::uint64_t next_session_id() noexcept {
static std::atomic<std::uint64_t> next{1};
return next.fetch_add(1, std::memory_order_relaxed);
}
void ensure_render_worker() {
if (!automatic_low_latency || render_worker.joinable())
return;
render_worker = std::jthread(
[this](std::stop_token stop) {
run_automatic_renderer(stop);
});
}
bool update_client_metrics(std::string_view message) {
if (!scene)
return false;
@@ -204,57 +264,59 @@ struct Gallery_Plot_Session::Impl {
return true;
}
[[nodiscard]] std::unique_lock<std::mutex> acquire_foreground_lock() {
foreground_waiters.fetch_add(1, std::memory_order_release);
scheduler_condition.notify_all();
std::unique_lock lock(mutex);
foreground_waiters.fetch_sub(1, std::memory_order_release);
scheduler_condition.notify_all();
return lock;
return std::unique_lock<std::mutex>(mutex);
}
void run_automatic_renderer(std::stop_token stop) {
using Clock = std::chrono::steady_clock;
std::unique_lock lock(mutex);
std::optional<Clock::time_point> last_render_started;
std::uint64_t active_generation = std::numeric_limits<std::uint64_t>::max();
while (!stop.stop_requested()) {
scheduler_condition.wait(lock, [this, &stop] {
return stop.stop_requested() || (scene && scene->can_render_automatically());
void arm_automatic_render_locked() noexcept {
if (!automatic_low_latency)
return;
auto& scheduler = Automatic_Render_Scheduler::instance();
if (!scene || !scene->can_render_automatically() || render_task_pending) {
scheduler.disarm(session_id);
return;
}
try {
const auto interval = std::chrono::nanoseconds(
std::max<std::uint64_t>(1, scene->kernel_refresh_interval_ns()));
const auto deadline = last_render_started ? *last_render_started + interval : Clock::now();
scheduler.arm(session_id, weak_from_this(), deadline);
} catch (...) {
scheduler.disarm(session_id);
}
}
void automatic_render_due() {
std::uint64_t generation{};
{
std::lock_guard lock(mutex);
if (!automatic_low_latency || !scene || !scene->can_render_automatically() || render_task_pending)
return;
render_task_pending = true;
generation = scene_generation;
}
auto self = shared_from_this();
try {
renderive::scheduling::enqueue_task([self = std::move(self), generation] {
self->run_automatic_render(generation);
});
if (stop.stop_requested())
break;
if (active_generation != scene_generation) {
active_generation = scene_generation;
last_render_started.reset();
} catch (...) {
std::lock_guard lock(mutex);
render_task_pending = false;
arm_automatic_render_locked();
}
}
void run_automatic_render(std::uint64_t generation) noexcept {
const auto started = Clock::now();
try {
std::lock_guard lock(mutex);
if (generation == scene_generation && scene && scene->can_render_automatically()) {
(void)scene->render_latest_frame();
last_render_started = started;
}
if (foreground_waiters.load(std::memory_order_acquire) != 0) {
scheduler_condition.wait(lock, [this, &stop] {
return stop.stop_requested() || foreground_waiters.load(std::memory_order_acquire) == 0;
});
continue;
}
const auto revision = scheduler_revision;
const auto interval = std::chrono::nanoseconds(std::max<std::uint64_t>(1, scene->kernel_refresh_interval_ns()));
const auto now = Clock::now();
const auto deadline = last_render_started ? *last_render_started + interval : now;
if (now < deadline) {
const auto spin_window = std::min(interval / 4, std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(250)));
const auto coarse_deadline = deadline > now + spin_window ? deadline - spin_window : now;
if (now < coarse_deadline) {
scheduler_condition.wait_until(lock, coarse_deadline, [this, &stop, revision, active_generation] {
return stop.stop_requested() || scheduler_revision != revision || scene_generation != active_generation ||
foreground_waiters.load(std::memory_order_acquire) != 0 || !scene || !scene->can_render_automatically();
});
continue;
}
lock.unlock();
while (!stop.stop_requested() && Clock::now() < deadline)
std::this_thread::yield();
lock.lock();
continue;
}
const auto started = Clock::now();
(void)scene->render_latest_frame();
last_render_started = started;
render_task_pending = false;
arm_automatic_render_locked();
} catch (...) {
std::lock_guard lock(mutex);
render_task_pending = false;
arm_automatic_render_locked();
}
}
static bool affects_render_schedule(const Web_Event& event) {
@@ -278,13 +340,11 @@ struct Gallery_Plot_Session::Impl {
}
std::unique_ptr<Gallery_Scene_Interface> scene;
std::mutex mutex;
std::condition_variable scheduler_condition;
std::uint64_t scheduler_revision{};
std::optional<Clock::time_point> last_render_started;
std::uint64_t scene_generation{};
bool automatic_low_latency{};
bool render_task_pending{};
std::uint64_t session_id{};
std::atomic<std::uint32_t> foreground_waiters{};
std::jthread render_worker;
std::optional<Web_Response> handle_gallery(const Gallery_Request& request) {
if (request.kind == Gallery_Request_Kind::Catalog)
return Web_Response{Web_Response_Type::Json, Gallery_Protocol::catalog_json()};
@@ -297,8 +357,8 @@ struct Gallery_Plot_Session::Impl {
};
scene = make_gallery_scene(session_id, open->case_id, open->frame_mode, automatic_low_latency);
++scene_generation;
if (scene->frame_mode() == Gallery_Frame_Mode::Low_Latency)
ensure_render_worker();
last_render_started.reset();
arm_automatic_render_locked();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(scene->case_id(), scene->controls().dump(),
@@ -313,10 +373,8 @@ struct Gallery_Plot_Session::Impl {
Gallery_Protocol::error_json("请先发送 gallery_open")
};
if (request.kind == Gallery_Request_Kind::Observe) {
if (update_client_metrics(request.message)) {
++scheduler_revision;
scheduler_condition.notify_all();
}
if (update_client_metrics(request.message))
arm_automatic_render_locked();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::observer_json(
@@ -324,10 +382,8 @@ struct Gallery_Plot_Session::Impl {
};
}
if (request.kind == Gallery_Request_Kind::Refresh) {
if (update_client_metrics(request.message)) {
++scheduler_revision;
scheduler_condition.notify_all();
}
if (update_client_metrics(request.message))
arm_automatic_render_locked();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(
@@ -382,6 +438,8 @@ struct Gallery_Plot_Session::Impl {
const auto mode = scene->frame_mode();
scene = make_gallery_scene(session_id, id, mode, automatic_low_latency);
++scene_generation;
last_render_started.reset();
arm_automatic_render_locked();
return Web_Response{
Web_Response_Type::Json,
Gallery_Protocol::case_json_from_controls(id, scene->controls().dump(),
@@ -455,16 +513,14 @@ struct Gallery_Plot_Session::Impl {
},
event);
if (reschedule)
++scheduler_revision;
arm_automatic_render_locked();
}
if (reschedule)
scheduler_condition.notify_all();
return response;
}
};
Gallery_Plot_Session::Gallery_Plot_Session() : Gallery_Plot_Session(false) {}
Gallery_Plot_Session::Gallery_Plot_Session(bool automatic_low_latency)
: impl_(std::make_unique<Impl>(automatic_low_latency)) {}
: impl_(std::make_shared<Impl>(automatic_low_latency)) {}
Gallery_Plot_Session::~Gallery_Plot_Session() = default;
std::optional<Web_Response> Gallery_Plot_Session::handle(const Web_Event& event) {
return impl_->handle(event);
+1 -1
View File
@@ -19,7 +19,7 @@ public:
private:
struct Impl;
std::unique_ptr<Impl> impl_;
std::shared_ptr<Impl> impl_;
};
} // namespace renderive::web
@@ -630,6 +630,7 @@ public:
render_performance.automatic_low_latency_scheduler =
automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency;
const Gallery_Client_Performance client_performance = client_performance_;
const auto scene_execution = plot_.render_scene().execution_statistics();
nlohmann::json observer_data =
adminive::to_frontend_json<nlohmann::json>(observer);
nlohmann::json consumer_feedback_data =
@@ -656,6 +657,13 @@ public:
{"client_performance", std::move(client_performance_data)},
{"scheduler", adminive::to_frontend_json<nlohmann::json>(
renderive::scheduling::scheduler_statistics())},
{"queue_pressure",
{{"scene_execution",
{{"capacity", scene_execution.capacity},
{"queued", scene_execution.queued},
{"peak_queued", scene_execution.peak_queued},
{"backpressure_count", scene_execution.backpressure_count},
{"backpressure_wait_ns", scene_execution.backpressure_wait_ns}}}}},
{"renderable_observers", controls_.observers(plot_.render_scene())},
{"performance_capture", gallery_performance_capture_json(plot_.render_scene())},
{"last_action_result", last_action_result_}
@@ -440,6 +440,8 @@ public:
[[nodiscard]] std::string telemetry_json() const override {
const auto status = scene_.frame_status();
const auto now = std::chrono::steady_clock::now();
const auto scene_execution = scene_.render_scene().execution_statistics();
const auto runtime = scene_.runtime_statistics();
return nlohmann::json{
{"kernel_observer",
{{"mode", mode_id(frame_mode_)},
@@ -464,6 +466,29 @@ public:
client_performance_.websocket_buffered_bytes}}},
{"scheduler", adminive::to_frontend_json<nlohmann::json>(
renderive::scheduling::scheduler_statistics())},
{"queue_pressure",
{{"scene_execution",
{{"capacity", scene_execution.capacity},
{"queued", scene_execution.queued},
{"peak_queued", scene_execution.peak_queued},
{"backpressure_count", scene_execution.backpressure_count},
{"backpressure_wait_ns", scene_execution.backpressure_wait_ns}}},
{"render_domain",
{{"capacity", runtime.render_domain.capacity},
{"active", runtime.render_domain.active},
{"peak_active", runtime.render_domain.peak_active},
{"queued", runtime.render_domain.queued},
{"peak_queued", runtime.render_domain.peak_queued},
{"backpressure_count", runtime.render_domain.backpressure_count},
{"backpressure_wait_ns", runtime.render_domain.backpressure_wait_ns}}},
{"gpu_completion",
{{"capacity", runtime.gpu_completion.capacity},
{"active", runtime.gpu_completion.active},
{"peak_active", runtime.gpu_completion.peak_active},
{"queued", runtime.gpu_completion.queued},
{"peak_queued", runtime.gpu_completion.peak_queued},
{"backpressure_count", runtime.gpu_completion.backpressure_count},
{"backpressure_wait_ns", runtime.gpu_completion.backpressure_wait_ns}}}}},
{"session_id", session_id_},
{"performance_capture",
gallery_performance_capture_json(scene_.render_scene())},