diff --git a/Core2/plot/Plot_Core.cpp b/Core2/plot/Plot_Core.cpp index e97b2e5..9e3f14d 100644 --- a/Core2/plot/Plot_Core.cpp +++ b/Core2/plot/Plot_Core.cpp @@ -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(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>(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::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>(impl_->scene)->frame_control.state(); + snapshot.limit_state = limit_state_name(true, static_cast(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 sink) { diff --git a/Core2/plot/Plot_Core.h b/Core2/plot/Plot_Core.h index 92ead55..2c6eee3 100644 --- a/Core2/plot/Plot_Core.h +++ b/Core2/plot/Plot_Core.h @@ -2,6 +2,7 @@ #include "../base/Types.h" #include "../event/Event.h" +#include #include #include @@ -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; diff --git a/Core2/tests/Core2_Frame_Pipeline_Tests.cpp b/Core2/tests/Core2_Frame_Pipeline_Tests.cpp index 22c4964..00cbf1f 100644 --- a/Core2/tests/Core2_Frame_Pipeline_Tests.cpp +++ b/Core2/tests/Core2_Frame_Pipeline_Tests.cpp @@ -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(); diff --git a/Kernel/src/renderive/frame_control/Frame_Consumer_Feedback.hpp b/Kernel/src/renderive/frame_control/Frame_Consumer_Feedback.hpp new file mode 100644 index 0000000..0cc952a --- /dev/null +++ b/Kernel/src/renderive/frame_control/Frame_Consumer_Feedback.hpp @@ -0,0 +1,5 @@ +#pragma once +#include +struct Frame_Consumer_Feedback { + std::uint64_t minimum_frame_interval_ns{}; +}; diff --git a/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp b/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp index 67b233e..bcad607 100644 --- a/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp +++ b/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp @@ -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 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); diff --git a/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.inl b/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.inl index f87c1b9..0aff315 100644 --- a/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.inl +++ b/Kernel/src/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.inl @@ -207,7 +207,7 @@ Low_Latency_Strategy::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 auto Low_Latency_Strategy::acquire_painter() -> Painter_Lease { @@ -223,9 +223,13 @@ void Low_Latency_Strategy::set_frequency_hz(double std::lock_guard 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 +void Low_Latency_Strategy::set_consumer_feedback(Frame_Consumer_Feedback feedback) { + std::lock_guard lock(state_mutex_); + state_.consumer_interval_ns = feedback.minimum_frame_interval_ns; + update_refresh_control(); } template void Low_Latency_Strategy::swap() { @@ -323,23 +327,33 @@ std::uint64_t Low_Latency_Strategy::now_ns() const return observer_.now_ns(); } template -void Low_Latency_Strategy::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::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 +void Low_Latency_Strategy::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 void Low_Latency_Strategy::observe(const Observation& observation) noexcept { diff --git a/Kernel/tests/renderive/frame_control/strategy/low_latency/Low_Latency_Pipeline_Test.cpp b/Kernel/tests/renderive/frame_control/strategy/low_latency/Low_Latency_Pipeline_Test.cpp index 16bf589..1395805 100644 --- a/Kernel/tests/renderive/frame_control/strategy/low_latency/Low_Latency_Pipeline_Test.cpp +++ b/Kernel/tests/renderive/frame_control/strategy/low_latency/Low_Latency_Pipeline_Test.cpp @@ -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; diff --git a/Kernel/tests/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy_Test_Types.hpp b/Kernel/tests/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy_Test_Types.hpp index b6dbd39..5ae9708 100644 --- a/Kernel/tests/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy_Test_Types.hpp +++ b/Kernel/tests/renderive/frame_control/strategy/low_latency/Low_Latency_Strategy_Test_Types.hpp @@ -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(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 }); diff --git a/main.cmake b/main.cmake index 3f8f300..5c8e38a 100644 --- a/main.cmake +++ b/main.cmake @@ -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 diff --git a/web_server/Gallery_Plot_Session.cpp b/web_server/Gallery_Plot_Session.cpp index 32d73dc..aa1ff63 100644 --- a/web_server/Gallery_Plot_Session.cpp +++ b/web_server/Gallery_Plot_Session.cpp @@ -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(scheduler_interval_ns)} + {"scheduler_frequency_hz", scheduler_interval_ns == 0 ? 0.0 : + 1'000'000'000.0 / static_cast(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(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(1, scene->automatic_render_interval_ns())); + std::max(1, scene->kernel_refresh_interval_ns())); if (next_deadline + interval < cycle_started) next_deadline = cycle_started; next_deadline += interval; diff --git a/web_server/Gallery_Protocol.cpp b/web_server/Gallery_Protocol.cpp index 27ddd79..99f8bb2 100644 --- a/web_server/Gallery_Protocol.cpp +++ b/web_server/Gallery_Protocol.cpp @@ -127,9 +127,9 @@ void append_common(std::vector& 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")); diff --git a/web_server/tests/Web_Bridge_Tests.cpp b/web_server/tests/Web_Bridge_Tests.cpp index 978ff9a..60c8597 100644 --- a/web_server/tests/Web_Bridge_Tests.cpp +++ b/web_server/tests/Web_Bridge_Tests.cpp @@ -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() || - limit.at("render_limited").get()); + EXPECT_TRUE(limit.at("consumer_limited").get()); EXPECT_FALSE(limit.at("frequency_limited").get()); + EXPECT_FALSE(limit.at("paint_limited").get()); + EXPECT_FALSE(limit.at("render_limited").get()); 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(), 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 observer_fields{ + constexpr std::array 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(), 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()); - 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()); + 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); diff --git a/webapp_gallery/app.js b/webapp_gallery/app.js index c31a999..529ae86 100644 --- a/webapp_gallery/app.js +++ b/webapp_gallery/app.js @@ -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" diff --git a/webapp_gallery/index.html b/webapp_gallery/index.html index a8d5130..06f1b3b 100644 --- a/webapp_gallery/index.html +++ b/webapp_gallery/index.html @@ -97,7 +97,7 @@ Kernel 用户频率:未受限 Kernel PaintEvent:未受限 Kernel 后台渲染:未受限 - Web 像素消费:未受限 + 消费者反馈:未受限
@@ -118,7 +118,8 @@
目标间隔0 ns
PaintEvent0 ns
后台渲染0 ns
-
当前瓶颈0 ns
+
内部瓶颈0 ns
+
消费者反馈0 ns
下次刷新0 ns
端到端延迟0 ns