ai自主改之前

This commit is contained in:
2026-08-24 13:12:57 +08:00
parent cb9822074b
commit ca6d8f8e79
45 changed files with 996 additions and 613 deletions
@@ -0,0 +1,8 @@
#include "Gpu_Completion_State.hpp"
#include "detail/Gpu_Completion_Service.hpp"
namespace aethera::render_3d {
Gpu_Completion_State gpu_completion_state() noexcept {
return detail::Gpu_Completion_Service::instance().state();
}
}
@@ -0,0 +1,38 @@
#pragma once
#include <render_common.hpp>
#include <cstddef>
#include <cstdint>
namespace aethera::render_3d {
struct Gpu_Completion_State_Tag {};
/* 只由 GPU Completion Domain 写入并通过双缓冲发布。 */
struct Gpu_Completion_State : State_Type<Gpu_Completion_State_Tag> {
std::size_t capacity{};
std::size_t in_flight{};
std::size_t peak_in_flight{};
std::size_t watched{};
std::size_t peak_watched{};
std::size_t active_fences{};
std::size_t pending_fences{};
std::uint64_t reservation_count{};
std::uint64_t completion_count{};
std::uint64_t cancellation_count{};
std::uint64_t fence_probe_count{};
std::uint64_t fence_wait_count{};
std::uint64_t fence_wait_timeout_count{};
std::uint64_t fence_wait_total_ns{};
std::uint64_t fence_wait_max_ns{};
std::uint64_t callback_total_ns{};
std::uint64_t callback_max_ns{};
std::uint64_t callback_failure_count{};
std::uint64_t backpressure_count{};
std::uint64_t backpressure_wait_ns{};
std::uint64_t fault_count{};
std::uint64_t abandoned_count{};
bool stopping{};
bool operator==(const Gpu_Completion_State&) const = default;
};
[[nodiscard]] Gpu_Completion_State gpu_completion_state() noexcept;
}
@@ -2,11 +2,12 @@
#include "Datoviz_Visual_Backend.hpp"
#include "Gpu_Completion_Service.hpp"
#include "Render_Domain.hpp"
#include <concurrentqueue-1.0.5/blockingconcurrentqueue.h>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdio>
#include <functional>
#include <deque>
#include <iterator>
#include <limits>
#include <mutex>
@@ -85,17 +86,15 @@ struct Async_Render_Backend::Implementation
std::optional<Gpu_Completion_Service::Result> result{}; /* fence 正常交付时的结果。 */
std::exception_ptr failure{}; /* 提交域或完成服务的 Unknown Failure。 */
};
using Command = std::variant<Submission, Gpu_Completion>;
struct Stop {};
using Command = std::variant<Submission, Gpu_Completion, Stop>;
static constexpr std::size_t command_capacity = 64;
std::shared_ptr<Render_Domain> render_domain; /* 同 GPU 唯一的轻量 Queue Submit 域。 */
std::unique_ptr<Datoviz_Visual_Backend> backend{}; /* 仅准备线程构造、准备、收集和销毁。 */
std::uint32_t gpu_index{}; /* Datoviz 共享 GPU Context 的设备下标。 */
bool validation_enabled{}; /* 是否启用 Vulkan 验证。 */
std::vector<Visual_Registration> visual_registrations{}; /* 首帧初始化后释放的稳定 Visual 注册表。 */
moodycamel::BlockingConcurrentQueue<Command> commands{command_capacity}; /* 多生产者到单准备线程的命令入口。 */
std::thread preparation_thread{}; /* 每 Scene 独立的 Datoviz CPU 准备域。 */
std::unique_ptr<Datoviz_Visual_Backend> backend{}; /* Builder build() 创建;随后由本 Scene 串行使用。 */
std::optional<Submission> deferred_submission{}; /* 三槽占满时仅保留的最新画面和历史回调。 */
std::mutex command_mutex{}; /* 只保护本 Scene 的入队和 drain 所有权。 */
std::deque<Command> commands{}; /* 不同 Scene 可并行,同一 Scene 保持严格串行。 */
bool drain_scheduled{}; /* 是否已有 Taskflow worker 负责本 Scene。 */
std::mutex callback_mutex{}; /* 保护完成回调替换与 render 捕获。 */
Frame_Callback frame_callback{}; /* 后续 render 捕获的完成出口。 */
std::mutex failure_mutex{}; /* 保护跨线程传播的最后 Unknown Failure。 */
@@ -106,19 +105,19 @@ struct Async_Render_Backend::Implementation
Implementation(std::uint32_t gpu_index_value,
bool validation_enabled_value,
std::vector<Visual_Registration> visuals)
std::vector<Visual_Registration> visuals,
const Scene_3D_Parameters& initial_scene)
: render_domain(Render_Domain::acquire(gpu_index_value)),
gpu_index(gpu_index_value),
validation_enabled(validation_enabled_value),
visual_registrations(std::move(visuals)) {}
backend(std::make_unique<Datoviz_Visual_Backend>(
gpu_index_value, validation_enabled_value, visuals, initial_scene)) {}
~Implementation();
void start();
void stop() noexcept;
void run() noexcept;
void fail(std::exception_ptr value) noexcept;
void enqueue(Command command);
void initialize(const Scene_3D_Parameters& parameters);
void drain() noexcept;
void execute(Command command) noexcept;
void finish_stop();
[[nodiscard]] Async_Render_Backend::Submit_Result render(
Shared_Prepared_Visual_Batch visuals, Scene_3D_Parameters parameters,
Frame_3D* frame, Scene::Event_Batch events);
@@ -135,35 +134,28 @@ struct Async_Render_Backend::Implementation
Async_Render_Backend::Async_Render_Backend(
std::uint32_t gpu_index, bool validation_enabled,
std::vector<Visual_Registration> visuals)
std::vector<Visual_Registration> visuals,
const Scene_3D_Parameters& initial_scene)
: implementation_(std::make_shared<Implementation>(
gpu_index, validation_enabled, std::move(visuals))) {
implementation_->start();
}
gpu_index, validation_enabled, std::move(visuals), initial_scene)) {}
Async_Render_Backend::~Async_Render_Backend() {
implementation_->stop();
}
Async_Render_Backend::Implementation::~Implementation() {
stop();
if (preparation_thread.joinable() &&
preparation_thread.get_id() == std::this_thread::get_id())
preparation_thread.detach();
}
void Async_Render_Backend::Implementation::start() {
auto self = shared_from_this();
preparation_thread = std::thread([self = std::move(self)] { self->run(); });
}
void Async_Render_Backend::Implementation::stop() noexcept {
if (stop_requested.exchange(true, std::memory_order_acq_rel)) return;
{
std::lock_guard lock(admission_mutex);
available.store(false, std::memory_order_release);
stop_requested.store(true, std::memory_order_release);
}
if (preparation_thread.joinable() &&
preparation_thread.get_id() != std::this_thread::get_id())
preparation_thread.join();
try {
enqueue(Stop{});
}
catch (...) { fail(std::current_exception()); }
}
void Async_Render_Backend::Implementation::fail(
std::exception_ptr value) noexcept {
@@ -182,15 +174,33 @@ void Async_Render_Backend::Implementation::fail(
catch (...) {}
}
void Async_Render_Backend::Implementation::enqueue(Command command) {
if (!commands.enqueue(std::move(command))) throw std::bad_alloc{};
bool schedule{};
{
std::lock_guard lock(command_mutex);
commands.push_back(std::move(command));
if (!drain_scheduled) {
drain_scheduled = true;
schedule = true;
}
}
if (!schedule) return;
auto self = shared_from_this();
aethera::schedule_task([self] { self->drain(); });
}
void Async_Render_Backend::Implementation::initialize(
const Scene_3D_Parameters& parameters) {
if (backend) return;
backend = std::make_unique<Datoviz_Visual_Backend>(
gpu_index, validation_enabled, visual_registrations, parameters);
visual_registrations.clear();
visual_registrations.shrink_to_fit();
void Async_Render_Backend::Implementation::drain() noexcept {
for (;;) {
std::optional<Command> command;
{
std::lock_guard lock(command_mutex);
if (commands.empty()) {
drain_scheduled = false;
break;
}
command.emplace(std::move(commands.front()));
commands.pop_front();
}
execute(std::move(*command));
}
}
Async_Render_Backend::Submit_Result
Async_Render_Backend::Implementation::render(
@@ -254,7 +264,6 @@ void Async_Render_Backend::Implementation::accept(Submission submission) {
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();
@@ -492,52 +501,42 @@ void Async_Render_Backend::Implementation::dispatch(
if ((wheel && pointer_valid) || pointer_valid || key) event->accept();
event->mark_dispatch_completed();
}
void Async_Render_Backend::Implementation::run() noexcept {
for (;;) {
Command command;
const bool received = commands.wait_dequeue_timed(
command, std::chrono::milliseconds(10));
if (received) {
try {
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));
}
catch (...) {
fail(std::current_exception());
if (auto* submission = std::get_if<Submission>(&command)) {
auto pending = std::make_shared<Pending>();
pending->extent = submission->parameters.viewport;
pending->completions = std::move(submission->completions);
try { resolve(std::move(pending), std::nullopt); }
catch (...) { fail(std::current_exception()); }
}
else if (auto* completion = std::get_if<Gpu_Completion>(&command)) {
try { resolve(std::move(completion->pending), std::nullopt); }
catch (...) { fail(std::current_exception()); }
}
}
void Async_Render_Backend::Implementation::execute(Command command) noexcept {
try {
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
finish_stop();
}
catch (...) {
fail(std::current_exception());
if (auto* submission = std::get_if<Submission>(&command)) {
auto pending = std::make_shared<Pending>();
pending->extent = submission->parameters.viewport;
pending->completions = std::move(submission->completions);
try { resolve(std::move(pending), std::nullopt); }
catch (...) { fail(std::current_exception()); }
}
// render/event 与 stop 共享 admission_mutex,因此停止后只有已经
// 提交到 GPU 的完成通知还能到达。只有一次真实的 timed dequeue
// 为空且 backend 已无在途目标,准备域才完成排空并退出。
if (!received && stop_requested.load(std::memory_order_acquire) &&
(!backend || backend->idle())) {
if (deferred_submission) {
auto pending = std::make_shared<Pending>();
pending->extent = deferred_submission->parameters.viewport;
pending->completions =
std::move(deferred_submission->completions);
deferred_submission.reset();
try { resolve(std::move(pending), std::nullopt); }
catch (...) { fail(std::current_exception()); }
}
backend.reset();
return;
else if (auto* completion = std::get_if<Gpu_Completion>(&command)) {
try { resolve(std::move(completion->pending), std::nullopt); }
catch (...) { fail(std::current_exception()); }
}
}
if (stop_requested.load(std::memory_order_acquire)) finish_stop();
}
void Async_Render_Backend::Implementation::finish_stop() {
if (deferred_submission) {
auto pending = std::make_shared<Pending>();
pending->extent = deferred_submission->parameters.viewport;
pending->completions = std::move(deferred_submission->completions);
deferred_submission.reset();
resolve(std::move(pending), std::nullopt);
}
if (backend && !backend->idle()) return;
backend.reset();
}
Async_Render_Backend::Submit_Result Async_Render_Backend::render(
@@ -13,7 +13,8 @@ public:
backend_unavailable
};
Async_Render_Backend(std::uint32_t gpu_index, bool validation_enabled,
std::vector<Visual_Registration> visuals);
std::vector<Visual_Registration> visuals,
const Scene_3D_Parameters& initial_scene);
~Async_Render_Backend();
Async_Render_Backend(const Async_Render_Backend&) = delete;
Async_Render_Backend& operator=(const Async_Render_Backend&) = delete;
@@ -814,8 +814,7 @@ Datoviz_Visual_Backend::Datoviz_Visual_Backend(
std::uint32_t gpu_index, bool validation_enabled,
const std::vector<Visual_Registration>& visuals,
const Scene_3D_Parameters& initial_scene)
: preparation_thread_(std::this_thread::get_id()),
targets_(std::make_unique<Frame_Targets>()) {
: targets_(std::make_unique<Frame_Targets>()) {
try {
render_context_ = Datoviz_Render_Context::acquire(gpu_index, validation_enabled);
std::scoped_lock context_lock(render_context_->api_mutex(),
@@ -842,11 +841,6 @@ Datoviz_Visual_Backend::~Datoviz_Visual_Backend() noexcept {
try { destroy(); }
catch (...) { abandon_resources(); }
}
void Datoviz_Visual_Backend::require_domain() const {
if (std::this_thread::get_id() != preparation_thread_)
throw std::logic_error(
"Datoviz Scene preparation may only run on its preparation thread");
}
void Datoviz_Visual_Backend::create_scene(
const std::vector<Visual_Registration>& registrations,
const Scene_3D_Parameters& initial_scene) {
@@ -1327,7 +1321,6 @@ bool Datoviz_Visual_Backend::matches_command_structure(
void Datoviz_Visual_Backend::apply(
const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& prepared,
std::uint8_t target_index, bool bind_target) {
require_domain();
if (figure_extent_ != scene.viewport) {
if (dvz_figure_resize(figure_, scene.viewport.width,
scene.viewport.height) != DVZ_OK)
@@ -1747,7 +1740,6 @@ void Datoviz_Visual_Backend::dispatch_pointer(
::aethera::Event_Type event, float x, float y,
::aethera::Mouse_Button mouse_button,
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
require_domain();
std::lock_guard api_lock(render_context_->api_mutex());
input_changed_ = true;
if (applied_camera_ && applied_camera_->controller == Camera_Controller::turntable) {
@@ -1773,7 +1765,6 @@ void Datoviz_Visual_Backend::dispatch_pointer(
void Datoviz_Visual_Backend::dispatch_wheel(
float x, float y, float delta_x, float delta_y,
::aethera::Keyboard_Modifier keyboard_modifiers, Extent viewport) {
require_domain();
std::lock_guard api_lock(render_context_->api_mutex());
input_changed_ = true;
if (applied_camera_ &&
@@ -1786,7 +1777,6 @@ void Datoviz_Visual_Backend::dispatch_wheel(
}
void Datoviz_Visual_Backend::dispatch_key(
const ::aethera::Key_Event& event) {
require_domain();
std::lock_guard api_lock(render_context_->api_mutex());
input_changed_ = true;
if (event.key == ::aethera::Key::home && event.type == ::aethera::Event_Type::key_press) {
@@ -1936,7 +1926,6 @@ Datoviz_Visual_Backend::reuse_recorded_target(
std::optional<Datoviz_Visual_Backend::Pending_Frame> Datoviz_Visual_Backend::prepare(
const Scene_3D_Parameters& scene, const Prepared_Visual_Batch& visuals,
std::uint64_t frame_sequence, bool observe, bool readback) {
require_domain();
if (scene.viewport.empty()) return std::nullopt;
if (auto reused = reuse_recorded_target(
scene, visuals, frame_sequence, observe, readback))
@@ -2097,7 +2086,6 @@ void Datoviz_Visual_Backend::submit(Pending_Frame& pending) {
}
Datoviz_Visual_Backend::Completed_Frame Datoviz_Visual_Backend::collect(
Pending_Frame pending) {
require_domain();
/* Fence 已完成,读回缓冲与查询池只属于本目标槽;不同 Scene 的映射内存
* 下载可以并行,且不能反向阻塞 Render_Domain 的下一批提交。 */
std::lock_guard target_lock(target_mutex_);
@@ -2110,7 +2098,6 @@ Datoviz_Visual_Backend::Completed_Frame Datoviz_Visual_Backend::collect(
return {pending.extent, std::move(collection.pixels), std::move(pending.trace)};
}
void Datoviz_Visual_Backend::discard(Pending_Frame pending) {
require_domain();
std::lock_guard target_lock(target_mutex_);
target(pending).discard_after_completion();
}
@@ -2157,9 +2144,6 @@ void Datoviz_Visual_Backend::abandon_resources() noexcept {
retain_quarantined_context(render_context_);
}
void Datoviz_Visual_Backend::destroy() {
if (std::this_thread::get_id() != preparation_thread_)
throw std::logic_error(
"Datoviz backend may only be destroyed on its preparation thread");
if (quarantined_) {
abandon_resources();
return;
@@ -79,7 +79,6 @@ private:
std::optional<Prepared_Visual> applied{}; /* Last applied metadata and immutable payload snapshot. */
std::vector<External_Attribute> attributes{}; /* Dynamic payload buffers, one triple region per field. */
};
void require_domain() const;
void create_scene(const std::vector<Visual_Registration>& visuals,
const Scene_3D_Parameters& initial_scene);
[[nodiscard]] DvzVisual* create_visual(Visual_Family family);
@@ -112,7 +111,6 @@ private:
[[nodiscard]] Frame_Target& target(const Pending_Frame& pending);
void abandon_resources() noexcept;
void destroy();
std::thread::id preparation_thread_; /* Scene API、资源准备与回收的唯一线程。 */
std::shared_ptr<Datoviz_Render_Context> render_context_; /* 同一 GPU 上所有后端共享的 Device 与分配器。 */
mutable std::mutex target_mutex_{}; /* 当前 Scene 三个目标槽的唯一生命周期同步源。 */
struct Runtime_Slot {
@@ -10,6 +10,7 @@ Gpu_Completion_Service& Gpu_Completion_Service::instance() {
return service;
}
Gpu_Completion_Service::Gpu_Completion_Service() {
publish_state(0, 0);
thread_ = std::thread([this] {
run();
});
@@ -62,6 +63,13 @@ void Gpu_Completion_Service::update_peak(std::atomic_size_t& peak,
!peak.compare_exchange_weak(current, value,
std::memory_order_relaxed)) {}
}
void Gpu_Completion_Service::update_max(std::atomic_uint64_t& maximum,
std::uint64_t value) noexcept {
std::uint64_t current = maximum.load(std::memory_order_relaxed);
while (current < value &&
!maximum.compare_exchange_weak(current, value,
std::memory_order_relaxed)) {}
}
void Gpu_Completion_Service::cancel_reserved(
const std::shared_ptr<Pending_Fence>& pending) {
if (!pending) return;
@@ -112,21 +120,42 @@ Gpu_Completion_Service::Prepare_Result Gpu_Completion_Service::prepare(
release_slot();
throw;
}
reservation_count_.fetch_add(1, std::memory_order_relaxed);
wake();
return {Reservation(std::move(pending)), Admission_Result::none};
}
Gpu_Completion_Service::Statistics Gpu_Completion_Service::statistics() const noexcept {
return {
static_cast<std::size_t>(default_capacity),
in_flight_.load(std::memory_order_relaxed),
peak_in_flight_.load(std::memory_order_relaxed),
watched_.load(std::memory_order_relaxed),
peak_watched_.load(std::memory_order_relaxed),
backpressure_count_.load(std::memory_order_relaxed),
backpressure_wait_ns_.load(std::memory_order_relaxed),
fault_count_.load(std::memory_order_relaxed),
abandoned_count_.load(std::memory_order_relaxed)
};
Gpu_Completion_State Gpu_Completion_Service::state() const noexcept {
std::lock_guard lock(state_mutex_);
return *state_.pending;
}
void Gpu_Completion_Service::publish_state(std::size_t active_fences,
std::size_t pending_fences) noexcept {
std::lock_guard lock(state_mutex_);
auto& state = *state_.current;
state.capacity = static_cast<std::size_t>(default_capacity);
state.in_flight = in_flight_.load(std::memory_order_relaxed);
state.peak_in_flight = peak_in_flight_.load(std::memory_order_relaxed);
state.watched = watched_.load(std::memory_order_relaxed);
state.peak_watched = peak_watched_.load(std::memory_order_relaxed);
state.active_fences = active_fences;
state.pending_fences = pending_fences;
state.reservation_count = reservation_count_.load(std::memory_order_relaxed);
state.completion_count = completion_count_.load(std::memory_order_relaxed);
state.cancellation_count = cancellation_count_.load(std::memory_order_relaxed);
state.fence_probe_count = fence_probe_count_.load(std::memory_order_relaxed);
state.fence_wait_count = fence_wait_count_.load(std::memory_order_relaxed);
state.fence_wait_timeout_count = fence_wait_timeout_count_.load(std::memory_order_relaxed);
state.fence_wait_total_ns = fence_wait_total_ns_.load(std::memory_order_relaxed);
state.fence_wait_max_ns = fence_wait_max_ns_.load(std::memory_order_relaxed);
state.callback_total_ns = callback_total_ns_.load(std::memory_order_relaxed);
state.callback_max_ns = callback_max_ns_.load(std::memory_order_relaxed);
state.callback_failure_count = callback_failure_count_.load(std::memory_order_relaxed);
state.backpressure_count = backpressure_count_.load(std::memory_order_relaxed);
state.backpressure_wait_ns = backpressure_wait_ns_.load(std::memory_order_relaxed);
state.fault_count = fault_count_.load(std::memory_order_relaxed);
state.abandoned_count = abandoned_count_.load(std::memory_order_relaxed);
state.stopping = stopping_.load(std::memory_order_relaxed);
state_.advance();
}
void Gpu_Completion_Service::wake() noexcept {
wake_generation_.fetch_add(1, std::memory_order_release);
@@ -141,6 +170,7 @@ void Gpu_Completion_Service::run() noexcept {
try {
active.reserve(static_cast<std::size_t>(default_capacity));
std::size_t wait_group_index{};
auto last_state_publication = std::chrono::steady_clock::time_point{};
const auto wait_age_ns = [](std::chrono::steady_clock::time_point started) {
const auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - started)
@@ -176,14 +206,24 @@ void Gpu_Completion_Service::run() noexcept {
fault_count_.fetch_add(1, std::memory_order_relaxed);
abandoned_count_.fetch_add(1, std::memory_order_relaxed);
}
const auto callback_started = std::chrono::steady_clock::now();
try { completion(std::move(completion_result)); }
catch (...) {
callback_failure_count_.fetch_add(1, std::memory_order_relaxed);
try {
on_exception(contextual_exception(
"delivering GPU completion", std::current_exception()));
}
catch (...) {}
}
const auto callback_elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - callback_started).count();
if (callback_elapsed > 0) {
const auto elapsed = static_cast<std::uint64_t>(callback_elapsed);
callback_total_ns_.fetch_add(elapsed, std::memory_order_relaxed);
update_max(callback_max_ns_, elapsed);
}
completion_count_.fetch_add(1, std::memory_order_relaxed);
}
catch (...) {
try {
@@ -200,6 +240,7 @@ void Gpu_Completion_Service::run() noexcept {
wake_generation_.load(std::memory_order_acquire);
{ std::lock_guard lock(pending_mutex_); while (!pending_.empty()) { active.push_back(std::move(pending_.front())); pending_.pop_front(); } }
std::vector<Device_Fences> groups;
std::size_t reserved_count{};
for (auto iterator = active.begin(); iterator != active.end();) {
Pending_Fence::Status status;
VkDevice device{VK_NULL_HANDLE};
@@ -215,9 +256,11 @@ void Gpu_Completion_Service::run() noexcept {
stopping_.load(std::memory_order_acquire))) {
cancel_reserved(*iterator);
iterator = active.erase(iterator);
cancellation_count_.fetch_add(1, std::memory_order_relaxed);
release_slot();
continue;
}
if (status == Pending_Fence::Status::reserved) ++reserved_count;
if (status == Pending_Fence::Status::watched) {
auto group = std::find_if(
groups.begin(), groups.end(),
@@ -232,9 +275,19 @@ void Gpu_Completion_Service::run() noexcept {
}
++iterator;
}
const auto publication_time = std::chrono::steady_clock::now();
if (last_state_publication == std::chrono::steady_clock::time_point{} ||
publication_time - last_state_publication >= state_publication_interval) {
// 域内只按诊断粒度发布双缓冲 State;逐 fence 热路径只更新原子计数。
publish_state(active.size(), reserved_count);
last_state_publication = publication_time;
}
bool pending_empty;
{ std::lock_guard lock(pending_mutex_); pending_empty = pending_.empty(); }
if (stopping_.load(std::memory_order_acquire) && active.empty() && pending_empty) return;
if (stopping_.load(std::memory_order_acquire) && active.empty() && pending_empty) {
publish_state(0, 0);
return;
}
// Probe every watched fence first. A permanently unsignaled fence is
// converted into a logical failure after a bounded interval. The
// submission is explicitly marked abandoned so its owner can
@@ -270,6 +323,7 @@ void Gpu_Completion_Service::run() noexcept {
completed_any = true;
continue;
}
fence_probe_count_.fetch_add(1, std::memory_order_relaxed);
const VkResult result = vkGetFenceStatus(device, fence);
if (result == VK_NOT_READY) {
++iterator;
@@ -296,9 +350,20 @@ void Gpu_Completion_Service::run() noexcept {
}
wait_group_index %= groups.size();
const Device_Fences& group = groups[wait_group_index++];
const auto wait_started = std::chrono::steady_clock::now();
fence_wait_count_.fetch_add(1, std::memory_order_relaxed);
const VkResult wait_result = vkWaitForFences(
group.device, static_cast<std::uint32_t>(group.fences.size()),
group.fences.data(), VK_FALSE, fence_wait_timeout_ns);
const auto wait_elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - wait_started).count();
if (wait_elapsed > 0) {
const auto elapsed = static_cast<std::uint64_t>(wait_elapsed);
fence_wait_total_ns_.fetch_add(elapsed, std::memory_order_relaxed);
update_max(fence_wait_max_ns_, elapsed);
}
if (wait_result == VK_TIMEOUT)
fence_wait_timeout_count_.fetch_add(1, std::memory_order_relaxed);
for (auto iterator = active.begin(); iterator != active.end();) {
VkDevice device{VK_NULL_HANDLE};
VkFence fence{VK_NULL_HANDLE};
@@ -315,7 +380,10 @@ void Gpu_Completion_Service::run() noexcept {
continue;
}
VkResult result = wait_result;
if (wait_result == VK_SUCCESS || wait_result == VK_TIMEOUT) result = vkGetFenceStatus(device, fence);
if (wait_result == VK_SUCCESS || wait_result == VK_TIMEOUT) {
fence_probe_count_.fetch_add(1, std::memory_order_relaxed);
result = vkGetFenceStatus(device, fence);
}
if (result == VK_NOT_READY) {
++iterator;
continue;
@@ -346,6 +414,7 @@ void Gpu_Completion_Service::run() noexcept {
catch (...) {}
if (watched)
watched_.fetch_sub(1, std::memory_order_relaxed);
cancellation_count_.fetch_add(1, std::memory_order_relaxed);
if (on_exception) {
try { on_exception(service_failure); }
catch (...) {}
@@ -364,6 +433,7 @@ void Gpu_Completion_Service::run() noexcept {
catch (...) { break; }
fail_pending(pending);
}
publish_state(0, 0);
}
}
}
@@ -1,4 +1,6 @@
#pragma once
#include "../Gpu_Completion_State.hpp"
#include <double_buffer/mechanism.hpp>
#include <volk.h>
#include <atomic>
#include <chrono>
@@ -30,17 +32,6 @@ public:
VkResult vulkan_result{VK_SUCCESS}; /* Vulkan 原始结果码。 */
std::uint64_t wait_duration_ns{}; /* fence 等待时间,单位为纳秒。 */
};
struct Statistics {
std::size_t capacity{}; /* 最大并发 reservation 数。 */
std::size_t in_flight{}; /* 当前 reservation 数。 */
std::size_t peak_in_flight{}; /* 历史最大 reservation 数。 */
std::size_t watched{}; /* 当前受监视 fence 数。 */
std::size_t peak_watched{}; /* 历史最大受监视 fence 数。 */
std::uint64_t backpressure_count{}; /* 容量不足累计次数。 */
std::uint64_t backpressure_wait_ns{}; /* 准入累计等待时间,单位为纳秒。 */
std::uint64_t fault_count{}; /* Vulkan 或超时故障累计次数。 */
std::uint64_t abandoned_count{}; /* 被隔离的 fence 累计数。 */
};
using Completion = std::function<void(Result)>;
using Exception_Handler = std::function<void(std::exception_ptr)>;
class Reservation final {
@@ -69,7 +60,7 @@ public:
[[nodiscard]] Prepare_Result prepare(Completion completion,
Exception_Handler on_exception,
bool observe);
[[nodiscard]] Statistics statistics() const noexcept;
[[nodiscard]] Gpu_Completion_State state() const noexcept;
private:
struct Pending_Fence {
enum class Status {
@@ -90,14 +81,19 @@ private:
Gpu_Completion_Service();
~Gpu_Completion_Service();
static void update_peak(std::atomic_size_t& peak, std::size_t value) noexcept;
static void update_max(std::atomic_uint64_t& maximum,
std::uint64_t value) noexcept;
static void cancel_reserved(const std::shared_ptr<Pending_Fence>& pending);
void acquire_slot();
void release_slot() noexcept;
void publish_state(std::size_t active_fences,
std::size_t pending_fences) noexcept;
void wake() noexcept;
void run() noexcept;
static constexpr std::ptrdiff_t default_capacity = 1024;
static constexpr std::uint64_t fence_wait_timeout_ns = 1'000'000;
static constexpr std::uint64_t maximum_fence_age_ns = 30'000'000'000ULL;
static constexpr auto state_publication_interval = std::chrono::milliseconds(100);
std::counting_semaphore<default_capacity> slots_{default_capacity}; /* 有界 reservation 槽位。 */
std::mutex pending_mutex_; /* 保护新登记 fence 队列。 */
std::deque<std::shared_ptr<Pending_Fence>> pending_; /* 完成线程尚未分组的 fence。 */
@@ -108,11 +104,24 @@ private:
std::atomic_size_t peak_in_flight_{}; /* 历史最大 reservation 数。 */
std::atomic_size_t watched_{}; /* 当前受监视 fence 数。 */
std::atomic_size_t peak_watched_{}; /* 历史最大受监视 fence 数。 */
std::atomic_uint64_t reservation_count_{};
std::atomic_uint64_t completion_count_{};
std::atomic_uint64_t cancellation_count_{};
std::atomic_uint64_t fence_probe_count_{};
std::atomic_uint64_t fence_wait_count_{};
std::atomic_uint64_t fence_wait_timeout_count_{};
std::atomic_uint64_t fence_wait_total_ns_{};
std::atomic_uint64_t fence_wait_max_ns_{};
std::atomic_uint64_t callback_total_ns_{};
std::atomic_uint64_t callback_max_ns_{};
std::atomic_uint64_t callback_failure_count_{};
std::atomic_uint64_t backpressure_count_{}; /* 准入背压累计次数。 */
std::atomic_uint64_t backpressure_wait_ns_{}; /* 准入背压累计纳秒。 */
std::atomic_uint64_t fault_count_{}; /* 完成故障累计次数。 */
std::atomic_uint64_t abandoned_count_{}; /* 放弃 fence 累计次数。 */
std::atomic_bool stopping_{}; /* 服务是否正在停止。 */
mutable std::mutex state_mutex_; /* 只保护域级 State 的指针交换。 */
double_buffer::Publish_Double_Buffer<Gpu_Completion_State> state_{};
std::thread thread_; /* 专用 fence 完成线程。 */
};
}
@@ -1,9 +1,18 @@
#include "Render_Scene_3D.hpp" /* 三维异步提交阶段实现。 */
namespace aethera::render_3d {
Render_Scene_3D::Private::~Private() {
/* 先停止后端产生新回调,再等待已经触发的 completion topology 释放借用帧。 */
backend.reset();
std::unique_lock lock(completion_mutex);
completion_condition.wait(lock, [this] { return !completion_running; });
}
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)); }
tf::Taskflow& Render_Scene_3D::completion_taskflow() {
return static_cast<Private&>(*d).completion_graph;
}
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); }
}
+10 -3
View File
@@ -56,12 +56,19 @@ struct Render_Scene_3D : Def<Render_Scene_3D, Scene, Dependency_Graph_Type<Submi
std::uint32_t gpu_index{}; /* Datoviz 使用的物理 GPU 下标。 */
bool validation_enabled{}; /* 是否启用 Vulkan 验证。 */
};
enum class Render_Result { submitted, view_inactive, empty_viewport, backend_unavailable };
enum class Render_Result { submitted, frame_in_flight, view_inactive, empty_viewport, backend_unavailable };
using Frame_Callback = std::function<void(Frame_3D*)>;
/* 无等待提交到调用方拥有的帧;frame 必须存活到完成回调返回。 */
/* 无等待提交到调用方拥有的帧;必须先安装回调,回调返回前只允许一帧在途。 */
[[nodiscard]] Render_Result render(Frame_3D* frame);
/* 安装完成帧回调;回调由 Datoviz Render Domain 线程传回同一外部帧。 */
/* 安装完成帧回调;回调传回同一外部帧,返回时 Scene 才释放下一帧准入。 */
void set_frame_callback(Frame_Callback callback);
/*
* GPU Taskflow
* Scene emplace/erase/clear
* completion Taskflow
* share_pixels() Frame_3D*
*/
[[nodiscard]] tf::Taskflow& completion_taskflow();
void activate_view();
void deactivate_view();
};
+72 -6
View File
@@ -1,6 +1,8 @@
#pragma once
#include "../detail/Async_Render_Backend.hpp"
#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <stdexcept>
namespace aethera::render_3d {
namespace detail {
@@ -23,6 +25,13 @@ struct Render_Scene_3D::Private : Prev_Private {
Callback_Run set_frame_callback; /* 安装最终完成帧回调。 */
Active_Run set_active; /* 修改最终 Scene 的活动属性。 */
};
std::mutex render_mutex{}; /* 只保护完成回调和单帧准入。 */
bool frame_in_flight{}; /* render 准入到异步完成回调返回的唯一状态源。 */
Frame_Callback frame_callback{}; /* Scene 的唯一对外完成出口。 */
tf::Taskflow completion_graph{}; /* GPU 像素完成后、发布回调前执行的外部续写图。 */
std::mutex completion_mutex{}; /* 只保护 completion 图拓扑的在途生命周期。 */
std::condition_variable completion_condition{};
bool completion_running{}; /* 析构等待异步 completion 图结束的唯一状态源。 */
std::shared_ptr<detail::Async_Render_Backend> backend{}; /* Scene 拥有的异步后端;已入队命令自行延长实现寿命。 */
std::shared_ptr<void> paint_context{}; /* Paint 节点读取 Scene 当前 Prop 的生命周期门闩。 */
Root* camera_component{}; /* Builder 绑定的 Camera 组件。 */
@@ -31,6 +40,7 @@ struct Render_Scene_3D::Private : Prev_Private {
std::array<plot::Axis_Descriptor, 3> (*read_axes)(const Root*){}; /* 读取三轴当前配置。 */
Frame_3D* active_frame{}; /* 当前同步 process 借用的外部帧;提交完成后清空。 */
const Dispatch* dispatch{}; /* 最终 Scene 类型对应的静态公开分派表。 */
~Private();
/* Builder 内部初始化后端;必须在绑定 Visual Paint 目标之前调用一次。 */
template <Attached Object>
void initialize_backend(Object* object, std::uint32_t gpu_index, bool validation_enabled,
@@ -157,7 +167,41 @@ void Render_Scene_3D::Private::initialize_backend(
read_axes = axes_reader;
const auto initial = parameters(object);
backend = std::make_shared<detail::Async_Render_Backend>(gpu_index, validation_enabled,
std::move(visuals));
std::move(visuals), initial);
backend->set_frame_callback([this](Frame_3D* frame) {
Frame_Callback callback;
{
std::lock_guard lock(render_mutex);
callback = frame_callback;
}
auto finish = [this, frame, callback = std::move(callback)]() mutable {
frame->mark(Frame_Trace_Marker::callback_started);
if (callback) callback(frame);
frame->mark(Frame_Trace_Marker::callback_finished);
{
std::lock_guard lock(render_mutex);
frame_in_flight = false;
}
{
std::lock_guard lock(completion_mutex);
completion_running = false;
}
completion_condition.notify_all();
};
if (completion_graph.empty()) {
finish();
return;
}
{
std::lock_guard lock(completion_mutex);
completion_running = true;
}
/*
* GPU 完成线程只触发 Taskflow topology,不等待编码/发送节点;最终回调在 topology 完成后执行。
* 帧准入在 finish() 之前始终有效,因此 completion 图运行期间该外部 Frame 不会被下一帧复用。
*/
aethera::detail::run_taskflow(completion_graph, std::move(finish));
});
paint_context = std::make_shared<detail::Scene_Paint_Context<Object>>(
detail::Scene_Paint_Context<Object>{
backend, object, nullptr, initial,
@@ -219,20 +263,21 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
Prev_Private::process(object, frame, [&](const Scene::Private::Result&) {
frame->mark(Frame_Trace_Marker::paint_started);
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>(); });
prepare.for_each_bound([](Renderable* renderable, Renderable::Private& data) { if (data.dispatch->state.pending(renderable)->prepare_executed) renderable->template mark_dirty<Submit_Tag>(); });
const auto submit = object->template current_dependency_graph<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;
Root* root = node.object;
auto* dispatch = data->dispatch;
auto& state = *dispatch->state.get(root);
auto& state = *dispatch->state.pending(root);
state.paint_graph_rebuilt = false;
state.paint_execution_time_ns = 0;
const bool dirty = root->template dirty<Submit_Tag>();
state.paint_dirty = dirty;
state.paint_executed = dispatch->paint.predicate(root, dirty);
if (!state.paint_executed) return;
const auto paint_started = std::chrono::steady_clock::now();
if (dispatch->paint.builder) {
if (!data->paint_graph) data->paint_graph = std::make_unique<tf::Taskflow>();
const bool rebuild = dispatch->paint.rebuild_predicate(root);
@@ -240,8 +285,13 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
state.paint_task_count = data->paint_graph->num_tasks();
if (!data->paint_graph->empty()) aethera::detail::run_taskflow(*data->paint_graph);
} else { state.paint_task_count = dispatch->paint.run ? 1 : 0; if (dispatch->paint.run) dispatch->paint.run(root); }
if (!data->paint_extension.empty())
aethera::detail::run_taskflow(data->paint_extension);
root->template take_dirty<Submit_Tag>();
dispatch->state.notify(root);
state.paint_execution_time_ns = static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - paint_started).count());
dispatch->state.publish(root);
});
if (!result) throw std::logic_error("render scene submit graph became invalid during submission");
if (context->visuals->empty())
@@ -257,6 +307,23 @@ void Render_Scene_3D::Private::process(Object* object, Callback&& callback) requ
template <Attached Object>
Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Object* object, Frame_3D* frame) {
if (!frame) throw std::invalid_argument("Render_Scene_3D requires a non-null external frame");
{
std::lock_guard lock(render_mutex);
if (!frame_callback)
throw std::logic_error("Render_Scene_3D requires a frame callback before render");
if (frame_in_flight) return Render_Result::frame_in_flight;
frame_in_flight = true;
}
bool submitted{};
struct Admission_Scope {
Private& data;
bool& submitted;
~Admission_Scope() {
if (submitted) return;
std::lock_guard lock(data.render_mutex);
data.frame_in_flight = false;
}
} admission{*this, submitted};
frame->mark(Frame_Trace_Marker::scene_render_requested);
struct Active_Frame_Scope {
Frame_3D*& target; /* 最终 Private 的同步 process 帧槽位。 */
@@ -264,7 +331,6 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Object* object,
~Active_Frame_Scope() { target = previous; }
} active_frame_scope{active_frame, active_frame};
active_frame = frame;
bool submitted{};
object->process([&] { submitted = true; frame->mark(Frame_Trace_Marker::scene_render_finished); });
if (submitted) return Render_Result::submitted;
const auto& prop = object->template read_prop<Render_Scene_3D::Base_Tag>();
@@ -272,6 +338,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, 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); std::lock_guard lock(data.render_mutex); data.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>(); }
}
+10 -1
View File
@@ -67,7 +67,15 @@ TEST(Render_3D_Scene, Paint_Submits_Without_Waiting_For_Gpu) {
Frame_3D unchanged_frame{Frame_Identity{2, 0}};
Frame_3D exchanged_unchanged_frame{Frame_Identity{3, 0}};
Frame_3D* completed_frame{};
scene->set_frame_callback([&](Frame_3D* value) { completed_frame = value; frame_ready.store(true, std::memory_order_release); });
std::atomic_size_t completion_calls{};
scene->completion_taskflow().emplace([&] {
completion_calls.fetch_add(1, std::memory_order_release);
}).name("test.scene_3d.completion");
scene->set_frame_callback([&](Frame_3D* value) {
EXPECT_GT(completion_calls.load(std::memory_order_acquire), 0u);
completed_frame = value;
frame_ready.store(true, std::memory_order_release);
});
const auto started = std::chrono::steady_clock::now();
EXPECT_EQ(scene->render(&frame), Render_Scene_3D::Render_Result::submitted);
const auto submit_duration = std::chrono::steady_clock::now() - started;
@@ -86,6 +94,7 @@ TEST(Render_3D_Scene, Paint_Submits_Without_Waiting_For_Gpu) {
EXPECT_EQ(scene->render(&exchanged_unchanged_frame), Render_Scene_3D::Render_Result::submitted);
for (std::size_t attempt = 0; attempt != 500 && !frame_ready.load(std::memory_order_acquire); ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(10));
EXPECT_EQ(completed_frame, &exchanged_unchanged_frame);
EXPECT_EQ(completion_calls.load(std::memory_order_acquire), 3u);
EXPECT_TRUE(std::ranges::equal(unchanged_frame.pixels(), exchanged_unchanged_frame.pixels()));
}
catch (const std::exception& error) {