3D 第一个控件成功显示
This commit is contained in:
@@ -108,6 +108,10 @@ if (NOT TARGET Renderive_render_2D)
|
||||
rcl_log_append(${CMAKE_CURRENT_LIST_LINE} "[FATAL_ERROR] Renderive_Web requires Renderive_render_2D")
|
||||
return()
|
||||
endif ()
|
||||
if (NOT TARGET Renderive_render_3D)
|
||||
rcl_log_append(${CMAKE_CURRENT_LIST_LINE} "[FATAL_ERROR] Renderive_Web requires Renderive_render_3D")
|
||||
return()
|
||||
endif ()
|
||||
if (NOT TARGET Adminive::Nlohmann)
|
||||
set(Renderive_Web_saved_build_testing "${BUILD_TESTING}")
|
||||
set(BUILD_TESTING OFF)
|
||||
@@ -129,13 +133,14 @@ target_compile_features(Renderive_Web PUBLIC cxx_std_20)
|
||||
target_compile_definitions(Renderive_Web PRIVATE NOMINMAX)
|
||||
target_link_libraries(Renderive_Web
|
||||
PUBLIC Renderive_render_2D Drogon::Drogon Adminive::MagicEnum magic_enum::magic_enum
|
||||
PRIVATE Adminive::Nlohmann spdlog::spdlog
|
||||
PRIVATE Renderive_render_3D Adminive::Nlohmann spdlog::spdlog
|
||||
)
|
||||
set(Renderive_Web_Server_source_dir "${CMAKE_CURRENT_LIST_DIR}/server")
|
||||
append_glob_source(Renderive_Web_Server_sources "${Renderive_Web_Server_source_dir}")
|
||||
add_executable(Renderive_Web_Server ${Renderive_Web_Server_sources})
|
||||
target_compile_features(Renderive_Web_Server PRIVATE cxx_std_20)
|
||||
target_link_libraries(Renderive_Web_Server PRIVATE Renderive_Web)
|
||||
renderive_stage_render_3D_runtime(Renderive_Web_Server)
|
||||
#add_dependencies(Renderive_Web_Server Renderive_Web_Assets)
|
||||
if (MSVC)
|
||||
target_compile_options(Renderive_Web PRIVATE /utf-8)
|
||||
@@ -156,6 +161,7 @@ if (RENDERIVE_BUILD_TESTS)
|
||||
set(Renderive_Web_test_target "Renderive_Web_${Renderive_Web_test_name}_${Renderive_Web_test_hash}")
|
||||
add_executable("${Renderive_Web_test_target}" "${Renderive_Web_test_source}")
|
||||
target_link_libraries("${Renderive_Web_test_target}" PRIVATE Renderive_Web Adminive::Nlohmann GTest::gtest_main)
|
||||
renderive_stage_render_3D_runtime("${Renderive_Web_test_target}")
|
||||
add_test(NAME "${Renderive_Web_test_target}" COMMAND "${Renderive_Web_test_target}")
|
||||
endforeach ()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Pixel_Frame.h"
|
||||
#include "Web_Performance_Log.h"
|
||||
#include "render_2D/export.h"
|
||||
#include "render_3D/Point_Demo.h"
|
||||
#include <renderive/base/statistics/Rolling_Statistics.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <algorithm>
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <numbers>
|
||||
@@ -91,6 +93,9 @@ public:
|
||||
[[nodiscard]] virtual adminive::Update_Result apply_patch(std::string_view target, const nlohmann::json& patch) = 0;
|
||||
virtual void resize(Size size) = 0;
|
||||
virtual void dispatch(const Event& event) = 0;
|
||||
virtual void dispatch(const Pointer_Event& event) = 0;
|
||||
virtual void dispatch(const Wheel_Event& event) = 0;
|
||||
virtual void dispatch(const Key_Event& event) = 0;
|
||||
virtual bool render_latest_frame() = 0;
|
||||
[[nodiscard]] virtual std::optional<std::string> encode_latest_pixels() = 0;
|
||||
virtual void record_pixel_response(std::chrono::steady_clock::time_point request_started, std::chrono::steady_clock::time_point encode_started, std::chrono::steady_clock::time_point encode_finished, std::size_t pixel_bytes) = 0;
|
||||
@@ -235,6 +240,15 @@ public:
|
||||
plot_.deactivate_view();
|
||||
plot_.dispatch_event(event);
|
||||
}
|
||||
void dispatch(const Pointer_Event& event) {
|
||||
plot_.dispatch_event(event);
|
||||
}
|
||||
void dispatch(const Wheel_Event& event) {
|
||||
plot_.dispatch_event(event);
|
||||
}
|
||||
void dispatch(const Key_Event& event) {
|
||||
plot_.dispatch_event(event);
|
||||
}
|
||||
bool render_latest_frame() {
|
||||
if (!plot_.view_active())
|
||||
return false;
|
||||
@@ -1302,7 +1316,350 @@ private:
|
||||
bool rendered_since_last_pixel_{};
|
||||
std::string last_action_result_;
|
||||
};
|
||||
|
||||
render_3d::Frame_Mode point_frame_mode(Gallery_Frame_Mode mode) {
|
||||
switch (mode) {
|
||||
case Gallery_Frame_Mode::Manual:
|
||||
return render_3d::Frame_Mode::Manual;
|
||||
case Gallery_Frame_Mode::Low_Latency:
|
||||
return render_3d::Frame_Mode::Low_Latency;
|
||||
case Gallery_Frame_Mode::Playback:
|
||||
return render_3d::Frame_Mode::Playback;
|
||||
}
|
||||
throw std::invalid_argument("unknown 3D frame mode");
|
||||
}
|
||||
|
||||
class Point_Gallery_Scene final : public Gallery_Scene_Interface {
|
||||
public:
|
||||
Point_Gallery_Scene(Gallery_Frame_Mode mode, bool automatic_low_latency)
|
||||
: mode_(mode), automatic_low_latency_(automatic_low_latency),
|
||||
performance_started_(std::chrono::steady_clock::now()),
|
||||
demo_(render_3d::make_point_demo({
|
||||
{560, 320}, {}, point_frame_mode(mode), 60.0, 0, false})) {
|
||||
if (mode_ == Gallery_Frame_Mode::Playback) {
|
||||
for (int index = 0; index < 24; ++index) {
|
||||
phase_ = -1.20F + static_cast<float>(index) * 0.05F;
|
||||
publish_demo_points();
|
||||
(void)demo_.scene->prepare_frame();
|
||||
}
|
||||
(void)record_render([this] { return demo_.scene->render_prepared_frame(); });
|
||||
} else {
|
||||
(void)record_render([this] { return demo_.scene->request_frame(); });
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string& case_id() const noexcept override {
|
||||
return case_id_;
|
||||
}
|
||||
[[nodiscard]] nlohmann::json controls() const override {
|
||||
return {{"resources", nlohmann::json::array()},
|
||||
{"observers", nlohmann::json::array()},
|
||||
{"render_plan", nullptr},
|
||||
{"performance_capture", nullptr}};
|
||||
}
|
||||
[[nodiscard]] Gallery_Frame_Mode frame_mode() const noexcept override {
|
||||
return mode_;
|
||||
}
|
||||
void reset_monitoring() override {
|
||||
performance_started_ = std::chrono::steady_clock::now();
|
||||
render_attempt_count_ = 0;
|
||||
successful_render_count_ = 0;
|
||||
render_duration_statistics_.clear();
|
||||
pixel_encode_statistics_.clear();
|
||||
pixel_request_statistics_.clear();
|
||||
last_render_ms_ = 0.0;
|
||||
last_pixel_encode_ms_ = 0.0;
|
||||
last_pixel_request_ms_ = 0.0;
|
||||
last_pixel_bytes_ = 0;
|
||||
client_performance_ = {};
|
||||
}
|
||||
[[nodiscard]] bool can_render_automatically() const noexcept override {
|
||||
return active_ && automatic_low_latency_ &&
|
||||
mode_ == Gallery_Frame_Mode::Low_Latency;
|
||||
}
|
||||
[[nodiscard]] std::uint64_t kernel_refresh_interval_ns() const override {
|
||||
const auto interval = demo_.scene->frame_status().next_refresh_interval_ns;
|
||||
return interval == 0 ? 16'666'667U : interval;
|
||||
}
|
||||
void set_client_metrics(Gallery_Client_Performance metrics) noexcept override {
|
||||
client_performance_ = metrics;
|
||||
}
|
||||
[[nodiscard]] adminive::Update_Result apply_patch(
|
||||
std::string_view target, const nlohmann::json&) override {
|
||||
adminive::Update_Result result;
|
||||
result.message = "point_3d has no patchable control: " + std::string(target);
|
||||
return result;
|
||||
}
|
||||
void resize(Size size) override {
|
||||
if (size.width <= 0 || size.height <= 0)
|
||||
return;
|
||||
demo_.scene->resize({static_cast<std::uint32_t>(size.width),
|
||||
static_cast<std::uint32_t>(size.height)});
|
||||
rendered_since_last_pixel_ = false;
|
||||
}
|
||||
void dispatch(const Event& event) override {
|
||||
if (event.type == Event_Type::Show)
|
||||
active_ = true;
|
||||
else if (event.type == Event_Type::Hide)
|
||||
active_ = false;
|
||||
demo_.scene->dispatch(event);
|
||||
}
|
||||
void dispatch(const Pointer_Event& event) override {
|
||||
if (active_)
|
||||
demo_.scene->dispatch(event);
|
||||
}
|
||||
void dispatch(const Wheel_Event& event) override {
|
||||
if (active_)
|
||||
demo_.scene->dispatch(event);
|
||||
}
|
||||
void dispatch(const Key_Event& event) override {
|
||||
if (active_)
|
||||
demo_.scene->dispatch(event);
|
||||
}
|
||||
bool render_latest_frame() override {
|
||||
if (!active_)
|
||||
return false;
|
||||
if (can_render_automatically()) {
|
||||
phase_ += 0.045F;
|
||||
publish_demo_points();
|
||||
}
|
||||
return record_render([this] { return demo_.scene->request_frame(); });
|
||||
}
|
||||
[[nodiscard]] std::optional<std::string> encode_latest_pixels() override {
|
||||
if (!active_)
|
||||
return std::nullopt;
|
||||
if (!rendered_since_last_pixel_ && !can_render_automatically() &&
|
||||
!render_latest_frame())
|
||||
return std::nullopt;
|
||||
const auto frame = demo_.scene->latest_frame();
|
||||
if (!frame)
|
||||
return std::nullopt;
|
||||
rendered_since_last_pixel_ = false;
|
||||
auto encoded = encode_rgba8_pixel_frame(
|
||||
frame->rgba8.data(), frame->extent.width, frame->extent.height,
|
||||
static_cast<std::size_t>(frame->extent.width) * 4U);
|
||||
return encoded.empty() ? std::nullopt
|
||||
: std::optional<std::string>(std::move(encoded));
|
||||
}
|
||||
void record_pixel_response(
|
||||
std::chrono::steady_clock::time_point request_started,
|
||||
std::chrono::steady_clock::time_point encode_started,
|
||||
std::chrono::steady_clock::time_point encode_finished,
|
||||
std::size_t pixel_bytes) override {
|
||||
last_pixel_encode_ms_ = std::chrono::duration<double, std::milli>(
|
||||
encode_finished - encode_started)
|
||||
.count();
|
||||
last_pixel_request_ms_ = std::chrono::duration<double, std::milli>(
|
||||
encode_finished - request_started)
|
||||
.count();
|
||||
last_pixel_bytes_ = pixel_bytes;
|
||||
pixel_encode_statistics_.add(last_pixel_encode_ms_);
|
||||
pixel_request_statistics_.add(last_pixel_request_ms_);
|
||||
}
|
||||
[[nodiscard]] std::string action(const Gallery_Action_Request& request,
|
||||
bool& recognized) override {
|
||||
recognized = true;
|
||||
if (request.id == "mode_prepare") {
|
||||
const bool prepared = demo_.scene->prepare_frame();
|
||||
last_action_result_ = prepared ? "prepared" : "prepare_rejected";
|
||||
return prepared ? "Point frame prepared" : "Point frame prepare rejected";
|
||||
}
|
||||
if (request.id == "mode_refresh") {
|
||||
const bool refreshed = demo_.scene->refresh_manual_frame();
|
||||
last_action_result_ = refreshed ? "refresh_succeeded" : "refresh_failed";
|
||||
return refreshed ? "Point manual frame refreshed" : "No manual frame to refresh";
|
||||
}
|
||||
if (request.id == "mode_render" || request.id == "mode_dequeue") {
|
||||
const bool rendered = record_render(
|
||||
[this] { return demo_.scene->render_prepared_frame(); });
|
||||
last_action_result_ = rendered ? "rendered" : "render_queue_empty";
|
||||
return rendered ? "Point frame rendered" : "No Point frame available";
|
||||
}
|
||||
if (request.id == "mode_discard") {
|
||||
const bool discarded = demo_.scene->discard_pending_frame();
|
||||
last_action_result_ = discarded ? "manually_discarded" : "discard_failed";
|
||||
return discarded ? "Pending Point frame discarded" : "No Point frame to discard";
|
||||
}
|
||||
if (request.id == "mode_enqueue") {
|
||||
phase_ += 0.05F;
|
||||
publish_demo_points();
|
||||
const bool enqueued = demo_.scene->prepare_frame();
|
||||
last_action_result_ = enqueued ? "enqueued" : "enqueue_failed";
|
||||
return enqueued ? "Point playback frame enqueued" : "Point enqueue failed";
|
||||
}
|
||||
if (request.id == "orbit_points") {
|
||||
phase_ += 0.25F;
|
||||
publish_demo_points();
|
||||
last_action_result_ = "points_orbited";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Point positions updated through Latest_Real_Time_Data";
|
||||
}
|
||||
if (request.id == "add_point") {
|
||||
auto points = current_points();
|
||||
points.push_back({{0.12F, -0.72F, 0.58F}, {55, 235, 230, 255}, 46.0F});
|
||||
demo_.points->update(std::move(points));
|
||||
last_action_result_ = "point_added";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Point added to the bulk payload";
|
||||
}
|
||||
if (request.id == "remove_point") {
|
||||
auto points = current_points();
|
||||
if (points.size() > render_3d::point_demo_data().size()) {
|
||||
points.pop_back();
|
||||
demo_.points->update(std::move(points));
|
||||
last_action_result_ = "point_removed";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Last added Point removed";
|
||||
}
|
||||
last_action_result_ = "no_added_point";
|
||||
return "No added Point to remove";
|
||||
}
|
||||
if (request.id == "point_churn") {
|
||||
auto stable = current_points();
|
||||
auto transient = stable;
|
||||
transient.push_back({{0.0F, 0.0F, 0.8F}, {255, 255, 255, 255}, 72.0F});
|
||||
demo_.points->update(std::move(transient));
|
||||
demo_.points->update(std::move(stable));
|
||||
last_action_result_ = "point_churned";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Transient Point added and removed before the next frame snapshot";
|
||||
}
|
||||
if (request.id == "reset_points") {
|
||||
phase_ = 0.0F;
|
||||
publish_demo_points();
|
||||
last_action_result_ = "points_reset";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Point demo payload reset";
|
||||
}
|
||||
recognized = false;
|
||||
return {};
|
||||
}
|
||||
void record_action_notice_if_empty(std::string_view notice) override {
|
||||
if (last_action_result_.empty())
|
||||
last_action_result_ = notice;
|
||||
}
|
||||
[[nodiscard]] std::string telemetry_json() const override {
|
||||
const auto status = demo_.scene->frame_status();
|
||||
const auto frame = demo_.scene->latest_frame();
|
||||
const auto points = current_points();
|
||||
const auto render_window = render_duration_statistics_.snapshot();
|
||||
const auto encode_window = pixel_encode_statistics_.snapshot();
|
||||
const auto request_window = pixel_request_statistics_.snapshot();
|
||||
const double elapsed = std::max(
|
||||
std::chrono::duration<double>(std::chrono::steady_clock::now() -
|
||||
performance_started_)
|
||||
.count(),
|
||||
1e-9);
|
||||
Gallery_Render_Performance performance;
|
||||
performance.render_attempt_count = render_attempt_count_;
|
||||
performance.successful_render_count = successful_render_count_;
|
||||
performance.failed_render_count = render_attempt_count_ - successful_render_count_;
|
||||
performance.lifetime_average_fps = successful_render_count_ / elapsed;
|
||||
performance.last_render_ms = last_render_ms_;
|
||||
performance.average_render_ms = render_window.average;
|
||||
performance.maximum_render_ms = render_window.maximum;
|
||||
performance.render_deviation_ms = render_window.deviation;
|
||||
performance.render_p50_ms = render_window.p50;
|
||||
performance.render_p95_ms = render_window.p95;
|
||||
performance.render_p99_ms = render_window.p99;
|
||||
performance.render_sample_count = render_window.sample_count;
|
||||
performance.last_pixel_encode_ms = last_pixel_encode_ms_;
|
||||
performance.average_pixel_encode_ms = encode_window.average;
|
||||
performance.maximum_pixel_encode_ms = encode_window.maximum;
|
||||
performance.pixel_encode_deviation_ms = encode_window.deviation;
|
||||
performance.pixel_encode_p50_ms = encode_window.p50;
|
||||
performance.pixel_encode_p95_ms = encode_window.p95;
|
||||
performance.pixel_encode_p99_ms = encode_window.p99;
|
||||
performance.pixel_encode_sample_count = encode_window.sample_count;
|
||||
performance.last_pixel_request_ms = last_pixel_request_ms_;
|
||||
performance.average_pixel_request_ms = request_window.average;
|
||||
performance.pixel_request_deviation_ms = request_window.deviation;
|
||||
performance.pixel_request_p95_ms = request_window.p95;
|
||||
performance.pixel_request_p99_ms = request_window.p99;
|
||||
performance.last_pixel_bytes = last_pixel_bytes_;
|
||||
performance.automatic_low_latency_scheduler = can_render_automatically();
|
||||
|
||||
const auto extent = frame ? frame->extent : render_3d::Extent{560, 320};
|
||||
nlohmann::json telemetry{
|
||||
{"case", case_id_},
|
||||
{"frame_mode", gallery_enum_id(mode_)},
|
||||
{"frame_index", status.latest_sequence},
|
||||
{"viewport", {{"width", extent.width}, {"height", extent.height}}},
|
||||
{"view_active", active_},
|
||||
{"kernel_frame_count", status.consumed_frame_count},
|
||||
{"performance", adminive::to_frontend_json<nlohmann::json>(performance)},
|
||||
{"kernel_observer",
|
||||
{{"mode", gallery_enum_id(mode_)},
|
||||
{"last_event", last_action_result_.empty()
|
||||
? (frame ? "rendered" : "none")
|
||||
: last_action_result_},
|
||||
{"frequency_hz", status.frequency_hz},
|
||||
{"produced_frame_count", status.produced_frame_count},
|
||||
{"consumed_frame_count", status.consumed_frame_count},
|
||||
{"dropped_frame_count", status.dropped_frame_count},
|
||||
{"failed_operation_count", status.failed_operation_count},
|
||||
{"pending_frame_count", status.pending_frame_count},
|
||||
{"latest_sequence", status.latest_sequence},
|
||||
{"next_refresh_interval_ns", status.next_refresh_interval_ns}}},
|
||||
{"consumer_feedback", nlohmann::json::object()},
|
||||
{"client_performance",
|
||||
adminive::to_frontend_json<nlohmann::json>(client_performance_)},
|
||||
{"renderable_observers", nlohmann::json::array()},
|
||||
{"performance_capture", nullptr},
|
||||
{"last_action_result", last_action_result_},
|
||||
{"point_3d", {{"point_count", points.size()},
|
||||
{"data_revision", demo_.points->revision()}}},
|
||||
{"data_shape", {{"input_elements", points.size()},
|
||||
{"rendered_elements", points.size()}}}}
|
||||
;
|
||||
return telemetry.dump();
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<render_3d::Point> current_points() const {
|
||||
return demo_.points->snapshot().value_or(std::vector<render_3d::Point>{});
|
||||
}
|
||||
void publish_demo_points() {
|
||||
demo_.points->update(render_3d::point_demo_data(phase_));
|
||||
}
|
||||
template <class Render>
|
||||
bool record_render(Render&& render) {
|
||||
++render_attempt_count_;
|
||||
const auto started = std::chrono::steady_clock::now();
|
||||
const bool rendered = std::invoke(std::forward<Render>(render));
|
||||
last_render_ms_ = std::chrono::duration<double, std::milli>(
|
||||
std::chrono::steady_clock::now() - started)
|
||||
.count();
|
||||
render_duration_statistics_.add(last_render_ms_);
|
||||
successful_render_count_ += rendered ? 1U : 0U;
|
||||
rendered_since_last_pixel_ = rendered;
|
||||
return rendered;
|
||||
}
|
||||
|
||||
std::string case_id_{"point_3d"};
|
||||
Gallery_Frame_Mode mode_;
|
||||
bool automatic_low_latency_{};
|
||||
bool active_{true};
|
||||
std::chrono::steady_clock::time_point performance_started_;
|
||||
render_3d::Point_Demo demo_;
|
||||
float phase_{};
|
||||
std::uint64_t render_attempt_count_{};
|
||||
std::uint64_t successful_render_count_{};
|
||||
double last_render_ms_{};
|
||||
Rolling_Statistics render_duration_statistics_{256};
|
||||
double last_pixel_encode_ms_{};
|
||||
double last_pixel_request_ms_{};
|
||||
std::size_t last_pixel_bytes_{};
|
||||
Rolling_Statistics pixel_encode_statistics_{256};
|
||||
Rolling_Statistics pixel_request_statistics_{256};
|
||||
Gallery_Client_Performance client_performance_;
|
||||
bool rendered_since_last_pixel_{};
|
||||
std::string last_action_result_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) {
|
||||
if (case_id == "point_3d")
|
||||
return std::make_unique<Point_Gallery_Scene>(mode, automatic_low_latency);
|
||||
switch (mode) {
|
||||
case Gallery_Frame_Mode::Manual:
|
||||
return std::make_unique<Gallery_Scene<Manual_Scene2D>>(session_id, std::move(case_id), mode, automatic_low_latency);
|
||||
|
||||
@@ -53,6 +53,8 @@ const std::vector<Case_Model>& cases() {
|
||||
"鼠标框选、多区域保留、标注样式和清空。", 70},
|
||||
{"constellation", "星座图", "Constellation_Diagram", "点图控件",
|
||||
"PSK4/PSK8/PSK16 模式、相位、寿命、坐标范围和方形拟合。", 80}
|
||||
,{"point_3d", "Datoviz 3D 点组件", "Point Visual", "3D",
|
||||
"Kernel 状态双缓冲、实时批量点数据、单线程 Datoviz 渲染与 RGBA 回读。", 90}
|
||||
};
|
||||
return value;
|
||||
}
|
||||
@@ -93,6 +95,39 @@ std::size_t session_control_count(Gallery_Frame_Mode frame_mode) {
|
||||
std::vector<Action_Model> registered_actions(std::string_view case_id,
|
||||
Gallery_Frame_Mode frame_mode) {
|
||||
std::vector<Action_Model> result;
|
||||
if (case_id == "point_3d") {
|
||||
const auto keep_frame_action = [frame_mode](std::string_view id) {
|
||||
switch (frame_mode) {
|
||||
case Gallery_Frame_Mode::Manual:
|
||||
return id == "mode_prepare" || id == "mode_refresh" ||
|
||||
id == "mode_render" || id == "mode_discard";
|
||||
case Gallery_Frame_Mode::Low_Latency:
|
||||
return id == "mode_prepare" || id == "mode_render" ||
|
||||
id == "mode_discard";
|
||||
case Gallery_Frame_Mode::Playback:
|
||||
return id == "mode_enqueue" || id == "mode_dequeue";
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (auto& action : gallery_frame_actions(frame_mode)) {
|
||||
if (keep_frame_action(action.id))
|
||||
result.push_back(std::move(action));
|
||||
}
|
||||
const auto add = [&result](std::string id, std::string label,
|
||||
std::string api, std::string description) {
|
||||
result.push_back({std::move(id), std::move(label), std::move(api),
|
||||
std::move(description), "Point Visual", {}, {}, 0.0,
|
||||
true});
|
||||
};
|
||||
add("orbit_points", "移动点", "Point_Data::update",
|
||||
"通过 Kernel Latest_Real_Time_Data 发布一批新位置");
|
||||
add("add_point", "添加点", "Point_Data::update", "向批量点数据追加一个点");
|
||||
add("remove_point", "移除点", "Point_Data::update", "移除最后追加的点");
|
||||
add("point_churn", "点增删压力", "Point_Data::update",
|
||||
"在帧快照前连续追加并移除临时点");
|
||||
add("reset_points", "重置点", "Point_Data::update", "恢复 3D 点演示数据");
|
||||
return result;
|
||||
}
|
||||
append_session_actions(frame_mode, result);
|
||||
for (auto& action : gallery_frame_actions(frame_mode))
|
||||
result.push_back(std::move(action));
|
||||
@@ -520,8 +555,9 @@ std::string Gallery_Protocol::catalog_json() {
|
||||
const std::size_t renderable_controls = gallery_renderable_control_count(item.id);
|
||||
for (const auto mode : frame_modes) {
|
||||
const std::string key = gallery_enum_id(mode);
|
||||
const std::size_t controls =
|
||||
gallery_detail::session_control_count(mode) + renderable_controls;
|
||||
const std::size_t controls = item.id == "point_3d"
|
||||
? 0U
|
||||
: gallery_detail::session_control_count(mode) + renderable_controls;
|
||||
const std::size_t actions =
|
||||
gallery_detail::registered_actions(item.id, mode).size();
|
||||
entry["control_count_by_mode"][key] = controls;
|
||||
@@ -529,9 +565,10 @@ std::string Gallery_Protocol::catalog_json() {
|
||||
controls_total += controls;
|
||||
actions_total += actions;
|
||||
}
|
||||
entry["control_count"] =
|
||||
gallery_detail::session_control_count(Gallery_Frame_Mode::Low_Latency) +
|
||||
renderable_controls;
|
||||
entry["control_count"] = item.id == "point_3d"
|
||||
? 0U
|
||||
: gallery_detail::session_control_count(Gallery_Frame_Mode::Low_Latency) +
|
||||
renderable_controls;
|
||||
entry["action_count"] =
|
||||
gallery_detail::registered_actions(
|
||||
item.id, Gallery_Frame_Mode::Low_Latency).size();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Pixel_Frame.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
#include <tmmintrin.h>
|
||||
@@ -110,4 +111,35 @@ std::string encode_pixel_frame(Image_View image, Color background) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
std::string encode_rgba8_pixel_frame(const std::byte* pixels,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::size_t stride) {
|
||||
constexpr std::size_t bytes_per_pixel = 4;
|
||||
if (pixels == nullptr || width == 0 || height == 0)
|
||||
return {};
|
||||
if (width > std::numeric_limits<std::size_t>::max() / bytes_per_pixel)
|
||||
return {};
|
||||
const std::size_t row_bytes = static_cast<std::size_t>(width) * bytes_per_pixel;
|
||||
if (stride < row_bytes ||
|
||||
height > (std::numeric_limits<std::size_t>::max() - pixel_frame_header_size) /
|
||||
row_bytes)
|
||||
return {};
|
||||
|
||||
std::string frame(pixel_frame_header_size + row_bytes * height, '\0');
|
||||
frame[0] = 'R';
|
||||
frame[1] = 'V';
|
||||
frame[2] = 'P';
|
||||
frame[3] = '1';
|
||||
write_u32_le(frame.data() + 4, width);
|
||||
write_u32_le(frame.data() + 8, height);
|
||||
write_u32_le(frame.data() + 12, static_cast<std::uint32_t>(row_bytes));
|
||||
char* output = frame.data() + pixel_frame_header_size;
|
||||
for (std::uint32_t row = 0; row < height; ++row) {
|
||||
std::memcpy(output + static_cast<std::size_t>(row) * row_bytes,
|
||||
pixels + static_cast<std::size_t>(row) * stride, row_bytes);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
} // namespace renderive::web
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#pragma once
|
||||
#include "render_2D/base/Types.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
namespace renderive::web {
|
||||
inline constexpr std::size_t pixel_frame_header_size = 16;
|
||||
[[nodiscard]] std::string encode_pixel_frame(Image_View image,
|
||||
Color background = Color::black());
|
||||
[[nodiscard]] std::string encode_rgba8_pixel_frame(const std::byte* pixels,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
std::size_t stride);
|
||||
} // namespace renderive::web
|
||||
|
||||
@@ -34,7 +34,7 @@ Gallery_Request gallery_request(Gallery_Request_Kind kind, std::string message)
|
||||
|
||||
std::string open_message(std::string_view mode) {
|
||||
return std::string(
|
||||
R"({"category":"event","type":"gallery_open","case":"datoviz_3d","frame_mode":")") +
|
||||
R"({"category":"event","type":"gallery_open","case":"point_3d","frame_mode":")") +
|
||||
std::string(mode) + R"("})";
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ TEST(RenderiveWebDatovizGallery, CatalogUsesOnlyThreeDimensionalActions) {
|
||||
const auto catalog = nlohmann::json::parse(Gallery_Protocol::catalog_json());
|
||||
const auto item = std::find_if(
|
||||
catalog.at("cases").begin(), catalog.at("cases").end(),
|
||||
[](const nlohmann::json& value) { return value.at("id") == "datoviz_3d"; });
|
||||
[](const nlohmann::json& value) { return value.at("id") == "point_3d"; });
|
||||
ASSERT_NE(item, catalog.at("cases").end());
|
||||
EXPECT_EQ(item->at("category"), "3D");
|
||||
EXPECT_EQ(item->at("control_count"), 0);
|
||||
@@ -193,11 +193,11 @@ TEST(RenderiveWebDatovizGallery, CatalogUsesOnlyThreeDimensionalActions) {
|
||||
R"({"resources":[],"observers":[],"render_plan":null,"performance_capture":null})";
|
||||
const auto state_for = [&](Gallery_Frame_Mode mode) {
|
||||
return nlohmann::json::parse(Gallery_Protocol::case_json_from_controls(
|
||||
"datoviz_3d", empty_controls, "{}", {}, mode, false));
|
||||
"point_3d", empty_controls, "{}", {}, mode, false));
|
||||
};
|
||||
const std::set<std::string> common{
|
||||
"add_sphere", "move_sphere", "remove_sphere", "reset_camera",
|
||||
"sphere_churn", "toggle_projection"};
|
||||
"add_point", "orbit_points", "remove_point", "point_churn",
|
||||
"reset_points"};
|
||||
auto expected = common;
|
||||
expected.insert({"mode_prepare", "mode_refresh", "mode_render", "mode_discard"});
|
||||
EXPECT_EQ(action_ids(state_for(Gallery_Frame_Mode::Manual)), expected);
|
||||
@@ -239,12 +239,39 @@ TEST(RenderiveWebDatovizGallery, AutomaticLowLatencyFramesCarryAnimatedState) {
|
||||
expect_actual_datoviz_pixels(later, 560, 320);
|
||||
}
|
||||
|
||||
TEST(RenderiveWebDatovizGallery, WebPointerEventsReachTheDatovizController) {
|
||||
Gallery_Plot_Session session;
|
||||
ASSERT_EQ(open_scene(session, "low_latency").at("type"), "case_state");
|
||||
const std::string before = request_pixels(session);
|
||||
|
||||
Pointer_Event press(Event_Type::Pointer_Press);
|
||||
press.position = {210.0, 135.0};
|
||||
press.button = Mouse_Button::Left;
|
||||
EXPECT_FALSE(session.handle(Web_Event{press}).has_value());
|
||||
|
||||
Pointer_Event move(Event_Type::Pointer_Move);
|
||||
move.position = {330.0, 190.0};
|
||||
move.buttons = 1;
|
||||
EXPECT_FALSE(session.handle(Web_Event{move}).has_value());
|
||||
move.position = {390.0, 225.0};
|
||||
EXPECT_FALSE(session.handle(Web_Event{move}).has_value());
|
||||
|
||||
Pointer_Event release(Event_Type::Pointer_Release);
|
||||
release.position = move.position;
|
||||
release.button = Mouse_Button::Left;
|
||||
EXPECT_FALSE(session.handle(Web_Event{release}).has_value());
|
||||
|
||||
const std::string after = request_pixels(session);
|
||||
expect_actual_datoviz_pixels(after, 560, 320);
|
||||
EXPECT_NE(after, before);
|
||||
}
|
||||
|
||||
TEST(RenderiveWebDatovizGallery, ManualRenderUsesThePreparedLogicalState) {
|
||||
Gallery_Plot_Session session;
|
||||
ASSERT_EQ(open_scene(session, "manual").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "move_sphere").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "orbit_points").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "mode_prepare").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "move_sphere").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "orbit_points").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "mode_refresh").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "mode_render").at("type"), "case_state");
|
||||
const std::string prepared = request_pixels(session);
|
||||
@@ -288,7 +315,7 @@ TEST(RenderiveWebDatovizGallery, AddedAndChurnedVisualsAreActuallyRemoved) {
|
||||
ASSERT_EQ(open_scene(session, "manual").at("type"), "case_state");
|
||||
const std::string baseline = request_pixels(session);
|
||||
|
||||
ASSERT_EQ(invoke_action(session, "add_sphere").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "add_point").at("type"), "case_state");
|
||||
manual_render_cycle(session);
|
||||
const std::string added = request_pixels(session);
|
||||
EXPECT_NE(added, baseline);
|
||||
@@ -314,23 +341,24 @@ TEST(RenderiveWebDatovizGallery, AddedAndChurnedVisualsAreActuallyRemoved) {
|
||||
const auto green_y = centroid_y([](unsigned red, unsigned green, unsigned blue) {
|
||||
return green > red + 20U && green > blue + 20U;
|
||||
});
|
||||
const auto yellow_y = centroid_y([](unsigned red, unsigned green, unsigned blue) {
|
||||
return red > 100U && green > 100U && blue < 100U;
|
||||
const auto cyan_y = centroid_y([](unsigned red, unsigned green, unsigned blue) {
|
||||
return green > red + 50U && blue > red + 50U &&
|
||||
std::abs(static_cast<int>(green) - static_cast<int>(blue)) < 30;
|
||||
});
|
||||
ASSERT_TRUE(green_y.has_value());
|
||||
ASSERT_TRUE(yellow_y.has_value());
|
||||
EXPECT_LT(*yellow_y, *green_y)
|
||||
ASSERT_TRUE(cyan_y.has_value());
|
||||
EXPECT_GT(*cyan_y, *green_y)
|
||||
<< "Frame_3D_View must expose top-left-origin RGBA rows";
|
||||
|
||||
ASSERT_EQ(invoke_action(session, "remove_sphere").at("type"), "case_state");
|
||||
ASSERT_EQ(invoke_action(session, "remove_point").at("type"), "case_state");
|
||||
manual_render_cycle(session);
|
||||
const std::string removed = request_pixels(session);
|
||||
EXPECT_EQ(removed, baseline);
|
||||
|
||||
const auto churned = invoke_action(session, "sphere_churn");
|
||||
const auto churned = invoke_action(session, "point_churn");
|
||||
ASSERT_EQ(churned.at("type"), "case_state");
|
||||
EXPECT_EQ(churned.at("telemetry").at("kernel_observer").at("last_event"),
|
||||
"sphere_churned");
|
||||
"point_churned");
|
||||
const std::string after_churn = request_pixels(session);
|
||||
expect_actual_datoviz_pixels(after_churn, 560, 320);
|
||||
EXPECT_EQ(after_churn, baseline);
|
||||
|
||||
@@ -228,10 +228,10 @@ TEST(RenderiveWebGallery, AdminiveCatalogCoversEveryControlCaseAndThreeModes) {
|
||||
EXPECT_EQ(catalog.at("protocol"), "renderive.control-gallery");
|
||||
EXPECT_EQ(catalog.at("protocol_version"), 4);
|
||||
EXPECT_EQ(catalog.at("case_descriptor").at("protocol"), "adminive.resource");
|
||||
EXPECT_EQ(catalog.at("cases").size(), 8U);
|
||||
EXPECT_EQ(catalog.at("cases").size(), 9U);
|
||||
EXPECT_EQ(catalog.at("frame_modes").size(), 3U);
|
||||
EXPECT_EQ(catalog.at("coverage").at("page_count"), 3);
|
||||
EXPECT_EQ(catalog.at("coverage").at("canvas_count"), 24);
|
||||
EXPECT_EQ(catalog.at("coverage").at("canvas_count"), 27);
|
||||
EXPECT_GT(catalog.at("coverage").at("manual_control_count").get<std::size_t>(), 180U);
|
||||
EXPECT_GT(catalog.at("coverage").at("manual_action_count").get<std::size_t>(), 50U);
|
||||
EXPECT_EQ(catalog.at("coverage").at("frequency_modes"),
|
||||
|
||||
Reference in New Issue
Block a user