Files
Renderive/Kernel/tests/renderive/scene/Scene2D_Context_Test.cpp
T
2026-08-11 23:17:03 +08:00

212 lines
9.0 KiB
C++

#include <gtest/gtest.h>
#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 {
++render_count;
scene_is_2d = dynamic_cast<Scene_2D_Base*>(context.scene) != nullptr;
render_thread = std::this_thread::get_id();
}
std::atomic<int> render_count{};
bool scene_is_2d{};
std::thread::id render_thread;
};
TEST(scene2d_context_test, swaps_renderable_cache_and_renders_in_background) {
Scene2D_Context<> scene;
const auto caller_thread = std::this_thread::get_id();
auto first = std::make_shared<Scene2D_Context_Test_Renderable>(scene);
auto second = std::make_shared<Scene2D_Context_Test_Renderable>(scene);
scene.attach_renderable(first);
scene.render();
scene.wait_for_render();
EXPECT_EQ(first->render_count.load(), 1);
EXPECT_TRUE(first->scene_is_2d);
EXPECT_NE(first->render_thread, caller_thread);
scene.detach_renderable(*first);
scene.attach_renderable(second);
scene.render();
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);
}
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, multiple_dependency_parents_invalidate_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.add_dependency_parent(*child, *second_parent);
scene.render();
scene.wait_for_render();
EXPECT_EQ(child->render_count, 1);
first_parent->invalidate_cache();
scene.render();
scene.wait_for_render();
EXPECT_EQ(child->render_count, 2);
second_parent->invalidate_cache();
scene.render();
scene.wait_for_render();
EXPECT_EQ(child->render_count, 3);
int dependency_edges{};
for (const auto& relation : scene.topology_snapshot().dependency) {
if (relation.child.get() == child.get() && relation.parent) {
++dependency_edges;
}
}
EXPECT_EQ(dependency_edges, 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);
}
TEST(scene2d_context_test, detach_dependency_parent_invalidates_promoted_cached_child) {
Scene2D_Context<> scene;
auto grandparent = std::make_shared<Scene2D_Cached_Renderable>(scene, 1);
auto parent = std::make_shared<Scene2D_Cached_Renderable>(scene, 2);
auto child = std::make_shared<Scene2D_Cached_Renderable>(scene, 3);
scene.attach_renderable(grandparent);
scene.attach_renderable(parent);
scene.attach_renderable(child);
scene.set_dependency_parent(*parent, grandparent.get());
scene.set_dependency_parent(*child, parent.get());
scene.render();
scene.wait_for_render();
EXPECT_EQ(grandparent->render_count, 1);
EXPECT_EQ(parent->render_count, 1);
EXPECT_EQ(child->render_count, 1);
scene.detach_renderable(*parent);
scene.render();
scene.wait_for_render();
EXPECT_EQ(grandparent->render_count, 1);
EXPECT_EQ(parent->render_count, 1);
EXPECT_EQ(child->render_count, 2);
const auto topology = scene.topology_snapshot();
for (const auto& relation : topology.dependency) {
if (relation.child.get() == child.get()) {
EXPECT_EQ(relation.parent.get(), grandparent.get());
}
}
}
struct Scene2D_Attach_Throwing_Cache : Color_Cache {
explicit Scene2D_Attach_Throwing_Cache(std::pmr::memory_resource&) {
if (++construction_count == throw_on_construction) {
throw std::runtime_error("cache construction failed");
}
}
void clear() override {}
void composite(const Color_Cache&) override {}
inline static int construction_count{};
inline static int throw_on_construction{};
};
TEST(scene2d_context_test, failed_attach_leaves_renderable_fully_detached) {
Scene2D_Attach_Throwing_Cache::construction_count = 0;
Scene2D_Attach_Throwing_Cache::throw_on_construction = 2;
Scene2D_Context<Low_Latency_Strategy<Scene2D_Frame_Data>, Scene2D_Attach_Throwing_Cache> scene;
auto renderable = std::make_shared<Scene2D_Context_Test_Renderable>(scene);
EXPECT_THROW(scene.attach_renderable(renderable), std::runtime_error);
EXPECT_EQ(scene.renderable_count(), 0);
EXPECT_TRUE(scene.topology_snapshot().renderables.empty());
Scene2D_Attach_Throwing_Cache::throw_on_construction = 0;
EXPECT_NO_THROW(scene.attach_renderable(renderable));
EXPECT_EQ(scene.renderable_count(), 1);
}