修复若干问题

This commit is contained in:
2026-08-15 03:21:01 +08:00
parent 04a9156cd6
commit 6655e8571b
54 changed files with 1525 additions and 397 deletions
+10
View File
@@ -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 "$<CONFIG>" -L "^Renderive_Kernel$" --output-on-failure
DEPENDS ${Renderive_Kernel_test_targets}
USES_TERMINAL)
endif ()
-2
View File
@@ -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"
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <atomic>
#include <cstdint>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <cmath>
#include <condition_variable>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include "renderive/renderable/Renderable.hpp"
#include "renderive/scene/Scene.hpp"
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <type_traits>
#include "renderive/renderable/Renderable.hpp"
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <atomic>
#include <memory>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <cstdint>
#include <memory>
@@ -1,3 +1,4 @@
#include "render_3D/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <string>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <atomic>
#include <cstddef>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <atomic>
#include <cstdint>
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
@@ -1,3 +1,5 @@
#include "render_3D/Scene_Context.hpp"
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include "renderive/scene/Scene.hpp"
static_assert(Scene<Scene2D_Context<>>);
@@ -1,3 +1,4 @@
#include "render_2D/scene/Scene_Context.hpp"
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
+8
View File
@@ -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"
"$<TARGET_FILE:${Renderive_Qt_test_target}>"
)
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 "$<CONFIG>" -L "^Renderive_Qt$" --output-on-failure
DEPENDS ${Renderive_Qt_test_targets}
USES_TERMINAL)
endif ()
+1 -1
View File
@@ -1,5 +1,5 @@
#pragma once
#include "render_2D/scene/Scene.h"
#include "render_2D/scene/Plot_Scene.h"
#include <QPointer>
#include <memory>
class QWidget;
+2 -2
View File
@@ -33,7 +33,7 @@ QColor to_qcolor(Color color) {
} // namespace
Abs_Plot_Private::Abs_Plot_Private(bool continuous_rendering)
: scene(std::make_unique<Scene2D>()), continuous(continuous_rendering) {}
: scene(std::make_unique<Plot_Scene>()), 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;
}
+1 -1
View File
@@ -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<Renderable> root_renderable() const;
void set_background_color(const QColor& color);
~Abs_Plot() override;
+1 -1
View File
@@ -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<Scene2D> scene;
std::unique_ptr<Plot_Scene> scene;
std::shared_ptr<Qt_Presentation_Sink> presentation_sink;
QTimer* timer{};
bool continuous{};
+8
View File
@@ -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 "$<CONFIG>" -L "^Renderive_render_2D$" --output-on-failure
DEPENDS ${Renderive_render_2D_test_targets}
USES_TERMINAL)
endif ()
+1 -1
View File
@@ -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"
@@ -1,11 +1,16 @@
#pragma once
#include "../base/Types.h"
#include "../scene/Scene.h"
#include "../scene/Plot_Scene.h"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
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<const Performance_Display_Snapshot> display_snapshot() const;
private:
template <detail::Scene_Frame_Control Frame_Control>
friend std::shared_ptr<Performance_Overlay> attach_performance_overlay(Basic_Scene2D<Frame_Control>&);
template <detail::Scene_Frame_Control Frame_Control>
friend std::shared_ptr<Performance_Overlay> attach_performance_overlay(Basic_Scene2D<Frame_Control>&, const Performance_Overlay_Options&);
template <detail::Scene_Frame_Control Frame_Control>
friend void set_performance_plot_name(Basic_Scene2D<Frame_Control>&, std::string);
template <detail::Scene_Frame_Control Frame_Control>
friend void set_performance_overlay_enabled(Basic_Scene2D<Frame_Control>&, bool);
friend std::shared_ptr<Performance_Overlay> attach_performance_overlay(Plot_Scene&);
friend std::shared_ptr<Performance_Overlay> 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> impl_;
void set_enabled(bool enabled);
@@ -59,41 +60,37 @@ private:
std::size_t renderable_count,
Size viewport);
};
template <detail::Scene_Frame_Control Frame_Control>
std::shared_ptr<Performance_Overlay> attach_performance_overlay(Basic_Scene2D<Frame_Control>& scene) {
inline std::shared_ptr<Performance_Overlay> attach_performance_overlay(Plot_Scene& scene) {
if (auto existing = scene.performance_overlay())
return existing;
auto overlay = std::make_shared<Performance_Overlay>();
scene.set_performance_overlay(overlay);
return overlay;
}
template <detail::Scene_Frame_Control Frame_Control>
std::shared_ptr<Performance_Overlay> attach_performance_overlay(Basic_Scene2D<Frame_Control>& scene, const Performance_Overlay_Options& options) {
inline std::shared_ptr<Performance_Overlay> 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<Performance_Overlay>(options);
scene.set_performance_overlay(overlay);
return overlay;
}
template <detail::Scene_Frame_Control Frame_Control>
void set_performance_plot_name(Basic_Scene2D<Frame_Control>& 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 <detail::Scene_Frame_Control Frame_Control>
void set_performance_overlay_enabled(Basic_Scene2D<Frame_Control>& 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
@@ -6,6 +6,17 @@
#include <renderive/scene/base/Render_Contexts.hpp>
namespace renderive {
namespace detail {
namespace {
struct Renderable_Group final : Renderable {
using Renderable::Renderable;
};
}
renderive_Owner<Renderable> make_renderable_group(bool cache_enabled) {
return renderive_Owner<Renderable_Group>::make(cache_enabled);
}
} // namespace detail
Renderable::Renderable(bool cache_enabled)
: render_base(std::make_unique<Impl>(), {.cache_enabled = cache_enabled}) {}
@@ -4,6 +4,7 @@
#include <renderive/base/Attached_Impl.hpp>
#include <renderive/renderable/Inheritance.hpp>
#include <renderive/renderable/base/Renderable_Base.hpp>
#include <renderive/renderable/base/Renderive_Owner.hpp>
#include <concepts>
#include <cstddef>
#include <cstdint>
@@ -16,6 +17,7 @@ struct Renderable;
namespace detail {
struct Painter;
struct Renderable_State_Observer;
LIB_DECL ::renderive_Owner<Renderable> make_renderable_group(bool cache_enabled);
}
enum struct Renderable_Observer_Event {
None,
@@ -0,0 +1,63 @@
#pragma once
#include <cstdint>
#include <string>
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
+199
View File
@@ -0,0 +1,199 @@
#include "Plot_Scene.h"
#include "detail/Plot_Scene_Model.hpp"
#include "../plottable/Performance_Overlay.h"
#include "../renderable/Renderable.h"
#include <renderive/scene/base/Scene_Base.hpp>
#include <stdexcept>
#include <variant>
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<detail::Manual_Frame_Control>;
using Low_Latency_Plot_Scene = Plot_Scene_Model<detail::Low_Latency_Frame_Control>;
using Playback_Plot_Scene = Plot_Scene_Model<detail::Playback_Frame_Control>;
}
struct Plot_Scene::Impl {
using Model = std::variant<std::unique_ptr<Manual_Plot_Scene>,
std::unique_ptr<Low_Latency_Plot_Scene>,
std::unique_ptr<Playback_Plot_Scene>>;
explicit Impl(Plot_Frame_Mode mode) : mode(mode), model(create(mode)) {}
template <class Operation>
decltype(auto) apply(Operation&& operation) {
return std::visit(
[&operation](auto& scene) -> decltype(auto) {
return operation(*scene);
},
model);
}
template <class Operation>
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<Manual_Plot_Scene>();
case Plot_Frame_Mode::Low_Latency:
return std::make_unique<Low_Latency_Plot_Scene>();
case Plot_Frame_Mode::Playback:
return std::make_unique<Playback_Plot_Scene>();
}
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<Impl>(options.frame_mode)) {}
Plot_Scene::~Plot_Scene() = default;
void Plot_Scene::init() { impl_->apply([](auto& scene) { scene.init(); }); }
renderive_Owner<Renderable> 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<Scene_2D_Base&>(render_scene()).attach_builder();
}
void Plot_Scene::edit_renderables(Scene_2D_Base::Renderable_Edit edit) {
static_cast<Scene_2D_Base&>(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<void(Image_View)>& 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<Presentation_Sink> sink) {
impl_->apply([&sink](auto& scene) { scene.set_presentation_sink(std::move(sink)); });
}
std::shared_ptr<Performance_Overlay> Plot_Scene::performance_overlay() const {
return impl_->apply([](const auto& scene) { return scene.performance_overlay(); });
}
void Plot_Scene::set_performance_overlay(
std::shared_ptr<Performance_Overlay> overlay) {
impl_->apply([&overlay](auto& scene) {
scene.set_performance_overlay(std::move(overlay));
});
}
} // namespace renderive
+85
View File
@@ -0,0 +1,85 @@
#pragma once
#include "../base/Types.h"
#include "../event/Event.h"
#include "../renderable/Renderable.h"
#include "Plot_Frame_Status.h"
#include <renderive/frame_control/Frame_Consumer_Feedback.hpp>
#include <renderive/renderable/base/Renderive_Owner.hpp>
#include <renderive/scene/base/Scene_Base.hpp>
#include <functional>
#include <memory>
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<Renderable> 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<void(Image_View)>& 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<Presentation_Sink> sink);
[[nodiscard]] std::shared_ptr<Performance_Overlay> performance_overlay() const;
void set_performance_overlay(std::shared_ptr<Performance_Overlay> overlay);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace renderive
-16
View File
@@ -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<Renderable> make_renderable_group(bool cache_enabled) {
return renderive_Owner<Renderable_Group>::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);
}
}
@@ -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;
@@ -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 <renderive/base/observer/Observer.hpp>
#include <renderive/frame_control/Frame_Consumer_Feedback.hpp>
#include <renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp>
#include <renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp>
#include <renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.hpp>
#include <renderive/scene/Scene2D_Context.hpp>
#include "../Scene_Context.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
@@ -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<Frame_Observer>;
@@ -165,21 +84,20 @@ using Playback_Frame_Control = ::Flow_Refresh_Strategy<::Scene2D_Frame_Data, std
template <class Frame_Control>
concept Scene_Frame_Control = std::same_as<Frame_Control, Manual_Frame_Control> || std::same_as<Frame_Control, Low_Latency_Frame_Control> || std::same_as<Frame_Control, Playback_Frame_Control>;
template <Scene_Frame_Control Frame_Control>
constexpr Frame_Control_Mode frame_control_mode() noexcept {
constexpr Plot_Frame_Mode frame_control_mode() noexcept {
if constexpr (std::same_as<Frame_Control, Manual_Frame_Control>)
return Frame_Control_Mode::Manual;
return Plot_Frame_Mode::Manual;
if constexpr (std::same_as<Frame_Control, Low_Latency_Frame_Control>)
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<Renderable> 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 <detail::Scene_Frame_Control Frame_Control>
struct Basic_Scene2D final : ::Scene2D_Context<Frame_Control, detail::Blend2D_Color_Cache, Scene_State> {
struct Plot_Scene_Model final : ::Scene2D_Context<Frame_Control, detail::Blend2D_Color_Cache, Scene_State> {
using Kernel_Scene = ::Scene2D_Context<Frame_Control, detail::Blend2D_Color_Cache, Scene_State>;
using Scene_State_Strategy = typename Kernel_Scene::Scene_State_Strategy;
static constexpr Frame_Control_Mode Mode = detail::frame_control_mode<Frame_Control>();
Basic_Scene2D() : Basic_Scene2D(std::make_shared<detail::Frame_Observer_Data>()) {}
~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<Frame_Control>();
Plot_Scene_Model() : Plot_Scene_Model(std::make_shared<detail::Frame_Observer_Data>()) {}
~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_Context<Frame_Control, detail::Blend2D_Co
return true;
}
[[nodiscard]] bool refresh_manual_frame() {
if constexpr (Mode == Frame_Control_Mode::Manual)
if constexpr (Mode == Plot_Frame_Mode::Manual)
return this->frame_control.refresh();
return false;
}
@@ -279,7 +197,7 @@ struct Basic_Scene2D final : ::Scene2D_Context<Frame_Control, detail::Blend2D_Co
return true;
}
[[nodiscard]] bool discard_pending_frame() {
if constexpr (Mode == Frame_Control_Mode::Playback)
if constexpr (Mode == Plot_Frame_Mode::Playback)
return false;
else
return this->frame_control.discard_pending_frame();
@@ -298,7 +216,7 @@ struct Basic_Scene2D final : ::Scene2D_Context<Frame_Control, detail::Blend2D_Co
return false;
const auto frame_viewport = this->Scene_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_Context<Frame_Control, detail::Blend2D_Co
return active_.load(std::memory_order_acquire);
}
void set_max_render_fps(double fps) {
if constexpr (Mode != Frame_Control_Mode::Low_Latency) {
if constexpr (Mode != Plot_Frame_Mode::Low_Latency) {
throw std::logic_error("maximum render FPS is only available in low-latency mode");
}
else {
@@ -353,7 +271,7 @@ struct Basic_Scene2D final : ::Scene2D_Context<Frame_Control, detail::Blend2D_Co
}
}
void clear_max_render_fps() {
if constexpr (Mode != Frame_Control_Mode::Low_Latency) {
if constexpr (Mode != Plot_Frame_Mode::Low_Latency) {
throw std::logic_error("maximum render FPS is only available in low-latency mode");
}
else {
@@ -362,49 +280,44 @@ struct Basic_Scene2D final : ::Scene2D_Context<Frame_Control, detail::Blend2D_Co
}
}
void set_consumer_feedback(Frame_Consumer_Feedback 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.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<double>::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<double>::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_Context<Frame_Control, detail::Blend2D_Co
snapshot.render_finish_state_wait_ns = observer_->render_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<std::uint32_t>(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_Context<Frame_Control, detail::Blend2D_Co
this->notify_model_dirty();
}
private:
explicit Basic_Scene2D(std::shared_ptr<detail::Frame_Observer_Data> observer)
explicit Plot_Scene_Model(std::shared_ptr<detail::Frame_Observer_Data> observer)
: Kernel_Scene(*memory_resource(Memory_Domain::Plot_Frame), detail::Frame_Observer_State(detail::Frame_Observer{observer})), observer_(std::move(observer)) {}
std::shared_ptr<detail::Frame_Observer_Data> observer_;
mutable std::mutex control_mutex_;
@@ -463,7 +399,4 @@ private:
std::shared_ptr<Performance_Overlay> performance_;
std::atomic_bool active_{};
};
using Manual_Scene2D = Basic_Scene2D<detail::Manual_Frame_Control>;
using Scene2D = Basic_Scene2D<detail::Low_Latency_Frame_Control>;
using Playback_Scene2D = Basic_Scene2D<detail::Playback_Frame_Control>;
}
@@ -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 = []<class Scene_Type>(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()<Manual_Scene2D>(Frame_Control_Mode::Manual);
verify.template operator()<Scene2D>(Frame_Control_Mode::Low_Latency);
verify.template operator()<Playback_Scene2D>(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<double>::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());
+49 -49
View File
@@ -53,7 +53,7 @@ static_assert(!Legacy_Axis_Property_Api<Time_Axis>);
static_assert(!Legacy_Time_Axis_Property_Api<Time_Axis>);
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<int>(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<Render_Plan_Version> 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;
+8
View File
@@ -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 "$<CONFIG>" -L "^Renderive_render_3D$" --output-on-failure
DEPENDS ${Renderive_render_3D_test_targets}
USES_TERMINAL)
endif ()
-29
View File
@@ -1,29 +0,0 @@
#include "Point_Demo.h"
#include <cmath>
#include <memory>
#include <utility>
namespace renderive::render_3d {
std::vector<Point> 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<Point_Scene>(options, visual);
return {std::move(visual), std::move(scene)};
}
} // namespace renderive::render_3d
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include "Point_Scene.h"
#include <memory>
#include <vector>
namespace renderive::render_3d {
struct Point_Demo {
std::shared_ptr<Point_Visual> visual;
std::unique_ptr<Point_Scene> scene;
};
[[nodiscard]] std::vector<Point> point_demo_data(float phase_radians = 0.0F);
[[nodiscard]] Point_Demo make_point_demo(Scene_Options options = {});
} // namespace renderive::render_3d
+1
View File
@@ -1,4 +1,5 @@
#include "Point_Scene.h"
#include "Scene_Context.hpp"
#include "detail/Datoviz_Visual_Backend.h"
#include "detail/Gpu_Completion_Service.h"
@@ -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{};
@@ -1,5 +1,6 @@
#include "render_3D/Datoviz_Visuals.h"
#include "render_3D/Scene_Context.hpp"
#include <renderive/scene/Scene.hpp>
#include <gtest/gtest.h>
@@ -1,4 +1,4 @@
#include "render_3D/Point_Demo.h"
#include "render_3D/Point_Scene.h"
#include <renderive/scene/base/Scene_Base.hpp>
@@ -15,6 +15,26 @@
namespace renderive::render_3d {
namespace {
struct Test_Point_Scene {
std::shared_ptr<Point_Visual> visual;
std::unique_ptr<Point_Scene> scene;
};
Test_Point_Scene make_test_scene(Scene_Options options) {
std::vector<Point> 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<Point_Scene>(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());
+1
View File
@@ -2,6 +2,7 @@
#include "render_3D/detail/Point_Core.h"
#include <renderive/frame_control/Frame_Control.hpp>
#include "render_3D/Scene_Context.hpp"
#include <renderive/scene/Scene.hpp>
#include <gtest/gtest.h>
+770
View File
@@ -0,0 +1,770 @@
# Renderive 当前架构核实与改造 TODO
## P0External 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 nodeA、B
* [ ] A 主动 fail
* [ ] B 永久不回调
* [ ] 验证整个 graph 是否永久不返回
* [ ] 构造 A fail、B 正常延迟完成
* [ ] 验证失败 frame 的 completion 时间是否被 B 强制拖长
* [ ] 明确当前设计到底是:
* fail-fast
* drain-before-fail
* cooperative cancellation
**如果问题成立,再决定语义。**
推荐先明确 contract,不要直接写代码:
```text
方案 Afail-fast
一个节点失败后,frame 尽快失败返回。
方案 Bdrain
一个节点失败后,已启动外部操作仍全部收尾后再返回。
方案 Ccancel + drain
请求其他 operation cancellation
无法取消的 operation 做后台资源回收,
但 frame logical completion 可以提前结束。
```
* [ ] 根据 Renderive 的资源安全要求确定一种正式语义
* [ ] 再增加对应 Runtime 状态机和测试
---
## P1Render 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 增长
* [ ] 确认长期运行是否确实存在无界增长
**如果成立,再选择方案:**
### 方案 ATopology Interning
* [ ] 为 topology 计算稳定 fingerprint/hash
* [ ] 相同 topology 复用已有 immutable Render Plan
```text
topology fingerprint
Render_Plan cache
```
### 方案 BBounded 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 目标部署规模:
* 单桌面程序 14 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 压测数据之前重构这一项。**
---
## P2Frame 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
```
* [ ] 只有出现离线渲染/视频/批处理需求时再设计跨帧流水线
---
## P2Scene_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
```
**当前不建议仅为了减小文件而拆类。**
只有当职责本身形成独立生命周期/状态机时再拆。
---
## P2Taskflow 依赖边界确认
### 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 即可。
---
## P2Datoviz 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
```
---
## P2CPU / 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
输出报告时必须保持这些概念分离。
---
## P3Low 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
```
---
## P3Runtime 压测
### 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 更需要的是**验证和收敛**,而不是继续扩张调度抽象。
+8
View File
@@ -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 "$<CONFIG>" -L "^Renderive_Web$" --output-on-failure
DEPENDS ${Renderive_Web_test_targets}
USES_TERMINAL)
endif ()
+14 -14
View File
@@ -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 <structive/property/property.hpp>
#include <cstdint>
@@ -83,19 +83,19 @@ struct Type_Descriptor<Manual_Refresh_Strategy<Scene_Frame, Mutex, Observer>> {
using renderive::web::gallery_action;
return object<T>(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<Low_Latency_Strategy<Scene_Frame, Mutex, Observer>> {
using renderive::web::gallery_action;
return object<T>(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<Flow_Refresh_Strategy<Scene_Frame, Mutex, Observer>> {
using renderive::web::gallery_action;
return object<T>(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)));
}
};
+11 -11
View File
@@ -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 <class Json>
struct Value_Adapter<renderive::Frame_Control_Mode, Json> {
struct Value_Adapter<renderive::Plot_Frame_Mode, Json> {
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<renderive::Frame_Control_Mode>(value);
renderive::web::gallery_enum_cast<renderive::Plot_Frame_Mode>(value);
if (!parsed)
throw std::invalid_argument("unknown frame control mode: " + value);
target = *parsed;
@@ -82,14 +82,14 @@ struct Value_Adapter<renderive::Frame_Control_Mode, Json> {
};
template <>
struct Type_Descriptor<renderive::Frame_Observer_Snapshot> {
struct Type_Descriptor<renderive::Plot_Frame_Status> {
static auto get() {
using T = renderive::Frame_Observer_Snapshot;
using T = renderive::Plot_Frame_Status;
return object<T>("kernel_observer",
ADMINIVE_FIELD_LABEL(T, mode, "模式")
.enum_label<renderive::Frame_Control_Mode::Manual>("手动刷新")
.enum_label<renderive::Frame_Control_Mode::Low_Latency>("低延迟")
.enum_label<renderive::Frame_Control_Mode::Playback>("回放队列"),
.enum_label<renderive::Plot_Frame_Mode::Manual>("手动刷新")
.enum_label<renderive::Plot_Frame_Mode::Low_Latency>("低延迟")
.enum_label<renderive::Plot_Frame_Mode::Playback>("回放队列"),
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<renderive::Renderable_Observation> {
}
};
static_assert(Described_Type<renderive::Frame_Observer_Snapshot>);
static_assert(Described_Type<renderive::Plot_Frame_Status>);
static_assert(Described_Type<renderive::web::Gallery_Consumer_Feedback_Snapshot>);
static_assert(Described_Type<renderive::web::Gallery_Render_Performance>);
static_assert(Described_Type<renderive::web::Gallery_Client_Performance>);
+29 -29
View File
@@ -85,13 +85,13 @@ void append_session_actions(Gallery_Frame_Mode frame_mode,
std::vector<Action_Model>& result) {
switch (frame_mode) {
case Gallery_Frame_Mode::Manual:
append_gallery_actions<Gallery_Session_Control<Manual_Scene2D>>(result);
append_gallery_actions<Gallery_Session_Control<Plot_Scene>>(result);
return;
case Gallery_Frame_Mode::Low_Latency:
append_gallery_actions<Gallery_Session_Control<Scene2D>>(result);
append_gallery_actions<Gallery_Session_Control<Plot_Scene>>(result);
return;
case Gallery_Frame_Mode::Playback:
append_gallery_actions<Gallery_Session_Control<Playback_Scene2D>>(result);
append_gallery_actions<Gallery_Session_Control<Plot_Scene>>(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<Gallery_Session_Control<Manual_Scene2D>>();
return gallery_control_count<Gallery_Session_Control<Plot_Scene>>();
case Gallery_Frame_Mode::Low_Latency:
return gallery_control_count<Gallery_Session_Control<Scene2D>>();
return gallery_control_count<Gallery_Session_Control<Plot_Scene>>();
case Gallery_Frame_Mode::Playback:
return gallery_control_count<Gallery_Session_Control<Playback_Scene2D>>();
return gallery_control_count<Gallery_Session_Control<Plot_Scene>>();
}
throw std::invalid_argument("unknown gallery frame mode");
}
@@ -298,16 +298,16 @@ bool observer_detail_field(std::string_view name) {
template <class Predicate>
Json observer_fields(Predicate&& predicate) {
const Json descriptor =
adminive::to_descriptor_json<Json, renderive::Frame_Observer_Snapshot>();
adminive::to_descriptor_json<Json, renderive::Plot_Frame_Status>();
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<renderive::Frame_Observer_Snapshot>(
Json field = described_dashboard_field<renderive::Plot_Frame_Status>(
"kernel_observer", name);
if (name == "frequency_hz") {
field["enabled_source"] =
described_source<renderive::Frame_Observer_Snapshot>(
described_source<renderive::Plot_Frame_Status>(
"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<renderive::Frame_Observer_Snapshot>(
Json bottleneck = described_dashboard_field<renderive::Plot_Frame_Status>(
"kernel_observer", "limit_state");
bottleneck["format"] = "duration_enum";
bottleneck["duration_sources"] = {
{"frequency_limited", described_source<renderive::Frame_Observer_Snapshot>(
{"frequency_limited", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "target_interval_ns")},
{"paint_limited", described_source<renderive::Frame_Observer_Snapshot>(
{"paint_limited", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "paint_duration_ns")},
{"render_limited", described_source<renderive::Frame_Observer_Snapshot>(
{"render_limited", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "render_duration_ns")},
{"consumer_limited", described_source<renderive::Frame_Observer_Snapshot>(
{"consumer_limited", described_source<renderive::Plot_Frame_Status>(
"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<renderive::Frame_Observer_Snapshot>(
described_dashboard_field<renderive::Plot_Frame_Status>(
"kernel_observer", "pending_frame_count"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
described_dashboard_field<renderive::Plot_Frame_Status>(
"kernel_observer", "dropped_frame_count"),
std::move(point_pair), std::move(bottleneck),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
described_dashboard_field<renderive::Plot_Frame_Status>(
"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<renderive::Frame_Observer_Snapshot>(
{"current_source", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "limit_state")},
{"active_label", "当前瓶颈"}, {"inactive_label", "未受限"},
{"disabled_label", "已关闭"},
{"fields", Json::array({
{{"label", "内核用户频率"}, {"active_value", "frequency_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
{"duration_source", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "target_interval_ns")},
{"enabled_source", described_source<renderive::Frame_Observer_Snapshot>(
{"enabled_source", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "frequency_limit_enabled")}},
{{"label", "内核绘制事件"}, {"active_value", "paint_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
{"duration_source", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "paint_duration_ns")}},
{{"label", "内核后台渲染"}, {"active_value", "render_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
{"duration_source", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "render_duration_ns")}},
{{"label", "消费者反馈"}, {"active_value", "consumer_limited"},
{"duration_source", described_source<renderive::Frame_Observer_Snapshot>(
{"duration_source", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "consumer_interval_ns")},
{"enabled_source", described_source<renderive::Frame_Observer_Snapshot>(
{"enabled_source", described_source<renderive::Plot_Frame_Status>(
"kernel_observer", "consumer_feedback_enabled")}}
})}
}},
@@ -466,11 +466,11 @@ Json dashboard_contract() {
{"aria_label", "内核低延迟全量统计"},
{"header", {
{"prefix", "内核"}, {"suffix", "观察器"}, {"event_label", "事件"},
{"mode", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
{"mode", described_dashboard_field<renderive::Plot_Frame_Status>(
"kernel_observer", "mode")},
{"limit", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
{"limit", described_dashboard_field<renderive::Plot_Frame_Status>(
"kernel_observer", "limit_state")},
{"event", described_dashboard_field<renderive::Frame_Observer_Snapshot>(
{"event", described_dashboard_field<renderive::Plot_Frame_Status>(
"kernel_observer", "last_event")}
}},
{"sections", Json::array({
@@ -507,7 +507,7 @@ Json dashboard_contract() {
}}
};
dashboard["observer"]["descriptor"] =
adminive::to_descriptor_json<Json, renderive::Frame_Observer_Snapshot>();
adminive::to_descriptor_json<Json, renderive::Plot_Frame_Status>();
dashboard["observer"]["consumer_feedback_descriptor"] =
adminive::to_descriptor_json<Json, Gallery_Consumer_Feedback_Snapshot>();
return dashboard;
+1 -1
View File
@@ -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 <memory>
#include <string>
namespace adminive {
@@ -43,7 +43,7 @@ struct Type_Descriptor<renderive::web::Gallery_Session_Control<Scene_Type>> {
static auto get() {
using T = renderive::web::Gallery_Session_Control<Scene_Type>;
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<T>("scene", "场景",
callback_property<T, renderive::Color>(
"background_color", "背景颜色", Field_Control::color,
+3 -3
View File
@@ -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<Frequency_Axis> spectrum_frequency_axis;
renderive_Owner<Axis> spectrum_power_axis;
renderive_Owner<Frequency_Axis> waterfall_frequency_axis;
@@ -306,7 +306,7 @@ struct Web_Plot_Session::Impl {
}
else if constexpr (std::is_same_v<T, Set_Demo_Mode>) {
mode = value.mode;
plot.notify_model_dirty();
plot.request_redraw();
}
else if constexpr (std::is_same_v<T, Set_Center_Frequency>) {
set_center_frequency(value.megahertz);
@@ -316,7 +316,7 @@ struct Web_Plot_Session::Impl {
}
else if constexpr (std::is_same_v<T, Set_Gain>) {
gain_db = value.decibels;
plot.notify_model_dirty();
plot.request_redraw();
}
else if constexpr (std::is_same_v<T, Set_Max_Hold>) {
spectrum->set<&Spectrum::Properties::max_hold_visible>(value.enabled);
+31 -26
View File
@@ -143,12 +143,24 @@ std::optional<double> action_number(const Gallery_Action_Request& request) {
return std::nullopt;
return std::get<double>(*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 Scene_Type>
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<Gallery_Session_Control<Scene_Type>>(
session_control_ = std::make_unique<Gallery_Session_Control<Plot_Scene>>(
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::size_t>(
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<double>(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::milliseconds>(
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<Gallery_Session_Control<Scene_Type>> session_control_;
std::unique_ptr<Gallery_Session_Control<Plot_Scene>> 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<Gallery_Scene_Interface> 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<Gallery_Scene<Manual_Scene2D>>(session_id, std::move(case_id), mode, automatic_low_latency);
case Gallery_Frame_Mode::Low_Latency:
return std::make_unique<Gallery_Scene<Scene2D>>(session_id, std::move(case_id), mode, automatic_low_latency);
case Gallery_Frame_Mode::Playback:
return std::make_unique<Gallery_Scene<Playback_Scene2D>>(session_id, std::move(case_id), mode, automatic_low_latency);
}
throw std::invalid_argument("unknown gallery frame mode");
return std::make_unique<Gallery_Scene>(session_id, std::move(case_id),
mode, automatic_low_latency);
}
}
+40 -2
View File
@@ -13,6 +13,7 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <memory>
#include <optional>
#include <stdexcept>
@@ -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<double, std::milli>(
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<Performance_Clock::time_point>& 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<Performance_Clock::time_point>& 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<double>(samples.back() - *first).count();
return seconds > 0.0 ? static_cast<double>(count - 1) / seconds : 0.0;
}
static std::vector<Point> 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<Performance_Clock::time_point> render_history_;
std::deque<Performance_Clock::time_point> pixel_history_;
std::string last_event_{"initialized"};
std::string last_action_result_;
};
@@ -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<double>(), 0.0);
EXPECT_GT(performance.at("pixel_response_fps").get<double>(), 0.0);
EXPECT_GT(performance.at("last_render_ms").get<double>(), 0.0);
EXPECT_GT(performance.at("last_pixel_encode_ms").get<double>(), 0.0);
}
TEST(RenderiveWebDatovizGallery, AutomaticLowLatencyFramesCarryAnimatedState) {