#include "render_2D/scene/Scene_Context.hpp" #include #include #include #include #include #include #include "renderive/renderable/Renderable.hpp" #include "renderive/renderable/Renderable_Test_Harness.hpp" #include "renderive/scene/Scene.hpp" struct Renderable_Base_Test_Renderable : Renderable_Test_Harness { Renderable_Base_Test_Renderable(bool cache_enabled) : Renderable_Test_Harness({.cache_enabled = cache_enabled}) {} void prepare_for_test(const Prepare_Render_Context&) override { ++render_count; } int render_count{}; }; TEST(renderable_base_test, implementation_binds_scene_and_strategy) { Scene2D_Context<> low_latency_scene; Scene2D_Context> manual_scene; auto low_latency_renderable = renderive_Owner::make(true); auto manual_renderable = renderive_Owner::make(true); { auto attach = low_latency_scene.attach_builder(); attach.attach(low_latency_renderable); } { auto attach = manual_scene.attach_builder(); attach.attach(manual_renderable); } EXPECT_EQ(&low_latency_renderable->scene_for_test(), &low_latency_scene); EXPECT_EQ(&low_latency_renderable->scene_for_test().frame_control_strategy(), &low_latency_scene.frame_control); EXPECT_DOUBLE_EQ(low_latency_renderable->scene_for_test() .frame_control_strategy() .frequency_hz(), 60.0); EXPECT_TRUE(std::isnan(manual_renderable->scene_for_test() .frame_control_strategy() .frequency_hz())); } TEST(renderable_base_test, controls_cache_with_configuration) { Scene2D_Context<> scene; auto cached = renderive_Owner::make(true); auto uncached = renderive_Owner::make(false); { auto attach = scene.attach_builder(); attach.attach(cached); attach.attach(uncached); } scene.render(); scene.wait_for_render(); scene.render(); scene.wait_for_render(); EXPECT_EQ(cached->render_count, 1); EXPECT_EQ(uncached->render_count, 2); cached->invalidate_prepare_for_test(); scene.render(); scene.wait_for_render(); 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 = renderive_Owner::make(false); { auto attach = scene.attach_builder(); attach.attach(renderable); } scene.render(); scene.wait_for_render(); renderable->set_configuration_for_test({.cache_enabled = true}); scene.render(); scene.wait_for_render(); EXPECT_TRUE(renderable->configuration_for_test().cache_enabled); EXPECT_EQ(renderable->render_count, 2); } struct Renderable_Base_Cache_Invalidation_Test_Renderable : Renderable_Test_Harness { explicit Renderable_Base_Cache_Invalidation_Test_Renderable() : Renderable_Test_Harness({.cache_enabled = true}) {} void prepare_for_test(const Prepare_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 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 = renderive_Owner::make(); { auto attach = scene.attach_builder(); attach.attach(renderable); } scene.render(); renderable->wait_started(); renderable->invalidate_prepare_for_test(); renderable->release(); scene.wait_for_render(); EXPECT_FALSE(renderable->prepare_cache_valid_for_test()); scene.render(); scene.wait_for_render(); EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 2); EXPECT_TRUE(renderable->prepare_cache_valid_for_test()); } struct Renderable_Base_Render_Graph_Rebuild_Test_Renderable : Renderable_Test_Harness { explicit Renderable_Base_Render_Graph_Rebuild_Test_Renderable() = default; void build_prepare_graph_for_test(Renderable_Graph_Builder& 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("prepare", "graph_rebuild_test", [](const Prepare_Render_Context&) {}); } 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::shared_ptr graph_snapshot() { return render_graph_for_test(); } void request_graph_rebuild() { rebuild_render_graph_for_test(); } std::atomic 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_render_graph_build) { Renderable_Base_Render_Graph_Rebuild_Test_Renderable renderable; std::shared_ptr first_graph; std::thread builder([&] { first_graph = renderable.graph_snapshot(); }); renderable.wait_build_started(); std::thread rebuilder([&] { renderable.request_graph_rebuild(); }); renderable.release(); builder.join(); rebuilder.join(); const auto second_graph = renderable.graph_snapshot(); EXPECT_EQ(renderable.build_count.load(std::memory_order_acquire), 2); EXPECT_NE(first_graph.get(), second_graph.get()); } TEST(renderable_base_test, render_graph_snapshots_remain_valid_during_concurrent_rebuilds) { Renderable_Base_Render_Graph_Rebuild_Test_Renderable renderable; renderable.release(); std::atomic running{true}; std::thread rebuilder([&] { for (int index = 0; index < 500; ++index) { renderable.request_graph_rebuild(); } running.store(false, std::memory_order_release); }); while (running.load(std::memory_order_acquire)) { const auto graph = renderable.graph_snapshot(); ASSERT_TRUE(graph); EXPECT_EQ(graph->graph.nodes.size(), 1); } rebuilder.join(); const auto graph = renderable.graph_snapshot(); ASSERT_TRUE(graph); EXPECT_EQ(graph->graph.nodes.size(), 1); } TEST(renderable_base_test, configuration_snapshot_is_safe_during_scene_updates) { Scene2D_Context<> scene; auto renderable = renderive_Owner::make(false); { auto attach = scene.attach_builder(); attach.attach(renderable); } std::atomic running{true}; std::thread writer([&] { for (int index = 0; index < 1000; ++index) { renderable->set_configuration_for_test( {.cache_enabled = index % 2 != 0}); } running.store(false, std::memory_order_release); }); while (running.load(std::memory_order_acquire)) { const auto configuration = renderable->configuration_for_test(); static_cast(configuration.cache_enabled); } writer.join(); EXPECT_TRUE(renderable->configuration_for_test().cache_enabled); }