diff --git a/kernel/src/kernel/Task_Graph.cpp b/kernel/src/kernel/Task_Graph.cpp index eceebd4..eeca8ae 100644 --- a/kernel/src/kernel/Task_Graph.cpp +++ b/kernel/src/kernel/Task_Graph.cpp @@ -18,6 +18,7 @@ struct Task_Graph::Private { }; explicit Private(std::string graph_name) : taskflow(std::move(graph_name)) {} + std::vector> attributes{}; tf::Taskflow taskflow{}; /* 第三方 Taskflow 仅存在于本实现单元。 */ std::vector nodes{}; /* 与原生 graph 插入顺序一致的业务节点。 */ std::unordered_map name_counts{}; /* 同名业务节点的下一个序号。 */ @@ -31,7 +32,16 @@ struct Task_Graph::Private { : base + "#" + std::to_string(occurrence); } void collect_nodes(std::string_view parent, + const std::vector>& inherited, std::vector& result) const { + auto graph_attributes = inherited; + for (const auto& attribute : attributes) { + const auto found = std::ranges::find( + graph_attributes, attribute.first, + &std::pair::first); + if (found == graph_attributes.end()) graph_attributes.push_back(attribute); + else found->second = attribute.second; + } for (const auto& source : nodes) { Taskflow_Graph_Trace::Node node{}; node.native_id = static_cast(source.task.hash_value()); @@ -46,7 +56,14 @@ struct Task_Graph::Private { node.parent_node_id = std::string(parent); node.name = source.name; node.type = std::string(tf::to_string(source.task.type())); - node.attributes = source.attributes; + node.attributes = graph_attributes; + for (const auto& attribute : source.attributes) { + const auto found = std::ranges::find( + node.attributes, attribute.first, + &std::pair::first); + if (found == node.attributes.end()) node.attributes.push_back(attribute); + else found->second = attribute.second; + } source.task.for_each_predecessor([&](tf::Task value) { node.predecessors.push_back( static_cast(value.hash_value())); @@ -57,7 +74,8 @@ struct Task_Graph::Private { }); const auto module_id = node.node_id; result.push_back(std::move(node)); - if (source.child) source.child->collect_nodes(module_id, result); + if (source.child) + source.child->collect_nodes(module_id, node.attributes, result); } } @@ -83,7 +101,7 @@ void* Task_Graph_Access::native_storage(Task_Graph& graph) noexcept { std::vector Task_Graph_Access::nodes( const Task_Graph& graph) { std::vector result; - graph.d->collect_nodes({}, result); + graph.d->collect_nodes({}, {}, result); return result; } @@ -146,6 +164,7 @@ Task_Graph& Task_Graph::operator=(Task_Graph&& other) noexcept { } d->taskflow = std::move(other.d->taskflow); d->nodes = std::move(other.d->nodes); + d->attributes = std::move(other.d->attributes); d->name_counts = std::move(other.d->name_counts); ++d->generation; return *this; @@ -214,6 +233,18 @@ Task_Node Task_Graph::compose(std::string task_name, Task_Graph& child) { Task_Node::Private{d, d->nodes.size() - 1, d->generation})}; } +Task_Graph& Task_Graph::describe(std::string key, std::string value) { + if (key.empty()) + throw std::invalid_argument("Task graph attribute key is empty"); + const auto found = std::ranges::find( + d->attributes, key, &std::pair::first); + if (found == d->attributes.end()) + d->attributes.emplace_back(std::move(key), std::move(value)); + else + found->second = std::move(value); + return *this; +} + void Task_Graph::clear() { d->taskflow.clear(); d->nodes.clear(); diff --git a/kernel/src/kernel/Task_Graph.hpp b/kernel/src/kernel/Task_Graph.hpp index 879c22f..324ee82 100644 --- a/kernel/src/kernel/Task_Graph.hpp +++ b/kernel/src/kernel/Task_Graph.hpp @@ -44,6 +44,7 @@ public: Task_Node add_condition(std::string name, std::function work); /* 添加模块节点并引用 child;child 必须活到本图完成执行。 */ Task_Node compose(std::string name, Task_Graph& child); + Task_Graph& describe(std::string key, std::string value); void clear(); [[nodiscard]] bool empty() const noexcept; [[nodiscard]] std::size_t size() const noexcept; diff --git a/kernel/src/kernel/frame.cpp b/kernel/src/kernel/frame.cpp index 2334633..1cb2e67 100644 --- a/kernel/src/kernel/frame.cpp +++ b/kernel/src/kernel/frame.cpp @@ -66,7 +66,7 @@ void Render_Frame::record(Frame_Trace_Measurement measurement, std::uint64_t val std::uint64_t expected{}; d->measurements[index].compare_exchange_strong(expected, encode_present_value(value_ns), std::memory_order_release, std::memory_order_relaxed); } -Frame_Statistics_Sample Render_Frame::statistics(Frame_Dimension dimension) const { +Frame_Statistics_Sample Render_Frame::statistics() const { std::array, marker_count> markers{}; for (std::size_t index = 0; index < marker_count; ++index) { const auto encoded = d->markers[index].load(std::memory_order_acquire); @@ -108,16 +108,10 @@ Frame_Statistics_Sample Render_Frame::statistics(Frame_Dimension dimension) cons result.set(Frame_Statistic::event_dispatch_ms, interval( Frame_Trace_Marker::event_dispatch_started, Frame_Trace_Marker::event_dispatch_finished)); - result.set(Frame_Statistic::prepare_ms, interval( - Frame_Trace_Marker::prepare_started, Frame_Trace_Marker::prepare_finished)); - result.set(Frame_Statistic::paint_ms, interval( - Frame_Trace_Marker::paint_started, Frame_Trace_Marker::paint_finished)); result.set(Frame_Statistic::backend_queue_ms, interval( Frame_Trace_Marker::backend_queue_entered, Frame_Trace_Marker::backend_queue_left)); result.set(Frame_Statistic::gpu_submission_ms, interval( Frame_Trace_Marker::gpu_submitted, Frame_Trace_Marker::gpu_completed)); - result.set(Frame_Statistic::readback_stage_ms, interval( - Frame_Trace_Marker::readback_started, Frame_Trace_Marker::readback_finished)); result.set(Frame_Statistic::callback_ms, interval( Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished)); @@ -146,112 +140,6 @@ Frame_Statistics_Sample Render_Frame::statistics(Frame_Dimension dimension) cons for (std::size_t index = 0; index < measurement_statistics.size(); ++index) result.set(measurement_statistics[index], measurement(measurement_keys[index])); - double remaining = marker(Frame_Trace_Marker::frame_ready); - const auto take = [&](double requested) { - const auto value = std::min(remaining, std::max(0.0, requested)); - remaining -= value; - return value; - }; - const double scene_time = interval(Frame_Trace_Marker::scene_render_started, - Frame_Trace_Marker::scene_render_finished); - const double event_time = std::min(scene_time, interval( - Frame_Trace_Marker::event_dispatch_started, - Frame_Trace_Marker::event_dispatch_finished)); - const double prepare_time = std::min(std::max(0.0, scene_time - event_time), interval( - Frame_Trace_Marker::prepare_started, Frame_Trace_Marker::prepare_finished)); - const double paint_time = std::min( - std::max(0.0, scene_time - event_time - prepare_time), interval( - Frame_Trace_Marker::paint_started, Frame_Trace_Marker::paint_finished)); - - if (dimension == Frame_Dimension::two_dimensional) { - result.set(Frame_Statistic::pipeline_2d_event_ms, take(event_time)); - result.set(Frame_Statistic::pipeline_2d_prepare_ms, take(prepare_time)); - result.set(Frame_Statistic::pipeline_2d_paint_ms, paint_time); - double paint_remaining = paint_time; - const auto take_paint = [&](Frame_Trace_Marker first, - Frame_Trace_Marker last) { - const double value = std::min(paint_remaining, interval(first, last)); - paint_remaining -= value; - return take(value); - }; - result.set(Frame_Statistic::pipeline_2d_frame_target_ms, take_paint( - Frame_Trace_Marker::paint_frame_target_started, - Frame_Trace_Marker::paint_frame_target_finished)); - result.set(Frame_Statistic::pipeline_2d_background_ms, take_paint( - Frame_Trace_Marker::paint_background_started, - Frame_Trace_Marker::paint_background_finished)); - result.set(Frame_Statistic::pipeline_2d_cache_targets_ms, take_paint( - Frame_Trace_Marker::paint_cache_targets_started, - Frame_Trace_Marker::paint_cache_targets_finished)); - result.set(Frame_Statistic::pipeline_2d_taskflow_ms, take_paint( - Frame_Trace_Marker::paint_taskflow_started, - Frame_Trace_Marker::paint_taskflow_finished)); - result.set(Frame_Statistic::pipeline_2d_paint_coordination_ms, - take(paint_remaining)); - result.set(Frame_Statistic::pipeline_2d_scene_coordination_ms, - take(std::max(0.0, scene_time - event_time - prepare_time - paint_time))); - result.set(Frame_Statistic::pipeline_2d_callback_ms, take(interval( - Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished))); - result.set(Frame_Statistic::pipeline_2d_frame_handoff_ms, remaining); - return result; - } - - result.set(Frame_Statistic::pipeline_3d_event_ms, take(event_time)); - result.set(Frame_Statistic::pipeline_3d_prepare_ms, take(prepare_time)); - result.set(Frame_Statistic::pipeline_3d_submit_graph_ms, take(paint_time)); - result.set(Frame_Statistic::pipeline_3d_scene_coordination_ms, - take(std::max(0.0, scene_time - event_time - prepare_time - paint_time))); - const double scene_finished = marker(Frame_Trace_Marker::scene_render_finished); - const double queue_entered = marker(Frame_Trace_Marker::backend_queue_entered); - const double backend_prepare_started = marker(Frame_Trace_Marker::backend_prepare_started); - const double backend_prepare_finished = marker(Frame_Trace_Marker::backend_prepare_finished); - const double submit_queued = marker(Frame_Trace_Marker::backend_submit_queued); - const double queue_left = marker(Frame_Trace_Marker::backend_queue_left); - result.set(Frame_Statistic::pipeline_3d_prepare_queue_ms, take(std::max( - 0.0, backend_prepare_started - std::max(scene_finished, queue_entered)))); - double preparation_window = std::max(0.0, - backend_prepare_finished - backend_prepare_started); - const auto take_preparation = [&](Frame_Trace_Measurement key) { - const double value = std::min(preparation_window, - std::max(0.0, measurement(key))); - preparation_window -= value; - return take(value); - }; - result.set(Frame_Statistic::pipeline_3d_backend_apply_ms, - take_preparation(Frame_Trace_Measurement::backend_apply_ns)); - result.set(Frame_Statistic::pipeline_3d_backend_plan_ms, - take_preparation(Frame_Trace_Measurement::backend_plan_ns)); - result.set(Frame_Statistic::pipeline_3d_backend_execute_ms, - take_preparation(Frame_Trace_Measurement::backend_execute_ns)); - result.set(Frame_Statistic::pipeline_3d_backend_commands_ms, take(preparation_window)); - result.set(Frame_Statistic::pipeline_3d_backend_queue_ms, - take(std::max(0.0, queue_left - submit_queued))); - const double gpu_submitted = marker(Frame_Trace_Marker::gpu_submitted); - double submit_window = std::max(0.0, gpu_submitted - queue_left); - const double measured_submit = std::min(submit_window, std::max( - 0.0, measurement(Frame_Trace_Measurement::backend_submit_ns))); - result.set(Frame_Statistic::pipeline_3d_backend_submit_ms, take(measured_submit)); - submit_window -= measured_submit; - result.set(Frame_Statistic::pipeline_3d_submit_handoff_ms, take(submit_window)); - double gpu_window = interval(Frame_Trace_Marker::gpu_submitted, - Frame_Trace_Marker::gpu_completed); - const auto take_gpu = [&](Frame_Trace_Measurement key) { - const double value = std::min(gpu_window, std::max(0.0, measurement(key))); - gpu_window -= value; - return take(value); - }; - result.set(Frame_Statistic::pipeline_3d_gpu_render_ms, - take_gpu(Frame_Trace_Measurement::gpu_render_ns)); - result.set(Frame_Statistic::pipeline_3d_gpu_transition_ms, - take_gpu(Frame_Trace_Measurement::gpu_transition_ns)); - result.set(Frame_Statistic::pipeline_3d_gpu_copy_ms, - take_gpu(Frame_Trace_Measurement::gpu_copy_ns)); - result.set(Frame_Statistic::pipeline_3d_gpu_sync_ms, take(gpu_window)); - result.set(Frame_Statistic::pipeline_3d_readback_ms, take(interval( - Frame_Trace_Marker::readback_started, Frame_Trace_Marker::readback_finished))); - result.set(Frame_Statistic::pipeline_3d_callback_ms, take(interval( - Frame_Trace_Marker::callback_started, Frame_Trace_Marker::callback_finished))); - result.set(Frame_Statistic::pipeline_3d_completion_handoff_ms, remaining); return result; } diff --git a/kernel/src/kernel/frame.hpp b/kernel/src/kernel/frame.hpp index 496ad21..1c422b3 100644 --- a/kernel/src/kernel/frame.hpp +++ b/kernel/src/kernel/frame.hpp @@ -8,7 +8,6 @@ namespace aethera { namespace detail { struct Taskflow_Frame_Access; } -enum struct Frame_Dimension : std::uint8_t; struct Frame_Statistics_Sample; enum struct Frame_Trace_Marker : std::uint8_t { created, @@ -131,7 +130,7 @@ public: [[nodiscard]] std::uint64_t created_time_unix_ns() const noexcept; void mark(Frame_Trace_Marker marker) noexcept; void record(Frame_Trace_Measurement measurement, std::uint64_t value_ns) noexcept; - [[nodiscard]] Frame_Statistics_Sample statistics(Frame_Dimension dimension) const; + [[nodiscard]] Frame_Statistics_Sample statistics() const; void request_taskflow_trace() noexcept; [[nodiscard]] bool taskflow_trace_requested() const noexcept; [[nodiscard]] Taskflow_Frame_Trace take_taskflow_trace(); diff --git a/kernel/src/kernel/frame_statistics.cpp b/kernel/src/kernel/frame_statistics.cpp index b637e1d..5ff3a5e 100644 --- a/kernel/src/kernel/frame_statistics.cpp +++ b/kernel/src/kernel/frame_statistics.cpp @@ -203,8 +203,8 @@ Frame_Statistics_Accumulator::Frame_Statistics_Accumulator(std::size_t capacity) } const Frame_Statistics_State& Frame_Statistics_Accumulator::submit( - const Render_Frame& frame, Frame_Dimension dimension) { - auto sample = frame.statistics(dimension); + const Render_Frame& frame) { + auto sample = frame.statistics(); const auto now = std::chrono::steady_clock::now(); const auto frame_identity = frame.identity(); if (previous_completion_ != std::chrono::steady_clock::time_point{}) { diff --git a/kernel/src/kernel/frame_statistics.hpp b/kernel/src/kernel/frame_statistics.hpp index be63438..74295db 100644 --- a/kernel/src/kernel/frame_statistics.hpp +++ b/kernel/src/kernel/frame_statistics.hpp @@ -7,7 +7,6 @@ #include #include namespace aethera { -enum struct Frame_Dimension : std::uint8_t { two_dimensional, three_dimensional }; enum struct Frame_Statistic : std::uint8_t { plot_tick_queue_ms, plot_update_ms, @@ -15,11 +14,8 @@ enum struct Frame_Statistic : std::uint8_t { server_completion_ms, scene_render_ms, event_dispatch_ms, - prepare_ms, - paint_ms, backend_queue_ms, gpu_submission_ms, - readback_stage_ms, callback_ms, backend_apply_ms, backend_plan_ms, @@ -31,36 +27,6 @@ enum struct Frame_Statistic : std::uint8_t { gpu_copy_ms, gpu_total_ms, readback_ms, - pipeline_2d_event_ms, - pipeline_2d_prepare_ms, - pipeline_2d_paint_ms, - pipeline_2d_frame_target_ms, - pipeline_2d_background_ms, - pipeline_2d_cache_targets_ms, - pipeline_2d_taskflow_ms, - pipeline_2d_paint_coordination_ms, - pipeline_2d_scene_coordination_ms, - pipeline_2d_callback_ms, - pipeline_2d_frame_handoff_ms, - pipeline_3d_event_ms, - pipeline_3d_prepare_ms, - pipeline_3d_submit_graph_ms, - pipeline_3d_scene_coordination_ms, - pipeline_3d_prepare_queue_ms, - pipeline_3d_backend_apply_ms, - pipeline_3d_backend_plan_ms, - pipeline_3d_backend_execute_ms, - pipeline_3d_backend_commands_ms, - pipeline_3d_backend_queue_ms, - pipeline_3d_backend_submit_ms, - pipeline_3d_submit_handoff_ms, - pipeline_3d_gpu_render_ms, - pipeline_3d_gpu_transition_ms, - pipeline_3d_gpu_copy_ms, - pipeline_3d_gpu_sync_ms, - pipeline_3d_readback_ms, - pipeline_3d_callback_ms, - pipeline_3d_completion_handoff_ms, frame_interval_ms, count }; @@ -155,8 +121,7 @@ struct Frame_Statistics_State { struct Frame_Statistics_Accumulator final { public: explicit Frame_Statistics_Accumulator(std::size_t capacity = 600); - [[nodiscard]] const Frame_Statistics_State& submit( - const Render_Frame& frame, Frame_Dimension dimension); + [[nodiscard]] const Frame_Statistics_State& submit(const Render_Frame& frame); void reset() noexcept; private: std::vector values_; diff --git a/kernel/src/kernel/renderable.hpp b/kernel/src/kernel/renderable.hpp index 35c9add..801bfe5 100644 --- a/kernel/src/kernel/renderable.hpp +++ b/kernel/src/kernel/renderable.hpp @@ -31,16 +31,8 @@ concept Renderable_Object = Attached && std::derived_from; struct Renderable : Def { /* Renderable 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */ struct Prop : Prev_Prop {}; - /* Renderable 每次 Scene 执行后发布的业务图状态与统计。 */ - struct State : Prev_State { - bool render_dirty{}; /* 条件判断时观察到的 Render_Graph_Tag dirty 状态。 */ - bool render_executed{}; /* 本次 Scene 执行是否运行了业务图。 */ - bool graph_rebuilt{}; /* 本次 advance 是否重建了业务图。 */ - std::size_t task_count{}; /* 当前业务图的任务数。 */ - std::uint64_t execution_time_ns{}; /* 本次业务图实际执行耗时,单位为纳秒。 */ - /* 支持测试、快照比较和变更检测的逐字段相等比较。 */ - bool operator==(const State&) const = default; - }; + /* 任务拓扑和执行状态由逐帧 Taskflow trace 发布,不进入业务 State。 */ + struct State : Prev_State {}; /* 完整声明、内部派发以及 Prepare/Paint CRTP 能力契约见 renderable.ipp 中的 Renderable::Private。 */ struct Private; /* 返回该对象参与当前事件竞争时的几何距离;无值表示沿用普通绘制层级路由。 */ diff --git a/kernel/src/kernel/renderable.ipp b/kernel/src/kernel/renderable.ipp index 21ef1ac..9141b42 100644 --- a/kernel/src/kernel/renderable.ipp +++ b/kernel/src/kernel/renderable.ipp @@ -12,20 +12,20 @@ struct Renderable::Private : Prev_Private { using Build_Graph_Run = std::optional (*)(Root*); using Graph_Rebuild_Run = bool (*)(Root*); using Render_Predicate_Run = bool (*)(Root*, bool); - using Pending_State_Run = void* (*)(Root*); using Publish_State_Run = void (*)(Root*); std::string business_name_value{}; + std::string diagnostic_component_id{}; /* 按需诊断使用的组件语义 ID;不属于业务状态。 */ Build_Graph_Run build_graph_run{}; Graph_Rebuild_Run graph_rebuild_run{}; Render_Predicate_Run render_predicate_run{}; - Pending_State_Run pending_state_run{}; Publish_State_Run publish_state_run{}; Event_Run event_run{}; Event_Routing_Distance_Run event_routing_distance_run{}; Color_Cache_Visit color_cache_visit{}; std::optional graph{}; Task_Graph extension{"renderable.extension"}; + bool render_graph_executed{}; /* 当前 Scene 帧的条件节点是否选择了本对象业务图。 */ [[nodiscard]] std::string_view business_name() const noexcept { return business_name_value; @@ -39,9 +39,6 @@ struct Renderable::Private : Prev_Private { [[nodiscard]] bool should_render(Root* object, bool dirty) const { return render_predicate_run ? render_predicate_run(object, dirty) : dirty; } - [[nodiscard]] void* pending_renderable_state(Root* object) const { - return pending_state_run(object); - } void publish_renderable_state(Root* object) const { publish_state_run(object); } @@ -102,10 +99,6 @@ void Renderable::Private::bind_private_crtp(Object* object) { return private_data.should_render(value, state, dirty); return dirty; }; - data.pending_state_run = [](Root* root) -> void* { - auto* value = static_cast(root); - return &double_buffer::detail::Internal_Access::pending_state(value); - }; data.publish_state_run = [](Root* root) { auto* value = static_cast(root); double_buffer::detail::Internal_Access::publish_state(value); @@ -125,9 +118,8 @@ void Renderable::Private::bind_private_crtp(Object* object) { if constexpr (std::derived_from) { data.color_cache_visit = [](Root* root, void* context, Color_Cache_Visitor visitor) { auto* value = static_cast(root); - const auto& render_state = static_cast( - double_buffer::detail::Internal_Access::current_state(value)); - if (render_state.render_executed) + if (double_buffer::detail::Internal_Access::get(value) + .render_graph_executed) visitor(context, double_buffer::detail::Internal_Access::pending_buffer(value)); else visitor(context, double_buffer::detail::Internal_Access::current_buffer(value)); diff --git a/kernel/src/kernel/scene.hpp b/kernel/src/kernel/scene.hpp index 78fdf38..ffddf96 100644 --- a/kernel/src/kernel/scene.hpp +++ b/kernel/src/kernel/scene.hpp @@ -20,17 +20,9 @@ struct Scene : Def; /* Scene 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */ struct Prop : Prev_Prop {}; - /* Scene 每次 process(...) 后发布的总图结构与执行统计。 */ + /* Scene 只发布输入事件业务统计;任务图结构与执行数据来自逐帧 trace。 */ struct State : Prev_State { Event_Statistics_State event_statistics{}; /* 输入进入 Scene 到 Renderable 消费完成的分类统计。 */ - bool taskflow_rebuilt{}; /* 本次 process(...) 前的 advance 是否重新构建了总 Taskflow。 */ - std::size_t renderable_count{}; /* Prepare 数据依赖图中的 Renderable 数量。 */ - std::size_t taskflow_task_count{}; /* 当前 Prepare Taskflow 中的任务节点数量。 */ - std::size_t taskflow_dependency_count{}; /* 当前 Prepare Taskflow 中的直接依赖边数量。 */ - std::size_t taskflow_max_predecessors{}; /* 当前 Prepare Taskflow 中单个任务的最大直接前驱数量。 */ - std::size_t taskflow_max_successors{}; /* 当前 Prepare Taskflow 中单个任务的最大直接后继数量。 */ - std::uint64_t taskflow_execution_time_ns{}; /* 本次 process(...) 执行 Prepare Taskflow 的耗时,单位为纳秒;没有任务时为 0。 */ - /* 支持测试、快照比较和变更检测的逐字段相等比较。 */ bool operator==(const State&) const = default; }; /* 完整声明、字段及可覆盖 CRTP hook 见 scene.ipp 中的 Scene::Private。 */ diff --git a/kernel/src/kernel/scene.ipp b/kernel/src/kernel/scene.ipp index cadba00..d3cbd11 100644 --- a/kernel/src/kernel/scene.ipp +++ b/kernel/src/kernel/scene.ipp @@ -1,7 +1,6 @@ #pragma once #include "Task_Graph_Internal.hpp" #include -#include #include #include #include @@ -65,19 +64,19 @@ void Scene::Private::process(Object* object, Callback&& callback) requires std:: } template void Scene::Private::process(Object* object, Render_Frame* frame, Callback&& callback) requires std::invocable { - auto& state = static_cast( - double_buffer::detail::Internal_Access::pending_state(object)); - state.taskflow_execution_time_ns = 0; if (frame) frame->mark(Frame_Trace_Marker::prepare_started); if (runtime->taskflow && !runtime->taskflow->empty()) - state.taskflow_execution_time_ns = frame && frame->taskflow_trace_requested() - ? detail::run_taskflow(*runtime->taskflow, *frame, "scene.renderables") - : detail::run_taskflow(*runtime->taskflow); + if (frame && frame->taskflow_trace_requested()) + (void)detail::run_taskflow(*runtime->taskflow, *frame, "scene.renderables"); + else + (void)detail::run_taskflow(*runtime->taskflow); if (frame) frame->mark(Frame_Trace_Marker::prepare_finished); double_buffer::detail::Internal_Access::current_dependency_graph(object).for_each_bound( [](Renderable* renderable, Renderable::Private& data) { data.publish_renderable_state(renderable); }); + auto& state = static_cast( + double_buffer::detail::Internal_Access::pending_state(object)); state.event_statistics = event_statistics.state(); double_buffer::detail::Internal_Access::publish_state(object); Result result; @@ -90,8 +89,6 @@ void Scene::Private::after_advance(Object* object, const Prop* current_prop, State_Access current_states) { auto* resource = detail::task_memory_resource(); - auto& scene_state = pending_states.get(); - scene_state.taskflow_rebuilt = false; std::pmr::unordered_set advanced_objects{resource}; advanced_objects.insert(object); double_buffer::detail::Internal_Access::for_each_current_dependency_graph(object, @@ -112,16 +109,17 @@ void Scene::Private::after_advance(Object* object, [&](auto& graph_state) { taskflow_dirty = taskflow_dirty || graph_state.dirty(); }); render_dependencies.for_each_bound( [&](Renderable* renderable, Renderable::Private& data) { - auto& state = *static_cast( - data.pending_renderable_state(renderable)); - state.graph_rebuilt = false; if (!data.graph || data.should_rebuild_runtime_graph(renderable)) { data.graph = data.build_runtime_graph(renderable); - state.graph_rebuilt = true; + if (data.graph) { + data.graph->describe("owner_kind", "renderable"); + if (!data.diagnostic_component_id.empty()) + data.graph->describe("owner_component", + data.diagnostic_component_id); + } double_buffer::detail::Internal_Access::mark_dirty( renderable); } - state.task_count = data.graph ? data.graph->size() : 0; }); if (!taskflow_dirty) return; if (!runtime->taskflow) runtime->taskflow = std::make_unique("scene.render"); @@ -132,7 +130,6 @@ void Scene::Private::after_advance(Object* object, renderables.insert(renderable); } ); - scene_state.renderable_count = renderables.size(); taskflow.clear(); struct Render_Tasks { Task_Node entry; @@ -149,32 +146,26 @@ void Scene::Private::after_advance(Object* object, if (!data) continue; const auto task_prefix = std::string(data->business_name()) + ".render"; auto render_if = taskflow.add_condition(task_prefix + ".condition", [data, root] { - auto& state = *static_cast(data->pending_renderable_state(root)); - state.execution_time_ns = 0; const bool dirty = double_buffer::detail::Internal_Access::dirty(root); - state.render_dirty = dirty; - state.render_executed = data->should_render(root, dirty); - if (state.render_executed) { - state.execution_time_ns = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); - } - return state.render_executed ? 0 : 1; + data->render_graph_executed = data->should_render(root, dirty); + return data->render_graph_executed ? 0 : 1; }); Task_Node render_run = data->graph ? taskflow.compose(task_prefix + ".graph", *data->graph) : taskflow.add(task_prefix + ".empty", [] {}); auto render_done = taskflow.add(task_prefix + ".complete", [data, root] { - auto& state = *static_cast(data->pending_renderable_state(root)); - if (state.render_executed) { + if (data->render_graph_executed) (void)double_buffer::detail::Internal_Access::take_dirty(root); - const auto finished = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); - state.execution_time_ns = finished - state.execution_time_ns; - } }); + const auto identify_renderable_task = [data](Task_Node& node) { + node.describe("owner_kind", "renderable"); + if (!data->diagnostic_component_id.empty()) + node.describe("owner_component", data->diagnostic_component_id); + }; + identify_renderable_task(render_if); + identify_renderable_task(render_run); + identify_renderable_task(render_done); render_if.precede(render_run); render_if.precede(render_done); if (data->extension.empty()) { @@ -183,6 +174,7 @@ void Scene::Private::after_advance(Object* object, else { auto extension = taskflow.compose( task_prefix + ".extension", data->extension); + identify_renderable_task(extension); render_run.precede(extension); extension.precede(render_done); } @@ -214,19 +206,7 @@ void Scene::Private::after_advance(Object* object, ); }; connect_dependencies(render_dependencies); - scene_state.taskflow_task_count = taskflow.size(); - scene_state.taskflow_dependency_count = 0; - scene_state.taskflow_max_predecessors = 0; - scene_state.taskflow_max_successors = 0; - for (const auto& node : detail::Task_Graph_Access::nodes(taskflow)) { - scene_state.taskflow_dependency_count += node.successors.size(); - scene_state.taskflow_max_predecessors = std::max( - scene_state.taskflow_max_predecessors, node.predecessors.size()); - scene_state.taskflow_max_successors = std::max( - scene_state.taskflow_max_successors, node.successors.size()); - } double_buffer::detail::Internal_Access::access_pending_dependency_graph(object, [](auto& graph_state) { if (graph_state.dirty()) graph_state.take_dirty(); }); - scene_state.taskflow_rebuilt = true; } } diff --git a/kernel/src/test/render_test.cpp b/kernel/src/test/render_test.cpp index 5573936..2836d7f 100644 --- a/kernel/src/test/render_test.cpp +++ b/kernel/src/test/render_test.cpp @@ -113,12 +113,12 @@ TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundarie int scene_updates = 0; int runtime_updates = 0; renderable->set_state_callback([&](const auto& state) { + (void)state; ++renderable_updates; - EXPECT_TRUE(state.render_executed); }); scene->set_state_callback([&](const auto& state) { + (void)state; ++scene_updates; - EXPECT_GT(state.taskflow_task_count, 0u); }); aethera::set_runtime_state_callback([&](const auto& state) { ++runtime_updates; @@ -131,34 +131,6 @@ TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundarie EXPECT_EQ(runtime_updates, 1); aethera::clear_runtime_state_callback(); } -TEST(scene_state, structural_statistics_survive_a_process_without_rebuild) { - aethera::initialize_runtime({.workers = 2}); - auto renderable = build_object(); - auto scene = build_object(); - add_renderable(*scene, renderable.get()); - std::size_t task_count{}; - std::size_t dependency_count{}; - int updates{}; - scene->set_state_callback([&](const auto& state) { - EXPECT_EQ(state.renderable_count, 1u); - EXPECT_GT(state.taskflow_task_count, 0u); - if (updates == 0) { - EXPECT_TRUE(state.taskflow_rebuilt); - task_count = state.taskflow_task_count; - dependency_count = state.taskflow_dependency_count; - } - else { - EXPECT_FALSE(state.taskflow_rebuilt); - EXPECT_EQ(state.taskflow_task_count, task_count); - EXPECT_EQ(state.taskflow_dependency_count, dependency_count); - } - ++updates; - }); - scene->process([](const auto&) {}); - scene->process([](const auto&) {}); - EXPECT_EQ(updates, 2); -} - TEST(scene_condition, render_order_does_not_create_a_false_data_dependency) { aethera::initialize_runtime({.workers = 2}); auto source = build_object(); @@ -189,6 +161,8 @@ TEST(task_graph_observer, business_dag_and_native_execution_share_node_identity) aethera::initialize_runtime({.workers = 2}); std::atomic_int completed{}; aethera::Task_Graph child{"test.visual"}; + child.describe("owner_kind", "renderable") + .describe("owner_component", "spectrum"); auto prepare = child.add("prepare.samples", [&] { completed.fetch_add(1); }); auto paint = child.add("paint.visual", [&] { completed.fetch_add(1); }); prepare.precede(paint); @@ -233,6 +207,12 @@ TEST(task_graph_observer, business_dag_and_native_execution_share_node_identity) ASSERT_NE(child_prepare, trace.graphs.front().nodes.end()); EXPECT_EQ(child_prepare->parent_node_id, module->node_id); EXPECT_EQ(child_prepare->node_id, "test.frame/spectrum/prepare.samples"); + EXPECT_TRUE(std::ranges::contains( + child_prepare->attributes, + std::pair{"owner_kind", "renderable"})); + EXPECT_TRUE(std::ranges::contains( + child_prepare->attributes, + std::pair{"owner_component", "spectrum"})); std::unordered_set metadata_ids; for (const auto& node : trace.graphs.front().nodes) diff --git a/render_2D/render_2D/plottable/Afterglow.ipp b/render_2D/render_2D/plottable/Afterglow.ipp index 29a2482..3571db1 100644 --- a/render_2D/render_2D/plottable/Afterglow.ipp +++ b/render_2D/render_2D/plottable/Afterglow.ipp @@ -9,6 +9,7 @@ struct Afterglow::Private : Prev_Private { detail::Raster_Layout layout{}; /* 两根轴决定的色块矩阵布局。 */ std::vector intensity{}; /* 历史频谱衰减累加后的未归一化强度。 */ std::vector pixels{}; /* 归一化强度经色图转换后的像素矩阵。 */ + std::vector partition_maxima{}; /* 各独占分块在累加后产生的归约输入。 */ Size canvas{}; /* 当前 Scene viewport 的像素尺寸。 */ Plot_Ratio maximum{1.0}; /* 本帧强度归一化分母,最小为 1。 */ bool valid{}; /* 轴布局和输入数据是否足以生成色块。 */ @@ -84,7 +85,9 @@ inline Task_Graph Afterglow::Private::build_graph(Afterglow* object, const Prop& auto normalize = graph.add("prepare.normalize", [this] { normalize_frame(); }); + auto colored = graph.add("prepare.color.complete", [] {}); if (partition_count == 0) begin.precede(normalize); + normalize.precede(colored); for (Plot_Partition_Count index = 0; index < partition_count; ++index) { auto accumulate = graph.add("prepare.accumulate", [this, object, index] { accumulate_partition(object, index); @@ -98,7 +101,8 @@ inline Task_Graph Afterglow::Private::build_graph(Afterglow* object, const Prop& begin.precede(accumulate); accumulate.precede(normalize); normalize.precede(color); - color.precede(paint); + color.precede(colored); + colored.precede(paint); } return graph; } @@ -135,6 +139,8 @@ inline void Afterglow::Private::prepare_frame(Afterglow* object) { prepared.intensity.assign(cells, 0.0); prepared.pixels.assign(cells, 0); } + prepared.partition_maxima.assign( + detail::raster_partition_count(graph_partition_grid), 1.0); prepared.valid = true; } inline void Afterglow::Private::accumulate_partition(Afterglow* object, Plot_Partition_Count index) { @@ -181,9 +187,21 @@ inline void Afterglow::Private::accumulate_partition(Afterglow* object, Plot_Par } } } + Plot_Ratio maximum{1.0}; + for (int y = partition.first_y; y < partition.last_y; ++y) + for (int x = partition.first_x; x < partition.last_x; ++x) { + const auto [column, row] = + detail::raster_coordinates(prepared.layout, x, y); + maximum = std::max(maximum, prepared.intensity[ + static_cast(row) * columns + column]); + } + prepared.partition_maxima[index] = maximum; } inline void Afterglow::Private::normalize_frame() { - if (prepared.valid && !prepared.intensity.empty()) prepared.maximum = std::max(1.0, *std::max_element(prepared.intensity.begin(), prepared.intensity.end())); + prepared.maximum = prepared.valid && !prepared.partition_maxima.empty() + ? *std::max_element(prepared.partition_maxima.begin(), + prepared.partition_maxima.end()) + : 1.0; } inline void Afterglow::Private::color_partition(Afterglow* object, Plot_Partition_Count index) { if (!prepared.valid) return; diff --git a/render_2D/render_2D/plottable/Spectrum.ipp b/render_2D/render_2D/plottable/Spectrum.ipp index 29eade3..d811667 100644 --- a/render_2D/render_2D/plottable/Spectrum.ipp +++ b/render_2D/render_2D/plottable/Spectrum.ipp @@ -1,7 +1,9 @@ #pragma once #include +#include #include #include +#include #include "common/Curve_Plot.hpp" namespace aethera::render_2d { struct Spectrum::Private : Prev_Private { @@ -25,7 +27,7 @@ struct Spectrum::Private : Prev_Private { struct Prepared { std::vector partitions{}; /* Prepare 子图各分块的独立输出。 */ std::vector markers{}; /* 中心频率及自定义频率标记。 */ - std::vector extremes{}; /* 当前帧启用的最大值和最小值标记。 */ + std::array, 2> extremes{}; /* 最大/最小值由两个独立 Prepare 节点写入。 */ Rect_F sweep_region{}; /* 扫频背景在画布中的矩形。 */ Size canvas_size{}; /* 所属 Scene viewport 决定的颜色层尺寸。 */ bool valid{}; /* 两根轴与画布是否足以生成绘制数据。 */ @@ -36,6 +38,7 @@ struct Spectrum::Private : Prev_Private { Prepared prepared{}; /* 当前权威 State、Frame 和轴状态推导出的 Paint 输入。 */ std::vector maxima{}; /* 当前样本历史逐点最大值;只由 Prepare 更新。 */ std::vector minima{}; /* 当前样本历史逐点最小值;只由 Prepare 更新。 */ + bool hold_history_reset{}; /* prepare.frame 是否已用当前帧初始化整段保持值。 */ std::size_t graph_partition_count{}; /* Builder 内部绑定 Scene 与两根轴;三个来源都必须比 Spectrum 生命周期更长。 */ void bind_render_sources(Frequency_Object* frequency_axis_value, Power_Object* power_axis_value); @@ -46,6 +49,8 @@ struct Spectrum::Private : Prev_Private { [[nodiscard]] Task_Graph build_graph(Spectrum* object, const Prop& state); [[nodiscard]] bool should_rebuild_graph(Spectrum* object, const Prop& state); void prepare_frame(Spectrum* object, std::size_t partition_count); + void update_hold_partition(Spectrum* object, std::size_t partition_index); + void prepare_extreme(Spectrum* object, bool maximum); void prepare_partition(Spectrum* object, std::size_t partition_index); void begin_paint(Spectrum* object); void paint_partition(Spectrum* object, std::size_t partition_index); @@ -99,15 +104,35 @@ inline Task_Graph Spectrum::Private::build_graph(Spectrum* object, const Prop& s auto prepare_begin = graph.add("prepare.frame", [this, object, partition_count] { prepare_frame(object, partition_count); }); + auto hold_complete = graph.add("prepare.hold.complete", [] {}); + std::vector hold_partitions; + hold_partitions.reserve(partition_count); + for (std::size_t index = 0; index < partition_count; ++index) { + auto partition = graph.add("prepare.hold.partition", [this, object, index] { + update_hold_partition(object, index); + }); + prepare_begin.precede(partition); + partition.precede(hold_complete); + hold_partitions.push_back(partition); + } + if (hold_partitions.empty()) prepare_begin.precede(hold_complete); std::vector prepared_partitions; prepared_partitions.reserve(partition_count); for (std::size_t index = 0; index < partition_count; ++index) { auto partition = graph.add("prepare.partition", [this, object, index] { prepare_partition(object, index); }); - prepare_begin.precede(partition); + hold_complete.precede(partition); prepared_partitions.push_back(partition); } + auto maximum = graph.add("prepare.extreme.maximum", [this, object] { + prepare_extreme(object, true); + }); + auto minimum = graph.add("prepare.extreme.minimum", [this, object] { + prepare_extreme(object, false); + }); + prepare_begin.precede(maximum); + prepare_begin.precede(minimum); auto paint_begin = graph.add("paint.begin", [this, object] { begin_paint(object); }); @@ -123,6 +148,8 @@ inline Task_Graph Spectrum::Private::build_graph(Spectrum* object, const Prop& s paint_begin.precede(partition); partition.precede(overlays); } + maximum.precede(overlays); + minimum.precede(overlays); if (partition_count == 0) paint_begin.precede(overlays); return graph; } @@ -130,16 +157,11 @@ inline void Spectrum::Private::prepare_frame(Spectrum* object, std::size_t parti auto& private_data = double_buffer::detail::Internal_Access::get(object); const auto& state = static_cast(double_buffer::detail::Internal_Access::current_prop(object)); const auto& frame = double_buffer::detail::Internal_Access::current_buffer(object); - if (maxima.size() != frame.samples.size()) { + hold_history_reset = maxima.size() != frame.samples.size(); + if (hold_history_reset) { maxima = frame.samples; minima = frame.samples; } - else { - for (std::size_t index = 0; index < frame.samples.size(); ++index) { - maxima[index] = std::max(maxima[index], frame.samples[index]); - minima[index] = std::min(minima[index], frame.samples[index]); - } - } const auto& scene_state = this->template read_current_prop(scene); const auto& frequency_layout = this->template read_current_prop(frequency_axis); const auto& power_layout = this->template read_current_prop(power_axis); @@ -156,19 +178,55 @@ inline void Spectrum::Private::prepare_frame(Spectrum* object, std::size_t parti const Marker_Style style = static_cast(index) == state.selected_marker ? Marker_Style::selected : Marker_Style::normal; prepared.markers.push_back({detail::map_plot_point(frequency_axis, frequency, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation), detail::map_plot_point(frequency_axis, frequency, power_axis, power_state.coordinate_range.target, frequency_layout.orientation), style}); } - if (!frame.samples.empty() && (state.max_marker_visible || state.min_marker_visible)) { - const Spectrum_Interpolation_Ratio denominator = frame.samples.size() > 1 ? static_cast(frame.samples.size() - 1) : 1.0; - const auto prepare_extreme = [&](bool maximum) { - const auto iterator = maximum ? std::max_element(frame.samples.begin(), frame.samples.end()) : std::min_element(frame.samples.begin(), frame.samples.end()); - const auto index = static_cast(std::distance(frame.samples.begin(), iterator)); - const Spectrum_Frequency frequency = state.frequency_range.origin + state.frequency_range.length() * static_cast(index) / denominator; - prepared.extremes.push_back({detail::map_plot_point(frequency_axis, frequency, power_axis, *iterator, frequency_layout.orientation), maximum}); - }; - if (state.max_marker_visible) prepare_extreme(true); - if (state.min_marker_visible) prepare_extreme(false); - } prepared.valid = true; } +inline void Spectrum::Private::update_hold_partition( + Spectrum* object, std::size_t partition_index) { + if (hold_history_reset || graph_partition_count == 0) return; + const auto& frame = double_buffer::detail::Internal_Access:: + current_buffer(object); + const std::size_t first = frame.samples.size() * partition_index / + graph_partition_count; + const std::size_t last = frame.samples.size() * (partition_index + 1) / + graph_partition_count; + for (std::size_t index = first; index < last; ++index) { + maxima[index] = std::max(maxima[index], frame.samples[index]); + minima[index] = std::min(minima[index], frame.samples[index]); + } +} +inline void Spectrum::Private::prepare_extreme(Spectrum* object, bool maximum) { + const auto& state = static_cast( + double_buffer::detail::Internal_Access::current_prop(object)); + const std::size_t slot = maximum ? 0 : 1; + if (!prepared.valid || (maximum ? !state.max_marker_visible + : !state.min_marker_visible)) { + prepared.extremes[slot].reset(); + return; + } + const auto& frame = double_buffer::detail::Internal_Access:: + current_buffer(object); + if (frame.samples.empty()) { + prepared.extremes[slot].reset(); + return; + } + const auto iterator = maximum + ? std::max_element(frame.samples.begin(), frame.samples.end()) + : std::min_element(frame.samples.begin(), frame.samples.end()); + const auto index = static_cast( + std::distance(frame.samples.begin(), iterator)); + const Spectrum_Interpolation_Ratio denominator = frame.samples.size() > 1 + ? static_cast(frame.samples.size() - 1) + : 1.0; + const Spectrum_Frequency frequency = state.frequency_range.origin + + state.frequency_range.length() * + static_cast(index) / denominator; + const auto& frequency_layout = this->template read_current_prop< + Abs_Axis::Base_Tag>(frequency_axis); + prepared.extremes[slot] = Prepared_Extreme{ + detail::map_plot_point(frequency_axis, frequency, power_axis, *iterator, + frequency_layout.orientation), + maximum}; +} inline void Spectrum::Private::prepare_partition(Spectrum* object, std::size_t partition_index) { if (!prepared.valid || partition_index >= prepared.partitions.size()) return; auto& private_data = double_buffer::detail::Internal_Access::get(object); @@ -233,8 +291,9 @@ inline void Spectrum::Private::paint_overlays(Spectrum* object) { if (pen.enabled()) painter.line(marker.first, marker.second, pen); } for (const auto& extreme : prepared.extremes) { - const Pen& pen = extreme.maximum ? state.max_pen : state.min_pen; - painter.circle(extreme.point, 3.0, pen, Brush{pen.color, Brush_Style::solid}); + if (!extreme) continue; + const Pen& pen = extreme->maximum ? state.max_pen : state.min_pen; + painter.circle(extreme->point, 3.0, pen, Brush{pen.color, Brush_Style::solid}); } } template diff --git a/render_2D/render_2D/plottable/Waterfall.ipp b/render_2D/render_2D/plottable/Waterfall.ipp index 05c0894..abdfb07 100644 --- a/render_2D/render_2D/plottable/Waterfall.ipp +++ b/render_2D/render_2D/plottable/Waterfall.ipp @@ -9,9 +9,14 @@ namespace aethera::render_2d { struct Waterfall::Private : Prev_Private { struct Prepared { + struct Tile { + detail::Raster_Partition source{}; /* 含插值 halo 的独占不可变源区域。 */ + Rect_F target{}; /* source 在完整热图目标矩形中的精确投影。 */ + std::vector pixels{}; + }; std::vector> rows_by_slot{}; /* 时间槽到本帧源行的唯一映射;空槽保持空指针。 */ detail::Raster_Layout layout{}; /* 可视频段与完整时间窗口组成的色块布局。 */ - std::vector pixels{}; /* 固定时间窗口的像素矩阵;无数据槽保持透明。 */ + std::vector tiles{}; /* 每个任务只写、随后只读自己的插值源块。 */ Rect_F tooltip_box{}; /* 当前 hover 提示框的画布矩形。 */ std::string tooltip_text{}; /* 当前 hover 频率文本;空值表示不绘制。 */ Size canvas{}; /* 当前 Scene viewport 的像素尺寸。 */ @@ -143,7 +148,7 @@ inline void Waterfall::Private::prepare_frame(Waterfall* object) { return; } prepared.source_first = selection->first; - prepared.pixels.assign(static_cast(prepared.layout.width) * prepared.layout.height, 0); + prepared.tiles.resize(detail::raster_partition_count(graph_partition_grid)); const Axis_Coordinate direction = time_range.length() < 0.0 ? -1.0 : 1.0; const Axis_Coordinate first_center = time_range.origin + direction * 0.5; prepared.rows_by_slot.resize(static_cast(time_slots)); @@ -174,10 +179,33 @@ inline void Waterfall::Private::prepare_frame(Waterfall* object) { inline void Waterfall::Private::prepare_partition(Waterfall* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = this->template read_current_prop(object); - const detail::Raster_Partition partition = detail::raster_partition( + const detail::Raster_Partition core = detail::raster_partition( prepared.layout, index, graph_partition_grid); - for (int y = partition.first_y; y < partition.last_y; ++y) { - for (int x = partition.first_x; x < partition.last_x; ++x) { + if (core.empty() || index >= prepared.tiles.size()) return; + const int halo = state.interpolation_mode == Image_Interpolation_Mode::nearest + ? 0 : state.interpolation_mode == Image_Interpolation_Mode::bilinear ? 1 : 2; + auto& tile = prepared.tiles[index]; + tile.source = { + std::max(0, core.first_x - halo), + std::min(prepared.layout.width, core.last_x + halo), + std::max(0, core.first_y - halo), + std::min(prepared.layout.height, core.last_y + halo)}; + const int tile_width = tile.source.last_x - tile.source.first_x; + const int tile_height = tile.source.last_y - tile.source.first_y; + tile.pixels.assign(static_cast(tile_width) * tile_height, 0); + const Rect_F target = prepared.layout.target.normalized(); + const auto project_x = [&](int boundary) { + return target.x + target.width * boundary / prepared.layout.width; + }; + const auto project_y = [&](int boundary) { + return target.y + target.height * boundary / prepared.layout.height; + }; + const double left = project_x(tile.source.first_x); + const double top = project_y(tile.source.first_y); + tile.target = {left, top, project_x(tile.source.last_x) - left, + project_y(tile.source.last_y) - top}; + for (int y = tile.source.first_y; y < tile.source.last_y; ++y) { + for (int x = tile.source.first_x; x < tile.source.last_x; ++x) { const auto [column, slot] = detail::raster_coordinates(prepared.layout, x, y); if (slot < 0 || static_cast(slot) >= @@ -188,8 +216,8 @@ inline void Waterfall::Private::prepare_partition(Waterfall* object, Plot_Partit if (!row) continue; const std::size_t source = static_cast( prepared.source_first + column); - prepared.pixels[static_cast(y) * - prepared.layout.width + x] = + tile.pixels[static_cast(y - tile.source.first_y) * + tile_width + (x - tile.source.first_x)] = premultiply(state.color_map.sample( detail::normalized_plot_value(row->values[source], state.power_range))); @@ -200,13 +228,19 @@ inline void Waterfall::Private::paint_partition(Waterfall* object, Plot_Partition_Count index) { const auto& state = this->template read_current_prop(object); if (!prepared.valid || index >= - detail::raster_partition_count(graph_partition_grid)) + detail::raster_partition_count(graph_partition_grid) || + index >= prepared.tiles.size()) return; const Rect_F region = detail::raster_paint_region( prepared.layout, index, graph_partition_grid); if (region.empty()) return; + const auto& tile = prepared.tiles[index]; + const int tile_width = tile.source.last_x - tile.source.first_x; + const int tile_height = tile.source.last_y - tile.source.first_y; + if (tile_width <= 0 || tile_height <= 0 || tile.pixels.empty()) return; detail::Painter painter(this->paint_surface(), prepared.canvas, region); - detail::paint_raster(painter, prepared.layout, prepared.pixels, state.interpolation_mode); + painter.heatmap(tile.target, tile_width, tile_height, tile.pixels, + state.interpolation_mode); } inline void Waterfall::Private::paint_tooltip(Waterfall* object) { if (!prepared.valid || prepared.tooltip_text.empty()) return; diff --git a/render_2D/render_2D/scene/Render_Scene_2D.cpp b/render_2D/render_2D/scene/Render_Scene_2D.cpp index 6f8b623..c48f362 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.cpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.cpp @@ -1,7 +1,6 @@ #include "Render_Scene_2D.hpp" /* 二维 Paint Taskflow、批次事件路由、缓存失效与合成实现。 */ namespace aethera::render_2d { Render_Scene_2D::Private::~Private() = default; -bool Render_Scene_2D::State::operator==(const State&) const = default; bool Render_Scene_2D::Prop::operator==(const Prop&) const = default; Render_Scene_2D::Render_Result_Type Render_Scene_2D::render(Frame_2D* frame) { @@ -19,8 +18,8 @@ void Render_Scene_2D::activate_view() { void Render_Scene_2D::deactivate_view() { double_buffer::detail::Internal_Access::get(this).set_view_active(this, false); } -void Render_Scene_2D::reset_frame_statistics() { - double_buffer::detail::Internal_Access::get(this).reset_frame_statistics(this); +void Render_Scene_2D::reset_diagnostics() { + double_buffer::detail::Internal_Access::get(this).reset_diagnostics(this); } } diff --git a/render_2D/render_2D/scene/Render_Scene_2D.hpp b/render_2D/render_2D/scene/Render_Scene_2D.hpp index 8159444..a79207d 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.hpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.hpp @@ -3,7 +3,6 @@ #include "../base/Renderable_2D.hpp" #include "../render/Blend2D_Cache.hpp" #include -#include #include #include #include @@ -17,10 +16,7 @@ struct Render_Scene_2D : Def @@ -57,7 +53,7 @@ struct Render_Scene_2D : Def #include "../event/Event_Routing_Rules.hpp" #include #include @@ -65,9 +64,7 @@ struct Render_Scene_2D::Private : Prev_Private { bool frame_in_flight{}; /* Scene::advance 到像素发布完成的唯一不可重入准入状态。 */ Frame_Callback frame_callback{}; /* Plot publish 后的外接消费出口。 */ Frame_Callback frame_retired_callback{}; /* 全链路 trace 完成后的物理帧归还出口。 */ - Frame_Statistics_Accumulator frame_statistics{}; /* Scene 内部增量计算;State 只发布定长统计结果。 */ std::unique_ptr frame_taskflow{}; - std::chrono::steady_clock::time_point active_render_started{}; ~Private(); Frame_2D* active_frame{}; /* 异步帧 DAG 借用的外部帧;完成回调前保持存活。 */ Blend2D_Cache* frame_target{}; /* 当前异步帧的最终颜色层;帧 DAG 完成后清空。 */ @@ -90,7 +87,7 @@ struct Render_Scene_2D::Private : Prev_Private { template void set_frame_callback(Object* object, Frame_Callback callback); template void set_frame_retired_callback(Object* object, Frame_Callback callback); template void set_view_active(Object* object, bool active); - template void reset_frame_statistics(Object* object); + template void reset_diagnostics(Object* object); /* CRTP 覆盖:Builder 挂接最终 Private 后安装二维 Scene 的无虚函数业务分派。 */ template void bind_private_crtp(Object* object); @@ -208,6 +205,8 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { runtime->taskflow = std::make_unique("scene.render"); frame_taskflow = std::make_unique("render_2d.frame"); auto& graph = *frame_taskflow; + graph.describe("owner_kind", "scene") + .describe("owner_component", "scene"); auto begin = graph.add("scene.begin", [this, object] { auto* frame = active_frame; if (!frame) throw std::logic_error("2D frame DAG lost its active frame"); @@ -229,8 +228,6 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { }); auto& state = static_cast( double_buffer::detail::Internal_Access::pending_state(object)); - state.taskflow_execution_time_ns = 0; - active_render_started = std::chrono::steady_clock::now(); auto& target = detail::Frame_2D_Access::render_target(frame); frame_target = ⌖ frame->mark(Frame_Trace_Marker::prepare_started); @@ -310,9 +307,6 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) { auto finish = graph.add("scene.render.complete", [this, object] { auto& state = static_cast( double_buffer::detail::Internal_Access::pending_state(object)); - state.taskflow_execution_time_ns = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now() - active_render_started).count()); state.event_statistics = event_statistics.state(); active_frame->mark(Frame_Trace_Marker::paint_taskflow_finished); active_frame->mark(Frame_Trace_Marker::paint_finished); @@ -393,9 +387,6 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) { [](Renderable* renderable, Renderable::Private& data) { data.publish_renderable_state(renderable); }); - auto& scene_state = static_cast(double_buffer::detail::Internal_Access::pending_state(object)); - scene_state.frame_statistics = frame_statistics.submit( - *frame, Frame_Dimension::two_dimensional); double_buffer::detail::Internal_Access::publish_state(object); active_frame = nullptr; @@ -489,13 +480,11 @@ void Render_Scene_2D::Private::dispatch_events(Object* object, Size viewport, } } template -void Render_Scene_2D::Private::reset_frame_statistics(Object* object) { - double_buffer::detail::Internal_Access::publish_state(object, +void Render_Scene_2D::Private::reset_diagnostics(Object* object) { + double_buffer::detail::Internal_Access::publish_state(object, [this](State_Access states) { - frame_statistics.reset(); event_statistics.reset(); - auto& state = states.template get(); - state.frame_statistics = {}; + auto& state = states.template get(); state.event_statistics = {}; }); } diff --git a/render_2D/tests/Async_Render_Contract_Test.cpp b/render_2D/tests/Async_Render_Contract_Test.cpp index 0524d32..a39ae77 100644 --- a/render_2D/tests/Async_Render_Contract_Test.cpp +++ b/render_2D/tests/Async_Render_Contract_Test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,6 +9,7 @@ #include #include #include +#include #include namespace { @@ -40,6 +42,7 @@ int main() { using Spectrum_Object = Spectrum; using Constellation_Object = Constellation_Diagram; using Waterfall_Object = Waterfall; + using Afterglow_Object = Afterglow; using Scene_Object = Render_Scene_2D; initialize_runtime({.workers = 4}); @@ -49,11 +52,15 @@ int main() { auto* time = build_object