#include #include #include #include #include "renderive/renderable/Renderable.hpp" #include "renderive/scene/Scene.hpp" struct Renderable_Task_Graph_Test_Renderable : Renderable_Base { Renderable_Task_Graph_Test_Renderable(Scene_Base& scene, std::vector& order, int base) : Renderable_Base(scene, {.cache_enabled = false}), order(&order), base(base) {} void build_task_graph(Renderable_Task_Graph& graph) override { auto first = graph.emplace([this](const Scene_Render_Context&) { order->push_back(base + 1); }, "first"); auto second = graph.emplace([this](const Scene_Render_Context&) { order->push_back(base + 2); }, "second"); graph.precede(first, second); } std::vector* order; int base; }; TEST(renderable_task_graph_test, combines_internal_and_external_dependencies) { Scene2D_Context<> scene; std::vector order; auto dependency = std::make_shared(scene, order, 0); auto dependent = std::make_shared(scene, order, 10); scene.attach_renderable(dependency); scene.attach_renderable(dependent); scene.set_dependency_parent(*dependent, dependency.get()); scene.render(); scene.wait_for_render(); EXPECT_EQ(order, (std::vector{1, 2, 11, 12})); } struct Renderable_Task_Graph_Branch_Test_Renderable : Renderable_Base { Renderable_Task_Graph_Branch_Test_Renderable(Scene_Base& scene, std::atomic& state, std::atomic& invalid) : Renderable_Base(scene, {.cache_enabled = false}), state(&state), invalid(&invalid) {} void build_task_graph(Renderable_Task_Graph& graph) override { auto first = graph.emplace([this](const Scene_Render_Context&) { state->fetch_or(1u, std::memory_order_release); }, "first"); auto second = graph.emplace([this](const Scene_Render_Context&) { state->fetch_or(2u, std::memory_order_release); }, "second"); auto join = graph.emplace([this](const Scene_Render_Context&) { if ((state->load(std::memory_order_acquire) & 3u) != 3u) { invalid->store(true, std::memory_order_release); } state->fetch_or(4u, std::memory_order_release); }, "join"); graph.precede(first, join); graph.precede(second, join); } std::atomic* state; std::atomic* invalid; }; TEST(renderable_task_graph_test, preserves_internal_fan_in_dependencies) { Scene2D_Context<> scene; std::atomic state{}; std::atomic invalid{}; auto renderable = std::make_shared(scene, state, invalid); scene.attach_renderable(renderable); scene.render(); scene.wait_for_render(); EXPECT_FALSE(invalid.load(std::memory_order_acquire)); EXPECT_EQ(state.load(std::memory_order_acquire), 7u); } struct Renderable_Task_Graph_Parent_Test_Renderable : Renderable_Base { Renderable_Task_Graph_Parent_Test_Renderable(Scene_Base& scene, std::atomic& state) : Renderable_Base(scene, {.cache_enabled = false}), state(&state) {} void build_task_graph(Renderable_Task_Graph& graph) override { graph.emplace([this](const Scene_Render_Context&) { state->fetch_or(1u, std::memory_order_release); }, "parent_a"); graph.emplace([this](const Scene_Render_Context&) { state->fetch_or(2u, std::memory_order_release); }, "parent_b"); } std::atomic* state; }; struct Renderable_Task_Graph_Child_Test_Renderable : Renderable_Base { Renderable_Task_Graph_Child_Test_Renderable(Scene_Base& scene, std::atomic& state, std::atomic& invalid) : Renderable_Base(scene, {.cache_enabled = false}), state(&state), invalid(&invalid) {} void render(const Scene_Render_Context&) override { if ((state->load(std::memory_order_acquire) & 3u) != 3u) { invalid->store(true, std::memory_order_release); } } std::atomic* state; std::atomic* invalid; }; TEST(renderable_task_graph_test, waits_for_all_parent_terminal_tasks_before_dependent_module) { Scene2D_Context<> scene; std::atomic state{}; std::atomic invalid{}; auto parent = std::make_shared(scene, state); auto child = std::make_shared(scene, state, invalid); scene.attach_renderable(parent); scene.attach_renderable(child); scene.set_dependency_parent(*child, parent.get()); scene.render(); scene.wait_for_render(); EXPECT_FALSE(invalid.load(std::memory_order_acquire)); EXPECT_EQ(state.load(std::memory_order_acquire), 3u); } #ifndef RENDERIVE_TASKFLOW_MOCK #include #include struct Renderable_Task_Graph_Concurrency_Test_Renderable : Renderable_Base { Renderable_Task_Graph_Concurrency_Test_Renderable(Scene_Base& scene, std::atomic& active, std::atomic& maximum) : Renderable_Base(scene, {.cache_enabled = false}), active(&active), maximum(&maximum) {} void build_task_graph(Renderable_Task_Graph& graph) override { auto work = [this](const Scene_Render_Context&) { const int value = active->fetch_add(1, std::memory_order_acq_rel) + 1; int observed = maximum->load(std::memory_order_acquire); while (observed < value && !maximum->compare_exchange_weak(observed, value, std::memory_order_acq_rel)) {} std::this_thread::sleep_for(std::chrono::milliseconds(50)); active->fetch_sub(1, std::memory_order_acq_rel); }; graph.emplace(work, "parallel_a"); graph.emplace(work, "parallel_b"); } std::atomic* active; std::atomic* maximum; }; TEST(renderable_task_graph_concurrency_test, runs_independent_internal_tasks_concurrently) { Scene2D_Context<> scene; std::atomic active{}; std::atomic maximum{}; auto renderable = std::make_shared(scene, active, maximum); scene.attach_renderable(renderable); scene.render(); scene.wait_for_render(); EXPECT_GE(maximum.load(std::memory_order_acquire), 2); } #endif TEST(renderable_task_graph_test, rejects_tasks_from_different_graphs) { Renderable_Task_Graph first; Renderable_Task_Graph second; auto first_task = first.emplace([](const Scene_Render_Context&) {}); auto second_task = second.emplace([](const Scene_Render_Context&) {}); EXPECT_THROW(first.precede(first_task, second_task), std::invalid_argument); } TEST(renderable_task_graph_test, rejects_stale_task_handles_after_clear) { Renderable_Task_Graph graph; auto stale = graph.emplace([](const Scene_Render_Context&) {}); graph.clear(); auto current = graph.emplace([](const Scene_Render_Context&) {}); EXPECT_THROW(graph.precede(stale, current), std::invalid_argument); } TEST(renderable_task_graph_test, rejects_cycles_when_dependency_is_added) { Renderable_Task_Graph graph; auto first = graph.emplace([](const Scene_Render_Context&) {}); auto second = graph.emplace([](const Scene_Render_Context&) {}); auto third = graph.emplace([](const Scene_Render_Context&) {}); graph.precede(first, second); graph.precede(second, third); EXPECT_THROW(graph.precede(third, first), std::invalid_argument); }