帧策略展示优化
This commit is contained in:
+174
-71
@@ -1,8 +1,8 @@
|
||||
#include "Plot.hpp"
|
||||
#include "Renderable_Adapter.hpp"
|
||||
#include "Taskflow_Trace_Json.hpp"
|
||||
#include <Frame_Pacing_Policy.hpp>
|
||||
#include <Frame_Scheduler.hpp>
|
||||
#include <Frame_Policy/Frame_Policy.hpp>
|
||||
#include <Frame_Policy/Frame_Scheduler.hpp>
|
||||
#include <concurrentqueue-1.0.5/concurrentqueue.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
@@ -76,8 +76,8 @@ std::string_view pixel_format_name(render_3d::Pixel_Format format) {
|
||||
throw std::logic_error("unknown 3D pixel format");
|
||||
}
|
||||
|
||||
nlohmann::json frame_policy_schema(const Frame_Pacing_Policy& pacing) {
|
||||
const auto current = pacing.read();
|
||||
nlohmann::json frame_policy_schema(const Frame_Policy& policy) {
|
||||
const auto& current = policy.read_state<Frame_Policy::Base_Tag>();
|
||||
return {
|
||||
{"id", "frame-analysis"}, {"label", "渲染与媒体流水线"}, {"kind", "analysis"},
|
||||
{"fields", nlohmann::json::array({
|
||||
@@ -107,7 +107,7 @@ nlohmann::json frame_policy_schema(const Frame_Pacing_Policy& pacing) {
|
||||
};
|
||||
}
|
||||
|
||||
nlohmann::json write_frame_policy_prop(Frame_Pacing_Policy& pacing,
|
||||
nlohmann::json write_frame_policy_prop(Frame_Policy& pacing,
|
||||
std::string_view key,
|
||||
const nlohmann::json& value) {
|
||||
if (key == "render_enabled" || key == "video_enabled") {
|
||||
@@ -142,6 +142,101 @@ nlohmann::json write_frame_policy_prop(Frame_Pacing_Policy& pacing,
|
||||
return {{"success", false}, {"error", "unknown frame runtime property"}};
|
||||
}
|
||||
|
||||
nlohmann::json frame_policy_state_json(const Frame_Policy::State& state) {
|
||||
const auto milliseconds = [](std::uint64_t nanoseconds) {
|
||||
return static_cast<double>(nanoseconds) / 1'000'000.0;
|
||||
};
|
||||
const auto ratio = [](std::uint64_t numerator, std::uint64_t denominator) {
|
||||
return denominator == 0 ? 0.0 :
|
||||
static_cast<double>(numerator) / static_cast<double>(denominator);
|
||||
};
|
||||
const auto observed_ns = state.observed_until_ns > state.observation_started_ns
|
||||
? state.observed_until_ns - state.observation_started_ns : 0U;
|
||||
const auto observed_seconds = static_cast<double>(observed_ns) / 1'000'000'000.0;
|
||||
const auto rate = [observed_seconds](std::uint64_t count) {
|
||||
return observed_seconds > 0.0
|
||||
? static_cast<double>(count) / observed_seconds : 0.0;
|
||||
};
|
||||
const auto completion_span = state.last_completion_ns > state.first_completion_ns
|
||||
? state.last_completion_ns - state.first_completion_ns : 0U;
|
||||
const auto effective_fps = completion_span != 0 && state.completed_frame_count > 1
|
||||
? static_cast<double>(state.completed_frame_count - 1U) *
|
||||
1'000'000'000.0 / static_cast<double>(completion_span)
|
||||
: 0.0;
|
||||
const auto interval_mean = state.completion_interval_count == 0 ? 0.0 :
|
||||
static_cast<double>(state.completion_interval_total_ns) /
|
||||
static_cast<double>(state.completion_interval_count);
|
||||
const auto interval_variance = state.completion_interval_count == 0 ? 0.0 :
|
||||
std::max(0.0,
|
||||
state.completion_interval_squared_total_ns2 /
|
||||
static_cast<double>(state.completion_interval_count) -
|
||||
interval_mean * interval_mean);
|
||||
const auto target_achievement =
|
||||
state.mode == Frame_Pacing_Mode::fixed_rate && state.fixed_rate_fps > 0.0
|
||||
? effective_fps / state.fixed_rate_fps : 0.0;
|
||||
return {
|
||||
{"generation", state.generation},
|
||||
{"configuration", {
|
||||
{"mode", pacing_mode_name(state.mode)},
|
||||
{"render_enabled", state.render_enabled},
|
||||
{"video_enabled", state.video_enabled},
|
||||
{"fixed_rate_fps", state.fixed_rate_fps}}},
|
||||
{"observation", {
|
||||
{"duration_ms", milliseconds(observed_ns)},
|
||||
{"request_count", state.request_count},
|
||||
{"submitted_frame_count", state.submitted_frame_count},
|
||||
{"completed_frame_count", state.completed_frame_count},
|
||||
{"active_frame_count", state.active_frame_count}}},
|
||||
{"throughput", {
|
||||
{"request_rate_fps", rate(state.request_count)},
|
||||
{"submission_rate_fps", rate(state.submitted_frame_count)},
|
||||
{"completion_rate_fps", effective_fps},
|
||||
{"target_achievement_ratio", target_achievement},
|
||||
{"latest_frame_interval_ms",
|
||||
milliseconds(state.latest_completion_interval_ns)},
|
||||
{"average_frame_interval_ms", milliseconds(
|
||||
state.completion_interval_count == 0 ? 0U :
|
||||
state.completion_interval_total_ns /
|
||||
state.completion_interval_count)},
|
||||
{"frame_interval_jitter_ms",
|
||||
std::sqrt(interval_variance) / 1'000'000.0}}},
|
||||
{"requests", {
|
||||
{"periodic", state.periodic_request_count},
|
||||
{"immediate", state.immediate_request_count},
|
||||
{"maximum_rate", state.maximum_rate_request_count},
|
||||
{"accepted", state.accepted_request_count},
|
||||
{"coalesced", state.coalesced_request_count},
|
||||
{"policy_rejected", state.policy_rejection_count},
|
||||
{"frame_slot_backpressure", state.frame_slot_backpressure_count},
|
||||
{"scene_rejected", state.scene_rejection_count},
|
||||
{"acceptance_ratio", ratio(
|
||||
state.accepted_request_count, state.request_count)},
|
||||
{"coalescing_ratio", ratio(
|
||||
state.coalesced_request_count, state.request_count)},
|
||||
{"backpressure_ratio", ratio(
|
||||
state.frame_slot_backpressure_count,
|
||||
state.accepted_request_count)}}},
|
||||
{"latency", {
|
||||
{"latest_tick_queue_ms", milliseconds(state.latest_tick_queue_ns)},
|
||||
{"average_tick_queue_ms", milliseconds(
|
||||
state.submitted_frame_count == 0 ? 0U :
|
||||
state.tick_queue_total_ns / state.submitted_frame_count)},
|
||||
{"maximum_tick_queue_ms", milliseconds(state.maximum_tick_queue_ns)},
|
||||
{"latest_completion_ms", milliseconds(
|
||||
state.latest_completion_latency_ns)},
|
||||
{"average_completion_ms", milliseconds(
|
||||
state.completed_frame_count == 0 ? 0U :
|
||||
state.completion_latency_total_ns /
|
||||
state.completed_frame_count)},
|
||||
{"maximum_completion_ms", milliseconds(
|
||||
state.maximum_completion_latency_ns)}}},
|
||||
{"last_frame", {
|
||||
{"sequence", state.last_frame_sequence},
|
||||
{"request_source", magic_enum::enum_name(
|
||||
state.last_request_source)}}}
|
||||
};
|
||||
}
|
||||
|
||||
void append_statistic_json(nlohmann::json& output,
|
||||
const Frame_Statistics_State& state) {
|
||||
for (const auto statistic : magic_enum::enum_values<Frame_Statistic>()) {
|
||||
@@ -260,6 +355,7 @@ nlohmann::json taskflow_trace_json(
|
||||
{"sequence", trace.identity.sequence},
|
||||
{"correlation_id", trace.identity.correlation_id},
|
||||
{"request_source", magic_enum::enum_name(trace.request_source)},
|
||||
{"frame_policy", frame_policy_state_json(trace.frame_policy)},
|
||||
{"created_time_unix_ns", trace.created_time_unix_ns},
|
||||
{"worker_count", trace.worker_count},
|
||||
{"markers", std::move(markers)},
|
||||
@@ -337,6 +433,8 @@ struct Plot::Private {
|
||||
|
||||
struct Managed_Frame {
|
||||
std::chrono::microseconds presentation_time{}; /* 共享页面时钟产生的媒体时间戳。 */
|
||||
std::chrono::steady_clock::time_point tick_issued_at{}; /* 本逻辑帧请求进入 Plot 的时刻。 */
|
||||
std::chrono::steady_clock::time_point submitted_at{}; /* Scene 接受本逻辑帧的时刻。 */
|
||||
Frame frame{}; /* 三缓冲物理槽拥有且反复承载逻辑帧。 */
|
||||
std::atomic<Frame_State> state{Frame_State::available}; /* 本槽唯一生命周期状态。 */
|
||||
std::atomic_size_t diagnostic_readers{}; /* 无锁诊断读取认领;非零时该槽不可复用。 */
|
||||
@@ -366,7 +464,7 @@ struct Plot::Private {
|
||||
std::atomic_uint64_t next_stream_id{1};
|
||||
std::atomic<std::shared_ptr<const std::string>> terminal_failure{}; /* 首次 Plot Unknown Failure 的唯一终止状态。 */
|
||||
std::uint64_t next_frame_sequence{1};
|
||||
Frame_Pacing_Policy frame_policy{};
|
||||
std::unique_ptr<Frame_Policy> frame_policy{};
|
||||
Frame_Scheduler::Timer frame_timer{}; /* 每 Plot/Scene 只有轻量时间轮节点,不持有线程。 */
|
||||
static constexpr std::size_t scene_frame_capacity{3};
|
||||
std::array<Managed_Frame, scene_frame_capacity> frame_slots{}; /* Scene 与外接消费者共享生命周期的稳定三缓冲。 */
|
||||
@@ -380,12 +478,6 @@ struct Plot::Private {
|
||||
std::atomic_bool tick_task_scheduled{}; /* 唯一短任务准入;不占用 Worker 等待。 */
|
||||
std::atomic<Render_Admission_State> render_admission{Render_Admission_State::ready}; /* Plot 渲染准入及物理槽背压的唯一状态源。 */
|
||||
std::chrono::steady_clock::time_point clock_origin{std::chrono::steady_clock::now()};
|
||||
std::atomic_uint64_t received_tick_count{}; /* 页面时钟交付给本 Plot 的 tick 总数。 */
|
||||
std::atomic_uint64_t coalesced_tick_count{}; /* 尚未消费时被更新 tick 替换的旧 tick 总数。 */
|
||||
std::atomic_uint64_t policy_skip_count{}; /* 帧策略拒绝的 tick 总数。 */
|
||||
std::atomic_uint64_t frame_slot_busy_count{}; /* 三个物理帧槽均被占用的提交次数。 */
|
||||
std::atomic_uint64_t scene_rejection_count{}; /* Scene 单帧准入拒绝的提交次数。 */
|
||||
std::atomic_uint64_t submitted_frame_count{}; /* 成功提交给 Scene 的帧总数。 */
|
||||
std::atomic_size_t taskflow_trace_remaining{}; /* 尚待标记的实际渲染帧数。 */
|
||||
static constexpr std::size_t maximum_taskflow_trace_frames{120};
|
||||
/* 高 32 位 requested,低 32 位 captured。每槽只发布一次不可变 Trace,
|
||||
@@ -411,6 +503,10 @@ struct Plot::Private {
|
||||
Private(std::unique_ptr<Scene_Object> value_scene,
|
||||
std::unique_ptr<Scene_View> value_view)
|
||||
: view(std::move(value_view)), scene(std::move(value_scene)) {
|
||||
auto built_policy = Frame_Policy::Builder<Frame_Policy>{}.build();
|
||||
if (!built_policy)
|
||||
throw std::logic_error("Frame Policy dependency graph is invalid");
|
||||
frame_policy = std::move(*built_policy);
|
||||
for (auto& slot : frame_slots) {
|
||||
if constexpr (std::same_as<Scene_Object, Scene_2D>)
|
||||
slot.frame = std::make_unique<Frame_2D>(Frame_Identity{});
|
||||
@@ -483,7 +579,7 @@ void Plot::Private::fail(std::exception_ptr failure) noexcept {
|
||||
|
||||
nlohmann::json Plot::Private::schema() const {
|
||||
auto result = view->schema();
|
||||
auto analysis = frame_policy_schema(frame_policy);
|
||||
auto analysis = frame_policy_schema(*frame_policy);
|
||||
const auto generator = view->data_generator_schema();
|
||||
if (!generator.is_null()) analysis["data_generator"] = generator;
|
||||
result["frame_analysis"] = std::move(analysis);
|
||||
@@ -537,7 +633,7 @@ void Plot::Private::publish(
|
||||
void Plot::Private::refresh_schedule() {
|
||||
if (!frame_timer.valid()) return;
|
||||
const auto current_consumers = consumers.load(std::memory_order_acquire);
|
||||
const auto pacing = frame_policy.read();
|
||||
const auto& pacing = frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
if (!pacing.render_enabled || current_consumers->empty()) {
|
||||
frame_timer.cancel();
|
||||
return;
|
||||
@@ -553,29 +649,33 @@ void Plot::Private::refresh_schedule() {
|
||||
.issued_at = now,
|
||||
.time_milliseconds = std::chrono::duration<double, std::milli>(
|
||||
now - clock_origin).count(),
|
||||
.source = Plot_Render_Tick_Source::maximum_rate});
|
||||
.source = Frame_Request_Source::maximum_rate});
|
||||
arm_tick_consumer(lifetime);
|
||||
}
|
||||
|
||||
void Plot::Private::submit_tick_request(Plot_Render_Tick tick) {
|
||||
const auto source = tick.source;
|
||||
const auto issued_at = tick.issued_at;
|
||||
if (!tick_requests.enqueue(std::move(tick)))
|
||||
throw std::bad_alloc{};
|
||||
frame_policy->record_request(source, issued_at);
|
||||
tick_request_generation.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
void Plot::Private::keep_latest_tick(const Plot_Render_Tick& tick) {
|
||||
const auto priority = [](Plot_Render_Tick_Source source) {
|
||||
const auto priority = [](Frame_Request_Source source) {
|
||||
switch (source) {
|
||||
case Plot_Render_Tick_Source::periodic: return 0;
|
||||
case Plot_Render_Tick_Source::maximum_rate: return 1;
|
||||
case Plot_Render_Tick_Source::immediate: return 2;
|
||||
case Frame_Request_Source::unspecified: return 0;
|
||||
case Frame_Request_Source::periodic: return 0;
|
||||
case Frame_Request_Source::maximum_rate: return 1;
|
||||
case Frame_Request_Source::immediate: return 2;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
if (deferred_tick) {
|
||||
const auto current_priority = priority(deferred_tick->source);
|
||||
const auto next_priority = priority(tick.source);
|
||||
coalesced_tick_count.fetch_add(1, std::memory_order_relaxed);
|
||||
frame_policy->record_request_coalesced();
|
||||
if (current_priority > next_priority ||
|
||||
(current_priority == next_priority &&
|
||||
deferred_tick->issued_at >= tick.issued_at))
|
||||
@@ -601,7 +701,7 @@ void Plot::Private::release_render_admission(std::weak_ptr<Plot> lifetime) {
|
||||
expected, Render_Admission_State::ready,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire))
|
||||
return;
|
||||
const auto pacing = frame_policy.read();
|
||||
const auto& pacing = frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
if (pacing.render_enabled && pacing.mode == Frame_Pacing_Mode::maximum_rate) {
|
||||
const auto current_consumers = consumers.load(std::memory_order_acquire);
|
||||
if (!current_consumers->empty()) {
|
||||
@@ -610,13 +710,14 @@ void Plot::Private::release_render_admission(std::weak_ptr<Plot> lifetime) {
|
||||
.issued_at = now,
|
||||
.time_milliseconds = std::chrono::duration<double, std::milli>(
|
||||
now - clock_origin).count(),
|
||||
.source = Plot_Render_Tick_Source::maximum_rate});
|
||||
.source = Frame_Request_Source::maximum_rate});
|
||||
}
|
||||
}
|
||||
arm_tick_consumer(std::move(lifetime));
|
||||
}
|
||||
|
||||
void Plot::Private::consume_tick(std::weak_ptr<Plot> lifetime) {
|
||||
if (frame_policy->consume_events()) refresh_schedule();
|
||||
const auto observed_generation =
|
||||
tick_request_generation.load(std::memory_order_acquire);
|
||||
Plot_Render_Tick requested;
|
||||
@@ -627,6 +728,7 @@ void Plot::Private::consume_tick(std::weak_ptr<Plot> lifetime) {
|
||||
auto tick = std::exchange(deferred_tick, {});
|
||||
if (tick) clock_tick(*tick);
|
||||
}
|
||||
if (frame_policy->consume_events()) refresh_schedule();
|
||||
tick_task_scheduled.store(false, std::memory_order_release);
|
||||
if (tick_request_generation.load(std::memory_order_acquire) !=
|
||||
observed_generation ||
|
||||
@@ -637,17 +739,18 @@ void Plot::Private::consume_tick(std::weak_ptr<Plot> lifetime) {
|
||||
|
||||
void Plot::Private::clock_tick(const Plot_Render_Tick& tick) {
|
||||
if (terminal_failure.load(std::memory_order_acquire)) return;
|
||||
const auto pacing = frame_policy.read();
|
||||
const auto& pacing = frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
const bool accepted = pacing.render_enabled &&
|
||||
(tick.source == Plot_Render_Tick_Source::immediate ||
|
||||
(tick.source == Plot_Render_Tick_Source::periodic &&
|
||||
(tick.source == Frame_Request_Source::immediate ||
|
||||
(tick.source == Frame_Request_Source::periodic &&
|
||||
pacing.mode == Frame_Pacing_Mode::fixed_rate) ||
|
||||
(tick.source == Plot_Render_Tick_Source::maximum_rate &&
|
||||
(tick.source == Frame_Request_Source::maximum_rate &&
|
||||
pacing.mode == Frame_Pacing_Mode::maximum_rate));
|
||||
if (!accepted) {
|
||||
policy_skip_count.fetch_add(1, std::memory_order_relaxed);
|
||||
frame_policy->record_policy_rejection();
|
||||
return;
|
||||
}
|
||||
frame_policy->record_request_accepted(tick.source);
|
||||
render_frame(tick);
|
||||
}
|
||||
|
||||
@@ -722,7 +825,7 @@ nlohmann::json Plot::Private::trace_response(
|
||||
void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
if (terminal_failure.load(std::memory_order_acquire)) return;
|
||||
const auto streams = stream_snapshot();
|
||||
const auto pacing = frame_policy.read();
|
||||
const auto& pacing = frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
if (!pacing.render_enabled || streams.consumers->empty()) return;
|
||||
|
||||
auto admission_expected = Render_Admission_State::ready;
|
||||
@@ -773,7 +876,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
break;
|
||||
}
|
||||
if (!managed) {
|
||||
frame_slot_busy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
frame_policy->record_frame_slot_backpressure();
|
||||
keep_latest_tick(tick);
|
||||
/*
|
||||
* 三个槽都仍被外接消费者持有时,只保留 latest pending。这里绝不能
|
||||
@@ -789,6 +892,8 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
managed->presentation_time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::duration<double, std::milli>(tick.time_milliseconds));
|
||||
managed->tick_issued_at = tick.issued_at;
|
||||
managed->submitted_at = {};
|
||||
const auto rollback_unsubmitted = [this, slot_index] {
|
||||
auto& slot = frame_slots[slot_index];
|
||||
auto expected = Frame_State::rendering;
|
||||
@@ -808,28 +913,21 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
const std::uint64_t sequence = next_frame_sequence++;
|
||||
const Frame_Identity identity{
|
||||
sequence, tick.sequence == 0 ? sequence : tick.sequence};
|
||||
const auto request_source = [&] {
|
||||
switch (tick.source) {
|
||||
case Plot_Render_Tick_Source::periodic:
|
||||
return Frame_Request_Source::periodic;
|
||||
case Plot_Render_Tick_Source::immediate:
|
||||
return Frame_Request_Source::immediate;
|
||||
case Plot_Render_Tick_Source::maximum_rate:
|
||||
return Frame_Request_Source::maximum_rate;
|
||||
}
|
||||
return Frame_Request_Source::unspecified;
|
||||
}();
|
||||
const auto request_source = tick.source;
|
||||
const auto policy_state =
|
||||
frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
Render_Frame* logical_frame{};
|
||||
if (auto* frame_2d = std::get_if<std::unique_ptr<Frame_2D>>(&managed->frame)) {
|
||||
(*frame_2d)->begin(identity, Frame_2D::native_pixel_format,
|
||||
request_source);
|
||||
request_source, policy_state);
|
||||
logical_frame = frame_2d->get();
|
||||
} else {
|
||||
auto& frame_3d = std::get<std::unique_ptr<Frame_3D>>(managed->frame);
|
||||
frame_3d->begin(identity,
|
||||
pacing.video_enabled ? Frame_3D_Output::pixels
|
||||
: Frame_3D_Output::diagnostics,
|
||||
Frame_3D::native_pixel_format, request_source);
|
||||
Frame_3D::native_pixel_format, request_source,
|
||||
policy_state);
|
||||
logical_frame = frame_3d.get();
|
||||
}
|
||||
taskflow_trace_claimed = mark_taskflow_trace(*logical_frame);
|
||||
@@ -864,13 +962,16 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
Size{static_cast<int>(tick.width), static_cast<int>(tick.height)});
|
||||
const auto result = (*scene_2d)->render(&output);
|
||||
if (!result) {
|
||||
scene_rejection_count.fetch_add(1, std::memory_order_relaxed);
|
||||
frame_policy->record_scene_rejection();
|
||||
rollback_unsubmitted();
|
||||
restore_taskflow_trace_claim();
|
||||
release_render_admission(lifetime);
|
||||
} else {
|
||||
taskflow_trace_claimed = false;
|
||||
submitted_frame_count.fetch_add(1, std::memory_order_relaxed);
|
||||
managed->submitted_at = std::chrono::steady_clock::now();
|
||||
frame_policy->record_frame_submitted(
|
||||
sequence, request_source,
|
||||
managed->submitted_at - managed->tick_issued_at);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -881,10 +982,13 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
const auto result = scene_3d->render(&output);
|
||||
if (result == Render_Scene_3D::Render_Result::submitted) {
|
||||
taskflow_trace_claimed = false;
|
||||
submitted_frame_count.fetch_add(1, std::memory_order_relaxed);
|
||||
managed->submitted_at = std::chrono::steady_clock::now();
|
||||
frame_policy->record_frame_submitted(
|
||||
sequence, request_source,
|
||||
managed->submitted_at - managed->tick_issued_at);
|
||||
return;
|
||||
}
|
||||
scene_rejection_count.fetch_add(1, std::memory_order_relaxed);
|
||||
frame_policy->record_scene_rejection();
|
||||
rollback_unsubmitted();
|
||||
restore_taskflow_trace_claim();
|
||||
release_render_admission(lifetime);
|
||||
@@ -918,7 +1022,8 @@ void Plot::Private::publish_completed_frame() {
|
||||
throw std::logic_error("Scene completion graph has no rendering Plot frame");
|
||||
|
||||
try {
|
||||
const auto pacing = frame_policy.read();
|
||||
const auto& pacing =
|
||||
frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
const auto identity = frame->identity();
|
||||
Frame_Identity rendered_identity = identity;
|
||||
std::shared_ptr<const std::vector<std::byte>> pixel_storage;
|
||||
@@ -1132,6 +1237,12 @@ void Plot::Private::finalize_retired_frame(Render_Frame* frame) {
|
||||
if (!managed)
|
||||
throw std::logic_error("retired frame has no owned Plot slot");
|
||||
|
||||
frame_policy->record_frame_completed(
|
||||
frame->identity().sequence,
|
||||
managed->submitted_at.time_since_epoch().count() == 0
|
||||
? std::chrono::steady_clock::duration::zero()
|
||||
: std::chrono::steady_clock::now() - managed->submitted_at);
|
||||
|
||||
const auto statistics_generation_value =
|
||||
statistics_generation.load(std::memory_order_acquire);
|
||||
if (applied_statistics_generation != statistics_generation_value) {
|
||||
@@ -1173,10 +1284,10 @@ void Plot::Private::finalize_retired_frame(Render_Frame* frame) {
|
||||
|
||||
/* 物理槽是唯一背压原因;归还任意槽后只解除一次耗尽状态。 */
|
||||
auto admission = Render_Admission_State::frame_slots_exhausted;
|
||||
if (render_admission.compare_exchange_strong(
|
||||
admission, Render_Admission_State::ready,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire))
|
||||
arm_tick_consumer(lifetime);
|
||||
static_cast<void>(render_admission.compare_exchange_strong(
|
||||
admission, Render_Admission_State::ready,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire));
|
||||
arm_tick_consumer(lifetime);
|
||||
|
||||
if (captured_trace) {
|
||||
auto owner = lifetime;
|
||||
@@ -1229,7 +1340,7 @@ void Plot::ensure_started() {
|
||||
.issued_at = tick.issued_at,
|
||||
.sequence = tick.sequence,
|
||||
.time_milliseconds = tick.time_milliseconds,
|
||||
.source = Plot_Render_Tick_Source::periodic});
|
||||
.source = Frame_Request_Source::periodic});
|
||||
}
|
||||
});
|
||||
/*
|
||||
@@ -1337,21 +1448,19 @@ void Plot::configure_stream(Stream_Id stream, std::uint32_t width,
|
||||
void Plot::schedule_render(Plot_Render_Tick tick) {
|
||||
ensure_started();
|
||||
if (d->terminal_failure.load(std::memory_order_acquire)) return;
|
||||
d->received_tick_count.fetch_add(1, std::memory_order_relaxed);
|
||||
d->submit_tick_request(std::move(tick));
|
||||
d->arm_tick_consumer(weak_from_this());
|
||||
}
|
||||
|
||||
void Plot::render_once() {
|
||||
ensure_started();
|
||||
if (!d->frame_policy.request_immediate()) return;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto elapsed = now - d->clock_origin;
|
||||
schedule_render(Plot_Render_Tick{
|
||||
.issued_at = now,
|
||||
.time_milliseconds =
|
||||
std::chrono::duration<double, std::milli>(elapsed).count(),
|
||||
.source = Plot_Render_Tick_Source::immediate});
|
||||
.source = Frame_Request_Source::immediate});
|
||||
}
|
||||
|
||||
void Plot::submit_input(Plot_Input_Event event) {
|
||||
@@ -1385,8 +1494,9 @@ nlohmann::json Plot::write_prop(std::string_view component,
|
||||
ensure_started();
|
||||
if (component != "frame-analysis")
|
||||
return d->view->write_prop(component, key, value);
|
||||
auto result = write_frame_policy_prop(d->frame_policy, key, value);
|
||||
if (result.value("success", false)) d->refresh_schedule();
|
||||
auto result = write_frame_policy_prop(*d->frame_policy, key, value);
|
||||
if (result.value("success", false))
|
||||
d->arm_tick_consumer(weak_from_this());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1450,7 +1560,8 @@ nlohmann::json Plot::diagnostics() const {
|
||||
? 1'000.0 / interval.trimmed_average : 0.0;
|
||||
}
|
||||
|
||||
const auto pacing = d->frame_policy.read();
|
||||
const auto& pacing =
|
||||
d->frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
const auto stream = d->stream_snapshot();
|
||||
const auto admission = d->render_admission.load(std::memory_order_acquire);
|
||||
const auto admission_name = [&] {
|
||||
@@ -1479,7 +1590,7 @@ nlohmann::json Plot::diagnostics() const {
|
||||
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", 3},
|
||||
{"protocol", "aethera.plot.diagnostics"}, {"version", 4},
|
||||
{"dimension", is_3d ? "3D" : "2D"},
|
||||
{"sequence", identity.sequence},
|
||||
{"correlation_id", identity.correlation_id},
|
||||
@@ -1495,18 +1606,8 @@ nlohmann::json Plot::diagnostics() const {
|
||||
{"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}}},
|
||||
{"plot_scheduler", {
|
||||
{"received_ticks", d->received_tick_count.load(std::memory_order_relaxed)},
|
||||
{"coalesced_ticks", d->coalesced_tick_count.load(std::memory_order_relaxed)},
|
||||
{"policy_skips", d->policy_skip_count.load(std::memory_order_relaxed)},
|
||||
{"render_admission", admission_name},
|
||||
{"frame_slot_busy", d->frame_slot_busy_count.load(std::memory_order_relaxed)},
|
||||
{"scene_rejections", d->scene_rejection_count.load(std::memory_order_relaxed)},
|
||||
{"submitted_frames", d->submitted_frame_count.load(std::memory_order_relaxed)}}},
|
||||
{"frame_policy", frame_policy_state_json(pacing)},
|
||||
{"render_admission", admission_name},
|
||||
{"frame_statistics", std::move(frame_statistics)},
|
||||
{"input_statistics", std::move(input_statistics)}};
|
||||
if (is_3d) {
|
||||
@@ -1601,5 +1702,7 @@ nlohmann::json Plot::post_publish_taskflow_trace() const {
|
||||
void Plot::reset_diagnostics() {
|
||||
std::visit([](auto& scene) { scene->reset_diagnostics(); }, d->scene);
|
||||
d->statistics_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||
d->frame_policy->reset_statistics();
|
||||
d->arm_tick_consumer(weak_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,19 +27,13 @@ struct Plot_Input_Event {
|
||||
std::uint32_t native_key{}; /* 浏览器原生按键码。 */
|
||||
bool auto_repeat{}; /* 是否为系统重复按键。 */
|
||||
};
|
||||
enum struct Plot_Render_Tick_Source : std::uint8_t {
|
||||
periodic,
|
||||
immediate,
|
||||
maximum_rate
|
||||
};
|
||||
|
||||
struct Plot_Render_Tick {
|
||||
std::chrono::steady_clock::time_point issued_at{}; /* 页面帧时钟发布本 tick 的单调时刻;手动帧在提交时填写。 */
|
||||
std::uint64_t sequence{}; /* Kernel 全局 Frame_Scheduler 时间轴上的关联序号。 */
|
||||
double time_milliseconds{}; /* 页面级单调时间线,所有图共享同一个动画时刻。 */
|
||||
std::uint32_t width{320}; /* 当前图在媒体图集中的固定像素宽度。 */
|
||||
std::uint32_t height{192}; /* 当前图在媒体图集中的固定像素高度。 */
|
||||
Plot_Render_Tick_Source source{Plot_Render_Tick_Source::immediate}; /* 本次请求来自周期时钟、手动操作或最大吞吐自驱动。 */
|
||||
Frame_Request_Source source{Frame_Request_Source::immediate}; /* 本次请求来自周期时钟、手动操作或最大吞吐自驱动。 */
|
||||
};
|
||||
enum struct Plot_Pixel_Layout : std::uint8_t {
|
||||
bgra8,
|
||||
|
||||
Reference in New Issue
Block a user