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>
This commit is contained in:
parent
d63d8d83fa
commit
b065463226
@ -112,8 +112,15 @@ add_library(GroveEngine::core ALIAS grove_core)
|
|||||||
option(GROVE_BUILD_IMPLEMENTATIONS "Build GroveEngine implementations" ON)
|
option(GROVE_BUILD_IMPLEMENTATIONS "Build GroveEngine implementations" ON)
|
||||||
|
|
||||||
if(GROVE_BUILD_IMPLEMENTATIONS)
|
if(GROVE_BUILD_IMPLEMENTATIONS)
|
||||||
# Find OpenSSL for SHA256 hashing
|
# Find OpenSSL for SHA256 hashing (optional on MinGW)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL QUIET)
|
||||||
|
if(OpenSSL_FOUND)
|
||||||
|
message(STATUS "OpenSSL found - using hardware-accelerated SHA256")
|
||||||
|
set(GROVE_USE_OPENSSL ON)
|
||||||
|
else()
|
||||||
|
message(STATUS "OpenSSL not found - using fallback SHA256 implementation")
|
||||||
|
set(GROVE_USE_OPENSSL OFF)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(grove_impl STATIC
|
add_library(grove_impl STATIC
|
||||||
# --- Working files (IDataNode-based) ---
|
# --- Working files (IDataNode-based) ---
|
||||||
@ -140,11 +147,17 @@ if(GROVE_BUILD_IMPLEMENTATIONS)
|
|||||||
GroveEngine::core
|
GroveEngine::core
|
||||||
topictree::topictree
|
topictree::topictree
|
||||||
stillhammer_logger
|
stillhammer_logger
|
||||||
OpenSSL::Crypto
|
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
${CMAKE_DL_LIBS}
|
${CMAKE_DL_LIBS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(GROVE_USE_OPENSSL)
|
||||||
|
target_link_libraries(grove_impl PUBLIC OpenSSL::Crypto)
|
||||||
|
target_compile_definitions(grove_impl PUBLIC GROVE_USE_OPENSSL=1)
|
||||||
|
else()
|
||||||
|
target_compile_definitions(grove_impl PUBLIC GROVE_USE_OPENSSL=0)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Enable position-independent code for static library (needed for .so modules)
|
# Enable position-independent code for static library (needed for .so modules)
|
||||||
set_target_properties(grove_impl PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
set_target_properties(grove_impl PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
|
|||||||
@ -257,6 +257,183 @@ public:
|
|||||||
bgfx::frame();
|
bgfx::frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void executeCommandBuffer(const RHICommandBuffer& cmdBuffer) override {
|
||||||
|
// Track current state for bgfx calls
|
||||||
|
RenderState currentState;
|
||||||
|
BufferHandle currentVB;
|
||||||
|
BufferHandle currentIB;
|
||||||
|
BufferHandle currentInstBuffer;
|
||||||
|
uint32_t instStart = 0;
|
||||||
|
uint32_t instCount = 0;
|
||||||
|
|
||||||
|
for (const Command& cmd : cmdBuffer.getCommands()) {
|
||||||
|
switch (cmd.type) {
|
||||||
|
case CommandType::SetState: {
|
||||||
|
currentState = cmd.setState.state;
|
||||||
|
// Build bgfx state flags
|
||||||
|
uint64_t state = BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A;
|
||||||
|
|
||||||
|
switch (currentState.blend) {
|
||||||
|
case BlendMode::Alpha:
|
||||||
|
state |= BGFX_STATE_BLEND_ALPHA;
|
||||||
|
break;
|
||||||
|
case BlendMode::Additive:
|
||||||
|
state |= BGFX_STATE_BLEND_ADD;
|
||||||
|
break;
|
||||||
|
case BlendMode::Multiply:
|
||||||
|
state |= BGFX_STATE_BLEND_MULTIPLY;
|
||||||
|
break;
|
||||||
|
case BlendMode::None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (currentState.cull) {
|
||||||
|
case CullMode::CW:
|
||||||
|
state |= BGFX_STATE_CULL_CW;
|
||||||
|
break;
|
||||||
|
case CullMode::CCW:
|
||||||
|
state |= BGFX_STATE_CULL_CCW;
|
||||||
|
break;
|
||||||
|
case CullMode::None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentState.depthTest) {
|
||||||
|
state |= BGFX_STATE_DEPTH_TEST_LESS;
|
||||||
|
}
|
||||||
|
if (currentState.depthWrite) {
|
||||||
|
state |= BGFX_STATE_WRITE_Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
bgfx::setState(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::SetTexture: {
|
||||||
|
bgfx::TextureHandle tex = { cmd.setTexture.texture.id };
|
||||||
|
bgfx::UniformHandle sampler = { cmd.setTexture.sampler.id };
|
||||||
|
bgfx::setTexture(cmd.setTexture.slot, sampler, tex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::SetUniform: {
|
||||||
|
bgfx::UniformHandle uniform = { cmd.setUniform.uniform.id };
|
||||||
|
bgfx::setUniform(uniform, cmd.setUniform.data, cmd.setUniform.numVec4s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::SetVertexBuffer: {
|
||||||
|
currentVB = cmd.setVertexBuffer.buffer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::SetIndexBuffer: {
|
||||||
|
currentIB = cmd.setIndexBuffer.buffer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::SetInstanceBuffer: {
|
||||||
|
currentInstBuffer = cmd.setInstanceBuffer.buffer;
|
||||||
|
instStart = cmd.setInstanceBuffer.start;
|
||||||
|
instCount = cmd.setInstanceBuffer.count;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::SetScissor: {
|
||||||
|
bgfx::setScissor(cmd.setScissor.x, cmd.setScissor.y,
|
||||||
|
cmd.setScissor.w, cmd.setScissor.h);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::Draw: {
|
||||||
|
// Set vertex buffer before draw
|
||||||
|
if (currentVB.isValid()) {
|
||||||
|
bool isDynamic = (currentVB.id & 0x8000) != 0;
|
||||||
|
uint16_t idx = currentVB.id & 0x7FFF;
|
||||||
|
if (isDynamic) {
|
||||||
|
bgfx::DynamicVertexBufferHandle h = { idx };
|
||||||
|
bgfx::setVertexBuffer(0, h, 0, cmd.draw.vertexCount);
|
||||||
|
} else {
|
||||||
|
bgfx::VertexBufferHandle h = { idx };
|
||||||
|
bgfx::setVertexBuffer(0, h, cmd.draw.startVertex, cmd.draw.vertexCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::DrawIndexed: {
|
||||||
|
// Set vertex and index buffers before draw
|
||||||
|
if (currentVB.isValid()) {
|
||||||
|
bool isDynamic = (currentVB.id & 0x8000) != 0;
|
||||||
|
uint16_t idx = currentVB.id & 0x7FFF;
|
||||||
|
if (isDynamic) {
|
||||||
|
bgfx::DynamicVertexBufferHandle h = { idx };
|
||||||
|
bgfx::setVertexBuffer(0, h);
|
||||||
|
} else {
|
||||||
|
bgfx::VertexBufferHandle h = { idx };
|
||||||
|
bgfx::setVertexBuffer(0, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentIB.isValid()) {
|
||||||
|
bool isDynamic = (currentIB.id & 0x8000) != 0;
|
||||||
|
uint16_t idx = currentIB.id & 0x7FFF;
|
||||||
|
if (isDynamic) {
|
||||||
|
bgfx::DynamicIndexBufferHandle h = { idx };
|
||||||
|
bgfx::setIndexBuffer(h, cmd.drawIndexed.startIndex, cmd.drawIndexed.indexCount);
|
||||||
|
} else {
|
||||||
|
bgfx::IndexBufferHandle h = { idx };
|
||||||
|
bgfx::setIndexBuffer(h, cmd.drawIndexed.startIndex, cmd.drawIndexed.indexCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::DrawInstanced: {
|
||||||
|
// Set vertex, index, and instance buffers
|
||||||
|
if (currentVB.isValid()) {
|
||||||
|
bool isDynamic = (currentVB.id & 0x8000) != 0;
|
||||||
|
uint16_t idx = currentVB.id & 0x7FFF;
|
||||||
|
if (isDynamic) {
|
||||||
|
bgfx::DynamicVertexBufferHandle h = { idx };
|
||||||
|
bgfx::setVertexBuffer(0, h);
|
||||||
|
} else {
|
||||||
|
bgfx::VertexBufferHandle h = { idx };
|
||||||
|
bgfx::setVertexBuffer(0, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentIB.isValid()) {
|
||||||
|
bool isDynamic = (currentIB.id & 0x8000) != 0;
|
||||||
|
uint16_t idx = currentIB.id & 0x7FFF;
|
||||||
|
if (isDynamic) {
|
||||||
|
bgfx::DynamicIndexBufferHandle h = { idx };
|
||||||
|
bgfx::setIndexBuffer(h, 0, cmd.drawInstanced.indexCount);
|
||||||
|
} else {
|
||||||
|
bgfx::IndexBufferHandle h = { idx };
|
||||||
|
bgfx::setIndexBuffer(h, 0, cmd.drawInstanced.indexCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentInstBuffer.isValid()) {
|
||||||
|
bool isDynamic = (currentInstBuffer.id & 0x8000) != 0;
|
||||||
|
uint16_t idx = currentInstBuffer.id & 0x7FFF;
|
||||||
|
if (isDynamic) {
|
||||||
|
bgfx::DynamicVertexBufferHandle h = { idx };
|
||||||
|
bgfx::setInstanceDataBuffer(h, instStart, instCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CommandType::Submit: {
|
||||||
|
bgfx::ProgramHandle program = { cmd.submit.shader.id };
|
||||||
|
bgfx::submit(cmd.submit.view, program, cmd.submit.depth);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t m_width = 0;
|
uint16_t m_width = 0;
|
||||||
uint16_t m_height = 0;
|
uint16_t m_height = 0;
|
||||||
|
|||||||
@ -60,6 +60,9 @@ public:
|
|||||||
// Frame
|
// Frame
|
||||||
virtual void frame() = 0;
|
virtual void frame() = 0;
|
||||||
|
|
||||||
|
// Command buffer execution
|
||||||
|
virtual void executeCommandBuffer(const class RHICommandBuffer& cmdBuffer) = 0;
|
||||||
|
|
||||||
// Factory
|
// Factory
|
||||||
static std::unique_ptr<IRHIDevice> create();
|
static std::unique_ptr<IRHIDevice> create();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
#include "../RHI/RHIDevice.h"
|
#include "../RHI/RHIDevice.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <queue>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace grove {
|
namespace grove {
|
||||||
@ -20,25 +22,69 @@ void RenderGraph::setup(rhi::IRHIDevice& device) {
|
|||||||
void RenderGraph::compile() {
|
void RenderGraph::compile() {
|
||||||
if (m_compiled) return;
|
if (m_compiled) return;
|
||||||
|
|
||||||
|
const size_t n = m_passes.size();
|
||||||
|
if (n == 0) {
|
||||||
|
m_compiled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Build name to index map
|
// Build name to index map
|
||||||
std::unordered_map<std::string, size_t> nameToIndex;
|
std::unordered_map<std::string, size_t> nameToIndex;
|
||||||
for (size_t i = 0; i < m_passes.size(); ++i) {
|
for (size_t i = 0; i < n; ++i) {
|
||||||
nameToIndex[m_passes[i]->getName()] = i;
|
nameToIndex[m_passes[i]->getName()] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create sorted indices based on sort order and dependencies
|
// Build adjacency list and in-degree count for topological sort
|
||||||
m_sortedIndices.clear();
|
std::vector<std::vector<size_t>> adjacency(n);
|
||||||
m_sortedIndices.reserve(m_passes.size());
|
std::vector<size_t> inDegree(n, 0);
|
||||||
|
|
||||||
for (size_t i = 0; i < m_passes.size(); ++i) {
|
for (size_t i = 0; i < n; ++i) {
|
||||||
m_sortedIndices.push_back(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]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by sort order (topological sort would be more complete but this is simpler)
|
// Kahn's algorithm for topological sort with sort order as secondary key
|
||||||
std::sort(m_sortedIndices.begin(), m_sortedIndices.end(),
|
// Use a priority queue to respect sortOrder among nodes with same in-degree
|
||||||
[this](size_t a, size_t b) {
|
auto compare = [this](size_t a, size_t b) {
|
||||||
return m_passes[a]->getSortOrder() < m_passes[b]->getSortOrder();
|
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;
|
m_compiled = true;
|
||||||
}
|
}
|
||||||
@ -51,13 +97,13 @@ void RenderGraph::execute(const FramePacket& frame, rhi::IRHIDevice& device) {
|
|||||||
// Single command buffer for single-threaded execution
|
// Single command buffer for single-threaded execution
|
||||||
rhi::RHICommandBuffer cmdBuffer;
|
rhi::RHICommandBuffer cmdBuffer;
|
||||||
|
|
||||||
// Execute passes in order
|
// Execute passes in topologically sorted order
|
||||||
for (size_t idx : m_sortedIndices) {
|
for (size_t idx : m_sortedIndices) {
|
||||||
m_passes[idx]->execute(frame, cmdBuffer);
|
m_passes[idx]->execute(frame, cmdBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Execute command buffer on device
|
// Execute the recorded command buffer on the device
|
||||||
// For now, passes directly call bgfx through the device
|
device.executeCommandBuffer(cmdBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderGraph::shutdown(rhi::IRHIDevice& device) {
|
void RenderGraph::shutdown(rhi::IRHIDevice& device) {
|
||||||
|
|||||||
@ -2,8 +2,15 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <openssl/sha.h>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#if GROVE_USE_OPENSSL
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#else
|
||||||
|
// Simple fallback hash using std::hash - NOT cryptographically secure
|
||||||
|
// but sufficient for change detection in render graph
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace grove {
|
namespace grove {
|
||||||
|
|
||||||
@ -271,6 +278,7 @@ void JsonDataNode::setBool(const std::string& name, bool value) {
|
|||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
std::string JsonDataNode::computeHash(const std::string& input) const {
|
std::string JsonDataNode::computeHash(const std::string& input) const {
|
||||||
|
#if GROVE_USE_OPENSSL
|
||||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||||
SHA256(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), hash);
|
SHA256(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), hash);
|
||||||
|
|
||||||
@ -279,6 +287,14 @@ std::string JsonDataNode::computeHash(const std::string& input) const {
|
|||||||
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
|
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
|
||||||
}
|
}
|
||||||
return ss.str();
|
return ss.str();
|
||||||
|
#else
|
||||||
|
// Fallback: use std::hash (not cryptographic, but good enough for change detection)
|
||||||
|
std::hash<std::string> hasher;
|
||||||
|
size_t hashVal = hasher(input);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << std::setw(16) << std::setfill('0') << hashVal;
|
||||||
|
return ss.str();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string JsonDataNode::getDataHash() {
|
std::string JsonDataNode::getDataHash() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user