3D 优化
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
#include "Gallery_Plots.hpp"
|
||||
#include <array>
|
||||
#include <ranges>
|
||||
|
||||
namespace aethera::web {
|
||||
namespace {
|
||||
constexpr std::array definitions{
|
||||
Plot_Definition{"axes", "坐标轴", "基础组件", "二维数值轴与时间轴布局。", Plot_Dimension::two_d, make_axes_plot},
|
||||
Plot_Definition{"spectrum", "频谱图", "频域分析", "实时频谱、保持曲线和标记。", Plot_Dimension::two_d, make_spectrum_plot},
|
||||
Plot_Definition{"frequency_trace", "频率轨迹", "频域分析", "按时间推进的频率轨迹。", Plot_Dimension::two_d, make_frequency_trace_plot},
|
||||
Plot_Definition{"sweep_spectrum", "扫频图", "频域分析", "分块到达的扫频数据。", Plot_Dimension::two_d, make_sweep_spectrum_plot},
|
||||
Plot_Definition{"afterglow", "余辉图", "密度分析", "带衰减历史的频率功率密度。", Plot_Dimension::two_d, make_afterglow_plot},
|
||||
Plot_Definition{"waterfall", "瀑布图", "密度分析", "分区并行绘制的频谱历史。", Plot_Dimension::two_d, make_waterfall_plot},
|
||||
Plot_Definition{"constellation", "星座图", "信号分析", "调制星座点、参考锚点与选区。", Plot_Dimension::two_d, make_constellation_plot},
|
||||
Plot_Definition{"selection_overlay", "矩形选区", "交互组件", "独立的二维矩形选择覆盖层。", Plot_Dimension::two_d, make_selection_overlay_plot},
|
||||
Plot_Definition{"datoviz_point", "三维点图", "Datoviz 图元", "Datoviz point visual。", Plot_Dimension::three_d, make_datoviz_point_plot},
|
||||
Plot_Definition{"datoviz_splat", "三维高斯 Splat", "Datoviz 图元", "Datoviz Gaussian splat visual。", Plot_Dimension::three_d, make_datoviz_splat_plot},
|
||||
Plot_Definition{"datoviz_pixel", "三维像素", "Datoviz 图元", "Datoviz pixel visual。", Plot_Dimension::three_d, make_datoviz_pixel_plot},
|
||||
Plot_Definition{"datoviz_marker", "三维标记", "Datoviz 图元", "可拾取并显示坐标的 marker visual。", Plot_Dimension::three_d, make_datoviz_marker_plot},
|
||||
Plot_Definition{"datoviz_sphere", "三维球体", "Datoviz 图元", "Datoviz sphere visual。", Plot_Dimension::three_d, make_datoviz_sphere_plot},
|
||||
Plot_Definition{"datoviz_segment", "三维线段", "Datoviz 图元", "Datoviz segment visual。", Plot_Dimension::three_d, make_datoviz_segment_plot},
|
||||
Plot_Definition{"datoviz_vector", "三维向量", "Datoviz 图元", "Datoviz vector visual。", Plot_Dimension::three_d, make_datoviz_vector_plot},
|
||||
Plot_Definition{"datoviz_primitive", "三维图元", "Datoviz 图元", "基础拓扑顶点图元。", Plot_Dimension::three_d, make_datoviz_primitive_plot},
|
||||
Plot_Definition{"datoviz_mesh", "三维网格", "Datoviz 表面", "带法线和纹理坐标的 mesh visual。", Plot_Dimension::three_d, make_datoviz_mesh_plot},
|
||||
Plot_Definition{"datoviz_spectrogram", "三维频谱瀑布", "Datoviz 表面", "动态频谱网格与峰值标记。", Plot_Dimension::three_d, make_datoviz_spectrogram_plot},
|
||||
Plot_Definition{"datoviz_path", "三维路径", "Datoviz 图元", "Datoviz path visual。", Plot_Dimension::three_d, make_datoviz_path_plot},
|
||||
Plot_Definition{"datoviz_image", "三维图像", "Datoviz 纹理", "空间中的采样图像。", Plot_Dimension::three_d, make_datoviz_image_plot},
|
||||
Plot_Definition{"datoviz_labels", "三维标签", "Datoviz 纹理", "分类字段标签 visual。", Plot_Dimension::three_d, make_datoviz_labels_plot},
|
||||
Plot_Definition{"datoviz_glyph", "三维字形", "Datoviz 文本", "可控制几何和 atlas 坐标的 glyph visual。", Plot_Dimension::three_d, make_datoviz_glyph_plot},
|
||||
Plot_Definition{"datoviz_text", "三维文本", "Datoviz 文本", "动态 UTF-8 文本标签。", Plot_Dimension::three_d, make_datoviz_text_plot},
|
||||
Plot_Definition{"datoviz_volume", "三维体数据", "Datoviz 体渲染", "动态标量体字段的最大强度投影。", Plot_Dimension::three_d, make_datoviz_volume_plot},
|
||||
};
|
||||
}
|
||||
|
||||
std::span<const Plot_Definition> gallery_plot_definitions() noexcept {
|
||||
return definitions;
|
||||
}
|
||||
|
||||
const Plot_Definition* find_gallery_plot_definition(
|
||||
std::string_view id) noexcept {
|
||||
const auto found = std::ranges::find(definitions, id,
|
||||
&Plot_Definition::id);
|
||||
return found == definitions.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
std::string_view plot_dimension_name(Plot_Dimension dimension) noexcept {
|
||||
return dimension == Plot_Dimension::three_d ? "3D" : "2D";
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,25 @@
|
||||
#pragma once
|
||||
#include "Gallery_Plots_2D.hpp"
|
||||
#include "Gallery_Plots_3D.hpp"
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
namespace aethera::web {
|
||||
enum struct Plot_Dimension : std::uint8_t { two_d, three_d };
|
||||
|
||||
struct Plot_Definition {
|
||||
std::string_view id; /* 协议和路由使用的稳定标识。 */
|
||||
std::string_view title; /* 前端直接展示的业务名称。 */
|
||||
std::string_view category; /* Catalog 分组名称。 */
|
||||
std::string_view description; /* Plot 能力说明。 */
|
||||
Plot_Dimension dimension{Plot_Dimension::two_d}; /* 渲染后端维度。 */
|
||||
std::shared_ptr<Plot> (*create)(); /* 创建该 Plot 唯一实例的工厂。 */
|
||||
};
|
||||
|
||||
[[nodiscard]] std::span<const Plot_Definition>
|
||||
gallery_plot_definitions() noexcept;
|
||||
[[nodiscard]] const Plot_Definition* find_gallery_plot_definition(
|
||||
std::string_view id) noexcept;
|
||||
[[nodiscard]] std::string_view plot_dimension_name(
|
||||
Plot_Dimension dimension) noexcept;
|
||||
}
|
||||
|
||||
@@ -207,7 +207,68 @@ private:
|
||||
std::uniform_real_distribution<float> first{};
|
||||
std::uniform_real_distribution<float> second{};
|
||||
};
|
||||
struct Random_Data_Generator {};
|
||||
struct Random_Data_Generator {
|
||||
static std::uint64_t mix(std::uint64_t value) noexcept {
|
||||
value += 0x9E3779B97F4A7C15ULL;
|
||||
value = (value ^ (value >> 30U)) * 0xBF58476D1CE4E5B9ULL;
|
||||
value = (value ^ (value >> 27U)) * 0x94D049BB133111EBULL;
|
||||
return value ^ (value >> 31U);
|
||||
}
|
||||
static float signed_unit(std::uint64_t sequence, std::size_t index,
|
||||
std::uint64_t lane) noexcept {
|
||||
const auto bits = mix(sequence ^
|
||||
(static_cast<std::uint64_t>(index) << 8U) ^ lane);
|
||||
return static_cast<float>(bits >> 40U) /
|
||||
static_cast<float>(1U << 24U) * 2.0F - 1.0F;
|
||||
}
|
||||
static void push(Vec3& value, std::uint64_t sequence,
|
||||
std::size_t index, std::uint64_t lane,
|
||||
float amount = 0.018F) noexcept {
|
||||
value.x = std::clamp(value.x + amount * signed_unit(sequence, index, lane),
|
||||
-0.96F, 0.96F);
|
||||
value.y = std::clamp(value.y + amount * signed_unit(sequence, index, lane + 1U),
|
||||
-0.96F, 0.96F);
|
||||
value.z = std::clamp(value.z + amount * signed_unit(sequence, index, lane + 2U),
|
||||
-0.96F, 0.96F);
|
||||
}
|
||||
template <typename Visual_Object>
|
||||
void update(Visual_Object& visual, Axes_3D&, Marker_Visual*,
|
||||
const Plot_Render_Tick& request) {
|
||||
using Definition = typename Visual_Object::Attached_Object;
|
||||
using Prop = typename Definition::Prop;
|
||||
const auto sequence = request.sequence != 0
|
||||
? request.sequence
|
||||
: static_cast<std::uint64_t>(request.time_milliseconds * 10.0);
|
||||
auto items = visual.template read_prop<typename Definition::Base_Tag>().items;
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
auto& item = items[index];
|
||||
if constexpr (requires { item.position; })
|
||||
push(item.position, sequence, index, 0U);
|
||||
else if constexpr (requires { item.center; })
|
||||
push(item.center, sequence, index, 0U);
|
||||
else if constexpr (requires { item.origin; }) {
|
||||
push(item.origin, sequence, index, 0U);
|
||||
if constexpr (requires { item.direction; })
|
||||
push(item.direction, sequence, index, 3U, 0.008F);
|
||||
}
|
||||
else if constexpr (requires { item.start; item.end; }) {
|
||||
push(item.start, sequence, index, 0U);
|
||||
push(item.end, sequence, index, 3U);
|
||||
}
|
||||
if constexpr (requires { item.value; })
|
||||
item.value = std::clamp(
|
||||
item.value + 0.07F * signed_unit(sequence, index, 7U),
|
||||
0.0F, 1.0F);
|
||||
if constexpr (std::same_as<Definition, Text_Visual>) {
|
||||
if (index == 0)
|
||||
item.text = "Aethera frame " +
|
||||
std::to_string(sequence % 10'000U);
|
||||
}
|
||||
}
|
||||
visual.template set<&Prop::items>(std::move(items));
|
||||
|
||||
}
|
||||
};
|
||||
template <typename Visual_Object, typename Data_Generator = Random_Data_Generator>
|
||||
struct Visual_Scene_View final : public Plot::Scene_View {
|
||||
public:
|
||||
@@ -738,7 +799,6 @@ std::shared_ptr<Plot> make_datoviz_vector_plot() {
|
||||
}
|
||||
std::shared_ptr<Plot> make_datoviz_primitive_plot() {
|
||||
return make_visual_plot<Primitive_Visual>("Primitive Visual", Primitive_Visual::Builder<Primitive_Visual>{}
|
||||
.set(&Primitive_Visual::Prop::topology, Primitive_Topology::triangle_list)
|
||||
.set(&Primitive_Visual::Prop::items, std::vector<Primitive_Vertex>{{{-0.72F, -0.55F, 0.0F}, color(255, 86, 110), {0, 0, 1}}, {{0.72F, -0.55F, 0.0F}, color(75, 145, 255), {0, 0, 1}}, {{0.0F, 0.72F, 0.25F}, color(82, 226, 190), {0, 0, 1}}}).build());
|
||||
}
|
||||
std::shared_ptr<Plot> make_datoviz_mesh_plot() {
|
||||
@@ -811,18 +871,29 @@ std::shared_ptr<Plot> make_datoviz_path_plot() {
|
||||
return make_visual_plot<Path_Visual>("Path Visual", Path_Visual::Builder<Path_Visual>{}.set(&Path_Visual::Prop::items, std::move(path)).build());
|
||||
}
|
||||
std::shared_ptr<Plot> make_datoviz_image_plot() {
|
||||
std::vector<Color> pixels(32U * 32U);
|
||||
for (std::uint32_t y = 0; y < 32U; ++y)
|
||||
for (std::uint32_t x = 0; x < 32U; ++x)
|
||||
pixels[y * 32U + x] = (x / 8U + y / 8U) % 2U == 0U
|
||||
? color(40, 235, 205) : color(18, 42, 72);
|
||||
return make_visual_plot<Image_Visual>("Image Visual", Image_Visual::Builder<Image_Visual>{}
|
||||
.set(&Image_Visual::Prop::field_width, 32U).set(&Image_Visual::Prop::field_height, 32U)
|
||||
.set(&Image_Visual::Prop::items, std::vector<Image>{{{0.0F, 0.0F, 0.0F}, {1.45F, 1.0F}, {0, 0, 1, 1}, color(255, 255, 255)}}).build());
|
||||
.set(&Image_Visual::Prop::field_pixels, std::move(pixels))
|
||||
.set(&Image_Visual::Prop::items, std::vector<Image>{{{0.0F, 0.0F, 0.0F}, {1.45F, 1.0F}, {0, 0, 1, 1}}}).build());
|
||||
}
|
||||
std::shared_ptr<Plot> make_datoviz_labels_plot() {
|
||||
std::vector<std::int32_t> labels(8U * 8U);
|
||||
for (std::uint32_t y = 0; y < 8U; ++y)
|
||||
for (std::uint32_t x = 0; x < 8U; ++x)
|
||||
labels[y * 8U + x] = static_cast<std::int32_t>(
|
||||
x / 4U + 2U * (y / 4U));
|
||||
return make_visual_plot<Labels_Visual>("Labels Visual", Labels_Visual::Builder<Labels_Visual>{}
|
||||
.set(&Labels_Visual::Prop::field_width, 8U).set(&Labels_Visual::Prop::field_height, 8U)
|
||||
.set(&Labels_Visual::Prop::items, std::vector<Label>{{{-0.55F, 0.28F, 0.0F}, 0, {72, 34}, color(255, 255, 255)}, {{0.5F, 0.25F, 0.1F}, 1, {72, 34}, color(255, 255, 255)}, {{-0.45F, -0.32F, 0.1F}, 2, {72, 34}, color(255, 255, 255)}, {{0.55F, -0.3F, 0.0F}, 3, {72, 34}, color(255, 255, 255)}}).build());
|
||||
.set(&Labels_Visual::Prop::field_labels, std::move(labels))
|
||||
.set(&Labels_Visual::Prop::items, std::vector<Label>{{{-0.55F, 0.28F, 0.0F}, {72, 34}, {0, 0, 0.5F, 0.5F}}, {{0.5F, 0.25F, 0.1F}, {72, 34}, {0.5F, 0, 1, 0.5F}}, {{-0.45F, -0.32F, 0.1F}, {72, 34}, {0, 0.5F, 0.5F, 1}}, {{0.55F, -0.3F, 0.0F}, {72, 34}, {0.5F, 0.5F, 1, 1}}}).build());
|
||||
}
|
||||
std::shared_ptr<Plot> make_datoviz_glyph_plot() {
|
||||
return make_visual_plot<Glyph_Visual>("Glyph Visual", Glyph_Visual::Builder<Glyph_Visual>{}
|
||||
.set(&Glyph_Visual::Prop::field_width, 32U).set(&Glyph_Visual::Prop::field_height, 32U)
|
||||
.set(&Glyph_Visual::Prop::items, std::vector<Glyph>{{{-0.55F, -0.15F, 0.0F}, {-0.18F, -0.18F, 0.18F, 0.18F}, {0, 0, 1, 1}, color(255, 100, 120), -0.2F}, {{0.0F, 0.22F, 0.1F}, {-0.22F, -0.22F, 0.22F, 0.22F}, {0, 0, 1, 1}, color(82, 226, 190), 0.25F}, {{0.55F, -0.15F, 0.0F}, {-0.2F, -0.2F, 0.2F, 0.2F}, {0, 0, 1, 1}, color(80, 145, 255), 0.55F}}).build());
|
||||
}
|
||||
std::shared_ptr<Plot> make_datoviz_text_plot() {
|
||||
|
||||
+127
-75
@@ -2,6 +2,7 @@
|
||||
#include "Renderable_Adapter.hpp"
|
||||
#include "Taskflow_Trace_Json.hpp"
|
||||
#include <Frame_Policy/Frame_Policy.hpp>
|
||||
#include <Frame_Policy/Frame_Policy_3D.hpp>
|
||||
#include <Frame_Policy/Frame_Scheduler.hpp>
|
||||
#include <concurrentqueue-1.0.5/concurrentqueue.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -459,7 +460,9 @@ struct Plot::Private {
|
||||
std::atomic_uint64_t next_stream_id{1};
|
||||
std::atomic<std::shared_ptr<const std::string>> terminal_failure{}; /* 首次 Plot Unknown Failure 的唯一终止状态。 */
|
||||
std::uint64_t next_frame_sequence{1};
|
||||
std::unique_ptr<Frame_Policy> frame_policy{};
|
||||
std::unique_ptr<Frame_Policy> frame_policy_2d{};
|
||||
std::unique_ptr<Frame_Policy_3D> frame_policy_3d{};
|
||||
Frame_Policy* frame_policy{}; /* 指向当前维度策略的公共事实层。 */
|
||||
Frame_Scheduler::Timer frame_timer{}; /* 每 Plot/Scene 只有轻量时间轮节点,不持有线程。 */
|
||||
static constexpr std::size_t scene_frame_capacity{3};
|
||||
std::array<Managed_Frame, scene_frame_capacity> frame_slots{}; /* Scene 与外接消费者共享生命周期的稳定三缓冲。 */
|
||||
@@ -498,25 +501,38 @@ struct Plot::Private {
|
||||
Private(std::unique_ptr<Scene_Object> value_scene,
|
||||
std::unique_ptr<Scene_View> value_view)
|
||||
: view(std::move(value_view)), scene(std::move(value_scene)) {
|
||||
auto built_policy = Frame_Policy::Builder<Frame_Policy>{}.build();
|
||||
if (!built_policy)
|
||||
throw std::logic_error("Frame Policy dependency graph is invalid");
|
||||
frame_policy = std::move(*built_policy);
|
||||
if constexpr (std::same_as<Scene_Object, Scene_2D>) {
|
||||
auto built_policy = Frame_Policy::Builder<Frame_Policy>{}.build();
|
||||
if (!built_policy)
|
||||
throw std::logic_error("2D Frame Policy dependency graph is invalid");
|
||||
frame_policy_2d = std::move(*built_policy);
|
||||
frame_policy = frame_policy_2d.get();
|
||||
}
|
||||
else {
|
||||
auto built_policy =
|
||||
Frame_Policy_3D::Builder<Frame_Policy_3D>{}.build();
|
||||
if (!built_policy)
|
||||
throw std::logic_error("3D Frame Policy dependency graph is invalid");
|
||||
frame_policy_3d = std::move(*built_policy);
|
||||
frame_policy = frame_policy_3d.get();
|
||||
frame_policy->set_mode(Frame_Pacing_Mode::maximum_rate);
|
||||
static_cast<void>(frame_policy->consume_events());
|
||||
}
|
||||
for (auto& slot : frame_slots) {
|
||||
if constexpr (std::same_as<Scene_Object, Scene_2D>)
|
||||
slot.frame = std::make_unique<Frame_2D>(Frame_Identity{});
|
||||
else
|
||||
slot.frame = std::make_unique<Frame_3D>(Frame_Identity{});
|
||||
}
|
||||
auto& completion = std::visit(
|
||||
[](auto& scene_value) -> Task_Graph& {
|
||||
return scene_value->completion_taskflow();
|
||||
}, scene);
|
||||
completion_tail = completion.add("plot.frame.publish", [this] {
|
||||
publish_completed_frame();
|
||||
});
|
||||
completion_tail.describe("owner", "plot")
|
||||
.describe("stage", "completed pixels publish");
|
||||
if constexpr (std::same_as<Scene_Object, Scene_2D>) {
|
||||
auto& completion = std::get<std::unique_ptr<Scene_2D>>(scene)
|
||||
->completion_taskflow();
|
||||
completion_tail = completion.add("plot.frame.publish", [this] {
|
||||
publish_completed_frame();
|
||||
});
|
||||
completion_tail.describe("owner", "plot")
|
||||
.describe("stage", "completed pixels publish");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] nlohmann::json schema() const;
|
||||
@@ -530,7 +546,7 @@ struct Plot::Private {
|
||||
void refresh_schedule();
|
||||
void clock_tick(const Plot_Render_Tick& tick);
|
||||
void render_frame(Plot_Render_Tick tick);
|
||||
void publish_completed_frame();
|
||||
void publish_completed_frame(Render_Frame* completed = nullptr);
|
||||
void consume_completed_frame(Render_Frame* frame);
|
||||
void retire_completed_frame(Render_Frame* frame);
|
||||
void consume_retired_frames();
|
||||
@@ -575,6 +591,14 @@ void Plot::Private::fail(std::exception_ptr failure) noexcept {
|
||||
nlohmann::json Plot::Private::schema() const {
|
||||
auto result = view->schema();
|
||||
auto analysis = frame_policy_schema(*frame_policy);
|
||||
if (frame_policy_3d) {
|
||||
analysis["fields"].push_back({
|
||||
{"key", "pipeline_capacity"}, {"label", "3D 帧槽容量"},
|
||||
{"editor", "integer"}, {"editable", false}, {"value", 3},
|
||||
{"description", "3D 独立策略最多允许三帧处于 Prepare、GPU 和退休阶段。"},
|
||||
{"technical_description", "3D-only submitted-driven pipeline capacity; 2D policy is unchanged."}
|
||||
});
|
||||
}
|
||||
const auto generator = view->data_generator_schema();
|
||||
if (!generator.is_null()) analysis["data_generator"] = generator;
|
||||
result["frame_analysis"] = std::move(analysis);
|
||||
@@ -712,6 +736,8 @@ void Plot::Private::release_render_admission(std::weak_ptr<Plot> lifetime) {
|
||||
}
|
||||
|
||||
void Plot::Private::consume_tick(std::weak_ptr<Plot> lifetime) {
|
||||
if (frame_policy_3d)
|
||||
static_cast<void>(frame_policy_3d->consume_3d_events());
|
||||
if (frame_policy->consume_events()) refresh_schedule();
|
||||
const auto observed_generation =
|
||||
tick_request_generation.load(std::memory_order_acquire);
|
||||
@@ -723,6 +749,8 @@ void Plot::Private::consume_tick(std::weak_ptr<Plot> lifetime) {
|
||||
auto tick = std::exchange(deferred_tick, {});
|
||||
if (tick) clock_tick(*tick);
|
||||
}
|
||||
if (frame_policy_3d)
|
||||
static_cast<void>(frame_policy_3d->consume_3d_events());
|
||||
if (frame_policy->consume_events()) refresh_schedule();
|
||||
tick_task_scheduled.store(false, std::memory_order_release);
|
||||
if (tick_request_generation.load(std::memory_order_acquire) !=
|
||||
@@ -999,7 +1027,7 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
}
|
||||
|
||||
|
||||
void Plot::Private::publish_completed_frame() {
|
||||
void Plot::Private::publish_completed_frame(Render_Frame* completed) {
|
||||
Render_Frame* frame{};
|
||||
Managed_Frame* managed{};
|
||||
for (std::size_t index = 0; index < frame_slots.size(); ++index) {
|
||||
@@ -1011,69 +1039,62 @@ void Plot::Private::publish_completed_frame() {
|
||||
frame = std::visit(
|
||||
[](const auto& value) -> Render_Frame* { return value.get(); },
|
||||
frame_slots[index].frame);
|
||||
if (completed && frame != completed) continue;
|
||||
managed = &frame_slots[index];
|
||||
if (completed) break;
|
||||
}
|
||||
if (!managed)
|
||||
throw std::logic_error("Scene completion graph has no rendering Plot frame");
|
||||
|
||||
try {
|
||||
const auto& pacing =
|
||||
frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
const auto identity = frame->identity();
|
||||
Frame_Identity rendered_identity = identity;
|
||||
std::shared_ptr<const std::vector<std::byte>> pixel_storage;
|
||||
Plot_Pixel_Layout pixel_layout{Plot_Pixel_Layout::rgba8};
|
||||
std::uint32_t width{};
|
||||
std::uint32_t height{};
|
||||
if (auto* frame_2d =
|
||||
std::get_if<std::unique_ptr<Frame_2D>>(&managed->frame)) {
|
||||
pixel_layout = Plot_Pixel_Layout::bgra8;
|
||||
const auto image = (*frame_2d)->image();
|
||||
width = static_cast<std::uint32_t>(image.width);
|
||||
height = static_cast<std::uint32_t>(image.height);
|
||||
if (pacing.video_enabled) {
|
||||
auto output = (*frame_2d)->output_pixels();
|
||||
pixel_storage = std::make_shared<const std::vector<std::byte>>(
|
||||
std::move(output.bytes));
|
||||
width = static_cast<std::uint32_t>(output.width);
|
||||
height = static_cast<std::uint32_t>(output.height);
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto& frame_3d =
|
||||
std::get<std::unique_ptr<Frame_3D>>(managed->frame);
|
||||
rendered_identity = frame_3d->rendered_identity();
|
||||
const auto extent = frame_3d->extent();
|
||||
width = extent.width;
|
||||
height = extent.height;
|
||||
if (pacing.video_enabled &&
|
||||
frame_3d->output() == Frame_3D_Output::pixels)
|
||||
pixel_storage = frame_3d->share_pixels();
|
||||
}
|
||||
|
||||
auto pixels = std::make_shared<const Plot_Pixel_Frame>(Plot_Pixel_Frame{
|
||||
std::move(pixel_storage), pixel_layout, managed->presentation_time,
|
||||
identity.sequence, identity.correlation_id,
|
||||
rendered_identity.sequence, rendered_identity.correlation_id,
|
||||
width, height});
|
||||
|
||||
const auto published = std::make_shared<const Plot_Stream_Frame>(
|
||||
Plot_Stream_Frame{{}, std::move(pixels)});
|
||||
const auto publish_started = std::chrono::steady_clock::now();
|
||||
publish(std::move(published));
|
||||
frame->record(Frame_Trace_Measurement::plot_publish_ns,
|
||||
static_cast<std::uint64_t>(std::max<std::int64_t>(0,
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now() - publish_started).count())));
|
||||
auto expected = Frame_State::rendering;
|
||||
if (!managed->state.compare_exchange_strong(
|
||||
expected, Frame_State::consuming, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
throw std::logic_error("Plot frame left rendering before pixel publish");
|
||||
}
|
||||
catch (...) {
|
||||
throw;
|
||||
const auto &pacing = frame_policy->read_state<Frame_Policy::Base_Tag>();
|
||||
const auto identity = frame->identity();
|
||||
Frame_Identity rendered_identity = identity;
|
||||
std::shared_ptr<const std::vector<std::byte>> pixel_storage;
|
||||
Plot_Pixel_Layout pixel_layout{Plot_Pixel_Layout::rgba8};
|
||||
std::uint32_t width{};
|
||||
std::uint32_t height{};
|
||||
if (auto *frame_2d =
|
||||
std::get_if<std::unique_ptr<Frame_2D>>(&managed->frame)) {
|
||||
pixel_layout = Plot_Pixel_Layout::bgra8;
|
||||
const auto image = (*frame_2d)->image();
|
||||
width = static_cast<std::uint32_t>(image.width);
|
||||
height = static_cast<std::uint32_t>(image.height);
|
||||
if (pacing.video_enabled) {
|
||||
auto output = (*frame_2d)->output_pixels();
|
||||
pixel_storage = std::make_shared<const std::vector<std::byte>>(
|
||||
std::move(output.bytes));
|
||||
width = static_cast<std::uint32_t>(output.width);
|
||||
height = static_cast<std::uint32_t>(output.height);
|
||||
}
|
||||
} else {
|
||||
auto &frame_3d = std::get<std::unique_ptr<Frame_3D>>(managed->frame);
|
||||
rendered_identity = frame_3d->rendered_identity();
|
||||
const auto extent = frame_3d->extent();
|
||||
width = extent.width;
|
||||
height = extent.height;
|
||||
if (pacing.video_enabled && frame_3d->output() == Frame_3D_Output::pixels)
|
||||
pixel_storage = frame_3d->share_pixels();
|
||||
}
|
||||
|
||||
auto pixels = std::make_shared<const Plot_Pixel_Frame>(Plot_Pixel_Frame{
|
||||
std::move(pixel_storage), pixel_layout, managed->presentation_time,
|
||||
identity.sequence, identity.correlation_id, rendered_identity.sequence,
|
||||
rendered_identity.correlation_id, width, height});
|
||||
|
||||
const auto published = std::make_shared<const Plot_Stream_Frame>(
|
||||
Plot_Stream_Frame{{}, std::move(pixels)});
|
||||
const auto publish_started = std::chrono::steady_clock::now();
|
||||
publish(std::move(published));
|
||||
frame->record(Frame_Trace_Measurement::plot_publish_ns,
|
||||
static_cast<std::uint64_t>(std::max<std::int64_t>(
|
||||
0, std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now() - publish_started)
|
||||
.count())));
|
||||
auto expected = Frame_State::rendering;
|
||||
if (!managed->state.compare_exchange_strong(
|
||||
expected, Frame_State::consuming, std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
throw std::logic_error("Plot frame left rendering before pixel publish");
|
||||
}
|
||||
|
||||
void Plot::Private::consume_completed_frame(Render_Frame* frame) {
|
||||
@@ -1098,7 +1119,8 @@ void Plot::Private::consume_completed_frame(Render_Frame* frame) {
|
||||
* 释放 Plot 的 view/update 门,并立刻唤醒 busy 期间保留的 latest tick。
|
||||
* 之后外接 DAG 仍在当前 Frame trace 内执行,但不会阻塞下一帧渲染。
|
||||
*/
|
||||
release_render_admission(lifetime);
|
||||
if (std::holds_alternative<std::unique_ptr<Frame_2D>>(managed->frame))
|
||||
release_render_admission(lifetime);
|
||||
if (post_publish_graph.empty()) return;
|
||||
|
||||
bool expected = false;
|
||||
@@ -1357,10 +1379,25 @@ void Plot::ensure_started() {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
std::get<std::unique_ptr<Scene_3D>>(d->scene)->set_frame_callback(
|
||||
auto& scene_3d = std::get<std::unique_ptr<Scene_3D>>(d->scene);
|
||||
scene_3d->set_submitted_frame_callback(
|
||||
[weak](Frame_3D* frame, bool overlaps_gpu) {
|
||||
if (auto owner = weak.lock()) {
|
||||
try {
|
||||
owner->d->frame_policy_3d->record_gpu_submitted(
|
||||
frame->identity().sequence, overlaps_gpu);
|
||||
owner->d->release_render_admission(weak);
|
||||
}
|
||||
catch (...) { owner->d->fail(std::current_exception()); }
|
||||
}
|
||||
});
|
||||
scene_3d->set_frame_callback(
|
||||
[weak](Frame_3D* frame) {
|
||||
if (auto owner = weak.lock()) {
|
||||
try {
|
||||
owner->d->publish_completed_frame(frame);
|
||||
owner->d->frame_policy_3d->record_gpu_completed(
|
||||
frame->identity().sequence);
|
||||
owner->d->consume_completed_frame(frame);
|
||||
owner->d->retire_completed_frame(frame);
|
||||
}
|
||||
@@ -1606,6 +1643,20 @@ nlohmann::json Plot::diagnostics() const {
|
||||
{"frame_statistics", std::move(frame_statistics)},
|
||||
{"input_statistics", std::move(input_statistics)}};
|
||||
if (is_3d) {
|
||||
const auto& pipeline = d->frame_policy_3d->read_state<
|
||||
Frame_Policy_3D::Base_Tag>();
|
||||
output["frame_policy"]["three_dimensional_pipeline"] = {
|
||||
{"capacity", pipeline.pipeline_capacity},
|
||||
{"gpu_submission_count", pipeline.gpu_submission_count},
|
||||
{"overlapped_release_count", pipeline.overlapped_release_count},
|
||||
{"completion_gated_release_count",
|
||||
pipeline.completion_gated_release_count},
|
||||
{"gpu_completion_count", pipeline.gpu_completion_count},
|
||||
{"gpu_in_flight", pipeline.gpu_in_flight},
|
||||
{"peak_gpu_in_flight", pipeline.peak_gpu_in_flight},
|
||||
{"last_submitted_sequence", pipeline.last_submitted_sequence},
|
||||
{"last_completed_sequence", pipeline.last_completed_sequence}
|
||||
};
|
||||
const auto gpu = gpu_completion_state();
|
||||
const auto milliseconds = [](std::uint64_t nanoseconds) {
|
||||
return static_cast<double>(nanoseconds) / 1'000'000.0;
|
||||
@@ -1698,6 +1749,7 @@ void Plot::reset_diagnostics() {
|
||||
std::visit([](auto& scene) { scene->reset_diagnostics(); }, d->scene);
|
||||
d->statistics_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||
d->frame_policy->reset_statistics();
|
||||
if (d->frame_policy_3d) d->frame_policy_3d->reset_3d_statistics();
|
||||
d->arm_tick_consumer(weak_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,38 +178,20 @@ nlohmann::json merge_gallery_media_trace(nlohmann::json plot_trace,
|
||||
|
||||
int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root) {
|
||||
auto plots = std::make_shared<Plot_Map>();
|
||||
plots->emplace("axes", make_axes_plot());
|
||||
plots->emplace("spectrum", make_spectrum_plot());
|
||||
plots->emplace("frequency_trace", make_frequency_trace_plot());
|
||||
plots->emplace("sweep_spectrum", make_sweep_spectrum_plot());
|
||||
plots->emplace("afterglow", make_afterglow_plot());
|
||||
plots->emplace("waterfall", make_waterfall_plot());
|
||||
plots->emplace("constellation", make_constellation_plot());
|
||||
plots->emplace("selection_overlay", make_selection_overlay_plot());
|
||||
plots->emplace("datoviz_point", make_datoviz_point_plot());
|
||||
plots->emplace("datoviz_splat", make_datoviz_splat_plot());
|
||||
plots->emplace("datoviz_pixel", make_datoviz_pixel_plot());
|
||||
plots->emplace("datoviz_marker", make_datoviz_marker_plot());
|
||||
plots->emplace("datoviz_sphere", make_datoviz_sphere_plot());
|
||||
plots->emplace("datoviz_segment", make_datoviz_segment_plot());
|
||||
plots->emplace("datoviz_vector", make_datoviz_vector_plot());
|
||||
plots->emplace("datoviz_primitive", make_datoviz_primitive_plot());
|
||||
plots->emplace("datoviz_mesh", make_datoviz_mesh_plot());
|
||||
plots->emplace("datoviz_spectrogram", make_datoviz_spectrogram_plot());
|
||||
plots->emplace("datoviz_path", make_datoviz_path_plot());
|
||||
plots->emplace("datoviz_image", make_datoviz_image_plot());
|
||||
plots->emplace("datoviz_labels", make_datoviz_labels_plot());
|
||||
plots->emplace("datoviz_glyph", make_datoviz_glyph_plot());
|
||||
plots->emplace("datoviz_text", make_datoviz_text_plot());
|
||||
plots->emplace("datoviz_volume", make_datoviz_volume_plot());
|
||||
plots->reserve(gallery_plot_definitions().size());
|
||||
for (const auto& definition : gallery_plot_definitions())
|
||||
plots->emplace(definition.id, definition.create());
|
||||
|
||||
std::vector<Gallery_Video_Stream::Plot_Entry> gallery_2d;
|
||||
std::vector<Gallery_Video_Stream::Plot_Entry> gallery_3d;
|
||||
gallery_2d.reserve(8);
|
||||
gallery_3d.reserve(16);
|
||||
for (const auto& [id, plot] : *plots)
|
||||
(id.starts_with("datoviz_") ? gallery_3d : gallery_2d)
|
||||
.push_back({id, plot});
|
||||
for (const auto& definition : gallery_plot_definitions()) {
|
||||
auto plot = plots->at(std::string{definition.id});
|
||||
(definition.dimension == Plot_Dimension::three_d
|
||||
? gallery_3d : gallery_2d)
|
||||
.push_back({std::string{definition.id}, std::move(plot)});
|
||||
}
|
||||
const auto entry_order = [](const auto& left, const auto& right) {
|
||||
return left.id < right.id;
|
||||
};
|
||||
@@ -266,12 +248,15 @@ int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root)
|
||||
app.registerHandler("/plot", [plots, plot_media](const drogon::HttpRequestPtr&,
|
||||
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
||||
nlohmann::json result = nlohmann::json::array();
|
||||
for (const auto& [id, plot] : *plots) {
|
||||
static_cast<void>(plot);
|
||||
for (const auto& definition : gallery_plot_definitions()) {
|
||||
const auto id = std::string{definition.id};
|
||||
const auto& media = plot_media->at(id);
|
||||
result.push_back({
|
||||
{"id", id}, {"title", id}, {"category", "Plots"}, {"description", ""},
|
||||
{"dimension", id.starts_with("datoviz_") ? "3D" : "2D"}, {"websocket", "/ws/plot/" + id},
|
||||
{"id", id}, {"title", definition.title},
|
||||
{"category", definition.category},
|
||||
{"description", definition.description},
|
||||
{"dimension", plot_dimension_name(definition.dimension)},
|
||||
{"websocket", "/ws/plot/" + id},
|
||||
{"media", media},
|
||||
{"schema", "/plot/" + id + "/schema"},
|
||||
{"diagnostics", "/plot/" + id + "/diagnostics"},
|
||||
|
||||
Reference in New Issue
Block a user