Files
Aethera/render_3D/tests/Visual_Tests.cpp
T
2026-08-24 13:12:57 +08:00

105 lines
5.8 KiB
C++

#include <render_3D/Render_3D.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <thread>
namespace aethera::render_3d {
namespace {
template <typename Visual, typename Item>
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();
object->template set<&Visual::Prop::items>(std::vector<Item>{valid});
object->advance();
EXPECT_EQ(object->template read_prop<typename Visual::Base_Tag>().items.size(), 1U);
}
}
TEST(Render_3D_Visual, Migrated_Families_Build_And_Validate) {
verify_visual<Point_Visual>(Point{}, Point{.diameter_px = 0.0F});
verify_visual<Splat_Visual>(Splat{}, Splat{.sigma = {0.0F, 0.0F}});
verify_visual<Pixel_Visual>(Pixel{}, Pixel{.size_px = 0.0F});
verify_visual<Marker_Visual>(Marker{}, Marker{.diameter_px = 0.0F});
verify_visual<Sphere_Visual>(Sphere{}, Sphere{.radius = 0.0F});
verify_visual<Segment_Visual>(Segment{}, Segment{.width_px = 0.0F});
verify_visual<Vector_Visual>(Vector_Glyph{}, Vector_Glyph{.width_px = 0.0F});
verify_visual<Primitive_Visual>(Primitive_Vertex{}, Primitive_Vertex{.position = {NAN, 0.0F, 0.0F}});
verify_visual<Mesh_Visual>(Mesh_Vertex{}, Mesh_Vertex{.normal = {NAN, 0.0F, 0.0F}});
verify_visual<Path_Visual>(Path_Vertex{}, Path_Vertex{.width_px = 0.0F});
verify_visual<Image_Visual>(Image{}, Image{.extent = {0.0F, 0.0F}});
verify_visual<Labels_Visual>(Label{}, Label{.extent = {0.0F, 0.0F}});
verify_visual<Glyph_Visual>(Glyph{}, Glyph{.angle = NAN});
verify_visual<Text_Visual>(Text_Label{.text = "A"}, Text_Label{});
verify_visual<Volume_Visual>(Voxel{}, Voxel{.value = NAN});
}
TEST(Render_3D_Scene, Builder_Instantiates_Async_Paint_Contract) {
using Visual_Object = Impl<Point_Visual>;
using Scene_Object = Impl<Render_Scene_3D>;
auto visual_result = typename Visual_Object::Builder{}.build();
ASSERT_TRUE(visual_result.has_value());
auto visual = std::move(visual_result).value();
auto camera = *Impl<Camera_3D>::Builder{}.build();
auto axes = *Impl<Axes_3D>::Builder{}.build();
typename Scene_Object::Builder scene_builder;
scene_builder.add_renderable(visual.get()).add_camera(camera.get()).add_axes(axes.get());
static_assert(std::same_as<decltype(scene_builder.build()), std::expected<std::unique_ptr<Scene_Object>, Dependency_Graph_Error>>);
}
TEST(Render_3D_Scene, Paint_Submits_Without_Waiting_For_Gpu) {
using Visual_Object = Impl<Point_Visual>;
using Scene_Object = Impl<Render_Scene_3D>;
auto visual_result = typename Visual_Object::Builder{}.build();
ASSERT_TRUE(visual_result.has_value());
auto visual = std::move(visual_result).value();
auto camera = *Impl<Camera_3D>::Builder{}.build();
auto axes = *Impl<Axes_3D>::Builder{}.build();
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 {
typename Scene_Object::Builder scene_builder;
scene_builder.add_renderable(visual.get()).add_camera(camera.get()).add_axes(axes.get());
auto scene_result = scene_builder.build();
ASSERT_TRUE(scene_result.has_value());
auto scene = std::move(scene_result).value();
std::atomic_bool frame_ready{};
Frame_3D frame{Frame_Identity{1, 0}};
Frame_3D unchanged_frame{Frame_Identity{2, 0}};
Frame_3D exchanged_unchanged_frame{Frame_Identity{3, 0}};
Frame_3D* completed_frame{};
std::atomic_size_t completion_calls{};
scene->completion_taskflow().emplace([&] {
completion_calls.fetch_add(1, std::memory_order_release);
}).name("test.scene_3d.completion");
scene->set_frame_callback([&](Frame_3D* value) {
EXPECT_GT(completion_calls.load(std::memory_order_acquire), 0u);
completed_frame = value;
frame_ready.store(true, std::memory_order_release);
});
const auto started = std::chrono::steady_clock::now();
EXPECT_EQ(scene->render(&frame), Render_Scene_3D::Render_Result::submitted);
const auto submit_duration = std::chrono::steady_clock::now() - started;
EXPECT_LT(submit_duration, std::chrono::seconds(1));
for (std::size_t attempt = 0; attempt != 500 && !frame_ready.load(std::memory_order_acquire); ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(10));
EXPECT_EQ(completed_frame, &frame);
EXPECT_FALSE(frame.pixels().empty());
frame_ready.store(false, std::memory_order_release);
completed_frame = nullptr;
EXPECT_EQ(scene->render(&unchanged_frame), Render_Scene_3D::Render_Result::submitted);
for (std::size_t attempt = 0; attempt != 500 && !frame_ready.load(std::memory_order_acquire); ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(10));
EXPECT_EQ(completed_frame, &unchanged_frame);
EXPECT_FALSE(unchanged_frame.pixels().empty());
frame_ready.store(false, std::memory_order_release);
completed_frame = nullptr;
EXPECT_EQ(scene->render(&exchanged_unchanged_frame), Render_Scene_3D::Render_Result::submitted);
for (std::size_t attempt = 0; attempt != 500 && !frame_ready.load(std::memory_order_acquire); ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(10));
EXPECT_EQ(completed_frame, &exchanged_unchanged_frame);
EXPECT_EQ(completion_calls.load(std::memory_order_acquire), 3u);
EXPECT_TRUE(std::ranges::equal(unchanged_frame.pixels(), exchanged_unchanged_frame.pixels()));
}
catch (const std::exception& error) {
GTEST_SKIP() << "Vulkan/Datoviz unavailable: " << error.what();
}
}
}