#include #include #include #include #include #include "renderive/renderable/Renderable.hpp" #include "renderive/scene/Scene.hpp" static_assert(std::same_as&>().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(context.scene) != nullptr; render_thread = std::this_thread::get_id(); } std::atomic 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(scene); auto second = std::make_shared(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 started{}; std::atomic release{}; }; TEST(scene2d_context_test, final_color_cache_access_waits_for_render_completion) { Scene2D_Context<> scene; auto renderable = std::make_shared(scene); scene.attach_renderable(renderable); scene.render(); while (!renderable->started.load(std::memory_order_acquire)) { std::this_thread::yield(); } std::atomic access_started{}; std::atomic 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(*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(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(scene, 1); auto second_parent = std::make_shared(scene, 2); auto child = std::make_shared(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(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(scene, 1); auto parent = std::make_shared(scene, 2); auto child = std::make_shared(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, Scene2D_Attach_Throwing_Cache> scene; auto renderable = std::make_shared(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); }