Files
Renderive/web_server/app/render_2D/Gallery_Scene2D.cpp
T
2026-08-17 15:24:28 +08:00

446 lines
19 KiB
C++

#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 <algorithm>
#include <chrono>
#include <cmath>
#include <memory>
#include <span>
#include <string>
#include <vector>
namespace renderive::web {
template <>
struct Observer_Pfr_Adapter<Render_Scene_2D::State> {
using Type = adminive::Boost_Pfr_Base_Reflection_Adapter<
Render_Scene_2D::State, Render_Scene_2D::State_Fields>;
};
template <>
struct Observer_Pfr_Adapter<Render_Scene_2D::Observer> {
using Type = adminive::Boost_Pfr_Base_Reflection_Adapter<
Render_Scene_2D::Observer, Render_Scene_2D::Observer_Fields>;
};
namespace {
std::shared_ptr<Frame_Control_Strategy_Base> make_strategy(
Gallery_Frame_Mode mode) {
switch (mode) {
case Gallery_Frame_Mode::Manual:
return std::make_shared<Manual_Render_Scene_2D_Strategy>();
case Gallery_Frame_Mode::Low_Latency:
return std::make_shared<Low_Latency_Render_Scene_2D_Strategy>();
case Gallery_Frame_Mode::Playback:
return std::make_shared<Playback_Render_Scene_2D_Strategy>();
}
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::milliseconds>(
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,
bool automatic)
: case_id_(std::move(case_id)), mode_(mode), automatic_(automatic) {
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; }
bool can_render_automatically() const noexcept override {
return automatic_ && mode_ == Gallery_Frame_Mode::Low_Latency;
}
std::uint64_t kernel_refresh_interval_ns() const override {
return scene_->observer().next_refresh_interval_ns;
}
void set_client_metrics(Gallery_Client_Performance metrics) noexcept override {
client_ = metrics;
}
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<decltype(value)>;
if constexpr (std::same_as<T, Gallery_View_Input>) {
if (value.type == Gallery_View_Input_Type::Show)
scene_->activate_view();
else if (value.type == Gallery_View_Input_Type::Hide)
scene_->deactivate_view();
}
}, event);
}
Gallery_Render_Result render_latest_frame() override {
update_samples();
const auto result = scene_->render_frame(true);
if (!result) return Gallery_Render_Result::backend_failure;
if (*result == Plot_Render_Status::view_inactive)
return Gallery_Render_Result::inactive;
if (*result != Plot_Render_Status::rendered)
return Gallery_Render_Result::frame_unavailable;
if (scene_->wait_for_render() != Scene_Render_Result::none)
return Gallery_Render_Result::backend_failure;
++render_count_;
return Gallery_Render_Result::none;
}
std::optional<std::string> 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 == "mode_cycle" || request.id == "mode_render") {
static_cast<void>(render_latest_frame());
return "frame rendered";
}
if (request.id == "capture_next_frame") {
const auto capture = scene_->capture_next_frame();
if (capture) static_cast<void>(render_latest_frame());
return capture ? "frame captured" : "capture request rejected";
}
if (request.id == "capture_frames") {
std::size_t count = 20;
if (request.argument && std::holds_alternative<double>(*request.argument))
count = static_cast<std::size_t>(std::clamp(
std::get<double>(*request.argument), 1.0, 1000.0));
const auto capture = scene_->capture_frames(count);
if (capture) static_cast<void>(render_latest_frame());
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 <class Product, class... Arguments>
static renderive_Owner<Product> build(
typename Product::Builder& builder, Arguments&&... arguments) {
return static_cast<Renderable_Builder<Product, typename Product::State>&>(
builder)
.build(std::forward<Arguments>(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::size_t>(
std::max(1, viewport_.width - 86));
const auto height = static_cast<std::size_t>(
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<int>(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<Time_Axis>(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<int>(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<Frequency_Axis>(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<int>(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<Axis>(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<Axis>(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<Spectrum>(
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<Selection_Rectangle_Overlay>(
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<Waterfall>(
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<Afterglow>(
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 = 64;
state.block_count = 8;
Sweep_Spectrum::Builder builder{state, &attach};
sweep_ = build<Sweep_Spectrum>(
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<Frequency_Trace>(
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<Constellation_Diagram>(
builder, root, domain_axis_, value_axis_);
constellation_->set_object_name("Constellation Diagram");
}
}
scene_->activate_view();
update_samples();
static_cast<void>(scene_->render_frame(true));
static_cast<void>(scene_->wait_for_render());
}
void update_samples() {
const std::size_t count = waterfall_ ? 256U : afterglow_ ? 192U : 512U;
std::vector<double> values(count);
const double time = static_cast<double>(sample_sequence_++) * 0.075;
for (std::size_t index = 0; index < count; ++index) {
const double x = static_cast<double>(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_) {
values.resize(64);
sweep_->append_block(values);
}
if (trace_) trace_->append_sample(current_time_of_day(), std::sin(time));
if (constellation_) {
const double phase = time * 1.7;
constellation_->append_point(
{std::cos(phase) + std::sin(time * 9.0) * 0.08,
std::sin(phase) + std::cos(time * 7.0) * 0.08});
}
}
std::string case_id_;
Gallery_Frame_Mode mode_;
bool automatic_{};
Size viewport_{560, 320};
std::unique_ptr<Render_Scene_2D> scene_;
renderive_Owner<Frequency_Axis> frequency_axis_;
renderive_Owner<Axis> domain_axis_;
renderive_Owner<Axis> value_axis_;
renderive_Owner<Time_Axis> time_axis_;
renderive_Owner<Spectrum> spectrum_;
renderive_Owner<Waterfall> waterfall_;
renderive_Owner<Afterglow> afterglow_;
renderive_Owner<Sweep_Spectrum> sweep_;
renderive_Owner<Frequency_Trace> trace_;
renderive_Owner<Selection_Rectangle_Overlay> selection_;
renderive_Owner<Constellation_Diagram> constellation_;
Gallery_Client_Performance client_;
std::uint64_t render_count_{};
std::uint64_t sample_sequence_{};
};
} // namespace
std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene_2d(
std::uint64_t, std::string case_id, Gallery_Frame_Mode mode,
bool automatic_low_latency) {
return std::make_unique<Gallery_Scene2D>(
std::move(case_id), mode, automatic_low_latency);
}
} // namespace renderive::web