#include #include #include #include #include #include namespace aethera::render_3d { namespace { template void verify_visual(Item valid, Item) { using Object = Impl; 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{valid}); object->advance(); EXPECT_EQ(object->template read_prop().items.size(), 1U); } } TEST(Render_3D_Visual, Migrated_Families_Build_And_Validate) { verify_visual(Point{}, Point{.diameter_px = 0.0F}); verify_visual(Splat{}, Splat{.sigma = {0.0F, 0.0F}}); verify_visual(Pixel{}, Pixel{.size_px = 0.0F}); verify_visual(Marker{}, Marker{.diameter_px = 0.0F}); verify_visual(Sphere{}, Sphere{.radius = 0.0F}); verify_visual(Segment{}, Segment{.width_px = 0.0F}); verify_visual(Vector_Glyph{}, Vector_Glyph{.width_px = 0.0F}); verify_visual(Primitive_Vertex{}, Primitive_Vertex{.position = {NAN, 0.0F, 0.0F}}); verify_visual(Mesh_Vertex{}, Mesh_Vertex{.normal = {NAN, 0.0F, 0.0F}}); verify_visual(Path_Vertex{}, Path_Vertex{.width_px = 0.0F}); verify_visual(Image{}, Image{.extent = {0.0F, 0.0F}}); verify_visual(Label{}, Label{.extent = {0.0F, 0.0F}}); verify_visual(Glyph{}, Glyph{.angle = NAN}); verify_visual(Text_Label{.text = "A"}, Text_Label{}); verify_visual(Voxel{}, Voxel{.value = NAN}); } TEST(Render_3D_Scene, Builder_Instantiates_Async_Paint_Contract) { using Visual_Object = Impl; using Scene_Object = Impl; auto visual_result = typename Visual_Object::Builder{}.build(); ASSERT_TRUE(visual_result.has_value()); auto visual = std::move(visual_result).value(); auto camera = *Impl::Builder{}.build(); auto axes = *Impl::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, Dependency_Graph_Error>>); } TEST(Render_3D_Scene, Paint_Submits_Without_Waiting_For_Gpu) { using Visual_Object = Impl; using Scene_Object = Impl; auto visual_result = typename Visual_Object::Builder{}.build(); ASSERT_TRUE(visual_result.has_value()); auto visual = std::move(visual_result).value(); auto camera = *Impl::Builder{}.build(); auto axes = *Impl::Builder{}.build(); visual->set<&Point_Visual::Prop::items>(std::vector{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(); } } }