Files
Renderive/Kernel/tests/renderive/render_graph/Render_Graph_Runtime_Test.cpp
T
2026-08-15 17:44:35 +08:00

196 lines
7.1 KiB
C++

#include <gtest/gtest.h>
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <exception>
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <vector>
#include "renderive/render_graph/External_Operation.hpp"
#include "renderive/render_graph/Render_Plan.hpp"
#include "renderive/render_graph/detail/Render_Graph_Runtime.hpp"
#include "renderive/scheduling/detail/OneTBB_Runtime.hpp"
namespace {
std::shared_ptr<const Render_Plan> two_node_plan() {
Render_Graph_Builder builder;
const auto submit = builder.emplace(
301, 30, "Submit GPU", Render_Node_Kind::render);
const auto publish = builder.emplace(
302, 30, "Publish Frame", Render_Node_Kind::composite);
builder.precede(submit, publish);
Render_Plan_History history;
return history.publish(std::move(builder).finish());
}
std::vector<Node_Execution*> execution_slots(
const Render_Plan& plan, std::vector<Node_Execution>& storage) {
storage.resize(plan.graph.nodes.size());
std::vector<Node_Execution*> result(storage.size());
for (const auto& node : plan.graph.nodes) {
storage[node.execution_index].node_id = node.node_id;
result[node.execution_index] = &storage[node.execution_index];
}
return result;
}
} // namespace
TEST(external_operation_test, completion_before_subscription_is_delivered_once) {
External_Operation_Source source;
const auto operation = source.operation();
EXPECT_TRUE(source.complete());
EXPECT_FALSE(source.complete());
int completion_count{};
operation.on_complete([&](std::exception_ptr error) {
EXPECT_FALSE(error);
++completion_count;
});
EXPECT_EQ(completion_count, 1);
}
TEST(external_operation_test,
callback_exceptions_are_contained_for_both_completion_orders) {
External_Operation_Source completed_first;
const auto completed_operation = completed_first.operation();
ASSERT_TRUE(completed_first.complete());
EXPECT_NO_THROW(completed_operation.on_complete(
[](std::exception_ptr) { throw std::runtime_error("late callback"); }));
External_Operation_Source subscribed_first;
const auto subscribed_operation = subscribed_first.operation();
subscribed_operation.on_complete(
[](std::exception_ptr) { throw std::runtime_error("early callback"); });
EXPECT_TRUE(subscribed_first.complete());
}
TEST(render_graph_runtime_test, reusable_topology_executes_multiple_frames) {
const auto plan = two_node_plan();
renderive::render_graph::detail::Render_Graph_Runtime runtime(*plan);
std::array<std::size_t, 2> execution_count{};
const auto execute_node = [&](std::size_t index, Node_Execution_Metrics*) {
++execution_count.at(index);
return Node_Execution_Result::completed();
};
runtime.execute({}, execute_node);
runtime.execute({}, execute_node);
EXPECT_EQ(execution_count[0], 2U);
EXPECT_EQ(execution_count[1], 2U);
}
TEST(render_graph_runtime_test,
external_successor_stays_blocked_until_operation_completes) {
const auto plan = two_node_plan();
External_Operation_Source source;
std::vector<Node_Execution> execution_storage;
auto slots = execution_slots(*plan, execution_storage);
std::atomic<bool> submit_started{};
std::atomic<bool> publish_executed{};
renderive::render_graph::detail::Render_Graph_Runtime runtime(*plan);
const auto execute_node = [&](std::size_t index, Node_Execution_Metrics*) {
if (index == 0) {
submit_started.store(true, std::memory_order_release);
submit_started.notify_all();
return Node_Execution_Result::external(source.operation());
}
publish_executed.store(true, std::memory_order_release);
return Node_Execution_Result::completed();
};
std::thread execution([&] { runtime.execute(slots, execute_node); });
submit_started.wait(false, std::memory_order_acquire);
EXPECT_FALSE(publish_executed.load(std::memory_order_acquire));
EXPECT_TRUE(source.complete());
execution.join();
EXPECT_TRUE(publish_executed.load(std::memory_order_acquire));
EXPECT_EQ(execution_storage[0].status, Node_Execution_Status::complete);
EXPECT_EQ(execution_storage[1].status, Node_Execution_Status::complete);
EXPECT_GT(execution_storage[0].cpu_end_time_ns, 0U);
EXPECT_GE(execution_storage[0].external_end_time_ns,
execution_storage[0].external_start_time_ns);
}
TEST(render_graph_runtime_test,
failed_external_node_prevents_its_successor_from_running) {
const auto plan = two_node_plan();
External_Operation_Source source;
std::atomic<bool> submit_started{};
std::atomic<bool> publish_executed{};
std::exception_ptr graph_error;
renderive::render_graph::detail::Render_Graph_Runtime runtime(*plan);
const auto execute_node = [&](std::size_t index, Node_Execution_Metrics*) {
if (index == 0) {
submit_started.store(true, std::memory_order_release);
submit_started.notify_all();
return Node_Execution_Result::external(source.operation());
}
publish_executed.store(true, std::memory_order_release);
return Node_Execution_Result::completed();
};
std::thread execution([&] {
try {
runtime.execute({}, execute_node);
} catch (...) {
graph_error = std::current_exception();
}
});
submit_started.wait(false, std::memory_order_acquire);
EXPECT_TRUE(source.fail(
std::make_exception_ptr(std::runtime_error("GPU submission failed"))));
execution.join();
ASSERT_TRUE(graph_error);
EXPECT_THROW(std::rethrow_exception(graph_error), std::runtime_error);
EXPECT_FALSE(publish_executed.load(std::memory_order_acquire));
}
TEST(render_graph_runtime_test,
pending_external_operation_does_not_occupy_scheduler_worker) {
Render_Graph_Builder builder;
builder.emplace(401, 40, "GPU Fence", Render_Node_Kind::render);
Render_Plan_History history;
const auto plan = history.publish(std::move(builder).finish());
External_Operation_Source source;
std::atomic<bool> external_started{};
std::mutex probe_mutex;
std::condition_variable probe_completed;
bool probe_ran{};
renderive::render_graph::detail::Render_Graph_Runtime runtime(*plan);
const auto execute_node = [&](std::size_t, Node_Execution_Metrics*) {
external_started.store(true, std::memory_order_release);
external_started.notify_all();
return Node_Execution_Result::external(source.operation());
};
std::thread execution([&] { runtime.execute({}, execute_node); });
external_started.wait(false, std::memory_order_acquire);
renderive::scheduling::detail::OneTBB_Runtime::instance().enqueue([&] {
{
std::lock_guard lock(probe_mutex);
probe_ran = true;
}
probe_completed.notify_one();
});
{
std::unique_lock lock(probe_mutex);
EXPECT_TRUE(probe_completed.wait_for(
lock, std::chrono::seconds(1), [&] { return probe_ran; }));
}
EXPECT_TRUE(source.complete());
execution.join();
}