走偏的一版

This commit is contained in:
2026-08-14 09:12:42 +08:00
parent ba820afbcb
commit 4a73f96475
45 changed files with 2944 additions and 1408 deletions
+55 -7
View File
@@ -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)
@@ -119,18 +123,59 @@ if (NOT TARGET Adminive::Nlohmann)
set(BUILD_TESTING "${Renderive_Web_saved_build_testing}")
unset(Renderive_Web_saved_build_testing)
endif ()
set(Renderive_Web_source_dir "${CMAKE_CURRENT_LIST_DIR}/app")
append_glob_source(Renderive_Web_sources "${Renderive_Web_source_dir}")
# 公共模块只放 2D/3D 共用的像素协议和 Web 性能基础设施。
set(Renderive_Web_Common_sources
"${CMAKE_CURRENT_LIST_DIR}/app/common/Pixel_Frame.cpp"
"${CMAKE_CURRENT_LIST_DIR}/app/Web_Performance_Log.cpp")
add_library(Renderive_Web_Common STATIC ${Renderive_Web_Common_sources})
set_target_properties(Renderive_Web_Common PROPERTIES FOLDER Renderive_Web/common)
target_include_directories(Renderive_Web_Common PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/..>")
target_compile_features(Renderive_Web_Common PUBLIC cxx_std_20)
target_compile_definitions(Renderive_Web_Common PRIVATE NOMINMAX)
target_link_libraries(Renderive_Web_Common PRIVATE spdlog::spdlog)
# 2D 模块保留现有 Render2D gallery、像素转换和旧 Web_Plot_Session。
set(Renderive_Web_2D_sources
"${CMAKE_CURRENT_LIST_DIR}/app/Pixel_Frame.cpp"
"${CMAKE_CURRENT_LIST_DIR}/app/Web_Plot_Session.cpp"
"${CMAKE_CURRENT_LIST_DIR}/app/render_2D/Gallery_Scene2D.cpp")
add_library(Renderive_Web_2D STATIC ${Renderive_Web_2D_sources})
set_target_properties(Renderive_Web_2D PROPERTIES FOLDER Renderive_Web/render_2D)
target_include_directories(Renderive_Web_2D PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/..>")
target_compile_features(Renderive_Web_2D PUBLIC cxx_std_20)
target_compile_definitions(Renderive_Web_2D PRIVATE NOMINMAX)
target_link_libraries(Renderive_Web_2D
PUBLIC Renderive_Web_Common Renderive_render_2D
PRIVATE Adminive::Nlohmann Adminive::MagicEnum magic_enum::magic_enum)
# 3D 模块只负责 Scene3D/Datoviz 到公共 RVP1 像素协议的适配。
set(Renderive_Web_3D_sources
"${CMAKE_CURRENT_LIST_DIR}/app/render_3D/Gallery_Scene3D.cpp")
add_library(Renderive_Web_3D STATIC ${Renderive_Web_3D_sources})
set_target_properties(Renderive_Web_3D PROPERTIES FOLDER Renderive_Web/render_3D)
target_include_directories(Renderive_Web_3D PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/..>")
target_compile_features(Renderive_Web_3D PUBLIC cxx_std_20)
target_compile_definitions(Renderive_Web_3D PRIVATE NOMINMAX)
target_link_libraries(Renderive_Web_3D
PUBLIC Renderive_Web_Common Renderive_render_3D
PRIVATE Adminive::Nlohmann)
set(Renderive_Web_sources
"${CMAKE_CURRENT_LIST_DIR}/app/Gallery_Plot_Session.cpp"
"${CMAKE_CURRENT_LIST_DIR}/app/Gallery_Protocol.cpp"
"${CMAKE_CURRENT_LIST_DIR}/app/Gallery_WebSocket_Controller.cpp"
"${CMAKE_CURRENT_LIST_DIR}/app/Renderive_WebSocket_Controller.cpp"
"${CMAKE_CURRENT_LIST_DIR}/app/Web_Event_Adapter.cpp"
"${CMAKE_CURRENT_LIST_DIR}/app/Web_Server.cpp")
add_library(Renderive_Web STATIC ${Renderive_Web_sources})
set_target_properties(Renderive_Web PROPERTIES FOLDER Renderive_Web)
target_include_directories(Renderive_Web PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/..>"
)
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/..>")
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
)
PUBLIC Renderive_Web_Common Renderive_Web_2D Renderive_Web_3D Drogon::Drogon Adminive::MagicEnum magic_enum::magic_enum
PRIVATE 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})
@@ -138,6 +183,9 @@ target_compile_features(Renderive_Web_Server PRIVATE cxx_std_20)
target_link_libraries(Renderive_Web_Server PRIVATE Renderive_Web)
#add_dependencies(Renderive_Web_Server Renderive_Web_Assets)
if (MSVC)
target_compile_options(Renderive_Web_Common PRIVATE /utf-8)
target_compile_options(Renderive_Web_2D PRIVATE /utf-8)
target_compile_options(Renderive_Web_3D PRIVATE /utf-8)
target_compile_options(Renderive_Web PRIVATE /utf-8)
target_compile_options(Renderive_Web_Server PRIVATE /utf-8)
endif ()
+1 -6
View File
@@ -1,5 +1,6 @@
#pragma once
#include "common/Gallery_Types.h"
#include "renderive/scene/Scene2D_Context.hpp"
#include <structive/property/property.hpp>
@@ -11,12 +12,6 @@
namespace renderive::web {
enum class Gallery_Frame_Mode : std::uint8_t {
Manual,
Low_Latency,
Playback
};
struct Gallery_Action_Category {};
struct Gallery_Action_Attribute {
+1 -22
View File
@@ -1,6 +1,7 @@
#pragma once
#include "Gallery_Enum.h"
#include "common/Gallery_Performance_Types.h"
#include "render_2D/scene/Scene.h"
#include "render_2D/renderable/Renderable.h"
@@ -59,28 +60,6 @@ struct Gallery_Render_Performance {
bool automatic_low_latency_scheduler{};
};
struct Gallery_Client_Performance {
double transport_fps{};
double presentation_fps{};
std::uint64_t websocket_buffered_bytes{};
std::uint64_t changed_pixel_frames{};
std::uint64_t duplicate_pixel_frames{};
std::uint64_t frame_request_timeout_count{};
double frame_round_trip_ms{};
double frame_round_trip_average_ms{};
double frame_round_trip_deviation_ms{};
double frame_round_trip_p95_ms{};
double frame_round_trip_p99_ms{};
double display_interval_ms{};
double display_interval_average_ms{};
double display_interval_latest_ms{};
double display_interval_p95_ms{};
double display_interval_p99_ms{};
double display_interval_deviation_ms{};
std::uint64_t overwritten_pixel_frames{};
double last_pixel_receive_age_ms{};
double last_pixel_change_age_ms{};
};
} // namespace renderive::web
File diff suppressed because it is too large Load Diff
+47 -10
View File
@@ -1,4 +1,5 @@
#include "Gallery_Protocol.h"
#include "Gallery_Actions.h"
#include "Gallery_Enum.h"
#include "Gallery_Observer_Adminive.h"
#include "Gallery_Renderables.h"
@@ -52,7 +53,9 @@ const std::vector<Case_Model>& cases() {
{"selection_overlay", "框选叠层", "Selection_Rectangle_Overlay", "交互控件",
"鼠标框选、多区域保留、标注样式和清空。", 70},
{"constellation", "星座图", "Constellation_Diagram", "点图控件",
"PSK4/PSK8/PSK16 模式、相位、寿命、坐标范围和方形拟合。", 80}
"PSK4/PSK8/PSK16 模式、相位、寿命、坐标范围和方形拟合。", 80},
{"datoviz_3d", "Datoviz 3D", "Mesh / Texture / MSDF Text", "3D",
"Render3D 通过 Kernel 帧策略驱动迁移后的 Datoviz Vulkan 后端;3D 的已发布帧禁止自动覆盖。", 90}
};
return value;
}
@@ -79,7 +82,9 @@ void append_session_actions(Gallery_Frame_Mode frame_mode,
}
throw std::invalid_argument("unknown gallery frame mode");
}
std::size_t session_control_count(Gallery_Frame_Mode frame_mode) {
std::size_t session_control_count(std::string_view case_id, Gallery_Frame_Mode frame_mode) {
if (case_id == "datoviz_3d")
return 0;
switch (frame_mode) {
case Gallery_Frame_Mode::Manual:
return gallery_control_count<Gallery_Session_Control<Manual_Scene2D>>();
@@ -90,9 +95,41 @@ std::size_t session_control_count(Gallery_Frame_Mode frame_mode) {
}
throw std::invalid_argument("unknown gallery frame mode");
}
std::vector<Action_Model> registered_actions(std::string_view case_id,
Gallery_Frame_Mode frame_mode) {
void append_3d_actions(Gallery_Frame_Mode frame_mode, std::vector<Action_Model>& result) {
const auto append = [&result](const Gallery_Action_Attribute& action) {
result.push_back(gallery_action_model(action));
};
switch (frame_mode) {
case Gallery_Frame_Mode::Manual:
append(gallery_action("mode_prepare", "准备 3D 帧", "Manual_Scene3D::prepare_frame", "3D 帧策略"));
append(gallery_action("mode_refresh", "提交手动刷新", "Manual_Scene3D::refresh", "3D 帧策略"));
append(gallery_action("mode_render", "渲染已刷新帧", "Manual_Scene3D::render_frame", "3D 帧策略", {}, {}, {}, 0.0, true));
break;
case Gallery_Frame_Mode::Low_Latency:
append(gallery_action("mode_prepare", "发布 3D 帧", "Scene3D::prepare_frame", "3D 帧策略"));
append(gallery_action("mode_render", "消费 3D 帧", "Scene3D::render_frame", "3D 帧策略", {}, {}, {}, 0.0, true));
break;
case Gallery_Frame_Mode::Playback:
append(gallery_action("mode_enqueue", "压入 3D 帧", "Playback_Scene3D::prepare_frame", "3D 帧策略"));
append(gallery_action("mode_dequeue", "消费队首 3D 帧", "Playback_Scene3D::render_frame", "3D 帧策略", {}, {}, {}, 0.0, true));
break;
}
append(gallery_action("move_mesh", "移动蓝色对象", "Mesh::place", "3D 场景"));
append(gallery_action("add_mesh", "添加黄色对象", "Scene_Edit::attach", "3D 场景"));
append(gallery_action("remove_mesh", "移除黄色对象", "Scene_Edit::detach", "3D 场景"));
append(gallery_action("mesh_churn", "Attach/Detach 验证", "Scene_Edit::attach/detach", "3D 场景"));
append(gallery_action("reset_camera", "重置相机", "Camera::look_at/use_perspective", "3D 相机"));
append(gallery_action("toggle_projection", "切换投影", "Camera::use_perspective/use_orthographic", "3D 相机"));
append(gallery_action("toggle_texture", "切换纹理", "Material::use_texture", "3D 材质"));
append(gallery_action("change_text", "修改 MSDF 文本", "Text::set_string", "3D 文本"));
append(gallery_action("reset", "恢复 3D 场景", "Gallery_Scene3D factory", "3D 场景"));
}
std::vector<Action_Model> registered_actions(std::string_view case_id, Gallery_Frame_Mode frame_mode) {
std::vector<Action_Model> result;
if (case_id == "datoviz_3d") {
append_3d_actions(frame_mode, result);
return result;
}
append_session_actions(frame_mode, result);
for (auto& action : gallery_frame_actions(frame_mode))
result.push_back(std::move(action));
@@ -116,7 +153,7 @@ struct Type_Descriptor<renderive::web::gallery_detail::Case_Model> {
return object<T>("renderive_gallery_case",
ADMINIVE_FIELD_LABEL(T, id, "标识"),
ADMINIVE_FIELD_LABEL(T, title, "标题"),
ADMINIVE_FIELD_LABEL(T, component, "Core2 控"),
ADMINIVE_FIELD_LABEL(T, component, "Renderive 组"),
ADMINIVE_FIELD_LABEL(T, category, "分类"),
ADMINIVE_FIELD_LABEL(T, description, "说明"),
ADMINIVE_FIELD_LABEL(T, order, "顺序"),
@@ -133,7 +170,7 @@ struct Type_Descriptor<renderive::web::gallery_detail::Action_Model> {
return object<T>("renderive_gallery_action",
ADMINIVE_FIELD_LABEL(T, id, "动作"),
ADMINIVE_FIELD_LABEL(T, label, "名称"),
ADMINIVE_FIELD_LABEL(T, api, "Core2 API"),
ADMINIVE_FIELD_LABEL(T, api, "Renderive API"),
ADMINIVE_FIELD_LABEL(T, description, "说明"),
ADMINIVE_FIELD_LABEL(T, group, "分组"),
ADMINIVE_FIELD_LABEL(T, argument_input, "参数类型"),
@@ -153,7 +190,7 @@ adminive::Table_View action_view() {
return adminive::table_view<T>(
adminive::column<&T::label>("动作"),
adminive::column<&T::group>("分组"),
adminive::column<&T::api>("Core2 API"))
adminive::column<&T::api>("Renderive API"))
.titled("保留 API 动作");
}
@@ -517,11 +554,11 @@ std::string Gallery_Protocol::catalog_json() {
Json entry = adminive::to_frontend_json<Json>(item);
entry["control_count_by_mode"] = Json::object();
entry["action_count_by_mode"] = Json::object();
const std::size_t renderable_controls = gallery_renderable_control_count(item.id);
const std::size_t renderable_controls = item.id == "datoviz_3d" ? 0 : 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;
gallery_detail::session_control_count(item.id, mode) + renderable_controls;
const std::size_t actions =
gallery_detail::registered_actions(item.id, mode).size();
entry["control_count_by_mode"][key] = controls;
@@ -530,7 +567,7 @@ std::string Gallery_Protocol::catalog_json() {
actions_total += actions;
}
entry["control_count"] =
gallery_detail::session_control_count(Gallery_Frame_Mode::Low_Latency) +
gallery_detail::session_control_count(item.id, Gallery_Frame_Mode::Low_Latency) +
renderable_controls;
entry["action_count"] =
gallery_detail::registered_actions(
+1 -6
View File
@@ -1,5 +1,5 @@
#pragma once
#include "Gallery_Actions.h"
#include "common/Gallery_Types.h"
#include <optional>
#include <string>
@@ -10,11 +10,6 @@ struct Gallery_Open_Request {
std::string case_id;
Gallery_Frame_Mode frame_mode = Gallery_Frame_Mode::Low_Latency;
};
using Gallery_Value = std::variant<bool, double, std::string>;
struct Gallery_Action_Request {
std::string id;
std::optional<Gallery_Value> argument;
};
struct Gallery_Control_Patch_Request {
std::string target;
std::string patch_json;
+1 -1
View File
@@ -1,9 +1,9 @@
#pragma once
#include "common/Pixel_Frame.h"
#include "render_2D/base/Types.h"
#include <cstddef>
#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());
} // namespace renderive::web
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include <cstdint>
#include <variant>
namespace renderive::web {
enum class Gallery_View_Input_Type : std::uint8_t {
Show,
Hide,
Leave
};
struct Gallery_View_Input {
Gallery_View_Input_Type type{};
};
enum class Gallery_Pointer_Input_Type : std::uint8_t {
Move,
Press,
Release
};
enum class Gallery_Mouse_Button : std::uint8_t {
None,
Left,
Right,
Middle
};
struct Gallery_Pointer_Input {
Gallery_Pointer_Input_Type type{};
double x{};
double y{};
double global_x{};
double global_y{};
Gallery_Mouse_Button button{};
std::uint8_t buttons{};
std::uint8_t modifiers{};
};
struct Gallery_Wheel_Input {
double x{};
double y{};
double global_x{};
double global_y{};
Gallery_Mouse_Button button{};
std::uint8_t buttons{};
std::uint8_t modifiers{};
double angle_delta_x{};
double angle_delta_y{};
double pixel_delta_x{};
double pixel_delta_y{};
};
enum class Gallery_Key_Input_Type : std::uint8_t {
Press,
Release
};
enum class Gallery_Key : std::uint16_t {
Unknown,
Escape,
Enter,
Space,
Delete,
Backspace,
Left,
Right,
Up,
Down
};
struct Gallery_Key_Input {
Gallery_Key_Input_Type type{};
Gallery_Key key{};
std::uint32_t native_key{};
std::uint8_t modifiers{};
bool auto_repeat{};
};
using Gallery_Input_Event = std::variant<Gallery_View_Input, Gallery_Pointer_Input, Gallery_Wheel_Input, Gallery_Key_Input>;
}
@@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
namespace renderive::web {
struct Gallery_Client_Performance {
double transport_fps{};
double presentation_fps{};
std::uint64_t websocket_buffered_bytes{};
std::uint64_t changed_pixel_frames{};
std::uint64_t duplicate_pixel_frames{};
std::uint64_t frame_request_timeout_count{};
double frame_round_trip_ms{};
double frame_round_trip_average_ms{};
double frame_round_trip_deviation_ms{};
double frame_round_trip_p95_ms{};
double frame_round_trip_p99_ms{};
double display_interval_ms{};
double display_interval_average_ms{};
double display_interval_latest_ms{};
double display_interval_p95_ms{};
double display_interval_p99_ms{};
double display_interval_deviation_ms{};
std::uint64_t overwritten_pixel_frames{};
double last_pixel_receive_age_ms{};
double last_pixel_change_age_ms{};
};
}
@@ -0,0 +1,34 @@
#pragma once
#include "Gallery_Input.h"
#include "Gallery_Performance_Types.h"
#include "Gallery_Types.h"
#include "adminive/adminive.hpp"
#include <nlohmann/json.hpp>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
namespace renderive::web {
class Gallery_Scene_Interface {
public:
virtual ~Gallery_Scene_Interface() = default;
[[nodiscard]] virtual const std::string& case_id() const noexcept = 0;
[[nodiscard]] virtual nlohmann::json controls() const = 0;
[[nodiscard]] virtual Gallery_Frame_Mode frame_mode() const noexcept = 0;
virtual void reset_monitoring() = 0;
[[nodiscard]] virtual bool can_render_automatically() const noexcept = 0;
[[nodiscard]] virtual std::uint64_t kernel_refresh_interval_ns() const = 0;
virtual void set_client_metrics(Gallery_Client_Performance metrics) noexcept = 0;
[[nodiscard]] virtual adminive::Update_Result apply_patch(std::string_view target, const nlohmann::json& patch) = 0;
virtual void resize(int width, int height) = 0;
virtual void dispatch(const Gallery_Input_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;
[[nodiscard]] virtual std::string action(const Gallery_Action_Request& request, bool& recognized) = 0;
virtual void record_action_notice_if_empty(std::string_view notice) = 0;
[[nodiscard]] virtual std::string telemetry_json() const = 0;
};
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
#include <optional>
#include <string>
#include <variant>
namespace renderive::web {
enum class Gallery_Frame_Mode : std::uint8_t {
Manual,
Low_Latency,
Playback
};
using Gallery_Value = std::variant<bool, double, std::string>;
struct Gallery_Action_Request {
std::string id;
std::optional<Gallery_Value> argument;
};
}
+35
View File
@@ -0,0 +1,35 @@
#include "Pixel_Frame.h"
#include <cstring>
#include <limits>
namespace renderive::web {
namespace {
void write_u32_le(char* target, std::uint32_t value) {
target[0] = static_cast<char>(value & 0xffU);
target[1] = static_cast<char>((value >> 8U) & 0xffU);
target[2] = static_cast<char>((value >> 16U) & 0xffU);
target[3] = static_cast<char>((value >> 24U) & 0xffU);
}
}
std::string encode_rgba8_pixel_frame(const std::byte* data, std::uint32_t width, std::uint32_t height, std::uint32_t stride) {
if (data == nullptr || width == 0 || height == 0 || stride < width * 4U)
return {};
const std::size_t row_size = static_cast<std::size_t>(width) * 4U;
if (static_cast<std::size_t>(height) > (std::numeric_limits<std::size_t>::max() - pixel_frame_header_size) / row_size)
return {};
const std::size_t pixel_bytes = row_size * height;
std::string frame(pixel_frame_header_size + pixel_bytes, '\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_size));
char* output = frame.data() + pixel_frame_header_size;
for (std::uint32_t y = 0; y < height; ++y) {
const auto* row = data + static_cast<std::size_t>(y) * stride;
std::memcpy(output + static_cast<std::size_t>(y) * row_size, row, row_size);
}
return frame;
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
namespace renderive::web {
inline constexpr std::size_t pixel_frame_header_size = 16;
[[nodiscard]] std::string encode_rgba8_pixel_frame(const std::byte* data, std::uint32_t width, std::uint32_t height, std::uint32_t stride);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
#pragma once
#include "../common/Gallery_Scene_Interface.h"
#include <cstdint>
#include <memory>
#include <string>
namespace renderive::web {
[[nodiscard]] std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene_2d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency);
}
@@ -0,0 +1,331 @@
#include "Gallery_Scene3D.h"
#include "../common/Pixel_Frame.h"
#include <render_3D/scene/Basic_Scene3D.h>
#include <render_3D/text/Text.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <concepts>
#include <memory>
#include <stdexcept>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace renderive::web {
namespace {
using namespace renderive::render_3D;
Geometry_Handle triangle(float center_x, float center_y, float radius = 0.28F) {
auto geometry = std::make_shared<Geometry_Data>();
geometry->vertices = {
{center_x - radius, center_y - radius, 0.0F},
{center_x + radius, center_y - radius, 0.0F},
{center_x, center_y + radius, 0.0F},
};
geometry->normals = {{0.0F, 0.0F, 1.0F}, {0.0F, 0.0F, 1.0F}, {0.0F, 0.0F, 1.0F}};
geometry->uv = {{0.0F, 1.0F}, {1.0F, 1.0F}, {0.5F, 0.0F}};
geometry->indices = {0, 1, 2};
return geometry;
}
Image_Handle checkerboard() {
auto image = std::make_shared<Image_Data>();
image->width = 4;
image->height = 4;
image->rgba8.resize(4U * 4U * 4U);
for (std::uint32_t y = 0; y < image->height; ++y) {
for (std::uint32_t x = 0; x < image->width; ++x) {
const bool bright = ((x + y) & 1U) == 0;
const std::size_t offset = (static_cast<std::size_t>(y) * image->width + x) * 4U;
image->rgba8[offset + 0] = 255;
image->rgba8[offset + 1] = bright ? 210 : 40;
image->rgba8[offset + 2] = bright ? 210 : 40;
image->rgba8[offset + 3] = 255;
}
}
return image;
}
Matrix4 translated(float x, float y) {
Matrix4 result;
result.elements[12] = x;
result.elements[13] = y;
return result;
}
renderive_Owner<Material> material(Color4 color) {
return renderive_Owner<Material>::make(Material_Properties{.base_color = color});
}
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";
}
template <class Scene_Type>
class Gallery_Scene3D final : public Gallery_Scene_Interface {
public:
Gallery_Scene3D(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode frame_mode, bool automatic_low_latency)
: session_id_(session_id), case_id_(std::move(case_id)), frame_mode_(frame_mode), automatic_low_latency_(automatic_low_latency), scene_({.width = 560, .height = 320}) {
camera_ = renderive_Owner<Camera>::make();
red_material_ = material({1.0F, 0.10F, 0.08F, 1.0F});
green_material_ = material({0.08F, 1.0F, 0.14F, 1.0F});
blue_material_ = material({0.10F, 0.20F, 1.0F, 1.0F});
yellow_material_ = material({1.0F, 0.90F, 0.08F, 1.0F});
texture_ = renderive_Owner<Texture>::make(Texture_Properties{checkerboard()});
red_material_->use_texture(texture_);
red_mesh_ = renderive_Owner<Mesh>::make(Mesh_Properties{.geometry = triangle(-0.62F, -0.10F), .material = red_material_});
green_mesh_ = renderive_Owner<Mesh>::make(Mesh_Properties{.geometry = triangle(0.0F, -0.10F), .material = green_material_});
blue_mesh_ = renderive_Owner<Mesh>::make(Mesh_Properties{.geometry = triangle(0.62F, -0.10F), .material = blue_material_});
extra_mesh_ = renderive_Owner<Mesh>::make(Mesh_Properties{.geometry = triangle(0.0F, 0.62F, 0.18F), .material = yellow_material_});
text_ = renderive_Owner<Text>::make(Text_Properties{.string = "Renderive 3D · MSDF", .position = {-0.86F, 0.78F, 0.0F}, .size_px = 20.0F, .color = {1.0F, 1.0F, 1.0F, 1.0F}});
scene_.update([this](Scene_Edit& edit) {
edit.set_camera(camera_);
edit.attach(red_mesh_);
edit.attach(green_mesh_);
edit.attach(blue_mesh_);
edit.attach(text_);
});
initial_render();
}
[[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 frame_mode_;
}
void reset_monitoring() override {
render_attempt_count_ = 0;
successful_render_count_ = 0;
pixel_frame_count_ = 0;
last_render_ms_ = 0.0;
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_.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;
scene_.resize(static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height));
last_event_ = "resized";
}
void dispatch(const Gallery_Input_Event& event) override {
const auto* view = std::get_if<Gallery_View_Input>(&event);
if (!view)
return;
if (view->type == Gallery_View_Input_Type::Show)
active_ = true;
else if (view->type == Gallery_View_Input_Type::Hide)
active_ = false;
}
bool render_latest_frame() override {
if (!can_render_automatically())
return false;
if (scene_.pending_frame_count() != 0)
return render_prepared();
advance_blue_mesh();
return prepare_and_render();
}
[[nodiscard]] std::optional<std::string> encode_latest_pixels() override {
if (!active_)
return std::nullopt;
std::string pixels;
if (!scene_.with_frame([&pixels](Frame_3D_View frame) {
pixels = encode_rgba8_pixel_frame(frame.data, frame.width, frame.height, frame.stride);
}))
return std::nullopt;
return pixels.empty() ? std::nullopt : std::optional<std::string>(std::move(pixels));
}
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<double, std::milli>(encode_finished - encode_started).count();
}
[[nodiscard]] std::string action(const Gallery_Action_Request& request, bool& recognized) override {
recognized = true;
if (request.id == "mode_prepare" || request.id == "mode_enqueue") {
if constexpr (std::same_as<Scene_Type, Playback_Scene3D>)
advance_playback_mesh();
const bool prepared = scene_.prepare_frame();
last_event_ = prepared ? "frame_prepared" : "prepare_rejected";
return prepared ? "3D 帧已准备" : "3D 帧准备失败";
}
if (request.id == "mode_refresh") {
if constexpr (std::same_as<Scene_Type, Manual_Scene3D>) {
const bool refreshed = scene_.refresh();
last_event_ = refreshed ? "manual_refreshed" : "manual_refresh_failed";
return refreshed ? "Manual 3D 帧已刷新" : "没有可刷新的 3D 帧";
}
}
if (request.id == "mode_render" || request.id == "mode_dequeue") {
const bool rendered = render_prepared();
last_event_ = rendered ? "frame_rendered" : "render_rejected";
return rendered ? "3D 准备帧已渲染" : "没有可渲染的 3D 帧";
}
if (request.id == "move_mesh") {
advance_blue_mesh();
last_event_ = "mesh_moved";
return "蓝色 3D 对象已移动";
}
if (request.id == "add_mesh") {
if (!extra_attached_) {
scene_.update([this](Scene_Edit& edit) { edit.attach(extra_mesh_); });
extra_attached_ = true;
}
last_event_ = "mesh_added";
return "黄色 3D 对象已加入";
}
if (request.id == "remove_mesh") {
if (extra_attached_) {
scene_.update([this](Scene_Edit& edit) { edit.detach(extra_mesh_->identity()); });
extra_attached_ = false;
}
last_event_ = "mesh_removed";
return "黄色 3D 对象已移除";
}
if (request.id == "mesh_churn") {
if (!extra_attached_) {
scene_.update([this](Scene_Edit& edit) {
edit.attach(extra_mesh_);
edit.detach(extra_mesh_->identity());
});
}
last_event_ = "mesh_churned";
return "3D 对象 attach/detach 生命周期已执行";
}
if (request.id == "reset_camera") {
camera_->look_at({0.0F, 0.0F, 3.0F}, {}, {0.0F, 1.0F, 0.0F});
camera_->use_perspective(0.785398163F, 0.01F, 1000.0F);
orthographic_ = false;
last_event_ = "camera_reset";
return "3D 相机已重置";
}
if (request.id == "toggle_projection") {
orthographic_ = !orthographic_;
if (orthographic_)
camera_->use_orthographic(2.2F, 0.01F, 1000.0F);
else
camera_->use_perspective(0.785398163F, 0.01F, 1000.0F);
last_event_ = "projection_toggled";
return orthographic_ ? "已切换正交投影" : "已切换透视投影";
}
if (request.id == "toggle_texture") {
texture_enabled_ = !texture_enabled_;
red_material_->use_texture(texture_enabled_ ? texture_ : renderive_Owner<Texture>{});
last_event_ = "texture_toggled";
return texture_enabled_ ? "纹理已启用" : "纹理已关闭";
}
if (request.id == "change_text") {
alternate_text_ = !alternate_text_;
text_->set_string(alternate_text_ ? "Renderive + Datoviz" : "Renderive 3D · MSDF");
last_event_ = "text_changed";
return "MSDF 文本已更新";
}
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 {
return nlohmann::json{{"kernel_observer", {{"mode", mode_id(frame_mode_)}, {"last_event", last_event_}, {"pending_frame_count", scene_.pending_frame_count()}, {"dropped_frame_count", 0}}}, {"render_performance", {{"render_attempt_count", render_attempt_count_}, {"successful_render_count", successful_render_count_}, {"failed_render_count", render_attempt_count_ - successful_render_count_}, {"last_render_ms", last_render_ms_}, {"pixel_response_fps", 0.0}, {"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}}}, {"session_id", session_id_}, {"last_action", last_action_result_}}.dump();
}
private:
void initial_render() {
if constexpr (std::same_as<Scene_Type, Manual_Scene3D>) {
if (scene_.prepare_frame() && scene_.refresh())
render_prepared();
} else if (scene_.prepare_frame()) {
render_prepared();
}
}
bool prepare_and_render() {
if (!scene_.prepare_frame())
return false;
return render_prepared();
}
bool render_prepared() {
const auto started = std::chrono::steady_clock::now();
++render_attempt_count_;
const bool rendered = scene_.render_frame();
last_render_ms_ = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - started).count();
successful_render_count_ += rendered ? 1U : 0U;
return rendered;
}
void advance_blue_mesh() {
blue_offset_ += 0.08F;
if (blue_offset_ > 0.42F)
blue_offset_ = -0.42F;
blue_mesh_->place(translated(blue_offset_, 0.0F));
}
void advance_playback_mesh() {
blue_offset_ = std::min(0.25F, blue_offset_ + 0.0125F);
blue_mesh_->place(translated(blue_offset_, 0.0F));
}
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};
Scene_Type scene_;
renderive_Owner<Camera> camera_;
renderive_Owner<Texture> texture_;
renderive_Owner<Material> red_material_;
renderive_Owner<Material> green_material_;
renderive_Owner<Material> blue_material_;
renderive_Owner<Material> yellow_material_;
renderive_Owner<Mesh> red_mesh_;
renderive_Owner<Mesh> green_mesh_;
renderive_Owner<Mesh> blue_mesh_;
renderive_Owner<Mesh> extra_mesh_;
renderive_Owner<Text> text_;
Gallery_Client_Performance client_performance_;
float blue_offset_{};
bool extra_attached_{};
bool orthographic_{};
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::string last_event_{"initialized"};
std::string last_action_result_;
};
}
std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene_3d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency) {
switch (mode) {
case Gallery_Frame_Mode::Manual:
return std::make_unique<Gallery_Scene3D<Manual_Scene3D>>(session_id, std::move(case_id), mode, automatic_low_latency);
case Gallery_Frame_Mode::Low_Latency:
return std::make_unique<Gallery_Scene3D<Scene3D>>(session_id, std::move(case_id), mode, automatic_low_latency);
case Gallery_Frame_Mode::Playback:
return std::make_unique<Gallery_Scene3D<Playback_Scene3D>>(session_id, std::move(case_id), mode, automatic_low_latency);
}
throw std::invalid_argument("unknown gallery frame mode");
}
}
@@ -0,0 +1,8 @@
#pragma once
#include "../common/Gallery_Scene_Interface.h"
#include <cstdint>
#include <memory>
#include <string>
namespace renderive::web {
[[nodiscard]] std::unique_ptr<Gallery_Scene_Interface> make_gallery_scene_3d(std::uint64_t session_id, std::string case_id, Gallery_Frame_Mode mode, bool automatic_low_latency);
}
+37 -12
View File
@@ -1,6 +1,6 @@
#include "../app/Gallery_Plot_Session.h"
#include "../app/Gallery_Protocol.h"
#include "../app/Pixel_Frame.h"
#include "../app/common/Pixel_Frame.h"
#include <gtest/gtest.h>
#include <nlohmann/json.hpp>
@@ -196,17 +196,19 @@ TEST(RenderiveWebDatovizGallery, CatalogUsesOnlyThreeDimensionalActions) {
"datoviz_3d", empty_controls, "{}", {}, mode, false));
};
const std::set<std::string> common{
"add_sphere", "move_sphere", "remove_sphere", "reset_camera",
"sphere_churn", "toggle_projection"};
"add_mesh", "change_text", "move_mesh", "remove_mesh", "reset",
"reset_camera", "mesh_churn", "toggle_projection", "toggle_texture"};
auto expected = common;
expected.insert({"mode_prepare", "mode_refresh", "mode_render", "mode_discard"});
expected.insert({"mode_prepare", "mode_refresh", "mode_render"});
EXPECT_EQ(action_ids(state_for(Gallery_Frame_Mode::Manual)), expected);
expected = common;
expected.insert({"mode_prepare", "mode_render", "mode_discard"});
expected.insert({"mode_prepare", "mode_render"});
EXPECT_EQ(action_ids(state_for(Gallery_Frame_Mode::Low_Latency)), expected);
expected = common;
expected.insert({"mode_enqueue", "mode_dequeue"});
EXPECT_EQ(action_ids(state_for(Gallery_Frame_Mode::Playback)), expected);
EXPECT_FALSE(action_ids(state_for(Gallery_Frame_Mode::Manual)).contains("mode_discard"));
EXPECT_FALSE(action_ids(state_for(Gallery_Frame_Mode::Low_Latency)).contains("mode_discard"));
}
TEST(RenderiveWebDatovizGallery, PublishesRealRgbaAndRendersAfterResize) {
@@ -242,9 +244,9 @@ TEST(RenderiveWebDatovizGallery, AutomaticLowLatencyFramesCarryAnimatedState) {
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, "move_mesh").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, "move_mesh").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);
@@ -259,7 +261,8 @@ TEST(RenderiveWebDatovizGallery, ManualRenderUsesThePreparedLogicalState) {
TEST(RenderiveWebDatovizGallery, PlaybackDequeuesHistoricalStatesInOrder) {
Gallery_Plot_Session session;
ASSERT_EQ(open_scene(session, "playback").at("type"), "case_state");
ASSERT_EQ(invoke_action(session, "mode_enqueue").at("type"), "case_state");
for (int index = 0; index < 20; ++index)
ASSERT_EQ(invoke_action(session, "mode_enqueue").at("type"), "case_state");
ASSERT_EQ(invoke_action(session, "mode_dequeue").at("type"), "case_state");
const std::string first = request_pixels(session);
@@ -288,7 +291,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_mesh").at("type"), "case_state");
manual_render_cycle(session);
const std::string added = request_pixels(session);
EXPECT_NE(added, baseline);
@@ -322,19 +325,41 @@ TEST(RenderiveWebDatovizGallery, AddedAndChurnedVisualsAreActuallyRemoved) {
EXPECT_LT(*yellow_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_mesh").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, "mesh_churn");
ASSERT_EQ(churned.at("type"), "case_state");
EXPECT_EQ(churned.at("telemetry").at("kernel_observer").at("last_event"),
"sphere_churned");
"mesh_churned");
const std::string after_churn = request_pixels(session);
expect_actual_datoviz_pixels(after_churn, 560, 320);
EXPECT_EQ(after_churn, baseline);
}
TEST(RenderiveWebDatovizGallery, TextureAndTextActionsReachTheThreeDimensionalBackend) {
Gallery_Plot_Session session;
ASSERT_EQ(open_scene(session, "manual").at("type"), "case_state");
const std::string baseline = request_pixels(session);
expect_actual_datoviz_pixels(baseline, 560, 320);
ASSERT_EQ(invoke_action(session, "toggle_texture").at("type"), "case_state");
manual_render_cycle(session);
const std::string without_texture = request_pixels(session);
EXPECT_NE(without_texture, baseline);
ASSERT_EQ(invoke_action(session, "toggle_texture").at("type"), "case_state");
manual_render_cycle(session);
const std::string restored_texture = request_pixels(session);
EXPECT_EQ(restored_texture, baseline);
ASSERT_EQ(invoke_action(session, "change_text").at("type"), "case_state");
manual_render_cycle(session);
const std::string changed_text = request_pixels(session);
EXPECT_NE(changed_text, restored_texture);
}
} // namespace
} // namespace renderive::web
+2 -2
View File
@@ -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"),