架构微调
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+1
-1
@@ -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);
|
||||||
|
|||||||
+62
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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_) {
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user