This commit is contained in:
2026-08-24 09:37:04 +08:00
parent 1d3847d454
commit 4f21169ade
42 changed files with 1371 additions and 833 deletions
+31 -1
View File
@@ -1,12 +1,42 @@
#pragma once
#include "../visual/Prepared_Visual.hpp"
#include "../visual/Visuals.hpp"
#include <event.hpp>
#include <plot/Axis.hpp>
#include "../camera/Camera.hpp"
#include <cstdint>
#include <memory>
#include <variant>
#include <vector>
namespace aethera::render_3d::detail {
using Prepared_Visual_Payload = std::variant<
std::shared_ptr<const Point_Prepared_Data>,
std::shared_ptr<const Splat_Prepared_Data>,
std::shared_ptr<const Pixel_Prepared_Data>,
std::shared_ptr<const Marker_Prepared_Data>,
std::shared_ptr<const Sphere_Prepared_Data>,
std::shared_ptr<const Segment_Prepared_Data>,
std::shared_ptr<const Vector_Prepared_Data>,
std::shared_ptr<const Primitive_Prepared_Data>,
std::shared_ptr<const Mesh_Prepared_Data>,
std::shared_ptr<const Path_Prepared_Data>,
std::shared_ptr<const Image_Prepared_Data>,
std::shared_ptr<const Labels_Prepared_Data>,
std::shared_ptr<const Glyph_Prepared_Data>,
std::shared_ptr<const Text_Prepared_Data>,
std::shared_ptr<const Volume_Prepared_Data>>;
/* Scene 到 Backend 的唯一异构边界;Visual 对象内部不保存此信封。 */
struct Prepared_Visual {
Matrix4 transform{};
bool visible{true};
bool depth_test{true};
std::uint64_t revision{};
Prepared_Visual_Payload data{};
};
template <typename Data>
Prepared_Visual erase_prepared_visual(const Prepared_Visual_For<Data>& source) {
return {source.transform, source.visible, source.depth_test, source.revision,
source.data};
}
using Visual_Identity = std::uintptr_t;
struct Visual_Registration {
Visual_Identity identity{}; /* Scene 内稳定对象身份,仅供后端匹配原生 Visual。 */
@@ -183,24 +183,51 @@ bool uses_external_attributes(Visual_Family family) noexcept {
return family == Visual_Family::mesh || family == Visual_Family::marker;
}
bool has_coordinate_labels(const Prepared_Visual_Data& data) noexcept {
template <typename Data>
const Data& prepared_data(const Prepared_Visual& visual) {
const auto* value = std::get_if<std::shared_ptr<const Data>>(&visual.data);
if (value == nullptr || !*value)
throw std::logic_error("3D prepared Visual payload does not match its family");
return **value;
}
bool has_payload(const Prepared_Visual& visual) noexcept {
return std::visit([](const auto& value) { return static_cast<bool>(value); },
visual.data);
}
std::size_t prepared_item_count(const Prepared_Visual& visual) noexcept {
return std::visit([](const auto& value) -> std::size_t {
if (!value) return 0;
if constexpr (requires { value->positions; }) return value->positions.size();
else if constexpr (requires { value->centers; }) return value->centers.size();
else if constexpr (requires { value->starts; }) return value->starts.size();
else if constexpr (requires { value->origins; }) return value->origins.size();
else return value->values.size();
}, visual.data);
}
bool has_coordinate_labels(const Prepared_Visual& visual) noexcept {
const auto* payload = std::get_if<std::shared_ptr<const Marker_Prepared_Data>>(
&visual.data);
if (payload == nullptr || !*payload) return false;
return std::ranges::any_of(
data.coordinate_label_visibility,
(*payload)->coordinate_label_visibility,
[](std::uint8_t visible) { return visible != 0; });
}
bool same_visual_structure(const Prepared_Visual& applied,
const Prepared_Visual& incoming) noexcept {
if (applied.family != incoming.family ||
if (applied.data.index() != incoming.data.index() ||
applied.transform != incoming.transform ||
applied.visible != incoming.visible ||
applied.depth_test != incoming.depth_test ||
!applied.data || !incoming.data ||
applied.data->positions.size() != incoming.data->positions.size())
!has_payload(applied) || !has_payload(incoming) ||
prepared_item_count(applied) != prepared_item_count(incoming))
return false;
if (incoming.family == Visual_Family::marker &&
(has_coordinate_labels(*applied.data) ||
has_coordinate_labels(*incoming.data)))
if ((std::holds_alternative<std::shared_ptr<const Marker_Prepared_Data>>(
incoming.data)) &&
(has_coordinate_labels(applied) || has_coordinate_labels(incoming)))
return applied.revision == incoming.revision;
return true;
}
@@ -1327,8 +1354,8 @@ void Datoviz_Visual_Backend::apply(
}
void Datoviz_Visual_Backend::ensure_external_attributes(
Visual_Instance& target, const Prepared_Visual_Data& data) {
const auto item_count = data.positions.size();
Visual_Instance& target, const Prepared_Visual& visual) {
const auto item_count = prepared_item_count(visual);
if (item_count == 0) return;
if (item_count > std::numeric_limits<std::uint32_t>::max())
throw std::length_error("Datoviz external attribute item count exceeds uint32");
@@ -1393,12 +1420,13 @@ void Datoviz_Visual_Backend::ensure_external_attributes(
}
void Datoviz_Visual_Backend::upload_external_attributes(
Visual_Instance& target, const Prepared_Visual_Data& data,
Visual_Instance& target, const Prepared_Visual& visual,
std::uint8_t target_index) {
if (data.positions.empty()) return;
const auto item_count = prepared_item_count(visual);
if (item_count == 0) return;
const auto upload = [&](External_Attribute& attribute, const void* source,
std::size_t count) {
if (source == nullptr || count != data.positions.size())
if (source == nullptr || count != item_count)
throw std::logic_error("Datoviz external attribute fields have different item counts");
const auto byte_count = static_cast<DvzSize>(count * attribute.stride);
const auto byte_offset = static_cast<DvzSize>(target_index) *
@@ -1406,12 +1434,16 @@ void Datoviz_Visual_Backend::upload_external_attributes(
dvz_buffer_upload(attribute.gpu_buffer, byte_offset, byte_count, source);
};
auto attribute = target.attributes.begin();
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
if (target.family == Visual_Family::mesh)
if (target.family == Visual_Family::mesh) {
const auto& data = prepared_data<Mesh_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.normals.data(), data.normals.size());
else {
upload(*attribute++, data.sizes.data(), data.sizes.size());
} else {
const auto& data = prepared_data<Marker_Prepared_Data>(visual);
upload(*attribute++, data.positions.data(), data.positions.size());
upload(*attribute++, data.colors.data(), data.colors.size());
upload(*attribute++, data.diameters.data(), data.diameters.size());
upload(*attribute++, data.angles.data(), data.angles.size());
upload(*attribute++, data.shapes.data(), data.shapes.size());
}
@@ -1466,19 +1498,18 @@ void Datoviz_Visual_Backend::register_external_attributes(
void Datoviz_Visual_Backend::apply_visual(
Visual_Instance& target, const Prepared_Visual& point,
std::uint8_t target_index, bool bind_target) {
if (point.family != target.family)
throw std::logic_error("3D Visual family changed after Scene construction");
if (!point.data) throw std::logic_error("3D prepared visual has no immutable payload");
if (!has_payload(point)) throw std::logic_error("3D prepared visual has no immutable payload");
if (!uses_external_attributes(target.family) &&
point.revision == target.applied_revision) return;
const auto& data = *point.data;
const auto item_count = prepared_item_count(point);
auto* visual = target.visual;
const bool structure_changed = !target.applied ||
!same_visual_structure(*target.applied, point);
const bool coordinate_text_changed = target.coordinate_text != nullptr &&
(!target.applied || has_coordinate_labels(*target.applied->data) ||
has_coordinate_labels(data));
(!target.applied || has_coordinate_labels(*target.applied) ||
has_coordinate_labels(point));
if (coordinate_text_changed) {
const auto& data = prepared_data<Marker_Prepared_Data>(point);
std::vector<std::string> strings;
std::vector<DvzTextItem> items;
if (point.visible) {
@@ -1517,34 +1548,35 @@ void Datoviz_Visual_Backend::apply_visual(
mat4 transform{};
for (std::size_t row = 0; row < 4; ++row)
for (std::size_t column = 0; column < 4; ++column) transform[row][column] = point.transform.values[row * 4 + column];
if (point.family == Visual_Family::point) {
if (target.family == Visual_Family::point) {
const auto& data = prepared_data<Point_Prepared_Data>(point);
DvzPointStyleDesc style = dvz_point_style_desc();
style.edge_color.r = point.point_style.edge_color.red;
style.edge_color.g = point.point_style.edge_color.green;
style.edge_color.b = point.point_style.edge_color.blue;
style.edge_color.a = point.point_style.edge_color.alpha;
style.stroke_width_px = point.point_style.stroke_width_px;
style.aspect = aspect(point.point_style.aspect);
style.edge_color.r = data.style.edge_color.red;
style.edge_color.g = data.style.edge_color.green;
style.edge_color.b = data.style.edge_color.blue;
style.edge_color.a = data.style.edge_color.alpha;
style.stroke_width_px = data.style.stroke_width_px;
style.aspect = aspect(data.style.aspect);
if (dvz_point_set_style(visual, &style) != DVZ_OK) throw std::runtime_error("failed to apply Datoviz point style");
}
if (dvz_visual_set_transform(visual, transform) != DVZ_OK ||
dvz_visual_set_depth_test(visual, point.depth_test) != DVZ_OK ||
dvz_visual_set_visible(
visual, point.visible && !data.positions.empty()) != DVZ_OK)
visual, point.visible && item_count != 0) != DVZ_OK)
throw std::runtime_error("failed to apply Datoviz visual state");
}
if (data.positions.empty()) {
if (item_count == 0) {
if (dvz_visual_set_visible(visual, false) != DVZ_OK) throw std::runtime_error("failed to hide empty Datoviz point visual");
target.applied_revision = point.revision;
target.applied = point;
return;
}
const auto count = static_cast<std::uint32_t>(data.positions.size());
const auto count = static_cast<std::uint32_t>(item_count);
if (uses_external_attributes(target.family)) {
ensure_external_attributes(target, data);
ensure_external_attributes(target, point);
if (bind_target)
bind_external_attributes(target, target_index, count);
upload_external_attributes(target, data, target_index);
upload_external_attributes(target, point, target_index);
if (structure_changed &&
dvz_visual_set_visible(visual, point.visible) != DVZ_OK)
throw std::runtime_error("failed to apply Datoviz external visual visibility");
@@ -1553,24 +1585,26 @@ void Datoviz_Visual_Backend::apply_visual(
return;
}
DvzResult result = DVZ_OK;
switch (point.family) {
switch (target.family) {
case Visual_Family::point: {
const auto& data = prepared_data<Point_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"diameter_px", data.sizes.data(), count}
{"diameter_px", data.diameters.data(), count}
}
};
result = dvz_visual_set_data_many(visual, updates.data(), 3);
break;
}
case Visual_Family::splat: {
const auto& data = prepared_data<Splat_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 4> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"sigma", data.sigma.data(), count},
{"sigma", data.sigmas.data(), count},
{"angle", data.angles.data(), count}
}
};
@@ -1578,6 +1612,7 @@ void Datoviz_Visual_Backend::apply_visual(
break;
}
case Visual_Family::pixel: {
const auto& data = prepared_data<Pixel_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
@@ -1589,11 +1624,12 @@ void Datoviz_Visual_Backend::apply_visual(
break;
}
case Visual_Family::marker: {
const auto& data = prepared_data<Marker_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 5> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"diameter_px", data.sizes.data(), count},
{"diameter_px", data.diameters.data(), count},
{"angle", data.angles.data(), count},
{"shape", data.shapes.data(), count}
}
@@ -1602,42 +1638,57 @@ void Datoviz_Visual_Backend::apply_visual(
break;
}
case Visual_Family::sphere: {
const auto& data = prepared_data<Sphere_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"position", data.centers.data(), count},
{"color", data.colors.data(), count},
{"radius", data.sizes.data(), count}
{"radius", data.radii.data(), count}
}
};
result = dvz_visual_set_data_many(visual, updates.data(), 3);
break;
}
case Visual_Family::segment: {
const auto& data = prepared_data<Segment_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 4> updates{
{
{"position_start", data.positions.data(), count},
{"position_end", data.secondary_positions.data(), count},
{"position_start", data.starts.data(), count},
{"position_end", data.ends.data(), count},
{"color", data.colors.data(), count},
{"stroke_width_px", data.sizes.data(), count}
{"stroke_width_px", data.widths.data(), count}
}
};
result = dvz_visual_set_data_many(visual, updates.data(), 4);
break;
}
case Visual_Family::vector: {
const auto& data = prepared_data<Vector_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 4> updates{
{
{"position", data.positions.data(), count},
{"vector", data.secondary_positions.data(), count},
{"position", data.origins.data(), count},
{"vector", data.directions.data(), count},
{"color", data.colors.data(), count},
{"stroke_width_px", data.sizes.data(), count}
{"stroke_width_px", data.widths.data(), count}
}
};
result = dvz_visual_set_data_many(visual, updates.data(), 4);
break;
}
case Visual_Family::primitive:
case Visual_Family::primitive: {
const auto& data = prepared_data<Primitive_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"normal", data.normals.data(), count}
}
};
result = dvz_visual_set_data_many(visual, updates.data(), 3);
break;
}
case Visual_Family::mesh: {
const auto& data = prepared_data<Mesh_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
@@ -1649,18 +1700,30 @@ void Datoviz_Visual_Backend::apply_visual(
break;
}
case Visual_Family::path: {
const auto& data = prepared_data<Path_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 3> updates{
{
{"position", data.positions.data(), count},
{"color", data.colors.data(), count},
{"stroke_width_px", data.sizes.data(), count}
{"stroke_width_px", data.widths.data(), count}
}
};
result = dvz_visual_set_data_many(visual, updates.data(), 3);
break;
}
case Visual_Family::image:
case Visual_Family::image: {
const auto& data = prepared_data<Image_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 2> updates{
{
{"position", data.positions.data(), count},
{"extent", data.extents.data(), count}
}
};
result = dvz_visual_set_data_many(visual, updates.data(), 2);
break;
}
case Visual_Family::labels: {
const auto& data = prepared_data<Labels_Prepared_Data>(point);
const std::array<DvzVisualDataUpdate, 2> updates{
{
{"position", data.positions.data(), count},
@@ -1851,12 +1914,12 @@ Datoviz_Visual_Backend::reuse_recorded_target(
throw std::logic_error(
"recorded Datoviz frame contains an unknown Visual identity");
if (!uses_external_attributes(visual->family)) continue;
if (!source.visual.data)
if (!has_payload(source.visual))
throw std::logic_error(
"recorded Datoviz Visual has no immutable payload");
if (!source.visual.data->positions.empty())
if (prepared_item_count(source.visual) != 0)
upload_external_attributes(
*visual, *source.visual.data, target_index);
*visual, source.visual, target_index);
visual->applied_revision = source.visual.revision;
visual->applied = source.visual;
}
@@ -93,9 +93,9 @@ private:
void apply_visual(Visual_Instance& target, const Prepared_Visual& visual,
std::uint8_t target_index, bool bind_target);
void ensure_external_attributes(Visual_Instance& target,
const Prepared_Visual_Data& data);
const Prepared_Visual& visual);
void upload_external_attributes(Visual_Instance& target,
const Prepared_Visual_Data& data,
const Prepared_Visual& visual,
std::uint8_t target_index);
void bind_external_attributes(Visual_Instance& target,
std::uint8_t target_index,
@@ -59,20 +59,23 @@ Render_Scene_3D::Builder<Object>::add_renderable(Visual_Object* visual_value) {
binding.object = visual_value;
binding.family = Visual_Object::Attached_Object::Specification::family;
binding.bind = [](Root* root, std::shared_ptr<void> context) {
using Definition = typename Visual_Object::Attached_Object;
using Prepared = typename Definition::Prepared_Visual;
auto* object = static_cast<Visual_Object*>(root);
Base::private_access(object).template get<typename Visual_Object::Attached_Object::Base_Tag>()
Base::private_access(object).template get<typename Definition::Base_Tag>()
.bind_paint_target(std::move(context), [](void* raw_context, const Root* identity,
const detail::Prepared_Visual& prepared) {
const Prepared& prepared) {
auto& submission = *static_cast<detail::Scene_Paint_Context<Object>*>(raw_context);
const auto visual_identity = reinterpret_cast<detail::Visual_Identity>(identity);
auto erased = detail::erase_prepared_visual(prepared);
auto& visuals = *submission.visuals;
const auto found = std::ranges::find(
visuals, visual_identity,
&detail::Prepared_Visual_Instance::identity);
if (found == visuals.end())
visuals.push_back({visual_identity, prepared});
visuals.push_back({visual_identity, std::move(erased)});
else
found->visual = prepared;
found->visual = std::move(erased);
});
};
visuals.push_back(binding);
+5 -11
View File
@@ -1,15 +1,17 @@
#pragma once
#include "Prepared_Visual.hpp"
#include <renderable.hpp>
#include <expected>
#include <functional>
#include <memory>
#include <vector>
namespace aethera::render_3d {
template <typename Spec>
struct Basic_Visual : Def<Basic_Visual<Spec>, Renderable, Tagged_Buffer<detail::Prepared_Visual_Tag, detail::Prepared_Visual>> {
struct Basic_Visual : Def<Basic_Visual<Spec>, Renderable,
Tagged_Buffer<detail::Prepared_Visual_Tag,
detail::Prepared_Visual_For<typename Spec::Prepared_Data>>> {
using Specification = Spec;
using Item = typename Spec::Item;
using Prepared_Data = typename Spec::Prepared_Data;
using Prepared_Visual = detail::Prepared_Visual_For<Prepared_Data>;
struct Prop : Basic_Visual::Prev_Prop, Spec::Settings {
Matrix4 transform{}; /* 本地坐标到 Scene 坐标的变换。 */
bool visible{true}; /* 是否向后端提交可见图元。 */
@@ -24,14 +26,6 @@ struct Basic_Visual : Def<Basic_Visual<Spec>, Renderable, Tagged_Buffer<detail::
bool operator==(const State&) const;
};
struct Private;
enum class Update_Items_Result : std::uint8_t { updated, invalid_data };
/* 原子替换外部可读写图元集合;无效数据保持原值。 */
[[nodiscard]] Update_Items_Result update_items(std::vector<Item> items);
enum class Edit_Items_Result : std::uint8_t { updated, empty_edit, invalid_data };
/* 在当前 Prop 副本上编辑图元集合,通过验证后原子提交。 */
[[nodiscard]] Edit_Items_Result edit_items(const std::function<void(std::vector<Item>&)>& edit);
/* 返回当前发布 Prop 中的图元数。 */
[[nodiscard]] std::size_t item_count() const;
};
}
#include "Basic_Visual.ipp"
+80 -36
View File
@@ -4,51 +4,95 @@
namespace aethera::render_3d {
template <typename Spec>
struct Basic_Visual<Spec>::Private : Basic_Visual::Prev_Private {
using Enqueue_Paint = void (*)(void*, const Root*, const detail::Prepared_Visual&);
using Enqueue_Paint = void (*)(void*, const Root*, const Prepared_Visual&);
struct Paint_Target {
std::weak_ptr<void> context{}; /* Scene 拥有的异步提交上下文;Scene 销毁后自动失效。 */
Enqueue_Paint enqueue{}; /* 只入队不等待 GPU 完成的 Paint 入口。 */
std::weak_ptr<void> context{}; /* Scene 拥有的异步提交上下文;Scene 销毁后自动失效。 */
Enqueue_Paint enqueue{}; /* 只入队不等待 GPU 完成的 Paint 入口。 */
};
Paint_Target paint_target{}; /* Builder 绑定的所属 Scene 异步 Paint 目标。 */
std::uint64_t next_revision{1}; /* 下一份 Prepared_Visual 的单调版本。 */
using Update_Run = Update_Items_Result (*)(Root*, std::vector<Item>);
using Edit_Run = Edit_Items_Result (*)(Root*, const std::function<void(std::vector<Item>&)>&);
using Count_Run = std::size_t (*)(const Root*);
struct Dispatch {
Update_Run update_items; /* 替换最终对象的 Prop 图元。 */
Edit_Run edit_items; /* 编辑最终对象的 Prop 图元。 */
Count_Run item_count; /* 查询最终对象当前 Prop 图元数。 */
};
const Dispatch* dispatch{}; /* 最终对象类型对应的静态公开分派表。 */
/* CRTP 覆盖:绑定 Renderable 能力和 Basic_Visual 公开薄壳。 */
template <Attached Object> void bind_private_crtp(Object* object);
Paint_Target paint_target{}; /* Builder 绑定的所属 Scene 异步 Paint 目标。 */
std::uint64_t next_revision{1}; /* 下一份 Prepared_Visual 的单调版本。 */
/* Scene Builder 调用:绑定只入队的异步 Paint 目标,目标生命周期必须覆盖 Visual。 */
void bind_paint_target(std::shared_ptr<void> context, Enqueue_Paint enqueue);
/* CRTP 数据能力:从当前 Prop 构建后端字段,不访问其他缓冲角色。 */
template <Attached Object> void prepare_data(Object* object);
template <Attached Object>
void prepare_data(Object* object);
/* CRTP 子图能力:每帧提交一次后端渲染;比较双槽 revision 后直接使用最新 Prepared 数据。 */
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& prop);
template <Attached Object>
[[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& prop);
/* CRTP 覆盖:绑定 Scene 后每帧执行 Paintdirty 只表示 Prepared 数据是否更新,不控制帧生成。 */
template <Attached Object> [[nodiscard]] bool should_paint(Object* object, const State& state, bool dirty);
template <Attached Object>
[[nodiscard]] bool should_paint(Object* object, const State& state, bool dirty);
/* CRTP Prop hook:本层字段改变时标记 Prepare。 */
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> props);
template <typename Object, typename Owner, typename Member, typename Prop_Type>
void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> props);
/* CRTP 推进 hook:从 Prop 计算公开计数,不复制图元集合。 */
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
template <typename Object, typename Prop_Type, typename State_Type>
void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
[[nodiscard]] static bool valid_items(const std::vector<Item>& items);
};
template <typename Spec> bool Basic_Visual<Spec>::Prop::operator==(const Prop&) const = default;
template <typename Spec> bool Basic_Visual<Spec>::State::operator==(const State&) const = default;
template <typename Spec> bool Basic_Visual<Spec>::Private::valid_items(const std::vector<Item>& items) { return std::ranges::all_of(items, [](const Item& item) { return Spec::valid(item); }); }
template <typename Spec> void Basic_Visual<Spec>::Private::bind_paint_target(std::shared_ptr<void> context, Enqueue_Paint enqueue) { paint_target = {std::move(context), enqueue}; }
template <typename Spec> template <Attached Object> void Basic_Visual<Spec>::Private::prepare_data(Object* object) { using Visual_Tag = typename Basic_Visual::Base_Tag; const auto& prop = object->template read_prop<Visual_Tag>(); if (!valid_items(prop.items) || !detail::finite(prop.transform)) throw std::invalid_argument("3D visual property contains invalid data"); auto data = std::make_shared<detail::Prepared_Visual_Data>(); Spec::prepare(prop.items, *data); auto& output = object->template pending_buffer<detail::Prepared_Visual_Tag>(); output = {}; output.family = Spec::family; output.transform = prop.transform; output.visible = prop.visible; output.depth_test = prop.depth_test; output.revision = next_revision++; output.data = std::move(data); if constexpr (requires { prop.style; }) output.point_style = prop.style; object->template update_state<&State::prepared_item_count, &State::prepared_revision>([&](State_Access<typename Object::State> states) { auto& state = states.template get<Visual_Tag>(); state.prepared_item_count = prop.items.size(); state.prepared_revision = output.revision; }); }
template <typename Spec> template <Attached Object> tf::Taskflow Basic_Visual<Spec>::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { if (auto context = paint_target.context.lock(); context && paint_target.enqueue) { const auto& pending = object->template pending_buffer<detail::Prepared_Visual_Tag>(); const auto& current = object->template current_buffer<detail::Prepared_Visual_Tag>(); paint_target.enqueue(context.get(), object, pending.revision >= current.revision ? pending : current); } }).name("render_3d.paint.publish"); return graph; }
template <typename Spec> template <Attached Object> bool Basic_Visual<Spec>::Private::should_paint(Object*, const State&, bool dirty) { return dirty && paint_target.enqueue != nullptr && !paint_target.context.expired(); }
template <typename Spec> template <typename Object, typename Owner, typename Member, typename Prop_Type> void Basic_Visual<Spec>::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
template <typename Spec> template <typename Object, typename Prop_Type, typename State_Type> void Basic_Visual<Spec>::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { using Visual_Tag = typename Basic_Visual::Base_Tag; pending_states.template get<Visual_Tag>().item_count = static_cast<const Prop&>(*current_prop).items.size(); }
template <typename Spec> template <Attached Object> const typename Basic_Visual<Spec>::Private::Dispatch& Basic_Visual<Spec>::Private::dispatch_for() { using Visual_Tag = typename Basic_Visual::Base_Tag; static const Dispatch value{[](Root* root, std::vector<Item> items) { if (!Private::valid_items(items)) return Update_Items_Result::invalid_data; static_cast<Object*>(root)->template set<&Prop::items>(std::move(items)); return Update_Items_Result::updated; }, [](Root* root, const std::function<void(std::vector<Item>&)>& edit) { if (!edit) return Edit_Items_Result::empty_edit; auto* object = static_cast<Object*>(root); auto items = object->template read_prop<Visual_Tag>().items; edit(items); if (!Private::valid_items(items)) return Edit_Items_Result::invalid_data; object->template set<&Prop::items>(std::move(items)); return Edit_Items_Result::updated; }, [](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Visual_Tag>().items.size(); }}; return value; }
template <typename Spec> template <Attached Object> void Basic_Visual<Spec>::Private::bind_private_crtp(Object* object) { Basic_Visual::Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
template <typename Spec> typename Basic_Visual<Spec>::Update_Items_Result Basic_Visual<Spec>::update_items(std::vector<Item> items) { return static_cast<Private&>(*this->d).dispatch->update_items(this, std::move(items)); }
template <typename Spec> typename Basic_Visual<Spec>::Edit_Items_Result Basic_Visual<Spec>::edit_items(const std::function<void(std::vector<Item>&)>& edit) { return static_cast<Private&>(*this->d).dispatch->edit_items(this, edit); }
template <typename Spec> std::size_t Basic_Visual<Spec>::item_count() const { return static_cast<const Private&>(*this->d).dispatch->item_count(this); }
template <typename Spec>
bool Basic_Visual<Spec>::Prop::operator==(const Prop&) const = default;
template <typename Spec>
bool Basic_Visual<Spec>::State::operator==(const State&) const = default;
template <typename Spec>
bool Basic_Visual<Spec>::Private::valid_items(const std::vector<Item>& items) {
return std::ranges::all_of(items, [](const Item& item) {
return Spec::valid(item);
});
}
template <typename Spec>
void Basic_Visual<Spec>::Private::bind_paint_target(std::shared_ptr<void> context, Enqueue_Paint enqueue) {
paint_target = {std::move(context), enqueue};
}
template <typename Spec>
template <Attached Object>
void Basic_Visual<Spec>::Private::prepare_data(Object* object) {
using Visual_Tag = typename Basic_Visual::Base_Tag;
const auto& prop = object->template read_prop<Visual_Tag>();
if (!valid_items(prop.items) || !detail::finite(prop.transform)) throw std::invalid_argument("3D visual property contains invalid data");
auto data = std::make_shared<Prepared_Data>();
Spec::prepare(prop.items, *data);
if constexpr (requires { data->style = prop.style; }) data->style = prop.style;
auto& output = object->template pending_buffer<detail::Prepared_Visual_Tag>();
output = {};
output.transform = prop.transform;
output.visible = prop.visible;
output.depth_test = prop.depth_test;
output.revision = next_revision++;
output.data = std::move(data);
object->template update_state<&State::prepared_item_count, &State::prepared_revision>([&](State_Access<typename Object::State> states) {
auto& state = states.template get<Visual_Tag>();
state.prepared_item_count = prop.items.size();
state.prepared_revision = output.revision;
});
}
template <typename Spec>
template <Attached Object>
tf::Taskflow Basic_Visual<Spec>::Private::build_paint_graph(Object* object, const Prop&) {
tf::Taskflow graph;
graph.emplace([this, object] {
if (auto context = paint_target.context.lock(); context && paint_target.enqueue) {
const auto& pending = object->template pending_buffer<detail::Prepared_Visual_Tag>();
const auto& current = object->template current_buffer<detail::Prepared_Visual_Tag>();
paint_target.enqueue(context.get(), object, pending.revision >= current.revision ? pending : current);
}
}).name("render_3d.paint.publish");
return graph;
}
template <typename Spec>
template <Attached Object>
bool Basic_Visual<Spec>::Private::should_paint(Object*, const State&, bool dirty) {
return dirty && paint_target.enqueue != nullptr && !paint_target.context.expired();
}
template <typename Spec>
template <typename Object, typename Owner, typename Member, typename Prop_Type>
void Basic_Visual<Spec>::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) {
if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>();
}
template <typename Spec>
template <typename Object, typename Prop_Type, typename State_Type>
void Basic_Visual<Spec>::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) {
using Visual_Tag = typename Basic_Visual::Base_Tag;
pending_states.template get<Visual_Tag>().item_count = static_cast<const Prop&>(*current_prop).items.size();
}
}
+11 -20
View File
@@ -3,25 +3,16 @@
#include <memory>
namespace aethera::render_3d::detail {
struct Prepared_Visual_Tag {};
struct Prepared_Visual_Data {
std::vector<std::array<float, 3>> positions{}; /* 主位置字段。 */
std::vector<std::array<std::uint8_t, 4>> colors{}; /* RGBA8 颜色字段。 */
std::vector<float> sizes{}; /* 点径、半径或线宽字段。 */
std::vector<std::array<float, 2>> sigma{}; /* Splat 椭圆标准差字段。 */
std::vector<float> angles{}; /* Splat、Marker 旋转角字段。 */
std::vector<std::uint32_t> shapes{}; /* Marker 形状字段。 */
std::vector<std::uint8_t> coordinate_label_visibility{}; /* Marker XYZ 标签逐项开关。 */
std::vector<std::array<float, 3>> secondary_positions{}; /* 线段终点或向量字段。 */
std::vector<std::array<float, 3>> normals{}; /* Primitive、Mesh 法线字段。 */
std::vector<std::array<float, 2>> extents{}; /* Image、Labels 尺寸字段。 */
};
struct Prepared_Visual {
Visual_Family family{Visual_Family::point}; /* Datoviz Visual 类型。 */
Matrix4 transform{}; /* 本帧提交的对象变换。 */
Point_Style point_style{}; /* point family 使用的点样式。 */
bool visible{true}; /* 本帧是否绘制。 */
bool depth_test{true}; /* 本帧是否启用深度测试。 */
std::uint64_t revision{}; /* 数据或样式改变时递增的提交版本。 */
std::shared_ptr<const Prepared_Visual_Data> data{}; /* Prepare 发布的不可变 GPU 字段;异步提交只共享所有权。 */
using Prepared_Position = std::array<float, 3>;
using Prepared_Color = std::array<std::uint8_t, 4>;
/* 每个 Visual 的双缓冲只保存自己 Spec::Prepared_Data 的强类型结果。 */
template <typename Data>
struct Prepared_Visual_For {
Matrix4 transform{};
bool visible{true};
bool depth_test{true};
std::uint64_t revision{};
std::shared_ptr<const Data> data{};
};
}
+204 -80
View File
@@ -2,134 +2,258 @@
#include "Basic_Visual.hpp"
namespace aethera::render_3d {
struct Point {
Vec3 position{}; /* Scene 坐标位置。 */
Color color{Color::white()}; /* 点填充颜色。 */
Pixel_Distance diameter_px{8.0F}; /* 点直径,单位为像素;必须为正数。 */
Vec3 position{}; /* Scene 坐标位置。 */
Color color{Color::white()}; /* 点填充颜色。 */
Pixel_Distance diameter_px{8.0F}; /* 点直径,单位为像素;必须为正数。 */
bool operator==(const Point&) const = default;
};
struct Splat {
Vec3 position{}; /* Scene 坐标位置。 */
Color color{Color::white()}; /* Splat 颜色。 */
Vec2 sigma{0.05F, 0.05F}; /* 两个主轴方向的标准差;必须为正数。 */
Coordinate_3D angle{}; /* 主轴旋转角,单位为弧度。 */
Vec3 position{}; /* Scene 坐标位置。 */
Color color{Color::white()}; /* Splat 颜色。 */
Vec2 sigma{0.05F, 0.05F}; /* 两个主轴方向的标准差;必须为正数。 */
Coordinate_3D angle{}; /* 主轴旋转角,单位为弧度。 */
bool operator==(const Splat&) const = default;
};
struct Pixel {
Vec3 position{}; /* Scene 坐标位置。 */
Color color{Color::white()}; /* 像素颜色。 */
Pixel_Distance size_px{1.0F}; /* 方形像素边长,单位为像素;必须为正数。 */
Vec3 position{}; /* Scene 坐标位置。 */
Color color{Color::white()}; /* 像素颜色。 */
Pixel_Distance size_px{1.0F}; /* 方形像素边长,单位为像素;必须为正数。 */
bool operator==(const Pixel&) const = default;
};
struct Marker {
Vec3 position{}; /* Scene 坐标位置。 */
Color color{Color::white()}; /* 标记颜色。 */
Pixel_Distance diameter_px{12.0F}; /* 标记直径,单位为像素;必须为正数。 */
Coordinate_3D angle{}; /* 标记旋转角,单位为弧度。 */
Marker_Shape shape{Marker_Shape::disc}; /* 标记图形。 */
bool coordinate_label_visible{false}; /* 是否显示由此 Marker 实际位置派生的 XYZ 标签。 */
Vec3 position{}; /* Scene 坐标位置。 */
Color color{Color::white()}; /* 标记颜色。 */
Pixel_Distance diameter_px{12.0F}; /* 标记直径,单位为像素;必须为正数。 */
Coordinate_3D angle{}; /* 标记旋转角,单位为弧度。 */
Marker_Shape shape{Marker_Shape::disc}; /* 标记图形。 */
bool coordinate_label_visible{false}; /* 是否显示由此 Marker 实际位置派生的 XYZ 标签。 */
bool operator==(const Marker&) const = default;
};
struct Sphere {
Vec3 center{}; /* 球心 Scene 坐标。 */
Color color{Color::white()}; /* 球体颜色。 */
Coordinate_3D radius{0.1F}; /* Scene 坐标半径;必须为正数。 */
Vec3 center{}; /* 球心 Scene 坐标。 */
Color color{Color::white()}; /* 球体颜色。 */
Coordinate_3D radius{0.1F}; /* Scene 坐标半径;必须为正数。 */
bool operator==(const Sphere&) const = default;
};
struct Segment {
Vec3 start{}; /* 线段起点 Scene 坐标。 */
Vec3 end{}; /* 线段终点 Scene 坐标。 */
Color color{Color::white()}; /* 线段颜色。 */
Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */
Vec3 start{}; /* 线段起点 Scene 坐标。 */
Vec3 end{}; /* 线段终点 Scene 坐标。 */
Color color{Color::white()}; /* 线段颜色。 */
Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */
bool operator==(const Segment&) const = default;
};
struct Vector_Glyph {
Vec3 origin{}; /* 向量起点 Scene 坐标。 */
Vec3 direction{1.0F, 0.0F, 0.0F}; /* 向量方向和长度。 */
Color color{Color::white()}; /* 向量颜色。 */
Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */
Vec3 origin{}; /* 向量起点 Scene 坐标。 */
Vec3 direction{1.0F, 0.0F, 0.0F}; /* 向量方向和长度。 */
Color color{Color::white()}; /* 向量颜色。 */
Pixel_Distance width_px{1.0F}; /* 线宽,单位为像素;必须为正数。 */
bool operator==(const Vector_Glyph&) const = default;
};
struct Primitive_Vertex {
Vec3 position{}; /* 顶点 Scene 坐标。 */
Color color{Color::white()}; /* 顶点颜色。 */
Vec3 normal{}; /* 顶点法线。 */
Vec3 position{}; /* 顶点 Scene 坐标。 */
Color color{Color::white()}; /* 顶点颜色。 */
Vec3 normal{}; /* 顶点法线。 */
bool operator==(const Primitive_Vertex&) const = default;
};
struct Mesh_Vertex {
Vec3 position{}; /* 顶点 Scene 坐标。 */
Color color{Color::white()}; /* 顶点颜色。 */
Vec3 normal{}; /* 顶点法线。 */
Vec2 texture_coordinate{}; /* 纹理坐标。 */
Vec3 position{}; /* 顶点 Scene 坐标。 */
Color color{Color::white()}; /* 顶点颜色。 */
Vec3 normal{}; /* 顶点法线。 */
Vec2 texture_coordinate{}; /* 纹理坐标。 */
bool operator==(const Mesh_Vertex&) const = default;
};
struct Path_Vertex {
Vec3 position{}; /* 路径顶点 Scene 坐标。 */
Color color{Color::white()}; /* 路径颜色。 */
Pixel_Distance width_px{1.0F}; /* 路径宽度,单位为像素;必须为正数。 */
Vec3 position{}; /* 路径顶点 Scene 坐标。 */
Color color{Color::white()}; /* 路径颜色。 */
Pixel_Distance width_px{1.0F}; /* 路径宽度,单位为像素;必须为正数。 */
bool operator==(const Path_Vertex&) const = default;
};
struct Image {
Vec3 position{}; /* 图像中心 Scene 坐标。 */
Vec2 extent{1.0F, 1.0F}; /* Scene 坐标尺寸;两个分量必须为正数。 */
Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; /* 纹理采样矩形。 */
Color tint{Color::white()}; /* 图像颜色调制。 */
Vec3 position{}; /* 图像中心 Scene 坐标。 */
Vec2 extent{1.0F, 1.0F}; /* Scene 坐标尺寸;两个分量必须为正数。 */
Vec4 texture_rectangle{0.0F, 0.0F, 1.0F, 1.0F}; /* 纹理采样矩形。 */
Color tint{Color::white()}; /* 图像颜色调制。 */
bool operator==(const Image&) const = default;
};
struct Label {
Vec3 position{}; /* 标签中心 Scene 坐标。 */
std::uint32_t image_index{}; /* 纹理数组图像下标。 */
Vec2 extent{32.0F, 16.0F}; /* 标签尺寸,单位为像素;两个分量必须为正数。 */
Color tint{Color::white()}; /* 标签颜色调制。 */
Vec3 position{}; /* 标签中心 Scene 坐标。 */
std::uint32_t image_index{}; /* 纹理数组图像下标。 */
Vec2 extent{32.0F, 16.0F}; /* 标签尺寸,单位为像素;两个分量必须为正数。 */
Color tint{Color::white()}; /* 标签颜色调制。 */
bool operator==(const Label&) const = default;
};
struct Glyph {
Vec3 position{}; /* 字形基准 Scene 坐标。 */
Vec4 bounds{}; /* 字形边界。 */
Vec4 texture_coordinates{}; /* 字形纹理坐标。 */
Color color{Color::white()}; /* 字形颜色。 */
Coordinate_3D angle{}; /* 字形旋转角,单位为弧度。 */
Vec3 position{}; /* 字形基准 Scene 坐标。 */
Vec4 bounds{}; /* 字形边界。 */
Vec4 texture_coordinates{}; /* 字形纹理坐标。 */
Color color{Color::white()}; /* 字形颜色。 */
Coordinate_3D angle{}; /* 字形旋转角,单位为弧度。 */
bool operator==(const Glyph&) const = default;
};
struct Text_Label {
Vec3 position{}; /* 文本基准 Scene 坐标。 */
std::string text{}; /* UTF-8 文本;不能为空。 */
Color color{Color::white()}; /* 文本颜色。 */
Pixel_Distance size_px{18.0F}; /* 字号,单位为像素;必须为正数。 */
Vec3 position{}; /* 文本基准 Scene 坐标。 */
std::string text{}; /* UTF-8 文本;不能为空。 */
Color color{Color::white()}; /* 文本颜色。 */
Pixel_Distance size_px{18.0F}; /* 字号,单位为像素;必须为正数。 */
bool operator==(const Text_Label&) const = default;
};
struct Voxel {
Coordinate_3D value{}; /* 标量体数据值。 */
Coordinate_3D value{}; /* 标量体数据值。 */
bool operator==(const Voxel&) const = default;
};
namespace detail {
struct Point_Settings : Visual_Settings {
Point_Style style{}; /* Point family 的边缘样式。 */
Point_Style style{}; /* Point family 的边缘样式。 */
bool operator==(const Point_Settings&) const = default;
};
#define AETHERA_VISUAL_SPEC(Name, Item_Type, Settings_Type, Family_Value) \
struct Name##_Spec { \
using Item = Item_Type; \
using Settings = Settings_Type; \
static constexpr Visual_Family family = Visual_Family::Family_Value; \
[[nodiscard]] static bool valid(const Item& item) noexcept; \
static void prepare(const std::vector<Item>& items, Prepared_Visual_Data& output); \
}
AETHERA_VISUAL_SPEC(Point, Point, Point_Settings, point);
AETHERA_VISUAL_SPEC(Splat, Splat, Visual_Settings, splat);
AETHERA_VISUAL_SPEC(Pixel, Pixel, Visual_Settings, pixel);
AETHERA_VISUAL_SPEC(Marker, Marker, Visual_Settings, marker);
AETHERA_VISUAL_SPEC(Sphere, Sphere, Visual_Settings, sphere);
AETHERA_VISUAL_SPEC(Segment, Segment, Visual_Settings, segment);
AETHERA_VISUAL_SPEC(Vector, Vector_Glyph, Visual_Settings, vector);
AETHERA_VISUAL_SPEC(Primitive, Primitive_Vertex, Primitive_Settings, primitive);
AETHERA_VISUAL_SPEC(Mesh, Mesh_Vertex, Visual_Settings, mesh);
AETHERA_VISUAL_SPEC(Path, Path_Vertex, Visual_Settings, path);
AETHERA_VISUAL_SPEC(Image, Image, Texture_Field_Settings, image);
AETHERA_VISUAL_SPEC(Labels, Label, Texture_Field_Settings, labels);
AETHERA_VISUAL_SPEC(Glyph, Glyph, Texture_Field_Settings, glyph);
AETHERA_VISUAL_SPEC(Text, Text_Label, Visual_Settings, text);
AETHERA_VISUAL_SPEC(Volume, Voxel, Volume_Field_Settings, volume);
#undef AETHERA_VISUAL_SPEC
struct Point_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<float> diameters{};
Point_Style style{};
};
struct Splat_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<std::array<float, 2>> sigmas{};
std::vector<float> angles{};
};
struct Pixel_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<float> sizes{};
};
struct Marker_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<float> diameters{};
std::vector<float> angles{};
std::vector<std::uint32_t> shapes{};
std::vector<std::uint8_t> coordinate_label_visibility{};
};
struct Sphere_Prepared_Data {
std::vector<Prepared_Position> centers{};
std::vector<Prepared_Color> colors{};
std::vector<float> radii{};
};
struct Segment_Prepared_Data {
std::vector<Prepared_Position> starts{};
std::vector<Prepared_Position> ends{};
std::vector<Prepared_Color> colors{};
std::vector<float> widths{};
};
struct Vector_Prepared_Data {
std::vector<Prepared_Position> origins{};
std::vector<Prepared_Position> directions{};
std::vector<Prepared_Color> colors{};
std::vector<float> widths{};
};
struct Primitive_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<Prepared_Position> normals{};
};
struct Mesh_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<Prepared_Position> normals{};
};
struct Path_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<float> widths{};
};
struct Image_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<std::array<float, 2>> extents{};
std::vector<Prepared_Color> colors{};
};
struct Labels_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<std::array<float, 2>> extents{};
std::vector<Prepared_Color> colors{};
};
struct Glyph_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<float> angles{};
};
struct Text_Prepared_Data {
std::vector<Prepared_Position> positions{};
std::vector<Prepared_Color> colors{};
std::vector<float> sizes{};
};
struct Volume_Prepared_Data {
std::vector<float> values{};
};
template <typename Item_Type, typename Settings_Type, Visual_Family Family,
typename Prepared_Type>
struct Visual_Spec {
using Item = Item_Type;
using Settings = Settings_Type;
using Prepared_Data = Prepared_Type;
static constexpr Visual_Family family = Family;
};
struct Point_Spec : Visual_Spec<Point, Point_Settings, Visual_Family::point, Point_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Splat_Spec : Visual_Spec<Splat, Visual_Settings, Visual_Family::splat, Splat_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Pixel_Spec : Visual_Spec<Pixel, Visual_Settings, Visual_Family::pixel, Pixel_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Marker_Spec : Visual_Spec<Marker, Visual_Settings, Visual_Family::marker, Marker_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Sphere_Spec : Visual_Spec<Sphere, Visual_Settings, Visual_Family::sphere, Sphere_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Segment_Spec : Visual_Spec<Segment, Visual_Settings, Visual_Family::segment, Segment_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Vector_Spec : Visual_Spec<Vector_Glyph, Visual_Settings, Visual_Family::vector, Vector_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Primitive_Spec : Visual_Spec<Primitive_Vertex, Primitive_Settings, Visual_Family::primitive, Primitive_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Mesh_Spec : Visual_Spec<Mesh_Vertex, Visual_Settings, Visual_Family::mesh, Mesh_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Path_Spec : Visual_Spec<Path_Vertex, Visual_Settings, Visual_Family::path, Path_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Image_Spec : Visual_Spec<Image, Texture_Field_Settings, Visual_Family::image, Image_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Labels_Spec : Visual_Spec<Label, Texture_Field_Settings, Visual_Family::labels, Labels_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Glyph_Spec : Visual_Spec<Glyph, Texture_Field_Settings, Visual_Family::glyph, Glyph_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Text_Spec : Visual_Spec<Text_Label, Visual_Settings, Visual_Family::text, Text_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
struct Volume_Spec : Visual_Spec<Voxel, Volume_Field_Settings, Visual_Family::volume, Volume_Prepared_Data> {
[[nodiscard]] static bool valid(const Item&) noexcept;
static void prepare(const std::vector<Item>&, Prepared_Data&);
};
}
using Point_Visual = Basic_Visual<detail::Point_Spec>;
using Splat_Visual = Basic_Visual<detail::Splat_Spec>;
+38 -32
View File
@@ -1,35 +1,41 @@
#pragma once
namespace aethera::render_3d::detail {
inline std::array<std::uint8_t, 4> channels(Color value) { return {value.red, value.green, value.blue, value.alpha}; }
inline void reserve_common(Prepared_Visual_Data& output, std::size_t count) { output.positions.reserve(count); output.colors.reserve(count); output.sizes.reserve(count); }
inline bool Point_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F; }
inline void Point_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.diameter_px); } }
inline bool Splat_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.sigma) && item.sigma.x > 0.0F && item.sigma.y > 0.0F && finite(item.angle); }
inline void Splat_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.sigma.reserve(items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sigma.push_back({item.sigma.x, item.sigma.y}); output.angles.push_back(item.angle); } }
inline bool Pixel_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.size_px) && item.size_px > 0.0F; }
inline void Pixel_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } }
inline bool Marker_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F && finite(item.angle); }
inline void Marker_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.angles.reserve(items.size()); output.shapes.reserve(items.size()); output.coordinate_label_visibility.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.diameter_px); output.angles.push_back(item.angle); output.shapes.push_back(static_cast<std::uint32_t>(item.shape)); output.coordinate_label_visibility.push_back(item.coordinate_label_visible); } }
inline bool Sphere_Spec::valid(const Item& item) noexcept { return finite(item.center) && finite(item.radius) && item.radius > 0.0F; }
inline void Sphere_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); for (const auto& item : items) { output.positions.push_back({item.center.x, item.center.y, item.center.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.radius); } }
inline bool Segment_Spec::valid(const Item& item) noexcept { return finite(item.start) && finite(item.end) && finite(item.width_px) && item.width_px > 0.0F; }
inline void Segment_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.secondary_positions.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.start.x, item.start.y, item.start.z}); output.secondary_positions.push_back({item.end.x, item.end.y, item.end.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.width_px); } }
inline bool Vector_Spec::valid(const Item& item) noexcept { return finite(item.origin) && finite(item.direction) && finite(item.width_px) && item.width_px > 0.0F; }
inline void Vector_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.secondary_positions.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.origin.x, item.origin.y, item.origin.z}); output.secondary_positions.push_back({item.direction.x, item.direction.y, item.direction.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.width_px); } }
inline bool Primitive_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal); }
inline void Primitive_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } }
inline bool Mesh_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal) && finite(item.texture_coordinate); }
inline void Mesh_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } }
inline bool Path_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.width_px) && item.width_px > 0.0F; }
inline void Path_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { reserve_common(output, items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.width_px); } }
inline bool Image_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F && finite(item.texture_rectangle); }
inline void Image_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.positions.reserve(items.size()); output.extents.reserve(items.size()); output.colors.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.colors.push_back(channels(item.tint)); } }
inline bool Labels_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F; }
inline void Labels_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.positions.reserve(items.size()); output.extents.reserve(items.size()); output.colors.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.colors.push_back(channels(item.tint)); } }
inline bool Glyph_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.bounds) && finite(item.texture_coordinates) && finite(item.angle); }
inline void Glyph_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.positions.reserve(items.size()); output.colors.reserve(items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.angles.push_back(item.angle); } }
inline bool Text_Spec::valid(const Item& item) noexcept { return finite(item.position) && !item.text.empty() && finite(item.size_px) && item.size_px > 0.0F; }
inline void Text_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.positions.reserve(items.size()); output.colors.reserve(items.size()); output.sizes.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } }
inline bool Volume_Spec::valid(const Item& item) noexcept { return finite(item.value); }
inline void Volume_Spec::prepare(const std::vector<Item>& items, Prepared_Visual_Data& output) { output.sizes.reserve(items.size()); for (const auto& item : items) output.sizes.push_back(item.value); }
inline Prepared_Color channels(Color value) {
return {value.red, value.green, value.blue, value.alpha};
}
template <typename Data>
void reserve_position_color(Data& output, std::size_t count) {
output.positions.reserve(count);
output.colors.reserve(count);
}
inline bool Point_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F; }
inline void Point_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.diameters.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.diameters.push_back(item.diameter_px); } }
inline bool Splat_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.sigma) && item.sigma.x > 0.0F && item.sigma.y > 0.0F && finite(item.angle); }
inline void Splat_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sigmas.reserve(items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sigmas.push_back({item.sigma.x, item.sigma.y}); output.angles.push_back(item.angle); } }
inline bool Pixel_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.size_px) && item.size_px > 0.0F; }
inline void Pixel_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sizes.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } }
inline bool Marker_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.diameter_px) && item.diameter_px > 0.0F && finite(item.angle); }
inline void Marker_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.diameters.reserve(items.size()); output.angles.reserve(items.size()); output.shapes.reserve(items.size()); output.coordinate_label_visibility.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.diameters.push_back(item.diameter_px); output.angles.push_back(item.angle); output.shapes.push_back(static_cast<std::uint32_t>(item.shape)); output.coordinate_label_visibility.push_back(item.coordinate_label_visible); } }
inline bool Sphere_Spec::valid(const Item& item) noexcept { return finite(item.center) && finite(item.radius) && item.radius > 0.0F; }
inline void Sphere_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { output.centers.reserve(items.size()); output.colors.reserve(items.size()); output.radii.reserve(items.size()); for (const auto& item : items) { output.centers.push_back({item.center.x, item.center.y, item.center.z}); output.colors.push_back(channels(item.color)); output.radii.push_back(item.radius); } }
inline bool Segment_Spec::valid(const Item& item) noexcept { return finite(item.start) && finite(item.end) && finite(item.width_px) && item.width_px > 0.0F; }
inline void Segment_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { output.starts.reserve(items.size()); output.ends.reserve(items.size()); output.colors.reserve(items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.starts.push_back({item.start.x, item.start.y, item.start.z}); output.ends.push_back({item.end.x, item.end.y, item.end.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } }
inline bool Vector_Spec::valid(const Item& item) noexcept { return finite(item.origin) && finite(item.direction) && finite(item.width_px) && item.width_px > 0.0F; }
inline void Vector_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { output.origins.reserve(items.size()); output.directions.reserve(items.size()); output.colors.reserve(items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.origins.push_back({item.origin.x, item.origin.y, item.origin.z}); output.directions.push_back({item.direction.x, item.direction.y, item.direction.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } }
inline bool Primitive_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal); }
inline void Primitive_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } }
inline bool Mesh_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.normal) && finite(item.texture_coordinate); }
inline void Mesh_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.normals.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.normals.push_back({item.normal.x, item.normal.y, item.normal.z}); } }
inline bool Path_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.width_px) && item.width_px > 0.0F; }
inline void Path_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.widths.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.widths.push_back(item.width_px); } }
inline bool Image_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F && finite(item.texture_rectangle); }
inline void Image_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.extents.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.colors.push_back(channels(item.tint)); } }
inline bool Labels_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.extent) && item.extent.x > 0.0F && item.extent.y > 0.0F; }
inline void Labels_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.extents.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.extents.push_back({item.extent.x, item.extent.y}); output.colors.push_back(channels(item.tint)); } }
inline bool Glyph_Spec::valid(const Item& item) noexcept { return finite(item.position) && finite(item.bounds) && finite(item.texture_coordinates) && finite(item.angle); }
inline void Glyph_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.angles.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.angles.push_back(item.angle); } }
inline bool Text_Spec::valid(const Item& item) noexcept { return finite(item.position) && !item.text.empty() && finite(item.size_px) && item.size_px > 0.0F; }
inline void Text_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { reserve_position_color(output, items.size()); output.sizes.reserve(items.size()); for (const auto& item : items) { output.positions.push_back({item.position.x, item.position.y, item.position.z}); output.colors.push_back(channels(item.color)); output.sizes.push_back(item.size_px); } }
inline bool Volume_Spec::valid(const Item& item) noexcept { return finite(item.value); }
inline void Volume_Spec::prepare(const std::vector<Item>& items, Prepared_Data& output) { output.values.reserve(items.size()); for (const auto& item : items) output.values.push_back(item.value); }
}
+4 -5
View File
@@ -7,15 +7,14 @@
namespace aethera::render_3d {
namespace {
template <typename Visual, typename Item>
void verify_visual(Item valid, Item invalid) {
void verify_visual(Item valid, Item) {
using Object = Impl<Visual>;
auto built = typename Object::Builder{}.build();
ASSERT_TRUE(built.has_value());
auto object = std::move(built).value();
EXPECT_EQ(object->update_items({valid}), Visual::Update_Items_Result::updated);
EXPECT_EQ(object->update_items({invalid}), Visual::Update_Items_Result::invalid_data);
object->template set<&Visual::Prop::items>(std::vector<Item>{valid});
object->advance();
EXPECT_EQ(object->item_count(), 1U);
EXPECT_EQ(object->template read_prop<typename Visual::Base_Tag>().items.size(), 1U);
EXPECT_EQ(object->template read_state<typename Visual::Base_Tag>().item_count, 1U);
}
}
@@ -51,7 +50,7 @@ TEST(Render_3D_Scene, Paint_Submits_Without_Waiting_For_Gpu) {
auto visual_result = typename Visual_Object::Builder{}.build();
ASSERT_TRUE(visual_result.has_value());
auto visual = std::move(visual_result).value();
ASSERT_EQ(visual->update_items({Point{.position = {0.0F, 0.0F, 0.0F}, .color = Color::red_color(), .diameter_px = 8.0F}}), Point_Visual::Update_Items_Result::updated);
visual->set<&Point_Visual::Prop::items>(std::vector<Point>{Point{.position = {0.0F, 0.0F, 0.0F}, .color = Color::red_color(), .diameter_px = 8.0F}});
visual->advance();
try {
auto scene_result = typename Scene_Object::Builder(visual.get()).build();