332 lines
15 KiB
C++
332 lines
15 KiB
C++
#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");
|
|
}
|
|
}
|