#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 prepare(const Prepare_Render_Context&) override { ++render_count; scene_is_2d = dynamic_cast(&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 prepare(const Prepare_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 prepare(const Prepare_Render_Context&) override { ++render_count; } void build_paint_graph(Renderable_Graph_Builder& graph) override { graph.emplace("paint", "Paint", [this](const Paint_Render_Context& context) { 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_parent(Scene_Base::Relationship::dependency, child, first_parent); 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_parent(Scene_Base::Relationship::dependency, child, second_parent); 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(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_parent(Scene_Base::Relationship::dependency, child, first_parent); scene.add_parent(Scene_Base::Relationship::dependency, child, second_parent); scene.render(); scene.wait_for_render(); EXPECT_EQ(child->render_count, 1); first_parent->invalidate_prepare(); scene.render(); scene.wait_for_render(); EXPECT_EQ(child->render_count, 2); second_parent->invalidate_prepare(); 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(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_parent(Scene_Base::Relationship::dependency, parent, grandparent); scene.set_parent(Scene_Base::Relationship::dependency, child, parent); 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).wait(), 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).wait()); EXPECT_EQ(scene.renderable_count(), 1); } struct Scene2D_Viewport_Snapshot_Renderable : Renderable_Base { explicit Scene2D_Viewport_Snapshot_Renderable(Scene_Base& scene) : Renderable_Base(scene) {} void prepare(const Prepare_Render_Context& context) override { viewport = context.frame.viewport; } Frame_Viewport viewport; }; TEST(scene2d_context_test, published_scene_viewport_is_frozen_for_render) { Scene2D_Context<> scene; auto renderable = std::make_shared(scene); scene.attach_renderable(renderable); scene.Scene_State_Strategy::set<&Scene2D_State::viewport>(Frame_Viewport{320, 180}); scene.publish_frame_state(); scene.Scene_State_Strategy::set<&Scene2D_State::viewport>(Frame_Viewport{640, 360}); scene.render(); scene.wait_for_render(); EXPECT_EQ(renderable->viewport, (Frame_Viewport{320, 180})); scene.publish_frame_state(); renderable->invalidate_prepare(); scene.render(); scene.wait_for_render(); EXPECT_EQ(renderable->viewport, (Frame_Viewport{640, 360})); }