#include "Gallery_Scene2D.h" #include "../Gallery_Capture_Json.h" #include "../common/Observer_Json.h" #include "../Pixel_Frame.h" #include "render_2D/axis/Axis.h" #include "render_2D/axis/Frequency_Axis.h" #include "render_2D/axis/Time_Axis.h" #include "render_2D/plottable/Afterglow.h" #include "render_2D/plottable/Constellation_Diagram.h" #include "render_2D/plottable/Frequency_Trace.h" #include "render_2D/plottable/Selection_Rectangle_Overlay.h" #include "render_2D/plottable/Spectrum.h" #include "render_2D/plottable/Sweep_Spectrum.h" #include "render_2D/plottable/Waterfall.h" #include "render_2D/scene/Render_Scene_2D.h" #include #include #include #include #include #include #include #include #include namespace renderive::web { template <> struct Observer_Pfr_Adapter { using Type = adminive::Boost_Pfr_Base_Reflection_Adapter< Render_Scene_2D::State, Render_Scene_2D::State_Fields>; }; template <> struct Observer_Pfr_Adapter { using Type = adminive::Boost_Pfr_Base_Reflection_Adapter< Render_Scene_2D::Observer, Render_Scene_2D::Observer_Fields>; }; namespace { constexpr std::size_t sweep_bins_per_block = 64; constexpr std::size_t sweep_block_count = 8; std::shared_ptr make_strategy( Gallery_Frame_Mode mode) { switch (mode) { case Gallery_Frame_Mode::Manual: return std::make_shared(); case Gallery_Frame_Mode::Low_Latency: return std::make_shared(); case Gallery_Frame_Mode::Playback: return std::make_shared(); } return {}; } std::string_view 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 "low_latency"; } Time_Of_Day current_time_of_day() { constexpr std::int64_t day_ms = 24LL * 60LL * 60LL * 1000LL; const auto now = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); return {now % day_ms}; } class Gallery_Scene2D final : public Gallery_Scene_Interface { public: Gallery_Scene2D(std::string case_id, Gallery_Frame_Mode mode) : case_id_(std::move(case_id)), mode_(mode) { rebuild(); } ~Gallery_Scene2D() override { if (scene_) scene_->deactivate_view(); } const std::string& case_id() const noexcept override { return case_id_; } nlohmann::json controls() const override { return {{"resources", nlohmann::json::array()}, {"observers", nlohmann::json::array()}, {"render_plan", gallery_render_plan_json(*scene_)}, {"performance_capture", gallery_performance_capture_json(*scene_)}}; } Gallery_Frame_Mode frame_mode() const noexcept override { return mode_; } void reset_monitoring() override { render_count_ = 0; } void set_client_metrics(Gallery_Client_Performance metrics) noexcept override { client_ = metrics; constexpr double maximum_interval_ms = static_cast(std::numeric_limits::max()) / 1'000'000.0; if (metrics.display_interval_latest_ms <= 0.0 || metrics.display_interval_latest_ms > maximum_interval_ms) return; const auto observed_interval_ns = static_cast( metrics.display_interval_latest_ms * 1'000'000.0); if (observed_interval_ns == 0) return; const auto strategy = scene_->frame_control_strategy(); if (auto* low_latency = dynamic_cast(strategy.get())) { static_cast(low_latency->set_consumer_feedback( Frame_Consumer_Feedback{observed_interval_ns})); } } adminive::Update_Result apply_patch( std::string_view, const nlohmann::json&) override { return {false, "no editable resource matches the requested target"}; } void resize(int width, int height) override { const Size requested{std::max(1, width), std::max(1, height)}; if (requested == viewport_) return; viewport_ = requested; rebuild(); } void dispatch(const Gallery_Input_Event& event) override { std::visit([this](const auto& value) { using T = std::decay_t; if constexpr (std::same_as) { if (value.type == Gallery_View_Input_Type::Show) scene_->activate_view(); else if (value.type == Gallery_View_Input_Type::Hide) scene_->deactivate_view(); } }, event); } bool request_frame() override { update_samples(); const auto result = scene_->render_frame(true); if (!result || *result != Plot_Render_Status::rendered) return false; if (scene_->wait_for_render() != Scene_Render_Result::none) return false; ++render_count_; return true; } std::optional encode_latest_pixels() override { std::string result; scene_->with_frame([&](Image_View image) { result = encode_pixel_frame(image, render_count_); }); if (result.empty()) return std::nullopt; return result; } void record_pixel_response(std::chrono::steady_clock::time_point, std::chrono::steady_clock::time_point, std::chrono::steady_clock::time_point, std::size_t) override {} std::string action(const Gallery_Action_Request& request, bool& recognized) override { recognized = true; if (request.id == "frame_strategy_manual" || request.id == "frame_strategy_low_latency" || request.id == "frame_strategy_playback") { const auto next = request.id == "frame_strategy_manual" ? Gallery_Frame_Mode::Manual : request.id == "frame_strategy_playback" ? Gallery_Frame_Mode::Playback : Gallery_Frame_Mode::Low_Latency; const auto operation = scene_->edit_renderables( [strategy = make_strategy(next)]( Scene_2D_Base::Renderable_Editor& editor) mutable { editor.replace_frame_control_strategy(std::move(strategy)); }); if (operation.wait() != Scene_Edit_Error::none) return "frame strategy replacement rejected"; mode_ = next; return "frame strategy replaced"; } if (request.id == "capture_next_frame") { const auto capture = scene_->capture_next_frame(); return capture ? "capture request accepted" : "capture request rejected"; } if (request.id == "capture_frames") { std::size_t count = 20; if (request.argument && std::holds_alternative(*request.argument)) count = static_cast(std::clamp( std::get(*request.argument), 1.0, 1000.0)); const auto capture = scene_->capture_frames(count); return capture ? "frame capture started" : "capture request rejected"; } recognized = false; return {}; } void record_action_notice_if_empty(std::string_view) override {} std::string telemetry_json() const override { const auto observer = scene_->observer(); auto kernel_observer = observer_json(observer); kernel_observer["mode"] = mode_id(mode_); kernel_observer["event"] = "render_completed"; kernel_observer["limit"] = "none"; return nlohmann::json{ {"kernel_observer", std::move(kernel_observer)}, {"performance", {{"successful_render_count", render_count_}}}, {"client_performance", {{"transport_fps", client_.transport_fps}}}, {"performance_capture", gallery_performance_capture_json(*scene_)}} .dump(); } private: template static renderive_Owner build( typename Product::Builder& builder, Arguments&&... arguments) { return static_cast&>( builder) .build(std::forward(arguments)...); } void rebuild() { Render_Scene_2D::State scene_state; scene_state.viewport = viewport_; scene_state.background = Color{3, 8, 14, 255}; scene_ = Render_Scene_2D::Builder{scene_state}.build( make_strategy(mode_)); { auto attach = scene_->attach_builder(); const auto root = scene_->root_renderable(); const bool numeric_domain = case_id_ == "sweep_spectrum" || case_id_ == "constellation"; const bool time_domain = case_id_ == "frequency_trace"; const int left = 62; const int top = 18; const auto width = static_cast( std::max(1, viewport_.width - 86)); const auto height = static_cast( std::max(1, viewport_.height - 64)); if (time_domain || case_id_ == "waterfall" || case_id_ == "axis_lab") { Time_Axis::State state; state.orientation = time_domain || case_id_ == "axis_lab" ? Orientation::Horizontal : Orientation::Vertical; state.visible_count = 80; state.x = left; state.y = state.orientation == Orientation::Horizontal ? top + static_cast(height) : top; state.pixel_length = state.orientation == Orientation::Horizontal ? width : height; state.color = Color{69, 221, 190, 255}; Time_Axis::Builder builder{state, &attach}; time_axis_ = build(builder, root); time_axis_->set_object_name("Time Axis"); } if (!time_domain && !numeric_domain) { Frequency_Axis::State state; state.orientation = Orientation::Horizontal; state.coordinates = Range{88'000'000.0, 108'000'000.0}; state.x = left; state.y = top + static_cast(height); state.pixel_length = width; state.color = Color{118, 145, 184, 255}; state.wheel = true; state.drag = true; Frequency_Axis::Builder builder{state, &attach}; frequency_axis_ = build(builder, root); frequency_axis_->set_object_name("Frequency Axis"); } else if (numeric_domain) { Axis::State state; state.orientation = Orientation::Horizontal; state.coordinates = case_id_ == "constellation" ? Range{-1.25, 1.25} : Range{0.0, 300.0}; state.x = left; state.y = top + static_cast(height); state.pixel_length = width; state.color = Color{118, 145, 184, 255}; state.wheel = true; state.drag = true; Axis::Builder builder{state, &attach}; domain_axis_ = build(builder, root); domain_axis_->set_object_name("Domain Axis"); } if (case_id_ != "waterfall") { Axis::State state; state.orientation = Orientation::Vertical; state.coordinates = case_id_ == "constellation" ? Range{-1.25, 1.25} : time_domain ? Range{-1.0, 1.0} : Range{-120.0, -20.0}; state.x = left; state.y = top; state.pixel_length = height; state.color = Color{118, 145, 184, 255}; Axis::Builder builder{state, &attach}; value_axis_ = build(builder, root); value_axis_->set_object_name("Value Axis"); } if (case_id_ == "spectrum" || case_id_ == "selection_overlay") { Spectrum::State state; state.frequency_range = {88'000'000.0, 108'000'000.0}; state.frequency_point_size = 512; state.center_frequency = 98'000'000.0; state.max_hold_visible = true; state.current_pen = {Color{53, 230, 178, 255}, 2.0}; state.max_pen = {Color{255, 209, 102, 255}, 1.2}; state.min_pen = {Color{122, 162, 255, 255}, 1.2}; Spectrum::Builder builder{state, &attach}; spectrum_ = build( builder, root, frequency_axis_, value_axis_); spectrum_->set_object_name("Spectrum"); if (case_id_ == "selection_overlay") { Selection_Rectangle_Overlay::State overlay_state; overlay_state.selection_brush = {Color{23, 59, 102, 90}, Brush_Style::Solid}; Selection_Rectangle_Overlay::Builder overlay_builder{ overlay_state, &attach}; selection_ = build( overlay_builder, root, frequency_axis_, value_axis_); selection_->set_object_name("Selection Overlay"); } } else if (case_id_ == "waterfall") { Waterfall::State state; state.frequency_range = {88'000'000.0, 108'000'000.0}; state.power_range = {-120.0, -20.0}; state.frequency_bin_count = 256; Waterfall::Builder builder{state, &attach}; waterfall_ = build( builder, root, frequency_axis_, time_axis_); waterfall_->set_object_name("Waterfall"); } else if (case_id_ == "afterglow") { Afterglow::State state; state.frequency_range = {88'000'000.0, 108'000'000.0}; state.power_range = {-120.0, -20.0}; state.frequency_point_size = 192; state.power_point_size = 96; state.attenuation_rate = 0.18; Afterglow::Builder builder{state, &attach}; afterglow_ = build( builder, root, frequency_axis_, value_axis_); afterglow_->set_object_name("Afterglow"); } else if (case_id_ == "sweep_spectrum") { Sweep_Spectrum::State state; state.frequency_range = {0.0, 300.0}; state.bins_per_block = static_cast(sweep_bins_per_block); state.block_count = static_cast(sweep_block_count); Sweep_Spectrum::Builder builder{state, &attach}; sweep_ = build( builder, root, domain_axis_, value_axis_); sweep_->set_object_name("Sweep Spectrum"); } else if (case_id_ == "frequency_trace") { Frequency_Trace::State state; state.pen = {Color{53, 230, 178, 255}, 2.0}; Frequency_Trace::Builder builder{state, &attach}; trace_ = build( builder, root, time_axis_, value_axis_); trace_->set_object_name("Frequency Trace"); } else if (case_id_ == "constellation") { Constellation_Diagram::State state; state.i_range = {-1.25, 1.25}; state.q_range = {-1.25, 1.25}; state.point_color = Color{73, 230, 195, 255}; state.anchor_color = Color{255, 209, 102, 255}; Constellation_Diagram::Builder builder{state, &attach}; constellation_ = build( builder, root, domain_axis_, value_axis_); constellation_->set_object_name("Constellation Diagram"); } } scene_->activate_view(); } void update_samples() { const std::size_t count = waterfall_ ? 256U : afterglow_ ? 192U : sweep_ ? sweep_bins_per_block * sweep_block_count : 512U; std::vector values(count); const std::uint64_t sample_sequence = sample_sequence_++; const std::uint64_t time_sequence = sweep_ ? sample_sequence / sweep_block_count * sweep_block_count : sample_sequence; const double time = static_cast(time_sequence) * 0.075; for (std::size_t index = 0; index < count; ++index) { const double x = static_cast(index) / (count - 1); const auto gaussian = [](double value, double center, double width) { const double normalized = (value - center) / width; return std::exp(-0.5 * normalized * normalized); }; values[index] = std::clamp( -112.0 + std::sin(index * 0.31 + time) * 3.5 + 62.0 * gaussian(x, 0.31 + std::sin(time) * 0.025, 0.025) + 43.0 * gaussian(x, 0.58, 0.06) + 30.0 * gaussian(x, 0.79, 0.015), -120.0, -20.0); } if (spectrum_) spectrum_->update_samples(values); if (waterfall_) waterfall_->append_row(current_time_of_day(), values); if (afterglow_) afterglow_->append_spectrum(values); if (sweep_) { const std::size_t block_index = static_cast( sample_sequence % sweep_block_count); const std::size_t first = block_index * sweep_bins_per_block; sweep_->append_block(std::span( values.data() + first, sweep_bins_per_block)); } if (trace_) trace_->append_sample(current_time_of_day(), std::sin(time)); if (constellation_) { constexpr std::size_t symbol_count = static_cast(Constellation_Diagram_Type::Psk8); const std::size_t symbol_index = sample_sequence % symbol_count; const double phase = 2.0 * std::numbers::pi * static_cast(symbol_index) / static_cast(symbol_count); const double i_noise = std::sin(time * 17.0 + static_cast(symbol_index) * 0.73) * 0.04; const double q_noise = std::cos(time * 19.0 + static_cast(symbol_index) * 1.11) * 0.04; constellation_->append_point( {std::cos(phase) + i_noise, std::sin(phase) + q_noise}); } } std::string case_id_; Gallery_Frame_Mode mode_; Size viewport_{560, 320}; std::unique_ptr scene_; renderive_Owner frequency_axis_; renderive_Owner domain_axis_; renderive_Owner value_axis_; renderive_Owner time_axis_; renderive_Owner spectrum_; renderive_Owner waterfall_; renderive_Owner afterglow_; renderive_Owner sweep_; renderive_Owner trace_; renderive_Owner selection_; renderive_Owner constellation_; Gallery_Client_Performance client_; std::uint64_t render_count_{}; std::uint64_t sample_sequence_{}; }; } // namespace std::unique_ptr make_gallery_scene_2d( std::uint64_t, std::string case_id, Gallery_Frame_Mode mode) { return std::make_unique(std::move(case_id), mode); } } // namespace renderive::web