From 898a58b554f60dbf6a1d3fa37ce9ce5c0e6c08a5 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Wed, 12 Aug 2026 11:03:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render_2D/plottable/Afterglow.cpp | 45 ++- render_2D/plottable/Afterglow.h | 6 +- render_2D/plottable/Plottable.h | 9 +- render_2D/plottable/Spectrum.cpp | 88 +++-- render_2D/plottable/Spectrum.h | 10 +- render_2D/plottable/Waterfall.cpp | 44 ++- render_2D/plottable/Waterfall.h | 6 +- render_2D/renderable/Render_Partition.h | 78 ++-- render_2D/renderable/Renderable.cpp | 5 +- render_2D/renderable/Renderable.h | 3 +- .../tests/render_2D_Integration_Tests.cpp | 45 ++- web_server/CMakeLists.txt | 7 +- web_server/app/Gallery_Observer_Adminive.h | 4 +- web_server/app/Gallery_Protocol.cpp | 2 +- web_server/app/Gallery_Renderables.h | 364 ++++++++++-------- .../app/Gallery_Session_Control_Adminive.h | 40 +- web_server/tests/Web_Bridge_Tests.cpp | 26 +- webapp_gallery/app.js | 16 +- 18 files changed, 521 insertions(+), 277 deletions(-) diff --git a/render_2D/plottable/Afterglow.cpp b/render_2D/plottable/Afterglow.cpp index ce72e9c..aa6e9c4 100644 --- a/render_2D/plottable/Afterglow.cpp +++ b/render_2D/plottable/Afterglow.cpp @@ -66,9 +66,22 @@ void Afterglow_Control::publish() { void Afterglow_Control::build_paint_task_graph(Renderable_Task_Graph& graph) { auto& output = impl_->render_frame; + const auto view = render_state_view(); + const auto& state = render_properties(view); + const auto& history = view.get(impl_->history); + const int width = history.empty() + ? 0 + : std::min(state.frequency_point_size.get(), + static_cast(history.back().size())); + const int height = state.power_point_size.get() > 0 + ? state.power_point_size.get() + : std::max(1, static_cast(impl_->power_axis->transform(view).pixel_length)); + const std::size_t work_size = width > 0 && height > 0 + ? static_cast(width) * static_cast(height) + : 0; const int partition_count = output.partitioner.graph_partition_count( - render_properties(render_state_view()).partition_count.get(), - static_cast(Scene_Base::task_executor_worker_count())); + state.partition_mode, state.partition_count.get(), + static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto prepare = graph.emplace([this, partition_count](const Scene_Render_Context&) { prepare_render_frame(render_state_view(), partition_count); }, "prepare afterglow"); @@ -99,11 +112,15 @@ void Afterglow_Control::build_paint_task_graph(Renderable_Task_Graph& graph) { graph.precede(normalize, task); coloring.push_back(task); } - const auto compose = add_paint_task( - graph, "compose afterglow", - [this](Painter& painter, const Render_State_View& view) { paint(painter, view); }); + const auto paint_image = add_paint_task( + graph, "paint afterglow shared image", + [this](Painter& painter, const Render_State_View& frame_view, + const Scene_Render_Context& context) { + paint_render_frame(painter, frame_view, + context.frame_control_state.next_refresh_interval_ns); + }); for (const auto task : coloring) - graph.precede(task, compose); + graph.precede(task, paint_image); } void Afterglow_Control::prepare_render_frame(const Render_State_View& view, @@ -191,7 +208,9 @@ void Afterglow_Control::color_partition(const Render_State_View& view, } } -void Afterglow_Control::paint(Painter& painter, const Render_State_View& view) { +void Afterglow_Control::paint_render_frame(Painter& painter, + const Render_State_View& view, + std::uint64_t frame_interval_ns) { auto& output = impl_->render_frame; if (!output.valid) return; @@ -199,10 +218,18 @@ void Afterglow_Control::paint(Painter& painter, const Render_State_View& view) { output.pixels, Image_Interpolation_Mode::Bilinear); const auto& state = render_properties(view); if (output.partitioner.finish( - state.partition_count.get(), output.active_partitions, + state.partition_mode, output.active_partitions, frame_interval_ns, static_cast(Scene_Base::task_executor_worker_count()), - output.work_size)) + output.work_size, 4096)) task_graph_changed(); } + +void Afterglow_Control::paint(Painter& painter, const Render_State_View& view) { + prepare_render_frame(view, 1); + accumulate_partition(view, 0); + normalize_render_frame(); + color_partition(view, 0); + paint_render_frame(painter, view, 0); +} } } diff --git a/render_2D/plottable/Afterglow.h b/render_2D/plottable/Afterglow.h index daf5163..a612249 100644 --- a/render_2D/plottable/Afterglow.h +++ b/render_2D/plottable/Afterglow.h @@ -1,6 +1,7 @@ #pragma once #include "Plottable.h" #include "../axis/Axis.h" +#include "../renderable/Render_Partition.h" #include #include namespace renderive { @@ -9,7 +10,8 @@ struct Afterglow_Properties { Range power_range{0.0, 10.0}; Nonnegative_Count frequency_point_size; Nonnegative_Count power_point_size; - Nonnegative_Count partition_count; + Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; + Positive_Count partition_count; bool interpolate = true; Unit_Interval attenuation_rate{0.2}; Color_Map color_map; @@ -38,6 +40,8 @@ private: 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); + void paint_render_frame(Painter& painter, const Render_State_View& state, + std::uint64_t frame_interval_ns); void publish() override; }; } diff --git a/render_2D/plottable/Plottable.h b/render_2D/plottable/Plottable.h index 6e5f4ab..bf7bec5 100644 --- a/render_2D/plottable/Plottable.h +++ b/render_2D/plottable/Plottable.h @@ -60,7 +60,7 @@ public: template Value> Plottable_State& set(Value&& value) { Base::template set(std::forward(value)); - if constexpr (requires { &Properties::partition_count; }) { + if constexpr (requires { &Properties::partition_count; &Properties::partition_mode; }) { if constexpr (std::same_as) { if constexpr (Member == &Properties::partition_count) { @@ -68,6 +68,13 @@ public: return *this; } } + if constexpr (std::same_as) { + if constexpr (Member == &Properties::partition_mode) { + this->task_graph_changed(); + return *this; + } + } } this->changed(); return *this; diff --git a/render_2D/plottable/Spectrum.cpp b/render_2D/plottable/Spectrum.cpp index 045a4b5..ff7c309 100644 --- a/render_2D/plottable/Spectrum.cpp +++ b/render_2D/plottable/Spectrum.cpp @@ -24,7 +24,6 @@ struct Spectrum_Interaction { using Spectrum_Interaction_State = Double_State_Strategy; struct Spectrum_Render_Frame { Adaptive_Render_Partitioner partitioner; - std::vector layers; int active_partitions{1}; std::size_t work_size{}; bool valid{}; @@ -244,34 +243,45 @@ void Spectrum_Control::publish() { void Spectrum_Control::build_paint_task_graph(Renderable_Task_Graph& graph) { auto& output = impl_->render_frame; + const auto view = render_state_view(); + const auto& state = render_properties(view); + const auto& published_frame = view.get(impl_->frame); + const std::size_t work_size = published_frame ? published_frame->samples.size() : 0; const int partition_count = output.partitioner.graph_partition_count( - render_properties(render_state_view()).partition_count.get(), - static_cast(Scene_Base::task_executor_worker_count())); - output.layers.resize(static_cast(partition_count)); - const auto prepare = graph.emplace([this, partition_count](const Scene_Render_Context&) { - prepare_render_frame(render_state_view(), partition_count); - }, "prepare spectrum"); + state.partition_mode, state.partition_count.get(), + static_cast(Scene_Base::task_executor_worker_count()), work_size, 128); + const auto prepare = add_paint_task( + graph, "prepare spectrum and paint background", + [this, partition_count](Painter& painter, const Render_State_View& frame_view, + const Scene_Render_Context&) { + prepare_render_frame(frame_view, partition_count); + paint_background(painter, frame_view); + }); std::vector partitions; partitions.reserve(static_cast(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 " + std::to_string(index + 1)); + const auto partition = add_paint_task( + graph, "paint spectrum partition " + std::to_string(index + 1), + [this, index](Painter& painter, const Render_State_View& frame_view, + const Scene_Render_Context&) { + render_partition(painter, frame_view, index); + }); graph.precede(prepare, partition); partitions.push_back(partition); } - const auto compose = add_paint_task( - graph, "compose spectrum", - [this](Painter& painter, const Render_State_View& view) { paint(painter, view); }); + const auto overlay = add_paint_task( + graph, "paint spectrum overlay", + [this](Painter& painter, const Render_State_View& frame_view, + const Scene_Render_Context& context) { + paint_overlay(painter, frame_view, + context.frame_control_state.next_refresh_interval_ns); + }); for (const auto task : partitions) - graph.precede(task, compose); + graph.precede(task, overlay); } 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; const Axis_Transform frequency_axis = impl_->frequency_axis->transform(view); @@ -280,11 +290,9 @@ void Spectrum_Control::prepare_render_frame(const Render_State_View& view, output.work_size = published_frame ? published_frame->samples.size() : 0; 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(index)].clear(); } -void Spectrum_Control::render_partition(const Render_State_View& view, +void Spectrum_Control::render_partition(Painter& painter, const Render_State_View& view, int partition_index) { auto& output = impl_->render_frame; if (!output.valid || partition_index >= output.active_partitions) @@ -301,9 +309,6 @@ void Spectrum_Control::render_partition(const Render_State_View& view, if (current.values.empty()) return; - Painter painter(output.layers[static_cast(partition_index)], viewport_size()); - if (!painter) - return; painter.clip(mapped_rect(frequency_axis, power_axis, current.clip_domain, power_axis.coordinate_range)); if (state.max_hold_visible) { @@ -325,7 +330,24 @@ void Spectrum_Control::render_partition(const Render_State_View& view, state.current_pen, state.current_brush); } -void Spectrum_Control::paint(Painter& painter, const Render_State_View& view) { +void Spectrum_Control::paint_background(Painter& painter, const Render_State_View& view) { + const auto& state = render_properties(view); + const Axis_Transform frequency_axis = impl_->frequency_axis->transform(view); + const Axis_Transform power_axis = impl_->power_axis->transform(view); + const RectF content = mapped_rect(frequency_axis, power_axis, + frequency_axis.coordinate_range, + power_axis.coordinate_range); + if (content.empty()) + return; + if(state.sweep_region_visible) { + painter.rect(mapped_rect(frequency_axis, power_axis, state.sweep_frequency_range, + power_axis.coordinate_range), + Pen{.style = Line_Style::None}, state.sweep_region_brush); + } +} + +void Spectrum_Control::paint_overlay(Painter& painter, const Render_State_View& view, + std::uint64_t frame_interval_ns) { const auto& state = render_properties(view); const auto& published_frame = view.get(impl_->frame); const Spectrum_Frame empty_frame; @@ -339,13 +361,6 @@ void Spectrum_Control::paint(Painter& painter, const Render_State_View& view) { if (content.empty()) return; auto& output = impl_->render_frame; - if(state.sweep_region_visible) { - painter.rect(mapped_rect(frequency_axis, power_axis, state.sweep_frequency_range, - power_axis.coordinate_range), - Pen{.style = Line_Style::None}, state.sweep_region_brush); - } - for (int index = 0; output.valid && index < output.active_partitions; ++index) - painter.composite(output.layers[static_cast(index)]); if(state.middle_frequency_pen.enabled()) { painter.line(mapped_point(frequency_axis, state.center_frequency, power_axis, power_axis.coordinate_range.origin), @@ -388,10 +403,17 @@ void Spectrum_Control::paint(Painter& painter, const Render_State_View& view) { } } if (output.partitioner.finish( - state.partition_count.get(), output.active_partitions, + state.partition_mode, output.active_partitions, frame_interval_ns, static_cast(Scene_Base::task_executor_worker_count()), - output.work_size)) + output.work_size, 128)) task_graph_changed(); } + +void Spectrum_Control::paint(Painter& painter, const Render_State_View& view) { + prepare_render_frame(view, 1); + paint_background(painter, view); + render_partition(painter, view, 0); + paint_overlay(painter, view, 0); +} } } diff --git a/render_2D/plottable/Spectrum.h b/render_2D/plottable/Spectrum.h index a342067..6247c0b 100644 --- a/render_2D/plottable/Spectrum.h +++ b/render_2D/plottable/Spectrum.h @@ -2,13 +2,15 @@ #include "Hover_Tooltip.h" #include "Plottable.h" #include "../axis/Axis.h" +#include "../renderable/Render_Partition.h" #include #include #include namespace renderive { struct Spectrum_Properties : Hover_Tooltip_Properties { Nonnegative_Count frequency_point_size; - Nonnegative_Count partition_count; + Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; + Positive_Count partition_count; Range frequency_range{}; double center_frequency = 50.0; Range sweep_frequency_range{40.0, 60.0}; @@ -66,7 +68,11 @@ private: struct Impl; std::unique_ptr impl_; 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(Painter& painter, const Render_State_View& state, + int partition_index); + void paint_background(Painter& painter, const Render_State_View& state); + void paint_overlay(Painter& painter, const Render_State_View& state, + std::uint64_t frame_interval_ns); void publish() override; }; } diff --git a/render_2D/plottable/Waterfall.cpp b/render_2D/plottable/Waterfall.cpp index 3a13198..79fd13f 100644 --- a/render_2D/plottable/Waterfall.cpp +++ b/render_2D/plottable/Waterfall.cpp @@ -98,9 +98,23 @@ void Waterfall_Control::publish() { void Waterfall_Control::build_paint_task_graph(Renderable_Task_Graph& graph) { auto& output = impl_->render_frame; + const auto view = render_state_view(); + const auto& state = render_properties(view); + const auto& rows = view.get(impl_->rows); + std::size_t work_size{}; + if (!rows.empty()) { + const auto shortest = std::min_element( + rows.begin(), rows.end(), + [](const auto& left, const auto& right) { + return left.values.size() < right.values.size(); + }); + work_size = static_cast(std::max( + 0, std::min(state.frequency_bin_count.get(), + static_cast(shortest->values.size())))) * rows.size(); + } const int partition_count = output.partitioner.graph_partition_count( - render_properties(render_state_view()).partition_count.get(), - static_cast(Scene_Base::task_executor_worker_count())); + state.partition_mode, state.partition_count.get(), + static_cast(Scene_Base::task_executor_worker_count()), work_size, 4096); const auto prepare = graph.emplace([this, partition_count](const Scene_Render_Context&) { prepare_render_frame(render_state_view(), partition_count); }, "prepare waterfall"); @@ -115,11 +129,15 @@ void Waterfall_Control::build_paint_task_graph(Renderable_Task_Graph& graph) { graph.precede(prepare, partition); partitions.push_back(partition); } - const auto compose = add_paint_task( - graph, "compose waterfall", - [this](Painter& painter, const Render_State_View& view) { paint(painter, view); }); + const auto paint_image = add_paint_task( + graph, "paint waterfall shared image", + [this](Painter& painter, const Render_State_View& frame_view, + const Scene_Render_Context& context) { + paint_render_frame(painter, frame_view, + context.frame_control_state.next_refresh_interval_ns); + }); for (const auto partition : partitions) - graph.precede(partition, compose); + graph.precede(partition, paint_image); } void Waterfall_Control::prepare_render_frame(const Render_State_View& view, @@ -179,7 +197,9 @@ void Waterfall_Control::render_partition(const Render_State_View& view, int part } } -void Waterfall_Control::paint(Painter& painter, const Render_State_View& view) { +void Waterfall_Control::paint_render_frame(Painter& painter, + const Render_State_View& view, + std::uint64_t frame_interval_ns) { const auto& state = render_properties(view); const auto& interaction = view.get(impl_->interaction); auto& output = impl_->render_frame; @@ -188,9 +208,9 @@ void Waterfall_Control::paint(Painter& painter, const Render_State_View& view) { painter.heatmap(output.layout.target, output.layout.width, output.layout.height, output.pixels, state.interpolation_mode); if (output.partitioner.finish( - state.partition_count.get(), output.active_partitions, + state.partition_mode, output.active_partitions, frame_interval_ns, static_cast(Scene_Base::task_executor_worker_count()), - output.work_size)) + output.work_size, 4096)) 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); @@ -202,5 +222,11 @@ void Waterfall_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); } } + +void Waterfall_Control::paint(Painter& painter, const Render_State_View& view) { + prepare_render_frame(view, 1); + render_partition(view, 0); + paint_render_frame(painter, view, 0); +} } } diff --git a/render_2D/plottable/Waterfall.h b/render_2D/plottable/Waterfall.h index 197a249..c736dbd 100644 --- a/render_2D/plottable/Waterfall.h +++ b/render_2D/plottable/Waterfall.h @@ -2,6 +2,7 @@ #include "Hover_Tooltip.h" #include "Plottable.h" #include "../axis/Axis.h" +#include "../renderable/Render_Partition.h" #include #include namespace renderive { @@ -9,7 +10,8 @@ struct Waterfall_Properties : Hover_Tooltip_Properties { Range frequency_range{0.0, 10.0}; Range power_range{0.0, 10.0}; Nonnegative_Count frequency_bin_count; - Nonnegative_Count partition_count; + Render_Partition_Mode partition_mode{Render_Partition_Mode::Automatic}; + Positive_Count partition_count; bool visible_range_only{true}; Image_Interpolation_Mode interpolation_mode = Image_Interpolation_Mode::Nearest; Color_Map color_map; @@ -42,6 +44,8 @@ private: struct Impl; std::unique_ptr impl_; void prepare_render_frame(const Render_State_View& state, int graph_partition_count); + void paint_render_frame(Painter& painter, const Render_State_View& state, + std::uint64_t frame_interval_ns); void render_partition(const Render_State_View& state, int partition_index); void publish() override; }; diff --git a/render_2D/renderable/Render_Partition.h b/render_2D/renderable/Render_Partition.h index 11f3c62..2dfd022 100644 --- a/render_2D/renderable/Render_Partition.h +++ b/render_2D/renderable/Render_Partition.h @@ -4,8 +4,16 @@ #include #include #include +#include -namespace renderive::detail { +namespace renderive { + +enum class Render_Partition_Mode : std::uint8_t { + Automatic, + Fixed +}; + +namespace detail { struct Render_Partition_Range { std::size_t first{}; @@ -23,14 +31,20 @@ inline Render_Partition_Range render_partition_range(std::size_t size, class Adaptive_Render_Partitioner { public: - [[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)); + [[nodiscard]] int graph_partition_count(Render_Partition_Mode mode, + int configured_count, + int worker_count, + std::size_t work_size, + std::size_t minimum_partition_size) noexcept { + if (mode == Render_Partition_Mode::Fixed) + return std::max(1, configured_count); + + const std::size_t granularity = std::max(1, minimum_partition_size); + const int work_limit = static_cast(std::min( + static_cast(std::numeric_limits::max()), + std::max(1, work_size / granularity))); + const int automatic_limit = std::clamp(work_limit, 1, std::max(1, worker_count)); + automatic_count_ = std::clamp(automatic_count_, 1, automatic_limit); return automatic_count_; } @@ -42,29 +56,43 @@ public: 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) + [[nodiscard]] bool finish(Render_Partition_Mode mode, int active_count, + std::uint64_t frame_interval_ns, int worker_count, + std::size_t work_size, + std::size_t minimum_partition_size) noexcept { + if (mode != Render_Partition_Mode::Automatic) return false; - const auto elapsed = - std::chrono::duration_cast(Clock::now() - started_at_); - constexpr auto target = std::chrono::microseconds(1500); + const std::size_t granularity = std::max(1, minimum_partition_size); + const int work_limit = static_cast(std::min( + static_cast(std::numeric_limits::max()), + std::max(1, work_size / granularity))); + const int automatic_limit = std::clamp(work_limit, 1, std::max(1, worker_count)); + const auto elapsed = Clock::now() - started_at_; + const auto elapsed_ns = std::chrono::duration_cast(elapsed); + const auto bottleneck_threshold = frame_interval_ns == 0 + ? std::chrono::duration_cast( + std::chrono::microseconds(1500)) + : std::max(std::chrono::duration_cast( + std::chrono::microseconds(750)), + std::chrono::nanoseconds(frame_interval_ns / 4)); const int previous = automatic_count_; - const int maximum = std::max(1, worker_count); - if (elapsed > target * 2 && active_count < maximum && - work_size / static_cast(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; + const auto estimated_serial_cost = elapsed_ns * std::max(1, active_count); + if (elapsed_ns > bottleneck_threshold && active_count < automatic_limit) { + automatic_count_ = std::min(automatic_limit, active_count * 2); + } else if (active_count > 1 && + estimated_serial_cost < bottleneck_threshold * 3 / 5) { + automatic_count_ = 1; + } else { + automatic_count_ = std::clamp(active_count, 1, automatic_limit); + } return automatic_count_ != previous; } private: using Clock = std::chrono::steady_clock; Clock::time_point started_at_{}; - int automatic_count_{}; + int automatic_count_{1}; }; -} // namespace renderive::detail +} // namespace detail +} // namespace renderive diff --git a/render_2D/renderable/Renderable.cpp b/render_2D/renderable/Renderable.cpp index c60c5a3..4f0d96d 100644 --- a/render_2D/renderable/Renderable.cpp +++ b/render_2D/renderable/Renderable.cpp @@ -60,7 +60,8 @@ void Renderable::build_task_graph(Renderable_Task_Graph& graph) { void Renderable::build_paint_task_graph(Renderable_Task_Graph& graph) { add_paint_task(graph, "paint", [this](detail::Painter& painter, - const Render_State_View& state) { + const Render_State_View& state, + const Scene_Render_Context&) { paint(painter, state); }); } @@ -78,7 +79,7 @@ Renderable_Task_Graph::Task Renderable::add_paint_task( return; detail::Painter painter(*cache, viewport_size()); if (painter) - function(painter, render_state_view()); + function(painter, render_state_view(), context); }, std::move(name)); } diff --git a/render_2D/renderable/Renderable.h b/render_2D/renderable/Renderable.h index 39bc8ca..46f37f4 100644 --- a/render_2D/renderable/Renderable.h +++ b/render_2D/renderable/Renderable.h @@ -42,7 +42,8 @@ public: protected: using Paint_Task_Function = - std::function; + std::function; virtual void paint(detail::Painter& painter, const Render_State_View& state) = 0; virtual void build_paint_task_graph(Renderable_Task_Graph& graph); diff --git a/render_2D/tests/render_2D_Integration_Tests.cpp b/render_2D/tests/render_2D_Integration_Tests.cpp index c94bdcb..ccacd9c 100644 --- a/render_2D/tests/render_2D_Integration_Tests.cpp +++ b/render_2D/tests/render_2D_Integration_Tests.cpp @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include namespace renderive { namespace { @@ -668,12 +670,18 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenTaskGraphs) { .set_pixel_length(180) .build(); const auto spectrum = Spectrum::Builder{} + .set<&Spectrum::Properties::partition_mode>( + Render_Partition_Mode::Fixed) .set<&Spectrum::Properties::partition_count>(4) .build(root, frequency, power); const auto waterfall = Waterfall::Builder{} + .set<&Waterfall::Properties::partition_mode>( + Render_Partition_Mode::Fixed) .set<&Waterfall::Properties::partition_count>(4) .build(root, frequency, time); const auto afterglow = Afterglow::Builder{} + .set<&Afterglow::Properties::partition_mode>( + Render_Partition_Mode::Fixed) .set<&Afterglow::Properties::partition_count>(4) .build(root, frequency, power); @@ -692,14 +700,37 @@ TEST(Renderive_Core2, PartitionedPlotsBuildPropertyDrivenTaskGraphs) { 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->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); 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); + EXPECT_EQ(spectrum->task_graph()->nodes().size(), 3u); + EXPECT_EQ(waterfall->task_graph()->nodes().size(), 3u); + EXPECT_EQ(afterglow->task_graph()->nodes().size(), 5u); +} + +TEST(Renderive_Core2, AutomaticPartitioningSplitsOnlyForMeasuredBottlenecks) { + detail::Adaptive_Render_Partitioner partitioner; + EXPECT_EQ(partitioner.graph_partition_count( + Render_Partition_Mode::Automatic, 8, 8, 4096, 128), + 1); + EXPECT_EQ(partitioner.begin(1, 4096), 1); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + EXPECT_TRUE(partitioner.finish(Render_Partition_Mode::Automatic, 1, 0, + 8, 4096, 128)); + EXPECT_EQ(partitioner.graph_partition_count( + Render_Partition_Mode::Automatic, 8, 8, 4096, 128), + 2); + + EXPECT_EQ(partitioner.begin(2, 4096), 2); + EXPECT_TRUE(partitioner.finish(Render_Partition_Mode::Automatic, 2, 0, + 8, 4096, 128)); + EXPECT_EQ(partitioner.graph_partition_count( + Render_Partition_Mode::Automatic, 8, 8, 4096, 128), + 1); + EXPECT_EQ(partitioner.graph_partition_count( + Render_Partition_Mode::Fixed, 7, 2, 1, 4096), + 7); } TEST(Renderive_Core2, PlottableAxesAreDataDependenciesAndPaintOverlays) { Plot_Core plot; diff --git a/web_server/CMakeLists.txt b/web_server/CMakeLists.txt index e80706b..d7039bd 100644 --- a/web_server/CMakeLists.txt +++ b/web_server/CMakeLists.txt @@ -54,10 +54,15 @@ append_glob_source(Renderive_Web_Server_sources "${Renderive_Web_Server_source_d add_executable(Renderive_Web_Server ${Renderive_Web_Server_sources}) target_compile_features(Renderive_Web_Server PRIVATE cxx_std_20) target_link_libraries(Renderive_Web_Server PRIVATE Renderive_Web) +if (CMAKE_CONFIGURATION_TYPES) + set(Renderive_Web_assets_dir "${CMAKE_CURRENT_BINARY_DIR}/$/webapp_gallery") +else () + set(Renderive_Web_assets_dir "${CMAKE_CURRENT_BINARY_DIR}/webapp_gallery") +endif () add_custom_target(Renderive_Web_Assets COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_LIST_DIR}/../webapp_gallery" - "${CMAKE_CURRENT_BINARY_DIR}/webapp_gallery" + "${Renderive_Web_assets_dir}" COMMENT "Synchronizing Renderive Web assets" ) add_dependencies(Renderive_Web_Server Renderive_Web_Assets) diff --git a/web_server/app/Gallery_Observer_Adminive.h b/web_server/app/Gallery_Observer_Adminive.h index 42ffaf5..11eb9e7 100644 --- a/web_server/app/Gallery_Observer_Adminive.h +++ b/web_server/app/Gallery_Observer_Adminive.h @@ -96,7 +96,7 @@ struct Type_Descriptor { 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("内核观察器"); } }; @@ -114,7 +114,7 @@ struct Type_Descriptor { ADMINIVE_FIELD_LABEL(T, pixel_interval_ns, "像素响应周期"), ADMINIVE_FIELD_LABEL(T, presentation_interval_ns, "浏览器呈现周期"), ADMINIVE_FIELD_LABEL(T, manual_interval_ns, "手动消费者周期")) - .label("Consumer Feedback"); + .label("消费者反馈"); } }; diff --git a/web_server/app/Gallery_Protocol.cpp b/web_server/app/Gallery_Protocol.cpp index fb24065..171f717 100644 --- a/web_server/app/Gallery_Protocol.cpp +++ b/web_server/app/Gallery_Protocol.cpp @@ -85,7 +85,7 @@ adminive::Table_View action_view(); namespace adminive { template <> -struct { +struct Type_Descriptor { static auto get() { using T = renderive::web::gallery_detail::Case_Model; return object("renderive_gallery_case", diff --git a/web_server/app/Gallery_Renderables.h b/web_server/app/Gallery_Renderables.h index 97e111e..419fb6f 100644 --- a/web_server/app/Gallery_Renderables.h +++ b/web_server/app/Gallery_Renderables.h @@ -233,9 +233,9 @@ struct Type_Descriptor { static auto get() { using T = renderive::Range; using namespace renderive::web::gallery_adminive; - return adminive::object("range", "Range", - number<&T::origin>("origin", "Origin"), - number<&T::target>("target", "Target")); + return adminive::object("range", "范围", + number<&T::origin>("origin", "起点"), + number<&T::target>("target", "终点")); } }; @@ -244,12 +244,22 @@ struct Type_Descriptor { static auto get() { using T = renderive::Pen; using namespace renderive::web::gallery_adminive; - return adminive::object("pen", "Pen", - color<&T::color>("color", "Color"), - number<&T::width>("width", "Width"), - select<&T::style>("style", "Style"), - select<&T::cap>("cap", "Cap"), - select<&T::join>("join", "Join")); + return adminive::object("pen", "画笔", + color<&T::color>("color", "颜色"), + number<&T::width>("width", "宽度"), + select<&T::style>("style", "线型") + .enum_label("无") + .enum_label("实线") + .enum_label("虚线") + .enum_label("点线"), + select<&T::cap>("cap", "端点样式") + .enum_label("平头") + .enum_label("方头") + .enum_label("圆头"), + select<&T::join>("join", "连接样式") + .enum_label("尖角") + .enum_label("斜角") + .enum_label("圆角")); } }; @@ -258,9 +268,11 @@ struct Type_Descriptor { static auto get() { using T = renderive::Brush; using namespace renderive::web::gallery_adminive; - return adminive::object("brush", "Brush", - color<&T::color>("color", "Color"), - select<&T::style>("style", "Style")); + return adminive::object("brush", "画刷", + color<&T::color>("color", "颜色"), + select<&T::style>("style", "填充样式") + .enum_label("无") + .enum_label("纯色")); } }; @@ -269,10 +281,10 @@ struct Type_Descriptor { static auto get() { using T = renderive::Font; using namespace renderive::web::gallery_adminive; - return adminive::object("font", "Font", - number<&T::size>("size", "Size"), - number<&T::weight>("weight", "Weight"), - boolean<&T::italic>("italic", "Italic")); + return adminive::object("font", "字体", + number<&T::size>("size", "字号"), + number<&T::weight>("weight", "字重"), + boolean<&T::italic>("italic", "斜体")); } }; @@ -284,28 +296,32 @@ struct Type_Descriptor { using P = renderive::Axis_Properties; \ using namespace renderive::web::gallery_adminive; \ return adminive::object(Name, Label, \ - readonly_property("x", "X"), \ - readonly_property("y", "Y"), \ - property_select("orientation", "Orientation"), \ - readonly_property("pixel_length", "Pixel length"), \ - property_number("tick_length", "Tick length"), \ - property_number("sub_tick_length", "Sub tick length"), \ - property_color("color", "Color"), \ - property_select("locale", "Decimal separator"), \ - property_text("unit_text", "Unit text"), \ - property_object("unit_text_font", "Unit font"), \ - property_object("unit_text_pen", "Unit pen"), \ - property_object("unit_text_background_brush", "Unit background"), \ - property_number("label_rotation_degrees", "Label rotation"), \ - property_object("coordinates", "Coordinates"), \ - property_number("precision", "Precision"), \ - property_boolean("wheel", "Wheel zoom"), \ - property_boolean("drag", "Drag pan")); \ + readonly_property("x", "X 位置"), \ + readonly_property("y", "Y 位置"), \ + property_select("orientation", "方向") \ + .enum_label("水平") \ + .enum_label("垂直"), \ + readonly_property("pixel_length", "像素长度"), \ + property_number("tick_length", "主刻度长度"), \ + property_number("sub_tick_length", "次刻度长度"), \ + property_color("color", "颜色"), \ + property_select("locale", "小数点符号") \ + .enum_label("点") \ + .enum_label("逗号"), \ + property_text("unit_text", "单位文本"), \ + property_object("unit_text_font", "单位字体"), \ + property_object("unit_text_pen", "单位画笔"), \ + property_object("unit_text_background_brush", "单位背景"), \ + property_number("label_rotation_degrees", "标签旋转角度"), \ + property_object("coordinates", "坐标范围"), \ + property_number("precision", "小数位数"), \ + property_boolean("wheel", "滚轮缩放"), \ + property_boolean("drag", "拖拽平移")); \ } \ } -RENDERIVE_AXIS_DESCRIPTOR(Gallery_Axis, "axis", "Axis"); -RENDERIVE_AXIS_DESCRIPTOR(Gallery_Frequency_Axis, "frequency_axis", "Frequency axis"); +RENDERIVE_AXIS_DESCRIPTOR(Gallery_Axis, "axis", "数值轴"); +RENDERIVE_AXIS_DESCRIPTOR(Gallery_Frequency_Axis, "frequency_axis", "频率轴"); #undef RENDERIVE_AXIS_DESCRIPTOR template <> @@ -315,24 +331,28 @@ struct Type_Descriptor { using B = renderive::Axis_Base_Properties; using P = renderive::Time_Axis_Properties; using namespace renderive::web::gallery_adminive; - return adminive::object("time_axis", "Time axis", - readonly_property("x", "X"), - readonly_property("y", "Y"), - property_select("orientation", "Orientation"), - readonly_property("pixel_length", "Pixel length"), - property_number("tick_length", "Tick length"), - property_number("sub_tick_length", "Sub tick length"), - property_color("color", "Color"), - property_select("locale", "Decimal separator"), - property_text("unit_text", "Unit text"), - property_object("unit_text_font", "Unit font"), - property_object("unit_text_pen", "Unit pen"), - property_object("unit_text_background_brush", "Unit background"), - property_number("label_rotation_degrees", "Label rotation"), - property_number("visible_count", "Visible points"), - property_number("tick_label_spacing_px", "Label spacing"), - property_text("format", "Time format"), - property_boolean("newest_at_start", "Newest at start")); + return adminive::object("time_axis", "时间轴", + readonly_property("x", "X 位置"), + readonly_property("y", "Y 位置"), + property_select("orientation", "方向") + .enum_label("水平") + .enum_label("垂直"), + readonly_property("pixel_length", "像素长度"), + property_number("tick_length", "主刻度长度"), + property_number("sub_tick_length", "次刻度长度"), + property_color("color", "颜色"), + property_select("locale", "小数点符号") + .enum_label("点") + .enum_label("逗号"), + property_text("unit_text", "单位文本"), + property_object("unit_text_font", "单位字体"), + property_object("unit_text_pen", "单位画笔"), + property_object("unit_text_background_brush", "单位背景"), + property_number("label_rotation_degrees", "标签旋转角度"), + property_number("visible_count", "可见点数"), + property_number("tick_label_spacing_px", "标签间距"), + property_text("format", "时间格式"), + property_boolean("newest_at_start", "最新数据在起点")); } }; @@ -346,86 +366,122 @@ struct Type_Descriptor { } \ } -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "spectrum", "Spectrum", - property_number("frequency_point_size", "Frequency points"), - property_number("partition_count", "Render partitions") - .description("0 = automatic, 1 = single task, N = N parallel partitions"), - property_object("frequency_range", "Frequency range"), - property_number("center_frequency", "Center frequency"), - property_object("sweep_frequency_range", "Sweep range"), - property_boolean("max_hold_visible", "Max hold"), - property_boolean("min_hold_visible", "Min hold"), - property_boolean("max_marker_visible", "Max marker"), - property_boolean("use_min_marker", "Use min marker"), - property_boolean("sweep_region_visible", "Sweep region"), - property_boolean("visible_range_only", "Visible range only"), - property_select("interpolation_mode", "Interpolation"), - property_object("max_brush", "Max brush"), - property_object("current_brush", "Current brush"), - property_object("min_brush", "Min brush"), - property_object("max_pen", "Max pen"), - property_object("current_pen", "Current pen"), - property_object("min_pen", "Min pen"), - property_object("selected_marker_pen", "Selected marker pen"), - property_object("marker_pen", "Marker pen"), - property_object("middle_frequency_pen", "Middle frequency pen"), - property_object("sweep_region_brush", "Sweep region brush"), - property_boolean("tooltip_enabled", "Tooltip"), - property_object("tooltip_font", "Tooltip font"), - property_object("tooltip_text_pen", "Tooltip text pen"), - property_object("tooltip_background_brush", "Tooltip background")); +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum_Properties, "spectrum", "实时频谱", + property_number("frequency_point_size", "频率点数"), + property_select("partition_mode", "任务分区模式") + .enum_label("自动瓶颈优化") + .enum_label("固定分区"), + property_number("partition_count", "固定分区数") + .description("固定模式下的并行任务数;1 表示不分区") + .visible_on("${$self.partition_mode == 'Fixed'}"), + property_object("frequency_range", "频率范围"), + property_number("center_frequency", "中心频率"), + property_object("sweep_frequency_range", "扫频范围"), + property_boolean("max_hold_visible", "显示最大保持"), + property_boolean("min_hold_visible", "显示最小保持"), + property_boolean("max_marker_visible", "显示最大值标记"), + property_boolean("use_min_marker", "显示最小值标记"), + property_boolean("sweep_region_visible", "显示扫频区域"), + property_boolean("visible_range_only", "仅绘制可见范围"), + property_select("interpolation_mode", "曲线插值") + .enum_label("最近样本") + .enum_label("数值线性") + .enum_label("功率域线性") + .enum_label("左阶梯") + .enum_label("右阶梯") + .enum_label("三次插值"), + property_object("max_brush", "最大保持画刷"), + property_object("current_brush", "当前曲线画刷"), + property_object("min_brush", "最小保持画刷"), + property_object("max_pen", "最大保持画笔"), + property_object("current_pen", "当前曲线画笔"), + property_object("min_pen", "最小保持画笔"), + property_object("selected_marker_pen", "选中标记画笔"), + property_object("marker_pen", "标记画笔"), + property_object("middle_frequency_pen", "中心频率画笔"), + property_object("sweep_region_brush", "扫频区域画刷"), + property_boolean("tooltip_enabled", "启用提示框"), + property_object("tooltip_font", "提示框字体"), + property_object("tooltip_text_pen", "提示框文字画笔"), + property_object("tooltip_background_brush", "提示框背景")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall_Properties, "waterfall", "Waterfall", - property_object("frequency_range", "Frequency range"), - property_object("power_range", "Power range"), - property_number("frequency_bin_count", "Frequency bins"), - property_number("partition_count", "Render partitions") - .description("0 = automatic, 1 = single task, N = N parallel partitions"), - property_boolean("visible_range_only", "Visible range only"), - property_select("interpolation_mode", "Interpolation"), - property_select("color_map", "Color map"), - property_boolean("tooltip_enabled", "Tooltip"), - property_object("tooltip_font", "Tooltip font"), - property_object("tooltip_text_pen", "Tooltip text pen"), - property_object("tooltip_background_brush", "Tooltip background")); +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall_Properties, "waterfall", "频谱瀑布", + property_object("frequency_range", "频率范围"), + property_object("power_range", "功率范围"), + property_number("frequency_bin_count", "频率格数"), + property_select("partition_mode", "任务分区模式") + .enum_label("自动瓶颈优化") + .enum_label("固定分区"), + property_number("partition_count", "固定分区数") + .description("固定模式下的并行任务数;1 表示不分区") + .visible_on("${$self.partition_mode == 'Fixed'}"), + property_boolean("visible_range_only", "仅绘制可见范围"), + property_select("interpolation_mode", "图像插值") + .enum_label("最近邻") + .enum_label("双线性") + .enum_label("双三次"), + property_select("color_map", "色图") + .enum_label("频谱") + .enum_label("火焰") + .enum_label("灰度"), + property_boolean("tooltip_enabled", "启用提示框"), + property_object("tooltip_font", "提示框字体"), + property_object("tooltip_text_pen", "提示框文字画笔"), + property_object("tooltip_background_brush", "提示框背景")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow_Properties, "afterglow", "Afterglow", - property_object("frequency_range", "Frequency range"), - property_object("power_range", "Power range"), - property_number("frequency_point_size", "Frequency points"), - property_number("power_point_size", "Power points"), - property_number("partition_count", "Render partitions") - .description("0 = automatic, 1 = single task, N = N parallel partitions"), - property_boolean("interpolate", "Interpolate power"), - property_number("attenuation_rate", "Attenuation"), - property_select("color_map", "Color map")); +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow_Properties, "afterglow", "余辉频谱", + property_object("frequency_range", "频率范围"), + property_object("power_range", "功率范围"), + property_number("frequency_point_size", "频率点数"), + property_number("power_point_size", "功率点数"), + property_select("partition_mode", "任务分区模式") + .enum_label("自动瓶颈优化") + .enum_label("固定分区"), + property_number("partition_count", "固定分区数") + .description("固定模式下的并行任务数;1 表示不分区") + .visible_on("${$self.partition_mode == 'Fixed'}"), + property_boolean("interpolate", "插值功率点"), + property_number("attenuation_rate", "衰减率"), + property_select("color_map", "色图") + .enum_label("频谱") + .enum_label("火焰") + .enum_label("灰度")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum_Properties, "sweep_spectrum", "Sweep spectrum", - property_object("frequency_range", "Frequency range"), - property_number("bins_per_block", "Bins per block"), - property_number("block_count", "Block count"), - property_object("pen", "Sweep pen"), - property_object("current_frequency_pen", "Current frequency pen"), - property_boolean("visible_range_only", "Visible range only"), - property_select("interpolation_mode", "Interpolation")); +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum_Properties, "sweep_spectrum", "扫频频谱", + property_object("frequency_range", "频率范围"), + property_number("bins_per_block", "每块频率格数"), + property_number("block_count", "扫频块数"), + property_object("pen", "扫频曲线画笔"), + property_object("current_frequency_pen", "当前频率画笔"), + property_boolean("visible_range_only", "仅绘制可见范围"), + property_select("interpolation_mode", "曲线插值") + .enum_label("最近样本") + .enum_label("数值线性") + .enum_label("功率域线性") + .enum_label("左阶梯") + .enum_label("右阶梯") + .enum_label("三次插值")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Frequency_Trace, Frequency_Trace_Properties, "frequency_trace", "Frequency trace", - property_object("pen", "Trace pen")); +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Frequency_Trace, Frequency_Trace_Properties, "frequency_trace", "频率轨迹", + property_object("pen", "轨迹画笔")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay_Properties, "selection_overlay", "Selection overlay", - property_object("label_font", "Label font"), - property_object("label_pen", "Label pen"), - property_object("selection_brush", "Selection brush"), - property_object("selection_border_pen", "Selection border")); +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay_Properties, "selection_overlay", "框选区域", + property_object("label_font", "标签字体"), + property_object("label_pen", "标签画笔"), + property_object("selection_brush", "选区画刷"), + property_object("selection_border_pen", "选区边框画笔")); -RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Constellation_Diagram, Constellation_Diagram_Properties, "constellation", "Constellation diagram", - property_object("i_range", "I range"), - property_object("q_range", "Q range"), - property_color("point_color", "Point color"), - property_color("anchor_color", "Anchor color"), - property_number("point_lifetime_ms", "Point lifetime"), - property_select("type", "Constellation"), - property_number("phase_offset_radians", "Phase offset")); +RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Constellation_Diagram, Constellation_Diagram_Properties, "constellation", "星座图", + property_object("i_range", "I 轴范围"), + property_object("q_range", "Q 轴范围"), + property_color("point_color", "点颜色"), + property_color("anchor_color", "锚点颜色"), + property_number("point_lifetime_ms", "点保留时间"), + property_select("type", "调制类型") + .enum_label("4 PSK") + .enum_label("8 PSK") + .enum_label("16 PSK"), + property_number("phase_offset_radians", "相位偏移")); #undef RENDERIVE_GALLERY_DESCRIPTOR @@ -437,7 +493,7 @@ constexpr auto axis_metadata() { return structive::type_metadata( gallery_action("axis_probe", "读取坐标映射与刻度", "coord_to_pixel / pixel_to_coord / tick_step / sub_tick_count / tick_label", - "Axis")); + "坐标轴")); } } // namespace renderive::web::gallery_action_registry @@ -477,61 +533,61 @@ struct Type_Descriptor { return object(type_metadata( gallery_action("axis_probe", "读取坐标映射与刻度", "coord_to_pixel / pixel_to_coord / tick_step / sub_tick_count / tick_label", - "Axis"), + "坐标轴"), gallery_action("append_time", "追加时间点", - "Time_Axis::append_time / tick_to_time", "Time_Axis"))); + "Time_Axis::append_time / tick_to_time", "时间轴"))); } }; RENDERIVE_TYPE_ACTIONS(Gallery_Spectrum, - gallery_action("push_samples", "推送一帧样本", "Spectrum::update_samples", "Spectrum"), - gallery_action("power_at", "查询指定频率功率", "Spectrum::power_at", "Spectrum", + gallery_action("push_samples", "推送一帧样本", "Spectrum::update_samples", "频谱"), + gallery_action("power_at", "查询指定频率功率", "Spectrum::power_at", "频谱", {}, "number", "频率", 98e6), - gallery_action("add_marker", "添加点 Marker", "Spectrum::add_custom_marker", "Marker", + gallery_action("add_marker", "添加点标记", "Spectrum::add_custom_marker", "标记", {}, "number", "频率", 96e6), - gallery_action("add_line_marker", "添加线 Marker", "Spectrum::add_custom_line_marker", "Marker", + gallery_action("add_line_marker", "添加线标记", "Spectrum::add_custom_line_marker", "标记", {}, "number", "频率", 100e6), - gallery_action("remove_marker", "按频率删除 Marker", "Spectrum::remove_custom_marker", "Marker", + gallery_action("remove_marker", "按频率删除标记", "Spectrum::remove_custom_marker", "标记", {}, "number", "频率", 96e6), - gallery_action("remove_selected_marker", "删除选中 Marker", - "Spectrum::remove_selected_marker", "Marker"), - gallery_action("clear_markers", "清空 Marker", "Spectrum::clear_custom_markers", "Marker"), - gallery_action("select_marker", "选择 Marker 索引", "Spectrum::set_selected_marker_index", "Marker", + gallery_action("remove_selected_marker", "删除选中标记", + "Spectrum::remove_selected_marker", "标记"), + gallery_action("clear_markers", "清空标记", "Spectrum::clear_custom_markers", "标记"), + gallery_action("select_marker", "选择标记索引", "Spectrum::set_selected_marker_index", "标记", {}, "number", "索引", 0.0), - gallery_action("select_next_marker", "选择下一个 Marker", "Spectrum::select_next_marker", "Marker"), - gallery_action("select_previous_marker", "选择上一个 Marker", "Spectrum::select_previous_marker", "Marker"), - gallery_action("clear_marker_selection", "清除 Marker 选择", - "Spectrum::clear_marker_selection", "Marker"), - gallery_action("set_marker_frequency", "修改选中 Marker 频率", - "Spectrum::set_marker_frequency / set_current_marker_frequency", "Marker", + gallery_action("select_next_marker", "选择下一个标记", "Spectrum::select_next_marker", "标记"), + gallery_action("select_previous_marker", "选择上一个标记", "Spectrum::select_previous_marker", "标记"), + gallery_action("clear_marker_selection", "清除标记选择", + "Spectrum::clear_marker_selection", "标记"), + gallery_action("set_marker_frequency", "修改选中标记频率", + "Spectrum::set_marker_frequency / set_current_marker_frequency", "标记", {}, "number", "频率", 99e6)); RENDERIVE_TYPE_ACTIONS(Gallery_Waterfall, - gallery_action("append_row", "追加一行", "Waterfall::append_row", "Waterfall"), + gallery_action("append_row", "追加一行", "Waterfall::append_row", "频谱瀑布"), gallery_action("append_tick_row", "按 tick 追加一行", - "Time_Axis::append_time / Waterfall::append_row(int, span)", "Waterfall")); + "Time_Axis::append_time / Waterfall::append_row(int, span)", "频谱瀑布")); RENDERIVE_TYPE_ACTIONS(Gallery_Afterglow, - gallery_action("append_spectrum", "追加一帧频谱", "Afterglow::append_spectrum", "Afterglow")); + gallery_action("append_spectrum", "追加一帧频谱", "Afterglow::append_spectrum", "余辉频谱")); RENDERIVE_TYPE_ACTIONS(Gallery_Sweep_Spectrum, - gallery_action("append_block", "追加一个扫频块", "Sweep_Spectrum::append_block", "Sweep Spectrum")); + gallery_action("append_block", "追加一个扫频块", "Sweep_Spectrum::append_block", "扫频频谱")); RENDERIVE_TYPE_ACTIONS(Gallery_Frequency_Trace, - gallery_action("append_sample", "追加一个样本", "Frequency_Trace::append_sample", "Frequency Trace"), + gallery_action("append_sample", "追加一个样本", "Frequency_Trace::append_sample", "频率轨迹"), gallery_action("append_tick_sample", "按 tick 追加样本", "Time_Axis::append_time / Frequency_Trace::append_sample(int, double)", - "Frequency Trace")); + "频率轨迹")); RENDERIVE_TYPE_ACTIONS(Gallery_Selection_Rectangle_Overlay, gallery_action("clear_selection", "清空框选区域", - "Selection_Rectangle_Overlay::clear_selected_regions", "Selection Overlay")); + "Selection_Rectangle_Overlay::clear_selected_regions", "框选区域")); RENDERIVE_TYPE_ACTIONS(Gallery_Constellation_Diagram, gallery_action("append_points", "追加一组 IQ 点", - "Constellation_Diagram::append_point", "Constellation"), + "Constellation_Diagram::append_point", "星座图"), gallery_action("fit_square", "按轴拟合正方形", - "Constellation_Diagram::fit_square_to_axes", "Constellation")); + "Constellation_Diagram::fit_square_to_axes", "星座图")); #undef RENDERIVE_TYPE_ACTIONS diff --git a/web_server/app/Gallery_Session_Control_Adminive.h b/web_server/app/Gallery_Session_Control_Adminive.h index 11eccd5..a19c7f5 100644 --- a/web_server/app/Gallery_Session_Control_Adminive.h +++ b/web_server/app/Gallery_Session_Control_Adminive.h @@ -29,12 +29,12 @@ struct Type_Descriptor { static auto get() { using T = renderive::web::Gallery_Feedback_Policy; using namespace renderive::web::gallery_adminive; - return adminive::object("feedback_policy", "Consumer feedback", - boolean<&T::enabled>("enabled", "Enabled"), - boolean<&T::pixel>("pixel", "Pixel feedback"), - boolean<&T::presentation>("presentation", "Presentation feedback"), - boolean<&T::manual>("manual", "Manual feedback"), - number<&T::manual_fps>("manual_fps", "Manual FPS")); + return adminive::object("feedback_policy", "消费者反馈", + boolean<&T::enabled>("enabled", "启用反馈"), + boolean<&T::pixel>("pixel", "像素响应反馈"), + boolean<&T::presentation>("presentation", "浏览器呈现反馈"), + boolean<&T::manual>("manual", "手动反馈"), + number<&T::manual_fps>("manual_fps", "手动反馈帧率")); } }; @@ -44,16 +44,16 @@ struct Type_Descriptor { using T = renderive::web::Gallery_Session_Control; using namespace renderive::web::gallery_adminive; const auto dirty = [](T& value) { value.plot_.notify_model_dirty(); }; - return adminive::object("scene", "Scene", + return adminive::object("scene", "场景", callback_property( - "background_color", "Background", Field_Control::color, + "background_color", "背景颜色", Field_Control::color, [](const T& value) { return value.plot_.background_color(); }, [dirty](T& value, renderive::Color color) { value.plot_.set_background_color(color); dirty(value); }), callback_property( - "performance_overlay", "Performance overlay", Field_Control::boolean, + "performance_overlay", "性能叠加层", Field_Control::boolean, [](const T& value) { const auto overlay = value.plot_.performance_overlay(); return overlay && overlay->enabled(); @@ -63,28 +63,30 @@ struct Type_Descriptor { dirty(value); }), callback_property( - "renderable_visible", "Renderable visible", Field_Control::boolean, + "renderable_visible", "显示渲染元素", Field_Control::boolean, [](const T& value) { return value.renderable_.is_visible(); }, [dirty](T& value, bool visible) { value.renderable_.set_visible(visible); dirty(value); }), callback_property( - "cache_mode", "Cache mode", Field_Control::select, + "cache_mode", "缓存模式", Field_Control::select, [](const T& value) { return value.renderable_.get_cache_mode(); }, [dirty](T& value, renderive::Renderable_Cache_Mode mode) { value.renderable_.set_cache_mode(mode); dirty(value); - }), + }) + .enum_label("直接绘制") + .enum_label("本地像素缓存"), callback_property( - "object_name", "Object name", Field_Control::text, + "object_name", "对象名称", Field_Control::text, [](const T& value) { return value.renderable_.object_name(); }, [dirty](T& value, std::string name) { value.renderable_.set_object_name(std::move(name)); dirty(value); }), callback_property( - "frequency_limit_enabled", "Frequency limit", Field_Control::boolean, + "frequency_limit_enabled", "启用帧率限制", Field_Control::boolean, [](const T& value) { return value.plot_.max_render_fps() > 0.0; }, [dirty](T& value, bool enabled) { if (enabled) { @@ -96,7 +98,7 @@ struct Type_Descriptor { dirty(value); }), callback_property( - "max_render_fps", "Maximum render FPS", Field_Control::number, + "max_render_fps", "最大渲染帧率", Field_Control::number, [](const T& value) { const double fps = value.plot_.max_render_fps(); return fps > 0.0 ? fps : 30.0; @@ -107,7 +109,7 @@ struct Type_Descriptor { dirty(value); }), callback_property( - "feedback", "Consumer feedback", Field_Control::automatic, + "feedback", "消费者反馈", Field_Control::automatic, [](const T& value) { return value.feedback_; }, [dirty](T& value, renderive::web::Gallery_Feedback_Policy feedback) { value.feedback_ = std::move(feedback); @@ -129,11 +131,11 @@ struct Type_Descriptor { using T = renderive::web::Gallery_Session_Control; using renderive::web::gallery_action; return object(type_metadata( - gallery_action("reset", "恢复本图默认值", "Gallery_Scene::rebuild", "Session"), + gallery_action("reset", "恢复本图默认值", "Gallery_Scene::rebuild", "场景"), gallery_action("toggle_view", "切换 View 生命周期", - "Plot_Core::activate_view / deactivate_view", "Session"), + "Plot_Core::activate_view / deactivate_view", "场景"), gallery_action("read_data_shape", "读取数据形状", - "Renderable data query / observer telemetry", "Observer"))); + "Renderable data query / observer telemetry", "观察器"))); } }; diff --git a/web_server/tests/Web_Bridge_Tests.cpp b/web_server/tests/Web_Bridge_Tests.cpp index 503f1cf..5715f66 100644 --- a/web_server/tests/Web_Bridge_Tests.cpp +++ b/web_server/tests/Web_Bridge_Tests.cpp @@ -276,6 +276,8 @@ TEST(RenderiveWebGallery, ObserverTelemetryAndDashboardUseAdminiveDescriptors) { const auto& observer_descriptor = observer_contract.at("descriptor"); const auto& feedback_descriptor = observer_contract.at("consumer_feedback_descriptor"); + EXPECT_EQ(observer_descriptor.at("label"), "内核观察器"); + EXPECT_EQ(feedback_descriptor.at("label"), "消费者反馈"); Gallery_Plot_Session session; const auto opened = response_json(session.handle(gallery_request( @@ -356,16 +358,26 @@ TEST(RenderiveWebGallery, AdminiveResourcesPatchTheRealRenderableState) { 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("label"), "固定分区数"); EXPECT_EQ(partition_field->at("presentation").at("description"), - "0 = automatic, 1 = single task, N = N parallel partitions"); + "固定模式下的并行任务数;1 表示不分区"); + + const auto partition_mode_field = std::find_if( + spectrum.at("descriptor").at("fields").begin(), + spectrum.at("descriptor").at("fields").end(), + [](const auto& field) { return field.at("name") == "partition_mode"; }); + ASSERT_NE(partition_mode_field, spectrum.at("descriptor").at("fields").end()); + EXPECT_EQ(partition_mode_field->at("presentation").at("label"), "任务分区模式"); + EXPECT_EQ(spectrum.at("descriptor").at("label"), "实时频谱"); response = response_json(session.handle(gallery_request( Gallery_Request_Kind::Patch, - R"({"category":"event","type":"gallery_patch","target":"spectrum","patch":{"frequency_point_size":1024,"partition_count":4,"frequency_range":{"origin":90000000},"current_pen":{"color":"#ff8844"},"interpolation_mode":"Cubic_Value"}})"))); + R"({"category":"event","type":"gallery_patch","target":"spectrum","patch":{"frequency_point_size":1024,"partition_mode":"Fixed","partition_count":4,"frequency_range":{"origin":90000000},"current_pen":{"color":"#ff8844"},"interpolation_mode":"Cubic_Value"}})"))); ASSERT_EQ(response.at("type"), "case_state"); const auto& updated = find_resource(response, "spectrum").at("data"); EXPECT_EQ(updated.at("frequency_point_size"), 1024); EXPECT_EQ(updated.at("partition_count"), 4); + EXPECT_EQ(updated.at("partition_mode"), "Fixed"); EXPECT_EQ(updated.at("frequency_range").at("origin"), 90'000'000); EXPECT_EQ(updated.at("current_pen").at("color"), "#ff8844"); EXPECT_EQ(updated.at("interpolation_mode"), "Cubic_Value"); @@ -394,7 +406,9 @@ TEST(RenderiveWebGallery, PartitionControlRebuildsTheObservedTaskGraph) { return invoke_action(session, "mode_render").at("telemetry").at("kernel_observer"); }; - ASSERT_EQ(patch_controls(session, {{"partition_count", 1}}, "spectrum").at("type"), + ASSERT_EQ(patch_controls(session, + {{"partition_mode", "Fixed"}, {"partition_count", 1}}, + "spectrum").at("type"), "case_state"); const auto single = render(); EXPECT_EQ(single.at("task_execution_state"), "Completed"); @@ -448,7 +462,8 @@ TEST(RenderiveWebGallery, AxisSwapAndEveryPlotDirectionAreEditableAndRenderable) {{"orientation", "Horizontal"}, {"coordinates", {{"origin", -20.0}, {"target", -120.0}}}}); ASSERT_EQ(state.at("type"), "case_state"); - state = patch(session, case_id, {{"partition_count", 4}}); + state = patch(session, case_id, + {{"partition_mode", "Fixed"}, {"partition_count", 4}}); ASSERT_EQ(state.at("type"), "case_state"); EXPECT_EQ(resource_data(state, "frequency_axis").at("orientation"), "Vertical"); EXPECT_EQ(resource_data(state, "value_axis").at("orientation"), "Horizontal"); @@ -467,7 +482,8 @@ TEST(RenderiveWebGallery, AxisSwapAndEveryPlotDirectionAreEditableAndRenderable) state = patch(waterfall, "time_axis", {{"orientation", "Horizontal"}, {"newest_at_start", true}}); ASSERT_EQ(state.at("type"), "case_state"); - state = patch(waterfall, "waterfall", {{"partition_count", 4}}); + state = patch(waterfall, "waterfall", + {{"partition_mode", "Fixed"}, {"partition_count", 4}}); ASSERT_EQ(state.at("type"), "case_state"); EXPECT_EQ(resource_data(state, "frequency_axis").at("orientation"), "Vertical"); EXPECT_EQ(resource_data(state, "time_axis").at("orientation"), "Horizontal"); diff --git a/webapp_gallery/app.js b/webapp_gallery/app.js index 8ccac67..47dfd62 100644 --- a/webapp_gallery/app.js +++ b/webapp_gallery/app.js @@ -58,12 +58,18 @@ function grouped(items) { } return groups; } +function adminiveFieldVisible(expression, data) { + if (!expression) return true; + const equality = expression.match(/^\$\{\$self\.([A-Za-z_][A-Za-z0-9_]*) == '([^']*)'\}$/); + return equality ? String(data?.[equality[1]]) === equality[2] : true; +} function adminiveControls(resource) { const resources = resource?.resources || (resource ? [resource] : []); const controls = []; const visit = (fields, data, target, group, path = [], labels = []) => { for (const field of fields || []) { const presentation = field.presentation || {}; + if (!adminiveFieldVisible(presentation.visible_on, data)) continue; const fieldPath = [...path, field.name]; const fieldLabels = [...labels, presentation.label || field.name]; if (field.children?.length) { @@ -76,7 +82,7 @@ function adminiveControls(resource) { target, path: fieldPath, label: fieldLabels.join(" / "), - api: presentation.description || fieldPath.join("."), + api: fieldPath.join("."), description: presentation.description || "", group, input: presentation.control === "automatic" ? "text" : presentation.control || "text", @@ -637,7 +643,7 @@ function renderControl(item) { const row = document.createElement("div"); row.className = "control-row"; const copy = document.createElement("div"); copy.className = "control-copy"; const label = document.createElement("label"); label.textContent = item.label; - const api = document.createElement("code"); api.textContent = item.api; api.title = item.api; copy.append(label, api); + copy.append(label); let input; if (item.input === "select") { input = document.createElement("select"); @@ -648,10 +654,12 @@ function renderControl(item) { if (item.input === "boolean") input.checked = Boolean(item.value); else input.value = item.value; if (item.input === "number") { input.min = item.minimum; input.max = item.maximum; input.step = item.step || "any"; } } - input.className = "control-input"; input.title = item.description || item.api; + input.className = "control-input"; + input.setAttribute("aria-label", item.label); + input.title = item.description || item.label; const submit = value => { card.send("gallery_patch", {target: item.target, patch: nestedPatch(item.path, value)}); - elements.menuStatus.textContent = `提交 ${item.api} · 等待后端回读`; + elements.menuStatus.textContent = `提交“${item.label}”并等待后端回读`; }; if (item.input === "number") { let committedValue = input.value;