Files
Renderive/Kernel/src/renderive/render_graph/Render_Plan.cpp
T
2026-08-15 19:58:39 +08:00

170 lines
6.5 KiB
C++

#include "Render_Plan.hpp"
#include <algorithm>
#include <limits>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
namespace {
bool edge_less(const Render_Edge& left, const Render_Edge& right) {
return left.from < right.from || (left.from == right.from && left.to < right.to);
}
void normalize(Render_Graph& graph) {
std::sort(graph.nodes.begin(), graph.nodes.end(), [](const Render_Node& left,
const Render_Node& right) {
return left.node_id < right.node_id;
});
for (std::size_t index = 0; index < graph.nodes.size(); ++index)
graph.nodes[index].execution_index = index;
std::sort(graph.edges.begin(), graph.edges.end(), edge_less);
graph.edges.erase(std::unique(graph.edges.begin(), graph.edges.end()), graph.edges.end());
}
bool same_topology(const Render_Graph& left, const Render_Graph& right) {
if (left.nodes.size() != right.nodes.size() || left.edges != right.edges)
return false;
for (std::size_t index = 0; index < left.nodes.size(); ++index) {
const auto& a = left.nodes[index];
const auto& b = right.nodes[index];
if (a.node_id != b.node_id || a.owner_id != b.owner_id || a.name != b.name ||
a.kind != b.kind)
return false;
}
return true;
}
}
Render_Graph_Builder::Task Render_Graph_Builder::emplace(
Render_Node_Id node_id, std::uint64_t owner_id, std::string name,
Render_Node_Kind kind) {
if (node_id == 0)
throw std::invalid_argument("render node id must not be zero");
if (std::any_of(graph_.nodes.begin(), graph_.nodes.end(), [node_id](const Render_Node& node) {
return node.node_id == node_id;
}))
throw std::invalid_argument("duplicate render node id");
graph_.nodes.push_back({node_id, owner_id, std::move(name), kind,
graph_.nodes.size()});
return {this, generation_, graph_.nodes.size() - 1};
}
void Render_Graph_Builder::precede(Task from, Task to) {
validate(from);
validate(to);
if (from.index == to.index || reaches(to.index, from.index))
throw std::invalid_argument("render graph cycle");
const Render_Edge edge{graph_.nodes[from.index].node_id, graph_.nodes[to.index].node_id};
if (std::find(graph_.edges.begin(), graph_.edges.end(), edge) == graph_.edges.end())
graph_.edges.push_back(edge);
}
const Render_Graph& Render_Graph_Builder::graph() const noexcept {
return graph_;
}
Render_Graph Render_Graph_Builder::finish() && {
++generation_;
normalize(graph_);
return std::move(graph_);
}
void Render_Graph_Builder::validate(Task task) const {
if (task.builder != this || task.generation != generation_ ||
task.index >= graph_.nodes.size())
throw std::invalid_argument("invalid render graph task");
}
bool Render_Graph_Builder::reaches(std::size_t from, std::size_t target) const {
std::unordered_map<Render_Node_Id, std::size_t> indices;
indices.reserve(graph_.nodes.size());
for (std::size_t index = 0; index < graph_.nodes.size(); ++index)
indices.emplace(graph_.nodes[index].node_id, index);
std::vector<std::size_t> stack{from};
std::vector<bool> visited(graph_.nodes.size());
while (!stack.empty()) {
const std::size_t index = stack.back();
stack.pop_back();
if (index == target)
return true;
if (visited[index])
continue;
visited[index] = true;
const Render_Node_Id id = graph_.nodes[index].node_id;
for (const Render_Edge& edge : graph_.edges) {
if (edge.from == id)
stack.push_back(indices.at(edge.to));
}
}
return false;
}
std::shared_ptr<const Render_Plan> Render_Plan_History::publish(Render_Graph graph) {
normalize(graph);
std::lock_guard lock(mutex_);
if (!plans_.empty() && same_topology(plans_.back()->graph, graph))
return plans_.back();
if (next_version_ == 0 || next_version_ == std::numeric_limits<Render_Plan_Version>::max())
throw std::overflow_error("render plan version exhausted");
auto plan = std::make_shared<Render_Plan>();
plan->version = next_version_++;
plan->graph = std::move(graph);
plans_.push_back(plan);
if (plans_.size() > retained_plan_count)
plans_.erase(plans_.begin());
return plan;
}
std::shared_ptr<const Render_Plan> Render_Plan_History::current() const {
std::lock_guard lock(mutex_);
return plans_.empty() ? nullptr : plans_.back();
}
std::shared_ptr<const Render_Plan> Render_Plan_History::find(Render_Plan_Version version) const {
std::lock_guard lock(mutex_);
const auto iterator = std::lower_bound(
plans_.begin(), plans_.end(), version,
[](const std::shared_ptr<const Render_Plan>& plan, Render_Plan_Version value) {
return plan->version < value;
});
return iterator != plans_.end() && (*iterator)->version == version ? *iterator : nullptr;
}
Render_Plan_Difference compare_render_plans(const Render_Plan& before,
const Render_Plan& after) {
Render_Plan_Difference result;
std::unordered_set<Render_Node_Id> before_nodes;
std::unordered_set<Render_Node_Id> after_nodes;
before_nodes.reserve(before.graph.nodes.size());
after_nodes.reserve(after.graph.nodes.size());
for (const auto& node : before.graph.nodes)
before_nodes.insert(node.node_id);
for (const auto& node : after.graph.nodes)
after_nodes.insert(node.node_id);
for (Render_Node_Id id : after_nodes) {
if (!before_nodes.contains(id))
result.added_nodes.push_back(id);
}
for (Render_Node_Id id : before_nodes) {
if (!after_nodes.contains(id))
result.removed_nodes.push_back(id);
}
for (const auto& edge : after.graph.edges) {
if (std::find(before.graph.edges.begin(), before.graph.edges.end(), edge) ==
before.graph.edges.end())
result.added_edges.push_back(edge);
}
for (const auto& edge : before.graph.edges) {
if (std::find(after.graph.edges.begin(), after.graph.edges.end(), edge) ==
after.graph.edges.end())
result.removed_edges.push_back(edge);
}
std::sort(result.added_nodes.begin(), result.added_nodes.end());
std::sort(result.removed_nodes.begin(), result.removed_nodes.end());
std::sort(result.added_edges.begin(), result.added_edges.end(), edge_less);
std::sort(result.removed_edges.begin(), result.removed_edges.end(), edge_less);
return result;
}