GroveEngine/modules/BgfxRenderer/RenderGraph/RenderGraph.cpp
StillHammer b065463226 fix(BgfxRenderer): Implement CommandBuffer execution and topological sort
- Add executeCommandBuffer() to IRHIDevice interface
- Implement full command buffer execution in BgfxDevice (175 lines)
  - Handles SetState, SetTexture, SetUniform, SetScissor
  - Handles SetVertexBuffer, SetIndexBuffer, SetInstanceBuffer
  - Handles Draw, DrawIndexed, DrawInstanced, Submit
- Rewrite RenderGraph::compile() with Kahn's algorithm for topological sort
  - Now respects pass dependencies correctly
  - Uses sortOrder as secondary key via priority queue
  - Detects cycles and throws exception
- Call device.executeCommandBuffer() in RenderGraph::execute()
- Make OpenSSL optional with std::hash fallback for SHA256

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 11:50:05 +08:00

119 lines
3.3 KiB
C++

#include "RenderGraph.h"
#include "../RHI/RHIDevice.h"
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <stdexcept>
namespace grove {
void RenderGraph::addPass(std::unique_ptr<RenderPass> pass) {
m_passes.push_back(std::move(pass));
m_compiled = false;
}
void RenderGraph::setup(rhi::IRHIDevice& device) {
for (auto& pass : m_passes) {
pass->setup(device);
}
}
void RenderGraph::compile() {
if (m_compiled) return;
const size_t n = m_passes.size();
if (n == 0) {
m_compiled = true;
return;
}
// Build name to index map
std::unordered_map<std::string, size_t> nameToIndex;
for (size_t i = 0; i < n; ++i) {
nameToIndex[m_passes[i]->getName()] = i;
}
// Build adjacency list and in-degree count for topological sort
std::vector<std::vector<size_t>> adjacency(n);
std::vector<size_t> inDegree(n, 0);
for (size_t i = 0; i < n; ++i) {
auto deps = m_passes[i]->getDependencies();
for (const char* depName : deps) {
auto it = nameToIndex.find(depName);
if (it != nameToIndex.end()) {
size_t depIdx = it->second;
adjacency[depIdx].push_back(i); // depIdx -> i (dep must run before i)
inDegree[i]++;
}
}
}
// Kahn's algorithm for topological sort with sort order as secondary key
// Use a priority queue to respect sortOrder among nodes with same in-degree
auto compare = [this](size_t a, size_t b) {
return m_passes[a]->getSortOrder() > m_passes[b]->getSortOrder(); // min-heap
};
std::priority_queue<size_t, std::vector<size_t>, decltype(compare)> readyQueue(compare);
// Initialize with nodes that have no dependencies
for (size_t i = 0; i < n; ++i) {
if (inDegree[i] == 0) {
readyQueue.push(i);
}
}
m_sortedIndices.clear();
m_sortedIndices.reserve(n);
while (!readyQueue.empty()) {
size_t current = readyQueue.top();
readyQueue.pop();
m_sortedIndices.push_back(current);
// Decrease in-degree of dependents
for (size_t dependent : adjacency[current]) {
inDegree[dependent]--;
if (inDegree[dependent] == 0) {
readyQueue.push(dependent);
}
}
}
// Check for cycles
if (m_sortedIndices.size() != n) {
throw std::runtime_error("RenderGraph: Cycle detected in pass dependencies!");
}
m_compiled = true;
}
void RenderGraph::execute(const FramePacket& frame, rhi::IRHIDevice& device) {
if (!m_compiled) {
compile();
}
// Single command buffer for single-threaded execution
rhi::RHICommandBuffer cmdBuffer;
// Execute passes in topologically sorted order
for (size_t idx : m_sortedIndices) {
m_passes[idx]->execute(frame, cmdBuffer);
}
// Execute the recorded command buffer on the device
device.executeCommandBuffer(cmdBuffer);
}
void RenderGraph::shutdown(rhi::IRHIDevice& device) {
for (auto& pass : m_passes) {
pass->shutdown(device);
}
m_passes.clear();
m_sortedIndices.clear();
m_compiled = false;
}
} // namespace grove