190 lines
7.3 KiB
C++
190 lines
7.3 KiB
C++
#include <gtest/gtest.h>
|
|
#include <cmath>
|
|
#include <condition_variable>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <atomic>
|
|
#include "renderive/renderable/Renderable.hpp"
|
|
#include "renderive/scene/Scene.hpp"
|
|
struct Renderable_Base_Test_Renderable : Renderable_Base {
|
|
Renderable_Base_Test_Renderable(Scene_Base& scene, bool cache_enabled) : Renderable_Base(scene, {.cache_enabled = cache_enabled}) {}
|
|
void render(const Scene_Render_Context&) override {
|
|
++render_count;
|
|
}
|
|
int render_count{};
|
|
};
|
|
TEST(renderable_base_test, exposes_scene_and_strategy_through_accessors) {
|
|
Scene2D_Context<> low_latency_scene;
|
|
Scene2D_Context<Manual_Refresh_Strategy<Scene2D_Frame_Data>> manual_scene;
|
|
Renderable_Base_Test_Renderable low_latency_renderable(low_latency_scene, true);
|
|
Renderable_Base_Test_Renderable manual_renderable(manual_scene, true);
|
|
EXPECT_EQ(&low_latency_renderable.scene(), &low_latency_scene);
|
|
EXPECT_EQ(&low_latency_renderable.scene().frame_control_strategy(), &low_latency_scene.frame_control);
|
|
EXPECT_DOUBLE_EQ(low_latency_renderable.scene().frame_control_strategy().frequency_hz(), 60.0);
|
|
EXPECT_TRUE(std::isnan(manual_renderable.scene().frame_control_strategy().frequency_hz()));
|
|
}
|
|
TEST(renderable_base_test, controls_cache_with_configuration) {
|
|
Scene2D_Context<> scene;
|
|
auto cached = std::make_shared<Renderable_Base_Test_Renderable>(scene, true);
|
|
auto uncached = std::make_shared<Renderable_Base_Test_Renderable>(scene, false);
|
|
scene.attach_renderable(cached);
|
|
scene.attach_renderable(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_cache();
|
|
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 = std::make_shared<Renderable_Base_Test_Renderable>(scene, false);
|
|
scene.attach_renderable(renderable);
|
|
scene.render();
|
|
scene.wait_for_render();
|
|
scene.set_renderable_configuration(*renderable, {.cache_enabled = true});
|
|
scene.render();
|
|
scene.wait_for_render();
|
|
EXPECT_TRUE(renderable->configuration().cache_enabled);
|
|
EXPECT_EQ(renderable->render_count, 1);
|
|
}
|
|
struct Renderable_Base_Cache_Invalidation_Test_Renderable : Renderable_Base {
|
|
explicit Renderable_Base_Cache_Invalidation_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = true}) {}
|
|
void render(const Scene_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<int> 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 = std::make_shared<Renderable_Base_Cache_Invalidation_Test_Renderable>(scene);
|
|
scene.attach_renderable(renderable);
|
|
scene.render();
|
|
renderable->wait_started();
|
|
renderable->invalidate_cache();
|
|
renderable->release();
|
|
scene.wait_for_render();
|
|
EXPECT_FALSE(renderable->cache_valid());
|
|
scene.render();
|
|
scene.wait_for_render();
|
|
EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 2);
|
|
EXPECT_TRUE(renderable->cache_valid());
|
|
}
|
|
struct Renderable_Base_Task_Graph_Rebuild_Test_Renderable : Renderable_Base {
|
|
explicit Renderable_Base_Task_Graph_Rebuild_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {}
|
|
void build_task_graph(Renderable_Task_Graph& 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([](const Scene_Render_Context&) {}, "graph_rebuild_test");
|
|
}
|
|
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::atomic<int> 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_task_graph_build) {
|
|
Scene2D_Context<> scene;
|
|
Renderable_Base_Task_Graph_Rebuild_Test_Renderable renderable(scene);
|
|
std::shared_ptr<const Renderable_Task_Graph> first_graph;
|
|
std::thread builder([&] {
|
|
first_graph = renderable.task_graph();
|
|
});
|
|
renderable.wait_build_started();
|
|
std::thread rebuilder([&] {
|
|
renderable.rebuild_task_graph();
|
|
});
|
|
renderable.release();
|
|
builder.join();
|
|
rebuilder.join();
|
|
const auto second_graph = renderable.task_graph();
|
|
EXPECT_EQ(renderable.build_count.load(std::memory_order_acquire), 2);
|
|
EXPECT_NE(first_graph.get(), second_graph.get());
|
|
}
|
|
TEST(renderable_base_test, task_graph_snapshots_remain_valid_during_concurrent_rebuilds) {
|
|
Scene2D_Context<> scene;
|
|
Renderable_Base_Task_Graph_Rebuild_Test_Renderable renderable(scene);
|
|
renderable.release();
|
|
std::atomic<bool> running{true};
|
|
std::thread rebuilder([&] {
|
|
for (int index = 0; index < 500; ++index) {
|
|
renderable.rebuild_task_graph();
|
|
}
|
|
running.store(false, std::memory_order_release);
|
|
});
|
|
while (running.load(std::memory_order_acquire)) {
|
|
const auto graph = renderable.task_graph();
|
|
ASSERT_TRUE(graph);
|
|
EXPECT_EQ(graph->nodes().size(), 1);
|
|
}
|
|
rebuilder.join();
|
|
const auto graph = renderable.task_graph();
|
|
ASSERT_TRUE(graph);
|
|
EXPECT_EQ(graph->nodes().size(), 1);
|
|
}
|
|
TEST(renderable_base_test, configuration_snapshot_is_safe_during_scene_updates) {
|
|
Scene2D_Context<> scene;
|
|
auto renderable = std::make_shared<Renderable_Base_Test_Renderable>(scene, false);
|
|
scene.attach_renderable(renderable);
|
|
std::atomic<bool> running{true};
|
|
std::thread writer([&] {
|
|
for (int index = 0; index < 1000; ++index) {
|
|
scene.set_renderable_configuration(*renderable, {.cache_enabled = index % 2 != 0});
|
|
}
|
|
running.store(false, std::memory_order_release);
|
|
});
|
|
while (running.load(std::memory_order_acquire)) {
|
|
const auto configuration = renderable->configuration();
|
|
static_cast<void>(configuration.cache_enabled);
|
|
}
|
|
writer.join();
|
|
EXPECT_TRUE(renderable->configuration().cache_enabled);
|
|
}
|