大重构改完
This commit is contained in:
@@ -1,175 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#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<int>& 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<int>* order;
|
||||
int base;
|
||||
};
|
||||
TEST(renderable_task_graph_test, combines_internal_and_external_dependencies) {
|
||||
Scene2D_Context<> scene;
|
||||
std::vector<int> order;
|
||||
auto dependency = std::make_shared<Renderable_Task_Graph_Test_Renderable>(scene, order, 0);
|
||||
auto dependent = std::make_shared<Renderable_Task_Graph_Test_Renderable>(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<int>{1, 2, 11, 12}));
|
||||
}
|
||||
struct Renderable_Task_Graph_Branch_Test_Renderable : Renderable_Base {
|
||||
Renderable_Task_Graph_Branch_Test_Renderable(Scene_Base& scene, std::atomic<unsigned>& state, std::atomic<bool>& 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<unsigned>* state;
|
||||
std::atomic<bool>* invalid;
|
||||
};
|
||||
TEST(renderable_task_graph_test, preserves_internal_fan_in_dependencies) {
|
||||
Scene2D_Context<> scene;
|
||||
std::atomic<unsigned> state{};
|
||||
std::atomic<bool> invalid{};
|
||||
auto renderable = std::make_shared<Renderable_Task_Graph_Branch_Test_Renderable>(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<unsigned>& 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<unsigned>* state;
|
||||
};
|
||||
struct Renderable_Task_Graph_Child_Test_Renderable : Renderable_Base {
|
||||
Renderable_Task_Graph_Child_Test_Renderable(Scene_Base& scene, std::atomic<unsigned>& state, std::atomic<bool>& 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<unsigned>* state;
|
||||
std::atomic<bool>* invalid;
|
||||
};
|
||||
TEST(renderable_task_graph_test, waits_for_all_parent_terminal_tasks_before_dependent_module) {
|
||||
Scene2D_Context<> scene;
|
||||
std::atomic<unsigned> state{};
|
||||
std::atomic<bool> invalid{};
|
||||
auto parent = std::make_shared<Renderable_Task_Graph_Parent_Test_Renderable>(scene, state);
|
||||
auto child = std::make_shared<Renderable_Task_Graph_Child_Test_Renderable>(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 <chrono>
|
||||
#include <thread>
|
||||
struct Renderable_Task_Graph_Concurrency_Test_Renderable : Renderable_Base {
|
||||
Renderable_Task_Graph_Concurrency_Test_Renderable(Scene_Base& scene, std::atomic<int>& active, std::atomic<int>& 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<int>* active;
|
||||
std::atomic<int>* maximum;
|
||||
};
|
||||
TEST(renderable_task_graph_concurrency_test, runs_independent_internal_tasks_concurrently) {
|
||||
Scene2D_Context<> scene;
|
||||
std::atomic<int> active{};
|
||||
std::atomic<int> maximum{};
|
||||
auto renderable = std::make_shared<Renderable_Task_Graph_Concurrency_Test_Renderable>(scene, active, maximum);
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
while (active.load(std::memory_order_acquire) == 0)
|
||||
std::this_thread::yield();
|
||||
const auto running = scene.task_execution_snapshot();
|
||||
EXPECT_EQ(running.state, Task_Execution_State::Running);
|
||||
EXPECT_EQ(running.graph_node_count, 4u);
|
||||
EXPECT_GT(running.running_count, 0u);
|
||||
EXPECT_LT(running.completed_count, running.graph_node_count);
|
||||
scene.wait_for_render();
|
||||
EXPECT_GE(maximum.load(std::memory_order_acquire), 2);
|
||||
const auto execution = scene.task_execution_snapshot();
|
||||
EXPECT_EQ(execution.state, Task_Execution_State::Completed);
|
||||
EXPECT_EQ(execution.worker_count, Scene_Base::task_executor_worker_count());
|
||||
EXPECT_EQ(execution.graph_node_count, 4u);
|
||||
EXPECT_EQ(execution.completed_count, execution.graph_node_count);
|
||||
EXPECT_EQ(execution.running_count, 0u);
|
||||
EXPECT_EQ(execution.pending_count, 0u);
|
||||
EXPECT_GE(execution.peak_parallelism, 2u);
|
||||
EXPECT_EQ(execution.failed_count, 0u);
|
||||
EXPECT_GT(execution.duration_ns, 0u);
|
||||
}
|
||||
#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);
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Renderable_Base_Test_Renderable : Renderable_Base {
|
||||
Renderable_Base_Test_Renderable(Scene_Base& scene, bool cache_enabled) : Renderable_Base(scene, {.cache_enabled = cache_enabled}) {}
|
||||
void render(const Scene_Render_Context&) override {
|
||||
void prepare(const Scene_Render_Context&) override {
|
||||
++render_count;
|
||||
}
|
||||
int render_count{};
|
||||
@@ -35,7 +35,7 @@ TEST(renderable_base_test, controls_cache_with_configuration) {
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(cached->render_count, 1);
|
||||
EXPECT_EQ(uncached->render_count, 2);
|
||||
cached->invalidate_cache();
|
||||
cached->invalidate_prepare();
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(cached->render_count, 2);
|
||||
@@ -55,7 +55,7 @@ TEST(renderable_base_test, changes_configuration_through_scene) {
|
||||
}
|
||||
struct Renderable_Base_Cache_Invalidation_Test_Renderable : Renderable_Base {
|
||||
explicit Renderable_Base_Cache_Invalidation_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = true}) {}
|
||||
void render(const Scene_Render_Context&) override {
|
||||
void prepare(const Scene_Render_Context&) override {
|
||||
const int count = render_count.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
if (count != 1) {
|
||||
return;
|
||||
@@ -90,18 +90,18 @@ TEST(renderable_base_test, preserves_cache_invalidation_that_happens_during_rend
|
||||
scene.attach_renderable(renderable);
|
||||
scene.render();
|
||||
renderable->wait_started();
|
||||
renderable->invalidate_cache();
|
||||
renderable->invalidate_prepare();
|
||||
renderable->release();
|
||||
scene.wait_for_render();
|
||||
EXPECT_FALSE(renderable->cache_valid());
|
||||
EXPECT_FALSE(renderable->prepare_cache_valid());
|
||||
scene.render();
|
||||
scene.wait_for_render();
|
||||
EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 2);
|
||||
EXPECT_TRUE(renderable->cache_valid());
|
||||
EXPECT_TRUE(renderable->prepare_cache_valid());
|
||||
}
|
||||
struct Renderable_Base_Task_Graph_Rebuild_Test_Renderable : Renderable_Base {
|
||||
explicit Renderable_Base_Task_Graph_Rebuild_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {}
|
||||
void build_task_graph(Renderable_Task_Graph& graph) override {
|
||||
struct Renderable_Base_Render_Graph_Rebuild_Test_Renderable : Renderable_Base {
|
||||
explicit Renderable_Base_Render_Graph_Rebuild_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {}
|
||||
void build_prepare_graph(Renderable_Graph_Builder& graph) override {
|
||||
const int count = build_count.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
if (count == 1) {
|
||||
std::unique_lock lock(mutex);
|
||||
@@ -111,7 +111,8 @@ struct Renderable_Base_Task_Graph_Rebuild_Test_Renderable : Renderable_Base {
|
||||
return release_build;
|
||||
});
|
||||
}
|
||||
graph.emplace([](const Scene_Render_Context&) {}, "graph_rebuild_test");
|
||||
graph.emplace("prepare", "graph_rebuild_test", Render_Node_Kind::prepare,
|
||||
[](const Scene_Render_Context&) {});
|
||||
}
|
||||
void wait_build_started() {
|
||||
std::unique_lock lock(mutex);
|
||||
@@ -124,50 +125,56 @@ struct Renderable_Base_Task_Graph_Rebuild_Test_Renderable : Renderable_Base {
|
||||
release_build = true;
|
||||
condition.notify_all();
|
||||
}
|
||||
std::shared_ptr<const Renderable_Graph> graph_snapshot() {
|
||||
return render_graph();
|
||||
}
|
||||
void request_graph_rebuild() noexcept {
|
||||
rebuild_render_graph();
|
||||
}
|
||||
std::atomic<int> build_count{};
|
||||
std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
bool build_started{};
|
||||
bool release_build{};
|
||||
};
|
||||
TEST(renderable_base_test, preserves_rebuild_request_that_arrives_during_task_graph_build) {
|
||||
TEST(renderable_base_test, preserves_rebuild_request_that_arrives_during_render_graph_build) {
|
||||
Scene2D_Context<> scene;
|
||||
Renderable_Base_Task_Graph_Rebuild_Test_Renderable renderable(scene);
|
||||
std::shared_ptr<const Renderable_Task_Graph> first_graph;
|
||||
Renderable_Base_Render_Graph_Rebuild_Test_Renderable renderable(scene);
|
||||
std::shared_ptr<const Renderable_Graph> first_graph;
|
||||
std::thread builder([&] {
|
||||
first_graph = renderable.task_graph();
|
||||
first_graph = renderable.graph_snapshot();
|
||||
});
|
||||
renderable.wait_build_started();
|
||||
std::thread rebuilder([&] {
|
||||
renderable.rebuild_task_graph();
|
||||
renderable.request_graph_rebuild();
|
||||
});
|
||||
renderable.release();
|
||||
builder.join();
|
||||
rebuilder.join();
|
||||
const auto second_graph = renderable.task_graph();
|
||||
const auto second_graph = renderable.graph_snapshot();
|
||||
EXPECT_EQ(renderable.build_count.load(std::memory_order_acquire), 2);
|
||||
EXPECT_NE(first_graph.get(), second_graph.get());
|
||||
}
|
||||
TEST(renderable_base_test, task_graph_snapshots_remain_valid_during_concurrent_rebuilds) {
|
||||
TEST(renderable_base_test, render_graph_snapshots_remain_valid_during_concurrent_rebuilds) {
|
||||
Scene2D_Context<> scene;
|
||||
Renderable_Base_Task_Graph_Rebuild_Test_Renderable renderable(scene);
|
||||
Renderable_Base_Render_Graph_Rebuild_Test_Renderable renderable(scene);
|
||||
renderable.release();
|
||||
std::atomic<bool> running{true};
|
||||
std::thread rebuilder([&] {
|
||||
for (int index = 0; index < 500; ++index) {
|
||||
renderable.rebuild_task_graph();
|
||||
renderable.request_graph_rebuild();
|
||||
}
|
||||
running.store(false, std::memory_order_release);
|
||||
});
|
||||
while (running.load(std::memory_order_acquire)) {
|
||||
const auto graph = renderable.task_graph();
|
||||
const auto graph = renderable.graph_snapshot();
|
||||
ASSERT_TRUE(graph);
|
||||
EXPECT_EQ(graph->nodes().size(), 1);
|
||||
EXPECT_EQ(graph->graph.nodes.size(), 1);
|
||||
}
|
||||
rebuilder.join();
|
||||
const auto graph = renderable.task_graph();
|
||||
const auto graph = renderable.graph_snapshot();
|
||||
ASSERT_TRUE(graph);
|
||||
EXPECT_EQ(graph->nodes().size(), 1);
|
||||
EXPECT_EQ(graph->graph.nodes.size(), 1);
|
||||
}
|
||||
TEST(renderable_base_test, configuration_snapshot_is_safe_during_scene_updates) {
|
||||
Scene2D_Context<> scene;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
struct Renderable_Concept_Test : Renderable_Base {
|
||||
explicit Renderable_Concept_Test(Scene_Base& scene) : Renderable_Base(scene) {}
|
||||
void render(const Scene_Render_Context&) override {}
|
||||
void prepare(const Scene_Render_Context&) override {}
|
||||
};
|
||||
static_assert(Renderable<Renderable_Concept_Test>);
|
||||
static_assert(!std::same_as<Renderable_Base::Layer_Node, Renderable_Base::Dependency_Node>);
|
||||
|
||||
Reference in New Issue
Block a user