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
+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.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;
}
+10
View File
@@ -3,6 +3,7 @@
#include "../base/Types.h"
#include "../event/Event.h"
#include <renderive/frame_control/Frame_Consumer_Feedback.hpp>
#include <renderive/scene/base/Task_Execution_Snapshot.hpp>
#include <atomic>
#include <concepts>
@@ -69,6 +70,15 @@ struct Frame_Observer_Snapshot {
std::uint64_t render_finish_state_wait_ns{};
std::uint64_t queue_wait_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 {
+30 -17
View File
@@ -4,7 +4,6 @@
#include "../render/Blend2D_Cache.h"
#include "../renderable/Render_Partition.h"
#include <algorithm>
#include <array>
#include <deque>
namespace renderive {
namespace detail {
@@ -66,31 +65,39 @@ void Afterglow_Control::publish() {
}
void Afterglow_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
const auto prepare = graph.emplace([this](const Scene_Render_Context&) {
prepare_render_frame(render_state_view());
auto& output = impl_->render_frame;
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");
std::array<Renderable_Task_Graph::Task, maximum_render_partitions> accumulation;
std::array<Renderable_Task_Graph::Task, maximum_render_partitions> coloring;
for (int index = 0; index < maximum_render_partitions; ++index) {
accumulation[static_cast<std::size_t>(index)] = graph.emplace(
std::vector<Renderable_Task_Graph::Task> accumulation;
accumulation.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&) {
accumulate_partition(render_state_view(), index);
},
"accumulate afterglow partition");
graph.precede(prepare, accumulation[static_cast<std::size_t>(index)]);
"accumulate afterglow partition " + std::to_string(index + 1));
graph.precede(prepare, task);
accumulation.push_back(task);
}
const auto normalize = graph.emplace([this](const Scene_Render_Context&) {
normalize_render_frame();
}, "normalize afterglow");
for (const auto task : accumulation)
graph.precede(task, normalize);
for (int index = 0; index < maximum_render_partitions; ++index) {
coloring[static_cast<std::size_t>(index)] = graph.emplace(
std::vector<Renderable_Task_Graph::Task> coloring;
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&) {
color_partition(render_state_view(), index);
},
"color afterglow partition");
graph.precede(normalize, coloring[static_cast<std::size_t>(index)]);
"color afterglow partition " + std::to_string(index + 1));
graph.precede(normalize, task);
coloring.push_back(task);
}
const auto compose = add_paint_task(
graph, "compose afterglow",
@@ -99,7 +106,8 @@ void Afterglow_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
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& history = view.get(impl_->history);
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.intensity.resize(output.work_size);
output.pixels.resize(output.work_size);
output.active_partitions =
output.partitioner.begin(state.partition_count.get(), output.work_size);
output.active_partitions = output.partitioner.begin(graph_partition_count,
output.work_size);
output.valid = true;
}
@@ -189,7 +197,12 @@ void Afterglow_Control::paint(Painter& painter, const Render_State_View& view) {
return;
painter.heatmap(output.layout.target, output.layout.width, output.layout.height,
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:
struct 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 normalize_render_frame();
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>
Plottable_State& set(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();
return *this;
}
+24 -13
View File
@@ -4,7 +4,6 @@
#include "../render/Blend2D_Cache.h"
#include "../renderable/Render_Partition.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <iomanip>
#include <sstream>
@@ -25,7 +24,7 @@ struct Spectrum_Interaction {
using Spectrum_Interaction_State = Double_State_Strategy<Spectrum_Interaction_Base, Spectrum_Interaction>;
struct Spectrum_Render_Frame {
Adaptive_Render_Partitioner partitioner;
std::array<Blend2D_Color_Cache, maximum_render_partitions> layers;
std::vector<Blend2D_Color_Cache> layers;
int active_partitions{1};
std::size_t work_size{};
bool valid{};
@@ -244,17 +243,24 @@ void Spectrum_Control::publish() {
}
void Spectrum_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
const auto prepare = graph.emplace([this](const Scene_Render_Context&) {
prepare_render_frame(render_state_view());
auto& output = impl_->render_frame;
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");
std::array<Renderable_Task_Graph::Task, maximum_render_partitions> partitions;
for (int index = 0; index < maximum_render_partitions; ++index) {
partitions[static_cast<std::size_t>(index)] = graph.emplace(
std::vector<Renderable_Task_Graph::Task> partitions;
partitions.reserve(static_cast<std::size_t>(partition_count));
for (int index = 0; index < partition_count; ++index) {
const auto partition = graph.emplace(
[this, index](const Scene_Render_Context&) {
render_partition(render_state_view(), index);
},
"paint spectrum partition");
graph.precede(prepare, partitions[static_cast<std::size_t>(index)]);
"paint spectrum partition " + std::to_string(index + 1));
graph.precede(prepare, partition);
partitions.push_back(partition);
}
const auto compose = add_paint_task(
graph, "compose spectrum",
@@ -263,7 +269,8 @@ void Spectrum_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
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& published_frame = view.get(impl_->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);
output.valid = axes_are_orthogonal(frequency_axis, power_axis);
output.work_size = published_frame ? published_frame->samples.size() : 0;
output.active_partitions =
output.partitioner.begin(state.partition_count.get(), output.work_size);
output.active_partitions = output.partitioner.begin(graph_partition_count,
output.work_size);
for (int index = 0; index < output.active_partitions; ++index)
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);
}
}
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:
struct 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 publish() override;
};
+22 -12
View File
@@ -3,7 +3,6 @@
#include "Plottable_Real_Time_Data.h"
#include "../renderable/Render_Partition.h"
#include <algorithm>
#include <array>
#include <deque>
#include <iomanip>
#include <sstream>
@@ -98,17 +97,23 @@ void Waterfall_Control::publish() {
}
void Waterfall_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
const auto prepare = graph.emplace([this](const Scene_Render_Context&) {
prepare_render_frame(render_state_view());
auto& output = impl_->render_frame;
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");
std::array<Renderable_Task_Graph::Task, maximum_render_partitions> partitions;
for (int index = 0; index < maximum_render_partitions; ++index) {
partitions[static_cast<std::size_t>(index)] = graph.emplace(
std::vector<Renderable_Task_Graph::Task> partitions;
partitions.reserve(static_cast<std::size_t>(partition_count));
for (int index = 0; index < partition_count; ++index) {
const auto partition = graph.emplace(
[this, index](const Scene_Render_Context&) {
render_partition(render_state_view(), index);
},
"raster waterfall partition");
graph.precede(prepare, partitions[static_cast<std::size_t>(index)]);
"raster waterfall partition " + std::to_string(index + 1));
graph.precede(prepare, partition);
partitions.push_back(partition);
}
const auto compose = add_paint_task(
graph, "compose waterfall",
@@ -117,7 +122,8 @@ void Waterfall_Control::build_paint_task_graph(Renderable_Task_Graph& graph) {
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& rows = view.get(impl_->rows);
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.work_size = static_cast<std::size_t>(width) * height;
output.pixels.resize(output.work_size);
output.active_partitions =
output.partitioner.begin(state.partition_count.get(), output.work_size);
output.active_partitions = output.partitioner.begin(graph_partition_count,
output.work_size);
output.valid = true;
}
@@ -181,7 +187,11 @@ void Waterfall_Control::paint(Painter& painter, const Render_State_View& view) {
return;
painter.heatmap(output.layout.target, output.layout.width, output.layout.height,
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)) {
const Axis_Transform frequency_axis = impl_->frequency_axis->transform(view);
const double frequency = frequency_axis.point_to_coord(interaction.tooltip.position);
+1 -1
View File
@@ -41,7 +41,7 @@ protected:
private:
struct 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 publish() override;
};
+33 -32
View File
@@ -3,12 +3,10 @@
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <thread>
#include <limits>
namespace renderive::detail {
inline constexpr int maximum_render_partitions = 16;
struct Render_Partition_Range {
std::size_t first{};
std::size_t last{};
@@ -25,45 +23,48 @@ inline Render_Partition_Range render_partition_range(std::size_t size,
class Adaptive_Render_Partitioner {
public:
Adaptive_Render_Partitioner() noexcept
: automatic_count_(std::clamp(
static_cast<int>(std::thread::hardware_concurrency()), 1,
maximum_render_partitions)) {}
int begin(int configured_count, std::size_t work_size) noexcept {
automatic_ = configured_count == 0;
const int requested = automatic_ ? automatic_count_ : configured_count;
const int useful = static_cast<int>(
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_;
[[nodiscard]] int graph_partition_count(int configured_count,
int worker_count) noexcept {
if (configured_count > 0)
return configured_count;
if (automatic_count_ == 0)
automatic_count_ = std::max(1, worker_count);
automatic_count_ = std::clamp(automatic_count_, 1,
std::max(1, worker_count));
return automatic_count_;
}
void finish(std::size_t work_size) noexcept {
if (!automatic_)
return;
int begin(int graph_partition_count, std::size_t work_size) noexcept {
const int useful = static_cast<int>(std::min<std::size_t>(
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 =
std::chrono::duration_cast<std::chrono::microseconds>(Clock::now() - started_at_);
constexpr auto target = std::chrono::microseconds(1500);
if (elapsed > target * 2 && active_count_ < maximum_render_partitions &&
work_size / static_cast<std::size_t>(active_count_) >= 512) {
automatic_count_ = std::min(maximum_render_partitions, active_count_ + 1);
} else if (elapsed < target / 2 && active_count_ > 1) {
automatic_count_ = active_count_ - 1;
} else {
automatic_count_ = active_count_;
}
const int previous = automatic_count_;
const int maximum = std::max(1, worker_count);
if (elapsed > target * 2 && active_count < maximum &&
work_size / static_cast<std::size_t>(active_count) >= 512)
automatic_count_ = std::min(maximum, active_count + 1);
else if (elapsed < target / 2 && active_count > 1)
automatic_count_ = active_count - 1;
else
automatic_count_ = active_count;
return automatic_count_ != previous;
}
private:
using Clock = std::chrono::steady_clock;
Clock::time_point started_at_{};
int automatic_count_{1};
int active_count_{1};
bool automatic_{true};
int automatic_count_{};
};
} // namespace renderive::detail
+5
View File
@@ -88,6 +88,11 @@ void Renderable::changed() noexcept {
plot_.notify_model_dirty();
}
void Renderable::task_graph_changed() noexcept {
rebuild_task_graph();
plot_.notify_model_dirty();
}
Size Renderable::viewport_size() const noexcept {
return plot_.viewport_size();
}
+1
View File
@@ -50,6 +50,7 @@ protected:
std::string name,
Paint_Task_Function function);
void changed() noexcept;
void task_graph_changed() noexcept;
[[nodiscard]] Size viewport_size() const noexcept;
private:
@@ -652,7 +652,7 @@ TEST(Renderive_Core2, AxisRasterLayoutCoversAxisSwapAndEveryReversal) {
EXPECT_DOUBLE_EQ(points.back().x, 130.0);
EXPECT_DOUBLE_EQ(points.back().y, 30.0);
}
TEST(Renderive_Core2, PartitionedPlotsBuildExplicitInternalTaskGraphs) {
TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenTaskGraphs) {
Plot_Core plot;
plot.init();
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(waterfall->get<&Waterfall::Properties::partition_count>(), 4);
EXPECT_EQ(afterglow->get<&Afterglow::Properties::partition_count>(), 4);
EXPECT_EQ(spectrum->task_graph()->nodes().size(), 18u);
EXPECT_EQ(waterfall->task_graph()->nodes().size(), 18u);
EXPECT_EQ(afterglow->task_graph()->nodes().size(), 35u);
EXPECT_EQ(spectrum->task_graph()->nodes().size(), 6u);
EXPECT_EQ(waterfall->task_graph()->nodes().size(), 6u);
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) {
Plot_Core plot;