170 lines
6.5 KiB
C++
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;
|
|
}
|