改进
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())},
|
||||
|
||||
Reference in New Issue
Block a user