完美一版
This commit is contained in:
@@ -7,9 +7,11 @@
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
namespace aethera::render_3d::detail {
|
||||
namespace {
|
||||
float wheel_step(double pixel, double angle) { return static_cast<float>(pixel != 0.0 ? pixel : angle / 120.0); }
|
||||
Mouse_Button held_button(Mouse_Button_Mask buttons) { if ((buttons & 1U) != 0) return Mouse_Button::left; if ((buttons & 2U) != 0) return Mouse_Button::right; if ((buttons & 4U) != 0) return Mouse_Button::middle; return Mouse_Button::none; }
|
||||
void record_datoviz_trace(Frame_3D* frame, const Datoviz_Frame_Trace& trace) {
|
||||
if (trace.apply_ns) frame->record(Frame_Trace_Measurement::backend_apply_ns, trace.apply_ns);
|
||||
if (trace.emit_ns) frame->record(Frame_Trace_Measurement::backend_plan_ns, trace.emit_ns);
|
||||
@@ -27,6 +29,7 @@ struct Async_Render_Backend::Implementation : std::enable_shared_from_this<Imple
|
||||
struct Pending {
|
||||
std::optional<Datoviz_Visual_Backend::Pending_Frame> backend_frame{}; /* 已提交并等待 GPU fence 的后端帧。 */
|
||||
Frame_3D* output{}; /* 调用方拥有且保证存活到完成回调的输出帧。 */
|
||||
Render_Domain::Frame_Completion domain_completion{}; /* 回读结束后释放同 GPU 的完整帧门闩。 */
|
||||
};
|
||||
struct Submission {
|
||||
Prepared_Visual visual{}; /* 本帧不可变的 CPU Prepared 数据。 */
|
||||
@@ -72,16 +75,17 @@ void Async_Render_Backend::Implementation::submit(Submission submission) {
|
||||
auto pending = std::make_shared<Pending>(Pending{std::nullopt, submission.output});
|
||||
const auto parameters = submission.parameters;
|
||||
const auto sequence = submission.output->identity().sequence;
|
||||
const auto queued = render_domain->try_post([self, parameters, sequence, prepared, pending] {
|
||||
const auto queued = render_domain->try_post_frame([self, parameters, sequence, prepared, pending](Render_Domain::Frame_Completion domain_completion) {
|
||||
pending->domain_completion = std::move(domain_completion);
|
||||
pending->output->mark(Frame_Trace_Marker::backend_queue_left);
|
||||
auto reservation = Gpu_Completion_Service::instance().prepare([self, pending](Gpu_Completion_Service::Result result) { self->finish(pending, std::move(result)); }, [self](std::exception_ptr value) { self->fail(std::move(value)); }, true);
|
||||
if (!reservation) { self->complete(pending->output); return; }
|
||||
if (!reservation) { self->complete(pending->output); auto completion = std::exchange(pending->domain_completion, {}); if (completion) completion(); return; }
|
||||
pending->backend_frame = self->backend->submit(parameters, *prepared, sequence, true);
|
||||
if (!pending->backend_frame) { self->complete(pending->output); return; }
|
||||
if (!pending->backend_frame) { self->complete(pending->output); auto completion = std::exchange(pending->domain_completion, {}); if (completion) completion(); return; }
|
||||
record_datoviz_trace(pending->output, pending->backend_frame->trace);
|
||||
pending->output->mark(Frame_Trace_Marker::gpu_submitted);
|
||||
reservation.reservation.watch(pending->backend_frame->device, pending->backend_frame->fence);
|
||||
}, [self](std::exception_ptr value) { self->fail(std::move(value)); });
|
||||
}, [self, output = submission.output](std::exception_ptr value) { self->fail(std::move(value)); self->complete(output); });
|
||||
if (queued != Render_Domain::Try_Post_Result::queued) {
|
||||
std::lock_guard lock(render_mutex);
|
||||
pending_submissions.push_front(Submission{*prepared, parameters, submission.output});
|
||||
@@ -93,7 +97,7 @@ void Async_Render_Backend::Implementation::finish(std::shared_ptr<Pending> pendi
|
||||
pending->output->record(Frame_Trace_Measurement::gpu_fence_wait_ns, completion.wait_duration_ns);
|
||||
auto self = shared_from_this();
|
||||
try {
|
||||
const auto result = render_domain->post([self, pending = std::move(pending), completion] {
|
||||
const auto result = render_domain->post([self, pending, completion] {
|
||||
if (pending->backend_frame) {
|
||||
if (completion.error == Gpu_Completion_Service::Completion_Error::none) {
|
||||
pending->output->mark(Frame_Trace_Marker::readback_started);
|
||||
@@ -104,7 +108,9 @@ void Async_Render_Backend::Implementation::finish(std::shared_ptr<Pending> pendi
|
||||
} else self->backend->discard(std::move(*pending->backend_frame));
|
||||
}
|
||||
self->complete(pending->output);
|
||||
}, [self](std::exception_ptr value) { self->fail(std::move(value)); });
|
||||
auto domain_completion = std::exchange(pending->domain_completion, {});
|
||||
if (domain_completion) domain_completion();
|
||||
}, [self, pending](std::exception_ptr value) { self->fail(std::move(value)); auto domain_completion = std::exchange(pending->domain_completion, {}); if (domain_completion) domain_completion(); });
|
||||
if (result != Render_Domain::Admission_Result::none) fail(std::make_exception_ptr(std::runtime_error("render domain stopped before GPU completion collection")));
|
||||
} catch (...) { fail(std::current_exception()); }
|
||||
}
|
||||
@@ -145,7 +151,7 @@ Async_Render_Backend::Dispatch_Event_Result Async_Render_Backend::dispatch_event
|
||||
const auto* event_pointer = dynamic_cast<const Pointer_Event_Capability*>(event.get());
|
||||
self->backend->dispatch_wheel(static_cast<float>(event_pointer->position_x()), static_cast<float>(event_pointer->position_y()), wheel_step(event_wheel->pixel_delta_x_value(), event_wheel->angle_delta_x_value()), wheel_step(event_wheel->pixel_delta_y_value(), event_wheel->angle_delta_y_value()), event_pointer->keyboard_modifiers(), viewport);
|
||||
}
|
||||
else if (const auto* event_pointer = dynamic_cast<const Pointer_Event_Capability*>(event.get())) self->backend->dispatch_pointer(event->type, static_cast<float>(event_pointer->position_x()), static_cast<float>(event_pointer->position_y()), event_pointer->pointer_button(), event_pointer->keyboard_modifiers(), viewport);
|
||||
else if (const auto* event_pointer = dynamic_cast<const Pointer_Event_Capability*>(event.get())) self->backend->dispatch_pointer(event->type, static_cast<float>(event_pointer->position_x()), static_cast<float>(event_pointer->position_y()), event->type == Event_Type::pointer_move ? held_button(event_pointer->pointer_buttons()) : event_pointer->pointer_button(), event_pointer->keyboard_modifiers(), viewport);
|
||||
else if (const auto* event_key = dynamic_cast<const Key_Event*>(event.get())) self->backend->dispatch_key(*event_key);
|
||||
event->accept();
|
||||
}, [self](std::exception_ptr value) { self->fail(std::move(value)); });
|
||||
|
||||
@@ -55,6 +55,7 @@ DvzKeyCode key_code(::aethera::Key key, std::uint32_t native_key) {
|
||||
case ::aethera::Key::right: return DVZ_KEY_RIGHT;
|
||||
case ::aethera::Key::up: return DVZ_KEY_UP;
|
||||
case ::aethera::Key::down: return DVZ_KEY_DOWN;
|
||||
case ::aethera::Key::home: return DVZ_KEY_HOME;
|
||||
case ::aethera::Key::unknown: break;
|
||||
}
|
||||
return native_key <= static_cast<std::uint32_t>(DVZ_KEY_LAST)
|
||||
@@ -705,7 +706,8 @@ void Datoviz_Visual_Backend::create_scene(Visual_Family family, const Scene_3D_P
|
||||
camera.projection.far_clip = 100.0F;
|
||||
if (dvz_panel_set_camera_desc(panel_, &camera) != DVZ_OK) throw std::runtime_error("failed to create Datoviz point camera");
|
||||
DvzController* controller = dvz_arcball(scene_, nullptr);
|
||||
if (controller == nullptr ||
|
||||
arcball_ = controller != nullptr ? dvz_controller_arcball(controller) : nullptr;
|
||||
if (controller == nullptr || arcball_ == nullptr ||
|
||||
dvz_panel_bind_controller(panel_, controller, DVZ_DIM_MASK_XYZ) != DVZ_OK)
|
||||
throw std::runtime_error("failed to bind Datoviz arcball controller");
|
||||
input_router_ = dvz_input_router();
|
||||
@@ -917,6 +919,13 @@ void Datoviz_Visual_Backend::dispatch_wheel(
|
||||
void Datoviz_Visual_Backend::dispatch_key(
|
||||
const ::aethera::Key_Event& event) {
|
||||
require_domain();
|
||||
if (event.key == ::aethera::Key::home && event.type == ::aethera::Event_Type::key_press) {
|
||||
if (arcball_ == nullptr || target_extent_.empty()) throw std::runtime_error("failed to reset Datoviz arcball camera");
|
||||
const float width = static_cast<float>(target_extent_.width); const float height = static_cast<float>(target_extent_.height);
|
||||
const auto emit = [&](DvzPointerEventType type) { dvz_pointer_emit_position(input_router_, type, width * 0.5F, height * 0.5F, width, height, DVZ_POINTER_BUTTON_LEFT, DVZ_KEY_MODIFIER_NONE, 1.0F, dvz_input_timestamp_ns(), nullptr); };
|
||||
emit(DVZ_POINTER_EVENT_PRESS); emit(DVZ_POINTER_EVENT_RELEASE); emit(DVZ_POINTER_EVENT_PRESS); emit(DVZ_POINTER_EVENT_RELEASE);
|
||||
return;
|
||||
}
|
||||
const DvzKeyboardEventType type =
|
||||
event.type == ::aethera::Event_Type::key_release
|
||||
? DVZ_KEYBOARD_EVENT_RELEASE
|
||||
@@ -1079,6 +1088,7 @@ void Datoviz_Visual_Backend::destroy() {
|
||||
input_router_ = nullptr;
|
||||
}
|
||||
visual_ = nullptr;
|
||||
arcball_ = nullptr;
|
||||
panel_ = nullptr;
|
||||
figure_ = nullptr;
|
||||
if (scene_ != nullptr) {
|
||||
|
||||
@@ -61,6 +61,7 @@ private:
|
||||
DvzFigure* figure_{}; /* 当前离屏 Figure。 */
|
||||
DvzPanel* panel_{}; /* 承载唯一 Visual 的全屏 Panel。 */
|
||||
DvzVisual* visual_{}; /* Builder 指定 family 的唯一 Visual。 */
|
||||
DvzArcball* arcball_{}; /* 当前 Panel 相机的交互状态;由 Scene Controller 拥有。 */
|
||||
DvzInputRouter* input_router_{}; /* Scene 输入事件路由器。 */
|
||||
DvzPointerGestureHandler* gesture_handler_{}; /* 指针手势解析器。 */
|
||||
std::unique_ptr<Frame_Target> target_; /* 当前尺寸对应的离屏提交和读回目标。 */
|
||||
|
||||
@@ -144,6 +144,58 @@ Render_Domain::Try_Post_Result Render_Domain::try_post(std::function<void()> fun
|
||||
raise_context("trying to post render domain task", std::current_exception());
|
||||
}
|
||||
}
|
||||
Render_Domain::Try_Post_Result Render_Domain::try_post_frame(Frame_Task function, Exception_Handler on_exception) {
|
||||
if (!function) throw std::invalid_argument("render domain frame task is empty");
|
||||
if (!on_exception) throw std::invalid_argument("render domain frame exception handler is empty");
|
||||
if (stopping_.load(std::memory_order_acquire)) return Try_Post_Result::stopping;
|
||||
auto task = std::make_shared<Frame_Task_Entry>(Frame_Task_Entry{std::move(function), std::move(on_exception)});
|
||||
{
|
||||
std::lock_guard lock(frame_task_mutex_);
|
||||
if (frame_task_in_flight_) {
|
||||
if (frame_tasks_.size() >= static_cast<std::size_t>(default_capacity - 1)) return Try_Post_Result::queue_full;
|
||||
frame_tasks_.push_back(std::move(task));
|
||||
return Try_Post_Result::queued;
|
||||
}
|
||||
frame_task_in_flight_ = true;
|
||||
}
|
||||
const auto queued = try_post([this, task] { run_frame_task(task); }, [this, task](std::exception_ptr exception) {
|
||||
task->on_exception(std::move(exception));
|
||||
complete_frame_task();
|
||||
});
|
||||
if (queued != Try_Post_Result::queued) {
|
||||
std::lock_guard lock(frame_task_mutex_);
|
||||
frame_task_in_flight_ = false;
|
||||
}
|
||||
return queued;
|
||||
}
|
||||
void Render_Domain::run_frame_task(std::shared_ptr<Frame_Task_Entry> task) {
|
||||
auto completed = std::make_shared<std::atomic_bool>();
|
||||
auto completion = [this, completed] {
|
||||
if (completed->exchange(true, std::memory_order_acq_rel)) return;
|
||||
complete_frame_task();
|
||||
};
|
||||
try {
|
||||
std::invoke(task->function, completion);
|
||||
}
|
||||
catch (...) {
|
||||
task->on_exception(contextual_exception("executing render domain frame task", std::current_exception()));
|
||||
completion();
|
||||
}
|
||||
}
|
||||
void Render_Domain::complete_frame_task() {
|
||||
if (current_domain_ != this) throw std::logic_error("render domain frame completion must run on its domain");
|
||||
std::shared_ptr<Frame_Task_Entry> next;
|
||||
{
|
||||
std::lock_guard lock(frame_task_mutex_);
|
||||
if (frame_tasks_.empty()) {
|
||||
frame_task_in_flight_ = false;
|
||||
return;
|
||||
}
|
||||
next = std::move(frame_tasks_.front());
|
||||
frame_tasks_.pop_front();
|
||||
}
|
||||
run_frame_task(std::move(next));
|
||||
}
|
||||
Render_Domain::Admission_Result Render_Domain::post(Prepared_Task task) {
|
||||
if (!task.task_ || task.domain_.get() != this) throw std::logic_error("render domain prepared task is invalid");
|
||||
if (stopping_.load(std::memory_order_acquire)) return Admission_Result::stopping;
|
||||
|
||||
@@ -66,6 +66,8 @@ public:
|
||||
Render_Domain(const Render_Domain&) = delete;
|
||||
Render_Domain& operator=(const Render_Domain&) = delete;
|
||||
using Exception_Handler = std::function<void(std::exception_ptr)>;
|
||||
using Frame_Completion = std::function<void()>;
|
||||
using Frame_Task = std::function<void(Frame_Completion)>;
|
||||
[[nodiscard]] Prepare_Result prepare(std::function<void()> function,
|
||||
Exception_Handler on_exception);
|
||||
[[nodiscard]] Admission_Result post(std::function<void()> function,
|
||||
@@ -74,6 +76,9 @@ public:
|
||||
/* 无等待入队;Paint Taskflow 节点只允许使用此入口。 */
|
||||
[[nodiscard]] Try_Post_Result try_post(std::function<void()> function,
|
||||
Exception_Handler on_exception);
|
||||
/* 无等待登记完整 GPU 帧;同一 Render Domain 只允许一帧处于提交至回读生命周期。 */
|
||||
[[nodiscard]] Try_Post_Result try_post_frame(Frame_Task function,
|
||||
Exception_Handler on_exception);
|
||||
[[nodiscard]] Admission_Result post(Prepared_Task task);
|
||||
template <class Result>
|
||||
struct Invoke_Result {
|
||||
@@ -127,10 +132,16 @@ public:
|
||||
}
|
||||
[[nodiscard]] Statistics statistics() const noexcept;
|
||||
private:
|
||||
struct Frame_Task_Entry {
|
||||
Frame_Task function; /* 启动一帧并接收其异步完成出口。 */
|
||||
Exception_Handler on_exception; /* 帧任务 Unknown Failure 的隔离回调。 */
|
||||
};
|
||||
Render_Domain();
|
||||
static void destroy(Render_Domain* domain) noexcept;
|
||||
void request_stop() noexcept;
|
||||
static void update_peak(std::atomic_size_t& peak, std::size_t value) noexcept;
|
||||
void run_frame_task(std::shared_ptr<Frame_Task_Entry> task);
|
||||
void complete_frame_task();
|
||||
void acquire_admission();
|
||||
[[nodiscard]] bool try_acquire_admission() noexcept;
|
||||
void release_admission() noexcept;
|
||||
@@ -141,6 +152,9 @@ private:
|
||||
std::mutex task_mutex_; /* 保护有界准入后的任务队列。 */
|
||||
std::condition_variable task_condition_; /* 新任务或停止哨兵的唤醒源。 */
|
||||
std::deque<std::unique_ptr<Task>> tasks_; /* Render Domain 单消费者任务队列。 */
|
||||
std::mutex frame_task_mutex_; /* 保护跨后端的完整 GPU 帧队列。 */
|
||||
std::deque<std::shared_ptr<Frame_Task_Entry>> frame_tasks_; /* 等待前一帧完成回读的完整 GPU 帧。 */
|
||||
bool frame_task_in_flight_{}; /* 是否已有一帧占用同 GPU 的 Datoviz 生命周期。 */
|
||||
std::atomic_size_t admitted_{}; /* 已获准任务数。 */
|
||||
std::atomic_size_t peak_admitted_{}; /* 历史最大获准任务数。 */
|
||||
std::atomic_size_t queued_{}; /* 当前队列任务数。 */
|
||||
|
||||
@@ -24,7 +24,6 @@ struct Render_Scene_3D : Def<Render_Scene_3D, Scene, Dependency_Graph_Type<Submi
|
||||
template <typename Object>
|
||||
struct Builder : Prev_Builder<Object> {
|
||||
using Base = Prev_Builder<Object>;
|
||||
using Attach_Visual = std::expected<void, Dependency_Graph_Error> (*)(Object*, Root*);
|
||||
template <Attached Visual_Object>
|
||||
explicit Builder(Visual_Object* visual, std::uint32_t gpu_index = 0, bool validation_enabled = false);
|
||||
[[nodiscard]] std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> build();
|
||||
@@ -32,7 +31,6 @@ struct Render_Scene_3D : Def<Render_Scene_3D, Scene, Dependency_Graph_Type<Submi
|
||||
Root* visual{}; /* 不拥有的唯一 Visual;生命周期必须覆盖 Scene。 */
|
||||
Visual_Family visual_family{Visual_Family::point}; /* Visual 编译期 Spec 对应的后端 family。 */
|
||||
void (*bind_visual)(Root*, std::shared_ptr<void>){}; /* 把 Scene 拥有的弱提交上下文绑定到最终 Visual Private。 */
|
||||
Attach_Visual attach_visual{}; /* 以具体 Visual 类型安装 Prepare 与三维 Submit 节点。 */
|
||||
std::uint32_t gpu_index{}; /* Datoviz 使用的物理 GPU 下标。 */
|
||||
bool validation_enabled{}; /* 是否启用 Vulkan 验证。 */
|
||||
};
|
||||
|
||||
@@ -39,10 +39,11 @@ struct Render_Scene_3D::Private : Prev_Private {
|
||||
template <typename Object> template <Attached Visual_Object>
|
||||
Render_Scene_3D::Builder<Object>::Builder(Visual_Object* visual_value, std::uint32_t gpu_index_value, bool validation_enabled_value) : Base(), visual(visual_value), visual_family(Visual_Object::Attached_Object::Specification::family), gpu_index(gpu_index_value), validation_enabled(validation_enabled_value) {
|
||||
bind_visual = [](Root* root, std::shared_ptr<void> context) { auto* object = static_cast<Visual_Object*>(root); Base::private_access(object).template get<typename Visual_Object::Attached_Object::Base_Tag>().bind_paint_target(std::move(context), [](void* raw_context, const detail::Prepared_Visual& prepared) { auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context); const auto& prop = submission.scene->template read_prop<Render_Scene_3D::Base_Tag>(); submission.backend->render(prepared, detail::Scene_3D_Parameters{prop.viewport, prop.clear_color}, submission.frame); }); };
|
||||
attach_visual = [](Object* scene, Root* root) { auto* object = static_cast<Visual_Object*>(root); return scene->template edit_dependency_graph<Prepare_Data_Tag, Submit_Tag>([&](auto& prepare, auto& submit) { prepare.add(object); submit.add(object); }); };
|
||||
this->template add_dependency_node<Prepare_Data_Tag>(visual_value);
|
||||
this->template add_dependency_node<Submit_Tag>(visual_value);
|
||||
}
|
||||
template <typename Object>
|
||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_3D::Builder<Object>::build() { if (!visual || !bind_visual || !attach_visual) throw std::invalid_argument("Render_Scene_3D requires one Visual"); auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto scene = std::move(result).value(); auto& private_data = Base::private_access(scene.get()).template get<Render_Scene_3D::Base_Tag>(); private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, visual_family); bind_visual(visual, private_data.paint_context); auto graph_result = attach_visual(scene.get(), visual); if (!graph_result) return std::unexpected(graph_result.error()); return std::move(scene); }
|
||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Render_Scene_3D::Builder<Object>::build() { if (!visual || !bind_visual) throw std::invalid_argument("Render_Scene_3D requires one Visual"); auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto scene = std::move(result).value(); auto& private_data = Base::private_access(scene.get()).template get<Render_Scene_3D::Base_Tag>(); private_data.initialize_backend(scene.get(), gpu_index, validation_enabled, visual_family); bind_visual(visual, private_data.paint_context); return std::move(scene); }
|
||||
template <Attached Object> void Render_Scene_3D::Private::initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled, Visual_Family visual_family) { const auto& prop = object->template read_prop<Render_Scene_3D::Base_Tag>(); backend = std::make_shared<detail::Async_Render_Backend>(gpu_index, validation_enabled, visual_family, detail::Scene_3D_Parameters{prop.viewport, prop.clear_color}); paint_context = std::make_shared<detail::Scene_Paint_Context<Object>>(detail::Scene_Paint_Context<Object>{backend, object, nullptr}); }
|
||||
inline void Render_Scene_3D::Private::dispatch_events(Extent viewport) { this->consume_events([&](const std::shared_ptr<Event>& event) { const auto result = backend->dispatch_event(event, viewport); return result != detail::Async_Render_Backend::Dispatch_Event_Result::queue_full && result != detail::Async_Render_Backend::Dispatch_Event_Result::backend_unavailable; }); }
|
||||
template <Attached Object, typename Callback>
|
||||
@@ -67,6 +68,7 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
|
||||
const auto prepare = object->template current_dependency_graph<Prepare_Data_Tag>();
|
||||
prepare.for_each_bound([](Renderable* renderable, Renderable::Private& data) { if (data.dispatch->state.get(renderable)->prepare_executed) renderable->template mark_dirty<Submit_Tag>(); });
|
||||
const auto submit = object->template current_dependency_graph<Submit_Tag>();
|
||||
submit.for_each_bound([](Renderable* renderable, Renderable::Private&) { renderable->template mark_dirty<Submit_Tag>(); });
|
||||
const auto result = submit.for_each_topological_view([&](const auto& view, const Dependency_Graph::Node& node) {
|
||||
auto* data = view.private_data(node);
|
||||
if (!data) return;
|
||||
|
||||
Reference in New Issue
Block a user