This commit is contained in:
2026-08-12 09:45:18 +08:00
parent 9331b4cbf6
commit 0910b73ea4
22 changed files with 439 additions and 98 deletions
+119 -10
View File
@@ -1,5 +1,6 @@
#include "Scene_Base.hpp" #include "Scene_Base.hpp"
#include <algorithm> #include <algorithm>
#include <chrono>
#include <memory_resource> #include <memory_resource>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@@ -11,6 +12,12 @@
#include "renderive/state/base/State_Strategy_Base.hpp" #include "renderive/state/base/State_Strategy_Base.hpp"
static_assert(TF_VERSION == 400100, "Renderive requires Taskflow 4.1.0"); static_assert(TF_VERSION == 400100, "Renderive requires Taskflow 4.1.0");
namespace { namespace {
std::uint64_t steady_now_ns() noexcept {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
}
template <class Node_Accessor> template <class Node_Accessor>
Scene_Base::Renderable_List topological_order( Scene_Base::Renderable_List topological_order(
const Scene_Base::Renderable_List& renderables, const Scene_Base::Renderable_List& renderables,
@@ -331,6 +338,50 @@ std::vector<Scene_Base::Const_Renderable> Scene_Base::paint_order_snapshot() con
const auto ordered = display_order(*cache_renderables_); const auto ordered = display_order(*cache_renderables_);
return {ordered.begin(), ordered.end()}; return {ordered.begin(), ordered.end()};
} }
Task_Execution_Snapshot Scene_Base::task_execution_snapshot() const noexcept {
Task_Execution_Snapshot snapshot;
snapshot.worker_count = task_executor_worker_count();
snapshot.render_sequence =
task_execution_render_sequence_.load(std::memory_order_acquire);
snapshot.graph_node_count =
task_execution_node_count_.load(std::memory_order_acquire);
snapshot.completed_count =
task_execution_completed_count_.load(std::memory_order_acquire);
snapshot.running_count =
task_execution_running_count_.load(std::memory_order_acquire);
snapshot.peak_parallelism =
task_execution_peak_parallelism_.load(std::memory_order_acquire);
snapshot.failed_count =
task_execution_failed_count_.load(std::memory_order_acquire);
const std::size_t accounted = snapshot.completed_count + snapshot.running_count;
snapshot.pending_count = accounted < snapshot.graph_node_count
? snapshot.graph_node_count - accounted
: 0;
{
std::lock_guard lock(task_mutex_);
if (task_pending_)
snapshot.state = Task_Execution_State::Queued;
else if (rendering_)
snapshot.state = Task_Execution_State::Running;
else if (current_completion_ && current_completion_->completed)
snapshot.state = current_completion_->exception
? Task_Execution_State::Failed
: Task_Execution_State::Completed;
}
snapshot.duration_ns =
task_execution_duration_ns_.load(std::memory_order_acquire);
if (snapshot.state == Task_Execution_State::Running) {
const auto started =
task_execution_started_ns_.load(std::memory_order_acquire);
const auto now = steady_now_ns();
if (started != 0 && now >= started)
snapshot.duration_ns = now - started;
}
return snapshot;
}
std::size_t Scene_Base::task_executor_worker_count() noexcept {
return Execution_Context::executor().num_workers();
}
std::pmr::memory_resource& Scene_Base::memory_resource() const noexcept { std::pmr::memory_resource& Scene_Base::memory_resource() const noexcept {
return memory_domain_->resource(); return memory_domain_->resource();
} }
@@ -459,6 +510,20 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
}; };
std::pmr::monotonic_buffer_resource scratch_resource(&memory_resource()); std::pmr::monotonic_buffer_resource scratch_resource(&memory_resource());
tf::Taskflow scene_graph; tf::Taskflow scene_graph;
std::size_t planned_task_count{};
const auto tracked = [this, &planned_task_count](auto function) {
++planned_task_count;
return [this, function = std::move(function)]() mutable {
task_execution_started();
try {
function();
task_execution_finished(false);
} catch (...) {
task_execution_finished(true);
throw;
}
};
};
std::pmr::unordered_map<Renderable_Base*, std::size_t> indices(&scratch_resource); std::pmr::unordered_map<Renderable_Base*, std::size_t> indices(&scratch_resource);
indices.reserve(task.render_order.size()); indices.reserve(task.render_order.size());
std::pmr::vector<bool> render_flags(task.render_order.size(), false, &scratch_resource); std::pmr::vector<bool> render_flags(task.render_order.size(), false, &scratch_resource);
@@ -500,21 +565,21 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
std::pmr::vector<tf::Task> internal_tasks(&scratch_resource); std::pmr::vector<tf::Task> internal_tasks(&scratch_resource);
internal_tasks.reserve(nodes.size()); internal_tasks.reserve(nodes.size());
for (const auto& node : nodes) { for (const auto& node : nodes) {
internal_tasks.push_back(module.graph.emplace([this, function = node.function, context] { internal_tasks.push_back(module.graph.emplace(tracked([this, function = node.function, context] {
Render_Execution_Scope scope(*this); Render_Execution_Scope scope(*this);
function(context); function(context);
}).name(std::string(node.name))); })).name(std::string(node.name)));
} }
for (std::size_t node_index = 0; node_index < nodes.size(); ++node_index) { for (std::size_t node_index = 0; node_index < nodes.size(); ++node_index) {
for (std::size_t successor : nodes[node_index].successors) { for (std::size_t successor : nodes[node_index].successors) {
internal_tasks[node_index].precede(internal_tasks.at(successor)); internal_tasks[node_index].precede(internal_tasks.at(successor));
} }
} }
tf::Task mark_rendered = module.graph.emplace([&renderable, render_revision] { tf::Task mark_rendered = module.graph.emplace(tracked([&renderable, render_revision] {
renderable.mark_rendered(render_revision); renderable.mark_rendered(render_revision);
}).name("cache_publish"); })).name("cache_publish");
if (internal_tasks.empty()) { if (internal_tasks.empty()) {
module.graph.emplace([] {}).precede(mark_rendered); module.graph.emplace(tracked([] {})).precede(mark_rendered);
} else { } else {
for (std::size_t node_index = 0; node_index < nodes.size(); ++node_index) { for (std::size_t node_index = 0; node_index < nodes.size(); ++node_index) {
if (nodes[node_index].successors.empty()) { if (nodes[node_index].successors.empty()) {
@@ -523,7 +588,7 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
} }
} }
} else { } else {
module.graph.emplace([] {}).name("cache_hit"); module.graph.emplace(tracked([] {})).name("cache_hit");
} }
module.module_task = scene_graph.composed_of(module.graph).name("renderable"); module.module_task = scene_graph.composed_of(module.graph).name("renderable");
} }
@@ -538,18 +603,62 @@ void Scene_Base::execute_taskflow(Render_Task& task) {
} }
} }
const Scene_Render_Context final_context{this, task.frame_control_state, task.scene_state_revision, task.render_sequence, nullptr, nullptr}; const Scene_Render_Context final_context{this, task.frame_control_state, task.scene_state_revision, task.render_sequence, nullptr, nullptr};
tf::Task final_task = scene_graph.emplace([this, final_context, &task] { tf::Task final_task = scene_graph.emplace(tracked([this, final_context, &task] {
Render_Execution_Scope scope(*this); Render_Execution_Scope scope(*this);
generate_final_color_cache(final_context, task.display_order); generate_final_color_cache(final_context, task.display_order);
}).name("final_color_cache"); })).name("final_color_cache");
if (modules.empty()) { if (modules.empty()) {
scene_graph.emplace([] {}).precede(final_task); scene_graph.emplace(tracked([] {})).precede(final_task);
} else { } else {
for (Module& module : modules) { for (Module& module : modules) {
module.module_task.precede(final_task); module.module_task.precede(final_task);
} }
} }
Execution_Context::executor().run(scene_graph).get(); begin_task_execution(task.render_sequence);
set_task_execution_node_count(planned_task_count);
try {
Execution_Context::executor().run(scene_graph).get();
} catch (...) {
finish_task_execution();
throw;
}
finish_task_execution();
}
void Scene_Base::begin_task_execution(std::uint64_t render_sequence) noexcept {
task_execution_render_sequence_.store(render_sequence, std::memory_order_release);
task_execution_node_count_.store(0, std::memory_order_release);
task_execution_completed_count_.store(0, std::memory_order_release);
task_execution_running_count_.store(0, std::memory_order_release);
task_execution_peak_parallelism_.store(0, std::memory_order_release);
task_execution_failed_count_.store(0, std::memory_order_release);
task_execution_duration_ns_.store(0, std::memory_order_release);
task_execution_started_ns_.store(steady_now_ns(), std::memory_order_release);
}
void Scene_Base::set_task_execution_node_count(std::size_t count) noexcept {
task_execution_node_count_.store(count, std::memory_order_release);
}
void Scene_Base::task_execution_started() noexcept {
const std::size_t active =
task_execution_running_count_.fetch_add(1, std::memory_order_acq_rel) + 1;
std::size_t peak =
task_execution_peak_parallelism_.load(std::memory_order_acquire);
while (peak < active &&
!task_execution_peak_parallelism_.compare_exchange_weak(
peak, active, std::memory_order_acq_rel, std::memory_order_acquire)) {}
}
void Scene_Base::task_execution_finished(bool failed) noexcept {
if (failed)
task_execution_failed_count_.fetch_add(1, std::memory_order_acq_rel);
task_execution_completed_count_.fetch_add(1, std::memory_order_acq_rel);
task_execution_running_count_.fetch_sub(1, std::memory_order_acq_rel);
}
void Scene_Base::finish_task_execution() noexcept {
const auto started =
task_execution_started_ns_.load(std::memory_order_acquire);
const auto now = steady_now_ns();
task_execution_duration_ns_.store(
started != 0 && now >= started ? now - started : 0,
std::memory_order_release);
} }
void Scene_Base::validate_renderable_scene(const Renderable_Base& renderable) const { void Scene_Base::validate_renderable_scene(const Renderable_Base& renderable) const {
if (renderable.real_time_data_state_->scene_lifetime.get() != scene_lifetime_.get()) { if (renderable.real_time_data_state_->scene_lifetime.get() != scene_lifetime_.get()) {
+18 -1
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <array> #include <array>
#include <atomic>
#include <condition_variable> #include <condition_variable>
#include <concepts> #include <concepts>
#include <cstdint> #include <cstdint>
@@ -15,6 +16,7 @@
#include "renderive/renderable/base/Renderable_Base.hpp" #include "renderive/renderable/base/Renderable_Base.hpp"
#include "Scene_Lifetime.hpp" #include "Scene_Lifetime.hpp"
#include "Scene_Render_Context.hpp" #include "Scene_Render_Context.hpp"
#include "Task_Execution_Snapshot.hpp"
class Color_Cache; class Color_Cache;
class Scene_Base { class Scene_Base {
private: private:
@@ -65,6 +67,8 @@ public:
std::size_t renderable_count() const; std::size_t renderable_count() const;
Topology_Snapshot topology_snapshot() const; Topology_Snapshot topology_snapshot() const;
std::vector<Const_Renderable> paint_order_snapshot() const; std::vector<Const_Renderable> paint_order_snapshot() const;
[[nodiscard]] Task_Execution_Snapshot task_execution_snapshot() const noexcept;
[[nodiscard]] static std::size_t task_executor_worker_count() noexcept;
std::pmr::memory_resource& memory_resource() const noexcept; std::pmr::memory_resource& memory_resource() const noexcept;
std::pmr::memory_resource& upstream_memory_resource() const noexcept; std::pmr::memory_resource& upstream_memory_resource() const noexcept;
Frame_Control_Strategy_Base& frame_control_strategy(); Frame_Control_Strategy_Base& frame_control_strategy();
@@ -114,6 +118,11 @@ private:
}; };
void render_loop(); void render_loop();
void execute_taskflow(Render_Task& task); void execute_taskflow(Render_Task& task);
void begin_task_execution(std::uint64_t render_sequence) noexcept;
void set_task_execution_node_count(std::size_t count) noexcept;
void task_execution_started() noexcept;
void task_execution_finished(bool failed) noexcept;
void finish_task_execution() noexcept;
void validate_renderable_scene(const Renderable_Base& renderable) const; void validate_renderable_scene(const Renderable_Base& renderable) const;
bool is_renderable_attached_locked(const Renderable_Base& renderable) const; bool is_renderable_attached_locked(const Renderable_Base& renderable) const;
void validate_renderable_attached_locked(const Renderable_Base& renderable) const; void validate_renderable_attached_locked(const Renderable_Base& renderable) const;
@@ -125,7 +134,7 @@ private:
Renderable_List* render_renderables_; Renderable_List* render_renderables_;
Renderable_List* cache_renderables_; Renderable_List* cache_renderables_;
mutable std::mutex renderable_mutex_; mutable std::mutex renderable_mutex_;
std::recursive_mutex task_mutex_; mutable std::recursive_mutex task_mutex_;
std::condition_variable_any task_ready_; std::condition_variable_any task_ready_;
std::condition_variable_any render_completed_; std::condition_variable_any render_completed_;
std::thread worker_; std::thread worker_;
@@ -138,6 +147,14 @@ private:
bool task_pending_{}; bool task_pending_{};
bool rendering_{}; bool rendering_{};
bool stop_{}; bool stop_{};
std::atomic<std::uint64_t> task_execution_render_sequence_{};
std::atomic<std::size_t> task_execution_node_count_{};
std::atomic<std::size_t> task_execution_completed_count_{};
std::atomic<std::size_t> task_execution_running_count_{};
std::atomic<std::size_t> task_execution_peak_parallelism_{};
std::atomic<std::size_t> task_execution_failed_count_{};
std::atomic<std::uint64_t> task_execution_started_ns_{};
std::atomic<std::uint64_t> task_execution_duration_ns_{};
}; };
class Scene_2D_Base : public Scene_Base { class Scene_2D_Base : public Scene_Base {
public: public:
@@ -0,0 +1,25 @@
#pragma once
#include <cstddef>
#include <cstdint>
enum class Task_Execution_State : std::uint8_t {
Idle,
Queued,
Running,
Completed,
Failed
};
struct Task_Execution_Snapshot {
Task_Execution_State state{Task_Execution_State::Idle};
std::size_t worker_count{};
std::uint64_t render_sequence{};
std::size_t graph_node_count{};
std::size_t completed_count{};
std::size_t running_count{};
std::size_t pending_count{};
std::size_t peak_parallelism{};
std::size_t failed_count{};
std::uint64_t duration_ns{};
};
@@ -129,8 +129,25 @@ TEST(renderable_task_graph_concurrency_test, runs_independent_internal_tasks_con
auto renderable = std::make_shared<Renderable_Task_Graph_Concurrency_Test_Renderable>(scene, active, maximum); auto renderable = std::make_shared<Renderable_Task_Graph_Concurrency_Test_Renderable>(scene, active, maximum);
scene.attach_renderable(renderable); scene.attach_renderable(renderable);
scene.render(); scene.render();
while (active.load(std::memory_order_acquire) == 0)
std::this_thread::yield();
const auto running = scene.task_execution_snapshot();
EXPECT_EQ(running.state, Task_Execution_State::Running);
EXPECT_EQ(running.graph_node_count, 4u);
EXPECT_GT(running.running_count, 0u);
EXPECT_LT(running.completed_count, running.graph_node_count);
scene.wait_for_render(); scene.wait_for_render();
EXPECT_GE(maximum.load(std::memory_order_acquire), 2); EXPECT_GE(maximum.load(std::memory_order_acquire), 2);
const auto execution = scene.task_execution_snapshot();
EXPECT_EQ(execution.state, Task_Execution_State::Completed);
EXPECT_EQ(execution.worker_count, Scene_Base::task_executor_worker_count());
EXPECT_EQ(execution.graph_node_count, 4u);
EXPECT_EQ(execution.completed_count, execution.graph_node_count);
EXPECT_EQ(execution.running_count, 0u);
EXPECT_EQ(execution.pending_count, 0u);
EXPECT_GE(execution.peak_parallelism, 2u);
EXPECT_EQ(execution.failed_count, 0u);
EXPECT_GT(execution.duration_ns, 0u);
} }
#endif #endif
TEST(renderable_task_graph_test, rejects_tasks_from_different_graphs) { TEST(renderable_task_graph_test, rejects_tasks_from_different_graphs) {
@@ -38,6 +38,12 @@ TEST(scene_base_test, wait_for_render_propagates_background_render_failure) {
scene.attach_renderable(renderable); scene.attach_renderable(renderable);
scene.render(); scene.render();
EXPECT_THROW(scene.wait_for_render(), std::runtime_error); EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
const auto execution = scene.task_execution_snapshot();
EXPECT_EQ(execution.state, Task_Execution_State::Failed);
EXPECT_EQ(execution.graph_node_count, 3u);
EXPECT_EQ(execution.failed_count, 1u);
EXPECT_EQ(execution.running_count, 0u);
EXPECT_GT(execution.duration_ns, 0u);
} }
TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_failure) { TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_failure) {
{ {
+12
View File
@@ -553,6 +553,18 @@ Frame_Observer_Snapshot Plot_Core::frame_observer_snapshot() const {
snapshot.next_refresh_interval_ns = state.next_refresh_interval_ns; snapshot.next_refresh_interval_ns = state.next_refresh_interval_ns;
snapshot.end_to_end_ns = state.end_to_end_ns; snapshot.end_to_end_ns = state.end_to_end_ns;
} }
const auto task_execution = with_scene(
impl_->scene,
[](const auto& scene) { return scene.task_execution_snapshot(); });
snapshot.task_execution_state = task_execution.state;
snapshot.task_worker_count = task_execution.worker_count;
snapshot.task_graph_node_count = task_execution.graph_node_count;
snapshot.task_completed_node_count = task_execution.completed_count;
snapshot.task_running_node_count = task_execution.running_count;
snapshot.task_pending_node_count = task_execution.pending_count;
snapshot.task_peak_parallelism = task_execution.peak_parallelism;
snapshot.task_failed_node_count = task_execution.failed_count;
snapshot.task_graph_duration_ns = task_execution.duration_ns;
return snapshot; return snapshot;
} }
+10
View File
@@ -3,6 +3,7 @@
#include "../base/Types.h" #include "../base/Types.h"
#include "../event/Event.h" #include "../event/Event.h"
#include <renderive/frame_control/Frame_Consumer_Feedback.hpp> #include <renderive/frame_control/Frame_Consumer_Feedback.hpp>
#include <renderive/scene/base/Task_Execution_Snapshot.hpp>
#include <atomic> #include <atomic>
#include <concepts> #include <concepts>
@@ -69,6 +70,15 @@ struct Frame_Observer_Snapshot {
std::uint64_t render_finish_state_wait_ns{}; std::uint64_t render_finish_state_wait_ns{};
std::uint64_t queue_wait_ns{}; std::uint64_t queue_wait_ns{};
std::uint64_t end_to_end_ns{}; std::uint64_t end_to_end_ns{};
Task_Execution_State task_execution_state{Task_Execution_State::Idle};
std::size_t task_worker_count{};
std::size_t task_graph_node_count{};
std::size_t task_completed_node_count{};
std::size_t task_running_node_count{};
std::size_t task_pending_node_count{};
std::size_t task_peak_parallelism{};
std::size_t task_failed_node_count{};
std::uint64_t task_graph_duration_ns{};
}; };
class Presentation_Sink { class Presentation_Sink {
+30 -17
View File
@@ -4,7 +4,6 @@
#include "../render/Blend2D_Cache.h" #include "../render/Blend2D_Cache.h"
#include "../renderable/Render_Partition.h" #include "../renderable/Render_Partition.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <deque> #include <deque>
namespace renderive { namespace renderive {
namespace detail { namespace detail {
@@ -66,31 +65,39 @@ void Afterglow_Control::publish() {
} }
void Afterglow_Control::build_paint_task_graph(Renderable_Task_Graph& graph) { void Afterglow_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
const auto prepare = graph.emplace([this](const Scene_Render_Context&) { auto& output = impl_->render_frame;
prepare_render_frame(render_state_view()); const int partition_count = output.partitioner.graph_partition_count(
render_properties(render_state_view()).partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()));
const auto prepare = graph.emplace([this, partition_count](const Scene_Render_Context&) {
prepare_render_frame(render_state_view(), partition_count);
}, "prepare afterglow"); }, "prepare afterglow");
std::array<Renderable_Task_Graph::Task, maximum_render_partitions> accumulation; std::vector<Renderable_Task_Graph::Task> accumulation;
std::array<Renderable_Task_Graph::Task, maximum_render_partitions> coloring; accumulation.reserve(static_cast<std::size_t>(partition_count));
for (int index = 0; index < maximum_render_partitions; ++index) { for (int index = 0; index < partition_count; ++index) {
accumulation[static_cast<std::size_t>(index)] = graph.emplace( const auto task = graph.emplace(
[this, index](const Scene_Render_Context&) { [this, index](const Scene_Render_Context&) {
accumulate_partition(render_state_view(), index); accumulate_partition(render_state_view(), index);
}, },
"accumulate afterglow partition"); "accumulate afterglow partition " + std::to_string(index + 1));
graph.precede(prepare, accumulation[static_cast<std::size_t>(index)]); graph.precede(prepare, task);
accumulation.push_back(task);
} }
const auto normalize = graph.emplace([this](const Scene_Render_Context&) { const auto normalize = graph.emplace([this](const Scene_Render_Context&) {
normalize_render_frame(); normalize_render_frame();
}, "normalize afterglow"); }, "normalize afterglow");
for (const auto task : accumulation) for (const auto task : accumulation)
graph.precede(task, normalize); graph.precede(task, normalize);
for (int index = 0; index < maximum_render_partitions; ++index) { std::vector<Renderable_Task_Graph::Task> coloring;
coloring[static_cast<std::size_t>(index)] = graph.emplace( coloring.reserve(static_cast<std::size_t>(partition_count));
for (int index = 0; index < partition_count; ++index) {
const auto task = graph.emplace(
[this, index](const Scene_Render_Context&) { [this, index](const Scene_Render_Context&) {
color_partition(render_state_view(), index); color_partition(render_state_view(), index);
}, },
"color afterglow partition"); "color afterglow partition " + std::to_string(index + 1));
graph.precede(normalize, coloring[static_cast<std::size_t>(index)]); graph.precede(normalize, task);
coloring.push_back(task);
} }
const auto compose = add_paint_task( const auto compose = add_paint_task(
graph, "compose afterglow", graph, "compose afterglow",
@@ -99,7 +106,8 @@ void Afterglow_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
graph.precede(task, compose); graph.precede(task, compose);
} }
void Afterglow_Control::prepare_render_frame(const Render_State_View& view) { void Afterglow_Control::prepare_render_frame(const Render_State_View& view,
int graph_partition_count) {
const auto& state = render_properties(view); const auto& state = render_properties(view);
const auto& history = view.get(impl_->history); const auto& history = view.get(impl_->history);
auto& output = impl_->render_frame; auto& output = impl_->render_frame;
@@ -125,8 +133,8 @@ void Afterglow_Control::prepare_render_frame(const Render_State_View& view) {
output.work_size = static_cast<std::size_t>(width) * height; output.work_size = static_cast<std::size_t>(width) * height;
output.intensity.resize(output.work_size); output.intensity.resize(output.work_size);
output.pixels.resize(output.work_size); output.pixels.resize(output.work_size);
output.active_partitions = output.active_partitions = output.partitioner.begin(graph_partition_count,
output.partitioner.begin(state.partition_count.get(), output.work_size); output.work_size);
output.valid = true; output.valid = true;
} }
@@ -189,7 +197,12 @@ void Afterglow_Control::paint(Painter& painter, const Render_State_View& view) {
return; return;
painter.heatmap(output.layout.target, output.layout.width, output.layout.height, painter.heatmap(output.layout.target, output.layout.width, output.layout.height,
output.pixels, Image_Interpolation_Mode::Bilinear); output.pixels, Image_Interpolation_Mode::Bilinear);
output.partitioner.finish(output.work_size); const auto& state = render_properties(view);
if (output.partitioner.finish(
state.partition_count.get(), output.active_partitions,
static_cast<int>(Scene_Base::task_executor_worker_count()),
output.work_size))
task_graph_changed();
} }
} }
} }
+1 -1
View File
@@ -34,7 +34,7 @@ protected:
private: private:
struct Impl; struct Impl;
std::unique_ptr<Impl> impl_; std::unique_ptr<Impl> impl_;
void prepare_render_frame(const Render_State_View& state); void prepare_render_frame(const Render_State_View& state, int graph_partition_count);
void accumulate_partition(const Render_State_View& state, int partition_index); void accumulate_partition(const Render_State_View& state, int partition_index);
void normalize_render_frame(); void normalize_render_frame();
void color_partition(const Render_State_View& state, int partition_index); void color_partition(const Render_State_View& state, int partition_index);
+9
View File
@@ -60,6 +60,15 @@ public:
template <auto Member, Property_Member_Assignable<Properties, Member> Value> template <auto Member, Property_Member_Assignable<Properties, Member> Value>
Plottable_State& set(Value&& value) { Plottable_State& set(Value&& value) {
Base::template set<Member>(std::forward<Value>(value)); Base::template set<Member>(std::forward<Value>(value));
if constexpr (requires { &Properties::partition_count; }) {
if constexpr (std::same_as<decltype(Member),
decltype(&Properties::partition_count)>) {
if constexpr (Member == &Properties::partition_count) {
this->task_graph_changed();
return *this;
}
}
}
this->changed(); this->changed();
return *this; return *this;
} }
+24 -13
View File
@@ -4,7 +4,6 @@
#include "../render/Blend2D_Cache.h" #include "../render/Blend2D_Cache.h"
#include "../renderable/Render_Partition.h" #include "../renderable/Render_Partition.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <cmath> #include <cmath>
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
@@ -25,7 +24,7 @@ struct Spectrum_Interaction {
using Spectrum_Interaction_State = Double_State_Strategy<Spectrum_Interaction_Base, Spectrum_Interaction>; using Spectrum_Interaction_State = Double_State_Strategy<Spectrum_Interaction_Base, Spectrum_Interaction>;
struct Spectrum_Render_Frame { struct Spectrum_Render_Frame {
Adaptive_Render_Partitioner partitioner; Adaptive_Render_Partitioner partitioner;
std::array<Blend2D_Color_Cache, maximum_render_partitions> layers; std::vector<Blend2D_Color_Cache> layers;
int active_partitions{1}; int active_partitions{1};
std::size_t work_size{}; std::size_t work_size{};
bool valid{}; bool valid{};
@@ -244,17 +243,24 @@ void Spectrum_Control::publish() {
} }
void Spectrum_Control::build_paint_task_graph(Renderable_Task_Graph& graph) { void Spectrum_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
const auto prepare = graph.emplace([this](const Scene_Render_Context&) { auto& output = impl_->render_frame;
prepare_render_frame(render_state_view()); const int partition_count = output.partitioner.graph_partition_count(
render_properties(render_state_view()).partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()));
output.layers.resize(static_cast<std::size_t>(partition_count));
const auto prepare = graph.emplace([this, partition_count](const Scene_Render_Context&) {
prepare_render_frame(render_state_view(), partition_count);
}, "prepare spectrum"); }, "prepare spectrum");
std::array<Renderable_Task_Graph::Task, maximum_render_partitions> partitions; std::vector<Renderable_Task_Graph::Task> partitions;
for (int index = 0; index < maximum_render_partitions; ++index) { partitions.reserve(static_cast<std::size_t>(partition_count));
partitions[static_cast<std::size_t>(index)] = graph.emplace( for (int index = 0; index < partition_count; ++index) {
const auto partition = graph.emplace(
[this, index](const Scene_Render_Context&) { [this, index](const Scene_Render_Context&) {
render_partition(render_state_view(), index); render_partition(render_state_view(), index);
}, },
"paint spectrum partition"); "paint spectrum partition " + std::to_string(index + 1));
graph.precede(prepare, partitions[static_cast<std::size_t>(index)]); graph.precede(prepare, partition);
partitions.push_back(partition);
} }
const auto compose = add_paint_task( const auto compose = add_paint_task(
graph, "compose spectrum", graph, "compose spectrum",
@@ -263,7 +269,8 @@ void Spectrum_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
graph.precede(task, compose); graph.precede(task, compose);
} }
void Spectrum_Control::prepare_render_frame(const Render_State_View& view) { void Spectrum_Control::prepare_render_frame(const Render_State_View& view,
int graph_partition_count) {
const auto& state = render_properties(view); const auto& state = render_properties(view);
const auto& published_frame = view.get(impl_->frame); const auto& published_frame = view.get(impl_->frame);
auto& output = impl_->render_frame; auto& output = impl_->render_frame;
@@ -271,8 +278,8 @@ void Spectrum_Control::prepare_render_frame(const Render_State_View& view) {
const Axis_Transform power_axis = impl_->power_axis->transform(view); const Axis_Transform power_axis = impl_->power_axis->transform(view);
output.valid = axes_are_orthogonal(frequency_axis, power_axis); output.valid = axes_are_orthogonal(frequency_axis, power_axis);
output.work_size = published_frame ? published_frame->samples.size() : 0; output.work_size = published_frame ? published_frame->samples.size() : 0;
output.active_partitions = output.active_partitions = output.partitioner.begin(graph_partition_count,
output.partitioner.begin(state.partition_count.get(), output.work_size); output.work_size);
for (int index = 0; index < output.active_partitions; ++index) for (int index = 0; index < output.active_partitions; ++index)
output.layers[static_cast<std::size_t>(index)].clear(); output.layers[static_cast<std::size_t>(index)].clear();
} }
@@ -380,7 +387,11 @@ void Spectrum_Control::paint(Painter& painter, const Render_State_View& view) {
painter.text({box.x + 4.0, box.y + 3.0}, text.str(), state.tooltip_font, state.tooltip_text_pen); painter.text({box.x + 4.0, box.y + 3.0}, text.str(), state.tooltip_font, state.tooltip_text_pen);
} }
} }
output.partitioner.finish(output.work_size); if (output.partitioner.finish(
state.partition_count.get(), output.active_partitions,
static_cast<int>(Scene_Base::task_executor_worker_count()),
output.work_size))
task_graph_changed();
} }
} }
} }
+1 -1
View File
@@ -65,7 +65,7 @@ protected:
private: private:
struct Impl; struct Impl;
std::unique_ptr<Impl> impl_; std::unique_ptr<Impl> impl_;
void prepare_render_frame(const Render_State_View& state); void prepare_render_frame(const Render_State_View& state, int graph_partition_count);
void render_partition(const Render_State_View& state, int partition_index); void render_partition(const Render_State_View& state, int partition_index);
void publish() override; void publish() override;
}; };
+22 -12
View File
@@ -3,7 +3,6 @@
#include "Plottable_Real_Time_Data.h" #include "Plottable_Real_Time_Data.h"
#include "../renderable/Render_Partition.h" #include "../renderable/Render_Partition.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <deque> #include <deque>
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
@@ -98,17 +97,23 @@ void Waterfall_Control::publish() {
} }
void Waterfall_Control::build_paint_task_graph(Renderable_Task_Graph& graph) { void Waterfall_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
const auto prepare = graph.emplace([this](const Scene_Render_Context&) { auto& output = impl_->render_frame;
prepare_render_frame(render_state_view()); const int partition_count = output.partitioner.graph_partition_count(
render_properties(render_state_view()).partition_count.get(),
static_cast<int>(Scene_Base::task_executor_worker_count()));
const auto prepare = graph.emplace([this, partition_count](const Scene_Render_Context&) {
prepare_render_frame(render_state_view(), partition_count);
}, "prepare waterfall"); }, "prepare waterfall");
std::array<Renderable_Task_Graph::Task, maximum_render_partitions> partitions; std::vector<Renderable_Task_Graph::Task> partitions;
for (int index = 0; index < maximum_render_partitions; ++index) { partitions.reserve(static_cast<std::size_t>(partition_count));
partitions[static_cast<std::size_t>(index)] = graph.emplace( for (int index = 0; index < partition_count; ++index) {
const auto partition = graph.emplace(
[this, index](const Scene_Render_Context&) { [this, index](const Scene_Render_Context&) {
render_partition(render_state_view(), index); render_partition(render_state_view(), index);
}, },
"raster waterfall partition"); "raster waterfall partition " + std::to_string(index + 1));
graph.precede(prepare, partitions[static_cast<std::size_t>(index)]); graph.precede(prepare, partition);
partitions.push_back(partition);
} }
const auto compose = add_paint_task( const auto compose = add_paint_task(
graph, "compose waterfall", graph, "compose waterfall",
@@ -117,7 +122,8 @@ void Waterfall_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
graph.precede(partition, compose); graph.precede(partition, compose);
} }
void Waterfall_Control::prepare_render_frame(const Render_State_View& view) { void Waterfall_Control::prepare_render_frame(const Render_State_View& view,
int graph_partition_count) {
const auto& state = render_properties(view); const auto& state = render_properties(view);
const auto& rows = view.get(impl_->rows); const auto& rows = view.get(impl_->rows);
auto& output = impl_->render_frame; auto& output = impl_->render_frame;
@@ -150,8 +156,8 @@ void Waterfall_Control::prepare_render_frame(const Render_State_View& view) {
output.source_height = height; output.source_height = height;
output.work_size = static_cast<std::size_t>(width) * height; output.work_size = static_cast<std::size_t>(width) * height;
output.pixels.resize(output.work_size); output.pixels.resize(output.work_size);
output.active_partitions = output.active_partitions = output.partitioner.begin(graph_partition_count,
output.partitioner.begin(state.partition_count.get(), output.work_size); output.work_size);
output.valid = true; output.valid = true;
} }
@@ -181,7 +187,11 @@ void Waterfall_Control::paint(Painter& painter, const Render_State_View& view) {
return; return;
painter.heatmap(output.layout.target, output.layout.width, output.layout.height, painter.heatmap(output.layout.target, output.layout.width, output.layout.height,
output.pixels, state.interpolation_mode); output.pixels, state.interpolation_mode);
output.partitioner.finish(output.work_size); if (output.partitioner.finish(
state.partition_count.get(), output.active_partitions,
static_cast<int>(Scene_Base::task_executor_worker_count()),
output.work_size))
task_graph_changed();
if(state.tooltip_enabled && interaction.tooltip.active && output.layout.target.contains(interaction.tooltip.position)) { if(state.tooltip_enabled && interaction.tooltip.active && output.layout.target.contains(interaction.tooltip.position)) {
const Axis_Transform frequency_axis = impl_->frequency_axis->transform(view); const Axis_Transform frequency_axis = impl_->frequency_axis->transform(view);
const double frequency = frequency_axis.point_to_coord(interaction.tooltip.position); const double frequency = frequency_axis.point_to_coord(interaction.tooltip.position);
+1 -1
View File
@@ -41,7 +41,7 @@ protected:
private: private:
struct Impl; struct Impl;
std::unique_ptr<Impl> impl_; std::unique_ptr<Impl> impl_;
void prepare_render_frame(const Render_State_View& state); void prepare_render_frame(const Render_State_View& state, int graph_partition_count);
void render_partition(const Render_State_View& state, int partition_index); void render_partition(const Render_State_View& state, int partition_index);
void publish() override; void publish() override;
}; };
+33 -32
View File
@@ -3,12 +3,10 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <cstddef> #include <cstddef>
#include <thread> #include <limits>
namespace renderive::detail { namespace renderive::detail {
inline constexpr int maximum_render_partitions = 16;
struct Render_Partition_Range { struct Render_Partition_Range {
std::size_t first{}; std::size_t first{};
std::size_t last{}; std::size_t last{};
@@ -25,45 +23,48 @@ inline Render_Partition_Range render_partition_range(std::size_t size,
class Adaptive_Render_Partitioner { class Adaptive_Render_Partitioner {
public: public:
Adaptive_Render_Partitioner() noexcept [[nodiscard]] int graph_partition_count(int configured_count,
: automatic_count_(std::clamp( int worker_count) noexcept {
static_cast<int>(std::thread::hardware_concurrency()), 1, if (configured_count > 0)
maximum_render_partitions)) {} return configured_count;
if (automatic_count_ == 0)
int begin(int configured_count, std::size_t work_size) noexcept { automatic_count_ = std::max(1, worker_count);
automatic_ = configured_count == 0; automatic_count_ = std::clamp(automatic_count_, 1,
const int requested = automatic_ ? automatic_count_ : configured_count; std::max(1, worker_count));
const int useful = static_cast<int>( return automatic_count_;
std::min<std::size_t>(maximum_render_partitions,
std::max<std::size_t>(1, work_size)));
active_count_ = std::clamp(requested, 1,
std::min(maximum_render_partitions, useful));
started_at_ = Clock::now();
return active_count_;
} }
void finish(std::size_t work_size) noexcept { int begin(int graph_partition_count, std::size_t work_size) noexcept {
if (!automatic_) const int useful = static_cast<int>(std::min<std::size_t>(
return; static_cast<std::size_t>(std::numeric_limits<int>::max()),
std::max<std::size_t>(1, work_size)));
started_at_ = Clock::now();
return std::clamp(graph_partition_count, 1, useful);
}
[[nodiscard]] bool finish(int configured_count, int active_count,
int worker_count, std::size_t work_size) noexcept {
if (configured_count != 0)
return false;
const auto elapsed = const auto elapsed =
std::chrono::duration_cast<std::chrono::microseconds>(Clock::now() - started_at_); std::chrono::duration_cast<std::chrono::microseconds>(Clock::now() - started_at_);
constexpr auto target = std::chrono::microseconds(1500); constexpr auto target = std::chrono::microseconds(1500);
if (elapsed > target * 2 && active_count_ < maximum_render_partitions && const int previous = automatic_count_;
work_size / static_cast<std::size_t>(active_count_) >= 512) { const int maximum = std::max(1, worker_count);
automatic_count_ = std::min(maximum_render_partitions, active_count_ + 1); if (elapsed > target * 2 && active_count < maximum &&
} else if (elapsed < target / 2 && active_count_ > 1) { work_size / static_cast<std::size_t>(active_count) >= 512)
automatic_count_ = active_count_ - 1; automatic_count_ = std::min(maximum, active_count + 1);
} else { else if (elapsed < target / 2 && active_count > 1)
automatic_count_ = active_count_; automatic_count_ = active_count - 1;
} else
automatic_count_ = active_count;
return automatic_count_ != previous;
} }
private: private:
using Clock = std::chrono::steady_clock; using Clock = std::chrono::steady_clock;
Clock::time_point started_at_{}; Clock::time_point started_at_{};
int automatic_count_{1}; int automatic_count_{};
int active_count_{1};
bool automatic_{true};
}; };
} // namespace renderive::detail } // namespace renderive::detail
+5
View File
@@ -88,6 +88,11 @@ void Renderable::changed() noexcept {
plot_.notify_model_dirty(); plot_.notify_model_dirty();
} }
void Renderable::task_graph_changed() noexcept {
rebuild_task_graph();
plot_.notify_model_dirty();
}
Size Renderable::viewport_size() const noexcept { Size Renderable::viewport_size() const noexcept {
return plot_.viewport_size(); return plot_.viewport_size();
} }
+1
View File
@@ -50,6 +50,7 @@ protected:
std::string name, std::string name,
Paint_Task_Function function); Paint_Task_Function function);
void changed() noexcept; void changed() noexcept;
void task_graph_changed() noexcept;
[[nodiscard]] Size viewport_size() const noexcept; [[nodiscard]] Size viewport_size() const noexcept;
private: private:
@@ -652,7 +652,7 @@ TEST(Renderive_Core2, AxisRasterLayoutCoversAxisSwapAndEveryReversal) {
EXPECT_DOUBLE_EQ(points.back().x, 130.0); EXPECT_DOUBLE_EQ(points.back().x, 130.0);
EXPECT_DOUBLE_EQ(points.back().y, 30.0); EXPECT_DOUBLE_EQ(points.back().y, 30.0);
} }
TEST(Renderive_Core2, PartitionedPlotsBuildExplicitInternalTaskGraphs) { TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenTaskGraphs) {
Plot_Core plot; Plot_Core plot;
plot.init(); plot.init();
const auto root = plot.root_renderable(); const auto root = plot.root_renderable();
@@ -680,9 +680,26 @@ TEST(Renderive_Core2, PartitionedPlotsBuildExplicitInternalTaskGraphs) {
EXPECT_EQ(spectrum->get<&Spectrum::Properties::partition_count>(), 4); EXPECT_EQ(spectrum->get<&Spectrum::Properties::partition_count>(), 4);
EXPECT_EQ(waterfall->get<&Waterfall::Properties::partition_count>(), 4); EXPECT_EQ(waterfall->get<&Waterfall::Properties::partition_count>(), 4);
EXPECT_EQ(afterglow->get<&Afterglow::Properties::partition_count>(), 4); EXPECT_EQ(afterglow->get<&Afterglow::Properties::partition_count>(), 4);
EXPECT_EQ(spectrum->task_graph()->nodes().size(), 18u); EXPECT_EQ(spectrum->task_graph()->nodes().size(), 6u);
EXPECT_EQ(waterfall->task_graph()->nodes().size(), 18u); EXPECT_EQ(waterfall->task_graph()->nodes().size(), 6u);
EXPECT_EQ(afterglow->task_graph()->nodes().size(), 35u); EXPECT_EQ(afterglow->task_graph()->nodes().size(), 11u);
spectrum->set<&Spectrum::Properties::partition_count>(1);
waterfall->set<&Waterfall::Properties::partition_count>(1);
afterglow->set<&Afterglow::Properties::partition_count>(1);
spectrum->scene().publish_frame_state();
EXPECT_EQ(spectrum->task_graph()->nodes().size(), 3u);
EXPECT_EQ(waterfall->task_graph()->nodes().size(), 3u);
EXPECT_EQ(afterglow->task_graph()->nodes().size(), 5u);
spectrum->set<&Spectrum::Properties::partition_count>(0);
waterfall->set<&Waterfall::Properties::partition_count>(0);
afterglow->set<&Afterglow::Properties::partition_count>(0);
spectrum->scene().publish_frame_state();
const auto workers = Scene_Base::task_executor_worker_count();
EXPECT_EQ(spectrum->task_graph()->nodes().size(), workers + 2u);
EXPECT_EQ(waterfall->task_graph()->nodes().size(), workers + 2u);
EXPECT_EQ(afterglow->task_graph()->nodes().size(), workers * 2u + 3u);
} }
TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) {
Plot_Core plot; Plot_Core plot;
+15 -1
View File
@@ -81,7 +81,21 @@ struct Type_Descriptor<renderive::Frame_Observer_Snapshot> {
ADMINIVE_FIELD_LABEL(T, render_state_wait_ns, "Render State 等待"), ADMINIVE_FIELD_LABEL(T, render_state_wait_ns, "Render State 等待"),
ADMINIVE_FIELD_LABEL(T, render_finish_state_wait_ns, "Render Finish 等待"), ADMINIVE_FIELD_LABEL(T, render_finish_state_wait_ns, "Render Finish 等待"),
ADMINIVE_FIELD_LABEL(T, queue_wait_ns, "回放队列等待"), ADMINIVE_FIELD_LABEL(T, queue_wait_ns, "回放队列等待"),
ADMINIVE_FIELD_LABEL(T, end_to_end_ns, "端到端延迟")) ADMINIVE_FIELD_LABEL(T, end_to_end_ns, "端到端延迟"),
ADMINIVE_FIELD_LABEL(T, task_execution_state, "任务执行状态")
.enum_label<Task_Execution_State::Idle>("空闲")
.enum_label<Task_Execution_State::Queued>("已排队")
.enum_label<Task_Execution_State::Running>("执行中")
.enum_label<Task_Execution_State::Completed>("已完成")
.enum_label<Task_Execution_State::Failed>("失败"),
ADMINIVE_FIELD_LABEL(T, task_worker_count, "Taskflow 工作线程"),
ADMINIVE_FIELD_LABEL(T, task_graph_node_count, "任务图节点"),
ADMINIVE_FIELD_LABEL(T, task_completed_node_count, "已完成节点"),
ADMINIVE_FIELD_LABEL(T, task_running_node_count, "运行中节点"),
ADMINIVE_FIELD_LABEL(T, task_pending_node_count, "待执行节点"),
ADMINIVE_FIELD_LABEL(T, task_peak_parallelism, "峰值并行度"),
ADMINIVE_FIELD_LABEL(T, task_failed_node_count, "失败节点"),
ADMINIVE_FIELD_LABEL(T, task_graph_duration_ns, "任务图耗时"))
.label("Kernel Observer"); .label("Kernel Observer");
} }
}; };
+15 -1
View File
@@ -85,7 +85,7 @@ adminive::Table_View action_view();
namespace adminive { namespace adminive {
template <> template <>
struct Type_Descriptor<renderive::web::gallery_detail::Case_Model> { struct <renderive::web::gallery_detail::Case_Model> {
static auto get() { static auto get() {
using T = renderive::web::gallery_detail::Case_Model; using T = renderive::web::gallery_detail::Case_Model;
return object<T>("renderive_gallery_case", return object<T>("renderive_gallery_case",
@@ -312,6 +312,20 @@ Json dashboard_contract() {
std::move(point_pair), std::move(bottleneck), std::move(point_pair), std::move(bottleneck),
described_dashboard_field<renderive::Frame_Observer_Snapshot>( described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "last_event"), "kernel_observer", "last_event"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "task_execution_state"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "task_worker_count"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "task_graph_node_count"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "task_running_node_count"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "task_pending_node_count"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "task_peak_parallelism"),
described_dashboard_field<renderive::Frame_Observer_Snapshot>(
"kernel_observer", "task_graph_duration_ns"),
dashboard_field("像素超时", "client_performance.frame_request_timeout_count"), dashboard_field("像素超时", "client_performance.frame_request_timeout_count"),
dashboard_field("最近像素龄", "client_performance.last_pixel_receive_age_ms", "milliseconds", 0) dashboard_field("最近像素龄", "client_performance.last_pixel_receive_age_ms", "milliseconds", 0)
})} })}
+6 -3
View File
@@ -348,7 +348,8 @@ struct Type_Descriptor<renderive::web::Gallery_Time_Axis> {
RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "spectrum", "Spectrum", RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "spectrum", "Spectrum",
property_number<T, &P::frequency_point_size>("frequency_point_size", "Frequency points"), property_number<T, &P::frequency_point_size>("frequency_point_size", "Frequency points"),
property_number<T, &P::partition_count>("partition_count", "Render partitions"), property_number<T, &P::partition_count>("partition_count", "Render partitions")
.description("0 = automatic, 1 = single task, N = N parallel partitions"),
property_object<T, &P::frequency_range>("frequency_range", "Frequency range"), property_object<T, &P::frequency_range>("frequency_range", "Frequency range"),
property_number<T, &P::center_frequency>("center_frequency", "Center frequency"), property_number<T, &P::center_frequency>("center_frequency", "Center frequency"),
property_object<T, &P::sweep_frequency_range>("sweep_frequency_range", "Sweep range"), property_object<T, &P::sweep_frequency_range>("sweep_frequency_range", "Sweep range"),
@@ -378,7 +379,8 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall_Properties, "waterfall
property_object<T, &P::frequency_range>("frequency_range", "Frequency range"), property_object<T, &P::frequency_range>("frequency_range", "Frequency range"),
property_object<T, &P::power_range>("power_range", "Power range"), property_object<T, &P::power_range>("power_range", "Power range"),
property_number<T, &P::frequency_bin_count>("frequency_bin_count", "Frequency bins"), property_number<T, &P::frequency_bin_count>("frequency_bin_count", "Frequency bins"),
property_number<T, &P::partition_count>("partition_count", "Render partitions"), property_number<T, &P::partition_count>("partition_count", "Render partitions")
.description("0 = automatic, 1 = single task, N = N parallel partitions"),
property_boolean<T, &P::visible_range_only>("visible_range_only", "Visible range only"), property_boolean<T, &P::visible_range_only>("visible_range_only", "Visible range only"),
property_select<T, &P::interpolation_mode>("interpolation_mode", "Interpolation"), property_select<T, &P::interpolation_mode>("interpolation_mode", "Interpolation"),
property_select<T, &P::color_map>("color_map", "Color map"), property_select<T, &P::color_map>("color_map", "Color map"),
@@ -392,7 +394,8 @@ RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow_Properties, "afterglow
property_object<T, &P::power_range>("power_range", "Power range"), property_object<T, &P::power_range>("power_range", "Power range"),
property_number<T, &P::frequency_point_size>("frequency_point_size", "Frequency points"), property_number<T, &P::frequency_point_size>("frequency_point_size", "Frequency points"),
property_number<T, &P::power_point_size>("power_point_size", "Power points"), property_number<T, &P::power_point_size>("power_point_size", "Power points"),
property_number<T, &P::partition_count>("partition_count", "Render partitions"), property_number<T, &P::partition_count>("partition_count", "Render partitions")
.description("0 = automatic, 1 = single task, N = N parallel partitions"),
property_boolean<T, &P::interpolate>("interpolate", "Interpolate power"), property_boolean<T, &P::interpolate>("interpolate", "Interpolate power"),
property_number<T, &P::attenuation_rate>("attenuation_rate", "Attenuation"), property_number<T, &P::attenuation_rate>("attenuation_rate", "Attenuation"),
property_select<T, &P::color_map>("color_map", "Color map")); property_select<T, &P::color_map>("color_map", "Color map"));
+48 -1
View File
@@ -232,7 +232,7 @@ TEST(RenderiveWebGallery, AdminiveCatalogCoversEveryControlCaseAndThreeModes) {
TEST(RenderiveWebGallery, CatalogOwnsDashboardFieldsAndDynamicBehavior) { TEST(RenderiveWebGallery, CatalogOwnsDashboardFieldsAndDynamicBehavior) {
const nlohmann::json catalog = parse_json(Gallery_Protocol::catalog_json()); const nlohmann::json catalog = parse_json(Gallery_Protocol::catalog_json());
const auto& dashboard = catalog.at("dashboard"); const auto& dashboard = catalog.at("dashboard");
EXPECT_EQ(dashboard.at("performance").at("fields").size(), 15U); EXPECT_EQ(dashboard.at("performance").at("fields").size(), 22U);
EXPECT_EQ(dashboard.at("limits").at("fields").size(), 4U); EXPECT_EQ(dashboard.at("limits").at("fields").size(), 4U);
EXPECT_EQ(dashboard.at("observer").at("sections").size(), 4U); EXPECT_EQ(dashboard.at("observer").at("sections").size(), 4U);
std::set<std::string> client_sources; std::set<std::string> client_sources;
@@ -291,6 +291,13 @@ TEST(RenderiveWebGallery, ObserverTelemetryAndDashboardUseAdminiveDescriptors) {
for (const auto& field : feedback_descriptor.at("fields")) for (const auto& field : feedback_descriptor.at("fields"))
EXPECT_TRUE(feedback.contains(field.at("name").get<std::string>())); EXPECT_TRUE(feedback.contains(field.at("name").get<std::string>()));
EXPECT_EQ(observer.at("mode"), "manual"); EXPECT_EQ(observer.at("mode"), "manual");
EXPECT_GT(observer.at("task_worker_count").get<std::size_t>(), 0U);
EXPECT_TRUE(observer.contains("task_execution_state"));
EXPECT_TRUE(observer.contains("task_graph_node_count"));
EXPECT_TRUE(observer.contains("task_running_node_count"));
EXPECT_TRUE(observer.contains("task_pending_node_count"));
EXPECT_TRUE(observer.contains("task_peak_parallelism"));
EXPECT_TRUE(observer.contains("task_graph_duration_ns"));
EXPECT_FALSE(telemetry.contains("low_latency_limit")); EXPECT_FALSE(telemetry.contains("low_latency_limit"));
} }
@@ -344,6 +351,13 @@ TEST(RenderiveWebGallery, AdminiveResourcesPatchTheRealRenderableState) {
EXPECT_EQ(spectrum.at("descriptor").at("name"), "spectrum"); EXPECT_EQ(spectrum.at("descriptor").at("name"), "spectrum");
EXPECT_TRUE(spectrum.at("data").at("frequency_range").is_object()); EXPECT_TRUE(spectrum.at("data").at("frequency_range").is_object());
EXPECT_EQ(spectrum.at("data").at("frequency_point_size"), 512); EXPECT_EQ(spectrum.at("data").at("frequency_point_size"), 512);
const auto partition_field = std::find_if(
spectrum.at("descriptor").at("fields").begin(),
spectrum.at("descriptor").at("fields").end(),
[](const auto& field) { return field.at("name") == "partition_count"; });
ASSERT_NE(partition_field, spectrum.at("descriptor").at("fields").end());
EXPECT_EQ(partition_field->at("presentation").at("description"),
"0 = automatic, 1 = single task, N = N parallel partitions");
response = response_json(session.handle(gallery_request( response = response_json(session.handle(gallery_request(
Gallery_Request_Kind::Patch, Gallery_Request_Kind::Patch,
@@ -367,6 +381,39 @@ TEST(RenderiveWebGallery, AdminiveResourcesPatchTheRealRenderableState) {
EXPECT_EQ(foreign.at("type"), "error"); EXPECT_EQ(foreign.at("type"), "error");
} }
TEST(RenderiveWebGallery, PartitionControlRebuildsTheObservedTaskGraph) {
Gallery_Plot_Session session;
ASSERT_EQ(response_json(session.handle(gallery_request(
Gallery_Request_Kind::Open,
open_message("spectrum", "manual")))).at("type"),
"case_state");
const auto render = [&session] {
EXPECT_EQ(invoke_action(session, "mode_prepare").at("type"), "case_state");
EXPECT_EQ(invoke_action(session, "mode_refresh").at("type"), "case_state");
return invoke_action(session, "mode_render").at("telemetry").at("kernel_observer");
};
ASSERT_EQ(patch_controls(session, {{"partition_count", 1}}, "spectrum").at("type"),
"case_state");
const auto single = render();
EXPECT_EQ(single.at("task_execution_state"), "Completed");
EXPECT_EQ(single.at("task_completed_node_count"),
single.at("task_graph_node_count"));
EXPECT_EQ(single.at("task_running_node_count"), 0);
EXPECT_EQ(single.at("task_pending_node_count"), 0);
ASSERT_EQ(patch_controls(session, {{"partition_count", 4}}, "spectrum").at("type"),
"case_state");
const auto partitioned = render();
EXPECT_EQ(partitioned.at("task_graph_node_count").get<std::size_t>(),
single.at("task_graph_node_count").get<std::size_t>() + 3U);
EXPECT_EQ(partitioned.at("task_completed_node_count"),
partitioned.at("task_graph_node_count"));
EXPECT_GT(partitioned.at("task_peak_parallelism").get<std::size_t>(), 0U);
EXPECT_GT(partitioned.at("task_graph_duration_ns").get<std::uint64_t>(), 0U);
}
TEST(RenderiveWebGallery, AxisSwapAndEveryPlotDirectionAreEditableAndRenderable) { TEST(RenderiveWebGallery, AxisSwapAndEveryPlotDirectionAreEditableAndRenderable) {
const auto patch = [](Gallery_Plot_Session& session, std::string_view target, const auto patch = [](Gallery_Plot_Session& session, std::string_view target,
nlohmann::json value) { nlohmann::json value) {