事件处理走双缓冲了

This commit is contained in:
2026-08-24 00:55:34 +08:00
parent 933ff2ec08
commit db251b5abf
21 changed files with 497 additions and 211 deletions
@@ -72,6 +72,7 @@ struct Async_Render_Backend::Implementation
Shared_Prepared_Visual_Batch visuals{}; /* Scene CPU Prepare 发布的大批量不可变快照。 */
Scene_3D_Parameters parameters{}; /* 本帧 Scene、Camera 与轴参数快照。 */
std::vector<Completion> completions{}; /* 合并到同一实际画面的逻辑帧。 */
Scene::Event_Batch events{}; /* Prepare 边界已交换的完整事件批次。 */
};
struct Pending {
Datoviz_Visual_Backend::Pending_Frame backend_frame{}; /* 三缓冲目标对应的已录制提交。 */
@@ -84,11 +85,7 @@ struct Async_Render_Backend::Implementation
std::optional<Gpu_Completion_Service::Result> result{}; /* fence 正常交付时的结果。 */
std::exception_ptr failure{}; /* 提交域或完成服务的 Unknown Failure。 */
};
struct Event_Command {
std::shared_ptr<const Event> event{}; /* Scene 转移给准备域的输入事件。 */
Extent viewport{}; /* 事件产生时的物理像素视口。 */
};
using Command = std::variant<Submission, Gpu_Completion, Event_Command>;
using Command = std::variant<Submission, Gpu_Completion>;
static constexpr std::size_t command_capacity = 64;
std::shared_ptr<Render_Domain> render_domain; /* 同 GPU 唯一的轻量 Queue Submit 域。 */
@@ -124,7 +121,7 @@ struct Async_Render_Backend::Implementation
void initialize(const Scene_3D_Parameters& parameters);
[[nodiscard]] Async_Render_Backend::Submit_Result render(
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
Frame_3D* frame);
Frame_3D* frame, Scene::Event_Batch events);
void accept(Submission submission);
void merge_deferred(Submission submission);
void prepare(Submission submission);
@@ -133,7 +130,7 @@ struct Async_Render_Backend::Implementation
void resolve(std::shared_ptr<Pending> pending,
std::optional<Datoviz_Visual_Backend::Completed_Frame> completed);
void complete(std::vector<Completion> completions);
void dispatch(Event_Command command);
void dispatch(const std::shared_ptr<Event>& event, Extent viewport);
};
Async_Render_Backend::Async_Render_Backend(
@@ -198,7 +195,7 @@ void Async_Render_Backend::Implementation::initialize(
Async_Render_Backend::Submit_Result
Async_Render_Backend::Implementation::render(
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
Frame_3D* frame) {
Frame_3D* frame, Scene::Event_Batch events) {
if (!frame)
throw std::invalid_argument("3D backend requires a non-null external frame");
if (!visuals || visuals->empty())
@@ -215,7 +212,8 @@ Async_Render_Backend::Implementation::render(
frame->mark(Frame_Trace_Marker::backend_queue_entered);
try {
enqueue(Submission{std::move(visuals), std::move(parameters),
{Completion{frame, std::move(callback)}}});
{Completion{frame, std::move(callback)}},
std::move(events)});
}
catch (...) {
fail(std::current_exception());
@@ -226,6 +224,10 @@ Async_Render_Backend::Implementation::render(
void Async_Render_Backend::Implementation::merge_deferred(
Submission submission) {
if (deferred_submission) {
submission.events.insert(
submission.events.begin(),
std::make_move_iterator(deferred_submission->events.begin()),
std::make_move_iterator(deferred_submission->events.end()));
submission.completions.insert(
submission.completions.begin(),
std::make_move_iterator(deferred_submission->completions.begin()),
@@ -253,6 +255,9 @@ void Async_Render_Backend::Implementation::prepare(Submission submission) {
std::optional<Datoviz_Visual_Backend::Pending_Frame> prepared;
try {
initialize(submission.parameters);
for (const auto& event : submission.events)
dispatch(event, submission.parameters.viewport);
submission.events.clear();
for (const auto& completion : submission.completions)
completion.output->mark(Frame_Trace_Marker::backend_prepare_started);
const auto sequence =
@@ -446,14 +451,21 @@ void Async_Render_Backend::Implementation::finish(Gpu_Completion completion) {
resolve(std::move(pending), std::nullopt);
}
}
void Async_Render_Backend::Implementation::dispatch(Event_Command command) {
if (!backend || !command.event) return;
void Async_Render_Backend::Implementation::dispatch(
const std::shared_ptr<Event>& event, Extent viewport) {
if (!event) return;
const auto* pointer =
dynamic_cast<const Pointer_Event_Capability*>(command.event.get());
dynamic_cast<const Pointer_Event_Capability*>(event.get());
const auto* wheel =
dynamic_cast<const Wheel_Event_Capability*>(command.event.get());
const auto* key = dynamic_cast<const Key_Event*>(command.event.get());
if (wheel) {
dynamic_cast<const Wheel_Event_Capability*>(event.get());
const auto* key = dynamic_cast<const Key_Event*>(event.get());
const bool pointer_valid = pointer &&
std::isfinite(pointer->position_x()) &&
std::isfinite(pointer->position_y()) &&
pointer->position_x() >= 0.0 && pointer->position_y() >= 0.0 &&
pointer->position_x() <= viewport.width &&
pointer->position_y() <= viewport.height;
if (wheel && pointer_valid) {
backend->dispatch_wheel(
static_cast<float>(pointer->position_x()),
static_cast<float>(pointer->position_y()),
@@ -461,20 +473,24 @@ void Async_Render_Backend::Implementation::dispatch(Event_Command command) {
wheel->angle_delta_x_value()),
wheel_step(wheel->pixel_delta_y_value(),
wheel->angle_delta_y_value()),
pointer->keyboard_modifiers(), command.viewport);
pointer->keyboard_modifiers(), viewport);
}
else if (pointer) {
else if (pointer_valid &&
(event->type == Event_Type::pointer_move ||
event->type == Event_Type::pointer_press ||
event->type == Event_Type::pointer_release)) {
backend->dispatch_pointer(
command.event->type,
event->type,
static_cast<float>(pointer->position_x()),
static_cast<float>(pointer->position_y()),
command.event->type == Event_Type::pointer_move
event->type == Event_Type::pointer_move
? held_button(pointer->pointer_buttons())
: pointer->pointer_button(),
pointer->keyboard_modifiers(), command.viewport);
pointer->keyboard_modifiers(), viewport);
}
else if (key) backend->dispatch_key(*key);
command.event->accept();
if ((wheel && pointer_valid) || pointer_valid || key) event->accept();
event->mark_dispatch_completed();
}
void Async_Render_Backend::Implementation::run() noexcept {
for (;;) {
@@ -483,12 +499,11 @@ void Async_Render_Backend::Implementation::run() noexcept {
command, std::chrono::milliseconds(10));
if (received) {
try {
if (auto* submission = std::get_if<Submission>(&command))
if (auto* submission = std::get_if<Submission>(&command)) {
accept(std::move(*submission));
}
else if (auto* completion = std::get_if<Gpu_Completion>(&command))
finish(std::move(*completion));
else if (auto* event = std::get_if<Event_Command>(&command))
dispatch(std::move(*event));
}
catch (...) {
fail(std::current_exception());
@@ -527,8 +542,9 @@ void Async_Render_Backend::Implementation::run() noexcept {
Async_Render_Backend::Submit_Result Async_Render_Backend::render(
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
Frame_3D* frame) {
return implementation_->render(std::move(visuals), parameters, frame);
Frame_3D* frame, Scene::Event_Batch events) {
return implementation_->render(std::move(visuals), parameters, frame,
std::move(events));
}
void Async_Render_Backend::set_frame_callback(Frame_Callback callback) {
std::lock_guard lock(implementation_->callback_mutex);
@@ -537,39 +553,4 @@ void Async_Render_Backend::set_frame_callback(Frame_Callback callback) {
bool Async_Render_Backend::available() const noexcept {
return implementation_->available.load(std::memory_order_acquire);
}
Async_Render_Backend::Dispatch_Event_Result
Async_Render_Backend::dispatch_event(std::shared_ptr<const Event> event,
Extent viewport) {
auto self = implementation_;
const auto* pointer = dynamic_cast<const Pointer_Event_Capability*>(event.get());
const auto* wheel = dynamic_cast<const Wheel_Event_Capability*>(event.get());
const auto* key = dynamic_cast<const Key_Event*>(event.get());
if (wheel && !pointer) return Dispatch_Event_Result::invalid_event;
if (pointer) {
if (!std::isfinite(pointer->position_x()) ||
!std::isfinite(pointer->position_y()))
return Dispatch_Event_Result::invalid_event;
if (pointer->position_x() < 0.0 || pointer->position_y() < 0.0 ||
pointer->position_x() > viewport.width ||
pointer->position_y() > viewport.height)
return Dispatch_Event_Result::ignored;
if (!wheel && event->type != Event_Type::pointer_move &&
event->type != Event_Type::pointer_press &&
event->type != Event_Type::pointer_release)
return Dispatch_Event_Result::invalid_event;
}
else if (!key) return Dispatch_Event_Result::ignored;
std::lock_guard admission_lock(self->admission_mutex);
if (!self->available.load(std::memory_order_acquire) ||
self->stop_requested.load(std::memory_order_acquire))
return Dispatch_Event_Result::backend_unavailable;
try {
self->enqueue(Implementation::Event_Command{std::move(event), viewport});
}
catch (...) {
self->fail(std::current_exception());
return Dispatch_Event_Result::backend_unavailable;
}
return Dispatch_Event_Result::queued;
}
}
@@ -1,6 +1,7 @@
#pragma once
#include "../base/Frame_3D.hpp"
#include "Backend_Types.hpp"
#include <scene.hpp>
#include <functional>
#include <memory>
namespace aethera::render_3d::detail {
@@ -19,19 +20,10 @@ public:
/* 将 Visual 快照所有权无等待转移给所属 GPU Render Domain。 */
[[nodiscard]] Submit_Result render(
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
Frame_3D* frame);
Frame_3D* frame, Scene::Event_Batch events);
/* 设置后续提交捕获的逻辑帧完成出口。 */
void set_frame_callback(Frame_Callback callback);
[[nodiscard]] bool available() const noexcept;
enum class Dispatch_Event_Result : std::uint8_t {
queued,
ignored,
invalid_event,
backend_unavailable
};
/* 将事件所有权无等待转移到 Render Domain;停止时由 Scene 在下一帧重试。 */
[[nodiscard]] Dispatch_Event_Result dispatch_event(
std::shared_ptr<const Event> event, Extent viewport);
private:
struct Implementation;
std::shared_ptr<Implementation> implementation_; /* 已入队闭包共享实现生命周期。 */
+10 -4
View File
@@ -64,7 +64,8 @@ Render_Domain::Post_Result Render_Domain::post(
active_posters_.fetch_sub(1, std::memory_order_release);
return Post_Result::stopping;
}
if (!tasks_.enqueue(std::move(task))) {
const bool enqueued = tasks_.enqueue(std::move(task));
if (!enqueued) {
active_posters_.fetch_sub(1, std::memory_order_release);
throw std::bad_alloc{};
}
@@ -75,8 +76,14 @@ void Render_Domain::run() noexcept {
current_domain_ = this;
for (;;) {
std::unique_ptr<Task> task;
const bool received = tasks_.wait_dequeue_timed(
bool received = tasks_.wait_dequeue_timed(
task, std::chrono::milliseconds(1));
if (!received && stopping_.load(std::memory_order_acquire) &&
active_posters_.load(std::memory_order_acquire) == 0) {
/* stop 边界关闭生产者后再真实探测队列,不能用
* size_approx 决定生命周期。 */
received = tasks_.try_dequeue(task);
}
if (received && task) {
try {
task->function();
@@ -95,8 +102,7 @@ void Render_Domain::run() noexcept {
// 已经消费完所有先于停止边界进入的任务。size_approx() 只能诊断,
// 不能参与线程退出正确性。
if (!received && stopping_.load(std::memory_order_acquire) &&
active_posters_.load(std::memory_order_acquire) == 0 &&
!task) {
active_posters_.load(std::memory_order_acquire) == 0) {
current_domain_ = nullptr;
if (destroy_on_exit_.load(std::memory_order_acquire)) {
if (thread_.joinable()) thread_.detach();
@@ -4,7 +4,6 @@ bool Render_Scene_3D::Prop::operator==(const Prop&) const = default;
bool Render_Scene_3D::State::operator==(const State&) const = default;
Render_Scene_3D::Render_Result Render_Scene_3D::render(Frame_3D* frame) { return static_cast<Private&>(*d).dispatch->render(this, frame); }
void Render_Scene_3D::set_frame_callback(Frame_Callback callback) { static_cast<Private&>(*d).dispatch->set_frame_callback(this, std::move(callback)); }
void Render_Scene_3D::dispatch_event(std::unique_ptr<Event> event) { static_cast<Private&>(*d).dispatch->push_event(this, std::move(event)); }
void Render_Scene_3D::activate_view() { static_cast<Private&>(*d).dispatch->set_active(this, true); }
void Render_Scene_3D::deactivate_view() { static_cast<Private&>(*d).dispatch->set_active(this, false); }
}
@@ -62,8 +62,6 @@ struct Render_Scene_3D : Def<Render_Scene_3D, Scene, Dependency_Graph_Type<Submi
[[nodiscard]] Render_Result render(Frame_3D* frame);
/* 安装完成帧回调;回调由 Datoviz Render Domain 线程传回同一外部帧。 */
void set_frame_callback(Frame_Callback callback);
/* 无锁提交事件所有权;下一次 Prepare 开始时无等待转交 Datoviz 渲染域。 */
void dispatch_event(std::unique_ptr<Event> event);
void activate_view();
void deactivate_view();
};
+5 -14
View File
@@ -17,12 +17,10 @@ struct Scene_Paint_Context {
struct Render_Scene_3D::Private : Prev_Private {
using Render_Run = Render_Result (*)(Root*, Frame_3D*);
using Callback_Run = void (*)(Root*, Frame_Callback);
using Event_Run = void (*)(Root*, std::unique_ptr<Event>);
using Active_Run = void (*)(Root*, bool);
struct Dispatch {
Render_Run render; /* 执行 CPU 图并异步提交 Paint。 */
Callback_Run set_frame_callback; /* 安装最终完成帧回调。 */
Event_Run push_event; /* 向 Kernel Scene 无锁待处理批次转移事件所有权。 */
Active_Run set_active; /* 修改最终 Scene 的活动属性。 */
};
std::shared_ptr<detail::Async_Render_Backend> backend{}; /* Scene 拥有的异步后端;已入队命令自行延长实现寿命。 */
@@ -44,8 +42,6 @@ struct Render_Scene_3D::Private : Prev_Private {
template <Attached Object> void bind_private_crtp(Object* object);
/* CRTP 覆盖:先执行 Kernel Prepare Taskflow,再运行只负责异步入队的三维 Submit 阶段。 */
template <Attached Object, typename Callback> void process(Object* object, Callback&& callback) requires std::invocable<Callback>;
/* Prepare 开始时交换公共 Scene 事件批次,并无等待提交到 Datoviz 渲染域。 */
void dispatch_events(Extent viewport);
template <Attached Object> [[nodiscard]] Render_Result render(Object* object, Frame_3D* frame);
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
};
@@ -164,12 +160,6 @@ void Render_Scene_3D::Private::initialize_backend(
backend, object, nullptr, initial,
std::make_shared<detail::Prepared_Visual_Batch>()});
}
inline void Render_Scene_3D::Private::dispatch_events(Extent viewport) {
this->consume_events([&](const std::shared_ptr<Event>& event) {
return backend->dispatch_event(event, viewport) !=
detail::Async_Render_Backend::Dispatch_Event_Result::backend_unavailable;
});
}
template <Attached Object, typename Callback>
void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requires std::invocable<Callback> {
Frame_3D* frame = active_frame;
@@ -178,7 +168,7 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
if (!prop.view_active || prop.viewport.empty() || !backend || !backend->available()) return;
frame->mark(Frame_Trace_Marker::scene_render_started);
frame->mark(Frame_Trace_Marker::event_dispatch_started);
dispatch_events(prop.viewport);
auto events = this->take_events(frame->identity().sequence);
frame->mark(Frame_Trace_Marker::event_dispatch_finished);
auto context = std::static_pointer_cast<detail::Scene_Paint_Context<Object>>(paint_context);
context->parameters = parameters(object);
@@ -202,7 +192,7 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
frame->mark(Frame_Trace_Marker::prepare_finished);
frame->mark(Frame_Trace_Marker::paint_started);
const auto result = context->backend->render(
context->visuals, context->parameters, frame);
context->visuals, context->parameters, frame, std::move(events));
frame->mark(Frame_Trace_Marker::paint_finished);
if (result == detail::Async_Render_Backend::Submit_Result::queued)
std::invoke(std::forward<Callback>(callback));
@@ -254,7 +244,8 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
if (context->visuals->empty())
throw std::logic_error("render scene submit graph published no Visual snapshots");
backend_submitted = context->backend->render(
context->visuals, context->parameters, context->frame) ==
context->visuals, context->parameters, context->frame,
std::move(events)) ==
detail::Async_Render_Backend::Submit_Result::queued;
frame->mark(Frame_Trace_Marker::paint_finished);
});
@@ -278,6 +269,6 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Object* object,
if (prop.viewport.empty()) return Render_Result::empty_viewport;
return Render_Result::backend_unavailable;
}
template <Attached Object> const Render_Scene_3D::Private::Dispatch& Render_Scene_3D::Private::dispatch_for() { static const Dispatch value{[](Root* root, Frame_3D* frame) { auto* object = static_cast<Object*>(root); return static_cast<typename Object::Private&>(*object->d).render(object, frame); }, [](Root* root, Frame_Callback callback) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); if (data.backend) data.backend->set_frame_callback(std::move(callback)); }, [](Root* root, std::unique_ptr<Event> event) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).push_event(std::move(event)); }, [](Root* root, bool active) { static_cast<Object*>(root)->template set<&Prop::view_active>(active); }}; return value; }
template <Attached Object> const Render_Scene_3D::Private::Dispatch& Render_Scene_3D::Private::dispatch_for() { static const Dispatch value{[](Root* root, Frame_3D* frame) { auto* object = static_cast<Object*>(root); return static_cast<typename Object::Private&>(*object->d).render(object, frame); }, [](Root* root, Frame_Callback callback) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); if (data.backend) data.backend->set_frame_callback(std::move(callback)); }, [](Root* root, bool active) { static_cast<Object*>(root)->template set<&Prop::view_active>(active); }}; return value; }
template <Attached Object> void Render_Scene_3D::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
}