仍有bug

This commit is contained in:
2026-08-24 16:27:27 +08:00
parent ca6d8f8e79
commit f463a756fc
27 changed files with 1201 additions and 898 deletions
+158 -466
View File
@@ -1,7 +1,7 @@
#include "Plot.hpp"
#include "Renderable_Adapter.hpp"
#include "Sliding_Statistics.hpp"
#include <nlohmann/json.hpp>
#include <magic_enum/magic_enum.hpp>
#include <render_2D/plottable/Plottables.hpp>
#include <render_3D/Render_3D.hpp>
#include <render_3D/Gpu_Completion_State.hpp>
@@ -17,6 +17,7 @@
#include <memory>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <span>
#include <stdexcept>
#include <unordered_map>
@@ -33,43 +34,11 @@ using Scene_3D = Impl<Render_Scene_3D>;
constexpr std::uint16_t plot_stream_protocol_version{9};
constexpr std::size_t diagnostic_window_capacity{600};
class Statistic_Input_Batch final {
public:
Statistic_Input_Batch(
std::initializer_list<std::pair<std::string_view, double>> initial) {
for (const auto& value : initial) emplace_back(value.first, value.second);
}
void emplace_back(std::string_view key, double value) {
if (size == values.size())
throw std::logic_error("frame statistic input capacity exceeded");
values[size++] = {key, value};
}
[[nodiscard]] std::span<const std::pair<std::string_view, double>> view() const {
return {values.data(), size};
}
private:
std::array<std::pair<std::string_view, double>, 48> values{}; /* 单帧全部瞬时统计的栈内存储。 */
std::size_t size{}; /* 当前已写入的有效字段数量。 */
};
double elapsed_milliseconds(std::chrono::steady_clock::time_point start,
std::chrono::steady_clock::time_point finish) {
if (start == std::chrono::steady_clock::time_point{} || finish < start)
return 0.0;
return std::chrono::duration<double, std::milli>(finish - start).count();
}
struct Web_Input_Metadata {
Plot_Input_Event input;
std::chrono::steady_clock::time_point scene_dispatched;
};
struct Plot_Input_Observation {
double admission_ms{}; /* WebSocket 接收到提交 Scene 事件流的耗时。 */
double scene_wait_ms{}; /* Scene 收到事件到 Prepare 消费事件的等待。 */
double dispatch_ms{}; /* Scene 开始分发到 Renderable 完成消费的耗时。 */
double server_consume_ms{}; /* WebSocket 接收到 Renderable 完成消费的服务端总耗时。 */
std::size_t coalesced_event_count{}; /* 本次服务端事件代表的浏览器原始事件数量。 */
};
struct Web_Input_Event {
virtual ~Web_Input_Event() = default;
[[nodiscard]] virtual const Web_Input_Metadata& web_input_metadata() const noexcept = 0;
@@ -128,29 +97,19 @@ private:
};
std::string_view pacing_mode_name(Frame_Pacing_Mode mode) {
switch (mode) {
case Frame_Pacing_Mode::manual: return "manual";
case Frame_Pacing_Mode::fixed_rate: return "fixed_rate";
case Frame_Pacing_Mode::maximum_rate: return "maximum_rate";
}
throw std::logic_error("unknown frame pacing mode");
const auto name = magic_enum::enum_name(mode);
if (name.empty()) throw std::logic_error("unknown frame pacing mode");
return name;
}
std::optional<Frame_Pacing_Mode> parse_pacing_mode(std::string_view value) {
if (value == "manual") return Frame_Pacing_Mode::manual;
if (value == "fixed_rate") return Frame_Pacing_Mode::fixed_rate;
if (value == "maximum_rate") return Frame_Pacing_Mode::maximum_rate;
return std::nullopt;
return magic_enum::enum_cast<Frame_Pacing_Mode>(value);
}
std::string_view pixel_format_name(render_2d::Pixel_Format format) {
switch (format) {
case render_2d::Pixel_Format::bgra8_premultiplied:
return "bgra8_premultiplied";
case render_2d::Pixel_Format::rgba8:
return "rgba8";
}
throw std::logic_error("unknown 2D pixel format");
const auto name = magic_enum::enum_name(format);
if (name.empty()) throw std::logic_error("unknown 2D pixel format");
return name;
}
std::string_view pixel_format_name(render_3d::Pixel_Format format) {
@@ -226,288 +185,23 @@ nlohmann::json Frame_Policy::write_prop(std::string_view key,
return {{"success", false}, {"error", "unknown frame runtime property"}};
}
class Plot_Diagnostics final {
public:
void submit(Render_Frame& frame, Frame_Identity rendered_identity,
std::uint32_t width, std::uint32_t height,
std::size_t pixel_bytes, std::string output_format,
std::string native_format,
std::vector<std::string> supported_formats,
const Frame_Pacing_Properties& pacing, bool is_3d);
void submit_input(const Plot_Input_Observation& observation);
void reset();
[[nodiscard]] nlohmann::json snapshot() const;
private:
Sliding_Statistics_Set frame_values{diagnostic_window_capacity};
Sliding_Statistics_Set input_values{diagnostic_window_capacity};
mutable std::mutex state_mutex;
std::chrono::steady_clock::time_point previous_completion{};
std::uint64_t previous_sequence{};
std::uint64_t dropped_sequences{};
std::uint64_t sequence{};
std::uint64_t correlation_id{};
std::uint64_t rendered_sequence{};
std::uint64_t rendered_correlation_id{};
std::uint64_t created_time_unix_ns{};
std::uint32_t width{};
std::uint32_t height{};
std::size_t pixel_bytes{};
std::string output_format{};
std::string native_format{};
std::vector<std::string> supported_formats{};
Frame_Pacing_Properties pacing{};
bool is_3d{};
};
std::string_view measurement_key(Frame_Trace_Measurement measurement) {
switch (measurement) {
case Frame_Trace_Measurement::backend_apply_ns: return "backend_apply_ms";
case Frame_Trace_Measurement::backend_plan_ns: return "backend_plan_ms";
case Frame_Trace_Measurement::backend_execute_ns: return "backend_execute_ms";
case Frame_Trace_Measurement::backend_submit_ns: return "backend_submit_ms";
case Frame_Trace_Measurement::gpu_fence_wait_ns: return "gpu_fence_wait_ms";
case Frame_Trace_Measurement::gpu_render_ns: return "gpu_render_ms";
case Frame_Trace_Measurement::gpu_transition_ns: return "gpu_transition_ms";
case Frame_Trace_Measurement::gpu_copy_ns: return "gpu_copy_ms";
case Frame_Trace_Measurement::gpu_total_ns: return "gpu_total_ms";
case Frame_Trace_Measurement::readback_ns: return "readback_ms";
case Frame_Trace_Measurement::count: break;
void append_statistic_json(nlohmann::json& output,
const Frame_Statistics_State& state) {
for (const auto statistic : magic_enum::enum_values<Frame_Statistic>()) {
if (statistic == Frame_Statistic::count) continue;
const auto& value =
state.values[static_cast<std::size_t>(statistic)];
if (value.count == 0) continue;
output[magic_enum::enum_name(statistic)] = {
{"count", value.count}, {"latest", value.latest},
{"minimum", value.minimum}, {"maximum", value.maximum},
{"average", value.average},
{"trimmed_average", value.trimmed_average},
{"variability", value.variability}, {"p50", value.p50},
{"p95", value.p95}, {"p99", value.p99}};
}
throw std::logic_error("unknown frame trace measurement");
}
void Plot_Diagnostics::submit(
Render_Frame& frame, Frame_Identity rendered_identity,
std::uint32_t value_width, std::uint32_t value_height,
std::size_t value_pixel_bytes, std::string value_output_format,
std::string value_native_format,
std::vector<std::string> value_supported_formats,
const Frame_Pacing_Properties& value_pacing, bool value_is_3d) {
constexpr auto marker_count = static_cast<std::size_t>(Frame_Trace_Marker::count);
constexpr auto measurement_count = static_cast<std::size_t>(Frame_Trace_Measurement::count);
std::array<std::optional<double>, marker_count> markers{};
for (const auto& point : frame.trace_points())
markers[static_cast<std::size_t>(point.marker)] =
static_cast<double>(point.elapsed_ns) / 1'000'000.0;
const auto marker = [&](Frame_Trace_Marker value) {
return markers[static_cast<std::size_t>(value)].value_or(0.0);
};
const auto interval = [&](Frame_Trace_Marker first, Frame_Trace_Marker last) {
const auto start = markers[static_cast<std::size_t>(first)];
const auto finish = markers[static_cast<std::size_t>(last)];
return start && finish ? std::max(0.0, *finish - *start) : 0.0;
};
std::array<double, measurement_count> measurements{};
const auto trace_measurements = frame.trace_values();
for (const auto& value : trace_measurements)
measurements[static_cast<std::size_t>(value.measurement)] =
static_cast<double>(value.value_ns) / 1'000'000.0;
const auto measurement = [&](Frame_Trace_Measurement value) {
return measurements[static_cast<std::size_t>(value)];
};
Statistic_Input_Batch values{
{"server_completion_ms", marker(Frame_Trace_Marker::frame_ready)},
{"payload_megabytes", static_cast<double>(value_pixel_bytes) / (1024.0 * 1024.0)},
{"scene_render_ms", interval(Frame_Trace_Marker::scene_render_started, Frame_Trace_Marker::scene_render_finished)},
{"event_dispatch_ms", interval(Frame_Trace_Marker::event_dispatch_started, Frame_Trace_Marker::event_dispatch_finished)},
{"prepare_ms", interval(Frame_Trace_Marker::prepare_started, Frame_Trace_Marker::prepare_finished)},
{"paint_ms", interval(Frame_Trace_Marker::paint_started, Frame_Trace_Marker::paint_finished)},
{"backend_queue_ms", interval(Frame_Trace_Marker::backend_queue_entered, Frame_Trace_Marker::backend_queue_left)},
{"gpu_submission_ms", interval(Frame_Trace_Marker::gpu_submitted, Frame_Trace_Marker::gpu_completed)},
{"readback_stage_ms", interval(Frame_Trace_Marker::readback_started, Frame_Trace_Marker::readback_finished)},
{"callback_ms", interval(Frame_Trace_Marker::callback_started, Frame_Trace_Marker::frame_ready)}};
for (const auto& value : trace_measurements)
values.emplace_back(measurement_key(value.measurement),
static_cast<double>(value.value_ns) / 1'000'000.0);
double remaining = marker(Frame_Trace_Marker::frame_ready);
auto take = [&](double requested) {
const auto result = std::min(remaining, std::max(0.0, requested));
remaining -= result;
return result;
};
const double scene_time = interval(Frame_Trace_Marker::scene_render_started,
Frame_Trace_Marker::scene_render_finished);
const double event_time = std::min(scene_time, interval(
Frame_Trace_Marker::event_dispatch_started,
Frame_Trace_Marker::event_dispatch_finished));
const double prepare_time = std::min(std::max(0.0, scene_time - event_time),
interval(Frame_Trace_Marker::prepare_started, Frame_Trace_Marker::prepare_finished));
const double paint_time = std::min(std::max(0.0, scene_time - event_time - prepare_time),
interval(Frame_Trace_Marker::paint_started, Frame_Trace_Marker::paint_finished));
values.emplace_back(value_is_3d ? "pipeline_3d_event_ms" : "pipeline_2d_event_ms", take(event_time));
values.emplace_back(value_is_3d ? "pipeline_3d_prepare_ms" : "pipeline_2d_prepare_ms", take(prepare_time));
values.emplace_back(value_is_3d ? "pipeline_3d_submit_graph_ms" : "pipeline_2d_paint_ms", take(paint_time));
values.emplace_back(value_is_3d ? "pipeline_3d_scene_coordination_ms" : "pipeline_2d_scene_coordination_ms",
take(std::max(0.0, scene_time - event_time - prepare_time - paint_time)));
if (value_is_3d) {
const double scene_finished = marker(Frame_Trace_Marker::scene_render_finished);
const double queue_entered = marker(Frame_Trace_Marker::backend_queue_entered);
const double backend_prepare_started = marker(Frame_Trace_Marker::backend_prepare_started);
const double backend_prepare_finished = marker(Frame_Trace_Marker::backend_prepare_finished);
const double submit_queued = marker(Frame_Trace_Marker::backend_submit_queued);
const double queue_left = marker(Frame_Trace_Marker::backend_queue_left);
values.emplace_back("pipeline_3d_prepare_queue_ms", take(std::max(
0.0, backend_prepare_started - std::max(scene_finished, queue_entered))));
double preparation_window = std::max(0.0, backend_prepare_finished - backend_prepare_started);
const auto take_preparation = [&](Frame_Trace_Measurement key) {
const double value = std::min(preparation_window, std::max(0.0, measurement(key)));
preparation_window -= value;
return take(value);
};
values.emplace_back("pipeline_3d_backend_apply_ms", take_preparation(Frame_Trace_Measurement::backend_apply_ns));
values.emplace_back("pipeline_3d_backend_plan_ms", take_preparation(Frame_Trace_Measurement::backend_plan_ns));
values.emplace_back("pipeline_3d_backend_execute_ms", take_preparation(Frame_Trace_Measurement::backend_execute_ns));
values.emplace_back("pipeline_3d_backend_commands_ms", take(preparation_window));
values.emplace_back("pipeline_3d_backend_queue_ms", take(std::max(0.0, queue_left - submit_queued)));
const double gpu_submitted = marker(Frame_Trace_Marker::gpu_submitted);
double submit_window = std::max(0.0, gpu_submitted - queue_left);
const double measured_submit = std::min(submit_window, std::max(
0.0, measurement(Frame_Trace_Measurement::backend_submit_ns)));
values.emplace_back("pipeline_3d_backend_submit_ms", take(measured_submit));
submit_window -= measured_submit;
values.emplace_back("pipeline_3d_submit_handoff_ms", take(submit_window));
double gpu_window = interval(Frame_Trace_Marker::gpu_submitted,
Frame_Trace_Marker::gpu_completed);
const auto take_gpu = [&](Frame_Trace_Measurement key) {
const double value = std::min(gpu_window, std::max(0.0, measurement(key)));
gpu_window -= value;
return take(value);
};
values.emplace_back("pipeline_3d_gpu_render_ms", take_gpu(Frame_Trace_Measurement::gpu_render_ns));
values.emplace_back("pipeline_3d_gpu_transition_ms", take_gpu(Frame_Trace_Measurement::gpu_transition_ns));
values.emplace_back("pipeline_3d_gpu_copy_ms", take_gpu(Frame_Trace_Measurement::gpu_copy_ns));
values.emplace_back("pipeline_3d_gpu_sync_ms", take(gpu_window));
values.emplace_back("pipeline_3d_readback_ms", take(interval(
Frame_Trace_Marker::readback_started, Frame_Trace_Marker::readback_finished)));
values.emplace_back("pipeline_3d_callback_ms", take(interval(
Frame_Trace_Marker::callback_started, Frame_Trace_Marker::frame_ready)));
values.emplace_back("pipeline_3d_completion_handoff_ms", remaining);
} else {
values.emplace_back("pipeline_2d_callback_ms", take(interval(
Frame_Trace_Marker::callback_started, Frame_Trace_Marker::frame_ready)));
values.emplace_back("pipeline_2d_frame_handoff_ms", remaining);
}
const auto now = std::chrono::steady_clock::now();
const auto identity = frame.identity();
{
std::lock_guard lock(state_mutex);
if (previous_completion != std::chrono::steady_clock::time_point{}) {
values.emplace_back("frame_interval_ms", elapsed_milliseconds(previous_completion, now));
if (identity.sequence > previous_sequence + 1U)
dropped_sequences += identity.sequence - previous_sequence - 1U;
}
previous_completion = now;
previous_sequence = identity.sequence;
sequence = identity.sequence;
correlation_id = identity.correlation_id;
this->rendered_sequence = rendered_identity.sequence;
rendered_correlation_id = rendered_identity.correlation_id;
created_time_unix_ns = frame.created_time_unix_ns();
width = value_width;
height = value_height;
pixel_bytes = value_pixel_bytes;
output_format = std::move(value_output_format);
native_format = std::move(value_native_format);
supported_formats = std::move(value_supported_formats);
pacing = value_pacing;
is_3d = value_is_3d;
}
frame_values.submit(values.view());
}
void Plot_Diagnostics::submit_input(const Plot_Input_Observation& observation) {
const std::array<std::pair<std::string_view, double>, 5> values{{
{"input_admission_ms", observation.admission_ms},
{"input_scene_wait_ms", observation.scene_wait_ms},
{"input_dispatch_ms", observation.dispatch_ms},
{"input_server_consume_ms", observation.server_consume_ms},
{"input_coalesced_event_count", static_cast<double>(observation.coalesced_event_count)}}};
input_values.submit(values);
}
void Plot_Diagnostics::reset() {
frame_values.reset();
input_values.reset();
std::lock_guard lock(state_mutex);
previous_completion = {};
previous_sequence = 0;
dropped_sequences = 0;
}
nlohmann::json Plot_Diagnostics::snapshot() const {
nlohmann::json frame_statistics = nlohmann::json::object();
for (const auto& value : frame_values.snapshot())
frame_statistics[value.key] = value.statistics;
nlohmann::json input_statistics = nlohmann::json::object();
for (const auto& value : input_values.snapshot())
input_statistics[value.key] = value.statistics;
std::lock_guard lock(state_mutex);
const auto interval = frame_statistics.find("frame_interval_ms");
const double frame_rate = interval != frame_statistics.end() &&
interval->value("trimmed_average", 0.0) > 0.0
? 1'000.0 / interval->value("trimmed_average", 0.0) : 0.0;
nlohmann::json output{
{"protocol", "aethera.plot.diagnostics"}, {"version", 1},
{"dimension", is_3d ? "3D" : "2D"},
{"sequence", sequence}, {"correlation_id", correlation_id},
{"rendered_sequence", rendered_sequence},
{"rendered_correlation_id", rendered_correlation_id},
{"generated_time_unix_ms", static_cast<double>(created_time_unix_ns) / 1'000'000.0},
{"delivery", pixel_bytes == 0 ? "diagnostics" : "gallery-video"},
{"frame_rate_fps", frame_rate}, {"dropped_sequence_count", dropped_sequences},
{"window_capacity", diagnostic_window_capacity},
{"pixel", {{"width", width}, {"height", height},
{"format", output_format}, {"native_format", native_format},
{"supported_formats", supported_formats}, {"byte_length", pixel_bytes}}},
{"pacing", {{"mode", pacing_mode_name(pacing.mode)},
{"fixed_rate_fps", pacing.fixed_rate_fps},
{"render_enabled", pacing.render_enabled},
{"video_enabled", pacing.video_enabled}}},
{"frame_statistics", std::move(frame_statistics)},
{"input_statistics", std::move(input_statistics)}};
if (is_3d) {
const auto gpu = gpu_completion_state();
const auto milliseconds = [](std::uint64_t nanoseconds) {
return static_cast<double>(nanoseconds) / 1'000'000.0;
};
output["gpu_completion_domain"] = {
{"capacity", gpu.capacity},
{"in_flight", gpu.in_flight},
{"peak_in_flight", gpu.peak_in_flight},
{"utilization_percent", gpu.capacity == 0 ? 0.0 :
100.0 * static_cast<double>(gpu.in_flight) /
static_cast<double>(gpu.capacity)},
{"watched", gpu.watched},
{"peak_watched", gpu.peak_watched},
{"active_fences", gpu.active_fences},
{"pending_fences", gpu.pending_fences},
{"reservation_count", gpu.reservation_count},
{"completion_count", gpu.completion_count},
{"cancellation_count", gpu.cancellation_count},
{"fence_probe_count", gpu.fence_probe_count},
{"fence_wait_count", gpu.fence_wait_count},
{"fence_wait_timeout_count", gpu.fence_wait_timeout_count},
{"fence_wait_total_ms", milliseconds(gpu.fence_wait_total_ns)},
{"fence_wait_average_ms", gpu.fence_wait_count == 0 ? 0.0 :
milliseconds(gpu.fence_wait_total_ns) /
static_cast<double>(gpu.fence_wait_count)},
{"fence_wait_max_ms", milliseconds(gpu.fence_wait_max_ns)},
{"callback_total_ms", milliseconds(gpu.callback_total_ns)},
{"callback_average_ms", gpu.completion_count == 0 ? 0.0 :
milliseconds(gpu.callback_total_ns) /
static_cast<double>(gpu.completion_count)},
{"callback_max_ms", milliseconds(gpu.callback_max_ns)},
{"callback_failure_count", gpu.callback_failure_count},
{"backpressure_count", gpu.backpressure_count},
{"backpressure_wait_ms", milliseconds(gpu.backpressure_wait_ns)},
{"fault_count", gpu.fault_count},
{"abandoned_count", gpu.abandoned_count},
{"stopping", gpu.stopping}};
}
return output;
}
template <typename Scene_Object>
void dispatch_plot_input(Scene_Object& scene, const Plot_Input_Event& input) {
@@ -598,7 +292,6 @@ struct Plot::Private {
std::atomic<std::shared_ptr<const std::string>> terminal_failure{}; /* 首次 Plot Unknown Failure 的唯一终止状态。 */
std::uint64_t next_frame_sequence{1};
Frame_Policy frame_policy{};
Plot_Diagnostics diagnostics{};
mutable std::mutex frame_mutex;
static constexpr std::size_t scene_frame_capacity{3};
std::array<Managed_Frame, scene_frame_capacity> frame_slots{}; /* Scene 借用的稳定三缓冲物理帧。 */
@@ -626,11 +319,10 @@ struct Plot::Private {
[[nodiscard]] nlohmann::json schema() const;
[[nodiscard]] Stream_Snapshot stream_snapshot() const;
void publish(std::shared_ptr<const Plot_Stream_Frame> frame) noexcept;
void consume_tick();
void consume_tick(std::weak_ptr<Plot> lifetime);
void clock_tick(const Plot_Render_Tick& tick);
void render_frame(Plot_Render_Tick tick);
void queue_completed_frame(Render_Frame* frame);
void collect_consumed_input_statistics();
void fail(std::exception_ptr failure) noexcept;
};
@@ -700,18 +392,27 @@ void Plot::Private::publish(
catch (...) {}
}
void Plot::Private::consume_tick() {
for (;;) {
std::optional<Plot_Render_Tick> tick;
{
std::lock_guard lock(tick_mutex);
tick = std::exchange(pending_tick, {});
if (!tick) {
tick_task_scheduled = false;
return;
}
}
clock_tick(*tick);
void Plot::Private::consume_tick(std::weak_ptr<Plot> lifetime) {
std::optional<Plot_Render_Tick> tick;
{
std::lock_guard lock(tick_mutex);
tick = std::exchange(pending_tick, {});
}
if (tick) clock_tick(*tick);
bool schedule_again{};
{
std::lock_guard lock(tick_mutex);
schedule_again = pending_tick.has_value();
if (!schedule_again) tick_task_scheduled = false;
}
if (schedule_again) {
aethera::schedule_task([lifetime] {
const auto plot = lifetime.lock();
if (!plot) return;
try { plot->d->consume_tick(lifetime); }
catch (...) { plot->d->fail(std::current_exception()); }
});
}
}
@@ -787,9 +488,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
(*scene_2d)->set<&Render_Scene_2D::Prop::viewport>(
Size{static_cast<int>(tick.width), static_cast<int>(tick.height)});
const auto result = (*scene_2d)->render(&output);
collect_consumed_input_statistics();
if (result != Render_Scene_2D::Render_Result::completed)
rollback_unsubmitted();
if (!result) rollback_unsubmitted();
return;
}
auto& output = *std::get<std::unique_ptr<Frame_3D>>(managed->frame);
@@ -799,7 +498,6 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
auto& scene_3d = std::get<std::unique_ptr<Scene_3D>>(scene);
scene_3d->set<&Render_Scene_3D::Prop::viewport>(Extent{tick.width, tick.height});
const auto result = scene_3d->render(&output);
collect_consumed_input_statistics();
if (result == Render_Scene_3D::Render_Result::submitted) return;
rollback_unsubmitted();
if (result == Render_Scene_3D::Render_Result::backend_unavailable)
@@ -811,40 +509,6 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
}
}
void Plot::Private::collect_consumed_input_statistics() {
aethera::Scene::Event_Report_Batch reports = std::visit(
[](auto& value) {
return value->template access_query_stream<aethera::Scene_Event_Stream_Tag>(
[&](std::span<const aethera::Scene::Event_Pointer> events) {
aethera::Scene::Event_Report_Batch result{value->memory_resource()};
result.reserve(events.size());
for (const auto& event : events) result.push_back(event);
return result;
});
}, scene);
for (const auto& event : reports) {
const auto* web_event = dynamic_cast<const Web_Input_Event*>(event.get());
if (!web_event) continue;
const auto& metadata = web_event->web_input_metadata();
const auto timing = event->dispatch_timing();
if (timing.completed_steady_ns == 0) continue;
const auto started = std::chrono::steady_clock::time_point{
std::chrono::nanoseconds(timing.started_steady_ns)};
const auto completed = std::chrono::steady_clock::time_point{
std::chrono::nanoseconds(timing.completed_steady_ns)};
Plot_Input_Observation observation{};
observation.admission_ms = elapsed_milliseconds(
metadata.input.received_time, metadata.scene_dispatched);
observation.scene_wait_ms = elapsed_milliseconds(
metadata.scene_dispatched, started);
observation.dispatch_ms = elapsed_milliseconds(started, completed);
observation.server_consume_ms = elapsed_milliseconds(
metadata.input.received_time, completed);
observation.coalesced_event_count =
metadata.input.coalesced_event_count;
diagnostics.submit_input(observation);
}
}
void Plot::Private::queue_completed_frame(Render_Frame* frame) {
if (!frame)
@@ -896,15 +560,11 @@ void Plot::Private::queue_completed_frame(Render_Frame* frame) {
std::shared_ptr<const std::vector<std::byte>> pixel_storage;
std::uint32_t width{};
std::uint32_t height{};
std::string_view output_format{"rgba8"};
std::string_view native_format{"rgba8"};
if (auto* frame_2d =
std::get_if<std::unique_ptr<Frame_2D>>(&managed->frame)) {
const auto image = (*frame_2d)->image();
width = static_cast<std::uint32_t>(image.width);
height = static_cast<std::uint32_t>(image.height);
output_format = pixel_format_name((*frame_2d)->output_format());
native_format = pixel_format_name(Frame_2D::native_pixel_format);
if (pacing.video_enabled) {
auto output = (*frame_2d)->output_pixels();
pixel_storage = std::make_shared<const std::vector<std::byte>>(
@@ -916,8 +576,6 @@ void Plot::Private::queue_completed_frame(Render_Frame* frame) {
else {
auto& frame_3d =
std::get<std::unique_ptr<Frame_3D>>(managed->frame);
output_format = pixel_format_name(frame_3d->output_format());
native_format = pixel_format_name(Frame_3D::native_pixel_format);
rendered_identity = frame_3d->rendered_identity();
const auto extent = frame_3d->extent();
width = extent.width;
@@ -933,24 +591,6 @@ void Plot::Private::queue_completed_frame(Render_Frame* frame) {
rendered_identity.sequence, rendered_identity.correlation_id,
width, height});
frame->mark(Frame_Trace_Marker::frame_ready);
std::vector<std::string> supported_formats;
const bool is_3d = std::holds_alternative<std::unique_ptr<Frame_3D>>(
managed->frame);
if (is_3d) {
supported_formats.reserve(Frame_3D::supported_pixel_formats.size());
for (const auto format : Frame_3D::supported_pixel_formats)
supported_formats.emplace_back(pixel_format_name(format));
} else {
supported_formats.reserve(Frame_2D::supported_pixel_formats.size());
for (const auto format : Frame_2D::supported_pixel_formats)
supported_formats.emplace_back(pixel_format_name(format));
}
diagnostics.submit(
*frame, rendered_identity, width, height,
pixels->rgba ? pixels->rgba->size() : 0U,
std::string{output_format}, std::string{native_format},
std::move(supported_formats), pacing, is_3d);
const auto published = std::make_shared<const Plot_Stream_Frame>(
Plot_Stream_Frame{{}, std::move(pixels)});
publish(std::move(published));
@@ -1059,7 +699,7 @@ void Plot::schedule_render(Plot_Render_Tick tick) {
const auto owner = weak.lock();
if (!owner) return;
try {
owner->d->consume_tick();
owner->d->consume_tick(weak);
}
catch (...) {
owner->d->fail(std::current_exception());
@@ -1106,77 +746,129 @@ void Plot::submit_input(Plot_Input_Event event) {
}
}
void Plot::async_schema(Json_Handler handler) {
if (!handler) throw std::invalid_argument("Plot schema handler is empty");
nlohmann::json Plot::schema() {
ensure_started();
auto self = shared_from_this();
aethera::schedule_task([self, handler = std::move(handler)]() mutable {
nlohmann::json result;
try {
result = self->d->schema();
}
catch (...) {
const auto failure = std::current_exception();
self->d->fail(failure);
result = {{"success", false},
{"error", exception_description(failure)}};
}
try { handler(std::move(result)); }
catch (...) {}
});
return d->schema();
}
void Plot::async_write_prop(std::string component, std::string key,
nlohmann::json value, Json_Handler handler) {
if (!handler) throw std::invalid_argument("Plot property handler is empty");
nlohmann::json Plot::write_prop(std::string_view component,
std::string_view key,
const nlohmann::json& value) {
ensure_started();
auto self = shared_from_this();
aethera::schedule_task(
[self, component = std::move(component), key = std::move(key),
value = std::move(value), handler = std::move(handler)]() mutable {
nlohmann::json result;
try {
result = component == "frame-analysis"
? self->d->frame_policy.write_prop(key, value)
: self->d->view->write_prop(component, key, value);
}
catch (...) {
const auto failure = std::current_exception();
self->d->fail(failure);
result = {{"success", false},
{"error", exception_description(failure)}};
}
try { handler(std::move(result)); }
catch (...) {}
});
return component == "frame-analysis"
? d->frame_policy.write_prop(key, value)
: d->view->write_prop(component, key, value);
}
void Plot::async_generate_data(nlohmann::json input, Json_Handler handler) {
if (!handler) throw std::invalid_argument("Plot data handler is empty");
nlohmann::json Plot::generate_data(const nlohmann::json& input) {
ensure_started();
auto self = shared_from_this();
aethera::schedule_task(
[self, input = std::move(input), handler = std::move(handler)]() mutable {
nlohmann::json result;
try {
result = self->d->view->generate_data(input);
}
catch (...) {
const auto failure = std::current_exception();
self->d->fail(failure);
result = {{"success", false},
{"error", exception_description(failure)}};
}
try { handler(std::move(result)); }
catch (...) {}
});
return d->view->generate_data(input);
}
nlohmann::json Plot::diagnostics() const {
return d->diagnostics.snapshot();
nlohmann::json frame_statistics = nlohmann::json::object();
Frame_Identity identity{};
std::uint64_t created_time_unix_ns{};
std::uint64_t dropped_sequences{};
double frame_rate{};
bool is_3d{};
const auto read_statistics = [&](const auto& state) {
const auto& statistics = state.frame_statistics;
append_statistic_json(frame_statistics, statistics);
identity = statistics.identity;
created_time_unix_ns = statistics.created_time_unix_ns;
dropped_sequences = statistics.dropped_sequences;
const auto& interval = statistics.values[
static_cast<std::size_t>(Frame_Statistic::frame_interval_ms)];
frame_rate = interval.trimmed_average > 0.0
? 1'000.0 / interval.trimmed_average : 0.0;
};
std::visit([&](const auto& scene) {
using Scene_Pointer = std::remove_cvref_t<decltype(scene)>;
if constexpr (std::same_as<Scene_Pointer, std::unique_ptr<Scene_2D>>) {
scene->template access_state<Render_Scene_2D::Base_Tag>(
read_statistics);
} else {
is_3d = true;
scene->template access_state<Render_Scene_3D::Base_Tag>(
read_statistics);
}
}, d->scene);
const auto pacing = d->frame_policy.snapshot();
const auto stream = d->stream_snapshot();
nlohmann::json supported_formats = nlohmann::json::array();
if (is_3d) {
for (const auto format : Frame_3D::supported_pixel_formats)
supported_formats.push_back(pixel_format_name(format));
} else {
for (const auto format : Frame_2D::supported_pixel_formats)
supported_formats.push_back(pixel_format_name(format));
}
const auto format = is_3d
? pixel_format_name(Frame_3D::native_pixel_format)
: pixel_format_name(pacing.video_enabled
? render_2d::Pixel_Format::rgba8 : Frame_2D::native_pixel_format);
const auto native_format = is_3d
? pixel_format_name(Frame_3D::native_pixel_format)
: pixel_format_name(Frame_2D::native_pixel_format);
const std::size_t byte_length = pacing.video_enabled
? static_cast<std::size_t>(stream.width) * stream.height * 4U : 0U;
nlohmann::json output{
{"protocol", "aethera.plot.diagnostics"}, {"version", 2},
{"dimension", is_3d ? "3D" : "2D"},
{"sequence", identity.sequence},
{"correlation_id", identity.correlation_id},
{"rendered_sequence", identity.sequence},
{"rendered_correlation_id", identity.correlation_id},
{"generated_time_unix_ms",
static_cast<double>(created_time_unix_ns) / 1'000'000.0},
{"delivery", pacing.video_enabled ? "gallery-video" : "diagnostics"},
{"frame_rate_fps", frame_rate},
{"dropped_sequence_count", dropped_sequences},
{"window_capacity", diagnostic_window_capacity},
{"pixel", {{"width", stream.width}, {"height", stream.height},
{"format", format}, {"native_format", native_format},
{"supported_formats", std::move(supported_formats)},
{"byte_length", byte_length}}},
{"pacing", {{"mode", pacing_mode_name(pacing.mode)},
{"fixed_rate_fps", pacing.fixed_rate_fps},
{"render_enabled", pacing.render_enabled},
{"video_enabled", pacing.video_enabled}}},
{"frame_statistics", std::move(frame_statistics)},
{"input_statistics", nlohmann::json::object()}};
if (is_3d) {
const auto gpu = gpu_completion_state();
const auto milliseconds = [](std::uint64_t nanoseconds) {
return static_cast<double>(nanoseconds) / 1'000'000.0;
};
output["gpu_completion_domain"] = {
{"capacity", gpu.capacity}, {"in_flight", gpu.in_flight},
{"peak_in_flight", gpu.peak_in_flight}, {"watched", gpu.watched},
{"peak_watched", gpu.peak_watched},
{"active_fences", gpu.active_fences},
{"pending_fences", gpu.pending_fences},
{"reservation_count", gpu.reservation_count},
{"completion_count", gpu.completion_count},
{"cancellation_count", gpu.cancellation_count},
{"fence_probe_count", gpu.fence_probe_count},
{"fence_wait_count", gpu.fence_wait_count},
{"fence_wait_timeout_count", gpu.fence_wait_timeout_count},
{"fence_wait_total_ms", milliseconds(gpu.fence_wait_total_ns)},
{"fence_wait_max_ms", milliseconds(gpu.fence_wait_max_ns)},
{"callback_total_ms", milliseconds(gpu.callback_total_ns)},
{"callback_max_ms", milliseconds(gpu.callback_max_ns)},
{"callback_failure_count", gpu.callback_failure_count},
{"backpressure_count", gpu.backpressure_count},
{"backpressure_wait_ms", milliseconds(gpu.backpressure_wait_ns)},
{"fault_count", gpu.fault_count},
{"abandoned_count", gpu.abandoned_count},
{"stopping", gpu.stopping}};
}
return output;
}
void Plot::reset_diagnostics() {
d->diagnostics.reset();
std::visit([](auto& scene) { scene->reset_frame_statistics(); }, d->scene);
}
}