内核优化

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
@@ -2,8 +2,10 @@
#include <atomic>
#include <memory>
#include <thread>
#include <utility>
#include "renderive/renderable/Renderable.hpp"
#include "renderive/scene/Scene.hpp"
static_assert(std::same_as<decltype(std::declval<const Scene2D_Context<>&>().topology_snapshot()), Scene_Base::Topology_Snapshot>);
struct Scene2D_Context_Test_Renderable : Renderable_Base {
explicit Scene2D_Context_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
void render(const Scene_Render_Context& context) override {
@@ -32,5 +34,98 @@ TEST(scene2d_context_test, swaps_renderable_cache_and_renders_in_background) {
scene.wait_for_render();
EXPECT_EQ(first->render_count.load(), 1);
EXPECT_EQ(second->render_count.load(), 1);
EXPECT_EQ(scene.frame_control_strategy->frame_control_state().publish_revision, 2);
EXPECT_EQ(scene.frame_control_strategy().frame_control_state().publish_revision, 2);
}
struct Scene2D_Final_Cache_Blocking_Renderable : Renderable_Base {
explicit Scene2D_Final_Cache_Blocking_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
void render(const Scene_Render_Context&) override {
while (!release.load(std::memory_order_acquire)) {
started.store(true, std::memory_order_release);
std::this_thread::yield();
}
}
std::atomic<bool> started{};
std::atomic<bool> release{};
};
TEST(scene2d_context_test, final_color_cache_access_waits_for_render_completion) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene2D_Final_Cache_Blocking_Renderable>(scene);
scene.attach_renderable(renderable);
scene.render();
while (!renderable->started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
std::atomic<bool> access_started{};
std::atomic<bool> cache_accessed{};
std::thread reader([&] {
access_started.store(true, std::memory_order_release);
scene.with_final_color_cache([&](const Recording_Color_Cache&) {
cache_accessed.store(true, std::memory_order_release);
});
});
while (!access_started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
EXPECT_FALSE(cache_accessed.load(std::memory_order_acquire));
renderable->release.store(true, std::memory_order_release);
reader.join();
EXPECT_TRUE(cache_accessed.load(std::memory_order_acquire));
}
struct Scene2D_Cached_Renderable : Renderable_Base {
Scene2D_Cached_Renderable(Scene_Base& scene, std::uint64_t value) : Renderable_Base(scene, {.cache_enabled = true}), value(value) {}
void render(const Scene_Render_Context& context) override {
++render_count;
dynamic_cast<Recording_Color_Cache&>(*context.color_cache).append(value);
}
std::uint64_t value;
int render_count{};
};
TEST(scene2d_context_test, reattached_cached_renderable_rebuilds_removed_color_cache) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene2D_Cached_Renderable>(scene, 7);
scene.attach_renderable(renderable);
scene.render();
scene.wait_for_render();
EXPECT_EQ(renderable->render_count, 1);
scene.detach_renderable(*renderable);
scene.attach_renderable(renderable);
scene.render();
scene.wait_for_render();
EXPECT_EQ(renderable->render_count, 2);
scene.with_final_color_cache([](const Recording_Color_Cache& cache) {
EXPECT_EQ(cache.values.size(), 1);
EXPECT_EQ(cache.values.at(0), 7);
});
}
TEST(scene2d_context_test, dependency_reparent_invalidates_cached_child) {
Scene2D_Context<> scene;
auto first_parent = std::make_shared<Scene2D_Cached_Renderable>(scene, 1);
auto second_parent = std::make_shared<Scene2D_Cached_Renderable>(scene, 2);
auto child = std::make_shared<Scene2D_Cached_Renderable>(scene, 3);
scene.attach_renderable(first_parent);
scene.attach_renderable(second_parent);
scene.attach_renderable(child);
scene.set_dependency_parent(*child, first_parent.get());
scene.render();
scene.wait_for_render();
EXPECT_EQ(first_parent->render_count, 1);
EXPECT_EQ(second_parent->render_count, 1);
EXPECT_EQ(child->render_count, 1);
scene.set_dependency_parent(*child, second_parent.get());
scene.render();
scene.wait_for_render();
EXPECT_EQ(first_parent->render_count, 1);
EXPECT_EQ(second_parent->render_count, 1);
EXPECT_EQ(child->render_count, 2);
}
TEST(scene2d_context_test, final_color_cache_callback_can_reenter_scene_control_api) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene2D_Cached_Renderable>(scene, 9);
scene.attach_renderable(renderable);
scene.render();
scene.wait_for_render();
scene.with_final_color_cache([&](const Recording_Color_Cache&) {
scene.set_renderable_configuration(*renderable, {.cache_enabled = false});
});
EXPECT_FALSE(renderable->configuration().cache_enabled);
}
@@ -24,12 +24,16 @@ TEST(scene2d_render_order_test, separates_dependency_order_from_display_order) {
auto axis = std::make_shared<Scene2D_Render_Order_Renderable>(scene, state, 1);
scene.attach_renderable(spectrum);
scene.attach_renderable(axis);
axis->dependency_node.append_child(spectrum->dependency_node);
spectrum->layer_node.append_child(axis->layer_node);
scene.set_dependency_parent(*spectrum, axis.get());
scene.set_display_parent(*axis, spectrum.get());
scene.render();
scene.wait_for_render();
EXPECT_EQ(state.render_order, (std::vector<std::uint64_t>{1, 2}));
EXPECT_EQ(scene.final_color_cache.values.size(), 2);
EXPECT_EQ(scene.final_color_cache.values.at(0), 2);
EXPECT_EQ(scene.final_color_cache.values.at(1), 1);
std::vector<std::uint64_t> final_values;
scene.with_final_color_cache([&final_values](const Recording_Color_Cache& cache) {
final_values.assign(cache.values.begin(), cache.values.end());
});
EXPECT_EQ(final_values.size(), 2);
EXPECT_EQ(final_values.at(0), 2);
EXPECT_EQ(final_values.at(1), 1);
}
@@ -21,3 +21,32 @@ TEST(scene3d_context_test, uses_scene_3d_base_contract) {
EXPECT_EQ(renderable->render_count.load(), 1);
EXPECT_TRUE(renderable->scene_is_3d);
}
struct Scene3D_Dependency_Cache_Test_Renderable : Renderable_Base {
explicit Scene3D_Dependency_Cache_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = true}) {}
void render(const Scene_Render_Context&) override {
++render_count;
}
int render_count{};
};
TEST(scene3d_context_test, propagates_cache_invalidation_through_unsorted_dependency_chain) {
Scene3D_Context<> scene;
auto leaf = std::make_shared<Scene3D_Dependency_Cache_Test_Renderable>(scene);
auto middle = std::make_shared<Scene3D_Dependency_Cache_Test_Renderable>(scene);
auto root = std::make_shared<Scene3D_Dependency_Cache_Test_Renderable>(scene);
scene.attach_renderable(leaf);
scene.attach_renderable(middle);
scene.attach_renderable(root);
scene.set_dependency_parent(*leaf, middle.get());
scene.set_dependency_parent(*middle, root.get());
scene.render();
scene.wait_for_render();
EXPECT_EQ(root->render_count, 1);
EXPECT_EQ(middle->render_count, 1);
EXPECT_EQ(leaf->render_count, 1);
root->invalidate_cache();
scene.render();
scene.wait_for_render();
EXPECT_EQ(root->render_count, 2);
EXPECT_EQ(middle->render_count, 2);
EXPECT_EQ(leaf->render_count, 2);
}
@@ -96,3 +96,17 @@ TEST(scene_memory_resource_test, supports_independent_resource_aware_components)
EXPECT_EQ(unique_product->value, 8);
EXPECT_GT(memory_resource.allocation_count(), 0);
}
TEST(scene_memory_resource_test, keeps_independent_renderable_memory_domain_alive_until_renderable_release) {
Scene_Memory_Resource_Test_Resource upstream;
std::shared_ptr<Scene_Memory_Resource_Test_Renderable> renderable;
{
Scene2D_Context<> scene(upstream);
renderable = std::make_shared<Scene_Memory_Resource_Test_Renderable>(scene);
renderable->task_graph();
}
auto& memory_resource = renderable->memory_resource();
void* storage = memory_resource.allocate(32, alignof(std::max_align_t));
memory_resource.deallocate(storage, 32, alignof(std::max_align_t));
renderable.reset();
EXPECT_GT(upstream.deallocation_count(), 0);
}
@@ -1,5 +1,7 @@
#include <gtest/gtest.h>
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
#include "renderive/scene/Scene.hpp"
@@ -34,3 +36,32 @@ TEST(scene_state_observer_test, scene_uses_triple_state_and_observes_lifecycle)
EXPECT_EQ(scene.Scene_State_Strategy::render_use_state().value, 9);
EXPECT_EQ(recorder.data->events, (std::vector<Scene_Base::Observation_Event>{Scene_Base::Observation_Event::render_submitted, Scene_Base::Observation_Event::render_started, Scene_Base::Observation_Event::render_completed}));
}
struct Scene_Reentrant_Observer_Data {
std::function<void(Scene_Base::Observation_Event)> callback;
};
struct Scene_Reentrant_Observer {
static constexpr bool enabled = true;
std::shared_ptr<Scene_Reentrant_Observer_Data> data{std::make_shared<Scene_Reentrant_Observer_Data>()};
void observe(const Scene_Base::Observation& observation) noexcept {
if (data->callback) {
data->callback(observation.event);
}
}
};
using Scene_Reentrant_Observer_State = Observer_State<Scene_Reentrant_Observer, Scene_State_Observer_Test_Time_Source>;
TEST(scene_state_observer_test, render_worker_observer_can_wait_for_current_render_without_deadlock) {
Scene_Reentrant_Observer recorder;
auto data = recorder.data;
using Scene = Scene2D_Context<Low_Latency_Strategy<Scene2D_Frame_Data>, Recording_Color_Cache, Scene_State_Observer_Test_State, Scene_State_Observer_Test_State_Observer, Scene_Reentrant_Observer_State>;
Scene scene(With_Observer(Scene_State_Observer_Test_State_Observer{}), With_Observer(Scene_Reentrant_Observer_State(recorder, Scene_State_Observer_Test_Time_Source{})));
std::atomic<bool> reentered{};
data->callback = [&](Scene_Base::Observation_Event event) {
if (event == Scene_Base::Observation_Event::render_started) {
scene.wait_for_render();
reentered.store(true, std::memory_order_release);
}
};
scene.render();
scene.wait_for_render();
EXPECT_TRUE(reentered.load(std::memory_order_acquire));
}
@@ -1,5 +1,10 @@
#include <gtest/gtest.h>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <thread>
#include "renderive/renderable/Renderable.hpp"
#include "renderive/scene/Scene.hpp"
struct Scene_Base_Test_Renderable : Renderable_Base {
@@ -21,3 +26,192 @@ TEST(scene_base_test, publishes_renderable_collection_before_background_render)
EXPECT_EQ(renderable->render_sequence, 1);
EXPECT_EQ(scene.renderable_count(), 1);
}
struct Scene_Base_Throwing_Renderable : Renderable_Base {
explicit Scene_Base_Throwing_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
void render(const Scene_Render_Context&) override {
throw std::runtime_error("render failed");
}
};
TEST(scene_base_test, wait_for_render_propagates_background_render_failure) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
scene.attach_renderable(renderable);
scene.render();
EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
}
TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_failure) {
{
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
scene.attach_renderable(renderable);
scene.render();
}
EXPECT_TRUE(true);
}
struct Scene_Base_Blocking_Renderable : Renderable_Base {
explicit Scene_Base_Blocking_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
void render(const Scene_Render_Context&) override {
std::unique_lock lock(mutex);
rendering = true;
condition.notify_all();
condition.wait(lock, [this] {
return released;
});
}
void wait_rendering() {
std::unique_lock lock(mutex);
condition.wait(lock, [this] {
return rendering;
});
}
void release() {
std::lock_guard lock(mutex);
released = true;
condition.notify_all();
}
std::mutex mutex;
std::condition_variable condition;
bool rendering{};
bool released{};
};
TEST(scene_base_test, scene_mutation_waits_until_background_render_is_idle) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene_Base_Blocking_Renderable>(scene);
scene.attach_renderable(renderable);
scene.render();
renderable->wait_rendering();
std::atomic<bool> mutation_finished{};
std::thread mutation([&] {
scene.set_renderable_configuration(*renderable, {.cache_enabled = true});
mutation_finished.store(true, std::memory_order_release);
});
std::this_thread::yield();
EXPECT_FALSE(mutation_finished.load(std::memory_order_acquire));
renderable->release();
mutation.join();
EXPECT_TRUE(mutation_finished.load(std::memory_order_acquire));
EXPECT_TRUE(renderable->configuration().cache_enabled);
}
TEST(scene_base_test, concurrent_render_and_topology_updates_remain_serialized) {
Scene2D_Context<> scene;
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(parent);
scene.attach_renderable(child);
std::thread renderer([&] {
for (int index = 0; index < 100; ++index) {
scene.render();
scene.wait_for_render();
}
});
std::thread mutator([&] {
for (int index = 0; index < 100; ++index) {
Renderable_Base* value = index % 2 == 0 ? parent.get() : nullptr;
scene.set_dependency_parent(*child, value);
scene.set_display_parent(*child, value);
scene.set_renderable_configuration(*child, {.cache_enabled = index % 2 == 0});
}
});
renderer.join();
mutator.join();
EXPECT_EQ(scene.renderable_count(), 2);
}
TEST(scene_base_test, detach_releases_renderable_from_both_scene_buffers) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable;
scene.attach_renderable(renderable);
scene.render();
scene.wait_for_render();
scene.detach_renderable(*renderable);
renderable.reset();
EXPECT_TRUE(weak.expired());
EXPECT_EQ(scene.renderable_count(), 0);
}
TEST(scene_base_test, attach_rejects_null_renderable) {
Scene2D_Context<> scene;
EXPECT_THROW(scene.attach_renderable({}), std::invalid_argument);
}
TEST(scene_base_test, topology_rejects_unattached_renderables) {
Scene2D_Context<> scene;
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(child);
EXPECT_THROW(scene.set_dependency_parent(*child, parent.get()), std::invalid_argument);
EXPECT_THROW(scene.set_display_parent(*child, parent.get()), std::invalid_argument);
scene.attach_renderable(parent);
auto detached_child = std::make_shared<Scene_Base_Test_Renderable>(scene);
EXPECT_THROW(scene.set_dependency_parent(*detached_child, parent.get()), std::invalid_argument);
}
TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
Scene2D_Context<> scene;
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(parent);
scene.attach_renderable(child);
scene.set_dependency_parent(*child, parent.get());
const auto revision = child->cache_revision();
EXPECT_NO_THROW(scene.set_dependency_parent(*child, parent.get()));
EXPECT_EQ(child->cache_revision(), revision);
const auto topology = scene.topology_snapshot();
EXPECT_EQ(topology.dependency.size(), 2);
bool found{};
for (const auto& relation : topology.dependency) {
if (relation.child.get() == child.get()) {
EXPECT_EQ(relation.parent.get(), parent.get());
found = true;
}
}
EXPECT_TRUE(found);
}
TEST(scene_base_test, topology_snapshot_retains_renderable_lifetime) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable;
scene.attach_renderable(renderable);
const auto snapshot = scene.topology_snapshot();
scene.detach_renderable(*renderable);
renderable.reset();
EXPECT_FALSE(weak.expired());
EXPECT_EQ(snapshot.renderables.size(), 1);
EXPECT_EQ(snapshot.renderables.front().get(), weak.lock().get());
}
TEST(scene_base_test, topology_snapshot_is_safe_during_topology_updates) {
Scene2D_Context<> scene;
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(parent);
scene.attach_renderable(child);
std::thread writer([&] {
for (int index = 0; index < 500; ++index) {
scene.set_dependency_parent(*child, index % 2 == 0 ? parent.get() : nullptr);
scene.set_display_parent(*child, index % 2 == 0 ? parent.get() : nullptr);
}
});
for (int index = 0; index < 500; ++index) {
const auto snapshot = scene.topology_snapshot();
EXPECT_EQ(snapshot.renderables.size(), 2);
EXPECT_EQ(snapshot.display.size(), 2);
EXPECT_EQ(snapshot.dependency.size(), 2);
}
writer.join();
}
TEST(scene_base_test, all_waiters_receive_the_same_render_failure) {
Scene2D_Context<> scene;
auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
scene.attach_renderable(renderable);
scene.render();
std::atomic<int> failures{};
auto waiter = [&] {
try {
scene.wait_for_render();
} catch (const std::runtime_error&) {
failures.fetch_add(1, std::memory_order_relaxed);
}
};
std::thread first(waiter);
std::thread second(waiter);
first.join();
second.join();
EXPECT_EQ(failures.load(), 2);
}