394 lines
29 KiB
C++
394 lines
29 KiB
C++
#pragma once
|
|
#include "../detail/Exception.hpp"
|
|
#include "../detail/Gpu_Completion_Service.hpp"
|
|
#include "Error_handling_specification/Failure_Policy.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
namespace aethera::render_3d {
|
|
namespace detail {
|
|
struct Scene_Paint_Context final {
|
|
Scene_3D_Parameters parameters{}; /* 当前 CPU Prepare 唯一读取的 Scene 参数。 */
|
|
Prepared_Visual_Batch visuals{}; /* 各 Visual 节点只写自己的预分配位置。 */
|
|
};
|
|
|
|
inline float wheel_step(double pixel, double angle) {
|
|
return angle != 0.0 ? static_cast<float>(angle / 120.0) : static_cast<float>(pixel / 100.0);
|
|
}
|
|
|
|
inline 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;
|
|
}
|
|
|
|
inline void publish_visual(void* raw_context, std::size_t slot, const Prepared_Visual& prepared) {
|
|
if (!raw_context) throw std::logic_error("3D Visual lost its Scene paint context");
|
|
auto& context = *static_cast<Scene_Paint_Context*>(raw_context);
|
|
if (slot >= context.visuals.size()) throw std::logic_error("3D Visual published outside its Scene slot");
|
|
context.visuals[slot].visual = prepared;
|
|
}
|
|
|
|
inline void publish_datoviz_observation(Frame_3D* frame, Datoviz_Frame_Observation observation) {
|
|
if (!frame) return;
|
|
if (observation.apply_ns) frame->record(Frame_Trace_Measurement::backend_apply_ns, observation.apply_ns);
|
|
const auto plan_ns = observation.runtime_plan_ns + observation.frame_plan_ns;
|
|
const auto execute_ns = observation.runtime_execute_ns + observation.frame_attach_ns + observation.frame_execute_ns;
|
|
if (plan_ns) frame->record(Frame_Trace_Measurement::backend_plan_ns, plan_ns);
|
|
if (execute_ns) frame->record(Frame_Trace_Measurement::backend_execute_ns, execute_ns);
|
|
const std::array detailed_timings{std::pair{Frame_Trace_Measurement::backend_target_acquire_ns, observation.target_acquire_ns}, std::pair{Frame_Trace_Measurement::backend_structure_check_ns, observation.structure_check_ns}, std::pair{Frame_Trace_Measurement::backend_query_ns, observation.query_ns}, std::pair{Frame_Trace_Measurement::backend_runtime_plan_ns, observation.runtime_plan_ns}, std::pair{Frame_Trace_Measurement::backend_runtime_plan_cpu_ns, observation.runtime_plan_cpu_ns}, std::pair{Frame_Trace_Measurement::backend_runtime_execute_ns, observation.runtime_execute_ns}, std::pair{Frame_Trace_Measurement::backend_runtime_execute_cpu_ns, observation.runtime_execute_cpu_ns}, std::pair{Frame_Trace_Measurement::backend_mvp_update_ns, observation.mvp_update_ns}, std::pair{Frame_Trace_Measurement::backend_frame_begin_ns, observation.frame_begin_ns}, std::pair{Frame_Trace_Measurement::backend_frame_plan_ns, observation.frame_plan_ns}, std::pair{Frame_Trace_Measurement::backend_frame_plan_cpu_ns, observation.frame_plan_cpu_ns}, std::pair{Frame_Trace_Measurement::backend_emit_replay_dirty_ns, observation.emit_replay_dirty_ns}, std::pair{Frame_Trace_Measurement::backend_emit_layout_ns, observation.emit_layout_ns}, std::pair{Frame_Trace_Measurement::backend_emit_prepare_ns, observation.emit_prepare_ns}, std::pair{Frame_Trace_Measurement::backend_emit_plan_build_ns, observation.emit_plan_build_ns}, std::pair{Frame_Trace_Measurement::backend_emit_contract_ns, observation.emit_contract_ns}, std::pair{Frame_Trace_Measurement::backend_emit_stream_ns, observation.emit_stream_ns}, std::pair{Frame_Trace_Measurement::backend_emit_stream_freeze_ns, observation.emit_stream_freeze_ns}, std::pair{Frame_Trace_Measurement::backend_emit_commit_ns, observation.emit_commit_ns}, std::pair{Frame_Trace_Measurement::backend_emit_plan_reset_ns, observation.emit_plan_reset_ns}, std::pair{Frame_Trace_Measurement::backend_emit_artifact_create_ns, observation.emit_artifact_create_ns}, std::pair{Frame_Trace_Measurement::backend_emit_artifact_freeze_ns, observation.emit_artifact_freeze_ns}, std::pair{Frame_Trace_Measurement::backend_emit_packet_encode_ns, observation.emit_packet_encode_ns}, std::pair{Frame_Trace_Measurement::backend_external_register_ns, observation.external_register_ns}, std::pair{Frame_Trace_Measurement::backend_frame_attach_ns, observation.frame_attach_ns}, std::pair{Frame_Trace_Measurement::backend_frame_execute_ns, observation.frame_execute_ns}, std::pair{Frame_Trace_Measurement::backend_frame_execute_cpu_ns, observation.frame_execute_cpu_ns}, std::pair{Frame_Trace_Measurement::backend_frame_finish_ns, observation.frame_finish_ns}, std::pair{Frame_Trace_Measurement::backend_drp_validation_ns, observation.drp_validation_ns}, std::pair{Frame_Trace_Measurement::backend_drp_state_ns, observation.drp_state_ns}, std::pair{Frame_Trace_Measurement::backend_drp_buffer_create_ns, observation.drp_buffer_create_ns}, std::pair{Frame_Trace_Measurement::backend_drp_texture_create_ns, observation.drp_texture_create_ns}, std::pair{Frame_Trace_Measurement::backend_drp_shader_create_ns, observation.drp_shader_create_ns}, std::pair{Frame_Trace_Measurement::backend_drp_shader_compile_ns, observation.drp_shader_compile_ns}, std::pair{Frame_Trace_Measurement::backend_drp_shader_module_create_ns, observation.drp_shader_module_create_ns}, std::pair{Frame_Trace_Measurement::backend_drp_pipeline_create_ns, observation.drp_pipeline_create_ns}, std::pair{Frame_Trace_Measurement::backend_drp_binding_create_ns, observation.drp_binding_create_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_ns, observation.drp_upload_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_decode_ns, observation.drp_upload_decode_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_vulkan_allocate_ns, observation.drp_upload_vulkan_allocate_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_host_copy_ns, observation.drp_upload_host_copy_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_command_allocate_ns, observation.drp_upload_command_allocate_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_command_record_ns, observation.drp_upload_command_record_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_fence_create_ns, observation.drp_upload_fence_create_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_submit_enqueue_ns, observation.drp_upload_submit_enqueue_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_submit_queue_wait_ns, observation.drp_upload_submit_queue_wait_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_queue_submit_ns, observation.drp_upload_queue_submit_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_fence_wait_ns, observation.drp_upload_fence_wait_ns}, std::pair{Frame_Trace_Measurement::backend_drp_upload_retire_ns, observation.drp_upload_retire_ns}, std::pair{Frame_Trace_Measurement::backend_drp_transfer_ns, observation.drp_transfer_ns}, std::pair{Frame_Trace_Measurement::backend_drp_record_ns, observation.drp_record_ns}};
|
|
for (const auto& [measurement, value] : detailed_timings) if (value) frame->record(measurement, value);
|
|
if (observation.queue_submit_wait_ns) frame->record(Frame_Trace_Measurement::backend_submit_queue_ns, observation.queue_submit_wait_ns);
|
|
if (observation.submit_ns) frame->record(Frame_Trace_Measurement::backend_submit_ns, observation.submit_ns);
|
|
if (observation.readback_ns) frame->record(Frame_Trace_Measurement::readback_ns, observation.readback_ns);
|
|
if (observation.gpu_completion_observation_ns) frame->record(Frame_Trace_Measurement::gpu_completion_observation_ns, observation.gpu_completion_observation_ns);
|
|
if (observation.completion_task_queue_ns) frame->record(Frame_Trace_Measurement::completion_task_queue_ns, observation.completion_task_queue_ns);
|
|
if (observation.gpu) {
|
|
frame->record(Frame_Trace_Measurement::gpu_render_ns, observation.gpu->render_ns);
|
|
frame->record(Frame_Trace_Measurement::gpu_transition_ns, observation.gpu->transition_ns);
|
|
frame->record(Frame_Trace_Measurement::gpu_copy_ns, observation.gpu->copy_ns);
|
|
frame->record(Frame_Trace_Measurement::gpu_total_ns, observation.gpu->total_ns);
|
|
}
|
|
Frame_3D_Access::assign_datoviz_observation(frame, std::move(observation));
|
|
}
|
|
} // namespace detail
|
|
|
|
struct Render_Scene_3D::Private : Prev_Private, private detail::Scene_Datoviz_State {
|
|
/* 一次 render() 的异步执行租约;不保存、选择或复用 Frame Policy 槽位。 */
|
|
struct Render_Execution final {
|
|
Frame_3D* frame{}; /* 必填、非拥有借用;由 Frame Policy 保持至 completion 返回。 */
|
|
Completion completion{};
|
|
std::atomic_bool cpu_finished{};
|
|
std::atomic_bool backend_started{};
|
|
std::atomic_bool backend_finished{};
|
|
std::atomic_bool completion_started{};
|
|
std::chrono::steady_clock::time_point completion_observed_at{};
|
|
std::optional<detail::Gpu_Completion_Service::Result> completion_result{};
|
|
std::exception_ptr cpu_failure{};
|
|
std::exception_ptr backend_failure{};
|
|
std::exception_ptr collection_failure{};
|
|
std::optional<detail::Datoviz_Pending_Frame> datoviz_frame{};
|
|
std::shared_ptr<detail::Scene_Paint_Context> paint_context{};
|
|
};
|
|
|
|
using Shared_Render_Execution = std::shared_ptr<Render_Execution>;
|
|
|
|
Private(Camera_3D& camera_value, Axes_3D& axes_value, std::uint32_t gpu_index_value = 0, bool validation_enabled_value = false);
|
|
~Private() override = default;
|
|
Render_Result render(Frame_3D* frame, Completion completion);
|
|
private:
|
|
std::atomic_bool backend_available{true};
|
|
bool backend_initialized{};
|
|
std::uint32_t gpu_index{};
|
|
bool validation_enabled{};
|
|
proxy<scene::Camera> camera{}; /* 非拥有观察;Camera_3D 必须覆盖 Scene 生命周期。 */
|
|
proxy<scene::Axes> axes{}; /* 非拥有观察;Axes_3D 必须覆盖 Scene 生命周期。 */
|
|
std::vector<detail::Visual_Registration> registrations{};
|
|
|
|
[[nodiscard]] const Prop& advance_scene();
|
|
[[nodiscard]] detail::Scene_3D_Parameters current_parameters(const Prop& properties);
|
|
void initialize_or_validate_backend(const detail::Scene_3D_Parameters& parameters);
|
|
[[nodiscard]] proxy<scene::Task_Graph> build_frame_graph(const Shared_Render_Execution& execution, const detail::Scene_3D_Parameters& parameters);
|
|
void dispatch_events(Event_Batch& events) override;
|
|
void dispatch_datoviz(Event& event, Extent viewport);
|
|
void render_datoviz(const Shared_Render_Execution& execution, const detail::Prepared_Visual_Batch& visuals, detail::Scene_3D_Parameters parameters) noexcept;
|
|
void observe_datoviz_completion(const Shared_Render_Execution& execution, std::optional<detail::Gpu_Completion_Service::Result> result, std::exception_ptr failure) noexcept;
|
|
void try_complete(const Shared_Render_Execution& execution) noexcept;
|
|
[[nodiscard]] std::exception_ptr collect_datoviz(Render_Execution& execution) noexcept;
|
|
static void complete_execution(Shared_Render_Execution execution, std::exception_ptr runtime_failure);
|
|
void fail_datoviz(std::exception_ptr failure) noexcept;
|
|
};
|
|
|
|
inline Render_Scene_3D::Private::Private(Camera_3D& camera_value, Axes_3D& axes_value, std::uint32_t gpu_index_value, bool validation_enabled_value) : gpu_index(gpu_index_value), validation_enabled(validation_enabled_value), camera(model_proxy_view<scene::Camera>(camera_value)), axes(model_proxy_view<scene::Axes>(axes_value)) {}
|
|
|
|
inline const Render_Scene_3D::Prop& Render_Scene_3D::Private::advance_scene() {
|
|
auto& properties = this->template attachment<Prop_Tag>();
|
|
properties.internal.advance();
|
|
camera->advance();
|
|
axes->advance();
|
|
commit<aethera::scene::Renderable>();
|
|
auto& renderables = relation<aethera::scene::Renderable>();
|
|
renderables.for_each_topological([](Dag_Node_Id, proxy<scene::Renderable>& renderable) {
|
|
renderable->advance();
|
|
});
|
|
return static_cast<const Prop&>(*properties.internal.use());
|
|
}
|
|
|
|
inline detail::Scene_3D_Parameters Render_Scene_3D::Private::current_parameters(const Prop& properties) {
|
|
const auto axis = axes->use();
|
|
return {properties.viewport, properties.clear_color, camera->use(), axis[0], axis[1], axis[2]};
|
|
}
|
|
|
|
inline void Render_Scene_3D::Private::initialize_or_validate_backend(const detail::Scene_3D_Parameters& parameters) {
|
|
std::vector<detail::Visual_Registration> current;
|
|
auto& renderables = relation<aethera::scene::Renderable>();
|
|
renderables.for_each_topological([&](Dag_Node_Id id, proxy<scene::Renderable>& renderable) {
|
|
current.push_back({static_cast<detail::Visual_Identity>(id.value), std::addressof(renderable->use())});
|
|
});
|
|
if (current.empty()) throw std::invalid_argument("Render_Scene_3D requires at least one Visual");
|
|
if (backend_initialized) {
|
|
const bool same = current.size() == registrations.size() && std::ranges::equal(current, registrations, [](const auto& left, const auto& right) {
|
|
return left.identity == right.identity && left.operations == right.operations;
|
|
});
|
|
if (!same) throw std::logic_error("Render_Scene_3D Visual DAG cannot change after Datoviz initialization");
|
|
return;
|
|
}
|
|
detail::Scene_Datoviz_State::initialize(gpu_index, validation_enabled, current, parameters);
|
|
registrations = std::move(current);
|
|
backend_initialized = true;
|
|
}
|
|
|
|
inline proxy<scene::Task_Graph> Render_Scene_3D::Private::build_frame_graph(const Shared_Render_Execution& execution, const detail::Scene_3D_Parameters& parameters) {
|
|
execution->paint_context = std::make_shared<detail::Scene_Paint_Context>();
|
|
execution->paint_context->parameters = parameters;
|
|
execution->paint_context->visuals.reserve(registrations.size());
|
|
for (const auto& registration : registrations) execution->paint_context->visuals.push_back({registration.identity, {}});
|
|
auto graph = scene::make_task_graph("render_3d.frame");
|
|
auto begin = graph->add("scene.begin", [execution] {
|
|
execution->frame->mark(Frame_Trace_Marker::scene_render_started);
|
|
execution->frame->mark(Frame_Trace_Marker::prepare_started);
|
|
execution->frame->mark(Frame_Trace_Marker::prepare_finished);
|
|
execution->frame->mark(Frame_Trace_Marker::paint_started);
|
|
});
|
|
if (!begin) throw std::logic_error("failed to create 3D frame setup task");
|
|
auto& renderables = relation<aethera::scene::Renderable>();
|
|
std::unordered_map<std::uint64_t, proxy<task_flow::Task_Node>> nodes;
|
|
std::size_t slot{};
|
|
renderables.for_each_topological([&](Dag_Node_Id id, proxy<scene::Renderable>& renderable) {
|
|
renderable->render(execution->paint_context, slot++, &detail::publish_visual);
|
|
auto component = renderable->taskflow();
|
|
auto composed = graph->compose("renderable." + std::to_string(id.value), component);
|
|
if (!composed) throw std::logic_error("failed to compose a 3D Renderable task graph");
|
|
if (graph->precede(*begin, *composed) != Precede_Task_Graph_Result::preceded) throw std::logic_error("failed to order 3D frame setup before a Renderable");
|
|
nodes.emplace(id.value, std::move(*composed));
|
|
});
|
|
renderables.for_each_dependency([&](Dag_Node_Id target, Dag_Node_Id source) {
|
|
if (graph->precede(nodes.at(source.value), nodes.at(target.value)) != Precede_Task_Graph_Result::preceded) throw std::logic_error("failed to preserve a 3D Renderable DAG dependency");
|
|
});
|
|
auto backend = graph->add("scene.backend.prepare", [this, execution, paint = execution->paint_context] {
|
|
if (!detail::prepared_visual_batch_complete(paint->visuals)) throw std::logic_error("3D Scene published an incomplete Visual batch");
|
|
render_datoviz(execution, paint->visuals, paint->parameters);
|
|
});
|
|
if (!backend) throw std::logic_error("failed to create 3D backend task");
|
|
for (auto& [id, node] : nodes) if (graph->precede(node, *backend) != Precede_Task_Graph_Result::preceded) throw std::logic_error("failed to order a 3D Renderable before backend submission");
|
|
dispatch_collected_events();
|
|
return graph;
|
|
}
|
|
|
|
inline void Render_Scene_3D::Private::dispatch_events(Event_Batch& events) {
|
|
auto& renderables = relation<aethera::scene::Renderable>();
|
|
aethera::detail::dispatch_scene_events<scene::Renderable>(events, renderables);
|
|
const auto& properties = static_cast<const Prop&>(*this->template attachment<Prop_Tag>().internal.use());
|
|
for (auto& event : events) if (event) dispatch_datoviz(*event, properties.viewport);
|
|
}
|
|
|
|
inline void Render_Scene_3D::Private::dispatch_datoviz(Event& event, Extent viewport) {
|
|
if (event.is_accepted()) return;
|
|
const auto* pointer = dynamic_cast<const Basic_Pointer_Event<Vec2>*>(std::addressof(event));
|
|
const auto* wheel = dynamic_cast<const Basic_Wheel_Event<Vec2>*>(std::addressof(event));
|
|
const auto* key = dynamic_cast<const Key_Event*>(std::addressof(event));
|
|
const bool pointer_valid = pointer && std::isfinite(pointer->position.x) && std::isfinite(pointer->position.y) && pointer->position.x >= 0.0F && pointer->position.y >= 0.0F && pointer->position.x <= static_cast<float>(viewport.width) && pointer->position.y <= static_cast<float>(viewport.height);
|
|
if (wheel && pointer_valid) {
|
|
dispatch_wheel(pointer->position.x, pointer->position.y, detail::wheel_step(wheel->pixel_delta_x, wheel->angle_delta_x), detail::wheel_step(wheel->pixel_delta_y, wheel->angle_delta_y), pointer->modifiers, viewport, event.occurred_at.nanoseconds);
|
|
}
|
|
else if (pointer_valid && (event.type == Event_Type::pointer_move || event.type == Event_Type::pointer_press || event.type == Event_Type::pointer_release)) {
|
|
const auto button = event.type == Event_Type::pointer_move ? detail::held_button(pointer->buttons) : pointer->button;
|
|
dispatch_pointer(event.type, pointer->position.x, pointer->position.y, button, pointer->modifiers, viewport, event.occurred_at.nanoseconds);
|
|
}
|
|
else if (key) {
|
|
dispatch_key(*key);
|
|
}
|
|
if ((wheel && pointer_valid) || pointer_valid || key) event.accept();
|
|
}
|
|
|
|
inline void Render_Scene_3D::Private::render_datoviz(const Shared_Render_Execution& execution, const detail::Prepared_Visual_Batch& visuals, detail::Scene_3D_Parameters parameters) noexcept {
|
|
execution->backend_started.store(true, std::memory_order_release);
|
|
try {
|
|
if (visuals.empty()) throw std::invalid_argument("3D Scene requires prepared Visual data");
|
|
if (!backend_available.load(std::memory_order_acquire)) throw std::runtime_error("3D Scene resources are unavailable");
|
|
auto* frame = execution->frame;
|
|
auto reservation = detail::Gpu_Completion_Service::instance().prepare([this, execution](detail::Gpu_Completion_Service::Result result) {
|
|
observe_datoviz_completion(execution, std::move(result), {});
|
|
}, [this, execution](std::exception_ptr failure) {
|
|
observe_datoviz_completion(execution, {}, std::move(failure));
|
|
}, frame->taskflow_trace_requested());
|
|
if (!reservation) throw std::runtime_error("GPU completion capacity is exhausted");
|
|
const auto sequence = frame->identity().sequence;
|
|
const bool observe = frame->taskflow_trace_requested();
|
|
const bool readback = frame->output() == Frame_3D_Output::pixels;
|
|
frame->mark(Frame_Trace_Marker::backend_prepare_started);
|
|
auto prepared = reuse_recorded_target(parameters, visuals, sequence, observe, readback);
|
|
if (!prepared) prepared = prepare(parameters, visuals, sequence, observe, readback);
|
|
if (!prepared) throw std::logic_error("Datoviz did not provide a frame target after Scene admission");
|
|
frame->mark(Frame_Trace_Marker::backend_prepare_finished);
|
|
execution->datoviz_frame.emplace(std::move(*prepared));
|
|
execution->datoviz_frame->observation.prepare_released_after_submission = false;
|
|
frame->mark(Frame_Trace_Marker::backend_queue_entered);
|
|
frame->mark(Frame_Trace_Marker::backend_submit_queued);
|
|
auto shared_reservation = std::make_shared<detail::Gpu_Completion_Service::Reservation>(std::move(reservation.reservation));
|
|
submit(*execution->datoviz_frame, [this, execution, frame, shared_reservation = std::move(shared_reservation)](std::exception_ptr submission_failure) mutable {
|
|
try {
|
|
frame->mark(Frame_Trace_Marker::backend_queue_left);
|
|
if (submission_failure) {
|
|
fail_datoviz(submission_failure);
|
|
if (execution->datoviz_frame) {
|
|
discard(std::move(*execution->datoviz_frame));
|
|
execution->datoviz_frame.reset();
|
|
}
|
|
execution->backend_failure = std::move(submission_failure);
|
|
execution->backend_finished.store(true, std::memory_order_release);
|
|
try_complete(execution);
|
|
return;
|
|
}
|
|
frame->mark(Frame_Trace_Marker::gpu_submitted);
|
|
shared_reservation->watch(execution->datoviz_frame->device, execution->datoviz_frame->fence);
|
|
}
|
|
catch (...) {
|
|
observe_datoviz_completion(execution, {}, std::current_exception());
|
|
}
|
|
});
|
|
}
|
|
catch (...) {
|
|
auto failure = std::current_exception();
|
|
fail_datoviz(failure);
|
|
if (execution->datoviz_frame) {
|
|
discard(std::move(*execution->datoviz_frame));
|
|
execution->datoviz_frame.reset();
|
|
}
|
|
execution->backend_failure = std::move(failure);
|
|
execution->backend_finished.store(true, std::memory_order_release);
|
|
try_complete(execution);
|
|
}
|
|
}
|
|
|
|
inline void Render_Scene_3D::Private::observe_datoviz_completion(const Shared_Render_Execution& execution, std::optional<detail::Gpu_Completion_Service::Result> result, std::exception_ptr failure) noexcept {
|
|
execution->frame->mark(Frame_Trace_Marker::gpu_completed);
|
|
execution->completion_result = std::move(result);
|
|
execution->backend_failure = std::move(failure);
|
|
execution->completion_observed_at = std::chrono::steady_clock::now();
|
|
execution->backend_finished.store(true, std::memory_order_release);
|
|
try_complete(execution);
|
|
}
|
|
|
|
inline void Render_Scene_3D::Private::try_complete(const Shared_Render_Execution& execution) noexcept {
|
|
if (!execution->cpu_finished.load(std::memory_order_acquire) || !execution->backend_finished.load(std::memory_order_acquire)) return;
|
|
if (execution->completion_started.exchange(true, std::memory_order_acq_rel)) return;
|
|
try {
|
|
auto graph = scene::make_task_graph("render_3d.completion");
|
|
auto task = graph->add("scene.collect", [this, execution] {
|
|
execution->collection_failure = collect_datoviz(*execution);
|
|
});
|
|
if (!task) throw std::logic_error("failed to create 3D completion task");
|
|
const auto submitted = detail::Frame_3D_Access::run_taskflow(execution->frame, graph, [execution](std::exception_ptr runtime_failure) mutable {
|
|
complete_execution(std::move(execution), std::move(runtime_failure));
|
|
});
|
|
if (submitted != Run_Taskflow_Result::submitted) throw std::runtime_error("3D completion task runtime is unavailable");
|
|
}
|
|
catch (...) {
|
|
fail_datoviz(std::current_exception());
|
|
}
|
|
}
|
|
|
|
inline std::exception_ptr Render_Scene_3D::Private::collect_datoviz(Render_Execution& execution) noexcept {
|
|
if (execution.backend_failure) return execution.backend_failure;
|
|
try {
|
|
if (!execution.datoviz_frame) throw std::logic_error("3D completion lost its Datoviz target");
|
|
const auto completion_started = std::chrono::steady_clock::now();
|
|
execution.frame->mark(Frame_Trace_Marker::readback_started);
|
|
auto& observation = execution.datoviz_frame->observation;
|
|
if (execution.completion_result) observation.gpu_completion_observation_ns = execution.completion_result->wait_duration_ns;
|
|
observation.completion_task_queue_ns = static_cast<std::uint64_t>(std::max<std::int64_t>(0, std::chrono::duration_cast<std::chrono::nanoseconds>(completion_started - execution.completion_observed_at).count()));
|
|
if (!execution.completion_result || execution.completion_result->error != detail::Gpu_Completion_Service::Completion_Error::none) throw std::runtime_error("Datoviz GPU completion failed");
|
|
auto completed = collect(std::move(*execution.datoviz_frame));
|
|
execution.datoviz_frame.reset();
|
|
execution.frame->mark(Frame_Trace_Marker::readback_finished);
|
|
detail::publish_datoviz_observation(execution.frame, std::move(completed.observation));
|
|
std::array outputs{execution.frame};
|
|
detail::Frame_3D_Access::assign_pixels(outputs, completed.extent, std::move(completed.pixels), execution.frame->identity());
|
|
return {};
|
|
}
|
|
catch (...) {
|
|
auto failure = std::current_exception();
|
|
fail_datoviz(failure);
|
|
if (execution.datoviz_frame) {
|
|
quarantine(std::move(*execution.datoviz_frame));
|
|
execution.datoviz_frame.reset();
|
|
}
|
|
return failure;
|
|
}
|
|
}
|
|
|
|
inline void Render_Scene_3D::Private::complete_execution(Shared_Render_Execution execution, std::exception_ptr runtime_failure) {
|
|
auto* frame = execution->frame;
|
|
auto callback = std::move(execution->completion);
|
|
auto failure = execution->cpu_failure ? std::move(execution->cpu_failure) : std::move(execution->backend_failure);
|
|
if (!failure) failure = std::move(execution->collection_failure);
|
|
if (!failure) failure = std::move(runtime_failure);
|
|
frame->mark(Frame_Trace_Marker::paint_finished);
|
|
frame->mark(Frame_Trace_Marker::scene_render_finished);
|
|
frame->mark(Frame_Trace_Marker::frame_ready);
|
|
execution->frame = nullptr;
|
|
execution->datoviz_frame.reset();
|
|
execution->paint_context.reset();
|
|
execution->completion_result.reset();
|
|
execution->backend_failure = {};
|
|
execution->collection_failure = {};
|
|
callback(frame, std::move(failure));
|
|
}
|
|
|
|
inline void Render_Scene_3D::Private::fail_datoviz(std::exception_ptr failure) noexcept {
|
|
backend_available.store(false, std::memory_order_release);
|
|
try {
|
|
if (failure) std::rethrow_exception(std::move(failure));
|
|
}
|
|
catch (const std::exception& exception) {
|
|
std::fprintf(stderr, "Render_Scene_3D backend failure: %s\n", exception.what());
|
|
}
|
|
catch (...) {}
|
|
}
|
|
|
|
inline Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Frame_3D* frame, Completion completion) {
|
|
if (!frame) return Render_Result::frame_missing;
|
|
if (!completion) return Render_Result::completion_missing;
|
|
frame->mark(Frame_Trace_Marker::scene_render_entered);
|
|
try {
|
|
frame->mark(Frame_Trace_Marker::scene_advance_started);
|
|
const Prop& properties = advance_scene();
|
|
frame->mark(Frame_Trace_Marker::scene_advance_finished);
|
|
if (!properties.view_active) return Render_Result::view_inactive;
|
|
if (properties.viewport.empty()) return Render_Result::empty_viewport;
|
|
if (!backend_available.load(std::memory_order_acquire)) return Render_Result::backend_unavailable;
|
|
const auto parameters = current_parameters(properties);
|
|
initialize_or_validate_backend(parameters);
|
|
auto execution = std::make_shared<Render_Execution>();
|
|
execution->frame = frame;
|
|
execution->completion = std::move(completion);
|
|
auto graph = build_frame_graph(execution, parameters);
|
|
frame->mark(Frame_Trace_Marker::scene_render_requested);
|
|
auto completed = [this, execution](std::exception_ptr failure) {
|
|
execution->cpu_failure = std::move(failure);
|
|
if (execution->cpu_failure && !execution->backend_started.load(std::memory_order_acquire)) execution->backend_finished.store(true, std::memory_order_release);
|
|
execution->cpu_finished.store(true, std::memory_order_release);
|
|
try_complete(execution);
|
|
};
|
|
const auto submitted = detail::Frame_3D_Access::run_taskflow(frame, graph, std::move(completed));
|
|
if (submitted != Run_Taskflow_Result::submitted) return Render_Result::backend_unavailable;
|
|
return Render_Result::submitted;
|
|
}
|
|
catch (...) {
|
|
fail_datoviz(std::current_exception());
|
|
return Render_Result::backend_unavailable;
|
|
}
|
|
}
|
|
} // namespace aethera::render_3d
|