#include #include "Gallery_Scene3D.h" #include "../Gallery_Capture_Json.h" #include "../Gallery_Enum.h" #include "../common/Gallery_Scheduler_Adminive.h" #include "../common/Pixel_Frame.h" #include #include "adminive/adminive.hpp" #include "adminive/adapters/nlohmann_json.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace adminive { template struct Value_Adapter< renderive::render_3d::Renderable_Observer_Event, Json> { using value_type = std::string; static std::string read( renderive::render_3d::Renderable_Observer_Event value) { return renderive::web::gallery_enum_id(value); } static void write( renderive::render_3d::Renderable_Observer_Event& target, std::string value) { const auto parsed = renderive::web::gallery_enum_cast< renderive::render_3d::Renderable_Observer_Event>(value); if (!parsed) ::renderive::error::unexpected("unknown 3D observer event"); target = *parsed; } }; template <> struct Type_Descriptor { static auto get() { using T = renderive::render_3d::Renderable_Observation; return object( "renderable_3d_observer", ADMINIVE_FIELD_LABEL(T, event, "事件"), ADMINIVE_FIELD_LABEL(T, event_time_ns, "事件时间"), ADMINIVE_FIELD_LABEL(T, cache_update_count, "缓存更新"), ADMINIVE_FIELD_LABEL(T, publish_count, "发布"), ADMINIVE_FIELD_LABEL(T, data_revision, "数据版本"), ADMINIVE_FIELD_LABEL(T, item_count, "项目数")); } }; } // namespace adminive namespace renderive::web { namespace { using namespace renderive::render_3d; Gallery_Render_Result gallery_render_error(Frame_Request_Result error) { switch (error) { case Frame_Request_Result::none: return Gallery_Render_Result::none; case Frame_Request_Result::cancelled: return Gallery_Render_Result::cancelled; case Frame_Request_Result::deadline_exceeded: return Gallery_Render_Result::deadline_exceeded; case Frame_Request_Result::external_failure: return Gallery_Render_Result::backend_failure; case Frame_Request_Result::scene_shutting_down: return Gallery_Render_Result::shutting_down; default: return Gallery_Render_Result::frame_unavailable; } } Gallery_Render_Result gallery_render_error(Scene_Render_Result error) { switch (error) { case Scene_Render_Result::none: return Gallery_Render_Result::none; case Scene_Render_Result::cancelled: return Gallery_Render_Result::cancelled; case Scene_Render_Result::deadline_exceeded: return Gallery_Render_Result::deadline_exceeded; case Scene_Render_Result::external_failure: return Gallery_Render_Result::backend_failure; case Scene_Render_Result::shutting_down: return Gallery_Render_Result::shutting_down; } ::renderive::error::unexpected("unknown scene render error"); } Frame_Request_Result frame_request_error(Scene_Render_Result error) { switch (error) { case Scene_Render_Result::none: return Frame_Request_Result::none; case Scene_Render_Result::cancelled: return Frame_Request_Result::cancelled; case Scene_Render_Result::deadline_exceeded: return Frame_Request_Result::deadline_exceeded; case Scene_Render_Result::external_failure: return Frame_Request_Result::external_failure; case Scene_Render_Result::shutting_down: return Frame_Request_Result::scene_shutting_down; } ::renderive::error::unexpected("unknown scene render error"); } struct Gallery_Point { double x{}; double y{}; }; Frame_Mode render_frame_mode(Gallery_Frame_Mode mode) { switch (mode) { case Gallery_Frame_Mode::Manual: return Frame_Mode::Manual; case Gallery_Frame_Mode::Low_Latency: return Frame_Mode::Low_Latency; case Gallery_Frame_Mode::Playback: return Frame_Mode::Playback; } ::renderive::error::unexpected("unknown gallery frame mode"); } std::string mode_id(Gallery_Frame_Mode mode) { switch (mode) { case Gallery_Frame_Mode::Manual: return "manual"; case Gallery_Frame_Mode::Low_Latency: return "low_latency"; case Gallery_Frame_Mode::Playback: return "playback"; } return "unknown"; } ::renderive::Event_Type event_type(Gallery_Pointer_Input_Type type) { switch (type) { case Gallery_Pointer_Input_Type::Move: return ::renderive::Event_Type::Pointer_Move; case Gallery_Pointer_Input_Type::Press: return ::renderive::Event_Type::Pointer_Press; case Gallery_Pointer_Input_Type::Release: return ::renderive::Event_Type::Pointer_Release; } return ::renderive::Event_Type::Pointer_Move; } ::renderive::Mouse_Button mouse_button(Gallery_Mouse_Button button) { switch (button) { case Gallery_Mouse_Button::Left: return ::renderive::Mouse_Button::Left; case Gallery_Mouse_Button::Right: return ::renderive::Mouse_Button::Right; case Gallery_Mouse_Button::Middle: return ::renderive::Mouse_Button::Middle; case Gallery_Mouse_Button::None: return ::renderive::Mouse_Button::None; } return ::renderive::Mouse_Button::None; } ::renderive::Key key(Gallery_Key value) { switch (value) { case Gallery_Key::Escape: return ::renderive::Key::Escape; case Gallery_Key::Enter: return ::renderive::Key::Enter; case Gallery_Key::Space: return ::renderive::Key::Space; case Gallery_Key::Delete: return ::renderive::Key::Delete; case Gallery_Key::Backspace: return ::renderive::Key::Backspace; case Gallery_Key::Left: return ::renderive::Key::Left; case Gallery_Key::Right: return ::renderive::Key::Right; case Gallery_Key::Up: return ::renderive::Key::Up; case Gallery_Key::Down: return ::renderive::Key::Down; case Gallery_Key::Unknown: return ::renderive::Key::Unknown; } return ::renderive::Key::Unknown; } Visual_Family visual_family(std::string_view case_id) { if (case_id == "datoviz_splat") return Visual_Family::Splat; if (case_id == "datoviz_pixel") return Visual_Family::Pixel; if (case_id == "datoviz_marker") return Visual_Family::Marker; if (case_id == "datoviz_sphere") return Visual_Family::Sphere; if (case_id == "datoviz_segment") return Visual_Family::Segment; if (case_id == "datoviz_vector") return Visual_Family::Vector; if (case_id == "datoviz_primitive") return Visual_Family::Primitive; if (case_id == "datoviz_mesh") return Visual_Family::Mesh; if (case_id == "datoviz_path") return Visual_Family::Path; if (case_id == "datoviz_image") return Visual_Family::Image; if (case_id == "datoviz_labels") return Visual_Family::Labels; if (case_id == "datoviz_glyph") return Visual_Family::Glyph; if (case_id == "datoviz_text") return Visual_Family::Text; if (case_id == "datoviz_volume") return Visual_Family::Volume; return Visual_Family::Point; } std::optional action_number( const Gallery_Action_Request& request) { if (!request.argument || !std::holds_alternative(*request.argument)) return std::nullopt; return std::get(*request.argument); } class Gallery_Scene3D final : public Gallery_Scene_Interface { public: Gallery_Scene3D(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) : session_id_(session_id), case_id_(std::move(case_id)), frame_mode_(mode), automatic_low_latency_(automatic_low_latency), visual_(Point_Visual::Builder{}.build(initial_points())), scene_(Scene_Options{.viewport = {560, 320}, .frame_mode = render_frame_mode(mode), .maximum_frames_per_second = 30.0, .visual_family = visual_family(case_id_)}, visual_) { if (!visual_) ::renderive::error::unexpected("failed to create Point_Visual"); visual_->set_object_name("3D Visual"); render_initial_frame(); } [[nodiscard]] const std::string& case_id() const noexcept override { return case_id_; } [[nodiscard]] nlohmann::json controls() const override { const auto observation = visual_->observation(); return {{"resources", nlohmann::json::array()}, {"observers", nlohmann::json::array({ {{"descriptor", adminive::to_descriptor_json< nlohmann::json, Renderable_Observation>()}, {"data", adminive::to_frontend_json( observation)}, {"target", "point-visual"}, {"title", "3D Visual"}}})}, {"render_plan", gallery_render_plan_json(scene_.render_scene())}, {"performance_capture", gallery_performance_capture_json(scene_.render_scene())}}; } [[nodiscard]] Gallery_Frame_Mode frame_mode() const noexcept override { return frame_mode_; } void reset_monitoring() override { render_attempt_count_ = 0; successful_render_count_ = 0; pixel_frame_count_ = 0; last_render_ms_ = 0.0; last_pixel_encode_ms_ = 0.0; render_history_.clear(); pixel_history_.clear(); last_event_ = "monitoring_reset"; } [[nodiscard]] bool can_render_automatically() const noexcept override { return active_ && automatic_low_latency_ && frame_mode_ == Gallery_Frame_Mode::Low_Latency; } [[nodiscard]] std::uint64_t kernel_refresh_interval_ns() const override { return scene_.frame_status().next_refresh_interval_ns; } void set_client_metrics(Gallery_Client_Performance metrics) noexcept override { client_performance_ = metrics; } [[nodiscard]] adminive::Update_Result apply_patch( std::string_view, const nlohmann::json&) override { adminive::Update_Result result; result.success = false; result.message = "Render3D gallery has no patchable controls"; return result; } void resize(int width, int height) override { if (width <= 0 || height <= 0) return; const auto error = scene_.resize({static_cast(width), static_cast(height)}); if (error != ::renderive::render_3d::Scene_Control_Error::none) { last_event_ = "resize_rejected"; return; } last_event_ = "resized"; } void dispatch(const Gallery_Input_Event& event) override { if (const auto* view = std::get_if(&event)) { if (view->type == Gallery_View_Input_Type::Show) active_ = true; else if (view->type == Gallery_View_Input_Type::Hide) active_ = false; return; } if (const auto* pointer = std::get_if(&event)) { ::renderive::Basic_Pointer_Event input( event_type(pointer->type)); input.position = {pointer->x, pointer->y}; input.global_position = {pointer->global_x, pointer->global_y}; input.button = mouse_button(pointer->button); input.buttons = pointer->buttons; input.modifiers = static_cast<::renderive::Keyboard_Modifier>( pointer->modifiers); if (scene_.dispatch(input) != ::renderive::render_3d::Scene_Control_Error::none) last_event_ = "pointer_rejected"; return; } if (const auto* wheel = std::get_if(&event)) { ::renderive::Basic_Wheel_Event input; input.position = {wheel->x, wheel->y}; input.global_position = {wheel->global_x, wheel->global_y}; input.pixel_delta_x = wheel->pixel_delta_x; input.pixel_delta_y = wheel->pixel_delta_y; input.angle_delta_x = wheel->angle_delta_x; input.angle_delta_y = wheel->angle_delta_y; input.modifiers = static_cast<::renderive::Keyboard_Modifier>( wheel->modifiers); if (scene_.dispatch(input) != ::renderive::render_3d::Scene_Control_Error::none) last_event_ = "wheel_rejected"; return; } const auto& keyboard = std::get(event); ::renderive::Key_Event input( keyboard.type == Gallery_Key_Input_Type::Release ? ::renderive::Event_Type::Key_Release : ::renderive::Event_Type::Key_Press); input.key = key(keyboard.key); input.native_key = keyboard.native_key; input.modifiers = static_cast<::renderive::Keyboard_Modifier>( keyboard.modifiers); input.auto_repeat = keyboard.auto_repeat; if (scene_.dispatch(input) != ::renderive::render_3d::Scene_Control_Error::none) last_event_ = "key_rejected"; } Gallery_Render_Result render_latest_frame() override { if (!can_render_automatically()) return Gallery_Render_Result::inactive; advance_blue_point(0.025F); const auto request_error = render_request(); return gallery_render_error(request_error); } [[nodiscard]] std::optional encode_latest_pixels() override { if (!active_) return std::nullopt; const auto frame = scene_.latest_frame(); if (!frame || frame->rgba8.empty()) return std::nullopt; return encode_rgba8_pixel_frame( frame->rgba8.data(), frame->extent.width, frame->extent.height, frame->extent.width * 4U, frame->sequence); } void record_pixel_response( std::chrono::steady_clock::time_point, std::chrono::steady_clock::time_point encode_started, std::chrono::steady_clock::time_point encode_finished, std::size_t pixel_bytes) override { ++pixel_frame_count_; last_pixel_bytes_ = pixel_bytes; last_pixel_encode_ms_ = std::chrono::duration( encode_finished - encode_started) .count(); record_sample(pixel_history_, encode_finished); } [[nodiscard]] std::string action(const Gallery_Action_Request& request, bool& recognized) override { recognized = true; if (request.id == "capture_next_frame") { auto& scene = scene_.render_scene(); if (scene.capture_state().enabled()) return "A performance capture session is already active"; const auto capture = scene.capture_next_frame(); if (!capture) { if (capture.error == Capture_Error::session_active) return "A performance capture session is already active"; ::renderive::error::unexpected("validated capture request rejected"); } last_action_result_ = "capture session=" + std::to_string(capture.session_id); return "Next 3D frame capture requested"; } if (request.id == "capture_frames") { const auto argument = action_number(request); if (!argument) { recognized = false; return {}; } const std::size_t count = static_cast( std::clamp(std::llround(*argument), 1LL, 120LL)); auto& scene = scene_.render_scene(); if (scene.capture_state().enabled()) return "A performance capture session is already active"; const auto capture = scene.capture_frames(count); if (!capture) { if (capture.error == Capture_Error::session_active) return "A performance capture session is already active"; ::renderive::error::unexpected("validated capture request rejected"); } last_action_result_ = "capture session=" + std::to_string(capture.session_id) + " frames=" + std::to_string(count); return "Consecutive 3D frame capture requested"; } if (request.id == "mode_prepare" || request.id == "mode_enqueue") { if (frame_mode_ == Gallery_Frame_Mode::Playback) { blue_offset_ = std::min(0.25F, blue_offset_ + 0.0125F); publish_points(); } const bool prepared = scene_.prepare_frame() == Frame_Request_Result::none; last_event_ = prepared ? "frame_prepared" : "prepare_rejected"; return prepared ? "3D frame prepared" : "3D frame prepare rejected"; } if (request.id == "mode_refresh") { const bool refreshed = scene_.refresh_manual_frame() == Frame_Request_Result::none; last_event_ = refreshed ? "manual_refreshed" : "manual_refresh_failed"; return refreshed ? "Manual 3D frame refreshed" : "No manual frame to refresh"; } if (request.id == "mode_render" || request.id == "mode_dequeue") { const bool rendered = render_prepared() == Frame_Request_Result::none; last_event_ = rendered ? "frame_rendered" : "render_rejected"; return rendered ? "3D frame rendered" : "No 3D frame to render"; } if (request.id == "move_mesh") { advance_blue_point(0.08F); last_event_ = "mesh_moved"; return "blue point moved"; } if (request.id == "add_mesh") { extra_attached_ = true; publish_points(); last_event_ = "mesh_added"; return "yellow point added"; } if (request.id == "remove_mesh") { extra_attached_ = false; publish_points(); last_event_ = "mesh_removed"; return "yellow point removed"; } if (request.id == "mesh_churn") { last_event_ = "mesh_churned"; return "point attach/detach lifecycle completed"; } if (request.id == "reset" || request.id == "reset_camera") { blue_offset_ = 0.0F; projection_scale_ = 1.0F; visual_->set<&Point_State::transform>(transform()); publish_points(); last_event_ = "camera_reset"; return "3D view reset"; } if (request.id == "toggle_projection") { projection_scale_ = projection_scale_ == 1.0F ? 0.72F : 1.0F; visual_->set<&Point_State::transform>(transform()); last_event_ = "projection_toggled"; return "3D projection toggled"; } if (request.id == "toggle_texture") { texture_enabled_ = !texture_enabled_; publish_points(); last_event_ = "texture_toggled"; return texture_enabled_ ? "point pattern enabled" : "point pattern disabled"; } if (request.id == "change_text") { alternate_text_ = !alternate_text_; publish_points(); last_event_ = "text_changed"; return "point annotation representation changed"; } recognized = false; return {}; } void record_action_notice_if_empty(std::string_view notice) override { if (last_action_result_.empty()) last_action_result_ = std::string(notice); } [[nodiscard]] std::string telemetry_json() const override { const auto status = scene_.frame_status(); const auto now = std::chrono::steady_clock::now(); const auto scene_execution = scene_.render_scene().execution_statistics(); const auto runtime = scene_.runtime_statistics(); return nlohmann::json{ {"kernel_observer", {{"mode", mode_id(frame_mode_)}, {"last_event", last_event_}, {"pending_frame_count", status.pending_frame_count}, {"dropped_frame_count", status.dropped_frame_count}}}, {"performance", {{"render_attempt_count", render_attempt_count_}, {"successful_render_count", successful_render_count_}, {"failed_render_count", render_attempt_count_ - successful_render_count_}, {"measured_fps", recent_rate(render_history_, now)}, {"last_render_ms", last_render_ms_}, {"pixel_response_fps", recent_rate(pixel_history_, now)}, {"last_pixel_encode_ms", last_pixel_encode_ms_}, {"last_pixel_bytes", last_pixel_bytes_}, {"automatic_low_latency_scheduler", can_render_automatically()}}}, {"client_performance", {{"transport_fps", client_performance_.transport_fps}, {"presentation_fps", client_performance_.presentation_fps}, {"websocket_buffered_bytes", client_performance_.websocket_buffered_bytes}}}, {"scheduler", adminive::to_frontend_json( renderive::scheduling::scheduler_statistics())}, {"queue_pressure", {{"scene_execution", {{"capacity", scene_execution.capacity}, {"queued", scene_execution.queued}, {"peak_queued", scene_execution.peak_queued}, {"backpressure_count", scene_execution.backpressure_count}, {"backpressure_wait_ns", scene_execution.backpressure_wait_ns}}}, {"render_domain", {{"capacity", runtime.render_domain.capacity}, {"active", runtime.render_domain.active}, {"peak_active", runtime.render_domain.peak_active}, {"queued", runtime.render_domain.queued}, {"peak_queued", runtime.render_domain.peak_queued}, {"backpressure_count", runtime.render_domain.backpressure_count}, {"backpressure_wait_ns", runtime.render_domain.backpressure_wait_ns}}}, {"gpu_completion", {{"capacity", runtime.gpu_completion.capacity}, {"active", runtime.gpu_completion.active}, {"peak_active", runtime.gpu_completion.peak_active}, {"queued", runtime.gpu_completion.queued}, {"peak_queued", runtime.gpu_completion.peak_queued}, {"backpressure_count", runtime.gpu_completion.backpressure_count}, {"backpressure_wait_ns", runtime.gpu_completion.backpressure_wait_ns}, {"fault_count", runtime.gpu_completion_fault_count}, {"abandoned_count", runtime.gpu_completion_abandoned_count}}}}}, {"session_id", session_id_}, {"performance_capture", gallery_performance_capture_json(scene_.render_scene())}, {"last_action", last_action_result_}} .dump(); } private: using Performance_Clock = std::chrono::steady_clock; static void record_sample(std::deque& samples, Performance_Clock::time_point now) { samples.push_back(now); const auto cutoff = now - std::chrono::seconds(2); while (!samples.empty() && samples.front() < cutoff) samples.pop_front(); } static double recent_rate( const std::deque& samples, Performance_Clock::time_point now) { const auto cutoff = now - std::chrono::seconds(1); const auto first = std::find_if(samples.begin(), samples.end(), [cutoff](auto sample) { return sample >= cutoff; }); const auto count = std::distance(first, samples.end()); if (count < 2) return 0.0; const double seconds = std::chrono::duration(samples.back() - *first).count(); return seconds > 0.0 ? static_cast(count - 1) / seconds : 0.0; } static std::vector initial_points() { return {{{-0.62F, -0.10F, 0.0F}, {255, 32, 24, 255}, 74.0F}, {{0.0F, -0.10F, 0.0F}, {24, 255, 36, 255}, 74.0F}, {{0.62F, -0.10F, 0.0F}, {28, 52, 255, 255}, 74.0F}}; } Matrix4 transform() const { Matrix4 value; value.values[0] = projection_scale_; value.values[5] = projection_scale_; return value; } void publish_points() { auto points = initial_points(); points[2].position.x += blue_offset_; if (!texture_enabled_) points[0].color = {255, 128, 128, 255}; if (alternate_text_) { points[1].diameter_px = 46.0F; points[1].color = {24, 220, 255, 255}; } if (extra_attached_) points.push_back({{0.0F, 0.62F, 0.0F}, {255, 230, 20, 255}, 58.0F}); if (visual_->update_points(std::move(points)) != Visual_Data_Error::none) ::renderive::error::unexpected("generated gallery point data is invalid"); } void advance_blue_point(float step) { blue_offset_ += step; if (blue_offset_ > 0.32F) blue_offset_ = -0.32F; publish_points(); } void render_initial_frame() { Frame_Request_Result error{Frame_Request_Result::none}; if (frame_mode_ == Gallery_Frame_Mode::Manual) { if (scene_.prepare_frame() == Frame_Request_Result::none && scene_.refresh_manual_frame() == Frame_Request_Result::none) error = render_prepared(); else error = Frame_Request_Result::no_pending_frame; } else { error = render_request(); } if (error != Frame_Request_Result::none) last_action_result_ = "initial frame is not available"; } Frame_Request_Result render_request() { const auto started = std::chrono::steady_clock::now(); ++render_attempt_count_; auto error = scene_.request_frame(); if (error == Frame_Request_Result::none) error = frame_request_error(scene_.render_scene().wait_for_render()); const bool rendered = error == Frame_Request_Result::none; last_render_ms_ = std::chrono::duration( std::chrono::steady_clock::now() - started) .count(); successful_render_count_ += rendered ? 1U : 0U; if (rendered) record_sample(render_history_, std::chrono::steady_clock::now()); return error; } Frame_Request_Result render_prepared() { const auto started = std::chrono::steady_clock::now(); ++render_attempt_count_; auto error = scene_.render_prepared_frame(); if (error == Frame_Request_Result::none) error = frame_request_error(scene_.render_scene().wait_for_render()); const bool rendered = error == Frame_Request_Result::none; last_render_ms_ = std::chrono::duration( std::chrono::steady_clock::now() - started) .count(); successful_render_count_ += rendered ? 1U : 0U; if (rendered) record_sample(render_history_, std::chrono::steady_clock::now()); return error; } std::uint64_t session_id_{}; std::string case_id_; Gallery_Frame_Mode frame_mode_{Gallery_Frame_Mode::Low_Latency}; bool automatic_low_latency_{}; bool active_{true}; std::shared_ptr visual_; Render_Scene_3D scene_; Gallery_Client_Performance client_performance_; float blue_offset_{}; float projection_scale_{1.0F}; bool extra_attached_{}; bool texture_enabled_{true}; bool alternate_text_{}; std::uint64_t render_attempt_count_{}; std::uint64_t successful_render_count_{}; std::uint64_t pixel_frame_count_{}; double last_render_ms_{}; double last_pixel_encode_ms_{}; std::size_t last_pixel_bytes_{}; std::deque render_history_; std::deque pixel_history_; std::string last_event_{"initialized"}; std::string last_action_result_; }; } // namespace std::unique_ptr make_gallery_scene_3d( std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) { return std::make_unique( session_id, std::move(case_id), mode, automatic_low_latency); } } // namespace renderive::web