diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index e1648f2..2143e72 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -1,5 +1,6 @@ set(Renderive_Kernel_dependencies global::taskflow) if (RENDERIVE_BUILD_TESTS) + set(Renderive_Kernel_test_targets) list(APPEND Renderive_Kernel_dependencies global::GTest) endif () rcl_add_dependency_action_targets(Renderive_Kernel_env ${Renderive_Kernel_dependencies}) @@ -42,8 +43,17 @@ if (RENDERIVE_BUILD_TESTS) add_executable("${Renderive_Kernel_test_target}" "${Renderive_Kernel_test_source}") target_include_directories("${Renderive_Kernel_test_target}" PRIVATE "${Renderive_Kernel_test_dir}" + "${CMAKE_SOURCE_DIR}/render_2D" + "${CMAKE_SOURCE_DIR}/render_3D" ) target_link_libraries("${Renderive_Kernel_test_target}" PRIVATE Renderive_Kernel GTest::gtest_main) add_test(NAME "${Renderive_Kernel_test_target}" COMMAND "${Renderive_Kernel_test_target}") + set_tests_properties("${Renderive_Kernel_test_target}" PROPERTIES LABELS "Renderive_Kernel") + list(APPEND Renderive_Kernel_test_targets "${Renderive_Kernel_test_target}") endforeach () + add_custom_target(Renderive_Kernel_check + COMMAND "${CMAKE_CTEST_COMMAND}" --test-dir "${CMAKE_BINARY_DIR}" + -C "$" -L "^Renderive_Kernel$" --output-on-failure + DEPENDS ${Renderive_Kernel_test_targets} + USES_TERMINAL) endif () diff --git a/Kernel/src/renderive/scene/Scene.hpp b/Kernel/src/renderive/scene/Scene.hpp index e0bba80..c477f66 100644 --- a/Kernel/src/renderive/scene/Scene.hpp +++ b/Kernel/src/renderive/scene/Scene.hpp @@ -1,6 +1,4 @@ #pragma once -#include "Scene2D_Context.hpp" -#include "Scene3D_Context.hpp" #include "base/Scene_Base.hpp" #include "base/Render_Contexts.hpp" #include "concept/Scene.hpp" diff --git a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp index 235a0f5..eb3bc68 100644 --- a/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp +++ b/Kernel/tests/renderive/real_time_data/Real_Time_Data_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp b/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp index caa2446..98e3cf2 100644 --- a/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp +++ b/Kernel/tests/renderive/renderable/base/Renderable_Base_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/renderable/color/Color_Cache_Test.cpp b/Kernel/tests/renderive/renderable/color/Color_Cache_Test.cpp index 267863f..1c3efd9 100644 --- a/Kernel/tests/renderive/renderable/color/Color_Cache_Test.cpp +++ b/Kernel/tests/renderive/renderable/color/Color_Cache_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include "renderive/renderable/Renderable.hpp" #include "renderive/scene/Scene.hpp" diff --git a/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp b/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp index 2ea55c4..3504372 100644 --- a/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp +++ b/Kernel/tests/renderive/renderable/concept/Renderable_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include "renderive/renderable/Renderable.hpp" diff --git a/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp b/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp index 5deaa87..7d33050 100644 --- a/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp +++ b/Kernel/tests/renderive/scene/Dynamic_Renderable_Lifecycle_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp b/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp index cfd60cb..2ff1cff 100644 --- a/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp +++ b/Kernel/tests/renderive/scene/Render_Plan_Execution_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp b/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp index 4606966..baffca3 100644 --- a/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp b/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp index 8f6f5a2..8ceb2f2 100644 --- a/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene2D_Render_Order_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp b/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp index 2dd4673..bdcc4c7 100644 --- a/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene3D_Context_Test.cpp @@ -1,3 +1,4 @@ +#include "render_3D/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/scene/Scene_Inheritance_Test.cpp b/Kernel/tests/renderive/scene/Scene_Inheritance_Test.cpp index 871de4e..e4b0066 100644 --- a/Kernel/tests/renderive/scene/Scene_Inheritance_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene_Inheritance_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include diff --git a/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp b/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp index 7f0637e..cccfbc7 100644 --- a/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene_Memory_Resource_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/scene/Scene_State_Observer_Test.cpp b/Kernel/tests/renderive/scene/Scene_State_Observer_Test.cpp index 60b0cf8..4f551d6 100644 --- a/Kernel/tests/renderive/scene/Scene_State_Observer_Test.cpp +++ b/Kernel/tests/renderive/scene/Scene_State_Observer_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp b/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp index 4197d41..252ed2a 100644 --- a/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp +++ b/Kernel/tests/renderive/scene/base/Scene_Base_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Kernel/tests/renderive/scene/concept/Scene_Test.cpp b/Kernel/tests/renderive/scene/concept/Scene_Test.cpp index 216d5a8..0f05250 100644 --- a/Kernel/tests/renderive/scene/concept/Scene_Test.cpp +++ b/Kernel/tests/renderive/scene/concept/Scene_Test.cpp @@ -1,3 +1,5 @@ +#include "render_3D/Scene_Context.hpp" +#include "render_2D/scene/Scene_Context.hpp" #include #include "renderive/scene/Scene.hpp" static_assert(Scene>); diff --git a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp index 366a906..490aad9 100644 --- a/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp +++ b/Kernel/tests/renderive/threading/Threading_Contract_Test.cpp @@ -1,3 +1,4 @@ +#include "render_2D/scene/Scene_Context.hpp" #include #include #include diff --git a/Qt/CMakeLists.txt b/Qt/CMakeLists.txt index 93c4baf..81809f0 100644 --- a/Qt/CMakeLists.txt +++ b/Qt/CMakeLists.txt @@ -1,5 +1,6 @@ set(Renderive_Qt_dependencies) if (RENDERIVE_BUILD_TESTS) + set(Renderive_Qt_test_targets) list(APPEND Renderive_Qt_dependencies global::GTest) endif () rcl_add_dependency_action_targets(Renderive_Qt_env ${Renderive_Qt_dependencies}) @@ -61,5 +62,12 @@ if (RENDERIVE_BUILD_TESTS) "QT_QPA_PLATFORM_PLUGIN_PATH=${Renderive_Qt_runtime_root}/plugins/platforms" "$" ) + set_tests_properties("${Renderive_Qt_test_target}" PROPERTIES LABELS "Renderive_Qt") + list(APPEND Renderive_Qt_test_targets "${Renderive_Qt_test_target}") endforeach () + add_custom_target(Renderive_Qt_check + COMMAND "${CMAKE_CTEST_COMMAND}" --test-dir "${CMAKE_BINARY_DIR}" + -C "$" -L "^Renderive_Qt$" --output-on-failure + DEPENDS ${Renderive_Qt_test_targets} + USES_TERMINAL) endif () diff --git a/Qt/bridge/Qt_Presentation_Sink.h b/Qt/bridge/Qt_Presentation_Sink.h index 7e072ae..6c30a56 100644 --- a/Qt/bridge/Qt_Presentation_Sink.h +++ b/Qt/bridge/Qt_Presentation_Sink.h @@ -1,5 +1,5 @@ #pragma once -#include "render_2D/scene/Scene.h" +#include "render_2D/scene/Plot_Scene.h" #include #include class QWidget; diff --git a/Qt/plot/Plot.cpp b/Qt/plot/Plot.cpp index 4d5d441..87fe0ef 100644 --- a/Qt/plot/Plot.cpp +++ b/Qt/plot/Plot.cpp @@ -33,7 +33,7 @@ QColor to_qcolor(Color color) { } // namespace Abs_Plot_Private::Abs_Plot_Private(bool continuous_rendering) - : scene(std::make_unique()), continuous(continuous_rendering) {} + : scene(std::make_unique()), continuous(continuous_rendering) {} Abs_Plot_Private::~Abs_Plot_Private() = default; @@ -71,7 +71,7 @@ void Abs_Plot::init() { d->scene->init(); } -Scene2D* Abs_Plot::scene() const { +Plot_Scene* Abs_Plot::scene() const { return d ? d->scene.get() : nullptr; } diff --git a/Qt/plot/Plot.h b/Qt/plot/Plot.h index f5cca64..c498fa0 100644 --- a/Qt/plot/Plot.h +++ b/Qt/plot/Plot.h @@ -13,7 +13,7 @@ class LIB_DECL Abs_Plot : public QWidget { public: virtual void init(); [[nodiscard]] QColor background_color() const; - [[nodiscard]] Scene2D* scene() const; + [[nodiscard]] Plot_Scene* scene() const; [[nodiscard]] renderive_Owner root_renderable() const; void set_background_color(const QColor& color); ~Abs_Plot() override; diff --git a/Qt/plot/Plot_p.h b/Qt/plot/Plot_p.h index a829f84..d984232 100644 --- a/Qt/plot/Plot_p.h +++ b/Qt/plot/Plot_p.h @@ -19,7 +19,7 @@ struct Abs_Plot_Private { void paint_event(Abs_Plot* plot); void draw_performance_overlay(Abs_Plot* plot, QPainter& painter); - std::unique_ptr scene; + std::unique_ptr scene; std::shared_ptr presentation_sink; QTimer* timer{}; bool continuous{}; diff --git a/render_2D/CMakeLists.txt b/render_2D/CMakeLists.txt index cc4dc4c..884da06 100644 --- a/render_2D/CMakeLists.txt +++ b/render_2D/CMakeLists.txt @@ -1,5 +1,6 @@ set(Renderive_render_2D_dependencies global::blend2d) if (RENDERIVE_BUILD_TESTS) + set(Renderive_render_2D_test_targets) list(APPEND Renderive_render_2D_dependencies global::GTest) endif () rcl_add_dependency_action_targets(Renderive_render_2D_env ${Renderive_render_2D_dependencies}) @@ -46,5 +47,12 @@ if (RENDERIVE_BUILD_TESTS) add_executable("${Renderive_render_2D_test_target}" "${Renderive_render_2D_test_source}") target_link_libraries("${Renderive_render_2D_test_target}" PRIVATE Renderive_render_2D GTest::gtest_main) add_test(NAME "${Renderive_render_2D_test_target}" COMMAND "${Renderive_render_2D_test_target}") + set_tests_properties("${Renderive_render_2D_test_target}" PROPERTIES LABELS "Renderive_render_2D") + list(APPEND Renderive_render_2D_test_targets "${Renderive_render_2D_test_target}") endforeach () + add_custom_target(Renderive_render_2D_check + COMMAND "${CMAKE_CTEST_COMMAND}" --test-dir "${CMAKE_BINARY_DIR}" + -C "$" -L "^Renderive_render_2D$" --output-on-failure + DEPENDS ${Renderive_render_2D_test_targets} + USES_TERMINAL) endif () diff --git a/render_2D/render_2D/export.h b/render_2D/render_2D/export.h index 39fd54e..25510fb 100644 --- a/render_2D/render_2D/export.h +++ b/render_2D/render_2D/export.h @@ -3,7 +3,7 @@ #include "base/Types.h" #include "event/Event.h" #include "renderable/Renderable.h" -#include "scene/Scene.h" +#include "scene/Plot_Scene.h" #include "axis/Axis.h" #include "plottable/Hover_Tooltip.h" #include "plottable/Plottables.h" diff --git a/render_2D/render_2D/plottable/Performance_Overlay.h b/render_2D/render_2D/plottable/Performance_Overlay.h index e2c9feb..bc93631 100644 --- a/render_2D/render_2D/plottable/Performance_Overlay.h +++ b/render_2D/render_2D/plottable/Performance_Overlay.h @@ -1,11 +1,16 @@ #pragma once #include "../base/Types.h" -#include "../scene/Scene.h" +#include "../scene/Plot_Scene.h" #include #include #include #include namespace renderive { +namespace detail { +void record_performance_frame(Performance_Overlay&, double, + const Low_Latency_Diagnostics&, std::size_t, + Size); +} struct Performance_Log_Options { bool enabled{}; std::string directory; @@ -40,14 +45,10 @@ struct LIB_DECL Performance_Overlay { [[nodiscard]] bool enabled() const noexcept; [[nodiscard]] std::shared_ptr display_snapshot() const; private: - template - friend std::shared_ptr attach_performance_overlay(Basic_Scene2D&); - template - friend std::shared_ptr attach_performance_overlay(Basic_Scene2D&, const Performance_Overlay_Options&); - template - friend void set_performance_plot_name(Basic_Scene2D&, std::string); - template - friend void set_performance_overlay_enabled(Basic_Scene2D&, bool); + friend std::shared_ptr attach_performance_overlay(Plot_Scene&); + friend std::shared_ptr attach_performance_overlay(Plot_Scene&, const Performance_Overlay_Options&); + friend void set_performance_plot_name(Plot_Scene&, std::string); + friend void set_performance_overlay_enabled(Plot_Scene&, bool); struct Impl; std::unique_ptr impl_; void set_enabled(bool enabled); @@ -59,41 +60,37 @@ private: std::size_t renderable_count, Size viewport); }; -template -std::shared_ptr attach_performance_overlay(Basic_Scene2D& scene) { +inline std::shared_ptr attach_performance_overlay(Plot_Scene& scene) { if (auto existing = scene.performance_overlay()) return existing; auto overlay = std::make_shared(); scene.set_performance_overlay(overlay); return overlay; } -template -std::shared_ptr attach_performance_overlay(Basic_Scene2D& scene, const Performance_Overlay_Options& options) { +inline std::shared_ptr attach_performance_overlay(Plot_Scene& scene, const Performance_Overlay_Options& options) { if (auto existing = scene.performance_overlay()) { existing->set_options(options); - scene.notify_model_dirty(); + scene.request_redraw(); return existing; } auto overlay = std::make_shared(options); scene.set_performance_overlay(overlay); return overlay; } -template -void set_performance_plot_name(Basic_Scene2D& scene, std::string name) { +inline void set_performance_plot_name(Plot_Scene& scene, std::string name) { auto overlay = scene.performance_overlay(); if (!overlay) overlay = attach_performance_overlay(scene); overlay->set_plot_name(std::move(name)); - scene.notify_model_dirty(); + scene.request_redraw(); } -template -void set_performance_overlay_enabled(Basic_Scene2D& scene, bool enabled) { +inline void set_performance_overlay_enabled(Plot_Scene& scene, bool enabled) { auto overlay = scene.performance_overlay(); if (!overlay && !enabled) return; if (!overlay) overlay = attach_performance_overlay(scene); overlay->set_enabled(enabled); - scene.notify_model_dirty(); + scene.request_redraw(); } } // namespace renderive diff --git a/render_2D/render_2D/renderable/Renderable.cpp b/render_2D/render_2D/renderable/Renderable.cpp index 22a9a5c..1d4ba92 100644 --- a/render_2D/render_2D/renderable/Renderable.cpp +++ b/render_2D/render_2D/renderable/Renderable.cpp @@ -6,6 +6,17 @@ #include namespace renderive { +namespace detail { +namespace { +struct Renderable_Group final : Renderable { + using Renderable::Renderable; +}; +} + +renderive_Owner make_renderable_group(bool cache_enabled) { + return renderive_Owner::make(cache_enabled); +} +} // namespace detail Renderable::Renderable(bool cache_enabled) : render_base(std::make_unique(), {.cache_enabled = cache_enabled}) {} diff --git a/render_2D/render_2D/renderable/Renderable.h b/render_2D/render_2D/renderable/Renderable.h index 699f408..c673630 100644 --- a/render_2D/render_2D/renderable/Renderable.h +++ b/render_2D/render_2D/renderable/Renderable.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,7 @@ struct Renderable; namespace detail { struct Painter; struct Renderable_State_Observer; +LIB_DECL ::renderive_Owner make_renderable_group(bool cache_enabled); } enum struct Renderable_Observer_Event { None, diff --git a/render_2D/render_2D/scene/Plot_Frame_Status.h b/render_2D/render_2D/scene/Plot_Frame_Status.h new file mode 100644 index 0000000..66c960e --- /dev/null +++ b/render_2D/render_2D/scene/Plot_Frame_Status.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +namespace renderive { + +enum class Plot_Frame_Mode : std::uint8_t { + Manual, + Low_Latency, + Playback, +}; + +struct Refresh_Control_Snapshot { + double frequency_hz{}; + std::uint64_t frame_count{}; + std::uint64_t next_refresh_interval_ns{}; +}; + +struct Low_Latency_Diagnostics { + bool active{}; + Refresh_Control_Snapshot refresh; +}; + +// A value result assembled at the read boundary. It does not own or mirror +// the scene observer state. +struct Plot_Frame_Status { + Plot_Frame_Mode mode{Plot_Frame_Mode::Low_Latency}; + std::string last_event; + std::string limit_state; + double frequency_hz{}; + std::uint64_t observation_count{}; + std::uint64_t produced_frame_count{}; + std::uint64_t consumed_frame_count{}; + std::uint64_t dropped_frame_count{}; + std::uint64_t failed_operation_count{}; + std::uint64_t pending_frame_count{}; + std::uint64_t latest_sequence{}; + std::uint64_t paint_duration_ns{}; + std::uint64_t render_duration_ns{}; + std::uint64_t target_interval_ns{}; + bool frequency_limit_enabled{}; + bool consumer_feedback_enabled{}; + 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_safety_interval_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{}; + std::uint64_t publish_state_wait_ns{}; + std::uint64_t ready_wait_ns{}; + std::uint64_t frame_age_at_render_ns{}; + std::uint64_t render_lease_wait_ns{}; + 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{}; +}; + +} // namespace renderive diff --git a/render_2D/render_2D/scene/Plot_Scene.cpp b/render_2D/render_2D/scene/Plot_Scene.cpp new file mode 100644 index 0000000..c9f4662 --- /dev/null +++ b/render_2D/render_2D/scene/Plot_Scene.cpp @@ -0,0 +1,199 @@ +#include "Plot_Scene.h" +#include "detail/Plot_Scene_Model.hpp" +#include "../plottable/Performance_Overlay.h" +#include "../renderable/Renderable.h" +#include + +#include +#include + +namespace renderive::detail { +void record_performance_frame(Performance_Overlay& overlay, double duration_ms, const Low_Latency_Diagnostics& diagnostics, std::size_t renderable_count, Size viewport) { + overlay.record_frame(duration_ms, diagnostics, renderable_count, viewport); +} +} + +namespace renderive { +namespace { +using Manual_Plot_Scene = Plot_Scene_Model; +using Low_Latency_Plot_Scene = Plot_Scene_Model; +using Playback_Plot_Scene = Plot_Scene_Model; +} + +struct Plot_Scene::Impl { + using Model = std::variant, + std::unique_ptr, + std::unique_ptr>; + + explicit Impl(Plot_Frame_Mode mode) : mode(mode), model(create(mode)) {} + + template + decltype(auto) apply(Operation&& operation) { + return std::visit( + [&operation](auto& scene) -> decltype(auto) { + return operation(*scene); + }, + model); + } + + template + decltype(auto) apply(Operation&& operation) const { + return std::visit( + [&operation](const auto& scene) -> decltype(auto) { + return operation(*scene); + }, + model); + } + + static Model create(Plot_Frame_Mode mode) { + switch (mode) { + case Plot_Frame_Mode::Manual: + return std::make_unique(); + case Plot_Frame_Mode::Low_Latency: + return std::make_unique(); + case Plot_Frame_Mode::Playback: + return std::make_unique(); + } + throw std::invalid_argument("unknown Plot_Scene frame mode"); + } + + Plot_Frame_Mode mode; + Model model; +}; + +Plot_Scene::Plot_Scene(Plot_Scene_Options options) + : impl_(std::make_unique(options.frame_mode)) {} + +Plot_Scene::~Plot_Scene() = default; + +void Plot_Scene::init() { impl_->apply([](auto& scene) { scene.init(); }); } + +renderive_Owner Plot_Scene::root_renderable() const { + return impl_->apply([](const auto& scene) { return scene.root_renderable(); }); +} + +Scene_2D_Base::Attach_Builder Plot_Scene::attach_builder() { + return static_cast(render_scene()).attach_builder(); +} + +void Plot_Scene::edit_renderables(Scene_2D_Base::Renderable_Edit edit) { + static_cast(render_scene()).edit_renderables(std::move(edit)); +} + +Scene_Base& Plot_Scene::render_scene() noexcept { + return impl_->apply([](auto& scene) -> Scene_Base& { return scene; }); +} + +const Scene_Base& Plot_Scene::render_scene() const noexcept { + return impl_->apply([](const auto& scene) -> const Scene_Base& { return scene; }); +} + +void Plot_Scene::set_background_color(Color color) { + impl_->apply([color](auto& scene) { scene.set_background_color(color); }); +} + +Color Plot_Scene::background_color() const noexcept { + return impl_->apply([](const auto& scene) { return scene.background_color(); }); +} + +void Plot_Scene::set_viewport_size(Size size) { + impl_->apply([size](auto& scene) { scene.set_viewport_size(size); }); +} + +Size Plot_Scene::viewport_size() const noexcept { + return impl_->apply([](const auto& scene) { return scene.viewport_size(); }); +} + +void Plot_Scene::dispatch_event(const Event& event) { + impl_->apply([&event](auto& scene) { scene.dispatch_event(event); }); +} + +bool Plot_Scene::prepare_frame() { + return impl_->apply([](auto& scene) { return scene.prepare_frame(); }); +} + +bool Plot_Scene::refresh_manual_frame() { + return impl_->apply([](auto& scene) { return scene.refresh_manual_frame(); }); +} + +bool Plot_Scene::render_prepared_frame() { + return impl_->apply([](auto& scene) { return scene.render_prepared_frame(); }); +} + +bool Plot_Scene::discard_pending_frame() { + return impl_->apply([](auto& scene) { return scene.discard_pending_frame(); }); +} + +bool Plot_Scene::render_frame(bool force) { + return impl_->apply([force](auto& scene) { return scene.render_frame(force); }); +} + +void Plot_Scene::with_frame(const std::function& consumer) { + impl_->apply([&consumer](auto& scene) { scene.with_frame(consumer); }); +} + +void Plot_Scene::activate_view() noexcept { + impl_->apply([](auto& scene) { scene.activate_view(); }); +} + +void Plot_Scene::deactivate_view() noexcept { + impl_->apply([](auto& scene) { scene.deactivate_view(); }); +} + +bool Plot_Scene::view_active() const noexcept { + return impl_->apply([](const auto& scene) { return scene.view_active(); }); +} + +void Plot_Scene::request_redraw() noexcept { render_scene().notify_model_dirty(); } + +void Plot_Scene::set_max_render_fps(double fps) { + impl_->apply([fps](auto& scene) { scene.set_max_render_fps(fps); }); +} + +void Plot_Scene::clear_max_render_fps() { + impl_->apply([](auto& scene) { scene.clear_max_render_fps(); }); +} + +double Plot_Scene::max_render_fps() const noexcept { + return impl_->apply([](const auto& scene) { return scene.max_render_fps(); }); +} + +void Plot_Scene::set_consumer_feedback(Frame_Consumer_Feedback feedback) { + impl_->apply([feedback](auto& scene) { scene.set_consumer_feedback(feedback); }); +} + +void Plot_Scene::clear_consumer_feedback() { + impl_->apply([](auto& scene) { scene.clear_consumer_feedback(); }); +} + +Plot_Frame_Mode Plot_Scene::frame_mode() const noexcept { return impl_->mode; } + +Plot_Frame_Status Plot_Scene::frame_status() const { + return impl_->apply([](const auto& scene) { return scene.frame_status(); }); +} + +Low_Latency_Diagnostics Plot_Scene::diagnostics() const { + return impl_->apply([](const auto& scene) { return scene.diagnostics(); }); +} + +Refresh_Control_Snapshot Plot_Scene::refresh_feedback_snapshot() const { + return impl_->apply( + [](const auto& scene) { return scene.refresh_feedback_snapshot(); }); +} + +void Plot_Scene::set_presentation_sink(std::weak_ptr sink) { + impl_->apply([&sink](auto& scene) { scene.set_presentation_sink(std::move(sink)); }); +} + +std::shared_ptr Plot_Scene::performance_overlay() const { + return impl_->apply([](const auto& scene) { return scene.performance_overlay(); }); +} + +void Plot_Scene::set_performance_overlay( + std::shared_ptr overlay) { + impl_->apply([&overlay](auto& scene) { + scene.set_performance_overlay(std::move(overlay)); + }); +} + +} // namespace renderive diff --git a/render_2D/render_2D/scene/Plot_Scene.h b/render_2D/render_2D/scene/Plot_Scene.h new file mode 100644 index 0000000..380c384 --- /dev/null +++ b/render_2D/render_2D/scene/Plot_Scene.h @@ -0,0 +1,85 @@ +#pragma once + +#include "../base/Types.h" +#include "../event/Event.h" +#include "../renderable/Renderable.h" +#include "Plot_Frame_Status.h" + +#include +#include +#include + +#include +#include + +class Scene_Base; + +namespace renderive { + +struct Performance_Overlay; + +struct Plot_Scene_Options { + Plot_Frame_Mode frame_mode{Plot_Frame_Mode::Low_Latency}; +}; + +struct Presentation_Sink { + virtual ~Presentation_Sink() = default; + virtual void request_present(Rect dirty_rect) = 0; +}; + +class LIB_DECL Plot_Scene final { +public: + explicit Plot_Scene(Plot_Scene_Options options = {}); + ~Plot_Scene(); + + Plot_Scene(const Plot_Scene&) = delete; + Plot_Scene& operator=(const Plot_Scene&) = delete; + Plot_Scene(Plot_Scene&&) = delete; + Plot_Scene& operator=(Plot_Scene&&) = delete; + + void init(); + [[nodiscard]] ::renderive_Owner root_renderable() const; + [[nodiscard]] Scene_2D_Base::Attach_Builder attach_builder(); + void edit_renderables(Scene_2D_Base::Renderable_Edit edit); + [[nodiscard]] Scene_Base& render_scene() noexcept; + [[nodiscard]] const Scene_Base& render_scene() const noexcept; + + void set_background_color(Color color); + [[nodiscard]] Color background_color() const noexcept; + void set_viewport_size(Size size); + [[nodiscard]] Size viewport_size() const noexcept; + void dispatch_event(const Event& event); + + [[nodiscard]] bool prepare_frame(); + [[nodiscard]] bool refresh_manual_frame(); + [[nodiscard]] bool render_prepared_frame(); + [[nodiscard]] bool discard_pending_frame(); + [[nodiscard]] bool render_frame(bool force = false); + void with_frame(const std::function& consumer); + + void activate_view() noexcept; + void deactivate_view() noexcept; + [[nodiscard]] bool view_active() const noexcept; + void request_redraw() noexcept; + + void set_max_render_fps(double fps); + void clear_max_render_fps(); + [[nodiscard]] double max_render_fps() const noexcept; + void set_consumer_feedback(Frame_Consumer_Feedback feedback); + void clear_consumer_feedback(); + + [[nodiscard]] Plot_Frame_Mode frame_mode() const noexcept; + [[nodiscard]] Plot_Frame_Status frame_status() const; + [[nodiscard]] Low_Latency_Diagnostics diagnostics() const; + [[nodiscard]] Refresh_Control_Snapshot refresh_feedback_snapshot() const; + + void set_presentation_sink(std::weak_ptr sink); + [[nodiscard]] std::shared_ptr performance_overlay() const; + void set_performance_overlay(std::shared_ptr overlay); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace renderive diff --git a/render_2D/render_2D/scene/Scene.cpp b/render_2D/render_2D/scene/Scene.cpp deleted file mode 100644 index 8b0f50f..0000000 --- a/render_2D/render_2D/scene/Scene.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "Scene.h" -#include "../plottable/Performance_Overlay.h" -#include "../renderable/Renderable.h" -namespace renderive::detail { -namespace { -struct Renderable_Group final : Renderable { - using Renderable::Renderable; -}; -} -renderive_Owner make_renderable_group(bool cache_enabled) { - return renderive_Owner::make(cache_enabled); -} -void record_performance_frame(Performance_Overlay& overlay, double duration_ms, const Low_Latency_Diagnostics& diagnostics, std::size_t renderable_count, Size viewport) { - overlay.record_frame(duration_ms, diagnostics, renderable_count, viewport); -} -} diff --git a/Kernel/src/renderive/scene/Scene2D_Context.hpp b/render_2D/render_2D/scene/Scene_Context.hpp similarity index 98% rename from Kernel/src/renderive/scene/Scene2D_Context.hpp rename to render_2D/render_2D/scene/Scene_Context.hpp index 3deb92b..5e7b500 100644 --- a/Kernel/src/renderive/scene/Scene2D_Context.hpp +++ b/render_2D/render_2D/scene/Scene_Context.hpp @@ -9,8 +9,8 @@ #include "renderive/frame_control/Frame_Control.hpp" #include "renderive/renderable/color/Concepts.hpp" #include "renderive/state/Double_State_Storage.hpp" -#include "base/Scene_Base.hpp" -#include "base/Frame_Viewport.hpp" +#include "renderive/scene/base/Scene_Base.hpp" +#include "renderive/scene/base/Frame_Viewport.hpp" struct Scene2D_Frame_Data : Abstract_Frame {}; struct Scene2D_State { Frame_Viewport viewport; diff --git a/render_2D/render_2D/scene/Scene.h b/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp similarity index 71% rename from render_2D/render_2D/scene/Scene.h rename to render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp index c7ce3c4..65116c5 100644 --- a/render_2D/render_2D/scene/Scene.h +++ b/render_2D/render_2D/scene/detail/Plot_Scene_Model.hpp @@ -1,14 +1,12 @@ #pragma once -#include "../base/Types.h" -#include "../event/Event.h" -#include "../render/Blend2D_Cache.h" -#include "../renderable/Renderable.h" +#include "../Plot_Scene.h" +#include "../../render/Blend2D_Cache.h" #include #include #include #include #include -#include +#include "../Scene_Context.hpp" #include #include #include @@ -25,59 +23,6 @@ namespace renderive { struct Performance_Overlay; struct Renderable; -enum class Frame_Control_Mode : std::uint8_t { - Manual, - Low_Latency, - Playback -}; -struct Refresh_Control_Snapshot { - double frequency_hz{}; - std::uint64_t frame_count{}; - std::uint64_t next_refresh_interval_ns{}; -}; -struct Low_Latency_Diagnostics { - bool active{}; - Refresh_Control_Snapshot refresh; -}; -struct Frame_Observer_Snapshot { - Frame_Control_Mode mode = Frame_Control_Mode::Low_Latency; - std::string last_event; - std::string limit_state; - double frequency_hz{}; - std::uint64_t observation_count{}; - std::uint64_t produced_frame_count{}; - std::uint64_t consumed_frame_count{}; - std::uint64_t dropped_frame_count{}; - std::uint64_t failed_operation_count{}; - std::uint64_t pending_frame_count{}; - std::uint64_t latest_sequence{}; - std::uint64_t paint_duration_ns{}; - std::uint64_t render_duration_ns{}; - std::uint64_t target_interval_ns{}; - bool frequency_limit_enabled{}; - bool consumer_feedback_enabled{}; - 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_safety_interval_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{}; - std::uint64_t publish_state_wait_ns{}; - std::uint64_t ready_wait_ns{}; - std::uint64_t frame_age_at_render_ns{}; - std::uint64_t render_lease_wait_ns{}; - 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 Presentation_Sink { - virtual ~Presentation_Sink() = default; - virtual void request_present(Rect dirty_rect) = 0; -}; struct Scene_State : Scene2D_State { Color background = Color::black(); }; @@ -86,11 +31,6 @@ struct Frame_Observer_Data { mutable std::mutex mutex; std::uint32_t last_event{}; std::uint64_t observation_count{}; - std::uint64_t produced_frame_count{}; - std::uint64_t consumed_frame_count{}; - std::uint64_t dropped_frame_count{}; - std::uint64_t failed_operation_count{}; - std::uint64_t pending_frame_count{}; std::uint64_t latest_sequence{}; std::uint64_t paint_duration_ns{}; std::uint64_t render_duration_ns{}; @@ -135,27 +75,6 @@ struct Frame_Observer { if (observation.statistics.sequence != 0) data->queue_wait_ns = observation.statistics.queue_wait_ns; } - if constexpr (requires { observation.state.prepared_frame_count; }) { - data->produced_frame_count = observation.state.prepared_frame_count; - data->consumed_frame_count = observation.state.render_count; - data->dropped_frame_count = observation.state.replaced_prepared_frame_count + observation.state.discarded_prepared_frame_count; - 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->produced_frame_count = observation.statistics.counters.published_frame_count; - data->consumed_frame_count = observation.state.completed_lifecycle_count; - data->dropped_frame_count = observation.statistics.counters.abandoned_frame_count + observation.statistics.counters.manually_discarded_frame_count; - data->failed_operation_count = observation.statistics.counters.swap_failure_count; - const auto retired = observation.state.completed_lifecycle_count + observation.statistics.counters.abandoned_frame_count + observation.statistics.counters.manually_discarded_frame_count; - data->pending_frame_count = observation.statistics.counters.published_frame_count > retired ? 1U : 0U; - } - else { - data->produced_frame_count = observation.state.enqueued_frame_count; - data->consumed_frame_count = observation.state.rendered_frame_count; - data->failed_operation_count = observation.state.empty_acquire_count; - data->pending_frame_count = observation.state.pending_frame_count; - } } }; using Frame_Observer_State = ::Observer_State; @@ -165,21 +84,20 @@ using Playback_Frame_Control = ::Flow_Refresh_Strategy<::Scene2D_Frame_Data, std template concept Scene_Frame_Control = std::same_as || std::same_as || std::same_as; template -constexpr Frame_Control_Mode frame_control_mode() noexcept { +constexpr Plot_Frame_Mode frame_control_mode() noexcept { if constexpr (std::same_as) - return Frame_Control_Mode::Manual; + return Plot_Frame_Mode::Manual; if constexpr (std::same_as) - return Frame_Control_Mode::Low_Latency; - return Frame_Control_Mode::Playback; + return Plot_Frame_Mode::Low_Latency; + return Plot_Frame_Mode::Playback; } -LIB_DECL renderive_Owner make_renderable_group(bool cache_enabled); LIB_DECL void record_performance_frame(Performance_Overlay& overlay, double duration_ms, const Low_Latency_Diagnostics& diagnostics, std::size_t renderable_count, Size viewport); -inline std::string observer_event_name(Frame_Control_Mode mode, std::uint32_t event) { - if (mode == Frame_Control_Mode::Manual) { +inline std::string observer_event_name(Plot_Frame_Mode mode, std::uint32_t event) { + if (mode == Plot_Frame_Mode::Manual) { static constexpr const char* names[]{"prepared", "prepared_replaced", "refresh_succeeded", "refresh_failed", "rendered", "real_time_data_updated", "manually_discarded"}; return event < std::size(names) ? names[event] : "none"; } - if (mode == Frame_Control_Mode::Playback) { + if (mode == Plot_Frame_Mode::Playback) { static constexpr const char* names[]{"enqueued", "dequeued", "queue_empty", "rendered", "real_time_data_updated"}; return event < std::size(names) ? names[event] : "none"; } @@ -195,14 +113,14 @@ inline Rect full_rect(Size size) { } } template -struct Basic_Scene2D final : ::Scene2D_Context { +struct Plot_Scene_Model final : ::Scene2D_Context { using Kernel_Scene = ::Scene2D_Context; using Scene_State_Strategy = typename Kernel_Scene::Scene_State_Strategy; - static constexpr Frame_Control_Mode Mode = detail::frame_control_mode(); - Basic_Scene2D() : Basic_Scene2D(std::make_shared()) {} - ~Basic_Scene2D() override = default; - Basic_Scene2D(const Basic_Scene2D&) = delete; - Basic_Scene2D& operator=(const Basic_Scene2D&) = delete; + static constexpr Plot_Frame_Mode Mode = detail::frame_control_mode(); + Plot_Scene_Model() : Plot_Scene_Model(std::make_shared()) {} + ~Plot_Scene_Model() override = default; + Plot_Scene_Model(const Plot_Scene_Model&) = delete; + Plot_Scene_Model& operator=(const Plot_Scene_Model&) = delete; void init() { std::lock_guard lock(initialization_mutex_); if (root_renderable()) @@ -266,7 +184,7 @@ struct Basic_Scene2D final : ::Scene2D_Contextframe_control.refresh(); return false; } @@ -279,7 +197,7 @@ struct Basic_Scene2D final : ::Scene2D_Contextframe_control.discard_pending_frame(); @@ -298,7 +216,7 @@ struct Basic_Scene2D final : ::Scene2D_ContextScene_State_Strategy::render_state_value().viewport; viewport = {frame_viewport.width, frame_viewport.height}; - if constexpr (Mode == Frame_Control_Mode::Manual) { + if constexpr (Mode == Plot_Frame_Mode::Manual) { if (!refresh_manual_frame()) return false; } @@ -342,7 +260,7 @@ struct Basic_Scene2D final : ::Scene2D_Contextframe_control.set_consumer_feedback(feedback); } void clear_consumer_feedback() { - if constexpr (Mode != Frame_Control_Mode::Low_Latency) + if constexpr (Mode != Plot_Frame_Mode::Low_Latency) throw std::logic_error("consumer feedback is only available in low-latency mode"); else this->frame_control.clear_consumer_feedback(); } [[nodiscard]] double max_render_fps() const noexcept { - if constexpr (Mode == Frame_Control_Mode::Low_Latency) + if constexpr (Mode == Plot_Frame_Mode::Low_Latency) return this->frame_control.state().frequency_hz; return std::numeric_limits::quiet_NaN(); } [[nodiscard]] Low_Latency_Diagnostics diagnostics() const { - if constexpr (Mode == Frame_Control_Mode::Low_Latency) { + if constexpr (Mode == Plot_Frame_Mode::Low_Latency) { const auto state = this->frame_control.state(); return {view_active(), {state.frequency_hz, state.completed_lifecycle_count, state.next_refresh_interval_ns}}; } - const auto observed = frame_observer_snapshot(); + const auto observed = frame_status(); return {view_active(), {std::numeric_limits::quiet_NaN(), observed.consumed_frame_count, 0}}; } [[nodiscard]] Refresh_Control_Snapshot refresh_feedback_snapshot() const { return diagnostics().refresh; } - [[nodiscard]] static constexpr Frame_Control_Mode frame_control_mode() noexcept { + [[nodiscard]] static constexpr Plot_Frame_Mode frame_control_mode() noexcept { return Mode; } - [[nodiscard]] Frame_Observer_Snapshot frame_observer_snapshot() const { - Frame_Observer_Snapshot snapshot; + [[nodiscard]] Plot_Frame_Status frame_status() const { + Plot_Frame_Status snapshot; { std::lock_guard lock(observer_->mutex); snapshot.mode = Mode; snapshot.last_event = observer_->observation_count == 0 ? std::string("none") : detail::observer_event_name(Mode, observer_->last_event); snapshot.limit_state = "not_applicable"; snapshot.observation_count = observer_->observation_count; - snapshot.produced_frame_count = observer_->produced_frame_count; - snapshot.consumed_frame_count = observer_->consumed_frame_count; - snapshot.dropped_frame_count = observer_->dropped_frame_count; - snapshot.failed_operation_count = observer_->failed_operation_count; - snapshot.pending_frame_count = observer_->pending_frame_count; snapshot.latest_sequence = observer_->latest_sequence; snapshot.paint_duration_ns = observer_->paint_duration_ns; snapshot.render_duration_ns = observer_->render_duration_ns; @@ -418,8 +331,31 @@ struct Basic_Scene2D final : ::Scene2D_Contextrender_finish_state_wait_ns; snapshot.queue_wait_ns = observer_->queue_wait_ns; } - if constexpr (Mode == Frame_Control_Mode::Low_Latency) { + if constexpr (Mode == Plot_Frame_Mode::Manual) { const auto state = this->frame_control.state(); + snapshot.produced_frame_count = state.prepared_frame_count; + snapshot.consumed_frame_count = state.render_count; + snapshot.dropped_frame_count = state.replaced_prepared_frame_count + + state.discarded_prepared_frame_count; + snapshot.failed_operation_count = state.failed_refresh_count; + snapshot.pending_frame_count = state.pending_frame ? 1U : 0U; + } + else if constexpr (Mode == Plot_Frame_Mode::Playback) { + const auto state = this->frame_control.state(); + snapshot.produced_frame_count = state.enqueued_frame_count; + snapshot.consumed_frame_count = state.rendered_frame_count; + snapshot.failed_operation_count = state.empty_acquire_count; + snapshot.pending_frame_count = state.pending_frame_count; + } + else { + const auto state = this->frame_control.state(); + const auto counters = this->frame_control.counter_statistics(); + snapshot.produced_frame_count = counters.published_frame_count; + snapshot.consumed_frame_count = state.completed_lifecycle_count; + snapshot.dropped_frame_count = counters.abandoned_frame_count + + counters.manually_discarded_frame_count; + snapshot.failed_operation_count = counters.swap_failure_count; + snapshot.pending_frame_count = this->frame_control.pending_frame_count(); snapshot.limit_state = detail::limit_state_name(static_cast(state.limit_state)); snapshot.frequency_hz = state.frequency_hz; snapshot.paint_duration_ns = state.paint_duration_ns; @@ -454,7 +390,7 @@ struct Basic_Scene2D final : ::Scene2D_Contextnotify_model_dirty(); } private: - explicit Basic_Scene2D(std::shared_ptr observer) + explicit Plot_Scene_Model(std::shared_ptr observer) : Kernel_Scene(*memory_resource(Memory_Domain::Plot_Frame), detail::Frame_Observer_State(detail::Frame_Observer{observer})), observer_(std::move(observer)) {} std::shared_ptr observer_; mutable std::mutex control_mutex_; @@ -463,7 +399,4 @@ private: std::shared_ptr performance_; std::atomic_bool active_{}; }; -using Manual_Scene2D = Basic_Scene2D; -using Scene2D = Basic_Scene2D; -using Playback_Scene2D = Basic_Scene2D; } diff --git a/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp b/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp index 17805bc..9f764c7 100644 --- a/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp +++ b/render_2D/tests/render_2D_Frame_Pipeline_Tests.cpp @@ -7,12 +7,12 @@ namespace renderive { namespace { TEST(Renderive_Core2_Frame_Pipeline, ManualLifecycleSeparatesPrepareRefreshAndRender) { - Manual_Scene2D plot; + Plot_Scene plot({.frame_mode = Plot_Frame_Mode::Manual}); plot.init(); plot.set_viewport_size({96, 54}); ASSERT_TRUE(plot.prepare_frame()); - auto snapshot = plot.frame_observer_snapshot(); - EXPECT_EQ(snapshot.mode, Frame_Control_Mode::Manual); + auto snapshot = plot.frame_status(); + EXPECT_EQ(snapshot.mode, Plot_Frame_Mode::Manual); EXPECT_EQ(snapshot.last_event, "prepared"); EXPECT_EQ(snapshot.limit_state, "not_applicable"); EXPECT_EQ(snapshot.produced_frame_count, 1u); @@ -20,14 +20,14 @@ TEST(Renderive_Core2_Frame_Pipeline, ManualLifecycleSeparatesPrepareRefreshAndRe EXPECT_EQ(snapshot.pending_frame_count, 1u); EXPECT_EQ(snapshot.latest_sequence, 1u); ASSERT_TRUE(plot.refresh_manual_frame()); - snapshot = plot.frame_observer_snapshot(); + snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "refresh_succeeded"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); ASSERT_TRUE(plot.render_prepared_frame()); - snapshot = plot.frame_observer_snapshot(); + snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "rendered"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 1u); @@ -36,12 +36,12 @@ TEST(Renderive_Core2_Frame_Pipeline, ManualLifecycleSeparatesPrepareRefreshAndRe EXPECT_EQ(plot.diagnostics().refresh.frame_count, 1u); } TEST(Renderive_Core2_Frame_Pipeline, HighFrequencyLowLatencyLifecycleExposesKernelTiming) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({96, 54}); plot.set_max_render_fps(1'000'000'000.0); ASSERT_TRUE(plot.prepare_frame()); - auto snapshot = plot.frame_observer_snapshot(); + auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "published"); EXPECT_EQ(snapshot.limit_state, "frequency_limited"); EXPECT_DOUBLE_EQ(snapshot.frequency_hz, 1'000'000'000.0); @@ -50,7 +50,7 @@ TEST(Renderive_Core2_Frame_Pipeline, HighFrequencyLowLatencyLifecycleExposesKern EXPECT_EQ(snapshot.consumed_frame_count, 0u); EXPECT_EQ(snapshot.pending_frame_count, 1u); ASSERT_TRUE(plot.render_prepared_frame()); - snapshot = plot.frame_observer_snapshot(); + snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "lifecycle_completed"); EXPECT_DOUBLE_EQ(snapshot.frequency_hz, 1'000'000'000.0); EXPECT_EQ(snapshot.target_interval_ns, 1u); @@ -69,14 +69,14 @@ TEST(Renderive_Core2_Frame_Pipeline, HighFrequencyLowLatencyLifecycleExposesKern EXPECT_EQ(refresh.next_refresh_interval_ns, snapshot.next_refresh_interval_ns); } TEST(Renderive_Core2_Frame_Pipeline, LowLatencyConsumerFeedbackFlowsThroughCore2IntoKernelScheduling) { - Scene2D plot; + Plot_Scene plot; 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(); + const auto snapshot = plot.frame_status(); EXPECT_TRUE(snapshot.frequency_limit_enabled); EXPECT_TRUE(snapshot.consumer_feedback_enabled); EXPECT_EQ(snapshot.limit_state, "consumer_limited"); @@ -90,7 +90,7 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyConsumerFeedbackFlowsThroughCore2 EXPECT_EQ(plot.refresh_feedback_snapshot().next_refresh_interval_ns, 40'000'000u); } TEST(Renderive_Core2_Frame_Pipeline, LowLatencyLimitsCanBeClearedIndependently) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({96, 54}); plot.set_max_render_fps(100.0); @@ -99,7 +99,7 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyLimitsCanBeClearedIndependently) plot.clear_max_render_fps(); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.render_prepared_frame()); - const auto snapshot = plot.frame_observer_snapshot(); + const auto snapshot = plot.frame_status(); EXPECT_FALSE(snapshot.frequency_limit_enabled); EXPECT_FALSE(snapshot.consumer_feedback_enabled); EXPECT_EQ(snapshot.target_interval_ns, 0u); @@ -108,19 +108,19 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyLimitsCanBeClearedIndependently) EXPECT_TRUE(snapshot.limit_state == "paint_limited" || snapshot.limit_state == "render_limited"); } TEST(Renderive_Core2_Frame_Pipeline, LowLatencyReplacementRetiresOldFrameWithoutPhantomPendingFrame) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({64, 40}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); - auto snapshot = plot.frame_observer_snapshot(); + auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "published"); EXPECT_EQ(snapshot.produced_frame_count, 2u); EXPECT_EQ(snapshot.dropped_frame_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 1u); EXPECT_EQ(snapshot.latest_sequence, 2u); ASSERT_TRUE(plot.render_prepared_frame()); - snapshot = plot.frame_observer_snapshot(); + snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "lifecycle_completed"); EXPECT_EQ(snapshot.produced_frame_count, 2u); EXPECT_EQ(snapshot.consumed_frame_count, 1u); @@ -129,12 +129,12 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyReplacementRetiresOldFrameWithout EXPECT_EQ(snapshot.latest_sequence, 2u); } TEST(Renderive_Core2_Frame_Pipeline, LowLatencyDiscardAndEmptyRenderRemainObservable) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({64, 40}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.discard_pending_frame()); - auto snapshot = plot.frame_observer_snapshot(); + auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "manually_discarded"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.consumed_frame_count, 0u); @@ -142,19 +142,19 @@ TEST(Renderive_Core2_Frame_Pipeline, LowLatencyDiscardAndEmptyRenderRemainObserv EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); EXPECT_FALSE(plot.render_prepared_frame()); - snapshot = plot.frame_observer_snapshot(); + snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "swap_failed"); EXPECT_EQ(snapshot.failed_operation_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); EXPECT_EQ(snapshot.latest_sequence, 1u); } TEST(Renderive_Core2_Frame_Pipeline, ManualDiscardAndFailedRefreshUpdateObserverState) { - Manual_Scene2D plot; + Plot_Scene plot({.frame_mode = Plot_Frame_Mode::Manual}); plot.init(); plot.set_viewport_size({64, 40}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.discard_pending_frame()); - auto snapshot = plot.frame_observer_snapshot(); + auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "manually_discarded"); EXPECT_EQ(snapshot.produced_frame_count, 1u); EXPECT_EQ(snapshot.dropped_frame_count, 1u); @@ -162,7 +162,7 @@ TEST(Renderive_Core2_Frame_Pipeline, ManualDiscardAndFailedRefreshUpdateObserver EXPECT_EQ(snapshot.latest_sequence, 1u); const auto last_paint_duration_ns = snapshot.paint_duration_ns; EXPECT_FALSE(plot.refresh_manual_frame()); - snapshot = plot.frame_observer_snapshot(); + snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "refresh_failed"); EXPECT_EQ(snapshot.failed_operation_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); @@ -170,20 +170,20 @@ TEST(Renderive_Core2_Frame_Pipeline, ManualDiscardAndFailedRefreshUpdateObserver EXPECT_EQ(snapshot.paint_duration_ns, last_paint_duration_ns); } TEST(Renderive_Core2_Frame_Pipeline, PlaybackPreservesQueueDepthAndEmptyAcquireFailure) { - Playback_Scene2D plot; + Plot_Scene plot({.frame_mode = Plot_Frame_Mode::Playback}); plot.init(); plot.set_viewport_size({64, 40}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); - auto snapshot = plot.frame_observer_snapshot(); + auto snapshot = plot.frame_status(); EXPECT_EQ(snapshot.produced_frame_count, 3u); EXPECT_EQ(snapshot.pending_frame_count, 3u); EXPECT_EQ(snapshot.latest_sequence, 3u); ASSERT_TRUE(plot.render_prepared_frame()); ASSERT_TRUE(plot.render_prepared_frame()); ASSERT_TRUE(plot.render_prepared_frame()); - snapshot = plot.frame_observer_snapshot(); + snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "rendered"); EXPECT_EQ(snapshot.consumed_frame_count, 3u); EXPECT_EQ(snapshot.pending_frame_count, 0u); @@ -192,7 +192,7 @@ TEST(Renderive_Core2_Frame_Pipeline, PlaybackPreservesQueueDepthAndEmptyAcquireF const auto last_render_duration_ns = snapshot.render_duration_ns; const auto last_queue_wait_ns = snapshot.queue_wait_ns; EXPECT_FALSE(plot.render_prepared_frame()); - snapshot = plot.frame_observer_snapshot(); + snapshot = plot.frame_status(); EXPECT_EQ(snapshot.last_event, "queue_empty"); EXPECT_EQ(snapshot.failed_operation_count, 1u); EXPECT_EQ(snapshot.pending_frame_count, 0u); @@ -202,18 +202,18 @@ TEST(Renderive_Core2_Frame_Pipeline, PlaybackPreservesQueueDepthAndEmptyAcquireF EXPECT_EQ(snapshot.queue_wait_ns, last_queue_wait_ns); } TEST(Renderive_Core2_Frame_Pipeline, PlaybackFullCyclePreservesPreviouslyQueuedFrames) { - Playback_Scene2D plot; + Plot_Scene plot({.frame_mode = Plot_Frame_Mode::Playback}); plot.init(); plot.set_viewport_size({96, 54}); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); ASSERT_TRUE(plot.prepare_frame()); - auto snapshot = plot.frame_observer_snapshot(); + auto snapshot = plot.frame_status(); ASSERT_EQ(snapshot.pending_frame_count, 3u); ASSERT_EQ(snapshot.produced_frame_count, 3u); ASSERT_TRUE(plot.render_frame(true)); - snapshot = plot.frame_observer_snapshot(); - EXPECT_EQ(snapshot.mode, Frame_Control_Mode::Playback); + snapshot = plot.frame_status(); + EXPECT_EQ(snapshot.mode, Plot_Frame_Mode::Playback); EXPECT_EQ(snapshot.last_event, "rendered"); EXPECT_EQ(snapshot.produced_frame_count, 4u); EXPECT_EQ(snapshot.consumed_frame_count, 1u); @@ -221,35 +221,35 @@ TEST(Renderive_Core2_Frame_Pipeline, PlaybackFullCyclePreservesPreviouslyQueuedF // The cycle enqueues sequence 4 but must consume the FIFO head (sequence 1). EXPECT_EQ(snapshot.latest_sequence, 1u); EXPECT_FALSE(plot.discard_pending_frame()); - EXPECT_EQ(plot.frame_observer_snapshot().pending_frame_count, 3u); + EXPECT_EQ(plot.frame_status().pending_frame_count, 3u); } TEST(Renderive_Core2_Frame_Pipeline, EveryModeHonorsActiveDirtyAndForceRenderGates) { - const auto verify = [](Frame_Control_Mode mode) { - Scene_Type scene; + const auto verify = [](Plot_Frame_Mode mode) { + Plot_Scene scene({.frame_mode = mode}); scene.init(); scene.set_viewport_size({80, 45}); EXPECT_FALSE(scene.view_active()); EXPECT_FALSE(scene.render_frame(false)); - EXPECT_EQ(scene.frame_observer_snapshot().observation_count, 0u); + EXPECT_EQ(scene.frame_status().observation_count, 0u); scene.activate_view(); ASSERT_TRUE(scene.render_frame(false)); - const auto completed = scene.frame_observer_snapshot(); + const auto completed = scene.frame_status(); EXPECT_EQ(completed.mode, mode); EXPECT_EQ(completed.consumed_frame_count, 1u); EXPECT_FALSE(scene.render_frame(false)); - EXPECT_EQ(scene.frame_observer_snapshot().consumed_frame_count, 1u); + EXPECT_EQ(scene.frame_status().consumed_frame_count, 1u); scene.deactivate_view(); scene.set_background_color(Color{8, 16, 24, 255}); EXPECT_FALSE(scene.render_frame(false)); ASSERT_TRUE(scene.render_frame(true)); - EXPECT_EQ(scene.frame_observer_snapshot().consumed_frame_count, 2u); + EXPECT_EQ(scene.frame_status().consumed_frame_count, 2u); }; - verify.template operator()(Frame_Control_Mode::Manual); - verify.template operator()(Frame_Control_Mode::Low_Latency); - verify.template operator()(Frame_Control_Mode::Playback); + verify(Plot_Frame_Mode::Manual); + verify(Plot_Frame_Mode::Low_Latency); + verify(Plot_Frame_Mode::Playback); } TEST(Renderive_Core2_Frame_Pipeline, FrequencyConfigurationRejectsInvalidValuesAndWrongModes) { - Scene2D low_latency; + Plot_Scene low_latency; EXPECT_THROW(low_latency.set_max_render_fps(0.0), std::invalid_argument); EXPECT_THROW(low_latency.set_max_render_fps(-1.0), std::invalid_argument); EXPECT_THROW(low_latency.set_max_render_fps(std::numeric_limits::quiet_NaN()), @@ -258,8 +258,8 @@ TEST(Renderive_Core2_Frame_Pipeline, FrequencyConfigurationRejectsInvalidValuesA std::invalid_argument); low_latency.set_max_render_fps(1'000.0); EXPECT_DOUBLE_EQ(low_latency.max_render_fps(), 1'000.0); - Manual_Scene2D manual; - Playback_Scene2D playback; + Plot_Scene manual({.frame_mode = Plot_Frame_Mode::Manual}); + Plot_Scene playback({.frame_mode = Plot_Frame_Mode::Playback}); EXPECT_THROW(manual.set_max_render_fps(60.0), std::logic_error); EXPECT_THROW(playback.set_max_render_fps(60.0), std::logic_error); EXPECT_FALSE(playback.refresh_manual_frame()); diff --git a/render_2D/tests/render_2D_Integration_Tests.cpp b/render_2D/tests/render_2D_Integration_Tests.cpp index 4c1d1d0..92f57ce 100644 --- a/render_2D/tests/render_2D_Integration_Tests.cpp +++ b/render_2D/tests/render_2D_Integration_Tests.cpp @@ -53,7 +53,7 @@ static_assert(!Legacy_Axis_Property_Api); static_assert(!Legacy_Time_Axis_Property_Api); TEST(Renderive_Core2, RootIdentityAndRefreshDiagnosticsComeFromKernelState) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); ASSERT_TRUE(root); @@ -157,7 +157,7 @@ TEST(Renderive_Core2, BicubicHeatmapUsesRealCubicResampling) { EXPECT_NE(bilinear, bicubic); } TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({320, 180}); plot.activate_view(); @@ -238,7 +238,7 @@ TEST(Renderive_Core2, KernelSceneRendersBusinessObjectsIntoBlend2DFrame) { EXPECT_FALSE(plot.render_frame()); } TEST(Renderive_Core2, StandaloneRenderableBuilderJoinsSceneOnlyInsideRuntimeEditor) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power] = build_initial(plot, [&](auto& attach) { @@ -254,7 +254,7 @@ TEST(Renderive_Core2, StandaloneRenderableBuilderJoinsSceneOnlyInsideRuntimeEdit const auto spectrum = Spectrum::Builder{}.build(root, frequency, power); ASSERT_TRUE(spectrum); EXPECT_TRUE(std::ranges::none_of( - plot.topology_snapshot().renderables, + plot.render_scene().topology_snapshot().renderables, [&](const auto& renderable) { return renderable.get() == spectrum.get(); })); @@ -268,13 +268,13 @@ TEST(Renderive_Core2, StandaloneRenderableBuilderJoinsSceneOnlyInsideRuntimeEdit editor.add_display_parent(power, spectrum); }); EXPECT_TRUE(std::ranges::any_of( - plot.topology_snapshot().renderables, + plot.render_scene().topology_snapshot().renderables, [&](const auto& renderable) { return renderable.get() == spectrum.get(); })); } TEST(Renderive_Core2, TimeAxisUsesOneFontStateAndFormatsConfiguredLabels) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); auto axis = build_initial(plot, [&](auto& attach) { @@ -292,7 +292,7 @@ TEST(Renderive_Core2, TimeAxisUsesOneFontStateAndFormatsConfiguredLabels) { EXPECT_EQ(axis->get<&Time_Axis::Properties::tick_label_spacing_px>(), 17); } TEST(Renderive_Core2, PerformanceOverlayConsumesKernelFrameDiagnostics) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({160, 90}); plot.activate_view(); @@ -313,46 +313,46 @@ TEST(Renderive_Core2, PerformanceOverlayConsumesKernelFrameDiagnostics) { EXPECT_DOUBLE_EQ(plot.max_render_fps(), 120.0); } TEST(Renderive_Core2, AllFrameControlModesUseKernelStrategiesAndExposeObservers) { - Manual_Scene2D manual; + Plot_Scene manual({.frame_mode = Plot_Frame_Mode::Manual}); manual.init(); manual.set_viewport_size({64, 40}); - EXPECT_EQ(manual.frame_control_mode(), Frame_Control_Mode::Manual); + EXPECT_EQ(manual.frame_mode(), Plot_Frame_Mode::Manual); ASSERT_TRUE(manual.prepare_frame()); - auto manual_observer = manual.frame_observer_snapshot(); + auto manual_observer = manual.frame_status(); EXPECT_EQ(manual_observer.last_event, "prepared"); EXPECT_EQ(manual_observer.pending_frame_count, 1u); ASSERT_TRUE(manual.refresh_manual_frame()); ASSERT_TRUE(manual.render_prepared_frame()); - manual_observer = manual.frame_observer_snapshot(); + manual_observer = manual.frame_status(); EXPECT_EQ(manual_observer.last_event, "rendered"); EXPECT_EQ(manual_observer.consumed_frame_count, 1u); - Scene2D low_latency; + Plot_Scene low_latency; low_latency.init(); low_latency.set_viewport_size({64, 40}); low_latency.activate_view(); ASSERT_TRUE(low_latency.render_frame(true)); - const auto low_observer = low_latency.frame_observer_snapshot(); - EXPECT_EQ(low_latency.frame_control_mode(), Frame_Control_Mode::Low_Latency); + const auto low_observer = low_latency.frame_status(); + EXPECT_EQ(low_latency.frame_mode(), Plot_Frame_Mode::Low_Latency); EXPECT_EQ(low_observer.last_event, "lifecycle_completed"); EXPECT_EQ(low_observer.consumed_frame_count, 1u); - Playback_Scene2D playback; + Plot_Scene playback({.frame_mode = Plot_Frame_Mode::Playback}); playback.init(); playback.set_viewport_size({64, 40}); ASSERT_TRUE(playback.prepare_frame()); ASSERT_TRUE(playback.prepare_frame()); ASSERT_TRUE(playback.prepare_frame()); - auto playback_observer = playback.frame_observer_snapshot(); - EXPECT_EQ(playback.frame_control_mode(), Frame_Control_Mode::Playback); + auto playback_observer = playback.frame_status(); + EXPECT_EQ(playback.frame_mode(), Plot_Frame_Mode::Playback); EXPECT_EQ(playback_observer.last_event, "enqueued"); EXPECT_EQ(playback_observer.pending_frame_count, 3u); ASSERT_TRUE(playback.render_prepared_frame()); - playback_observer = playback.frame_observer_snapshot(); + playback_observer = playback.frame_status(); EXPECT_EQ(playback_observer.last_event, "rendered"); EXPECT_EQ(playback_observer.pending_frame_count, 2u); EXPECT_EQ(playback_observer.consumed_frame_count, 1u); } TEST(Renderive_Core2, RetainedAxisAndTimeAxisApisRoundTripWithoutWebAdapters) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); ASSERT_TRUE(root); @@ -434,7 +434,7 @@ TEST(Renderive_Core2, RetainedAxisAndTimeAxisApisRoundTripWithoutWebAdapters) { EXPECT_EQ(time_axis->tick_label(first), "01:02:03.004"); } TEST(Renderive_Core2, RetainedSpectrumApisRoundTripAndMarkersRemainObservable) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({360, 240}); plot.activate_view(); @@ -523,7 +523,7 @@ TEST(Renderive_Core2, RetainedSpectrumApisRoundTripAndMarkersRemainObservable) { EXPECT_TRUE(plot.render_frame(true)); } TEST(Renderive_Core2, RetainedHeatmapSweepAndTraceApisPreserveDataShapes) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({420, 280}); plot.activate_view(); @@ -637,7 +637,7 @@ TEST(Renderive_Core2, RetainedHeatmapSweepAndTraceApisPreserveDataShapes) { EXPECT_TRUE(plot.render_frame(true)); } TEST(Renderive_Core2, RetainedSelectionAndConstellationApisDriveInteractionAndLayout) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({360, 260}); plot.activate_view(); @@ -716,7 +716,7 @@ TEST(Renderive_Core2, RetainedSelectionAndConstellationApisDriveInteractionAndLa EXPECT_TRUE(plot.render_frame(true)); } TEST(Renderive_Core2, PlottablePropertiesPublishOnlyAtFrameBoundary) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { @@ -746,7 +746,7 @@ TEST(Renderive_Core2, PlottablePropertiesPublishOnlyAtFrameBoundary) { ASSERT_TRUE(plot.render_prepared_frame()); } TEST(Renderive_Core2, EveryStatefulRenderableOwnsItsStateObserver) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { @@ -797,7 +797,7 @@ TEST(Renderive_Core2, EveryStatefulRenderableOwnsItsStateObserver) { EXPECT_EQ(spectrum->observation().event, Renderable_Observer_Event::Published); } TEST(Renderive_Core2, PlottableDataUpdatesReachKernelRealTimeDataStrategy) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { @@ -815,14 +815,14 @@ TEST(Renderive_Core2, PlottableDataUpdatesReachKernelRealTimeDataStrategy) { }); ASSERT_TRUE(spectrum); ASSERT_TRUE(afterglow); - const auto before = plot.frame_observer_snapshot().observation_count; + const auto before = plot.frame_status().observation_count; const std::array samples{-80.0, -70.0, -60.0, -50.0}; spectrum->update_samples(samples); - const auto latest = plot.frame_observer_snapshot(); + const auto latest = plot.frame_status(); EXPECT_EQ(latest.last_event, "real_time_data_updated"); EXPECT_GT(latest.observation_count, before); afterglow->append_spectrum(samples); - const auto history = plot.frame_observer_snapshot(); + const auto history = plot.frame_status(); EXPECT_EQ(history.last_event, "real_time_data_updated"); EXPECT_GT(history.observation_count, latest.observation_count); } @@ -871,7 +871,7 @@ TEST(Renderive_Core2, AxisRasterLayoutCoversAxisSwapAndEveryReversal) { EXPECT_DOUBLE_EQ(points.back().y, 30.0); } TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({320, 180}); const auto root = plot.root_renderable(); @@ -908,7 +908,7 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { spectrum->update_samples(samples); waterfall->append_row(0, samples); afterglow->append_spectrum(samples); - plot.publish_frame_state(); + plot.render_scene().publish_frame_state(); const auto planned_node_count = [](const Render_Plan& plan, const Renderable_Base& renderable) { @@ -923,7 +923,7 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { EXPECT_EQ(waterfall->get<&Waterfall::Properties::partition_count>(), 4); EXPECT_EQ(afterglow->get<&Afterglow::Properties::partition_count>(), 4); ASSERT_TRUE(plot.render_frame(true)); - const auto partitioned_plan = plot.render_plan_snapshot(); + const auto partitioned_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(partitioned_plan); EXPECT_EQ(planned_node_count(*partitioned_plan, *spectrum), 6u); EXPECT_EQ(std::count_if( @@ -940,9 +940,9 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { spectrum->set<&Spectrum::Properties::partition_count>(1); waterfall->set<&Waterfall::Properties::partition_count>(1); afterglow->set<&Afterglow::Properties::partition_count>(1); - plot.publish_frame_state(); + plot.render_scene().publish_frame_state(); ASSERT_TRUE(plot.render_frame(true)); - const auto single_plan = plot.render_plan_snapshot(); + const auto single_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(single_plan); EXPECT_EQ(planned_node_count(*single_plan, *spectrum), 3u); EXPECT_EQ(planned_node_count(*single_plan, *waterfall), 3u); @@ -951,9 +951,9 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenRenderGraphs) { spectrum->set<&Spectrum::Properties::partition_mode>(Render_Partition_Mode::Automatic); waterfall->set<&Waterfall::Properties::partition_mode>(Render_Partition_Mode::Automatic); afterglow->set<&Afterglow::Properties::partition_mode>(Render_Partition_Mode::Automatic); - plot.publish_frame_state(); + plot.render_scene().publish_frame_state(); ASSERT_TRUE(plot.render_frame(true)); - const auto automatic_plan = plot.render_plan_snapshot(); + const auto automatic_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(automatic_plan); EXPECT_EQ(planned_node_count(*automatic_plan, *spectrum), 3u); EXPECT_EQ(planned_node_count(*automatic_plan, *waterfall), 3u); @@ -989,7 +989,7 @@ TEST(Renderive_Core2, AutomaticPartitioningSplitsOnlyForMeasuredBottlenecks) { } TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSessions) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({640, 360}); const auto root = plot.root_renderable(); @@ -1018,7 +1018,7 @@ TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSe constexpr std::array partition_counts{64, 16, 1}; std::uint64_t tick{1}; for (std::size_t round = 0; round < 3; ++round) { - const Capture_Session_Id session_id = scene.capture_frames(8); + const Capture_Session_Id session_id = scene.render_scene().capture_frames(8); for (std::size_t frame = 0; frame < 8; ++frame) { if (frame % 2 == 0) { waterfall->set<&Waterfall::Properties::partition_count>( @@ -1028,14 +1028,14 @@ TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSe waterfall->append_row(static_cast(tick++), samples); ASSERT_TRUE(plot.render_frame(true)); } - const auto session = scene.capture_session(session_id); + const auto session = scene.render_scene().capture_session(session_id); ASSERT_TRUE(session); ASSERT_EQ(session->captured_count(), 8u); EXPECT_FALSE(session->active()); std::set versions; for (const auto& captured : session->frames) { ASSERT_TRUE(captured.snapshot); - const auto plan = scene.find_render_plan( + const auto plan = scene.render_scene().find_render_plan( captured.snapshot->render_plan_version); ASSERT_TRUE(plan); EXPECT_EQ(captured.snapshot->node_executions.size(), @@ -1045,12 +1045,12 @@ TEST(Renderive_Core2, DynamicWaterfallCaptureStressPreservesPlansSlotsAndExactSe versions.insert(captured.snapshot->render_plan_version); } EXPECT_GE(versions.size(), 3u); - EXPECT_FALSE(scene.capture_state().enabled()); + EXPECT_FALSE(scene.render_scene().capture_state().enabled()); } } TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { - Scene2D plot; + Plot_Scene plot; plot.init(); plot.set_viewport_size({320, 180}); const auto root = plot.root_renderable(); @@ -1068,7 +1068,7 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { spectrum->update_samples(samples); ASSERT_TRUE(plot.render_frame(true)); ASSERT_TRUE(plot.render_frame(true)); - const auto cached_plan = plot.render_plan_snapshot(); + const auto cached_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(cached_plan); const auto cached_has_kind = [&](Render_Node_Kind kind) { return std::any_of(cached_plan->graph.nodes.begin(), @@ -1084,7 +1084,7 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { Pen{Color{12, 34, 56, 255}, 2.0}); ASSERT_TRUE(plot.render_frame(true)); - const auto plan = plot.render_plan_snapshot(); + const auto plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(plan); const auto has_kind = [&](Render_Node_Kind kind) { return std::any_of(plan->graph.nodes.begin(), plan->graph.nodes.end(), @@ -1097,7 +1097,7 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { frequency->set<&Axis_Base_Properties::color>(Color{90, 100, 110, 255}); ASSERT_TRUE(plot.render_frame(true)); - const auto axis_style_plan = plot.render_plan_snapshot(); + const auto axis_style_plan = plot.render_scene().render_plan_snapshot(); ASSERT_TRUE(axis_style_plan); EXPECT_FALSE(std::any_of(axis_style_plan->graph.nodes.begin(), axis_style_plan->graph.nodes.end(), @@ -1107,7 +1107,7 @@ TEST(Renderive_Core2, PaintOnlyStyleChangesPreservePrepareCache) { })); } TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); const auto [frequency, power, spectrum] = build_initial(plot, [&](auto& attach) { @@ -1121,7 +1121,7 @@ TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { return std::tuple{frequency_axis, power_axis, value}; }); ASSERT_TRUE(spectrum); - const auto topology = plot.topology_snapshot(); + const auto topology = plot.render_scene().topology_snapshot(); const auto has_relationship = [](const auto& relationships, const auto* child, const auto* parent) { return std::any_of(relationships.begin(), relationships.end(), [child, parent](const auto& relationship) { return relationship.child.get() == child && relationship.parent.get() == parent; @@ -1131,7 +1131,7 @@ TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { EXPECT_TRUE(has_relationship(topology.dependency, spectrum.get(), power.get())); EXPECT_TRUE(has_relationship(topology.display, frequency.get(), spectrum.get())); EXPECT_TRUE(has_relationship(topology.display, power.get(), spectrum.get())); - const auto paint_order = plot.paint_order_snapshot(); + const auto paint_order = plot.render_scene().paint_order_snapshot(); const auto spectrum_position = std::find_if(paint_order.begin(), paint_order.end(), [&spectrum](const auto& renderable) { return renderable.get() == spectrum.get(); }); @@ -1148,7 +1148,7 @@ TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { EXPECT_LT(spectrum_position, power_position); } TEST(Renderive_Core2, InteractionOverlayPaintsAboveItsAxes) { - Scene2D plot; + Plot_Scene plot; plot.init(); const auto root = plot.root_renderable(); const auto [horizontal, vertical, selection] = build_initial(plot, [&](auto& attach) { @@ -1162,7 +1162,7 @@ TEST(Renderive_Core2, InteractionOverlayPaintsAboveItsAxes) { return std::tuple{horizontal_axis, vertical_axis, value}; }); ASSERT_TRUE(selection); - const auto paint_order = plot.paint_order_snapshot(); + const auto paint_order = plot.render_scene().paint_order_snapshot(); const auto position = [&paint_order](const auto* target) { return std::find_if(paint_order.begin(), paint_order.end(), [target](const auto& renderable) { return renderable.get() == target; diff --git a/render_3D/CMakeLists.txt b/render_3D/CMakeLists.txt index 2885ef7..8b3b5b3 100644 --- a/render_3D/CMakeLists.txt +++ b/render_3D/CMakeLists.txt @@ -80,6 +80,7 @@ function(renderive_stage_render_3D_runtime target) endfunction() if (RENDERIVE_BUILD_TESTS) + set(Renderive_render_3D_test_targets) find_package(GTest CONFIG REQUIRED) endif () if (NOT TARGET Renderive_Kernel) @@ -126,5 +127,12 @@ if (RENDERIVE_BUILD_TESTS) endif () renderive_stage_render_3D_runtime("${Renderive_render_3D_test_target}") add_test(NAME "${Renderive_render_3D_test_target}" COMMAND "${Renderive_render_3D_test_target}") + set_tests_properties("${Renderive_render_3D_test_target}" PROPERTIES LABELS "Renderive_render_3D") + list(APPEND Renderive_render_3D_test_targets "${Renderive_render_3D_test_target}") endforeach () + add_custom_target(Renderive_render_3D_check + COMMAND "${CMAKE_CTEST_COMMAND}" --test-dir "${CMAKE_BINARY_DIR}" + -C "$" -L "^Renderive_render_3D$" --output-on-failure + DEPENDS ${Renderive_render_3D_test_targets} + USES_TERMINAL) endif () diff --git a/render_3D/render_3D/Point_Demo.cpp b/render_3D/render_3D/Point_Demo.cpp deleted file mode 100644 index 359424b..0000000 --- a/render_3D/render_3D/Point_Demo.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "Point_Demo.h" - -#include -#include -#include - -namespace renderive::render_3d { - -std::vector point_demo_data(float phase_radians) { - const float orbit_x = 0.72F * std::cos(phase_radians); - const float orbit_y = 0.44F * std::sin(phase_radians); - return { - {{-0.72F, -0.38F, 0.15F}, {245, 70, 78, 255}, 42.0F}, - {{0.00F, 0.48F, -0.05F}, {65, 220, 132, 255}, 48.0F}, - {{orbit_x, orbit_y, 0.30F}, {75, 135, 255, 255}, 54.0F}, - {{-0.35F, 0.10F, -0.45F}, {250, 205, 75, 255}, 30.0F}, - {{0.38F, -0.12F, -0.25F}, {205, 95, 245, 255}, 34.0F}, - }; -} - -Point_Demo make_point_demo(Scene_Options options) { - Point_State state; - state.style = {{12, 16, 24, 255}, 2.0F, Point_Aspect::Outline}; - auto visual = Point_Visual::Builder{state}.build(point_demo_data()); - auto scene = std::make_unique(options, visual); - return {std::move(visual), std::move(scene)}; -} - -} // namespace renderive::render_3d diff --git a/render_3D/render_3D/Point_Demo.h b/render_3D/render_3D/Point_Demo.h deleted file mode 100644 index cbff025..0000000 --- a/render_3D/render_3D/Point_Demo.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "Point_Scene.h" - -#include -#include - -namespace renderive::render_3d { - -struct Point_Demo { - std::shared_ptr visual; - std::unique_ptr scene; -}; - -[[nodiscard]] std::vector point_demo_data(float phase_radians = 0.0F); -[[nodiscard]] Point_Demo make_point_demo(Scene_Options options = {}); - -} // namespace renderive::render_3d diff --git a/render_3D/render_3D/Point_Scene.cpp b/render_3D/render_3D/Point_Scene.cpp index 3566545..07db557 100644 --- a/render_3D/render_3D/Point_Scene.cpp +++ b/render_3D/render_3D/Point_Scene.cpp @@ -1,4 +1,5 @@ #include "Point_Scene.h" +#include "Scene_Context.hpp" #include "detail/Datoviz_Visual_Backend.h" #include "detail/Gpu_Completion_Service.h" diff --git a/Kernel/src/renderive/scene/Scene3D_Context.hpp b/render_3D/render_3D/Scene_Context.hpp similarity index 99% rename from Kernel/src/renderive/scene/Scene3D_Context.hpp rename to render_3D/render_3D/Scene_Context.hpp index 11adb0b..b7f0287 100644 --- a/Kernel/src/renderive/scene/Scene3D_Context.hpp +++ b/render_3D/render_3D/Scene_Context.hpp @@ -6,7 +6,7 @@ #include "renderive/base/observer/Observer.hpp" #include "renderive/frame_control/Frame_Control.hpp" #include "renderive/state/Triple_State_Storage.hpp" -#include "base/Scene_Base.hpp" +#include "renderive/scene/base/Scene_Base.hpp" struct Scene3D_Frame_Data : Abstract_Frame {}; struct Scene3D_State { std::uint64_t revision{}; diff --git a/render_3D/tests/Datoviz_Visual_Family_Tests.cpp b/render_3D/tests/Datoviz_Visual_Family_Tests.cpp index 25b41c6..488f4f9 100644 --- a/render_3D/tests/Datoviz_Visual_Family_Tests.cpp +++ b/render_3D/tests/Datoviz_Visual_Family_Tests.cpp @@ -1,5 +1,6 @@ #include "render_3D/Datoviz_Visuals.h" +#include "render_3D/Scene_Context.hpp" #include #include diff --git a/render_3D/tests/Point_Render_Integration_Tests.cpp b/render_3D/tests/Point_Render_Integration_Tests.cpp index bc72a68..de59806 100644 --- a/render_3D/tests/Point_Render_Integration_Tests.cpp +++ b/render_3D/tests/Point_Render_Integration_Tests.cpp @@ -1,4 +1,4 @@ -#include "render_3D/Point_Demo.h" +#include "render_3D/Point_Scene.h" #include @@ -15,6 +15,26 @@ namespace renderive::render_3d { namespace { +struct Test_Point_Scene { + std::shared_ptr visual; + std::unique_ptr scene; +}; + +Test_Point_Scene make_test_scene(Scene_Options options) { + std::vector points{ + {{-0.72F, -0.38F, 0.15F}, {245, 70, 78, 255}, 42.0F}, + {{0.00F, 0.48F, -0.05F}, {65, 220, 132, 255}, 48.0F}, + {{0.72F, 0.0F, 0.30F}, {75, 135, 255, 255}, 54.0F}, + {{-0.35F, 0.10F, -0.45F}, {250, 205, 75, 255}, 30.0F}, + {{0.38F, -0.12F, -0.25F}, {205, 95, 245, 255}, 34.0F}, + }; + Point_State state; + state.style = {{12, 16, 24, 255}, 2.0F, Point_Aspect::Outline}; + auto visual = Point_Visual::Builder{state}.build(std::move(points)); + auto scene = std::make_unique(options, visual); + return {std::move(visual), std::move(scene)}; +} + struct Event_Point { float x{}; float y{}; @@ -38,7 +58,7 @@ std::size_t colored_pixel_count(const Pixel_Frame& frame) { TEST(PointRenderIntegration, RendersRealRgbaAndResizes) { try { - auto demo = make_point_demo(Scene_Options{.viewport = {320, 200}}); + auto demo = make_test_scene(Scene_Options{.viewport = {320, 200}}); ASSERT_TRUE(demo.scene->request_frame()); auto first = demo.scene->latest_frame(); ASSERT_NE(first, nullptr); @@ -60,7 +80,7 @@ TEST(PointRenderIntegration, RendersRealRgbaAndResizes) { TEST(PointRenderIntegration, KernelEventsReachDatovizArcballOnItsDomain) { try { - auto demo = make_point_demo(Scene_Options{.viewport = {320, 200}}); + auto demo = make_test_scene(Scene_Options{.viewport = {320, 200}}); ASSERT_TRUE(demo.scene->request_frame()); const auto before = demo.scene->latest_frame()->rgba8; @@ -106,7 +126,7 @@ TEST(PointRenderIntegration, KernelEventsReachDatovizArcballOnItsDomain) { TEST(PointRenderIntegration, CapturesExternalGpuLifetimeAndSeparatedBackendPhases) { try { - auto demo = make_point_demo(Scene_Options{.viewport = {320, 200}}); + auto demo = make_test_scene(Scene_Options{.viewport = {320, 200}}); auto& kernel_scene = demo.scene->render_scene(); const Capture_Session_Id capture = kernel_scene.capture_next_frame(); ASSERT_TRUE(demo.scene->request_frame()); diff --git a/render_3D/tests/Point_State_Tests.cpp b/render_3D/tests/Point_State_Tests.cpp index 6ec7c98..2446381 100644 --- a/render_3D/tests/Point_State_Tests.cpp +++ b/render_3D/tests/Point_State_Tests.cpp @@ -2,6 +2,7 @@ #include "render_3D/detail/Point_Core.h" #include +#include "render_3D/Scene_Context.hpp" #include #include diff --git a/todolist.md b/todolist.md new file mode 100644 index 0000000..71ceacb --- /dev/null +++ b/todolist.md @@ -0,0 +1,770 @@ +# Renderive 当前架构核实与改造 TODO + +## P0:External Operation 生命周期与失败语义 + +### TODO-01:核实 External Operation 是否存在永久 pending 风险 + +* [ ] 阅读 `Kernel/src/renderive/render_graph/External_Operation.hpp` +* [ ] 明确 `External_Operation_Source` 当前生命周期规则 +* [ ] 核实 Source 被析构但未调用 `complete()` / `fail()` 时,Operation 的最终状态 +* [ ] 编写测试:创建 Operation 后直接销毁 Source +* [ ] 验证 `Render_Graph_Runtime` 是否会永久停留在 `waiting_external` +* [ ] 验证 graph future / frame completion 是否会永久阻塞 +* [ ] 检查 Datoviz GPU completion 所有异常路径是否保证最终 complete/fail + +**核实目标:** + +确认是否存在: + +```text +producer 消失 + ↓ +External_Operation 永久 pending + ↓ +node 永久 waiting_external + ↓ +frame 永久无法完成 +``` + +**如果问题成立,再考虑改造:** + +* [ ] 为 External Operation 定义明确 terminal states + +```text +pending +completed +failed +cancelled / abandoned / broken +``` + +* [ ] Source 析构时处理未完成 Operation +* [ ] 明确 broken producer 应映射为: + + * failure + * cancellation + * abandoned +* [ ] 保证 completion callback 只执行一次 +* [ ] 保证 complete/fail/cancel 与析构竞态安全 +* [ ] 增加 double-complete / complete-after-fail 等测试 +* [ ] 增加 Runtime 等待 external 时 producer 异常退出测试 + +--- + +### TODO-02:核实 Render Graph failure 后是否可能等待无关 External Operation + +重点位置: + +```text +Render_Graph_Runtime.cpp +``` + +特别检查 graph 结束条件中: + +```text +failed +active_tasks +waiting_external +unfinished_nodes +``` + +之间的关系。 + +* [ ] 构造两个并行 async node:A、B +* [ ] A 主动 fail +* [ ] B 永久不回调 +* [ ] 验证整个 graph 是否永久不返回 +* [ ] 构造 A fail、B 正常延迟完成 +* [ ] 验证失败 frame 的 completion 时间是否被 B 强制拖长 +* [ ] 明确当前设计到底是: + + * fail-fast + * drain-before-fail + * cooperative cancellation + +**如果问题成立,再决定语义。** + +推荐先明确 contract,不要直接写代码: + +```text +方案 A:fail-fast +一个节点失败后,frame 尽快失败返回。 + +方案 B:drain +一个节点失败后,已启动外部操作仍全部收尾后再返回。 + +方案 C:cancel + drain +请求其他 operation cancellation, +无法取消的 operation 做后台资源回收, +但 frame logical completion 可以提前结束。 +``` + +* [ ] 根据 Renderive 的资源安全要求确定一种正式语义 +* [ ] 再增加对应 Runtime 状态机和测试 + +--- + +## P1:Render Plan 生命周期与重复编译 + +### TODO-03:核实 Render_Plan_History 是否长期无限增长 + +重点检查: + +```text +Render_Plan.cpp +Render_Plan_History +compile_render_plan() +``` + +当前重点假设: + +```text +只与 plans_.back() 比较 topology +``` + +可能出现: + +```text +Topology A +Topology B +Topology A +Topology B +... +``` + +导致历史: + +```text +A1 B2 A3 B4 A5 B6 ... +``` + +* [ ] 确认 `Render_Plan_History` 是否只 dedupe 相邻 topology +* [ ] 确认旧 Render Plan 是否存在必须永久保存的语义 +* [ ] 检查 Capture 是否持有具体 Plan +* [ ] 检查 Observation / Analysis 是否依赖历史 Plan 对象 +* [ ] 模拟 10 万帧 A/B topology 交替 +* [ ] 观察: + + * plan 数量 + * 内存占用 + * version 增长 +* [ ] 确认长期运行是否确实存在无界增长 + +**如果成立,再选择方案:** + +### 方案 A:Topology Interning + +* [ ] 为 topology 计算稳定 fingerprint/hash +* [ ] 相同 topology 复用已有 immutable Render Plan + +```text +topology fingerprint + ↓ +Render_Plan cache +``` + +### 方案 B:Bounded History + +* [ ] History 保留最近 N 个 plan +* [ ] Capture 对需要长期保存的 Plan 单独持有 shared_ptr + +### 方案 C:二者结合 + +* [ ] topology cache +* [ ] bounded observation history +* [ ] capture 独立 pin + +优先研究方案 C。 + +--- + +### TODO-04:核实 Render Plan 是否每帧重复编译执行拓扑 + +重点检查: + +```text +Scene_Base::compile_render_plan() +Render_Graph_Runtime constructor +``` + +核实每帧是否重复执行: + +```text +node id → index map +successor adjacency +indegree +topological sort +cycle validation +``` + +* [ ] 使用 profiler 测量 Runtime constructor 成本 +* [ ] 分别测试: + + * 10 nodes + * 50 nodes + * 100 nodes + * 500 nodes + * 1000 nodes +* [ ] 测量每帧: + + * Render Plan compile 时间 + * Runtime construction 时间 + * topology validation 时间 +* [ ] 与真实 node execution 时间比较 +* [ ] 判断这部分是否已经进入 critical path + +**只有测量证明值得优化时再改。** + +潜在目标结构: + +```text +Render_Plan +├─ Logical metadata +└─ Compiled Execution Layout + ├─ node execution index + ├─ predecessor count[] + ├─ successor offset[] + ├─ successors[] + └─ static topology metadata +``` + +每帧 Runtime 只生成: + +```text +remaining_predecessors[] +node_status[] +timestamps[] +external state +``` + +* [ ] 尽量避免每帧重新构造 immutable topology +* [ ] 保持 Render Plan 本身 immutable +* [ ] 不把 per-frame execution state 塞回 Render Plan +* [ ] benchmark 改造前后成本 + +--- + +## P1:调度模型规模化核实 + +### TODO-05:统计每个 Scene 实际线程成本 + +当前重点检查: + +```text +Scene_Base worker thread + +3D Point Scene: +Render_Domain thread +Gpu_Completion_Service thread + +Global: +Taskflow Executor workers +``` + +* [ ] 启动 1 个 3D Scene,记录线程数 +* [ ] 启动 10 个 Scene +* [ ] 启动 50 个 Scene +* [ ] 启动 100 个 Scene +* [ ] 测量: + + * OS thread 数量 + * context switches + * idle CPU + * memory/thread stack + * frame latency + * scheduler wait +* [ ] 明确 Renderive 目标部署规模: + + * 单桌面程序 1~4 Scene + * 多窗口 + * Web 多 session + * server-side rendering + +**如果目标只是少量本地 Scene:** + +* [ ] 暂不修改 + +**如果未来需要大量 Scene:** + +* [ ] 研究共享 Render Domain +* [ ] 研究共享 GPU Completion Service +* [ ] Render Domain 按 device/backend 而不是按 Scene 划分 +* [ ] 使用 serial executor / strand 保持 backend affinity + +目标可能演化为: + +```text +Global CPU Executor + +GPU Device 0 +├─ Shared Render Domain +│ ├─ Scene A +│ ├─ Scene B +│ └─ Scene C +│ +└─ Shared Completion Service + ├─ fence A + ├─ fence B + └─ fence C +``` + +**不要在没有多 Scene 压测数据之前重构这一项。** + +--- + +## P2:Frame Pipeline 能力边界确认 + +### TODO-06:确认当前 Scene 是否严格 single-frame-in-flight + +重点检查: + +```text +Scene_Base::submit_render() +Scene_Base::lock_render_idle() +``` + +* [ ] 验证 GPU external operation 未完成时,下一帧 CPU DAG 能否开始 +* [ ] 确认当前行为是否: + +```text +Frame N CPU + ↓ +Frame N GPU + ↓ +Frame N complete + ↓ +Frame N+1 CPU +``` + +而不是: + +```text +Frame N GPU + + +Frame N+1 CPU +``` + +* [ ] 测量这种设计对 interactive latency 的影响 +* [ ] 验证 low-latency 模式下 GPU queue 是否保持浅队列 +* [ ] 验证 frame age 是否优于多帧堆积方案 + +**当前阶段不要默认这是问题。** + +明确记录设计决策: + +```text +Interactive mode: +优先 latency +→ single-frame-in-flight 可以是正确策略 + +Throughput / offline mode: +优先 throughput +→ 未来可能需要 multi-frame-in-flight +``` + +* [ ] 只有出现离线渲染/视频/批处理需求时再设计跨帧流水线 + +--- + +## P2:Scene_Base 职责边界检查 + +### TODO-07:持续监控 Scene_Base 是否演化成 God Object + +盘点当前职责: + +```text +Renderable lifetime +Scene state +Snapshot +Dependency +Render plan compile +Frame submission +Worker control +Edit queue +Exception propagation +Observation +Capture integration +Frame control +Memory domain +``` + +* [ ] 给 Scene_Base 当前职责做一次正式列表 +* [ ] 检查每个职责是否属于 orchestration +* [ ] 新功能进入 Scene_Base 前必须判断能否成为独立 subsystem + +特别避免未来加入: + +```text +GPU fence logic +backend-specific synchronization +generic async continuation internals +capture storage internals +scheduler implementation internals +GPU profiler implementation +``` + +应继续保持: + +```text +Scene_Base + ↓ orchestrate + +Render_Graph_Runtime +External_Operation +Render_Domain +Gpu_Completion_Service +Capture subsystem +Observation subsystem +``` + +**当前不建议仅为了减小文件而拆类。** + +只有当职责本身形成独立生命周期/状态机时再拆。 + +--- + +## P2:Taskflow 依赖边界确认 + +### TODO-08:正式确认 Taskflow 只是 Executor,而不是架构语义的一部分 + +当前目标模型: + +```text +Render_Graph_Runtime + ↓ +CPU Scheduler Adapter + ↓ +tf::Executor::silent_async() +``` + +* [ ] 搜索所有 `tf::` 使用点 +* [ ] 检查 Renderable / Render Plan / External Operation 是否泄漏 Taskflow 类型 +* [ ] 检查 Render_Graph_Runtime 是否依赖 Taskflow DAG API +* [ ] 确认 Taskflow 只承担 CPU function dispatch +* [ ] 做一个最小 mock executor 测试 + +例如: + +```cpp +scheduler([] { + ... +}); +``` + +替换成测试 executor 后 Runtime 仍正常运行。 + +* [ ] 如果成立,记录架构结论: + +```text +Taskflow is an implementation detail of CPU execution, +not Renderive's task graph model. +``` + +**如果已经成立,不需要再额外设计 Scheduler Concept。** + +先保持最小 callable abstraction 即可。 + +--- + +## P2:Datoviz Async 接入完整性核实 + +### TODO-09:逐条核实 GPU completion 所有路径都完成 External Operation + +检查: + +```text +Point_Scene +Render_Domain +Gpu_Completion_Service +Datoviz backend +``` + +覆盖以下路径: + +* [ ] 正常 submit +* [ ] submit 失败 +* [ ] fence 创建失败 +* [ ] fence wait 失败 +* [ ] device lost +* [ ] Render_Domain callback 抛异常 +* [ ] backend.collect() 抛异常 +* [ ] Scene 被销毁 +* [ ] application shutdown +* [ ] completion service shutdown +* [ ] frame 被逻辑取消 +* [ ] GPU operation 已提交但 Scene 生命周期结束 + +每条路径都验证最终发生: + +```text +complete +或 +fail/cancel +``` + +不能: + +```text +silent disappearance +→ External Operation 永久 pending +``` + +--- + +## P2:CPU / GPU 观测数据可信度核实 + +### TODO-10:验证节点时间戳语义 + +针对: + +```text +ready_time +start_time +cpu_end_time +external_start_time +external_end_time +end_time +``` + +逐项明确语义。 + +* [ ] `ready_time` 是否表示所有 dependency 已满足 +* [ ] `start_time - ready_time` 是否能准确代表 scheduler wait +* [ ] `cpu_end_time - start_time` 是否代表 CPU node execution +* [ ] external_start 是否发生在 CPU submit 完成附近 +* [ ] external_end 是否由真正的 external completion 驱动 +* [ ] node end 是否与 dependency unlock 同一语义点 +* [ ] worker_id 是否只表示 CPU execution worker +* [ ] external completion thread 是否不会错误覆盖 worker_id + +--- + +### TODO-11:验证 Critical Path 算法 + +* [ ] 手工构造简单 DAG + +```text +A 10ms ── C 5ms +B 20ms ──┘ +``` + +期望 critical path: + +```text +B → C = 25ms +``` + +* [ ] 增加 external node +* [ ] 增加 scheduler wait +* [ ] 明确 critical path 是否统计: + + * CPU execution + * external wait + * scheduler wait + * dependency wait +* [ ] 明确算法计算的是: + + * DAG theoretical execution critical path + * 还是 observed wall-clock critical path + +两者不能混淆。 + +--- + +### TODO-12:验证 GPU timing 与 fence wait 的区别 + +确保以下数据不会混用: + +```text +GPU timestamp duration +≠ +CPU fence waiting duration +``` + +* [ ] GPU render timestamp +* [ ] transition timestamp +* [ ] copy timestamp +* [ ] total GPU timestamp +* [ ] CPU submit duration +* [ ] completion service fence wait duration +* [ ] readback duration + +输出报告时必须保持这些概念分离。 + +--- + +## P3:Low Latency / Frame Control 回归测试 + +### TODO-13:正式锁定 latest-frame-wins 的行为语义 + +* [ ] 测试 pending frame 被替换 +* [ ] 测试 rendering frame 不会被中断 +* [ ] 测试事件修改模型后,新 snapshot 能看到状态 +* [ ] 测试被替换 frame 不会导致权威模型状态丢失 +* [ ] 测试 burst input: + + * mouse move + * resize + * camera control + * rapid data update +* [ ] 记录: + + * produced frames + * rendered frames + * dropped/replaced frames + * input-to-present latency + +明确: + +```text +丢的是待展示/待处理 frame +不是 authoritative scene state +``` + +--- + +## P3:Runtime 压测 + +### TODO-14:建立标准 DAG benchmark + +固定测试: + +```text +10 nodes +50 nodes +100 nodes +500 nodes +1000 nodes +``` + +分别包含: + +```text +完全串行 +完全并行 +树形 +diamond +随机 DAG +CPU + External 混合 DAG +``` + +记录: + +* [ ] graph compile time +* [ ] runtime setup time +* [ ] scheduler wait +* [ ] total CPU work +* [ ] wall time +* [ ] parallelism +* [ ] Taskflow worker utilization +* [ ] Runtime lock contention +* [ ] completion callback cost + +后续任何 scheduler 改造都与此 benchmark 对比。 + +--- + +### TODO-15:建立 Renderive 实际 workload benchmark + +不要只做 synthetic benchmark。 + +至少保留: + +* [ ] 纯 2D 场景 +* [ ] 小型 3D point scene +* [ ] 大型 3D point scene +* [ ] 多 Renderable dependency +* [ ] frequent update +* [ ] low-latency input +* [ ] GPU-heavy +* [ ] CPU-heavy +* [ ] mixed CPU/GPU + +每次架构改造比较: + +```text +P50 +P95 +P99 + +critical path +scheduler wait +CPU work +GPU duration +frame age +memory +thread count +``` + +--- + +# 暂时不要做的事情 + +下面这些目前没有证据证明值得做: + +* [ ] **不要**再包一层复杂 Taskflow abstraction +* [ ] **不要**给 `Scene_Render_Context` 增加 continuation +* [ ] **不要**为了异步统一而全面 coroutine 化 +* [ ] **不要**现在实现 multi-frame-in-flight +* [ ] **不要**仅为了减少线程数重写整个 Scene scheduler +* [ ] **不要**仅为了“架构漂亮”拆 Scene_Base +* [ ] **不要**重复实现 Taskflow 自己已经能完成的线程池能力 +* [ ] **不要**在没有 profiler 数据时优化 DAG 微小调度开销 + +--- + +# 建议执行顺序 + +```text +第一阶段:正确性 + TODO-01 External Operation 生命周期 + TODO-02 failure / cancellation + TODO-09 Datoviz 异常路径 + +第二阶段:长期运行稳定性 + TODO-03 Render Plan History + TODO-10/11/12 观测数据可信度 + +第三阶段:CPU Runtime 成本 + TODO-04 Render Plan compiled layout + TODO-14 synthetic benchmark + TODO-15 real workload benchmark + +第四阶段:规模化 + TODO-05 多 Scene thread model + TODO-06 multi-frame-in-flight 是否真的需要 + +持续性约束 + TODO-07 Scene_Base 边界 + TODO-08 Taskflow implementation-detail 边界 + TODO-13 Low Latency 回归测试 +``` + +## 最终原则 + +每一项都严格走: + +```text +提出怀疑 + ↓ +写最小复现 / benchmark + ↓ +确认问题是否真实存在 + ↓ +量化严重程度 + ↓ +定义行为 contract + ↓ +再修改实现 + ↓ +回归测试 + benchmark +``` + +不要变成: + +```text +看到一个理论缺陷 + ↓ +立刻重新设计架构 +``` + +现阶段 Renderive 更需要的是**验证和收敛**,而不是继续扩张调度抽象。 diff --git a/web_server/CMakeLists.txt b/web_server/CMakeLists.txt index 3c16162..9fcea37 100644 --- a/web_server/CMakeLists.txt +++ b/web_server/CMakeLists.txt @@ -1,5 +1,6 @@ set(Renderive_Web_dependencies global::drogon global::spdlog global::magic_enum) if (RENDERIVE_BUILD_TESTS) + set(Renderive_Web_test_targets) list(APPEND Renderive_Web_dependencies global::GTest) endif () rcl_add_dependency_action_targets(Renderive_Web_env ${Renderive_Web_dependencies}) @@ -207,5 +208,12 @@ if (RENDERIVE_BUILD_TESTS) target_link_libraries("${Renderive_Web_test_target}" PRIVATE Renderive_Web Adminive::Nlohmann GTest::gtest_main) renderive_stage_render_3D_runtime("${Renderive_Web_test_target}") add_test(NAME "${Renderive_Web_test_target}" COMMAND "${Renderive_Web_test_target}") + set_tests_properties("${Renderive_Web_test_target}" PROPERTIES LABELS "Renderive_Web") + list(APPEND Renderive_Web_test_targets "${Renderive_Web_test_target}") endforeach () + add_custom_target(Renderive_Web_check + COMMAND "${CMAKE_CTEST_COMMAND}" --test-dir "${CMAKE_BINARY_DIR}" + -C "$" -L "^Renderive_Web$" --output-on-failure + DEPENDS ${Renderive_Web_test_targets} + USES_TERMINAL) endif () diff --git a/web_server/app/Gallery_Actions.h b/web_server/app/Gallery_Actions.h index a1c42ab..2dd9bb4 100644 --- a/web_server/app/Gallery_Actions.h +++ b/web_server/app/Gallery_Actions.h @@ -1,7 +1,7 @@ #pragma once #include "common/Gallery_Types.h" -#include "renderive/scene/Scene2D_Context.hpp" +#include "render_2D/scene/Scene_Context.hpp" #include #include @@ -83,19 +83,19 @@ struct Type_Descriptor> { using renderive::web::gallery_action; return object(type_metadata( gallery_action("mode_prepare", "准备帧", - "Manual_Refresh_Strategy::acquire_painter / Basic_Scene2D::prepare_frame", + "Manual_Refresh_Strategy::acquire_painter / Plot_Scene::prepare_frame", "Manual_Refresh_Strategy"), gallery_action("mode_refresh", "提交手动刷新", - "Manual_Refresh_Strategy::refresh / Basic_Scene2D::refresh_manual_frame", + "Manual_Refresh_Strategy::refresh / Plot_Scene::refresh_manual_frame", "Manual_Refresh_Strategy"), gallery_action("mode_render", "渲染已刷新帧", - "Manual_Refresh_Strategy::acquire_renderer / Basic_Scene2D::render_prepared_frame", + "Manual_Refresh_Strategy::acquire_renderer / Plot_Scene::render_prepared_frame", "Manual_Refresh_Strategy", {}, {}, {}, 0.0, true), gallery_action("mode_discard", "丢弃待刷新帧", - "Basic_Scene2D::discard_pending_frame", + "Plot_Scene::discard_pending_frame", "Manual_Refresh_Strategy"), gallery_action("mode_cycle", "执行完整手动帧周期", - "Basic_Scene2D::render_frame", "Manual_Refresh_Strategy", + "Plot_Scene::render_frame", "Manual_Refresh_Strategy", {}, {}, {}, 0.0, true))); } }; @@ -107,15 +107,15 @@ struct Type_Descriptor> { using renderive::web::gallery_action; return object(type_metadata( gallery_action("mode_prepare", "发布低延迟帧", - "Low_Latency_Strategy::acquire_painter / Basic_Scene2D::prepare_frame", + "Low_Latency_Strategy::acquire_painter / Plot_Scene::prepare_frame", "Low_Latency_Strategy"), gallery_action("mode_render", "消费最新帧", - "Low_Latency_Strategy::acquire_renderer / Basic_Scene2D::render_prepared_frame", + "Low_Latency_Strategy::acquire_renderer / Plot_Scene::render_prepared_frame", "Low_Latency_Strategy", {}, {}, {}, 0.0, true), gallery_action("mode_discard", "丢弃陈旧帧", - "Basic_Scene2D::discard_pending_frame", "Low_Latency_Strategy"), + "Plot_Scene::discard_pending_frame", "Low_Latency_Strategy"), gallery_action("mode_cycle", "执行低延迟帧周期", - "Basic_Scene2D::render_frame", "Low_Latency_Strategy", + "Plot_Scene::render_frame", "Low_Latency_Strategy", {}, {}, {}, 0.0, true))); } }; @@ -127,16 +127,16 @@ struct Type_Descriptor> { using renderive::web::gallery_action; return object(type_metadata( gallery_action("mode_enqueue", "压入一帧", - "Flow_Refresh_Strategy::acquire_painter / Basic_Scene2D::prepare_frame", + "Flow_Refresh_Strategy::acquire_painter / Plot_Scene::prepare_frame", "Flow_Refresh_Strategy"), gallery_action("mode_enqueue_burst", "批量压入回放队列", - "Flow_Refresh_Strategy::acquire_painter / Basic_Scene2D::prepare_frame x N", + "Flow_Refresh_Strategy::acquire_painter / Plot_Scene::prepare_frame x N", "Flow_Refresh_Strategy", {}, "number", "帧数", 8.0), gallery_action("mode_dequeue", "消费队首帧", - "Flow_Refresh_Strategy::acquire_renderer / Basic_Scene2D::render_prepared_frame", + "Flow_Refresh_Strategy::acquire_renderer / Plot_Scene::render_prepared_frame", "Flow_Refresh_Strategy", {}, {}, {}, 0.0, true), gallery_action("mode_cycle", "执行回放帧周期", - "Basic_Scene2D::render_frame", "Flow_Refresh_Strategy", + "Plot_Scene::render_frame", "Flow_Refresh_Strategy", {}, {}, {}, 0.0, true))); } }; diff --git a/web_server/app/Gallery_Observer_Adminive.h b/web_server/app/Gallery_Observer_Adminive.h index fdde95c..b6d654c 100644 --- a/web_server/app/Gallery_Observer_Adminive.h +++ b/web_server/app/Gallery_Observer_Adminive.h @@ -2,7 +2,7 @@ #include "Gallery_Enum.h" #include "common/Gallery_Performance_Types.h" -#include "render_2D/scene/Scene.h" +#include "render_2D/scene/Plot_Scene.h" #include "render_2D/renderable/Renderable.h" #include "adminive/adminive.hpp" @@ -66,15 +66,15 @@ struct Gallery_Render_Performance { namespace adminive { template -struct Value_Adapter { +struct Value_Adapter { using value_type = std::string; - static std::string read(renderive::Frame_Control_Mode value) { + static std::string read(renderive::Plot_Frame_Mode value) { return renderive::web::gallery_enum_id(value); } - static void write(renderive::Frame_Control_Mode& target, std::string value) { + static void write(renderive::Plot_Frame_Mode& target, std::string value) { const auto parsed = - renderive::web::gallery_enum_cast(value); + renderive::web::gallery_enum_cast(value); if (!parsed) throw std::invalid_argument("unknown frame control mode: " + value); target = *parsed; @@ -82,14 +82,14 @@ struct Value_Adapter { }; template <> -struct Type_Descriptor { +struct Type_Descriptor { static auto get() { - using T = renderive::Frame_Observer_Snapshot; + using T = renderive::Plot_Frame_Status; return object("kernel_observer", ADMINIVE_FIELD_LABEL(T, mode, "模式") - .enum_label("手动刷新") - .enum_label("低延迟") - .enum_label("回放队列"), + .enum_label("手动刷新") + .enum_label("低延迟") + .enum_label("回放队列"), ADMINIVE_FIELD_LABEL(T, last_event, "最近事件"), ADMINIVE_FIELD_LABEL(T, limit_state, "当前瓶颈"), ADMINIVE_FIELD_LABEL(T, frequency_hz, "配置频率"), @@ -229,7 +229,7 @@ struct Type_Descriptor { } }; -static_assert(Described_Type); +static_assert(Described_Type); static_assert(Described_Type); static_assert(Described_Type); static_assert(Described_Type); diff --git a/web_server/app/Gallery_Protocol.cpp b/web_server/app/Gallery_Protocol.cpp index 344a1f0..a81c10b 100644 --- a/web_server/app/Gallery_Protocol.cpp +++ b/web_server/app/Gallery_Protocol.cpp @@ -85,13 +85,13 @@ void append_session_actions(Gallery_Frame_Mode frame_mode, std::vector& result) { switch (frame_mode) { case Gallery_Frame_Mode::Manual: - append_gallery_actions>(result); + append_gallery_actions>(result); return; case Gallery_Frame_Mode::Low_Latency: - append_gallery_actions>(result); + append_gallery_actions>(result); return; case Gallery_Frame_Mode::Playback: - append_gallery_actions>(result); + append_gallery_actions>(result); return; } throw std::invalid_argument("unknown gallery frame mode"); @@ -101,11 +101,11 @@ std::size_t session_control_count(std::string_view case_id, Gallery_Frame_Mode f return 0; switch (frame_mode) { case Gallery_Frame_Mode::Manual: - return gallery_control_count>(); + return gallery_control_count>(); case Gallery_Frame_Mode::Low_Latency: - return gallery_control_count>(); + return gallery_control_count>(); case Gallery_Frame_Mode::Playback: - return gallery_control_count>(); + return gallery_control_count>(); } throw std::invalid_argument("unknown gallery frame mode"); } @@ -298,16 +298,16 @@ bool observer_detail_field(std::string_view name) { template Json observer_fields(Predicate&& predicate) { const Json descriptor = - adminive::to_descriptor_json(); + adminive::to_descriptor_json(); Json result = Json::array(); for (const Json& field : descriptor.at("fields")) { const std::string name = field.at("name"); if (predicate(name)) { - Json field = described_dashboard_field( + Json field = described_dashboard_field( "kernel_observer", name); if (name == "frequency_hz") { field["enabled_source"] = - described_source( + described_source( "kernel_observer", "frequency_limit_enabled"); field["disabled_label"] = "已关闭"; } else if (name == "end_to_end_ns") { @@ -345,17 +345,17 @@ Json dashboard_contract() { Json point_pair{{"label", "输入→绘制"}, {"format", "pair"}, {"sources", Json::array({"data_shape.input_points", "data_shape.rendered_elements"})}, {"separator", "→"}}; - Json bottleneck = described_dashboard_field( + Json bottleneck = described_dashboard_field( "kernel_observer", "limit_state"); bottleneck["format"] = "duration_enum"; bottleneck["duration_sources"] = { - {"frequency_limited", described_source( + {"frequency_limited", described_source( "kernel_observer", "target_interval_ns")}, - {"paint_limited", described_source( + {"paint_limited", described_source( "kernel_observer", "paint_duration_ns")}, - {"render_limited", described_source( + {"render_limited", described_source( "kernel_observer", "render_duration_ns")}, - {"consumer_limited", described_source( + {"consumer_limited", described_source( "kernel_observer", "consumer_interval_ns")} }; Json dashboard{ @@ -392,12 +392,12 @@ Json dashboard_contract() { dashboard_field("呈现 P95 ms", "client_performance.display_interval_p95_ms", "fixed", 2), dashboard_field("呈现 P99 ms", "client_performance.display_interval_p99_ms", "fixed", 2), dashboard_field("响应负载 MB/s", "performance.pixel_payload_megabytes_per_second", "fixed", 1), - described_dashboard_field( + described_dashboard_field( "kernel_observer", "pending_frame_count"), - described_dashboard_field( + described_dashboard_field( "kernel_observer", "dropped_frame_count"), std::move(point_pair), std::move(bottleneck), - described_dashboard_field( + described_dashboard_field( "kernel_observer", "last_event"), dashboard_field("像素超时", "client_performance.frame_request_timeout_count"), dashboard_field("最近像素龄", "client_performance.last_pixel_receive_age_ms", "milliseconds", 0) @@ -409,7 +409,7 @@ Json dashboard_contract() { {"title", "内核帧观察器"}, {"source", "kernel_observer"}, {"descriptor", adminive::to_descriptor_json< - Json, renderive::Frame_Observer_Snapshot>()} + Json, renderive::Plot_Frame_Status>()} }}, {"renderables_source", "renderable_observers"}, {"renderable_fields", Json::array({ @@ -439,26 +439,26 @@ Json dashboard_contract() { }}, {"limits", { {"aria_label", "低延迟限速来源"}, {"title", "限速来源"}, - {"current_source", described_source( + {"current_source", described_source( "kernel_observer", "limit_state")}, {"active_label", "当前瓶颈"}, {"inactive_label", "未受限"}, {"disabled_label", "已关闭"}, {"fields", Json::array({ {{"label", "内核用户频率"}, {"active_value", "frequency_limited"}, - {"duration_source", described_source( + {"duration_source", described_source( "kernel_observer", "target_interval_ns")}, - {"enabled_source", described_source( + {"enabled_source", described_source( "kernel_observer", "frequency_limit_enabled")}}, {{"label", "内核绘制事件"}, {"active_value", "paint_limited"}, - {"duration_source", described_source( + {"duration_source", described_source( "kernel_observer", "paint_duration_ns")}}, {{"label", "内核后台渲染"}, {"active_value", "render_limited"}, - {"duration_source", described_source( + {"duration_source", described_source( "kernel_observer", "render_duration_ns")}}, {{"label", "消费者反馈"}, {"active_value", "consumer_limited"}, - {"duration_source", described_source( + {"duration_source", described_source( "kernel_observer", "consumer_interval_ns")}, - {"enabled_source", described_source( + {"enabled_source", described_source( "kernel_observer", "consumer_feedback_enabled")}} })} }}, @@ -466,11 +466,11 @@ Json dashboard_contract() { {"aria_label", "内核低延迟全量统计"}, {"header", { {"prefix", "内核"}, {"suffix", "观察器"}, {"event_label", "事件"}, - {"mode", described_dashboard_field( + {"mode", described_dashboard_field( "kernel_observer", "mode")}, - {"limit", described_dashboard_field( + {"limit", described_dashboard_field( "kernel_observer", "limit_state")}, - {"event", described_dashboard_field( + {"event", described_dashboard_field( "kernel_observer", "last_event")} }}, {"sections", Json::array({ @@ -507,7 +507,7 @@ Json dashboard_contract() { }} }; dashboard["observer"]["descriptor"] = - adminive::to_descriptor_json(); + adminive::to_descriptor_json(); dashboard["observer"]["consumer_feedback_descriptor"] = adminive::to_descriptor_json(); return dashboard; diff --git a/web_server/app/Gallery_Session_Control.h b/web_server/app/Gallery_Session_Control.h index e79164b..859f07f 100644 --- a/web_server/app/Gallery_Session_Control.h +++ b/web_server/app/Gallery_Session_Control.h @@ -1,7 +1,7 @@ #pragma once #include "render_2D/plottable/Performance_Overlay.h" #include "render_2D/renderable/Renderable.h" -#include "render_2D/scene/Scene.h" +#include "render_2D/scene/Plot_Scene.h" #include #include namespace adminive { diff --git a/web_server/app/Gallery_Session_Control_Adminive.h b/web_server/app/Gallery_Session_Control_Adminive.h index 0dc61e9..343d095 100644 --- a/web_server/app/Gallery_Session_Control_Adminive.h +++ b/web_server/app/Gallery_Session_Control_Adminive.h @@ -43,7 +43,7 @@ struct Type_Descriptor> { static auto get() { using T = renderive::web::Gallery_Session_Control; using namespace renderive::web::gallery_adminive; - const auto dirty = [](T& value) { value.scene_.notify_model_dirty(); }; + const auto dirty = [](T& value) { value.scene_.request_redraw(); }; return adminive::object("scene", "场景", callback_property( "background_color", "背景颜色", Field_Control::color, diff --git a/web_server/app/Web_Plot_Session.cpp b/web_server/app/Web_Plot_Session.cpp index 0830e0f..88a22ab 100644 --- a/web_server/app/Web_Plot_Session.cpp +++ b/web_server/app/Web_Plot_Session.cpp @@ -49,7 +49,7 @@ double gaussian(double x, double center, double width) { } } // namespace struct Web_Plot_Session::Impl { - Scene2D plot; + Plot_Scene plot; renderive_Owner spectrum_frequency_axis; renderive_Owner spectrum_power_axis; renderive_Owner waterfall_frequency_axis; @@ -306,7 +306,7 @@ struct Web_Plot_Session::Impl { } else if constexpr (std::is_same_v) { mode = value.mode; - plot.notify_model_dirty(); + plot.request_redraw(); } else if constexpr (std::is_same_v) { set_center_frequency(value.megahertz); @@ -316,7 +316,7 @@ struct Web_Plot_Session::Impl { } else if constexpr (std::is_same_v) { gain_db = value.decibels; - plot.notify_model_dirty(); + plot.request_redraw(); } else if constexpr (std::is_same_v) { spectrum->set<&Spectrum::Properties::max_hold_visible>(value.enabled); diff --git a/web_server/app/render_2D/Gallery_Scene2D.cpp b/web_server/app/render_2D/Gallery_Scene2D.cpp index e7eddd0..422bb66 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.cpp +++ b/web_server/app/render_2D/Gallery_Scene2D.cpp @@ -143,12 +143,24 @@ std::optional action_number(const Gallery_Action_Request& request) { return std::nullopt; return std::get(*request.argument); } + +Plot_Frame_Mode plot_frame_mode(Gallery_Frame_Mode mode) { + switch (mode) { + case Gallery_Frame_Mode::Manual: + return Plot_Frame_Mode::Manual; + case Gallery_Frame_Mode::Low_Latency: + return Plot_Frame_Mode::Low_Latency; + case Gallery_Frame_Mode::Playback: + return Plot_Frame_Mode::Playback; + } + throw std::invalid_argument("unknown gallery frame mode"); +} } // namespace -template class Gallery_Scene final : public Gallery_Scene_Interface { public: Gallery_Scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode frame_mode, bool automatic_low_latency) - : session_id_(session_id), case_id_(std::move(case_id)), frame_mode_(frame_mode), automatic_low_latency_(automatic_low_latency), performance_started_(std::chrono::steady_clock::now()) { + : session_id_(session_id), case_id_(std::move(case_id)), frame_mode_(frame_mode), automatic_low_latency_(automatic_low_latency), + plot_(Plot_Scene_Options{plot_frame_mode(frame_mode)}), performance_started_(std::chrono::steady_clock::now()) { plot_.init(); plot_.set_viewport_size({560, 320}); root_ = plot_.root_renderable(); @@ -172,7 +184,7 @@ public: } attach_performance_overlay(plot_); apply_layout(); - session_control_ = std::make_unique>( + session_control_ = std::make_unique>( plot_, *primary_, feedback_policy_); register_controls(); set_performance_plot_name(plot_, case_id_ + "/" + gallery_enum_id(frame_mode_)); @@ -193,9 +205,9 @@ public: [[nodiscard]] nlohmann::json controls() const { return { {"resources", controls_.resources()}, - {"observers", controls_.observers(plot_)}, - {"render_plan", controls_.render_plan(plot_)}, - {"performance_capture", gallery_performance_capture_json(plot_)} + {"observers", controls_.observers(plot_.render_scene())}, + {"render_plan", controls_.render_plan(plot_.render_scene())}, + {"performance_capture", gallery_performance_capture_json(plot_.render_scene())} }; } [[nodiscard]] Gallery_Frame_Mode frame_mode() const noexcept { @@ -262,7 +274,7 @@ public: } apply_layout(); update_model(); - plot_.notify_model_dirty(); + plot_.request_redraw(); return result; } void resize(int width, int height) override { @@ -351,10 +363,10 @@ public: last_action_result_.clear(); recognized = true; if (request.id == "capture_next_frame") { - const auto state = plot_.capture_state(); + const auto state = plot_.render_scene().capture_state(); if (state.enabled()) return "A performance capture session is already active"; - const auto session_id = plot_.capture_next_frame(); + const auto session_id = plot_.render_scene().capture_next_frame(); last_action_result_ = "capture session=" + std::to_string(session_id); return "Capture next render frame requested"; } @@ -364,10 +376,10 @@ public: return invalid_argument(recognized); const std::size_t count = static_cast( std::clamp(std::llround(*argument), 1LL, 1000LL)); - const auto state = plot_.capture_state(); + const auto state = plot_.render_scene().capture_state(); if (state.enabled()) return "A performance capture session is already active"; - const auto session_id = plot_.capture_frames(count); + const auto session_id = plot_.render_scene().capture_frames(count); last_action_result_ = "capture session=" + std::to_string(session_id) + ", frames=" + std::to_string(count); return "Consecutive render frame capture requested"; @@ -557,7 +569,7 @@ public: last_action_result_ = notice; } [[nodiscard]] std::string telemetry_json() const { - const auto observer = plot_.frame_observer_snapshot(); + const auto observer = plot_.frame_status(); const auto telemetry_now = std::chrono::steady_clock::now(); const double elapsed_seconds = std::max( std::chrono::duration(telemetry_now - performance_started_).count(), @@ -641,8 +653,8 @@ public: {"kernel_observer", std::move(observer_data)}, {"consumer_feedback", std::move(consumer_feedback_data)}, {"client_performance", std::move(client_performance_data)}, - {"renderable_observers", controls_.observers(plot_)}, - {"performance_capture", gallery_performance_capture_json(plot_)}, + {"renderable_observers", controls_.observers(plot_.render_scene())}, + {"performance_capture", gallery_performance_capture_json(plot_.render_scene())}, {"last_action_result", last_action_result_} }; if (primary_) { @@ -925,7 +937,7 @@ private: if (now - last_performance_log_ < std::chrono::seconds(1)) return; last_performance_log_ = now; - const auto observer = plot_.frame_observer_snapshot(); + const auto observer = plot_.frame_status(); const auto unix_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); @@ -1344,9 +1356,9 @@ private: std::string case_id_; Gallery_Frame_Mode frame_mode_ = Gallery_Frame_Mode::Low_Latency; bool automatic_low_latency_{}; - Scene_Type plot_; + Plot_Scene plot_; Gallery_Feedback_Policy feedback_policy_; - std::unique_ptr> session_control_; + std::unique_ptr> session_control_; Gallery_Controls controls_; std::chrono::steady_clock::time_point performance_started_; std::chrono::steady_clock::time_point last_performance_log_; @@ -1389,14 +1401,7 @@ private: std::string last_action_result_; }; std::unique_ptr make_gallery_scene_2d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) { - switch (mode) { - case Gallery_Frame_Mode::Manual: - return std::make_unique>(session_id, std::move(case_id), mode, automatic_low_latency); - case Gallery_Frame_Mode::Low_Latency: - return std::make_unique>(session_id, std::move(case_id), mode, automatic_low_latency); - case Gallery_Frame_Mode::Playback: - return std::make_unique>(session_id, std::move(case_id), mode, automatic_low_latency); - } - throw std::invalid_argument("unknown gallery frame mode"); + return std::make_unique(session_id, std::move(case_id), + mode, automatic_low_latency); } } diff --git a/web_server/app/render_3D/Gallery_Scene3D.cpp b/web_server/app/render_3D/Gallery_Scene3D.cpp index 540b270..04df948 100644 --- a/web_server/app/render_3D/Gallery_Scene3D.cpp +++ b/web_server/app/render_3D/Gallery_Scene3D.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -219,6 +220,9 @@ public: successful_render_count_ = 0; pixel_frame_count_ = 0; last_render_ms_ = 0.0; + last_pixel_encode_ms_ = 0.0; + render_history_.clear(); + pixel_history_.clear(); last_event_ = "monitoring_reset"; } @@ -325,6 +329,7 @@ public: last_pixel_encode_ms_ = std::chrono::duration( encode_finished - encode_started) .count(); + record_sample(pixel_history_, encode_finished); } [[nodiscard]] std::string action(const Gallery_Action_Request& request, @@ -433,19 +438,21 @@ public: [[nodiscard]] std::string telemetry_json() const override { const auto status = scene_.frame_status(); + const auto now = std::chrono::steady_clock::now(); return nlohmann::json{ {"kernel_observer", {{"mode", mode_id(frame_mode_)}, {"last_event", last_event_}, {"pending_frame_count", status.pending_frame_count}, {"dropped_frame_count", status.dropped_frame_count}}}, - {"render_performance", + {"performance", {{"render_attempt_count", render_attempt_count_}, {"successful_render_count", successful_render_count_}, {"failed_render_count", render_attempt_count_ - successful_render_count_}, + {"measured_fps", recent_rate(render_history_, now)}, {"last_render_ms", last_render_ms_}, - {"pixel_response_fps", 0.0}, + {"pixel_response_fps", recent_rate(pixel_history_, now)}, {"last_pixel_encode_ms", last_pixel_encode_ms_}, {"last_pixel_bytes", last_pixel_bytes_}, {"automatic_low_latency_scheduler", can_render_automatically()}}}, @@ -462,6 +469,31 @@ public: } private: + using Performance_Clock = std::chrono::steady_clock; + + static void record_sample(std::deque& samples, + Performance_Clock::time_point now) { + samples.push_back(now); + const auto cutoff = now - std::chrono::seconds(2); + while (!samples.empty() && samples.front() < cutoff) + samples.pop_front(); + } + + static double recent_rate( + const std::deque& samples, + Performance_Clock::time_point now) { + const auto cutoff = now - std::chrono::seconds(1); + const auto first = std::find_if(samples.begin(), samples.end(), + [cutoff](auto sample) { + return sample >= cutoff; + }); + const auto count = std::distance(first, samples.end()); + if (count < 2) + return 0.0; + const double seconds = std::chrono::duration(samples.back() - *first).count(); + return seconds > 0.0 ? static_cast(count - 1) / seconds : 0.0; + } + static std::vector initial_points() { return {{{-0.62F, -0.10F, 0.0F}, {255, 32, 24, 255}, 74.0F}, {{0.0F, -0.10F, 0.0F}, {24, 255, 36, 255}, 74.0F}, @@ -513,6 +545,8 @@ private: std::chrono::steady_clock::now() - started) .count(); successful_render_count_ += rendered ? 1U : 0U; + if (rendered) + record_sample(render_history_, std::chrono::steady_clock::now()); return rendered; } @@ -524,6 +558,8 @@ private: std::chrono::steady_clock::now() - started) .count(); successful_render_count_ += rendered ? 1U : 0U; + if (rendered) + record_sample(render_history_, std::chrono::steady_clock::now()); return rendered; } @@ -546,6 +582,8 @@ private: double last_render_ms_{}; double last_pixel_encode_ms_{}; std::size_t last_pixel_bytes_{}; + std::deque render_history_; + std::deque pixel_history_; std::string last_event_{"initialized"}; std::string last_action_result_; }; diff --git a/web_server/tests/Datoviz_Gallery_Tests.cpp b/web_server/tests/Datoviz_Gallery_Tests.cpp index 2f20695..47c8b89 100644 --- a/web_server/tests/Datoviz_Gallery_Tests.cpp +++ b/web_server/tests/Datoviz_Gallery_Tests.cpp @@ -236,6 +236,15 @@ TEST(RenderiveWebDatovizGallery, PublishesRealRgbaAndRendersAfterResize) { ASSERT_EQ(invoke_action(session, "mode_render").at("type"), "case_state"); const std::string resized = request_pixels(session); expect_actual_datoviz_pixels(resized, 900, 500); + + const auto observed = response_json(session.handle(gallery_request( + Gallery_Request_Kind::Observe, + R"({"category":"event","type":"gallery_observe","client_metrics":{}})"))); + const auto& performance = observed.at("telemetry").at("performance"); + EXPECT_GT(performance.at("measured_fps").get(), 0.0); + EXPECT_GT(performance.at("pixel_response_fps").get(), 0.0); + EXPECT_GT(performance.at("last_render_ms").get(), 0.0); + EXPECT_GT(performance.at("last_pixel_encode_ms").get(), 0.0); } TEST(RenderiveWebDatovizGallery, AutomaticLowLatencyFramesCarryAnimatedState) {