2D 绘图缓存节点

This commit is contained in:
2026-08-21 10:16:16 +08:00
parent 26a89335b4
commit 855e25981d
55 changed files with 772 additions and 488 deletions
@@ -1,7 +1,7 @@
#pragma once
#include "mechanism.hpp"
namespace double_buffer {
// Dependency_Graph 是有向无环依赖图。Node 保存对象和直接前驱Edge 额外记录触发 dirty 来源键,用于状态/Buffer/阶段脏标记传播
// Dependency_Graph 是有向无环依赖图。Node 保存对象和直接前驱Edge 可选携带 dirty 来源键,整体对象边只参与拓扑排序
struct Dependency_Graph {
using Error = Dependency_Graph_Error;
struct Node {
@@ -157,6 +157,11 @@ public:
void disconnect(Root* object) {
edit_dependency_graph->disconnect_impl(object);
}
/* 添加整个对象依赖;只形成拓扑边,不注册 dirty 传播。 */
template <detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::Dependency_Object Source>
Node* add_dependency(Target* target, Source* source) {
return edit_dependency_graph->template add_dependency_runtime<Bound_Object, Target, Source>(target, source, nullptr, nullptr, nullptr, bind_node_data);
}
/* 添加 Prop 字段级依赖;source 通过 set 修改该字段时目标阶段变脏。 */
template <auto Member, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::Prop_Dependency_Source<Member> Source>
Node* add_prop_dependency(Target* target, Source* source) {
@@ -295,6 +300,7 @@ private:
void rebuild_dirty_edges() {
dirty_edges.clear();
for (const auto& edge : edges) {
if (!edge.source_key || !edge.target_tag || !edge.target_dirty_key) continue;
Dirty_Source source{edge.source, edge.source_key};
auto [first, last] = dirty_edges.equal_range(source);
auto current = std::find_if(
-2
View File
@@ -26,8 +26,6 @@ using double_buffer::Dependency_Graph;
using double_buffer::Dependency_Graph_Error;
/* Prepare 数据阶段在 Dependency_Graph 图中的标签。 */
struct Prepare_Data_Tag {};
/* Paint 阶段在 Dependency_Graph 图中的标签。 */
struct Paint_Tag {};
/* 全局 Taskflow 运行时状态标签,用于注册状态回调。 */
struct Task_Runtime_State_Tag {};
/* 单一 Taskflow 任务类型的累计统计。 */
+1 -1
View File
@@ -71,7 +71,7 @@ struct Renderable : Def<Renderable, Root> {
/* Renderable 每次 Scene 执行后发布的阶段状态与统计。 */
struct State : Prev_State {
bool prepare_dirty{}; /* Prepare 条件判断时观察到的 Prepare_Data_Tag dirty 状态。 */
bool paint_dirty{}; /* Paint 条件判断时观察到的 Paint_Tag dirty 状态。 */
bool paint_dirty{}; /* 专用渲染阶段条件判断时观察到的 dirty 状态。 */
bool prepare_executed{}; /* 本次 Scene 执行是否运行了 Prepare 数据函数或子图。 */
bool paint_executed{}; /* 本次 Scene 执行是否运行了 Paint 数据函数或子图。 */
bool prepare_graph_rebuilt{}; /* 本次 Prepare 条件判断是否重建了 Prepare 子图。 */
+1 -1
View File
@@ -1,4 +1,4 @@
#include "scene.hpp"
#include "scene.hpp" /* 后端共有 Prepare Scene 实现。 */
namespace aethera {
Scene::Private::Private() : runtime(std::make_unique<Runtime>()) {}
Scene::Private::~Private() {
+9 -9
View File
@@ -3,21 +3,21 @@
namespace aethera {
/* Scene 状态标签,用于访问和订阅 Scene::State。 */
/*
* Scene 汇总 Prepare/Paint 两张 Dependency_Graph 图并构建 Taskflow。
* Scene 汇总跨渲染后端共有的 Prepare 数据依赖图并构建 Taskflow。
* 用户最终通过 Impl<Scene> 创建可使用实例;编辑 Dependency_Graph 后调用 advance() 提交结构变化,再调用 process(...) 执行当前场景。
*/
struct Scene : Def<Scene, Root, Dependency_Graph_Type<Prepare_Data_Tag, Renderable>, Dependency_Graph_Type<Paint_Tag, Renderable>> {
struct Scene : Def<Scene, Root, Dependency_Graph_Type<Prepare_Data_Tag, Renderable>> {
/* Scene 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */
struct Prop : Prev_Prop {};
/* Scene 每次 process(...) 后发布的总图结构与执行统计。 */
struct State : Prev_State {
bool taskflow_rebuilt{}; /* 本次 process(...) 前的 advance 是否重新构建了总 Taskflow。 */
std::size_t renderable_count{}; /* Prepare/Paint 两张依赖图中去重后的 Renderable 数量。 */
std::size_t taskflow_task_count{}; /* 当前总 Taskflow 中的任务节点数量。 */
std::size_t taskflow_dependency_count{}; /* 当前总 Taskflow 中的直接依赖边数量。 */
std::size_t taskflow_max_predecessors{}; /* 当前总 Taskflow 中单个任务的最大直接前驱数量。 */
std::size_t taskflow_max_successors{}; /* 当前总 Taskflow 中单个任务的最大直接后继数量。 */
std::uint64_t taskflow_execution_time_ns{}; /* 本次 process(...) 执行 Taskflow 的耗时,单位为纳秒;没有任务时为 0。 */
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;
};
+16 -78
View File
@@ -5,9 +5,9 @@
#include <vector>
namespace aethera {
struct Scene::Private : Prev_Private {
struct Result {}; /* process(...) 完成回调的结果类型;当前仅表示完成。 */
struct Runtime; /* Scene 的 Taskflow 构建产物;完整定义位于本文件下方。 */
std::unique_ptr<Runtime> runtime; /* Scene 唯一运行时构建产物的所有权。 */
struct Result {}; /* process(...) 完成回调的结果类型;当前仅表示完成。 */
struct Runtime; /* Scene 的 Taskflow 构建产物;完整定义位于本文件下方。 */
std::unique_ptr<Runtime> runtime; /* Scene 唯一运行时构建产物的所有权。 */
Private();
~Private();
/* Def CRTP hook:所有缓冲推进后重建必要的总 Taskflow,并更新 Scene_State_Tag 状态层。 */
@@ -23,7 +23,7 @@ struct Scene::Private : Prev_Private {
/* 派生 Scene 的 Private 还可覆盖 Def::Private 的四个 State 生命周期 hook,并通过 State_Access::get<Tag>() 访问状态层。 */
};
struct Scene::Private::Runtime {
std::unique_ptr<tf::Taskflow> taskflow; /* 当前已构建的总 Taskflow;为空表示尚未构建。 */
std::unique_ptr<tf::Taskflow> taskflow; /* 当前已构建的总 Taskflow;为空表示尚未构建。 */
};
template <Attached Object, typename Callback>
void Scene::Private::process(Object* object, Callback&& callback) requires std::invocable<Callback, const Result&> {
@@ -56,41 +56,29 @@ void Scene::Private::after_advance(Object* object,
}
);
bool taskflow_dirty = !runtime->taskflow;
object->template access_pending_dependency_graph<Prepare_Data_Tag, Paint_Tag>(
[&](auto& prepare_state, auto& paint_state) {
taskflow_dirty = taskflow_dirty || prepare_state.dirty() || paint_state.dirty();
}
);
object->template access_pending_dependency_graph<Prepare_Data_Tag>(
[&](auto& prepare_state) { taskflow_dirty = taskflow_dirty || prepare_state.dirty(); });
if (!taskflow_dirty) return;
if (!runtime->taskflow) runtime->taskflow = std::make_unique<tf::Taskflow>();
auto& taskflow = *runtime->taskflow;
auto prepare_dependencies = object->template current_dependency_graph<Prepare_Data_Tag>();
auto paint_dependencies = object->template current_dependency_graph<Paint_Tag>();
std::pmr::unordered_set<Renderable*> renderables{resource};
prepare_dependencies.for_each_bound(
[&](Renderable* renderable, Renderable::Private&) {
renderables.insert(renderable);
}
);
paint_dependencies.for_each_bound(
[&](Renderable* renderable, Renderable::Private&) {
renderables.insert(renderable);
}
);
scene_state.renderable_count = renderables.size();
detail::clear_stage_observers(&taskflow);
taskflow.clear();
struct Stage_Tasks {
tf::Task prepare_entry; /* Prepare 条件任务,作为该阶段依赖入口。 */
tf::Task prepare_exit; /* Prepare 完成任务,作为该阶段依赖出口。 */
tf::Task paint_entry; /* Paint 条件任务,作为该阶段依赖入口。 */
tf::Task paint_exit; /* Paint 完成任务,作为该阶段依赖出口。 */
tf::Task prepare_entry; /* Prepare 条件任务,作为该阶段依赖入口。 */
tf::Task prepare_exit; /* Prepare 完成任务,作为该阶段依赖出口。 */
};
std::pmr::unordered_map<Root*, Stage_Tasks> stage_tasks{resource};
for (auto* renderable : renderables) {
Root* root = renderable;
auto* data = prepare_dependencies.private_data(root);
if (!data) data = paint_dependencies.private_data(root);
if (!data) continue;
auto* dispatch = data->dispatch;
auto prepare_if = taskflow.emplace([data, dispatch, root] {
@@ -127,59 +115,16 @@ void Scene::Private::after_advance(Object* object,
}
auto prepare_done = taskflow.emplace([dispatch, root] {
auto& state = *dispatch->state.get(root);
if (!state.prepare_executed) return;
root->template take_dirty<Prepare_Data_Tag>();
root->template mark_dirty<Paint_Tag>();
if (state.prepare_executed) root->template take_dirty<Prepare_Data_Tag>();
dispatch->state.notify(root);
}).name("renderable.prepare.complete");
prepare_if.precede(prepare_run, prepare_done);
prepare_run.precede(prepare_done);
auto paint_if = taskflow.emplace([data, dispatch, root] {
auto& state = *dispatch->state.get(root);
state.paint_graph_rebuilt = false;
state.paint_execution_time_ns = 0;
if (dispatch->paint.builder) {
bool rebuild = dispatch->paint.rebuild_predicate(root);
if (!data->paint_graph_built || rebuild) {
*data->paint_graph = dispatch->paint.builder(root);
data->paint_graph_built = true;
state.paint_graph_rebuilt = true;
root->template mark_dirty<Paint_Tag>();
}
state.paint_task_count = data->paint_graph->num_tasks();
}
else {
state.paint_task_count = 1;
}
bool dirty = root->template dirty<Paint_Tag>();
state.paint_dirty = dirty;
state.paint_executed = dispatch->paint.predicate(root, dirty);
return state.paint_executed ? 0 : 1;
}).name("renderable.paint.condition");
tf::Task paint_run;
if (dispatch->paint.builder) {
if (!data->paint_graph) data->paint_graph = std::make_unique<tf::Taskflow>();
paint_run = taskflow.composed_of(*data->paint_graph).name("renderable.paint.graph");
}
else {
paint_run = taskflow.emplace([dispatch, root] {
dispatch->paint.run(root);
}).name("renderable.paint.data");
}
auto paint_done = taskflow.emplace([dispatch, root] {
auto& state = *dispatch->state.get(root);
if (state.paint_executed) root->template take_dirty<Paint_Tag>();
dispatch->state.notify(root);
}).name("renderable.paint.complete");
paint_if.precede(paint_run, paint_done);
paint_run.precede(paint_done);
prepare_done.precede(paint_if);
detail::bind_stage_observer(&taskflow, prepare_if.hash_value(), root, data, detail::Renderable_Stage::Prepare, detail::Stage_Observer_Point::Begin);
detail::bind_stage_observer(&taskflow, prepare_done.hash_value(), root, data, detail::Renderable_Stage::Prepare, detail::Stage_Observer_Point::End);
detail::bind_stage_observer(&taskflow, paint_if.hash_value(), root, data, detail::Renderable_Stage::Paint, detail::Stage_Observer_Point::Begin);
detail::bind_stage_observer(&taskflow, paint_done.hash_value(), root, data, detail::Renderable_Stage::Paint, detail::Stage_Observer_Point::End);
stage_tasks.emplace(root, Stage_Tasks{prepare_if, prepare_done, paint_if, paint_done});
stage_tasks.emplace(root, Stage_Tasks{prepare_if, prepare_done});
}
auto connect_dependencies = [&](const auto& dependency_graph, bool prepare) {
auto connect_dependencies = [&](const auto& dependency_graph) {
dependency_graph.for_each(
[&](const Dependency_Graph::Node& dependency_node) {
if (!dependency_graph.private_data(dependency_node)) return;
@@ -195,9 +140,7 @@ void Scene::Private::after_advance(Object* object,
if (dependency_graph.private_data(*dependency)) {
auto source = stage_tasks.find(dependency->object);
if (source != stage_tasks.end()) {
auto source_task = prepare ? source->second.prepare_exit : source->second.paint_exit;
auto target_task = prepare ? target->second.prepare_entry : target->second.paint_entry;
source_task.precede(target_task);
source->second.prepare_exit.precede(target->second.prepare_entry);
}
continue;
}
@@ -206,8 +149,7 @@ void Scene::Private::after_advance(Object* object,
}
);
};
connect_dependencies(prepare_dependencies, true);
connect_dependencies(paint_dependencies, false);
connect_dependencies(prepare_dependencies);
scene_state.taskflow_task_count = taskflow.num_tasks();
scene_state.taskflow_dependency_count = 0;
scene_state.taskflow_max_predecessors = 0;
@@ -219,12 +161,8 @@ void Scene::Private::after_advance(Object* object,
scene_state.taskflow_max_successors = std::max(scene_state.taskflow_max_successors, task.num_successors());
}
);
object->template access_pending_dependency_graph<Prepare_Data_Tag, Paint_Tag>(
[](auto& prepare_state, auto& paint_state) {
if (prepare_state.dirty()) prepare_state.take_dirty();
if (paint_state.dirty()) paint_state.take_dirty();
}
);
object->template access_pending_dependency_graph<Prepare_Data_Tag>(
[](auto& prepare_state) { if (prepare_state.dirty()) prepare_state.take_dirty(); });
scene_state.taskflow_rebuilt = true;
}
}
+16
View File
@@ -97,6 +97,22 @@ TEST(dependency_graph, topological_order_and_state_dirty_propagation) {
source->update_state<&Node_Object::State::value>(31);
EXPECT_TRUE(target->dirty<Graph_Tag>());
}
TEST(dependency_graph, whole_object_dependency_only_controls_topology) {
auto source = build_object<Node>();
auto target = build_object<Node>();
auto graph = build_object<Graph>();
ASSERT_TRUE(graph->edit_dependency_graph<Graph_Tag>([&](auto& editor) { editor.add_dependency(target.get(), source.get()); }).has_value());
graph->advance();
std::vector<double_buffer::Root*> order;
ASSERT_TRUE(graph->current_dependency_graph<Graph_Tag>().for_each_topological_view([&](const auto&, const auto& node) { order.push_back(node.object); }).has_value());
ASSERT_EQ(order.size(), 2u);
EXPECT_EQ(order[0], source.get());
EXPECT_EQ(order[1], target.get());
source->set<&Node_Object::Prop::value>(1);
source->update_state<&Node_Object::State::value>(1);
source->mark_dirty<Graph_Tag>();
EXPECT_FALSE(target->dirty<Graph_Tag>());
}
TEST(dependency_graph, state_dependency_granularity_is_selectable) {
auto member_source = build_object<Node>();
auto member_target = build_object<Node>();
+6 -13
View File
@@ -85,12 +85,7 @@ std::unique_ptr<Object_Type> build_object() {
}
template <typename Renderable>
void add_renderable(Scene& scene, Renderable* renderable) {
ASSERT_TRUE((scene.edit_dependency_graph<aethera::Prepare_Data_Tag, aethera::Paint_Tag>(
[&](auto& prepare, auto& paint) {
prepare.add(renderable);
paint.add(renderable);
}
).has_value()));
ASSERT_TRUE(scene.edit_dependency_graph<aethera::Prepare_Data_Tag>([&](auto& prepare) { prepare.add(renderable); }).has_value());
}
}
TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) {
@@ -102,12 +97,12 @@ TEST(renderable_capability, direct_stages_do_not_allocate_subgraphs) {
auto& data = renderable->data_for_test();
auto& base = static_cast<aethera::Renderable::Private&>(data);
EXPECT_EQ(data.prepare_calls, 1);
EXPECT_EQ(data.paint_calls, 1);
EXPECT_EQ(data.paint_calls, 0);
EXPECT_EQ(base.prepare_graph, nullptr);
EXPECT_EQ(base.paint_graph, nullptr);
scene->process([](const auto&) {});
EXPECT_EQ(data.prepare_calls, 1);
EXPECT_EQ(data.paint_calls, 1);
EXPECT_EQ(data.paint_calls, 0);
}
TEST(renderable_capability, graph_stage_builds_lazily_and_rebuilds_inside_condition) {
aethera::initialize_runtime(2);
@@ -141,7 +136,7 @@ TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundarie
renderable->set_state_callback<aethera::Renderable::Base_Tag>([&](const auto& state) {
++renderable_updates;
EXPECT_TRUE(state.prepare_executed);
EXPECT_TRUE(state.paint_executed);
EXPECT_FALSE(state.paint_executed);
});
scene->set_state_callback<aethera::Scene::Base_Tag>([&](const auto& state) {
++scene_updates;
@@ -190,13 +185,11 @@ TEST(scene_condition, upstream_change_makes_downstream_run_in_same_taskflow) {
auto source = build_object<Dependency>();
auto target = build_object<Dependency>();
auto scene = build_object<Scene>();
ASSERT_TRUE((scene->edit_dependency_graph<aethera::Prepare_Data_Tag, aethera::Paint_Tag>(
[&](auto& prepare, auto& paint) {
ASSERT_TRUE((scene->edit_dependency_graph<aethera::Prepare_Data_Tag>(
[&](auto& prepare) {
prepare.add(source.get());
prepare.add(target.get());
prepare.template add_dependency<&Dependency_Renderable::State::revision>(target.get(), source.get());
paint.add(source.get());
paint.add(target.get());
}
).has_value()));
scene->process([](const auto&) {});