321 lines
18 KiB
C++
321 lines
18 KiB
C++
#pragma once
|
|
#include "../event/Event_Routing_Rules.hpp"
|
|
#include <stdexcept>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
namespace aethera::render_2d {
|
|
template <typename Object>
|
|
Render_Scene_2D::Builder<Object>::Builder() : Base() {}
|
|
template <typename Object>
|
|
template <Attached Renderable_Object>
|
|
requires std::derived_from<Renderable_Object, Renderable_2D_Base>
|
|
Render_Scene_2D::Builder<Object>& Render_Scene_2D::Builder<Object>::add_renderable(Renderable_Object* renderable) {
|
|
attachments.emplace_back([renderable](Object* scene) {
|
|
auto& data = Base::private_access(renderable).template get<Renderable_2D_Base::Base_Tag>();
|
|
if (!data.scene_attach)
|
|
throw std::logic_error("2D renderable has no scene attachment contract");
|
|
auto attach = std::move(data.scene_attach);
|
|
return attach(scene);
|
|
});
|
|
return *this;
|
|
}
|
|
template <typename Object>
|
|
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_2D::Builder<Object>::build() {
|
|
auto result = Base::build();
|
|
if (!result) return std::unexpected(result.error());
|
|
auto scene = std::move(result).value();
|
|
for (auto& attach : attachments) {
|
|
auto attach_result = attach(scene.get());
|
|
if (!attach_result) return std::unexpected(attach_result.error());
|
|
}
|
|
return scene;
|
|
}
|
|
struct Render_Scene_2D::Private : Prev_Private {
|
|
using Render_Run = void (*)(Root*);
|
|
using Callback_Run = void (*)(Root*, Frame_Callback);
|
|
using Event_Run = void (*)(Root*, const Event&);
|
|
using Active_Run = void (*)(Root*, bool);
|
|
using Frame_View_Run = Image_View (*)(const Root*);
|
|
struct Paint_Node {
|
|
Root* object{}; /* Paint 拓扑位置对应的最终对象;Scene 不拥有。 */
|
|
Renderable_2D_Base::Private* private_data{}; /* 对象的二维能力层;对象存活期间有效。 */
|
|
Root* cache_owner{}; /* 所属缓存根;空值表示直接绘制最终帧。 */
|
|
bool cache_group_last{}; /* 是否为所属缓存组在拓扑序中的最后节点。 */
|
|
bool paint_requested{}; /* Scene 按本轮组级失效结果计算的执行许可。 */
|
|
};
|
|
struct Cache_Group {
|
|
Root* owner{}; /* 显式选择缓存的二维 Renderable 根。 */
|
|
std::vector<Root*> members{}; /* 本缓存覆盖的根及无冲突后继节点。 */
|
|
bool rebuild{}; /* 本轮是否因任一成员失效而整体重绘。 */
|
|
};
|
|
struct Dispatch {
|
|
Render_Run render; /* 执行最终 Scene 并合成颜色层。 */
|
|
Callback_Run set_frame_callback; /* 安装最终完成帧回调。 */
|
|
Event_Run dispatch_event; /* 向最终 Scene 当前 Paint 图派发事件。 */
|
|
Active_Run set_active; /* 修改最终 Scene 的视图活动状态。 */
|
|
Frame_View_Run frame_view; /* 访问最终 Scene 已合成的写侧帧。 */
|
|
};
|
|
const Dispatch* dispatch{}; /* Builder 绑定最终 Scene 类型后的静态分派表。 */
|
|
Frame_Callback frame_callback{}; /* 合成完成后的唯一像素发布出口。 */
|
|
std::unique_ptr<tf::Taskflow> paint_taskflow{}; /* 仅由二维 Paint 图构建的执行图。 */
|
|
bool rendering{}; /* 是否正在同步合成当前帧。 */
|
|
bool render_pending{}; /* 合成期间是否又收到 render();多个调用合并。 */
|
|
/* Impl CRTP 实现:在对象锁内执行 Kernel Scene,再按 Paint 图拓扑顺序合成颜色层。 */
|
|
std::vector<Paint_Node> paint_order{}; /* Paint 图当前拓扑序及 Scene 缓存分组结果。 */
|
|
std::vector<Cache_Group> cache_groups{}; /* 仅保存显式缓存根对应的执行分组。 */
|
|
template <Attached Object, typename Callback>
|
|
void process(Object* object, Callback&& callback) requires std::invocable<Callback>;
|
|
/* Def CRTP hook:二维 Paint 图结构变化后重建其独立 Taskflow。 */
|
|
template <Attached Object>
|
|
void after_advance(Object* object, Prop* pending_prop, State_Access<State> pending_states, const Prop* current_prop, State_Access<State> current_states);
|
|
/* 每帧在 Prepare 完成后计算组级 dirty,并为所有二维节点指定唯一绘制目标。 */
|
|
template <Attached Object> void prepare_paint_targets(Object* object, Blend2D_Cache& frame, Size viewport);
|
|
template <Attached Object> void render(Object* object);
|
|
/* CRTP 业务实现:按 Paint 图拓扑逆序派发事件。 */
|
|
template <Attached Object>
|
|
void dispatch_event(Object* object, const Event& event);
|
|
template <Attached Object>
|
|
[[nodiscard]] static const Dispatch& dispatch_for();
|
|
/* CRTP 覆盖:Builder 挂接最终 Private 后安装二维 Scene 的无虚函数业务分派。 */
|
|
template <Attached Object>
|
|
void bind_private_crtp(Object* object);
|
|
};
|
|
template <Attached Object>
|
|
void Render_Scene_2D::Private::after_advance(Object* object, Prop*, State_Access<State>, const Prop*, State_Access<State>) {
|
|
bool rebuild = !paint_taskflow;
|
|
object->template access_pending_dependency_graph<Paint_Tag>([&](auto& paint_state) { rebuild = rebuild || paint_state.dirty(); });
|
|
if (!rebuild) return;
|
|
if (!paint_taskflow) paint_taskflow = std::make_unique<tf::Taskflow>();
|
|
auto& taskflow = *paint_taskflow;
|
|
aethera::detail::clear_stage_observers(&taskflow);
|
|
taskflow.clear();
|
|
const auto dependencies = object->template current_dependency_graph<Paint_Tag>();
|
|
paint_order.clear();
|
|
cache_groups.clear();
|
|
std::unordered_map<Root*, Root*> cache_owner_by_object;
|
|
std::unordered_map<Root*, std::size_t> cache_group_by_owner;
|
|
const auto order_result = dependencies.for_each_topological_view([&](const auto& view, const Dependency_Graph::Node& node) {
|
|
auto* data = view.private_data(node);
|
|
if (!data) return;
|
|
Root* cache_owner{};
|
|
if (data->pending_cache) cache_owner = node.object;
|
|
else {
|
|
bool conflict{};
|
|
for (const auto* dependency : node.dependencies) {
|
|
if (!view.private_data(*dependency)) continue;
|
|
auto current = cache_owner_by_object.find(dependency->object);
|
|
const Root* dependency_owner = current == cache_owner_by_object.end() ? nullptr : current->second;
|
|
if (!dependency_owner || (cache_owner && cache_owner != dependency_owner)) { conflict = true; break; }
|
|
cache_owner = const_cast<Root*>(dependency_owner);
|
|
}
|
|
if (conflict) cache_owner = nullptr;
|
|
}
|
|
cache_owner_by_object.emplace(node.object, cache_owner);
|
|
paint_order.push_back(Paint_Node{node.object, data, cache_owner, false, false});
|
|
if (!cache_owner) return;
|
|
auto [group, inserted] = cache_group_by_owner.emplace(cache_owner, cache_groups.size());
|
|
if (inserted) cache_groups.push_back(Cache_Group{cache_owner});
|
|
cache_groups[group->second].members.push_back(node.object);
|
|
});
|
|
if (!order_result) throw std::logic_error("render scene paint graph became invalid while building cache groups");
|
|
std::unordered_set<Root*> closed_groups;
|
|
for (auto current = paint_order.rbegin(); current != paint_order.rend(); ++current)
|
|
if (current->cache_owner && closed_groups.insert(current->cache_owner).second) current->cache_group_last = true;
|
|
tf::Task previous;
|
|
bool has_previous{};
|
|
for (std::size_t index = 0; index < paint_order.size(); ++index) {
|
|
auto& paint_node = paint_order[index];
|
|
Root* root = paint_node.object;
|
|
auto& data = *paint_node.private_data;
|
|
auto* dispatch = data.dispatch;
|
|
auto paint_if = taskflow.emplace([this, &data, dispatch, root, index] {
|
|
auto& state = *dispatch->state.get(root);
|
|
state.paint_graph_rebuilt = false;
|
|
state.paint_execution_time_ns = 0;
|
|
if (dispatch->paint.builder) {
|
|
const bool rebuild_graph = dispatch->paint.rebuild_predicate(root);
|
|
if (!data.paint_graph_built || rebuild_graph) {
|
|
*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 = dispatch->paint.run ? 1 : 0;
|
|
const bool dirty = root->template dirty<Paint_Tag>();
|
|
state.paint_dirty = dirty;
|
|
state.paint_executed = paint_order[index].paint_requested && dispatch->paint.predicate(root, dirty);
|
|
return state.paint_executed ? 0 : 1;
|
|
}).name("render_2d.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("render_2d.paint.graph");
|
|
} else paint_run = taskflow.emplace([dispatch, root] { if (dispatch->paint.run) dispatch->paint.run(root); }).name("render_2d.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("render_2d.paint.complete");
|
|
paint_if.precede(paint_run, paint_done);
|
|
paint_run.precede(paint_done);
|
|
if (has_previous) previous.precede(paint_if);
|
|
aethera::detail::bind_stage_observer(&taskflow, paint_if.hash_value(), root, &data, aethera::detail::Renderable_Stage::Paint, aethera::detail::Stage_Observer_Point::Begin);
|
|
aethera::detail::bind_stage_observer(&taskflow, paint_done.hash_value(), root, &data, aethera::detail::Renderable_Stage::Paint, aethera::detail::Stage_Observer_Point::End);
|
|
previous = paint_done;
|
|
if (paint_node.cache_group_last) {
|
|
Root* cache_owner = paint_node.cache_owner;
|
|
auto composite = taskflow.emplace([this, object, cache_owner] {
|
|
const auto group = std::find_if(cache_groups.begin(), cache_groups.end(), [cache_owner](const Cache_Group& value) { return value.owner == cache_owner; });
|
|
const auto owner_node = std::find_if(paint_order.begin(), paint_order.end(), [cache_owner](const Paint_Node& value) { return value.object == cache_owner; });
|
|
if (group == cache_groups.end() || owner_node == paint_order.end()) return;
|
|
if (group->rebuild) owner_node->private_data->valid_cache = owner_node->private_data->paint_target;
|
|
if (owner_node->private_data->valid_cache)
|
|
object->template pending_buffer<Scene_Color_Cache_Tag>().composite(*owner_node->private_data->valid_cache);
|
|
}).name("render_2d.paint.cache.composite");
|
|
paint_done.precede(composite);
|
|
previous = composite;
|
|
}
|
|
has_previous = true;
|
|
}
|
|
object->template access_pending_dependency_graph<Paint_Tag>([](auto& paint_state) { if (paint_state.dirty()) paint_state.take_dirty(); });
|
|
}
|
|
template <Attached Object>
|
|
void Render_Scene_2D::Private::prepare_paint_targets(Object*, Blend2D_Cache& frame, Size viewport) {
|
|
for (auto& group : cache_groups) {
|
|
const auto owner_node = std::find_if(paint_order.begin(), paint_order.end(), [&](const Paint_Node& value) { return value.object == group.owner; });
|
|
if (owner_node == paint_order.end() || !owner_node->private_data->pending_cache)
|
|
throw std::logic_error("2D cache group lost its cache owner capability");
|
|
group.rebuild = owner_node->private_data->valid_cache == nullptr;
|
|
for (Root* member : group.members) {
|
|
const auto node = std::find_if(paint_order.begin(), paint_order.end(), [member](const Paint_Node& value) { return value.object == member; });
|
|
if (node == paint_order.end()) continue;
|
|
group.rebuild = group.rebuild || member->template dirty<Paint_Tag>();
|
|
if (node->private_data->dispatch->paint.builder) {
|
|
group.rebuild = group.rebuild || !node->private_data->paint_graph_built || node->private_data->dispatch->paint.rebuild_predicate(member);
|
|
}
|
|
}
|
|
if (group.rebuild) {
|
|
auto* target = owner_node->private_data->pending_cache(group.owner);
|
|
target->ensure_size(viewport);
|
|
target->clear();
|
|
for (Root* member : group.members) {
|
|
member->template mark_dirty<Paint_Tag>();
|
|
const auto node = std::find_if(paint_order.begin(), paint_order.end(), [member](const Paint_Node& value) { return value.object == member; });
|
|
if (node != paint_order.end()) { node->private_data->paint_target = target; node->paint_requested = true; }
|
|
}
|
|
} else {
|
|
for (Root* member : group.members) {
|
|
const auto node = std::find_if(paint_order.begin(), paint_order.end(), [member](const Paint_Node& value) { return value.object == member; });
|
|
if (node != paint_order.end()) {
|
|
node->private_data->paint_target = owner_node->private_data->valid_cache;
|
|
node->paint_requested = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (auto& node : paint_order) {
|
|
if (node.cache_owner) continue;
|
|
node.private_data->paint_target = &frame;
|
|
node.paint_requested = true;
|
|
node.object->template mark_dirty<Paint_Tag>();
|
|
}
|
|
}
|
|
template <Attached Object, typename Callback>
|
|
void Render_Scene_2D::Private::process(Object* object, Callback&& callback)
|
|
requires std::invocable<Callback> {
|
|
const auto& state = static_cast<const Prop&>(*static_cast<typename Object::Private&>(*this).current);
|
|
if (!state.view_active) {
|
|
return;
|
|
}
|
|
if (state.viewport.empty()) {
|
|
return;
|
|
}
|
|
object->template current_dependency_graph<Paint_Cache_Tag>().for_each([](const Dependency_Graph::Node& node) {
|
|
if (!node.object->template take_dirty<Paint_Cache_Tag>()) return;
|
|
node.object->template mark_dirty<Prepare_Data_Tag>();
|
|
node.object->template mark_dirty<Paint_Tag>();
|
|
});
|
|
std::vector<Renderable*> prepare_executions;
|
|
object->template current_dependency_graph<Prepare_Data_Tag>().for_each_bound([&](Renderable* renderable, Renderable::Private& data) {
|
|
bool execute = renderable->template dirty<Prepare_Data_Tag>();
|
|
if (data.dispatch->prepare.builder)
|
|
execute = execute || !data.prepare_graph_built || data.dispatch->prepare.rebuild_predicate(renderable);
|
|
if (execute) prepare_executions.push_back(renderable);
|
|
});
|
|
Prev_Private::process(object, [&](const Scene::Private::Result&) {
|
|
for (auto* renderable : prepare_executions) renderable->template mark_dirty<Paint_Tag>();
|
|
auto& frame = object->template pending_buffer<Scene_Color_Cache_Tag>();
|
|
frame.ensure_size(state.viewport);
|
|
frame.clear();
|
|
{
|
|
detail::Painter painter(frame, state.viewport);
|
|
painter.rect({0.0, 0.0, static_cast<double>(state.viewport.width),
|
|
static_cast<double>(state.viewport.height)},
|
|
Pen{.style = Line_Style::none}, Brush{state.background, Brush_Style::solid});
|
|
}
|
|
prepare_paint_targets(object, frame, state.viewport);
|
|
if (paint_taskflow && !paint_taskflow->empty()) aethera::detail::run_taskflow(*paint_taskflow);
|
|
});
|
|
std::invoke(std::forward<Callback>(callback));
|
|
}
|
|
template <Attached Object>
|
|
void Render_Scene_2D::Private::render(Object* object) {
|
|
if (rendering) { render_pending = true; return; }
|
|
rendering = true;
|
|
do {
|
|
render_pending = false;
|
|
object->process([&] { if (frame_callback) frame_callback(object->template pending_buffer<Scene_Color_Cache_Tag>().view()); });
|
|
} while (render_pending);
|
|
rendering = false;
|
|
}
|
|
template <Attached Object>
|
|
void Render_Scene_2D::Private::dispatch_event(Object* object, const Event& event) {
|
|
std::vector<Renderable*> order;
|
|
const auto graph = object->template current_dependency_graph<Paint_Tag>();
|
|
const auto result = graph.for_each_topological_view([&](const auto& view, const Dependency_Graph::Node& node) {
|
|
if (auto* renderable = view.object(node)) order.push_back(renderable);
|
|
});
|
|
if (!result) throw std::logic_error("render scene paint graph became invalid during event dispatch");
|
|
std::vector<Renderable*> delivery_order(order.rbegin(), order.rend());
|
|
detail::Nearest_Event_Target_Rule::apply(delivery_order,
|
|
[&](const Renderable* target) { return target->event_routing_distance(event); });
|
|
for (auto* target : delivery_order) {
|
|
if (event.is_accepted()) break;
|
|
target->dispatch_event(event);
|
|
}
|
|
}
|
|
template <Attached Object>
|
|
const Render_Scene_2D::Private::Dispatch& Render_Scene_2D::Private::dispatch_for() {
|
|
static const Dispatch value{
|
|
[](Root* root) {
|
|
auto* object = static_cast<Object*>(root);
|
|
static_cast<typename Object::Private&>(*object->d).render(object);
|
|
},
|
|
[](Root* root, Frame_Callback callback) {
|
|
auto* object = static_cast<Object*>(root);
|
|
static_cast<typename Object::Private&>(*object->d).frame_callback = std::move(callback);
|
|
},
|
|
[](Root* root, const Event& event) {
|
|
auto* object = static_cast<Object*>(root);
|
|
static_cast<typename Object::Private&>(*object->d).dispatch_event(object, event);
|
|
},
|
|
[](Root* root, bool active) {
|
|
static_cast<Object*>(root)->template set<&Prop::view_active>(active);
|
|
},
|
|
[](const Root* root) {
|
|
const auto* object = static_cast<const Object*>(root);
|
|
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
|
return private_data.buffer_storage.template get<Scene_Color_Cache_Tag>().pending->view();
|
|
}
|
|
};
|
|
return value;
|
|
}
|
|
template <Attached Object>
|
|
void Render_Scene_2D::Private::bind_private_crtp(Object* object) {
|
|
Prev_Private::bind_private_crtp(object);
|
|
dispatch = &Private::dispatch_for<Object>();
|
|
}
|
|
}
|