#include "Render_Plan.hpp" #include #include #include #include #include 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 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 stack{from}; std::vector 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 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::max()) throw std::overflow_error("render plan version exhausted"); auto plan = std::make_shared(); 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 Render_Plan_History::current() const { std::lock_guard lock(mutex_); return plans_.empty() ? nullptr : plans_.back(); } std::shared_ptr 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& 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 before_nodes; std::unordered_set 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; }