内核优化

This commit is contained in:
2026-08-10 13:50:17 +08:00
parent 2abdba699d
commit 302b1dcdfd
46 changed files with 1955 additions and 515 deletions
@@ -1,4 +1,5 @@
#include <gtest/gtest.h>
#include <atomic>
#include <memory>
#include <vector>
#include "renderive/renderable/Renderable.hpp"
@@ -25,14 +26,83 @@ TEST(renderable_task_graph_test, combines_internal_and_external_dependencies) {
auto dependent = std::make_shared<Renderable_Task_Graph_Test_Renderable>(scene, order, 10);
scene.attach_renderable(dependency);
scene.attach_renderable(dependent);
dependency->dependency_node.append_child(dependent->dependency_node);
scene.set_dependency_parent(*dependent, dependency.get());
scene.render();
scene.wait_for_render();
EXPECT_EQ(order, (std::vector<int>{1, 2, 11, 12}));
}
#include <algorithm>
#include <atomic>
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 {
@@ -52,7 +122,7 @@ struct Renderable_Task_Graph_Concurrency_Test_Renderable : Renderable_Base {
std::atomic<int>* active;
std::atomic<int>* maximum;
};
TEST(renderable_task_graph_concurrency_test, DISABLED_runs_independent_internal_tasks_concurrently) {
TEST(renderable_task_graph_concurrency_test, runs_independent_internal_tasks_concurrently) {
Scene2D_Context<> scene;
std::atomic<int> active{};
std::atomic<int> maximum{};
@@ -62,3 +132,27 @@ TEST(renderable_task_graph_concurrency_test, DISABLED_runs_independent_internal_
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);
}
@@ -1,6 +1,9 @@
#include <gtest/gtest.h>
#include <cmath>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <atomic>
#include "renderive/renderable/Renderable.hpp"
#include "renderive/scene/Scene.hpp"
struct Renderable_Base_Test_Renderable : Renderable_Base {
@@ -10,15 +13,15 @@ struct Renderable_Base_Test_Renderable : Renderable_Base {
}
int render_count{};
};
TEST(renderable_base_test, exposes_scene_and_strategy_members_directly) {
TEST(renderable_base_test, exposes_scene_and_strategy_through_accessors) {
Scene2D_Context<> low_latency_scene;
Scene2D_Context<Manual_Refresh_Strategy<Scene2D_Frame_Data>> manual_scene;
Renderable_Base_Test_Renderable low_latency_renderable(low_latency_scene, true);
Renderable_Base_Test_Renderable manual_renderable(manual_scene, true);
EXPECT_EQ(low_latency_renderable.scene, &low_latency_scene);
EXPECT_EQ(low_latency_renderable.scene->frame_control_strategy, &low_latency_scene.frame_control);
EXPECT_DOUBLE_EQ(low_latency_renderable.scene->frame_control_strategy->frequency_hz(), 60.0);
EXPECT_TRUE(std::isnan(manual_renderable.scene->frame_control_strategy->frequency_hz()));
EXPECT_EQ(&low_latency_renderable.scene(), &low_latency_scene);
EXPECT_EQ(&low_latency_renderable.scene().frame_control_strategy(), &low_latency_scene.frame_control);
EXPECT_DOUBLE_EQ(low_latency_renderable.scene().frame_control_strategy().frequency_hz(), 60.0);
EXPECT_TRUE(std::isnan(manual_renderable.scene().frame_control_strategy().frequency_hz()));
}
TEST(renderable_base_test, controls_cache_with_configuration) {
Scene2D_Context<> scene;
@@ -38,3 +41,149 @@ TEST(renderable_base_test, controls_cache_with_configuration) {
EXPECT_EQ(cached->render_count, 2);
EXPECT_EQ(uncached->render_count, 3);
}
TEST(renderable_base_test, changes_configuration_through_scene) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Renderable_Base_Test_Renderable>(scene, false);
scene.attach_renderable(renderable);
scene.render();
scene.wait_for_render();
scene.set_renderable_configuration(*renderable, {.cache_enabled = true});
scene.render();
scene.wait_for_render();
EXPECT_TRUE(renderable->configuration().cache_enabled);
EXPECT_EQ(renderable->render_count, 1);
}
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 {
const int count = render_count.fetch_add(1, std::memory_order_acq_rel) + 1;
if (count != 1) {
return;
}
std::unique_lock lock(mutex);
started = true;
condition.notify_all();
condition.wait(lock, [this] {
return released;
});
}
void wait_started() {
std::unique_lock lock(mutex);
condition.wait(lock, [this] {
return started;
});
}
void release() {
std::lock_guard lock(mutex);
released = true;
condition.notify_all();
}
std::atomic<int> render_count{};
std::mutex mutex;
std::condition_variable condition;
bool started{};
bool released{};
};
TEST(renderable_base_test, preserves_cache_invalidation_that_happens_during_render) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Renderable_Base_Cache_Invalidation_Test_Renderable>(scene);
scene.attach_renderable(renderable);
scene.render();
renderable->wait_started();
renderable->invalidate_cache();
renderable->release();
scene.wait_for_render();
EXPECT_FALSE(renderable->cache_valid());
scene.render();
scene.wait_for_render();
EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 2);
EXPECT_TRUE(renderable->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 {
const int count = build_count.fetch_add(1, std::memory_order_acq_rel) + 1;
if (count == 1) {
std::unique_lock lock(mutex);
build_started = true;
condition.notify_all();
condition.wait(lock, [this] {
return release_build;
});
}
graph.emplace([](const Scene_Render_Context&) {}, "graph_rebuild_test");
}
void wait_build_started() {
std::unique_lock lock(mutex);
condition.wait(lock, [this] {
return build_started;
});
}
void release() {
std::lock_guard lock(mutex);
release_build = true;
condition.notify_all();
}
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) {
Scene2D_Context<> scene;
Renderable_Base_Task_Graph_Rebuild_Test_Renderable renderable(scene);
std::shared_ptr<const Renderable_Task_Graph> first_graph;
std::thread builder([&] {
first_graph = renderable.task_graph();
});
renderable.wait_build_started();
std::thread rebuilder([&] {
renderable.rebuild_task_graph();
});
renderable.release();
builder.join();
rebuilder.join();
const auto second_graph = renderable.task_graph();
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) {
Scene2D_Context<> scene;
Renderable_Base_Task_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();
}
running.store(false, std::memory_order_release);
});
while (running.load(std::memory_order_acquire)) {
const auto graph = renderable.task_graph();
ASSERT_TRUE(graph);
EXPECT_EQ(graph->nodes().size(), 1);
}
rebuilder.join();
const auto graph = renderable.task_graph();
ASSERT_TRUE(graph);
EXPECT_EQ(graph->nodes().size(), 1);
}
TEST(renderable_base_test, configuration_snapshot_is_safe_during_scene_updates) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Renderable_Base_Test_Renderable>(scene, false);
scene.attach_renderable(renderable);
std::atomic<bool> running{true};
std::thread writer([&] {
for (int index = 0; index < 1000; ++index) {
scene.set_renderable_configuration(*renderable, {.cache_enabled = index % 2 != 0});
}
running.store(false, std::memory_order_release);
});
while (running.load(std::memory_order_acquire)) {
const auto configuration = renderable->configuration();
static_cast<void>(configuration.cache_enabled);
}
writer.join();
EXPECT_TRUE(renderable->configuration().cache_enabled);
}
@@ -8,11 +8,9 @@ struct Renderable_Concept_Test : Renderable_Base {
};
static_assert(Renderable<Renderable_Concept_Test>);
static_assert(!std::same_as<Renderable_Base::Layer_Node, Renderable_Base::Dependency_Node>);
TEST(renderable_concept_test, exposes_tree_nodes_scene_and_configuration) {
TEST(renderable_concept_test, exposes_scene_and_configuration_state) {
Scene2D_Context<> scene;
Renderable_Concept_Test renderable(scene);
EXPECT_EQ(renderable.scene, &scene);
EXPECT_EQ(renderable.layer_node.owner, &renderable);
EXPECT_EQ(renderable.dependency_node.owner, &renderable);
EXPECT_FALSE(renderable.configuration.cache_enabled);
EXPECT_EQ(&renderable.scene(), &scene);
EXPECT_FALSE(renderable.configuration().cache_enabled);
}