架构微调
This commit is contained in:
+48
-46
@@ -26,9 +26,6 @@ namespace {
|
||||
struct Frame_Observer_Data {
|
||||
mutable std::mutex mutex;
|
||||
std::uint32_t last_event{};
|
||||
std::uint32_t limit_state{};
|
||||
bool has_limit_state{};
|
||||
double frequency_hz{};
|
||||
std::uint64_t observation_count{};
|
||||
std::uint64_t produced_frame_count{};
|
||||
std::uint64_t consumed_frame_count{};
|
||||
@@ -38,9 +35,6 @@ struct Frame_Observer_Data {
|
||||
std::uint64_t latest_sequence{};
|
||||
std::uint64_t paint_duration_ns{};
|
||||
std::uint64_t render_duration_ns{};
|
||||
std::uint64_t target_interval_ns{};
|
||||
std::uint64_t bottleneck_duration_ns{};
|
||||
std::uint64_t next_refresh_interval_ns{};
|
||||
std::uint64_t paint_lease_wait_ns{};
|
||||
std::uint64_t paint_state_wait_ns{};
|
||||
std::uint64_t publish_state_wait_ns{};
|
||||
@@ -50,7 +44,6 @@ struct Frame_Observer_Data {
|
||||
std::uint64_t render_state_wait_ns{};
|
||||
std::uint64_t render_finish_state_wait_ns{};
|
||||
std::uint64_t queue_wait_ns{};
|
||||
std::uint64_t end_to_end_ns{};
|
||||
};
|
||||
|
||||
struct Frame_Observer {
|
||||
@@ -95,15 +88,6 @@ struct Frame_Observer {
|
||||
data->failed_operation_count = observation.state.failed_refresh_count;
|
||||
data->pending_frame_count = observation.state.pending_frame ? 1U : 0U;
|
||||
} else if constexpr (requires { observation.state.limit_state; }) {
|
||||
data->has_limit_state = true;
|
||||
data->limit_state = static_cast<std::uint32_t>(observation.state.limit_state);
|
||||
data->frequency_hz = observation.state.frequency_hz;
|
||||
data->target_interval_ns = observation.state.target_interval_ns;
|
||||
data->paint_duration_ns = observation.state.paint_duration_ns;
|
||||
data->render_duration_ns = observation.state.render_duration_ns;
|
||||
data->bottleneck_duration_ns = observation.state.bottleneck_duration_ns;
|
||||
data->next_refresh_interval_ns = observation.state.next_refresh_interval_ns;
|
||||
data->end_to_end_ns = observation.state.end_to_end_ns;
|
||||
data->produced_frame_count = observation.statistics.counters.published_frame_count;
|
||||
data->consumed_frame_count = observation.state.completed_lifecycle_count;
|
||||
data->dropped_frame_count =
|
||||
@@ -181,7 +165,8 @@ std::string observer_event_name(Frame_Control_Mode mode, std::uint32_t event) {
|
||||
std::string limit_state_name(bool available, std::uint32_t state) {
|
||||
if (!available)
|
||||
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"};
|
||||
return state < std::size(names) ? names[state] : "unknown";
|
||||
}
|
||||
|
||||
@@ -475,6 +460,12 @@ void Plot_Core::set_max_render_fps(double fps) {
|
||||
notify_model_dirty();
|
||||
}
|
||||
|
||||
void Plot_Core::set_consumer_feedback(Frame_Consumer_Feedback 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.set_consumer_feedback(feedback);
|
||||
}
|
||||
|
||||
double Plot_Core::max_render_fps() const noexcept {
|
||||
if (impl_->mode != Frame_Control_Mode::Low_Latency)
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
@@ -502,35 +493,46 @@ Frame_Control_Mode Plot_Core::frame_control_mode() const noexcept {
|
||||
}
|
||||
|
||||
Frame_Observer_Snapshot Plot_Core::frame_observer_snapshot() const {
|
||||
std::lock_guard lock(impl_->observer->mutex);
|
||||
return {impl_->mode,
|
||||
impl_->observer->observation_count == 0
|
||||
? std::string("none")
|
||||
: observer_event_name(impl_->mode, impl_->observer->last_event),
|
||||
limit_state_name(impl_->observer->has_limit_state, impl_->observer->limit_state),
|
||||
impl_->observer->frequency_hz,
|
||||
impl_->observer->observation_count,
|
||||
impl_->observer->produced_frame_count,
|
||||
impl_->observer->consumed_frame_count,
|
||||
impl_->observer->dropped_frame_count,
|
||||
impl_->observer->failed_operation_count,
|
||||
impl_->observer->pending_frame_count,
|
||||
impl_->observer->latest_sequence,
|
||||
impl_->observer->paint_duration_ns,
|
||||
impl_->observer->render_duration_ns,
|
||||
impl_->observer->target_interval_ns,
|
||||
impl_->observer->bottleneck_duration_ns,
|
||||
impl_->observer->next_refresh_interval_ns,
|
||||
impl_->observer->paint_lease_wait_ns,
|
||||
impl_->observer->paint_state_wait_ns,
|
||||
impl_->observer->publish_state_wait_ns,
|
||||
impl_->observer->ready_wait_ns,
|
||||
impl_->observer->frame_age_at_render_ns,
|
||||
impl_->observer->render_lease_wait_ns,
|
||||
impl_->observer->render_state_wait_ns,
|
||||
impl_->observer->render_finish_state_wait_ns,
|
||||
impl_->observer->queue_wait_ns,
|
||||
impl_->observer->end_to_end_ns};
|
||||
Frame_Observer_Snapshot snapshot;
|
||||
{
|
||||
std::lock_guard lock(impl_->observer->mutex);
|
||||
snapshot.mode = impl_->mode;
|
||||
snapshot.last_event = impl_->observer->observation_count == 0 ?
|
||||
std::string("none") :
|
||||
observer_event_name(impl_->mode, impl_->observer->last_event);
|
||||
snapshot.limit_state = "not_applicable";
|
||||
snapshot.observation_count = impl_->observer->observation_count;
|
||||
snapshot.produced_frame_count = impl_->observer->produced_frame_count;
|
||||
snapshot.consumed_frame_count = impl_->observer->consumed_frame_count;
|
||||
snapshot.dropped_frame_count = impl_->observer->dropped_frame_count;
|
||||
snapshot.failed_operation_count = impl_->observer->failed_operation_count;
|
||||
snapshot.pending_frame_count = impl_->observer->pending_frame_count;
|
||||
snapshot.latest_sequence = impl_->observer->latest_sequence;
|
||||
snapshot.paint_duration_ns = impl_->observer->paint_duration_ns;
|
||||
snapshot.render_duration_ns = impl_->observer->render_duration_ns;
|
||||
snapshot.paint_lease_wait_ns = impl_->observer->paint_lease_wait_ns;
|
||||
snapshot.paint_state_wait_ns = impl_->observer->paint_state_wait_ns;
|
||||
snapshot.publish_state_wait_ns = impl_->observer->publish_state_wait_ns;
|
||||
snapshot.ready_wait_ns = impl_->observer->ready_wait_ns;
|
||||
snapshot.frame_age_at_render_ns = impl_->observer->frame_age_at_render_ns;
|
||||
snapshot.render_lease_wait_ns = impl_->observer->render_lease_wait_ns;
|
||||
snapshot.render_state_wait_ns = impl_->observer->render_state_wait_ns;
|
||||
snapshot.render_finish_state_wait_ns = impl_->observer->render_finish_state_wait_ns;
|
||||
snapshot.queue_wait_ns = impl_->observer->queue_wait_ns;
|
||||
}
|
||||
if (impl_->mode == Frame_Control_Mode::Low_Latency) {
|
||||
const auto state = std::get<std::unique_ptr<Low_Latency_Plot_Scene>>(impl_->scene)->frame_control.state();
|
||||
snapshot.limit_state = limit_state_name(true, static_cast<std::uint32_t>(state.limit_state));
|
||||
snapshot.frequency_hz = state.frequency_hz;
|
||||
snapshot.paint_duration_ns = state.paint_duration_ns;
|
||||
snapshot.render_duration_ns = state.render_duration_ns;
|
||||
snapshot.target_interval_ns = state.target_interval_ns;
|
||||
snapshot.bottleneck_duration_ns = state.bottleneck_duration_ns;
|
||||
snapshot.consumer_interval_ns = state.consumer_interval_ns;
|
||||
snapshot.next_refresh_interval_ns = state.next_refresh_interval_ns;
|
||||
snapshot.end_to_end_ns = state.end_to_end_ns;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void Plot_Core::set_presentation_sink(std::weak_ptr<Presentation_Sink> sink) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../base/Types.h"
|
||||
#include "../event/Event.h"
|
||||
#include <renderive/frame_control/Frame_Consumer_Feedback.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
@@ -50,6 +51,7 @@ struct Frame_Observer_Snapshot {
|
||||
std::uint64_t render_duration_ns{};
|
||||
std::uint64_t target_interval_ns{};
|
||||
std::uint64_t bottleneck_duration_ns{};
|
||||
std::uint64_t consumer_interval_ns{};
|
||||
std::uint64_t next_refresh_interval_ns{};
|
||||
std::uint64_t paint_lease_wait_ns{};
|
||||
std::uint64_t paint_state_wait_ns{};
|
||||
@@ -112,6 +114,7 @@ public:
|
||||
[[nodiscard]] bool view_active() const noexcept;
|
||||
|
||||
void set_max_render_fps(double fps);
|
||||
void set_consumer_feedback(Frame_Consumer_Feedback feedback);
|
||||
[[nodiscard]] double max_render_fps() const noexcept;
|
||||
[[nodiscard]] Low_Latency_Diagnostics diagnostics() const;
|
||||
[[nodiscard]] Refresh_Control_Snapshot refresh_feedback_snapshot() const;
|
||||
|
||||
@@ -80,6 +80,22 @@ TEST(Renderive_Core2_Frame_Pipeline, HighFrequencyLowLatencyLifecycleExposesKern
|
||||
EXPECT_EQ(refresh.next_refresh_interval_ns, snapshot.next_refresh_interval_ns);
|
||||
}
|
||||
|
||||
TEST(Renderive_Core2_Frame_Pipeline, LowLatencyConsumerFeedbackFlowsThroughCore2IntoKernelScheduling) {
|
||||
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});
|
||||
ASSERT_TRUE(plot.prepare_frame());
|
||||
ASSERT_TRUE(plot.render_prepared_frame());
|
||||
const auto snapshot = plot.frame_observer_snapshot();
|
||||
EXPECT_EQ(snapshot.limit_state, "consumer_limited");
|
||||
EXPECT_EQ(snapshot.target_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(snapshot.consumer_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);
|
||||
}
|
||||
|
||||
TEST(Renderive_Core2_Frame_Pipeline, LowLatencyReplacementRetiresOldFrameWithoutPhantomPendingFrame) {
|
||||
Plot_Core plot(Frame_Control_Mode::Low_Latency);
|
||||
plot.init();
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
struct Frame_Consumer_Feedback {
|
||||
std::uint64_t minimum_frame_interval_ns{};
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "renderive/base/Concepts.hpp"
|
||||
#include "renderive/base/observer/Observer.hpp"
|
||||
#include "renderive/frame_control/base/Frame_Control_Strategy_Base.hpp"
|
||||
#include "renderive/frame_control/Frame_Consumer_Feedback.hpp"
|
||||
#include "renderive/real_time_data/Observation.hpp"
|
||||
template <class Scene_Frame, Mutex_Type Mutex = std::mutex, class Observer = Observer_State<>>
|
||||
class Low_Latency_Strategy : public Frame_Control_Strategy_Base {
|
||||
@@ -22,7 +23,8 @@ public:
|
||||
enum class Limit_State {
|
||||
frequency_limited,
|
||||
paint_limited,
|
||||
render_limited
|
||||
render_limited,
|
||||
consumer_limited
|
||||
};
|
||||
struct Configuration {
|
||||
double frequency_hz{60.0};
|
||||
@@ -70,6 +72,7 @@ public:
|
||||
std::uint64_t paint_duration_ns{};
|
||||
std::uint64_t render_duration_ns{};
|
||||
std::uint64_t bottleneck_duration_ns{};
|
||||
std::uint64_t consumer_interval_ns{};
|
||||
std::uint64_t end_to_end_ns{};
|
||||
std::uint64_t next_refresh_interval_ns{};
|
||||
std::uint64_t completed_lifecycle_count{};
|
||||
@@ -142,6 +145,7 @@ public:
|
||||
Painter_Lease acquire_painter();
|
||||
Render_Lease acquire_renderer();
|
||||
void set_frequency_hz(double frequency_hz);
|
||||
void set_consumer_feedback(Frame_Consumer_Feedback feedback);
|
||||
void swap() override;
|
||||
double frequency_hz() const noexcept override;
|
||||
std::uint64_t next_refresh_interval_ns() const noexcept override;
|
||||
@@ -155,6 +159,7 @@ public:
|
||||
private:
|
||||
static double checked_frequency_hz(double frequency_hz);
|
||||
std::uint64_t now_ns() const noexcept;
|
||||
void update_refresh_control();
|
||||
void update_state(const Frame& frame);
|
||||
void observe(const Observation& observation) noexcept;
|
||||
bool discard_pending_frame_locked(Observation& observation);
|
||||
|
||||
@@ -207,7 +207,7 @@ Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Low_Latency_Strategy(Observe
|
||||
: Frame_Control_Strategy_Base(checked_frequency_hz(configuration.frequency_hz), frequency_interval_ns(configuration.frequency_hz)), observer_(std::move(observer)), paint_(&frames_[0]), cache_(&frames_[1]), render_(&frames_[2]) {
|
||||
state_.frequency_hz = configuration.frequency_hz;
|
||||
state_.target_interval_ns = frequency_interval_ns(configuration.frequency_hz);
|
||||
state_.next_refresh_interval_ns = state_.target_interval_ns;
|
||||
update_refresh_control();
|
||||
}
|
||||
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
|
||||
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::acquire_painter() -> Painter_Lease {
|
||||
@@ -223,9 +223,13 @@ void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::set_frequency_hz(double
|
||||
std::lock_guard<Mutex> lock(state_mutex_);
|
||||
state_.frequency_hz = frequency_hz;
|
||||
state_.target_interval_ns = frequency_interval_ns(frequency_hz);
|
||||
state_.next_refresh_interval_ns = std::max(state_.target_interval_ns,
|
||||
state_.bottleneck_duration_ns);
|
||||
update_frame_control_state(state_.frequency_hz, state_.next_refresh_interval_ns);
|
||||
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) {
|
||||
std::lock_guard<Mutex> lock(state_mutex_);
|
||||
state_.consumer_interval_ns = feedback.minimum_frame_interval_ns;
|
||||
update_refresh_control();
|
||||
}
|
||||
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
|
||||
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::swap() {
|
||||
@@ -323,23 +327,33 @@ std::uint64_t Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::now_ns() const
|
||||
return observer_.now_ns();
|
||||
}
|
||||
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
|
||||
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_state(const Frame& frame) {
|
||||
state_.paint_duration_ns = frame.statistics.timing.paint_duration_ns;
|
||||
state_.render_duration_ns = frame.statistics.timing.render_duration_ns;
|
||||
state_.bottleneck_duration_ns = std::max(state_.paint_duration_ns, state_.render_duration_ns);
|
||||
state_.end_to_end_ns = frame.statistics.timing.end_to_end_ns;
|
||||
state_.next_refresh_interval_ns = std::max(state_.target_interval_ns, state_.bottleneck_duration_ns);
|
||||
if (state_.target_interval_ns != 0 && state_.bottleneck_duration_ns <= state_.target_interval_ns) {
|
||||
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_refresh_control() {
|
||||
state_.next_refresh_interval_ns = std::max({state_.target_interval_ns,
|
||||
state_.bottleneck_duration_ns,
|
||||
state_.consumer_interval_ns});
|
||||
if (state_.consumer_interval_ns > state_.target_interval_ns &&
|
||||
state_.consumer_interval_ns > state_.bottleneck_duration_ns) {
|
||||
state_.limit_state = Limit_State::consumer_limited;
|
||||
} else if (state_.target_interval_ns != 0 &&
|
||||
state_.bottleneck_duration_ns <= state_.target_interval_ns) {
|
||||
state_.limit_state = Limit_State::frequency_limited;
|
||||
} else if (state_.paint_duration_ns >= state_.render_duration_ns) {
|
||||
state_.limit_state = Limit_State::paint_limited;
|
||||
} else {
|
||||
state_.limit_state = Limit_State::render_limited;
|
||||
}
|
||||
update_frame_control_state(state_.frequency_hz, state_.next_refresh_interval_ns);
|
||||
}
|
||||
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
|
||||
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_state(const Frame& frame) {
|
||||
state_.paint_duration_ns = frame.statistics.timing.paint_duration_ns;
|
||||
state_.render_duration_ns = frame.statistics.timing.render_duration_ns;
|
||||
state_.bottleneck_duration_ns = std::max(state_.paint_duration_ns, state_.render_duration_ns);
|
||||
state_.end_to_end_ns = frame.statistics.timing.end_to_end_ns;
|
||||
update_refresh_control();
|
||||
++state_.completed_lifecycle_count;
|
||||
state_.frame_sequence = frame.statistics.sequence;
|
||||
state_.completed_frame_real_time_data_update_sequence = frame.statistics.real_time_data_update_sequence;
|
||||
update_frame_control_state(state_.frequency_hz, state_.next_refresh_interval_ns);
|
||||
}
|
||||
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
|
||||
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::observe(const Observation& observation) noexcept {
|
||||
|
||||
+19
@@ -61,6 +61,25 @@ TEST(low_latency_pipeline_test, classifies_frequency_paint_and_render_limits_acr
|
||||
EXPECT_EQ(state.frame_sequence, 3u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, consumer_feedback_limits_refresh_without_knowing_the_consumer_type) {
|
||||
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(100.0);
|
||||
strategy.set_consumer_feedback({40'000'000});
|
||||
complete_pipeline(strategy, time_source, 1, 2'000'000, 0, 3'000'000);
|
||||
auto state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::consumer_limited);
|
||||
EXPECT_EQ(state.target_interval_ns, 10'000'000u);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 40'000'000u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 40'000'000u);
|
||||
strategy.set_consumer_feedback({});
|
||||
state = strategy.state();
|
||||
EXPECT_EQ(state.limit_state, Low_Latency_Test_Strategy::Limit_State::frequency_limited);
|
||||
EXPECT_EQ(state.consumer_interval_ns, 0u);
|
||||
EXPECT_EQ(state.next_refresh_interval_ns, 10'000'000u);
|
||||
}
|
||||
|
||||
TEST(low_latency_pipeline_test, applies_frequency_and_equal_bottleneck_boundaries_deterministically) {
|
||||
Low_Latency_Test_Time_Source time_source;
|
||||
Low_Latency_Test_Observer observer;
|
||||
|
||||
+2
@@ -40,6 +40,7 @@ struct Low_Latency_Recorded_Observation {
|
||||
int limit_state{};
|
||||
std::uint64_t target_interval_ns{};
|
||||
std::uint64_t bottleneck_duration_ns{};
|
||||
std::uint64_t consumer_interval_ns{};
|
||||
std::uint64_t next_refresh_interval_ns{};
|
||||
std::uint64_t completed_lifecycle_count{};
|
||||
};
|
||||
@@ -74,6 +75,7 @@ struct Low_Latency_Test_Observer {
|
||||
static_cast<int>(observation.state.limit_state),
|
||||
observation.state.target_interval_ns,
|
||||
observation.state.bottleneck_duration_ns,
|
||||
observation.state.consumer_interval_ns,
|
||||
observation.state.next_refresh_interval_ns,
|
||||
observation.state.completed_lifecycle_count
|
||||
});
|
||||
|
||||
+6
-3
@@ -5,17 +5,20 @@ set(renderive_dependencies
|
||||
global::taskflow
|
||||
global::mpmcqueue
|
||||
global::drogon
|
||||
global::libdatachannel
|
||||
${Psc_Core_dependencies}
|
||||
)
|
||||
rcl_load_dependency_environment(${renderive_dependencies})
|
||||
find_package(Drogon)
|
||||
find_package(spdlog CONFIG REQUIRED)
|
||||
rcl_add_dependency_action_targets(renderive_env ${renderive_dependencies})
|
||||
library_is_installed_with_rely(renderive_dependencies_installed ${renderive_dependencies})
|
||||
if (NOT renderive_dependencies_installed)
|
||||
set(log "[FATAL_ERROR] Renderive 依赖未安装,请先构建 renderive_env 目标")
|
||||
rcl_log_append("${log}")
|
||||
rcl_add_dependency_action_targets(renderive_env ${renderive_dependencies})
|
||||
return()
|
||||
else ()
|
||||
find_package(Drogon)
|
||||
find_package(spdlog CONFIG REQUIRED)
|
||||
find_package(LibDataChannel CONFIG REQUIRED)
|
||||
endif ()
|
||||
find_package(Qt5 MODULE REQUIRED COMPONENTS
|
||||
Core Gui Widgets Svg Xml PrintSupport QuickWidgets Multimedia
|
||||
|
||||
@@ -226,14 +226,6 @@ public:
|
||||
return plot_.refresh_feedback_snapshot().next_refresh_interval_ns;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint64_t web_pixel_interval_ns() const {
|
||||
return frequency_interval_ns(number_value(state_, "pixel_stream_fps"));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint64_t automatic_render_interval_ns() const {
|
||||
return std::max(kernel_refresh_interval_ns(), web_pixel_interval_ns());
|
||||
}
|
||||
|
||||
void set_client_metrics(double transport_fps, double presentation_fps,
|
||||
std::uint64_t buffered_bytes,
|
||||
std::uint64_t changed_pixel_frames,
|
||||
@@ -630,6 +622,7 @@ public:
|
||||
{"render_duration_ns", observer.render_duration_ns},
|
||||
{"target_interval_ns", observer.target_interval_ns},
|
||||
{"bottleneck_duration_ns", observer.bottleneck_duration_ns},
|
||||
{"consumer_interval_ns", observer.consumer_interval_ns},
|
||||
{"next_refresh_interval_ns", observer.next_refresh_interval_ns},
|
||||
{"paint_lease_wait_ns", observer.paint_lease_wait_ns},
|
||||
{"paint_state_wait_ns", observer.paint_state_wait_ns},
|
||||
@@ -657,28 +650,24 @@ public:
|
||||
if (frame_mode_ == Gallery_Frame_Mode::Low_Latency) {
|
||||
telemetry["render_fps"] = plot_.max_render_fps();
|
||||
telemetry["refresh_feedback_hz"] = plot_.refresh_feedback_snapshot().frequency_hz;
|
||||
const auto kernel_interval_ns = kernel_refresh_interval_ns();
|
||||
const auto web_interval_ns = web_pixel_interval_ns();
|
||||
const auto scheduler_interval_ns = std::max(kernel_interval_ns, web_interval_ns);
|
||||
const auto scheduler_limit_source = web_interval_ns > kernel_interval_ns ?
|
||||
"web_pixel_stream" : observer.limit_state;
|
||||
const auto scheduler_interval_ns = kernel_refresh_interval_ns();
|
||||
telemetry["low_latency_limit"] = {
|
||||
{"current", scheduler_limit_source},
|
||||
{"kernel_current", observer.limit_state},
|
||||
{"current", observer.limit_state},
|
||||
{"frequency_limited", observer.limit_state == "frequency_limited"},
|
||||
{"paint_limited", observer.limit_state == "paint_limited"},
|
||||
{"render_limited", observer.limit_state == "render_limited"},
|
||||
{"web_pixel_stream_limited", web_interval_ns > kernel_interval_ns},
|
||||
{"consumer_limited", observer.limit_state == "consumer_limited"},
|
||||
{"configured_frequency_hz", observer.frequency_hz},
|
||||
{"consumer_feedback_source", "web_pixel_stream"},
|
||||
{"web_pixel_stream_fps", number_value(state_, "pixel_stream_fps")},
|
||||
{"target_interval_ns", observer.target_interval_ns},
|
||||
{"paint_duration_ns", observer.paint_duration_ns},
|
||||
{"render_duration_ns", observer.render_duration_ns},
|
||||
{"bottleneck_duration_ns", observer.bottleneck_duration_ns},
|
||||
{"kernel_refresh_interval_ns", kernel_interval_ns},
|
||||
{"web_pixel_stream_interval_ns", web_interval_ns},
|
||||
{"consumer_interval_ns", observer.consumer_interval_ns},
|
||||
{"scheduler_interval_ns", scheduler_interval_ns},
|
||||
{"scheduler_frequency_hz", 1'000'000'000.0 / static_cast<double>(scheduler_interval_ns)}
|
||||
{"scheduler_frequency_hz", scheduler_interval_ns == 0 ? 0.0 :
|
||||
1'000'000'000.0 / static_cast<double>(scheduler_interval_ns)}
|
||||
};
|
||||
}
|
||||
if (primary_) {
|
||||
@@ -917,10 +906,7 @@ private:
|
||||
const double pixel_megabytes_per_second = recent_pixel_megabytes_per_second(
|
||||
pixel_history_, now);
|
||||
const bool low_latency = frame_mode_ == Gallery_Frame_Mode::Low_Latency;
|
||||
const auto kernel_interval_ns = low_latency ? kernel_refresh_interval_ns() : 0;
|
||||
const auto web_interval_ns = low_latency ? web_pixel_interval_ns() : 0;
|
||||
const auto scheduler_interval_ns = low_latency ?
|
||||
std::max(kernel_interval_ns, web_interval_ns) : 0;
|
||||
const auto scheduler_interval_ns = low_latency ? kernel_refresh_interval_ns() : 0;
|
||||
const nlohmann::json line{
|
||||
{"event", "gallery_performance"},
|
||||
{"unix_ms", unix_ms},
|
||||
@@ -932,8 +918,8 @@ private:
|
||||
{"automatic_scheduler_interval_ns", scheduler_interval_ns},
|
||||
{"automatic_scheduler_fps", scheduler_interval_ns == 0 ? 0.0 :
|
||||
1'000'000'000.0 / static_cast<double>(scheduler_interval_ns)},
|
||||
{"automatic_scheduler_limit_source", !low_latency ? "not_applicable" :
|
||||
(web_interval_ns > kernel_interval_ns ? "web_pixel_stream" : observer.limit_state)},
|
||||
{"automatic_scheduler_limit_source", low_latency ? observer.limit_state : "not_applicable"},
|
||||
{"consumer_feedback_interval_ns", low_latency ? observer.consumer_interval_ns : 0},
|
||||
{"backend_render_fps", recent_rate(render_history_, now)},
|
||||
{"pixel_response_fps", pixel_fps},
|
||||
{"client_transport_fps", client_transport_fps_},
|
||||
@@ -965,6 +951,7 @@ private:
|
||||
{"paint_duration_ns", observer.paint_duration_ns},
|
||||
{"render_duration_ns", observer.render_duration_ns},
|
||||
{"bottleneck_duration_ns", observer.bottleneck_duration_ns},
|
||||
{"consumer_interval_ns", observer.consumer_interval_ns},
|
||||
{"next_refresh_interval_ns", observer.next_refresh_interval_ns},
|
||||
{"paint_lease_wait_ns", observer.paint_lease_wait_ns},
|
||||
{"paint_state_wait_ns", observer.paint_state_wait_ns},
|
||||
@@ -1218,8 +1205,11 @@ private:
|
||||
|
||||
void apply_state() {
|
||||
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"));
|
||||
plot_.set_consumer_feedback(
|
||||
{frequency_interval_ns(number_value(state_, "pixel_stream_fps"))});
|
||||
}
|
||||
set_performance_plot_name(plot_, case_id_ + "/" + std::string(frame_mode_name(frame_mode_)));
|
||||
set_performance_overlay_enabled(plot_, bool_value(state_, "performance_overlay"));
|
||||
if (primary_) {
|
||||
@@ -1560,7 +1550,7 @@ struct Gallery_Plot_Session::Impl {
|
||||
}
|
||||
(void)scene->render_latest_frame();
|
||||
const auto interval = std::chrono::nanoseconds(
|
||||
std::max<std::uint64_t>(1, scene->automatic_render_interval_ns()));
|
||||
std::max<std::uint64_t>(1, scene->kernel_refresh_interval_ns()));
|
||||
if (next_deadline + interval < cycle_started)
|
||||
next_deadline = cycle_started;
|
||||
next_deadline += interval;
|
||||
|
||||
@@ -127,9 +127,9 @@ void append_common(std::vector<Control_Definition>& result, Gallery_Frame_Mode f
|
||||
30, 0.01, 1'000'000'000, 1, "Low_Latency_Strategy", false,
|
||||
"Kernel 接受任意有限正频率;高目标用于触发并观察 Painter 或后台 Render 瓶颈。"));
|
||||
result.push_back(number("pixel_stream_fps", "WebSocket 像素 FPS",
|
||||
"WebSocket_Pixel_Stream::set_pull_frequency", 30, 1, 60, 1,
|
||||
"Plot_Core::set_consumer_feedback", 30, 1, 60, 1,
|
||||
"Web Transport", false,
|
||||
"限制浏览器拉取完整 RGBA 像素图的频率,并作为 Web 自动低延迟调度的消费频率上限。"));
|
||||
"限制浏览器拉取完整 RGBA 像素图的频率;Web 适配层将该频率转换为通用消费者反馈,Kernel 不感知 WebSocket。"));
|
||||
}
|
||||
result.push_back(flag("performance_overlay", "性能叠层", "set_performance_overlay_enabled",
|
||||
false, "Plot_Core"));
|
||||
|
||||
@@ -841,19 +841,21 @@ TEST(RenderiveWebGallery, ThreeFrameStrategiesExposeTheirRealActionsAndObservers
|
||||
EXPECT_TRUE(low_telemetry.contains("render_fps"));
|
||||
ASSERT_TRUE(low_telemetry.contains("low_latency_limit"));
|
||||
const auto& limit = low_telemetry.at("low_latency_limit");
|
||||
EXPECT_TRUE(limit.at("paint_limited").get<bool>() ||
|
||||
limit.at("render_limited").get<bool>());
|
||||
EXPECT_TRUE(limit.at("consumer_limited").get<bool>());
|
||||
EXPECT_FALSE(limit.at("frequency_limited").get<bool>());
|
||||
EXPECT_FALSE(limit.at("paint_limited").get<bool>());
|
||||
EXPECT_FALSE(limit.at("render_limited").get<bool>());
|
||||
EXPECT_EQ(limit.at("target_interval_ns"), 1);
|
||||
EXPECT_EQ(limit.at("consumer_interval_ns"), 33'333'333);
|
||||
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("render_finish_state_wait_ns"));
|
||||
constexpr std::array<std::string_view, 26> observer_fields{
|
||||
constexpr std::array<std::string_view, 27> observer_fields{
|
||||
"mode", "last_event", "limit_state", "configured_frequency_hz", "observation_count",
|
||||
"produced_frame_count", "consumed_frame_count", "dropped_frame_count",
|
||||
"failed_operation_count", "pending_frame_count", "latest_sequence",
|
||||
"paint_duration_ns", "render_duration_ns", "target_interval_ns",
|
||||
"bottleneck_duration_ns", "next_refresh_interval_ns", "paint_lease_wait_ns",
|
||||
"bottleneck_duration_ns", "consumer_interval_ns", "next_refresh_interval_ns", "paint_lease_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",
|
||||
"render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns"
|
||||
@@ -968,10 +970,11 @@ TEST(RenderiveWebGallery, LowLatencyStrategyKeepsForegroundResponsiveUnderAggres
|
||||
EXPECT_GT(telemetry.at("performance").at("successful_render_count").get<std::uint64_t>(), 4U);
|
||||
EXPECT_TRUE(observer.at("limit_state") == "frequency_limited" ||
|
||||
observer.at("limit_state") == "paint_limited" ||
|
||||
observer.at("limit_state") == "render_limited");
|
||||
observer.at("limit_state") == "render_limited" ||
|
||||
observer.at("limit_state") == "consumer_limited");
|
||||
}
|
||||
|
||||
TEST(RenderiveWebGallery, WebPixelStreamCapsAutomaticLowLatencyScheduler) {
|
||||
TEST(RenderiveWebGallery, WebAdapterPublishesGenericConsumerFeedbackToKernelScheduler) {
|
||||
Gallery_Plot_Session session(true);
|
||||
ASSERT_EQ(response_json(session.handle(gallery_request(
|
||||
Gallery_Request_Kind::Open, open_message("spectrum", "low_latency")))).at("type"),
|
||||
@@ -980,13 +983,18 @@ TEST(RenderiveWebGallery, WebPixelStreamCapsAutomaticLowLatencyScheduler) {
|
||||
Gallery_Request_Kind::Patch,
|
||||
R"({"category":"event","type":"gallery_patch","patch":{"max_render_fps":1000000000,"pixel_stream_fps":20}})")));
|
||||
ASSERT_EQ(patched.at("type"), "case_state");
|
||||
const auto& limit = patched.at("telemetry").at("low_latency_limit");
|
||||
EXPECT_EQ(patched.at("telemetry").at("kernel_observer").at("configured_frequency_hz"),
|
||||
1'000'000'000.0);
|
||||
EXPECT_EQ(patched.at("telemetry").at("kernel_observer").at("target_interval_ns"), 1);
|
||||
EXPECT_EQ(limit.at("current"), "web_pixel_stream");
|
||||
EXPECT_TRUE(limit.at("web_pixel_stream_limited").get<bool>());
|
||||
EXPECT_EQ(limit.at("web_pixel_stream_interval_ns"), 50'000'000);
|
||||
const auto& telemetry = patched.at("telemetry");
|
||||
const auto& observer = telemetry.at("kernel_observer");
|
||||
const auto& limit = telemetry.at("low_latency_limit");
|
||||
EXPECT_EQ(observer.at("configured_frequency_hz"), 1'000'000'000.0);
|
||||
EXPECT_EQ(observer.at("target_interval_ns"), 1);
|
||||
EXPECT_EQ(observer.at("consumer_interval_ns"), 50'000'000);
|
||||
EXPECT_EQ(observer.at("next_refresh_interval_ns"), 50'000'000);
|
||||
EXPECT_EQ(observer.at("limit_state"), "consumer_limited");
|
||||
EXPECT_EQ(limit.at("current"), "consumer_limited");
|
||||
EXPECT_TRUE(limit.at("consumer_limited").get<bool>());
|
||||
EXPECT_EQ(limit.at("consumer_feedback_source"), "web_pixel_stream");
|
||||
EXPECT_EQ(limit.at("consumer_interval_ns"), 50'000'000);
|
||||
EXPECT_EQ(limit.at("scheduler_interval_ns"), 50'000'000);
|
||||
EXPECT_DOUBLE_EQ(limit.at("scheduler_frequency_hz"), 20.0);
|
||||
const auto baseline = successful_render_count(session);
|
||||
@@ -1093,7 +1101,8 @@ TEST(RenderiveWebGallery, ProductionLowLatencySessionRendersWithoutPixelPulls) {
|
||||
EXPECT_EQ(telemetry.at("kernel_observer").at("target_interval_ns"), 8'333'333);
|
||||
EXPECT_TRUE(telemetry.at("kernel_observer").at("limit_state") == "frequency_limited" ||
|
||||
telemetry.at("kernel_observer").at("limit_state") == "paint_limited" ||
|
||||
telemetry.at("kernel_observer").at("limit_state") == "render_limited");
|
||||
telemetry.at("kernel_observer").at("limit_state") == "render_limited" ||
|
||||
telemetry.at("kernel_observer").at("limit_state") == "consumer_limited");
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("transport_fps"), 120.0);
|
||||
EXPECT_DOUBLE_EQ(telemetry.at("client_performance").at("presentation_fps"), 60.0);
|
||||
EXPECT_EQ(telemetry.at("client_performance").at("changed_pixel_frames"), 17);
|
||||
|
||||
@@ -231,7 +231,7 @@ class GalleryCard {
|
||||
const observer = this.telemetry.kernel_observer || {};
|
||||
const dataShape = this.telemetry.data_shape || {};
|
||||
const limit = this.telemetry.low_latency_limit || {};
|
||||
const limitNames = {frequency_limited: "Kernel 频率受限", paint_limited: "PaintEvent 受限", render_limited: "后台渲染受限", web_pixel_stream: "Web 像素消费受限", not_applicable: "N/A"};
|
||||
const limitNames = {frequency_limited: "Kernel 频率受限", paint_limited: "PaintEvent 受限", render_limited: "后台渲染受限", consumer_limited: "消费者反馈受限", not_applicable: "N/A"};
|
||||
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-present-fps").textContent = Number(this.presentationFps || 0).toFixed(1);
|
||||
@@ -255,12 +255,12 @@ class GalleryCard {
|
||||
setLimitFlag(".limit-frequency", limit.current === "frequency_limited", observer.target_interval_ns);
|
||||
setLimitFlag(".limit-paint", limit.current === "paint_limited", observer.paint_duration_ns);
|
||||
setLimitFlag(".limit-render", limit.current === "render_limited", observer.render_duration_ns);
|
||||
setLimitFlag(".limit-web", limit.current === "web_pixel_stream", limit.web_pixel_stream_interval_ns);
|
||||
setLimitFlag(".limit-consumer", limit.current === "consumer_limited", observer.consumer_interval_ns);
|
||||
}
|
||||
updateObserverDashboard(observer) {
|
||||
const nanosecondFields = new Set([
|
||||
"paint_duration_ns", "render_duration_ns", "target_interval_ns",
|
||||
"bottleneck_duration_ns", "next_refresh_interval_ns", "paint_lease_wait_ns",
|
||||
"bottleneck_duration_ns", "consumer_interval_ns", "next_refresh_interval_ns", "paint_lease_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",
|
||||
"render_finish_state_wait_ns", "queue_wait_ns", "end_to_end_ns"
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<span class="limit-frequency">Kernel 用户频率:<b>未受限</b></span>
|
||||
<span class="limit-paint">Kernel PaintEvent:<b>未受限</b></span>
|
||||
<span class="limit-render">Kernel 后台渲染:<b>未受限</b></span>
|
||||
<span class="limit-web">Web 像素消费:<b>未受限</b></span>
|
||||
<span class="limit-consumer">消费者反馈:<b>未受限</b></span>
|
||||
</div>
|
||||
<section class="kernel-observer-panel" aria-label="Kernel 低延迟全量统计">
|
||||
<header>
|
||||
@@ -118,7 +118,8 @@
|
||||
<div><small>目标间隔</small><b data-observer-field="target_interval_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="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_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>
|
||||
|
||||
Reference in New Issue
Block a user