#include "render_3D/Point_Visual.h" #include "render_3D/detail/Point_Core.h" #include #include #include #include #include #include #include #include #include #include namespace renderive::render_3d { namespace { using Test_Frame_Control = Low_Latency_Strategy; struct Test_Scene final : Scene3D_Context, Scene_Renderer { explicit Test_Scene(const std::shared_ptr& visual) { Scene_State_Strategy::set<&detail::Scene_State::viewport>( Extent{320, 180}); point_id = visual->renderable_id(); auto builder = attach_builder(); builder.attach(renderive_Owner(visual)); } ~Test_Scene() override { shutdown(); } void render_frame() { { auto painter = frame_control.acquire_painter(); ASSERT_TRUE(painter); publish_frame_state(); } auto renderer = frame_control.acquire_renderer(); ASSERT_TRUE(renderer); render(*renderer); wait_for_render(); } Node_Execution_Result render_scene( const Scene_Render_Context& context) override { prepared = context.prepared(point_id); if (!prepared) throw std::logic_error("Point_Visual did not publish prepared data"); EXPECT_EQ(context.frame.scene_state().viewport, (Extent{320, 180})); return Node_Execution_Result::completed(); } Renderable_Id point_id{}; std::shared_ptr prepared; }; static_assert(std::derived_from); static_assert(std::derived_from); TEST(PointState, PublishesStateAndBulkDataIntoFrameLocalPreparedOutput) { auto visual = Point_Visual::Builder{}.build( std::vector{{{1.0F, 2.0F, 3.0F}, {1, 2, 3, 255}, 7.0F}}); ASSERT_TRUE(visual); Point_State configured; configured.visible = false; configured.style.stroke_width_px = 3.0F; visual->set<&Point_State::visible>(configured.visible); visual->set<&Point_State::style>(configured.style); Test_Scene scene(visual); scene.render_frame(); ASSERT_TRUE(scene.prepared); EXPECT_EQ(scene.prepared->state, configured); ASSERT_EQ(scene.prepared->positions.size(), 1U); EXPECT_EQ(scene.prepared->positions.front(), (std::array{1.0F, 2.0F, 3.0F})); EXPECT_EQ(scene.prepared->data_revision, 1U); } TEST(PointState, PublishesImmutableBulkPayloadsAcrossConcurrentUpdates) { auto visual = Point_Visual::Builder{}.build(); ASSERT_TRUE(visual); Test_Scene scene(visual); constexpr int update_count = 200; std::atomic done{}; std::thread writer([&] { for (int value = 1; value <= update_count; ++value) { std::vector points(static_cast(value % 31 + 1)); for (auto& point : points) { point.position.x = static_cast(value); point.diameter_px = static_cast(value % 12 + 1); } visual->update_points(std::move(points)); } done.store(true, std::memory_order_release); }); do { scene.render_frame(); const auto prepared = scene.prepared; ASSERT_TRUE(prepared); if (!prepared->positions.empty()) { const float expected = prepared->positions.front()[0]; for (const auto& point : prepared->positions) EXPECT_EQ(point[0], expected); } } while (!done.load(std::memory_order_acquire)); writer.join(); scene.render_frame(); ASSERT_TRUE(scene.prepared); ASSERT_FALSE(scene.prepared->positions.empty()); EXPECT_EQ(scene.prepared->positions.front()[0], static_cast(update_count)); } TEST(PointState, RejectsInvalidStateAndPayloadAtTheOwningBoundary) { auto visual = Point_Visual::Builder{}.build(); ASSERT_TRUE(visual); Point_State invalid; invalid.style.stroke_width_px = -1.0F; EXPECT_THROW(visual->set<&Point_State::style>(invalid.style), std::invalid_argument); EXPECT_THROW(visual->update_points(std::vector{{{}, {}, 0.0F}}), std::invalid_argument); } TEST(PointRenderPlan, OrdersParallelPreparationBeforeSingleSceneRenderNode) { auto visual = Point_Visual::Builder{}.build( std::vector{{{}, {}, 8.0F}}); Test_Scene scene(visual); scene.render_frame(); const auto plan = scene.render_plan_snapshot(); ASSERT_TRUE(plan); const auto prepare = std::find_if( plan->graph.nodes.begin(), plan->graph.nodes.end(), [&](const Render_Node& node) { return node.owner_id == visual->renderable_id() && node.kind == Render_Node_Kind::prepare; }); const auto render = std::find_if( plan->graph.nodes.begin(), plan->graph.nodes.end(), [](const Render_Node& node) { return node.kind == Render_Node_Kind::render; }); ASSERT_NE(prepare, plan->graph.nodes.end()); ASSERT_NE(render, plan->graph.nodes.end()); EXPECT_NE(std::find(plan->graph.edges.begin(), plan->graph.edges.end(), Render_Edge{prepare->node_id, render->node_id}), plan->graph.edges.end()); } } // namespace } // namespace renderive::render_3d