262 lines
11 KiB
C++
262 lines
11 KiB
C++
#include <gtest/gtest.h>
|
|
#include <atomic>
|
|
#include <memory>
|
|
#include <thread>
|
|
#include <utility>
|
|
#include "renderive/renderable/Renderable.hpp"
|
|
#include "renderive/renderable/Renderable_Test_Harness.hpp"
|
|
#include "renderive/scene/Scene.hpp"
|
|
#include "Scene_Test_Helpers.hpp"
|
|
static_assert(std::same_as<decltype(std::declval<const Scene2D_Context<>&>().topology_snapshot()), Scene_Base::Topology_Snapshot>);
|
|
struct Scene2D_Context_Test_Renderable : Renderable_Test_Harness {
|
|
explicit Scene2D_Context_Test_Renderable()
|
|
: Renderable_Test_Harness({.cache_enabled = false}) {}
|
|
void prepare_for_test(const Prepare_Render_Context&) override {
|
|
++render_count;
|
|
scene_is_2d = dynamic_cast<Scene_2D_Base*>(&scene_for_test()) != 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 = renderive_Owner<Scene2D_Context_Test_Renderable>::make();
|
|
auto second = renderive_Owner<Scene2D_Context_Test_Renderable>::make();
|
|
attach_initial(scene, 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);
|
|
wait_renderable_edit(scene, [first, second](auto& editor) {
|
|
editor.detach(first);
|
|
editor.attach(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_Test_Harness {
|
|
explicit Scene2D_Final_Cache_Blocking_Renderable()
|
|
: Renderable_Test_Harness({.cache_enabled = false}) {}
|
|
void prepare_for_test(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<bool> started{};
|
|
std::atomic<bool> release{};
|
|
};
|
|
TEST(scene2d_context_test, final_color_cache_access_waits_for_render_completion) {
|
|
Scene2D_Context<> scene;
|
|
auto renderable = renderive_Owner<Scene2D_Final_Cache_Blocking_Renderable>::make();
|
|
attach_initial(scene, 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_Test_Harness {
|
|
Scene2D_Cached_Renderable(std::uint64_t value)
|
|
: Renderable_Test_Harness({.cache_enabled = true}), value(value) {}
|
|
void prepare_for_test(const Prepare_Render_Context&) override {
|
|
++render_count;
|
|
}
|
|
void build_paint_graph_for_test(Renderable_Graph_Builder& graph) override {
|
|
graph.emplace("paint", "Paint",
|
|
[this](const Paint_Render_Context& context) {
|
|
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 = renderive_Owner<Scene2D_Cached_Renderable>::make(7);
|
|
attach_initial(scene, renderable);
|
|
scene.render();
|
|
scene.wait_for_render();
|
|
EXPECT_EQ(renderable->render_count, 1);
|
|
wait_renderable_edit(scene, [renderable](auto& editor) {
|
|
editor.detach(renderable);
|
|
editor.attach(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 = renderive_Owner<Scene2D_Cached_Renderable>::make(1);
|
|
auto second_parent = renderive_Owner<Scene2D_Cached_Renderable>::make(2);
|
|
auto child = renderive_Owner<Scene2D_Cached_Renderable>::make(3);
|
|
with_attach_builder(scene, [&](auto& attach) {
|
|
attach.attach(first_parent);
|
|
attach.attach(second_parent);
|
|
attach.attach(child);
|
|
attach.set_dependency_parent(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);
|
|
wait_renderable_edit(scene, [child, second_parent](auto& editor) {
|
|
editor.set_dependency_parent(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 = renderive_Owner<Scene2D_Cached_Renderable>::make(1);
|
|
auto second_parent = renderive_Owner<Scene2D_Cached_Renderable>::make(2);
|
|
auto child = renderive_Owner<Scene2D_Cached_Renderable>::make(3);
|
|
with_attach_builder(scene, [&](auto& attach) {
|
|
attach.attach(first_parent);
|
|
attach.attach(second_parent);
|
|
attach.attach(child);
|
|
attach.set_dependency_parent(child, first_parent);
|
|
attach.add_dependency_parent(child, second_parent);
|
|
});
|
|
scene.render();
|
|
scene.wait_for_render();
|
|
EXPECT_EQ(child->render_count, 1);
|
|
first_parent->invalidate_prepare_for_test();
|
|
scene.render();
|
|
scene.wait_for_render();
|
|
EXPECT_EQ(child->render_count, 2);
|
|
second_parent->invalidate_prepare_for_test();
|
|
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 = renderive_Owner<Scene2D_Cached_Renderable>::make(9);
|
|
attach_initial(scene, renderable);
|
|
scene.render();
|
|
scene.wait_for_render();
|
|
scene.with_final_color_cache([&](const Recording_Color_Cache&) {
|
|
renderable->set_configuration_for_test({.cache_enabled = false});
|
|
});
|
|
EXPECT_FALSE(renderable->configuration_for_test().cache_enabled);
|
|
}
|
|
TEST(scene2d_context_test, detach_dependency_parent_invalidates_promoted_cached_child) {
|
|
Scene2D_Context<> scene;
|
|
auto grandparent = renderive_Owner<Scene2D_Cached_Renderable>::make(1);
|
|
auto parent = renderive_Owner<Scene2D_Cached_Renderable>::make(2);
|
|
auto child = renderive_Owner<Scene2D_Cached_Renderable>::make(3);
|
|
with_attach_builder(scene, [&](auto& attach) {
|
|
attach.attach(grandparent);
|
|
attach.attach(parent);
|
|
attach.attach(child);
|
|
attach.set_dependency_parent(parent, grandparent);
|
|
attach.set_dependency_parent(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);
|
|
wait_renderable_edit(scene, [grandparent, parent, child](auto& editor) {
|
|
editor.clear_dependency_parent(child);
|
|
editor.clear_dependency_parent(parent);
|
|
editor.set_dependency_parent(child, grandparent);
|
|
editor.detach(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 = renderive_Owner<Scene2D_Context_Test_Renderable>::make();
|
|
EXPECT_THROW(attach_initial(scene, 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(attach_initial(scene, renderable));
|
|
EXPECT_EQ(scene.renderable_count(), 1);
|
|
}
|
|
struct Scene2D_Viewport_Snapshot_Renderable : Renderable_Test_Harness {
|
|
explicit Scene2D_Viewport_Snapshot_Renderable() = default;
|
|
void prepare_for_test(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 = renderive_Owner<Scene2D_Viewport_Snapshot_Renderable>::make();
|
|
attach_initial(scene, 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_for_test();
|
|
scene.render();
|
|
scene.wait_for_render();
|
|
EXPECT_EQ(renderable->viewport, (Frame_Viewport{640, 360}));
|
|
}
|