架构微调

This commit is contained in:
2026-08-11 10:01:17 +08:00
parent 996e6f6154
commit 37bb77d280
13 changed files with 258 additions and 45 deletions
+19 -1
View File
@@ -166,7 +166,7 @@ std::string limit_state_name(bool available, std::uint32_t state) {
if (!available) if (!available)
return "not_applicable"; return "not_applicable";
static constexpr const char* names[]{"frequency_limited", "paint_limited", "render_limited", static constexpr const char* names[]{"frequency_limited", "paint_limited", "render_limited",
"consumer_limited"}; "consumer_limited", "unlimited"};
return state < std::size(names) ? names[state] : "unknown"; return state < std::size(names) ? names[state] : "unknown";
} }
@@ -460,12 +460,25 @@ void Plot_Core::set_max_render_fps(double fps) {
notify_model_dirty(); notify_model_dirty();
} }
void Plot_Core::clear_max_render_fps() {
if (impl_->mode != Frame_Control_Mode::Low_Latency)
throw std::logic_error("maximum render FPS is only available in low-latency mode");
std::get<std::unique_ptr<Low_Latency_Plot_Scene>>(impl_->scene)->frame_control.clear_frequency_limit();
notify_model_dirty();
}
void Plot_Core::set_consumer_feedback(Frame_Consumer_Feedback feedback) { void Plot_Core::set_consumer_feedback(Frame_Consumer_Feedback feedback) {
if (impl_->mode != Frame_Control_Mode::Low_Latency) if (impl_->mode != Frame_Control_Mode::Low_Latency)
throw std::logic_error("consumer feedback is only available in low-latency mode"); throw std::logic_error("consumer feedback is only available in low-latency mode");
std::get<std::unique_ptr<Low_Latency_Plot_Scene>>(impl_->scene)->frame_control.set_consumer_feedback(feedback); std::get<std::unique_ptr<Low_Latency_Plot_Scene>>(impl_->scene)->frame_control.set_consumer_feedback(feedback);
} }
void Plot_Core::clear_consumer_feedback() {
if (impl_->mode != Frame_Control_Mode::Low_Latency)
throw std::logic_error("consumer feedback is only available in low-latency mode");
std::get<std::unique_ptr<Low_Latency_Plot_Scene>>(impl_->scene)->frame_control.clear_consumer_feedback();
}
double Plot_Core::max_render_fps() const noexcept { double Plot_Core::max_render_fps() const noexcept {
if (impl_->mode != Frame_Control_Mode::Low_Latency) if (impl_->mode != Frame_Control_Mode::Low_Latency)
return std::numeric_limits<double>::quiet_NaN(); return std::numeric_limits<double>::quiet_NaN();
@@ -527,7 +540,12 @@ Frame_Observer_Snapshot Plot_Core::frame_observer_snapshot() const {
snapshot.paint_duration_ns = state.paint_duration_ns; snapshot.paint_duration_ns = state.paint_duration_ns;
snapshot.render_duration_ns = state.render_duration_ns; snapshot.render_duration_ns = state.render_duration_ns;
snapshot.target_interval_ns = state.target_interval_ns; snapshot.target_interval_ns = state.target_interval_ns;
snapshot.frequency_limit_enabled = state.frequency_limit_enabled;
snapshot.consumer_feedback_enabled = state.consumer_feedback_enabled;
snapshot.bottleneck_duration_ns = state.bottleneck_duration_ns; snapshot.bottleneck_duration_ns = state.bottleneck_duration_ns;
snapshot.consumer_sample_interval_ns = state.consumer_sample_interval_ns;
snapshot.consumer_smoothed_interval_ns = state.consumer_smoothed_interval_ns;
snapshot.consumer_variation_ns = state.consumer_variation_ns;
snapshot.consumer_interval_ns = state.consumer_interval_ns; snapshot.consumer_interval_ns = state.consumer_interval_ns;
snapshot.next_refresh_interval_ns = state.next_refresh_interval_ns; snapshot.next_refresh_interval_ns = state.next_refresh_interval_ns;
snapshot.end_to_end_ns = state.end_to_end_ns; snapshot.end_to_end_ns = state.end_to_end_ns;
+7
View File
@@ -50,7 +50,12 @@ struct Frame_Observer_Snapshot {
std::uint64_t paint_duration_ns{}; std::uint64_t paint_duration_ns{};
std::uint64_t render_duration_ns{}; std::uint64_t render_duration_ns{};
std::uint64_t target_interval_ns{}; std::uint64_t target_interval_ns{};
bool frequency_limit_enabled{};
bool consumer_feedback_enabled{};
std::uint64_t bottleneck_duration_ns{}; std::uint64_t bottleneck_duration_ns{};
std::uint64_t consumer_sample_interval_ns{};
std::uint64_t consumer_smoothed_interval_ns{};
std::uint64_t consumer_variation_ns{};
std::uint64_t consumer_interval_ns{}; std::uint64_t consumer_interval_ns{};
std::uint64_t next_refresh_interval_ns{}; std::uint64_t next_refresh_interval_ns{};
std::uint64_t paint_lease_wait_ns{}; std::uint64_t paint_lease_wait_ns{};
@@ -114,7 +119,9 @@ public:
[[nodiscard]] bool view_active() const noexcept; [[nodiscard]] bool view_active() const noexcept;
void set_max_render_fps(double fps); void set_max_render_fps(double fps);
void clear_max_render_fps();
void set_consumer_feedback(Frame_Consumer_Feedback feedback); void set_consumer_feedback(Frame_Consumer_Feedback feedback);
void clear_consumer_feedback();
[[nodiscard]] double max_render_fps() const noexcept; [[nodiscard]] double max_render_fps() const noexcept;
[[nodiscard]] Low_Latency_Diagnostics diagnostics() const; [[nodiscard]] Low_Latency_Diagnostics diagnostics() const;
[[nodiscard]] Refresh_Control_Snapshot refresh_feedback_snapshot() const; [[nodiscard]] Refresh_Control_Snapshot refresh_feedback_snapshot() const;
@@ -89,12 +89,35 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyConsumerFeedbackFlowsThroughCore2
ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame());
ASSERT_TRUE(plot.render_prepared_frame()); ASSERT_TRUE(plot.render_prepared_frame());
const auto snapshot = plot.frame_observer_snapshot(); const auto snapshot = plot.frame_observer_snapshot();
EXPECT_TRUE(snapshot.frequency_limit_enabled);
EXPECT_TRUE(snapshot.consumer_feedback_enabled);
EXPECT_EQ(snapshot.limit_state, "consumer_limited"); EXPECT_EQ(snapshot.limit_state, "consumer_limited");
EXPECT_EQ(snapshot.target_interval_ns, 10'000'000u); EXPECT_EQ(snapshot.target_interval_ns, 10'000'000u);
EXPECT_EQ(snapshot.consumer_sample_interval_ns, 40'000'000u);
EXPECT_EQ(snapshot.consumer_smoothed_interval_ns, 40'000'000u);
EXPECT_EQ(snapshot.consumer_variation_ns, 0u);
EXPECT_EQ(snapshot.consumer_interval_ns, 40'000'000u); EXPECT_EQ(snapshot.consumer_interval_ns, 40'000'000u);
EXPECT_EQ(snapshot.next_refresh_interval_ns, 40'000'000u); EXPECT_EQ(snapshot.next_refresh_interval_ns, 40'000'000u);
EXPECT_EQ(plot.refresh_feedback_snapshot().next_refresh_interval_ns, 40'000'000u); EXPECT_EQ(plot.refresh_feedback_snapshot().next_refresh_interval_ns, 40'000'000u);
} }
TEST(Renderive_Core2_Frame_Pipeline, LowLatencyLimitsCanBeClearedIndependently) {
Plot_Core plot(Frame_Control_Mode::Low_Latency);
plot.init();
plot.set_viewport_size({96, 54});
plot.set_max_render_fps(100.0);
plot.set_consumer_feedback({40'000'000});
plot.clear_consumer_feedback();
plot.clear_max_render_fps();
ASSERT_TRUE(plot.prepare_frame());
ASSERT_TRUE(plot.render_prepared_frame());
const auto snapshot = plot.frame_observer_snapshot();
EXPECT_FALSE(snapshot.frequency_limit_enabled);
EXPECT_FALSE(snapshot.consumer_feedback_enabled);
EXPECT_EQ(snapshot.target_interval_ns, 0u);
EXPECT_EQ(snapshot.consumer_interval_ns, 0u);
EXPECT_EQ(snapshot.next_refresh_interval_ns, snapshot.bottleneck_duration_ns);
EXPECT_TRUE(snapshot.limit_state == "paint_limited" || snapshot.limit_state == "render_limited");
}
TEST(Renderive_Core2_Frame_Pipeline, LowLatencyReplacementRetiresOldFrameWithoutPhantomPendingFrame) { TEST(Renderive_Core2_Frame_Pipeline, LowLatencyReplacementRetiresOldFrameWithoutPhantomPendingFrame) {
Plot_Core plot(Frame_Control_Mode::Low_Latency); Plot_Core plot(Frame_Control_Mode::Low_Latency);
@@ -1,5 +1,5 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
struct Frame_Consumer_Feedback { struct Frame_Consumer_Feedback {
std::uint64_t minimum_frame_interval_ns{}; std::uint64_t observed_interval_ns{};
}; };
@@ -24,7 +24,8 @@ public:
frequency_limited, frequency_limited,
paint_limited, paint_limited,
render_limited, render_limited,
consumer_limited consumer_limited,
unlimited
}; };
struct Configuration { struct Configuration {
double frequency_hz{60.0}; double frequency_hz{60.0};
@@ -68,10 +69,15 @@ public:
struct State { struct State {
double frequency_hz{60.0}; double frequency_hz{60.0};
Limit_State limit_state{Limit_State::frequency_limited}; Limit_State limit_state{Limit_State::frequency_limited};
bool frequency_limit_enabled{true};
bool consumer_feedback_enabled{};
std::uint64_t target_interval_ns{}; std::uint64_t target_interval_ns{};
std::uint64_t paint_duration_ns{}; std::uint64_t paint_duration_ns{};
std::uint64_t render_duration_ns{}; std::uint64_t render_duration_ns{};
std::uint64_t bottleneck_duration_ns{}; std::uint64_t bottleneck_duration_ns{};
std::uint64_t consumer_sample_interval_ns{};
std::uint64_t consumer_smoothed_interval_ns{};
std::uint64_t consumer_variation_ns{};
std::uint64_t consumer_interval_ns{}; std::uint64_t consumer_interval_ns{};
std::uint64_t end_to_end_ns{}; std::uint64_t end_to_end_ns{};
std::uint64_t next_refresh_interval_ns{}; std::uint64_t next_refresh_interval_ns{};
@@ -145,7 +151,9 @@ public:
Painter_Lease acquire_painter(); Painter_Lease acquire_painter();
Render_Lease acquire_renderer(); Render_Lease acquire_renderer();
void set_frequency_hz(double frequency_hz); void set_frequency_hz(double frequency_hz);
void clear_frequency_limit();
void set_consumer_feedback(Frame_Consumer_Feedback feedback); void set_consumer_feedback(Frame_Consumer_Feedback feedback);
void clear_consumer_feedback();
void swap() override; void swap() override;
double frequency_hz() const noexcept override; double frequency_hz() const noexcept override;
std::uint64_t next_refresh_interval_ns() const noexcept override; std::uint64_t next_refresh_interval_ns() const noexcept override;
@@ -160,6 +168,7 @@ private:
static double checked_frequency_hz(double frequency_hz); static double checked_frequency_hz(double frequency_hz);
std::uint64_t now_ns() const noexcept; std::uint64_t now_ns() const noexcept;
void update_refresh_control(); void update_refresh_control();
void update_consumer_estimator(std::uint64_t sample_interval_ns);
void update_state(const Frame& frame); void update_state(const Frame& frame);
void observe(const Observation& observation) noexcept; void observe(const Observation& observation) noexcept;
bool discard_pending_frame_locked(Observation& observation); bool discard_pending_frame_locked(Observation& observation);
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <limits>
#include <stdexcept> #include <stdexcept>
template <class Scene_Frame, Mutex_Type Mutex, class Observer> template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::Painter_Lease(Low_Latency_Strategy& strategy) Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::Painter_Lease(Low_Latency_Strategy& strategy)
@@ -221,14 +222,33 @@ template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::set_frequency_hz(double frequency_hz) { void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::set_frequency_hz(double frequency_hz) {
frequency_hz = checked_frequency_hz(frequency_hz); frequency_hz = checked_frequency_hz(frequency_hz);
std::lock_guard<Mutex> lock(state_mutex_); std::lock_guard<Mutex> lock(state_mutex_);
state_.frequency_limit_enabled = true;
state_.frequency_hz = frequency_hz; state_.frequency_hz = frequency_hz;
state_.target_interval_ns = frequency_interval_ns(frequency_hz); state_.target_interval_ns = frequency_interval_ns(frequency_hz);
update_refresh_control(); update_refresh_control();
} }
template <class Scene_Frame, Mutex_Type Mutex, class Observer> template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::clear_frequency_limit() {
std::lock_guard<Mutex> lock(state_mutex_);
state_.frequency_limit_enabled = false;
state_.frequency_hz = invalid_frequency_hz();
state_.target_interval_ns = 0;
update_refresh_control();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::set_consumer_feedback(Frame_Consumer_Feedback feedback) { void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::set_consumer_feedback(Frame_Consumer_Feedback feedback) {
std::lock_guard<Mutex> lock(state_mutex_); std::lock_guard<Mutex> lock(state_mutex_);
state_.consumer_interval_ns = feedback.minimum_frame_interval_ns; update_consumer_estimator(feedback.observed_interval_ns);
update_refresh_control();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::clear_consumer_feedback() {
std::lock_guard<Mutex> lock(state_mutex_);
state_.consumer_feedback_enabled = false;
state_.consumer_sample_interval_ns = 0;
state_.consumer_smoothed_interval_ns = 0;
state_.consumer_variation_ns = 0;
state_.consumer_interval_ns = 0;
update_refresh_control(); update_refresh_control();
} }
template <class Scene_Frame, Mutex_Type Mutex, class Observer> template <class Scene_Frame, Mutex_Type Mutex, class Observer>
@@ -331,20 +351,48 @@ void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_refresh_control(
state_.next_refresh_interval_ns = std::max({state_.target_interval_ns, state_.next_refresh_interval_ns = std::max({state_.target_interval_ns,
state_.bottleneck_duration_ns, state_.bottleneck_duration_ns,
state_.consumer_interval_ns}); state_.consumer_interval_ns});
if (state_.consumer_interval_ns > state_.target_interval_ns && if (state_.consumer_feedback_enabled && state_.consumer_interval_ns != 0 &&
state_.consumer_interval_ns > state_.bottleneck_duration_ns) { state_.consumer_interval_ns >= state_.target_interval_ns &&
state_.consumer_interval_ns >= state_.bottleneck_duration_ns) {
state_.limit_state = Limit_State::consumer_limited; state_.limit_state = Limit_State::consumer_limited;
} else if (state_.target_interval_ns != 0 && } else if (state_.frequency_limit_enabled && state_.target_interval_ns != 0 &&
state_.bottleneck_duration_ns <= state_.target_interval_ns) { state_.target_interval_ns >= state_.bottleneck_duration_ns) {
state_.limit_state = Limit_State::frequency_limited; state_.limit_state = Limit_State::frequency_limited;
} else if (state_.paint_duration_ns >= state_.render_duration_ns) { } else if (state_.bottleneck_duration_ns != 0) {
state_.limit_state = Limit_State::paint_limited; state_.limit_state = state_.paint_duration_ns >= state_.render_duration_ns ?
Limit_State::paint_limited : Limit_State::render_limited;
} else { } else {
state_.limit_state = Limit_State::render_limited; state_.limit_state = Limit_State::unlimited;
} }
update_frame_control_state(state_.frequency_hz, state_.next_refresh_interval_ns); update_frame_control_state(state_.frequency_hz, state_.next_refresh_interval_ns);
} }
template <class Scene_Frame, Mutex_Type Mutex, class Observer> template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_consumer_estimator(std::uint64_t sample_interval_ns) {
state_.consumer_sample_interval_ns = sample_interval_ns;
if (!state_.consumer_feedback_enabled) {
state_.consumer_feedback_enabled = true;
state_.consumer_smoothed_interval_ns = sample_interval_ns;
state_.consumer_variation_ns = 0;
} else {
const std::uint64_t smoothed = state_.consumer_smoothed_interval_ns;
const std::uint64_t deviation = sample_interval_ns >= smoothed ?
sample_interval_ns - smoothed : smoothed - sample_interval_ns;
if (deviation >= state_.consumer_variation_ns)
state_.consumer_variation_ns += (deviation - state_.consumer_variation_ns) / 4;
else
state_.consumer_variation_ns -= (state_.consumer_variation_ns - deviation) / 4;
if (sample_interval_ns >= state_.consumer_smoothed_interval_ns)
state_.consumer_smoothed_interval_ns += (sample_interval_ns - state_.consumer_smoothed_interval_ns) / 8;
else
state_.consumer_smoothed_interval_ns -= (state_.consumer_smoothed_interval_ns - sample_interval_ns) / 8;
}
const auto maximum = std::numeric_limits<std::uint64_t>::max();
const std::uint64_t variation_margin = state_.consumer_variation_ns > maximum / 4 ?
maximum : state_.consumer_variation_ns * 4;
state_.consumer_interval_ns = state_.consumer_smoothed_interval_ns > maximum - variation_margin ?
maximum : state_.consumer_smoothed_interval_ns + variation_margin;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_state(const Frame& frame) { void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_state(const Frame& frame) {
state_.paint_duration_ns = frame.statistics.timing.paint_duration_ns; state_.paint_duration_ns = frame.statistics.timing.paint_duration_ns;
state_.render_duration_ns = frame.statistics.timing.render_duration_ns; state_.render_duration_ns = frame.statistics.timing.render_duration_ns;
@@ -73,7 +73,7 @@ TEST(low_latency_pipeline_test, consumer_feedback_limits_refresh_without_knowing
EXPECT_EQ(state.target_interval_ns, 10'000'000u); EXPECT_EQ(state.target_interval_ns, 10'000'000u);
EXPECT_EQ(state.consumer_interval_ns, 40'000'000u); EXPECT_EQ(state.consumer_interval_ns, 40'000'000u);
EXPECT_EQ(state.next_refresh_interval_ns, 40'000'000u); EXPECT_EQ(state.next_refresh_interval_ns, 40'000'000u);
strategy.set_consumer_feedback({}); strategy.clear_consumer_feedback();
state = strategy.state(); state = strategy.state();
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited); EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
EXPECT_EQ(state.consumer_interval_ns, 0u); EXPECT_EQ(state.consumer_interval_ns, 0u);
@@ -125,3 +125,65 @@ TEST(low_latency_strategy_test, clamps_extremely_small_frequency_interval_withou
} }
EXPECT_EQ(strategy.state().target_interval_ns, std::numeric_limits<std::uint64_t>::max()); EXPECT_EQ(strategy.state().target_interval_ns, std::numeric_limits<std::uint64_t>::max());
} }
TEST(low_latency_strategy_test, smooths_consumer_feedback_in_kernel) {
Low_Latency_Test_Time_Source time_source;
Low_Latency_Test_Observer observer;
auto strategy = make_low_latency_test_strategy(time_source, observer);
strategy.set_frequency_hz(1000.0);
strategy.set_consumer_feedback({10'000'000});
auto state = strategy.state();
EXPECT_TRUE(state.consumer_feedback_enabled);
EXPECT_EQ(state.consumer_sample_interval_ns, 10'000'000u);
EXPECT_EQ(state.consumer_smoothed_interval_ns, 10'000'000u);
EXPECT_EQ(state.consumer_variation_ns, 0u);
EXPECT_EQ(state.consumer_interval_ns, 10'000'000u);
strategy.set_consumer_feedback({14'000'000});
state = strategy.state();
EXPECT_EQ(state.consumer_sample_interval_ns, 14'000'000u);
EXPECT_EQ(state.consumer_smoothed_interval_ns, 10'500'000u);
EXPECT_EQ(state.consumer_variation_ns, 1'000'000u);
EXPECT_EQ(state.consumer_interval_ns, 14'500'000u);
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::consumer_limited);
}
TEST(low_latency_strategy_test, allows_frequency_and_consumer_limits_to_be_cleared) {
Low_Latency_Test_Time_Source time_source;
Low_Latency_Test_Observer observer;
auto strategy = make_low_latency_test_strategy(time_source, observer);
strategy.set_frequency_hz(1000.0);
strategy.set_consumer_feedback({20'000'000});
strategy.clear_consumer_feedback();
auto state = strategy.state();
EXPECT_FALSE(state.consumer_feedback_enabled);
EXPECT_EQ(state.consumer_interval_ns, 0u);
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
strategy.clear_frequency_limit();
state = strategy.state();
EXPECT_FALSE(state.frequency_limit_enabled);
EXPECT_TRUE(std::isnan(state.frequency_hz));
EXPECT_EQ(state.target_interval_ns, 0u);
EXPECT_EQ(state.next_refresh_interval_ns, 0u);
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::unlimited);
}
TEST(low_latency_strategy_test, unlimited_mode_runs_at_measured_render_bottleneck) {
Low_Latency_Test_Time_Source time_source;
Low_Latency_Test_Observer observer;
auto strategy = make_low_latency_test_strategy(time_source, observer);
strategy.clear_frequency_limit();
strategy.clear_consumer_feedback();
{
auto frame = strategy.acquire_painter();
time_source.advance_ns(2'000);
frame->value = 1;
}
{
auto frame = strategy.acquire_renderer();
ASSERT_TRUE(frame);
time_source.advance_ns(5'000);
}
const auto state = strategy.state();
EXPECT_EQ(state.target_interval_ns, 0u);
EXPECT_EQ(state.consumer_interval_ns, 0u);
EXPECT_EQ(state.bottleneck_duration_ns, 5'000u);
EXPECT_EQ(state.next_refresh_interval_ns, 5'000u);
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::render_limited);
}
+30 -4
View File
@@ -253,10 +253,12 @@ public:
std::max(0.0, last_pixel_receive_age_ms) : 0.0; std::max(0.0, last_pixel_receive_age_ms) : 0.0;
client_last_pixel_change_age_ms_ = std::isfinite(last_pixel_change_age_ms) ? client_last_pixel_change_age_ms_ = std::isfinite(last_pixel_change_age_ms) ?
std::max(0.0, last_pixel_change_age_ms) : 0.0; std::max(0.0, last_pixel_change_age_ms) : 0.0;
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) { if (frame_mode_ == Gallery_Frame_Mode::Low_Latency &&
bool_value(state_, "consumer_feedback_enabled")) {
const auto interval_ns = std::max(milliseconds_to_ns(client_frame_round_trip_ms_), const auto interval_ns = std::max(milliseconds_to_ns(client_frame_round_trip_ms_),
milliseconds_to_ns(client_display_interval_ms_)); milliseconds_to_ns(client_display_interval_ms_));
plot_.set_consumer_feedback({interval_ns}); if (interval_ns != 0)
plot_.set_consumer_feedback({interval_ns});
} }
} }
@@ -634,7 +636,12 @@ public:
{"paint_duration_ns", observer.paint_duration_ns}, {"paint_duration_ns", observer.paint_duration_ns},
{"render_duration_ns", observer.render_duration_ns}, {"render_duration_ns", observer.render_duration_ns},
{"target_interval_ns", observer.target_interval_ns}, {"target_interval_ns", observer.target_interval_ns},
{"frequency_limit_enabled", observer.frequency_limit_enabled},
{"consumer_feedback_enabled", observer.consumer_feedback_enabled},
{"bottleneck_duration_ns", observer.bottleneck_duration_ns}, {"bottleneck_duration_ns", observer.bottleneck_duration_ns},
{"consumer_sample_interval_ns", observer.consumer_sample_interval_ns},
{"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns},
{"consumer_variation_ns", observer.consumer_variation_ns},
{"consumer_interval_ns", observer.consumer_interval_ns}, {"consumer_interval_ns", observer.consumer_interval_ns},
{"next_refresh_interval_ns", observer.next_refresh_interval_ns}, {"next_refresh_interval_ns", observer.next_refresh_interval_ns},
{"paint_lease_wait_ns", observer.paint_lease_wait_ns}, {"paint_lease_wait_ns", observer.paint_lease_wait_ns},
@@ -674,11 +681,16 @@ public:
{"render_limited", observer.limit_state == "render_limited"}, {"render_limited", observer.limit_state == "render_limited"},
{"consumer_limited", observer.limit_state == "consumer_limited"}, {"consumer_limited", observer.limit_state == "consumer_limited"},
{"configured_frequency_hz", observer.frequency_hz}, {"configured_frequency_hz", observer.frequency_hz},
{"frequency_limit_enabled", observer.frequency_limit_enabled},
{"consumer_feedback_enabled", observer.consumer_feedback_enabled},
{"consumer_feedback_source", "web_runtime"}, {"consumer_feedback_source", "web_runtime"},
{"target_interval_ns", observer.target_interval_ns}, {"target_interval_ns", observer.target_interval_ns},
{"paint_duration_ns", observer.paint_duration_ns}, {"paint_duration_ns", observer.paint_duration_ns},
{"render_duration_ns", observer.render_duration_ns}, {"render_duration_ns", observer.render_duration_ns},
{"bottleneck_duration_ns", observer.bottleneck_duration_ns}, {"bottleneck_duration_ns", observer.bottleneck_duration_ns},
{"consumer_sample_interval_ns", observer.consumer_sample_interval_ns},
{"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns},
{"consumer_variation_ns", observer.consumer_variation_ns},
{"consumer_interval_ns", observer.consumer_interval_ns}, {"consumer_interval_ns", observer.consumer_interval_ns},
{"scheduler_interval_ns", scheduler_interval_ns}, {"scheduler_interval_ns", scheduler_interval_ns},
{"scheduler_frequency_hz", scheduler_interval_ns == 0 ? 0.0 : {"scheduler_frequency_hz", scheduler_interval_ns == 0 ? 0.0 :
@@ -933,6 +945,11 @@ private:
{"automatic_scheduler_fps", scheduler_interval_ns == 0 ? 0.0 : {"automatic_scheduler_fps", scheduler_interval_ns == 0 ? 0.0 :
1'000'000'000.0 / static_cast<double>(scheduler_interval_ns)}, 1'000'000'000.0 / static_cast<double>(scheduler_interval_ns)},
{"automatic_scheduler_limit_source", low_latency ? observer.limit_state : "not_applicable"}, {"automatic_scheduler_limit_source", low_latency ? observer.limit_state : "not_applicable"},
{"frequency_limit_enabled", low_latency && observer.frequency_limit_enabled},
{"consumer_feedback_enabled", low_latency && observer.consumer_feedback_enabled},
{"consumer_feedback_sample_interval_ns", low_latency ? observer.consumer_sample_interval_ns : 0},
{"consumer_feedback_smoothed_interval_ns", low_latency ? observer.consumer_smoothed_interval_ns : 0},
{"consumer_feedback_variation_ns", low_latency ? observer.consumer_variation_ns : 0},
{"consumer_feedback_interval_ns", low_latency ? observer.consumer_interval_ns : 0}, {"consumer_feedback_interval_ns", low_latency ? observer.consumer_interval_ns : 0},
{"backend_render_fps", recent_rate(render_history_, now)}, {"backend_render_fps", recent_rate(render_history_, now)},
{"pixel_response_fps", pixel_fps}, {"pixel_response_fps", pixel_fps},
@@ -968,6 +985,9 @@ private:
{"paint_duration_ns", observer.paint_duration_ns}, {"paint_duration_ns", observer.paint_duration_ns},
{"render_duration_ns", observer.render_duration_ns}, {"render_duration_ns", observer.render_duration_ns},
{"bottleneck_duration_ns", observer.bottleneck_duration_ns}, {"bottleneck_duration_ns", observer.bottleneck_duration_ns},
{"consumer_sample_interval_ns", observer.consumer_sample_interval_ns},
{"consumer_smoothed_interval_ns", observer.consumer_smoothed_interval_ns},
{"consumer_variation_ns", observer.consumer_variation_ns},
{"consumer_interval_ns", observer.consumer_interval_ns}, {"consumer_interval_ns", observer.consumer_interval_ns},
{"next_refresh_interval_ns", observer.next_refresh_interval_ns}, {"next_refresh_interval_ns", observer.next_refresh_interval_ns},
{"paint_lease_wait_ns", observer.paint_lease_wait_ns}, {"paint_lease_wait_ns", observer.paint_lease_wait_ns},
@@ -1222,8 +1242,14 @@ private:
void apply_state() { void apply_state() {
plot_.set_background_color(color_from_hex(string_value(state_, "background_color"))); plot_.set_background_color(color_from_hex(string_value(state_, "background_color")));
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) {
plot_.set_max_render_fps(number_value(state_, "max_render_fps")); if (bool_value(state_, "frequency_limit_enabled"))
plot_.set_max_render_fps(number_value(state_, "max_render_fps"));
else
plot_.clear_max_render_fps();
if (!bool_value(state_, "consumer_feedback_enabled"))
plot_.clear_consumer_feedback();
}
set_performance_plot_name(plot_, case_id_ + "/" + std::string(frame_mode_name(frame_mode_))); set_performance_plot_name(plot_, case_id_ + "/" + std::string(frame_mode_name(frame_mode_)));
set_performance_overlay_enabled(plot_, bool_value(state_, "performance_overlay")); set_performance_overlay_enabled(plot_, bool_value(state_, "performance_overlay"));
if (primary_) { if (primary_) {
+7 -1
View File
@@ -123,9 +123,15 @@ void append_common(std::vector<Control_Definition>& result, Gallery_Frame_Mode f
result.push_back(color("background_color", "背景颜色", "Plot_Core::set_background_color", result.push_back(color("background_color", "背景颜色", "Plot_Core::set_background_color",
"#07111f", "Plot_Core")); "#07111f", "Plot_Core"));
if (frame_mode == Gallery_Frame_Mode::Low_Latency) { if (frame_mode == Gallery_Frame_Mode::Low_Latency) {
result.push_back(flag("frequency_limit_enabled", "启用频率上限",
"Plot_Core::set_max_render_fps / clear_max_render_fps", true,
"Low_Latency_Strategy", "关闭后用户频率不参与调度。"));
result.push_back(number("max_render_fps", "最大渲染 FPS", "Plot_Core::set_max_render_fps", result.push_back(number("max_render_fps", "最大渲染 FPS", "Plot_Core::set_max_render_fps",
30, 0.01, 1'000'000'000, 1, "Low_Latency_Strategy", false, 30, 0.01, 1'000'000'000, 1, "Low_Latency_Strategy", false,
"Kernel 接受任意有限正频率;高目标用于触发并观察 Painter 或后台 Render 瓶颈")); "仅在启用频率上限时参与调度"));
result.push_back(flag("consumer_feedback_enabled", "启用消费者反馈",
"Plot_Core::set_consumer_feedback / clear_consumer_feedback", true,
"Low_Latency_Strategy", "关闭后消费者反馈不参与调度。"));
} }
result.push_back(flag("performance_overlay", "性能叠层", "set_performance_overlay_enabled", result.push_back(flag("performance_overlay", "性能叠层", "set_performance_overlay_enabled",
false, "Plot_Core")); false, "Plot_Core"));
+27 -15
View File
@@ -849,12 +849,14 @@ TEST(RenderiveWebGallery, ThreeFrameStrategiesExposeTheirRealActionsAndObservers
EXPECT_GT(limit.at("bottleneck_duration_ns").get<std::uint64_t>(), 1U); EXPECT_GT(limit.at("bottleneck_duration_ns").get<std::uint64_t>(), 1U);
EXPECT_TRUE(low_telemetry.at("kernel_observer").contains("paint_lease_wait_ns")); EXPECT_TRUE(low_telemetry.at("kernel_observer").contains("paint_lease_wait_ns"));
EXPECT_TRUE(low_telemetry.at("kernel_observer").contains("render_finish_state_wait_ns")); EXPECT_TRUE(low_telemetry.at("kernel_observer").contains("render_finish_state_wait_ns"));
constexpr std::array<std::string_view, 27> observer_fields{ constexpr std::array<std::string_view, 32> observer_fields{
"mode", "last_event", "limit_state", "configured_frequency_hz", "observation_count", "mode", "last_event", "limit_state", "configured_frequency_hz", "observation_count",
"produced_frame_count", "consumed_frame_count", "dropped_frame_count", "produced_frame_count", "consumed_frame_count", "dropped_frame_count",
"failed_operation_count", "pending_frame_count", "latest_sequence", "failed_operation_count", "pending_frame_count", "latest_sequence",
"paint_duration_ns", "render_duration_ns", "target_interval_ns", "paint_duration_ns", "render_duration_ns", "target_interval_ns", "frequency_limit_enabled",
"bottleneck_duration_ns", "consumer_interval_ns", "next_refresh_interval_ns", "paint_lease_wait_ns", "consumer_feedback_enabled", "bottleneck_duration_ns", "consumer_sample_interval_ns",
"consumer_smoothed_interval_ns", "consumer_variation_ns", "consumer_interval_ns",
"next_refresh_interval_ns", "paint_lease_wait_ns",
"paint_state_wait_ns", "publish_state_wait_ns", "ready_wait_ns", "paint_state_wait_ns", "publish_state_wait_ns", "ready_wait_ns",
"frame_age_at_render_ns", "render_lease_wait_ns", "render_state_wait_ns", "frame_age_at_render_ns", "render_lease_wait_ns", "render_state_wait_ns",
"render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns" "render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns"
@@ -973,7 +975,7 @@ TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveUnderAggres
observer.at("limit_state") == "consumer_limited"); observer.at("limit_state") == "consumer_limited");
} }
TEST(RenderiveWebGallery, WebRuntimeMetricsPublishGenericConsumerFeedbackWithoutTransportFpsCap) { TEST(RenderiveWebGallery, WebRuntimeMetricsPublishKernelSmoothedConsumerFeedbackWithoutTransportFpsCap) {
Gallery_Plot_Session session(true); Gallery_Plot_Session session(true);
ASSERT_EQ(response_json(session.handle(gallery_request( ASSERT_EQ(response_json(session.handle(gallery_request(
Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"), Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
@@ -984,26 +986,36 @@ TEST(RenderiveWebGallery, WebRuntimeMetricsPublishGenericConsumerFeedbackWithout
"case_state"); "case_state");
auto observed = response_json(session.handle(gallery_request( auto observed = response_json(session.handle(gallery_request(
Gallery_Request_Kind::Observe, Gallery_Request_Kind::Observe,
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":20,"presentation_fps":20,"websocket_buffered_bytes":0,"changed_pixel_frames":10,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":500,"display_interval_ms":500,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})"))); R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":50,"presentation_fps":50,"websocket_buffered_bytes":0,"changed_pixel_frames":10,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":2,"display_interval_ms":20,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
auto observer = observed.at("telemetry").at("kernel_observer"); auto observer = observed.at("telemetry").at("kernel_observer");
auto limit = observed.at("telemetry").at("low_latency_limit"); auto limit = observed.at("telemetry").at("low_latency_limit");
EXPECT_EQ(observer.at("configured_frequency_hz"), 1'000'000'000.0); EXPECT_TRUE(observer.at("frequency_limit_enabled").get<bool>());
EXPECT_EQ(observer.at("target_interval_ns"), 1); EXPECT_TRUE(observer.at("consumer_feedback_enabled").get<bool>());
EXPECT_EQ(observer.at("consumer_interval_ns"), 500'000'000); EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 20'000'000);
EXPECT_EQ(observer.at("next_refresh_interval_ns"), 500'000'000); EXPECT_EQ(observer.at("consumer_smoothed_interval_ns"), 20'000'000);
EXPECT_EQ(observer.at("consumer_variation_ns"), 0);
EXPECT_EQ(observer.at("consumer_interval_ns"), 20'000'000);
EXPECT_EQ(observer.at("limit_state"), "consumer_limited"); EXPECT_EQ(observer.at("limit_state"), "consumer_limited");
EXPECT_EQ(limit.at("consumer_feedback_source"), "web_runtime"); EXPECT_EQ(limit.at("consumer_feedback_source"), "web_runtime");
EXPECT_FALSE(limit.contains("web_pixel_stream_fps"));
observed = response_json(session.handle(gallery_request( observed = response_json(session.handle(gallery_request(
Gallery_Request_Kind::Observe, Gallery_Request_Kind::Observe,
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":120,"presentation_fps":60,"websocket_buffered_bytes":0,"changed_pixel_frames":20,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":2,"display_interval_ms":20,"overwritten_pixel_frames":1,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})"))); R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":45,"presentation_fps":45,"websocket_buffered_bytes":0,"changed_pixel_frames":20,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":4,"display_interval_ms":22,"overwritten_pixel_frames":0,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})")));
observer = observed.at("telemetry").at("kernel_observer"); observer = observed.at("telemetry").at("kernel_observer");
EXPECT_EQ(observer.at("consumer_interval_ns"), 20'000'000); EXPECT_EQ(observer.at("consumer_sample_interval_ns"), 22'000'000);
EXPECT_EQ(observer.at("consumer_smoothed_interval_ns"), 20'250'000);
EXPECT_EQ(observer.at("consumer_variation_ns"), 500'000);
EXPECT_EQ(observer.at("consumer_interval_ns"), 22'250'000);
observed = response_json(session.handle(gallery_request( observed = response_json(session.handle(gallery_request(
Gallery_Request_Kind::Observe, Gallery_Request_Kind::Patch,
R"({"category":"event","type":"gallery_observe","client_metrics":{"transport_fps":120,"presentation_fps":120,"websocket_buffered_bytes":0,"changed_pixel_frames":30,"duplicate_pixel_frames":0,"frame_request_timeout_count":0,"frame_round_trip_ms":2,"display_interval_ms":8,"overwritten_pixel_frames":1,"last_pixel_receive_age_ms":1,"last_pixel_change_age_ms":1}})"))); R"({"category":"event","type":"gallery_patch","patch":{"consumer_feedback_enabled":false,"frequency_limit_enabled":false}})")));
observer = observed.at("telemetry").at("kernel_observer"); observer = observed.at("telemetry").at("kernel_observer");
EXPECT_EQ(observer.at("consumer_interval_ns"), 8'000'000); EXPECT_FALSE(observer.at("frequency_limit_enabled").get<bool>());
EXPECT_FALSE(observer.at("consumer_feedback_enabled").get<bool>());
EXPECT_EQ(observer.at("target_interval_ns"), 0);
EXPECT_EQ(observer.at("consumer_interval_ns"), 0);
EXPECT_TRUE(observer.at("limit_state") == "paint_limited" ||
observer.at("limit_state") == "render_limited" ||
observer.at("limit_state") == "unlimited");
const auto rejected = response_json(session.handle(gallery_request( const auto rejected = response_json(session.handle(gallery_request(
Gallery_Request_Kind::Patch, Gallery_Request_Kind::Patch,
R"({"category":"event","type":"gallery_patch","patch":{"pixel_stream_fps":30}})"))); R"({"category":"event","type":"gallery_patch","patch":{"pixel_stream_fps":30}})")));
+12 -13
View File
@@ -228,7 +228,7 @@ class GalleryCard {
const observer = this.telemetry.kernel_observer || {}; const observer = this.telemetry.kernel_observer || {};
const dataShape = this.telemetry.data_shape || {}; const dataShape = this.telemetry.data_shape || {};
const limit = this.telemetry.low_latency_limit || {}; const limit = this.telemetry.low_latency_limit || {};
const limitNames = {frequency_limited: "Kernel 频率受限", paint_limited: "PaintEvent 受限", render_limited: "后台渲染受限", consumer_limited: "消费者反馈受限", not_applicable: "N/A"}; const limitNames = {frequency_limited: "Kernel 频率受限", paint_limited: "PaintEvent 受限", render_limited: "后台渲染受限", consumer_limited: "消费者反馈受限", unlimited: "无限制", not_applicable: "N/A"};
this.node.querySelector(".perf-fps").textContent = Number(performanceData.measured_fps || 0).toFixed(1); this.node.querySelector(".perf-fps").textContent = Number(performanceData.measured_fps || 0).toFixed(1);
this.node.querySelector(".perf-transport-fps").textContent = Number(performanceData.pixel_response_fps || 0).toFixed(1); this.node.querySelector(".perf-transport-fps").textContent = Number(performanceData.pixel_response_fps || 0).toFixed(1);
this.node.querySelector(".perf-present-fps").textContent = Number(this.presentationFps || 0).toFixed(1); this.node.querySelector(".perf-present-fps").textContent = Number(this.presentationFps || 0).toFixed(1);
@@ -246,20 +246,21 @@ class GalleryCard {
this.node.querySelector(".perf-pixel-age").textContent = this.lastPixelReceivedAt ? `${Math.max(0, performance.now() - this.lastPixelReceivedAt).toFixed(0)} ms` : "—"; this.node.querySelector(".perf-pixel-age").textContent = this.lastPixelReceivedAt ? `${Math.max(0, performance.now() - this.lastPixelReceivedAt).toFixed(0)} ms` : "—";
this.updateObserverDashboard(observer); this.updateObserverDashboard(observer);
this.updateMotionStatus(); this.updateMotionStatus();
const setLimitFlag = (selector, active, duration) => { const setLimitFlag = (selector, enabled, active, duration) => {
const flag = this.node.querySelector(selector); const flag = this.node.querySelector(selector);
flag.dataset.active = String(Boolean(active)); flag.dataset.active = String(Boolean(active));
flag.querySelector("b").textContent = `${active ? "受限" : "未受限"} · ${formatNanoseconds(duration)}`; flag.querySelector("b").textContent = enabled ? `${active ? "受限" : "未受限"} · ${formatNanoseconds(duration)}` : "已关闭";
}; };
setLimitFlag(".limit-frequency", limit.current === "frequency_limited", observer.target_interval_ns); setLimitFlag(".limit-frequency", observer.frequency_limit_enabled !== false, limit.current === "frequency_limited", observer.target_interval_ns);
setLimitFlag(".limit-paint", limit.current === "paint_limited", observer.paint_duration_ns); setLimitFlag(".limit-paint", true, limit.current === "paint_limited", observer.paint_duration_ns);
setLimitFlag(".limit-render", limit.current === "render_limited", observer.render_duration_ns); setLimitFlag(".limit-render", true, limit.current === "render_limited", observer.render_duration_ns);
setLimitFlag(".limit-consumer", limit.current === "consumer_limited", observer.consumer_interval_ns); setLimitFlag(".limit-consumer", Boolean(observer.consumer_feedback_enabled), limit.current === "consumer_limited", observer.consumer_interval_ns);
} }
updateObserverDashboard(observer) { updateObserverDashboard(observer) {
const nanosecondFields = new Set([ const nanosecondFields = new Set([
"paint_duration_ns", "render_duration_ns", "target_interval_ns", "paint_duration_ns", "render_duration_ns", "target_interval_ns",
"bottleneck_duration_ns", "consumer_interval_ns", "next_refresh_interval_ns", "paint_lease_wait_ns", "bottleneck_duration_ns", "consumer_sample_interval_ns", "consumer_smoothed_interval_ns",
"consumer_variation_ns", "consumer_interval_ns", "next_refresh_interval_ns", "paint_lease_wait_ns",
"paint_state_wait_ns", "publish_state_wait_ns", "ready_wait_ns", "paint_state_wait_ns", "publish_state_wait_ns", "ready_wait_ns",
"frame_age_at_render_ns", "render_lease_wait_ns", "render_state_wait_ns", "frame_age_at_render_ns", "render_lease_wait_ns", "render_state_wait_ns",
"render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns" "render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns"
@@ -267,7 +268,7 @@ class GalleryCard {
for (const [name, field] of this.observerFields) { for (const [name, field] of this.observerFields) {
const raw = observer[name] ?? (name === "limit_state" ? "not_applicable" : name === "last_event" ? "none" : 0); const raw = observer[name] ?? (name === "limit_state" ? "not_applicable" : name === "last_event" ? "none" : 0);
field.textContent = nanosecondFields.has(name) ? formatNanoseconds(raw) : field.textContent = nanosecondFields.has(name) ? formatNanoseconds(raw) :
name === "configured_frequency_hz" ? `${Number(raw || 0).toLocaleString()} Hz` : name === "configured_frequency_hz" ? (observer.frequency_limit_enabled === false ? "已关闭" : `${Number(raw || 0).toLocaleString()} Hz`) :
typeof raw === "number" ? raw.toLocaleString() : String(raw); typeof raw === "number" ? raw.toLocaleString() : String(raw);
field.title = nanosecondFields.has(name) ? `${Number(raw || 0).toLocaleString()} ns` : String(raw); field.title = nanosecondFields.has(name) ? `${Number(raw || 0).toLocaleString()} ns` : String(raw);
} }
@@ -327,8 +328,7 @@ class GalleryCard {
this.lastPixelSignature = signature; this.lastPixelSignature = signature;
this.lastPixelReceivedAt = now; this.lastPixelReceivedAt = now;
if (this.frameRequestStartedAt > 0) { if (this.frameRequestStartedAt > 0) {
const sample = Math.max(0, now - this.frameRequestStartedAt); this.frameRoundTripMs = Math.max(0, now - this.frameRequestStartedAt);
this.frameRoundTripMs = this.frameRoundTripMs > 0 ? this.frameRoundTripMs * 0.8 + sample * 0.2 : sample;
this.frameRequestStartedAt = 0; this.frameRequestStartedAt = 0;
} }
if (this.latestPixelBuffer !== null) this.overwrittenPixelFrames++; if (this.latestPixelBuffer !== null) this.overwrittenPixelFrames++;
@@ -590,8 +590,7 @@ function connectCatalog() {
} }
function loop(time) { function loop(time) {
if (lastAnimationFrameAt > 0) { if (lastAnimationFrameAt > 0) {
const sample = Math.max(0, time - lastAnimationFrameAt); displayIntervalMs = Math.max(0, time - lastAnimationFrameAt);
displayIntervalMs = displayIntervalMs > 0 ? displayIntervalMs * 0.8 + sample * 0.2 : sample;
} }
lastAnimationFrameAt = time; lastAnimationFrameAt = time;
const page = pages.get(activeMode); const page = pages.get(activeMode);
+3
View File
@@ -121,6 +121,9 @@
<div><small>PaintEvent</small><b data-observer-field="paint_duration_ns">0 ns</b></div> <div><small>PaintEvent</small><b data-observer-field="paint_duration_ns">0 ns</b></div>
<div><small>后台渲染</small><b data-observer-field="render_duration_ns">0 ns</b></div> <div><small>后台渲染</small><b data-observer-field="render_duration_ns">0 ns</b></div>
<div><small>内部瓶颈</small><b data-observer-field="bottleneck_duration_ns">0 ns</b></div> <div><small>内部瓶颈</small><b data-observer-field="bottleneck_duration_ns">0 ns</b></div>
<div><small>消费原始样本</small><b data-observer-field="consumer_sample_interval_ns">0 ns</b></div>
<div><small>消费 SRTT</small><b data-observer-field="consumer_smoothed_interval_ns">0 ns</b></div>
<div><small>消费 RTTVAR</small><b data-observer-field="consumer_variation_ns">0 ns</b></div>
<div><small>消费者反馈</small><b data-observer-field="consumer_interval_ns">0 ns</b></div> <div><small>消费者反馈</small><b data-observer-field="consumer_interval_ns">0 ns</b></div>
<div><small>下次刷新</small><b data-observer-field="next_refresh_interval_ns">0 ns</b></div> <div><small>下次刷新</small><b data-observer-field="next_refresh_interval_ns">0 ns</b></div>
<div class="critical"><small>端到端延迟</small><b data-observer-field="end_to_end_ns">0 ns</b></div> <div class="critical"><small>端到端延迟</small><b data-observer-field="end_to_end_ns">0 ns</b></div>