修复若干问题
This commit is contained in:
@@ -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 ()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
@@ -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{};
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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;
|
||||
+54
-121
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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
|
||||
@@ -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,4 +1,5 @@
|
||||
#include "Point_Scene.h"
|
||||
#include "Scene_Context.hpp"
|
||||
|
||||
#include "detail/Datoviz_Visual_Backend.h"
|
||||
#include "detail/Gpu_Completion_Service.h"
|
||||
|
||||
+1
-1
@@ -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());
|
||||
|
||||
@@ -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
@@ -0,0 +1,770 @@
|
||||
# Renderive 当前架构核实与改造 TODO
|
||||
|
||||
## P0:External Operation 生命周期与失败语义
|
||||
|
||||
### TODO-01:核实 External Operation 是否存在永久 pending 风险
|
||||
|
||||
* [ ] 阅读 `Kernel/src/renderive/render_graph/External_Operation.hpp`
|
||||
* [ ] 明确 `External_Operation_Source` 当前生命周期规则
|
||||
* [ ] 核实 Source 被析构但未调用 `complete()` / `fail()` 时,Operation 的最终状态
|
||||
* [ ] 编写测试:创建 Operation 后直接销毁 Source
|
||||
* [ ] 验证 `Render_Graph_Runtime` 是否会永久停留在 `waiting_external`
|
||||
* [ ] 验证 graph future / frame completion 是否会永久阻塞
|
||||
* [ ] 检查 Datoviz GPU completion 所有异常路径是否保证最终 complete/fail
|
||||
|
||||
**核实目标:**
|
||||
|
||||
确认是否存在:
|
||||
|
||||
```text
|
||||
producer 消失
|
||||
↓
|
||||
External_Operation 永久 pending
|
||||
↓
|
||||
node 永久 waiting_external
|
||||
↓
|
||||
frame 永久无法完成
|
||||
```
|
||||
|
||||
**如果问题成立,再考虑改造:**
|
||||
|
||||
* [ ] 为 External Operation 定义明确 terminal states
|
||||
|
||||
```text
|
||||
pending
|
||||
completed
|
||||
failed
|
||||
cancelled / abandoned / broken
|
||||
```
|
||||
|
||||
* [ ] Source 析构时处理未完成 Operation
|
||||
* [ ] 明确 broken producer 应映射为:
|
||||
|
||||
* failure
|
||||
* cancellation
|
||||
* abandoned
|
||||
* [ ] 保证 completion callback 只执行一次
|
||||
* [ ] 保证 complete/fail/cancel 与析构竞态安全
|
||||
* [ ] 增加 double-complete / complete-after-fail 等测试
|
||||
* [ ] 增加 Runtime 等待 external 时 producer 异常退出测试
|
||||
|
||||
---
|
||||
|
||||
### TODO-02:核实 Render Graph failure 后是否可能等待无关 External Operation
|
||||
|
||||
重点位置:
|
||||
|
||||
```text
|
||||
Render_Graph_Runtime.cpp
|
||||
```
|
||||
|
||||
特别检查 graph 结束条件中:
|
||||
|
||||
```text
|
||||
failed
|
||||
active_tasks
|
||||
waiting_external
|
||||
unfinished_nodes
|
||||
```
|
||||
|
||||
之间的关系。
|
||||
|
||||
* [ ] 构造两个并行 async node:A、B
|
||||
* [ ] A 主动 fail
|
||||
* [ ] B 永久不回调
|
||||
* [ ] 验证整个 graph 是否永久不返回
|
||||
* [ ] 构造 A fail、B 正常延迟完成
|
||||
* [ ] 验证失败 frame 的 completion 时间是否被 B 强制拖长
|
||||
* [ ] 明确当前设计到底是:
|
||||
|
||||
* fail-fast
|
||||
* drain-before-fail
|
||||
* cooperative cancellation
|
||||
|
||||
**如果问题成立,再决定语义。**
|
||||
|
||||
推荐先明确 contract,不要直接写代码:
|
||||
|
||||
```text
|
||||
方案 A:fail-fast
|
||||
一个节点失败后,frame 尽快失败返回。
|
||||
|
||||
方案 B:drain
|
||||
一个节点失败后,已启动外部操作仍全部收尾后再返回。
|
||||
|
||||
方案 C:cancel + drain
|
||||
请求其他 operation cancellation,
|
||||
无法取消的 operation 做后台资源回收,
|
||||
但 frame logical completion 可以提前结束。
|
||||
```
|
||||
|
||||
* [ ] 根据 Renderive 的资源安全要求确定一种正式语义
|
||||
* [ ] 再增加对应 Runtime 状态机和测试
|
||||
|
||||
---
|
||||
|
||||
## P1:Render Plan 生命周期与重复编译
|
||||
|
||||
### TODO-03:核实 Render_Plan_History 是否长期无限增长
|
||||
|
||||
重点检查:
|
||||
|
||||
```text
|
||||
Render_Plan.cpp
|
||||
Render_Plan_History
|
||||
compile_render_plan()
|
||||
```
|
||||
|
||||
当前重点假设:
|
||||
|
||||
```text
|
||||
只与 plans_.back() 比较 topology
|
||||
```
|
||||
|
||||
可能出现:
|
||||
|
||||
```text
|
||||
Topology A
|
||||
Topology B
|
||||
Topology A
|
||||
Topology B
|
||||
...
|
||||
```
|
||||
|
||||
导致历史:
|
||||
|
||||
```text
|
||||
A1 B2 A3 B4 A5 B6 ...
|
||||
```
|
||||
|
||||
* [ ] 确认 `Render_Plan_History` 是否只 dedupe 相邻 topology
|
||||
* [ ] 确认旧 Render Plan 是否存在必须永久保存的语义
|
||||
* [ ] 检查 Capture 是否持有具体 Plan
|
||||
* [ ] 检查 Observation / Analysis 是否依赖历史 Plan 对象
|
||||
* [ ] 模拟 10 万帧 A/B topology 交替
|
||||
* [ ] 观察:
|
||||
|
||||
* plan 数量
|
||||
* 内存占用
|
||||
* version 增长
|
||||
* [ ] 确认长期运行是否确实存在无界增长
|
||||
|
||||
**如果成立,再选择方案:**
|
||||
|
||||
### 方案 A:Topology Interning
|
||||
|
||||
* [ ] 为 topology 计算稳定 fingerprint/hash
|
||||
* [ ] 相同 topology 复用已有 immutable Render Plan
|
||||
|
||||
```text
|
||||
topology fingerprint
|
||||
↓
|
||||
Render_Plan cache
|
||||
```
|
||||
|
||||
### 方案 B:Bounded History
|
||||
|
||||
* [ ] History 保留最近 N 个 plan
|
||||
* [ ] Capture 对需要长期保存的 Plan 单独持有 shared_ptr
|
||||
|
||||
### 方案 C:二者结合
|
||||
|
||||
* [ ] topology cache
|
||||
* [ ] bounded observation history
|
||||
* [ ] capture 独立 pin
|
||||
|
||||
优先研究方案 C。
|
||||
|
||||
---
|
||||
|
||||
### TODO-04:核实 Render Plan 是否每帧重复编译执行拓扑
|
||||
|
||||
重点检查:
|
||||
|
||||
```text
|
||||
Scene_Base::compile_render_plan()
|
||||
Render_Graph_Runtime constructor
|
||||
```
|
||||
|
||||
核实每帧是否重复执行:
|
||||
|
||||
```text
|
||||
node id → index map
|
||||
successor adjacency
|
||||
indegree
|
||||
topological sort
|
||||
cycle validation
|
||||
```
|
||||
|
||||
* [ ] 使用 profiler 测量 Runtime constructor 成本
|
||||
* [ ] 分别测试:
|
||||
|
||||
* 10 nodes
|
||||
* 50 nodes
|
||||
* 100 nodes
|
||||
* 500 nodes
|
||||
* 1000 nodes
|
||||
* [ ] 测量每帧:
|
||||
|
||||
* Render Plan compile 时间
|
||||
* Runtime construction 时间
|
||||
* topology validation 时间
|
||||
* [ ] 与真实 node execution 时间比较
|
||||
* [ ] 判断这部分是否已经进入 critical path
|
||||
|
||||
**只有测量证明值得优化时再改。**
|
||||
|
||||
潜在目标结构:
|
||||
|
||||
```text
|
||||
Render_Plan
|
||||
├─ Logical metadata
|
||||
└─ Compiled Execution Layout
|
||||
├─ node execution index
|
||||
├─ predecessor count[]
|
||||
├─ successor offset[]
|
||||
├─ successors[]
|
||||
└─ static topology metadata
|
||||
```
|
||||
|
||||
每帧 Runtime 只生成:
|
||||
|
||||
```text
|
||||
remaining_predecessors[]
|
||||
node_status[]
|
||||
timestamps[]
|
||||
external state
|
||||
```
|
||||
|
||||
* [ ] 尽量避免每帧重新构造 immutable topology
|
||||
* [ ] 保持 Render Plan 本身 immutable
|
||||
* [ ] 不把 per-frame execution state 塞回 Render Plan
|
||||
* [ ] benchmark 改造前后成本
|
||||
|
||||
---
|
||||
|
||||
## P1:调度模型规模化核实
|
||||
|
||||
### TODO-05:统计每个 Scene 实际线程成本
|
||||
|
||||
当前重点检查:
|
||||
|
||||
```text
|
||||
Scene_Base worker thread
|
||||
|
||||
3D Point Scene:
|
||||
Render_Domain thread
|
||||
Gpu_Completion_Service thread
|
||||
|
||||
Global:
|
||||
Taskflow Executor workers
|
||||
```
|
||||
|
||||
* [ ] 启动 1 个 3D Scene,记录线程数
|
||||
* [ ] 启动 10 个 Scene
|
||||
* [ ] 启动 50 个 Scene
|
||||
* [ ] 启动 100 个 Scene
|
||||
* [ ] 测量:
|
||||
|
||||
* OS thread 数量
|
||||
* context switches
|
||||
* idle CPU
|
||||
* memory/thread stack
|
||||
* frame latency
|
||||
* scheduler wait
|
||||
* [ ] 明确 Renderive 目标部署规模:
|
||||
|
||||
* 单桌面程序 1~4 Scene
|
||||
* 多窗口
|
||||
* Web 多 session
|
||||
* server-side rendering
|
||||
|
||||
**如果目标只是少量本地 Scene:**
|
||||
|
||||
* [ ] 暂不修改
|
||||
|
||||
**如果未来需要大量 Scene:**
|
||||
|
||||
* [ ] 研究共享 Render Domain
|
||||
* [ ] 研究共享 GPU Completion Service
|
||||
* [ ] Render Domain 按 device/backend 而不是按 Scene 划分
|
||||
* [ ] 使用 serial executor / strand 保持 backend affinity
|
||||
|
||||
目标可能演化为:
|
||||
|
||||
```text
|
||||
Global CPU Executor
|
||||
|
||||
GPU Device 0
|
||||
├─ Shared Render Domain
|
||||
│ ├─ Scene A
|
||||
│ ├─ Scene B
|
||||
│ └─ Scene C
|
||||
│
|
||||
└─ Shared Completion Service
|
||||
├─ fence A
|
||||
├─ fence B
|
||||
└─ fence C
|
||||
```
|
||||
|
||||
**不要在没有多 Scene 压测数据之前重构这一项。**
|
||||
|
||||
---
|
||||
|
||||
## P2:Frame Pipeline 能力边界确认
|
||||
|
||||
### TODO-06:确认当前 Scene 是否严格 single-frame-in-flight
|
||||
|
||||
重点检查:
|
||||
|
||||
```text
|
||||
Scene_Base::submit_render()
|
||||
Scene_Base::lock_render_idle()
|
||||
```
|
||||
|
||||
* [ ] 验证 GPU external operation 未完成时,下一帧 CPU DAG 能否开始
|
||||
* [ ] 确认当前行为是否:
|
||||
|
||||
```text
|
||||
Frame N CPU
|
||||
↓
|
||||
Frame N GPU
|
||||
↓
|
||||
Frame N complete
|
||||
↓
|
||||
Frame N+1 CPU
|
||||
```
|
||||
|
||||
而不是:
|
||||
|
||||
```text
|
||||
Frame N GPU
|
||||
+
|
||||
Frame N+1 CPU
|
||||
```
|
||||
|
||||
* [ ] 测量这种设计对 interactive latency 的影响
|
||||
* [ ] 验证 low-latency 模式下 GPU queue 是否保持浅队列
|
||||
* [ ] 验证 frame age 是否优于多帧堆积方案
|
||||
|
||||
**当前阶段不要默认这是问题。**
|
||||
|
||||
明确记录设计决策:
|
||||
|
||||
```text
|
||||
Interactive mode:
|
||||
优先 latency
|
||||
→ single-frame-in-flight 可以是正确策略
|
||||
|
||||
Throughput / offline mode:
|
||||
优先 throughput
|
||||
→ 未来可能需要 multi-frame-in-flight
|
||||
```
|
||||
|
||||
* [ ] 只有出现离线渲染/视频/批处理需求时再设计跨帧流水线
|
||||
|
||||
---
|
||||
|
||||
## P2:Scene_Base 职责边界检查
|
||||
|
||||
### TODO-07:持续监控 Scene_Base 是否演化成 God Object
|
||||
|
||||
盘点当前职责:
|
||||
|
||||
```text
|
||||
Renderable lifetime
|
||||
Scene state
|
||||
Snapshot
|
||||
Dependency
|
||||
Render plan compile
|
||||
Frame submission
|
||||
Worker control
|
||||
Edit queue
|
||||
Exception propagation
|
||||
Observation
|
||||
Capture integration
|
||||
Frame control
|
||||
Memory domain
|
||||
```
|
||||
|
||||
* [ ] 给 Scene_Base 当前职责做一次正式列表
|
||||
* [ ] 检查每个职责是否属于 orchestration
|
||||
* [ ] 新功能进入 Scene_Base 前必须判断能否成为独立 subsystem
|
||||
|
||||
特别避免未来加入:
|
||||
|
||||
```text
|
||||
GPU fence logic
|
||||
backend-specific synchronization
|
||||
generic async continuation internals
|
||||
capture storage internals
|
||||
scheduler implementation internals
|
||||
GPU profiler implementation
|
||||
```
|
||||
|
||||
应继续保持:
|
||||
|
||||
```text
|
||||
Scene_Base
|
||||
↓ orchestrate
|
||||
|
||||
Render_Graph_Runtime
|
||||
External_Operation
|
||||
Render_Domain
|
||||
Gpu_Completion_Service
|
||||
Capture subsystem
|
||||
Observation subsystem
|
||||
```
|
||||
|
||||
**当前不建议仅为了减小文件而拆类。**
|
||||
|
||||
只有当职责本身形成独立生命周期/状态机时再拆。
|
||||
|
||||
---
|
||||
|
||||
## P2:Taskflow 依赖边界确认
|
||||
|
||||
### TODO-08:正式确认 Taskflow 只是 Executor,而不是架构语义的一部分
|
||||
|
||||
当前目标模型:
|
||||
|
||||
```text
|
||||
Render_Graph_Runtime
|
||||
↓
|
||||
CPU Scheduler Adapter
|
||||
↓
|
||||
tf::Executor::silent_async()
|
||||
```
|
||||
|
||||
* [ ] 搜索所有 `tf::` 使用点
|
||||
* [ ] 检查 Renderable / Render Plan / External Operation 是否泄漏 Taskflow 类型
|
||||
* [ ] 检查 Render_Graph_Runtime 是否依赖 Taskflow DAG API
|
||||
* [ ] 确认 Taskflow 只承担 CPU function dispatch
|
||||
* [ ] 做一个最小 mock executor 测试
|
||||
|
||||
例如:
|
||||
|
||||
```cpp
|
||||
scheduler([] {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
替换成测试 executor 后 Runtime 仍正常运行。
|
||||
|
||||
* [ ] 如果成立,记录架构结论:
|
||||
|
||||
```text
|
||||
Taskflow is an implementation detail of CPU execution,
|
||||
not Renderive's task graph model.
|
||||
```
|
||||
|
||||
**如果已经成立,不需要再额外设计 Scheduler Concept。**
|
||||
|
||||
先保持最小 callable abstraction 即可。
|
||||
|
||||
---
|
||||
|
||||
## P2:Datoviz Async 接入完整性核实
|
||||
|
||||
### TODO-09:逐条核实 GPU completion 所有路径都完成 External Operation
|
||||
|
||||
检查:
|
||||
|
||||
```text
|
||||
Point_Scene
|
||||
Render_Domain
|
||||
Gpu_Completion_Service
|
||||
Datoviz backend
|
||||
```
|
||||
|
||||
覆盖以下路径:
|
||||
|
||||
* [ ] 正常 submit
|
||||
* [ ] submit 失败
|
||||
* [ ] fence 创建失败
|
||||
* [ ] fence wait 失败
|
||||
* [ ] device lost
|
||||
* [ ] Render_Domain callback 抛异常
|
||||
* [ ] backend.collect() 抛异常
|
||||
* [ ] Scene 被销毁
|
||||
* [ ] application shutdown
|
||||
* [ ] completion service shutdown
|
||||
* [ ] frame 被逻辑取消
|
||||
* [ ] GPU operation 已提交但 Scene 生命周期结束
|
||||
|
||||
每条路径都验证最终发生:
|
||||
|
||||
```text
|
||||
complete
|
||||
或
|
||||
fail/cancel
|
||||
```
|
||||
|
||||
不能:
|
||||
|
||||
```text
|
||||
silent disappearance
|
||||
→ External Operation 永久 pending
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P2:CPU / GPU 观测数据可信度核实
|
||||
|
||||
### TODO-10:验证节点时间戳语义
|
||||
|
||||
针对:
|
||||
|
||||
```text
|
||||
ready_time
|
||||
start_time
|
||||
cpu_end_time
|
||||
external_start_time
|
||||
external_end_time
|
||||
end_time
|
||||
```
|
||||
|
||||
逐项明确语义。
|
||||
|
||||
* [ ] `ready_time` 是否表示所有 dependency 已满足
|
||||
* [ ] `start_time - ready_time` 是否能准确代表 scheduler wait
|
||||
* [ ] `cpu_end_time - start_time` 是否代表 CPU node execution
|
||||
* [ ] external_start 是否发生在 CPU submit 完成附近
|
||||
* [ ] external_end 是否由真正的 external completion 驱动
|
||||
* [ ] node end 是否与 dependency unlock 同一语义点
|
||||
* [ ] worker_id 是否只表示 CPU execution worker
|
||||
* [ ] external completion thread 是否不会错误覆盖 worker_id
|
||||
|
||||
---
|
||||
|
||||
### TODO-11:验证 Critical Path 算法
|
||||
|
||||
* [ ] 手工构造简单 DAG
|
||||
|
||||
```text
|
||||
A 10ms ── C 5ms
|
||||
B 20ms ──┘
|
||||
```
|
||||
|
||||
期望 critical path:
|
||||
|
||||
```text
|
||||
B → C = 25ms
|
||||
```
|
||||
|
||||
* [ ] 增加 external node
|
||||
* [ ] 增加 scheduler wait
|
||||
* [ ] 明确 critical path 是否统计:
|
||||
|
||||
* CPU execution
|
||||
* external wait
|
||||
* scheduler wait
|
||||
* dependency wait
|
||||
* [ ] 明确算法计算的是:
|
||||
|
||||
* DAG theoretical execution critical path
|
||||
* 还是 observed wall-clock critical path
|
||||
|
||||
两者不能混淆。
|
||||
|
||||
---
|
||||
|
||||
### TODO-12:验证 GPU timing 与 fence wait 的区别
|
||||
|
||||
确保以下数据不会混用:
|
||||
|
||||
```text
|
||||
GPU timestamp duration
|
||||
≠
|
||||
CPU fence waiting duration
|
||||
```
|
||||
|
||||
* [ ] GPU render timestamp
|
||||
* [ ] transition timestamp
|
||||
* [ ] copy timestamp
|
||||
* [ ] total GPU timestamp
|
||||
* [ ] CPU submit duration
|
||||
* [ ] completion service fence wait duration
|
||||
* [ ] readback duration
|
||||
|
||||
输出报告时必须保持这些概念分离。
|
||||
|
||||
---
|
||||
|
||||
## P3:Low Latency / Frame Control 回归测试
|
||||
|
||||
### TODO-13:正式锁定 latest-frame-wins 的行为语义
|
||||
|
||||
* [ ] 测试 pending frame 被替换
|
||||
* [ ] 测试 rendering frame 不会被中断
|
||||
* [ ] 测试事件修改模型后,新 snapshot 能看到状态
|
||||
* [ ] 测试被替换 frame 不会导致权威模型状态丢失
|
||||
* [ ] 测试 burst input:
|
||||
|
||||
* mouse move
|
||||
* resize
|
||||
* camera control
|
||||
* rapid data update
|
||||
* [ ] 记录:
|
||||
|
||||
* produced frames
|
||||
* rendered frames
|
||||
* dropped/replaced frames
|
||||
* input-to-present latency
|
||||
|
||||
明确:
|
||||
|
||||
```text
|
||||
丢的是待展示/待处理 frame
|
||||
不是 authoritative scene state
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P3:Runtime 压测
|
||||
|
||||
### TODO-14:建立标准 DAG benchmark
|
||||
|
||||
固定测试:
|
||||
|
||||
```text
|
||||
10 nodes
|
||||
50 nodes
|
||||
100 nodes
|
||||
500 nodes
|
||||
1000 nodes
|
||||
```
|
||||
|
||||
分别包含:
|
||||
|
||||
```text
|
||||
完全串行
|
||||
完全并行
|
||||
树形
|
||||
diamond
|
||||
随机 DAG
|
||||
CPU + External 混合 DAG
|
||||
```
|
||||
|
||||
记录:
|
||||
|
||||
* [ ] graph compile time
|
||||
* [ ] runtime setup time
|
||||
* [ ] scheduler wait
|
||||
* [ ] total CPU work
|
||||
* [ ] wall time
|
||||
* [ ] parallelism
|
||||
* [ ] Taskflow worker utilization
|
||||
* [ ] Runtime lock contention
|
||||
* [ ] completion callback cost
|
||||
|
||||
后续任何 scheduler 改造都与此 benchmark 对比。
|
||||
|
||||
---
|
||||
|
||||
### TODO-15:建立 Renderive 实际 workload benchmark
|
||||
|
||||
不要只做 synthetic benchmark。
|
||||
|
||||
至少保留:
|
||||
|
||||
* [ ] 纯 2D 场景
|
||||
* [ ] 小型 3D point scene
|
||||
* [ ] 大型 3D point scene
|
||||
* [ ] 多 Renderable dependency
|
||||
* [ ] frequent update
|
||||
* [ ] low-latency input
|
||||
* [ ] GPU-heavy
|
||||
* [ ] CPU-heavy
|
||||
* [ ] mixed CPU/GPU
|
||||
|
||||
每次架构改造比较:
|
||||
|
||||
```text
|
||||
P50
|
||||
P95
|
||||
P99
|
||||
|
||||
critical path
|
||||
scheduler wait
|
||||
CPU work
|
||||
GPU duration
|
||||
frame age
|
||||
memory
|
||||
thread count
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 暂时不要做的事情
|
||||
|
||||
下面这些目前没有证据证明值得做:
|
||||
|
||||
* [ ] **不要**再包一层复杂 Taskflow abstraction
|
||||
* [ ] **不要**给 `Scene_Render_Context` 增加 continuation
|
||||
* [ ] **不要**为了异步统一而全面 coroutine 化
|
||||
* [ ] **不要**现在实现 multi-frame-in-flight
|
||||
* [ ] **不要**仅为了减少线程数重写整个 Scene scheduler
|
||||
* [ ] **不要**仅为了“架构漂亮”拆 Scene_Base
|
||||
* [ ] **不要**重复实现 Taskflow 自己已经能完成的线程池能力
|
||||
* [ ] **不要**在没有 profiler 数据时优化 DAG 微小调度开销
|
||||
|
||||
---
|
||||
|
||||
# 建议执行顺序
|
||||
|
||||
```text
|
||||
第一阶段:正确性
|
||||
TODO-01 External Operation 生命周期
|
||||
TODO-02 failure / cancellation
|
||||
TODO-09 Datoviz 异常路径
|
||||
|
||||
第二阶段:长期运行稳定性
|
||||
TODO-03 Render Plan History
|
||||
TODO-10/11/12 观测数据可信度
|
||||
|
||||
第三阶段:CPU Runtime 成本
|
||||
TODO-04 Render Plan compiled layout
|
||||
TODO-14 synthetic benchmark
|
||||
TODO-15 real workload benchmark
|
||||
|
||||
第四阶段:规模化
|
||||
TODO-05 多 Scene thread model
|
||||
TODO-06 multi-frame-in-flight 是否真的需要
|
||||
|
||||
持续性约束
|
||||
TODO-07 Scene_Base 边界
|
||||
TODO-08 Taskflow implementation-detail 边界
|
||||
TODO-13 Low Latency 回归测试
|
||||
```
|
||||
|
||||
## 最终原则
|
||||
|
||||
每一项都严格走:
|
||||
|
||||
```text
|
||||
提出怀疑
|
||||
↓
|
||||
写最小复现 / benchmark
|
||||
↓
|
||||
确认问题是否真实存在
|
||||
↓
|
||||
量化严重程度
|
||||
↓
|
||||
定义行为 contract
|
||||
↓
|
||||
再修改实现
|
||||
↓
|
||||
回归测试 + benchmark
|
||||
```
|
||||
|
||||
不要变成:
|
||||
|
||||
```text
|
||||
看到一个理论缺陷
|
||||
↓
|
||||
立刻重新设计架构
|
||||
```
|
||||
|
||||
现阶段 Renderive 更需要的是**验证和收敛**,而不是继续扩张调度抽象。
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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>);
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user