GroveEngine/modules/BgfxRenderer/RenderGraph/RenderGraph.cpp
StillHammer 4a30b1f149 feat(BgfxRenderer): Complete Phase 4 - ShaderManager integration
- Refactor ShaderManager to use RHI abstraction (no bgfx:: exposed)
- Implement Option E: inject ShaderHandle via pass constructors
- SpritePass/DebugPass now receive shader in constructor
- RenderPass::execute() takes IRHIDevice& for dynamic buffer updates
- SpritePass::execute() updates instance buffer from FramePacket
- Integrate ShaderManager lifecycle in BgfxRendererModule
- Add test_22_bgfx_sprites.cpp (visual test with SDL2)
- Add test_22_bgfx_sprites_headless.cpp (headless data structure test)
- Update PLAN_BGFX_RENDERER.md with Phase 4 completion and Phase 6.5

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 22:27:19 +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, device, 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