#include "BgfxRendererModule.h" #include "RHI/RHIDevice.h" #include "Frame/FrameAllocator.h" #include "Frame/FramePacket.h" #include "Shaders/ShaderManager.h" #include "RenderGraph/RenderGraph.h" #include "Scene/SceneCollector.h" #include "Resources/ResourceCache.h" #include "Debug/DebugOverlay.h" #include "Passes/ClearPass.h" #include "Passes/TilemapPass.h" #include "Passes/SpritePass.h" #include "Passes/TextPass.h" #include "Passes/ParticlePass.h" #include "Passes/DebugPass.h" #include #include #include namespace grove { BgfxRendererModule::BgfxRendererModule() = default; BgfxRendererModule::~BgfxRendererModule() = default; void BgfxRendererModule::setConfiguration(const IDataNode& config, IIO* io, ITaskScheduler* scheduler) { m_io = io; // Setup logger m_logger = spdlog::get("BgfxRenderer"); if (!m_logger) { m_logger = spdlog::stdout_color_mt("BgfxRenderer"); } // Read static config via IDataNode m_width = static_cast(config.getInt("windowWidth", 1280)); m_height = static_cast(config.getInt("windowHeight", 720)); m_backend = config.getString("backend", "opengl"); m_shaderPath = config.getString("shaderPath", "./shaders"); m_vsync = config.getBool("vsync", true); m_maxSprites = config.getInt("maxSpritesPerBatch", 10000); size_t allocatorSize = static_cast(config.getInt("frameAllocatorSizeMB", 16)) * 1024 * 1024; // Window handle (passed via config or 0 if separate WindowModule) // Use double to preserve 64-bit pointer values // Also try getInt as fallback for compatibility with older code that uses setInt void* windowHandle = nullptr; double handleDouble = config.getDouble("nativeWindowHandle", 0.0); if (handleDouble != 0.0) { windowHandle = reinterpret_cast(static_cast(handleDouble)); } else { // Fallback: try reading as int (for 32-bit handles or compatibility) int handleInt = config.getInt("nativeWindowHandle", 0); if (handleInt != 0) { windowHandle = reinterpret_cast(static_cast(static_cast(handleInt))); m_logger->warn("nativeWindowHandle passed as int - consider using setDouble for 64-bit handles"); } } // Display handle (X11 Display* on Linux, 0/nullptr on Windows) void* displayHandle = nullptr; double displayDouble = config.getDouble("nativeDisplayHandle", 0.0); if (displayDouble != 0.0) { displayHandle = reinterpret_cast(static_cast(displayDouble)); } m_logger->info("Initializing BgfxRenderer: {}x{} backend={}", m_width, m_height, m_backend); // Initialize subsystems m_frameAllocator = std::make_unique(allocatorSize); m_device = rhi::IRHIDevice::create(); if (!m_device->init(windowHandle, displayHandle, m_width, m_height)) { m_logger->error("Failed to initialize RHI device"); return; } // Log device capabilities auto caps = m_device->getCapabilities(); m_logger->info("GPU: {} ({})", caps.gpuName, caps.rendererName); m_logger->info("Max texture size: {}, Max draw calls: {}", caps.maxTextureSize, caps.maxDrawCalls); // Initialize shader manager m_shaderManager = std::make_unique(); m_shaderManager->init(*m_device, caps.rendererName); m_logger->info("ShaderManager initialized with {} programs", m_shaderManager->getProgramCount()); // Get shader handles for passes rhi::ShaderHandle spriteShader = m_shaderManager->getProgram("sprite"); rhi::ShaderHandle debugShader = m_shaderManager->getProgram("debug"); if (!spriteShader.isValid()) { m_logger->error("Failed to load sprite shader"); return; } // Setup render graph with passes (inject shaders via constructors) m_logger->info("Creating RenderGraph..."); m_renderGraph = std::make_unique(); m_renderGraph->addPass(std::make_unique()); m_logger->info("Added ClearPass"); // Setup resource cache first (needed by passes) m_resourceCache = std::make_unique(); // Create TilemapPass (renders before sprites) auto tilemapPass = std::make_unique(spriteShader); tilemapPass->setResourceCache(m_resourceCache.get()); m_renderGraph->addPass(std::move(tilemapPass)); m_logger->info("Added TilemapPass"); // Create SpritePass and keep reference for texture binding auto spritePass = std::make_unique(spriteShader); m_spritePass = spritePass.get(); // Non-owning reference m_spritePass->setResourceCache(m_resourceCache.get()); m_renderGraph->addPass(std::move(spritePass)); m_logger->info("Added SpritePass"); // Create TextPass (uses sprite shader for glyph quads) m_renderGraph->addPass(std::make_unique(spriteShader)); m_logger->info("Added TextPass"); // Create ParticlePass (uses sprite shader, renders after sprites with additive blending) auto particlePass = std::make_unique(spriteShader); particlePass->setResourceCache(m_resourceCache.get()); m_renderGraph->addPass(std::move(particlePass)); m_logger->info("Added ParticlePass"); m_renderGraph->addPass(std::make_unique(debugShader)); m_logger->info("Added DebugPass"); m_renderGraph->setup(*m_device); m_logger->info("RenderGraph setup complete"); m_renderGraph->compile(); m_logger->info("RenderGraph compiled"); // Setup scene collector with IIO subscriptions and correct dimensions m_sceneCollector = std::make_unique(); m_sceneCollector->setup(io, m_width, m_height); m_logger->info("SceneCollector setup complete with dimensions {}x{}", m_width, m_height); // Setup debug overlay m_debugOverlay = std::make_unique(); bool debugEnabled = config.getBool("debugOverlay", false); m_debugOverlay->setEnabled(debugEnabled); if (debugEnabled) { m_logger->info("Debug overlay enabled"); } // Load default texture if specified in config std::string defaultTexturePath = config.getString("defaultTexture", ""); if (!defaultTexturePath.empty()) { uint16_t texId = m_resourceCache->loadTextureWithId(*m_device, defaultTexturePath); if (texId > 0) { rhi::TextureHandle tex = m_resourceCache->getTextureById(texId); m_spritePass->setTexture(tex); m_logger->info("Loaded default texture: {} (id={})", defaultTexturePath, texId); } else { m_logger->warn("Failed to load default texture: {}", defaultTexturePath); } } // Load additional textures (texture1, texture2, etc.) for (int i = 1; i <= 10; ++i) { std::string key = "texture" + std::to_string(i); std::string path = config.getString(key, ""); if (!path.empty()) { uint16_t texId = m_resourceCache->loadTextureWithId(*m_device, path); if (texId > 0) { m_logger->info("Loaded texture: {} (id={})", path, texId); } else { m_logger->warn("Failed to load texture: {}", path); } } } m_logger->info("BgfxRenderer initialized successfully"); } void BgfxRendererModule::process(const IDataNode& input) { if (!m_device) { m_logger->error("Device not initialized"); return; } // Reset frame allocator for this frame if (m_frameAllocator) { m_frameAllocator->reset(); } // Collect scene data from IIO messages and prepare frame packet if (m_sceneCollector && m_renderGraph && m_frameAllocator && m_io) { // Get delta time from input (or default to 16ms) float deltaTime = static_cast(input.getDouble("deltaTime", 0.016)); // Collect all IIO messages for this frame m_sceneCollector->collect(m_io, deltaTime); // Generate immutable FramePacket for render passes FramePacket packet = m_sceneCollector->finalize(*m_frameAllocator); // Apply view transform (projection matrix for 2D rendering) m_device->setViewClear(0, packet.clearColor, 1.0f); m_device->setViewRect(0, packet.mainView.viewportX, packet.mainView.viewportY, packet.mainView.viewportW, packet.mainView.viewportH); m_device->setViewTransform(0, packet.mainView.viewMatrix, packet.mainView.projMatrix); // Execute render graph with collected scene data m_renderGraph->execute(packet, *m_device); // Clear staging buffers for next frame m_sceneCollector->clear(); } // Present frame m_device->frame(); m_frameCount++; } void BgfxRendererModule::shutdown() { m_logger->info("BgfxRenderer shutting down, {} frames rendered", m_frameCount); if (m_renderGraph && m_device) { m_renderGraph->shutdown(*m_device); } if (m_resourceCache && m_device) { m_resourceCache->clear(*m_device); } if (m_shaderManager && m_device) { m_shaderManager->shutdown(*m_device); } if (m_device) { m_device->shutdown(); } m_renderGraph.reset(); m_resourceCache.reset(); m_shaderManager.reset(); m_sceneCollector.reset(); m_frameAllocator.reset(); m_device.reset(); } std::unique_ptr BgfxRendererModule::getState() { // Minimal state for hot-reload (renderer is stateless gameplay-wise) auto state = std::make_unique("state"); state->setInt("frameCount", static_cast(m_frameCount)); // GPU resources are recreated on reload return state; } void BgfxRendererModule::setState(const IDataNode& state) { m_frameCount = static_cast(state.getInt("frameCount", 0)); m_logger->info("State restored: frameCount={}", m_frameCount); } const IDataNode& BgfxRendererModule::getConfiguration() { if (!m_configCache) { m_configCache = std::make_unique("config"); m_configCache->setInt("windowWidth", m_width); m_configCache->setInt("windowHeight", m_height); m_configCache->setString("backend", m_backend); m_configCache->setString("shaderPath", m_shaderPath); m_configCache->setBool("vsync", m_vsync); m_configCache->setInt("maxSpritesPerBatch", m_maxSprites); } return *m_configCache; } std::unique_ptr BgfxRendererModule::getHealthStatus() { auto health = std::make_unique("health"); health->setString("status", "running"); health->setInt("frameCount", static_cast(m_frameCount)); health->setInt("allocatorUsedBytes", static_cast(m_frameAllocator ? m_frameAllocator->getUsed() : 0)); health->setInt("textureCount", static_cast(m_resourceCache ? m_resourceCache->getTextureCount() : 0)); health->setInt("shaderCount", static_cast(m_resourceCache ? m_resourceCache->getShaderCount() : 0)); return health; } } // namespace grove // ============================================================================ // C Export (required for dlopen/LoadLibrary) // Skip when building as static library to avoid multiple definition errors // ============================================================================ #ifndef GROVE_MODULE_STATIC #ifdef _WIN32 #define GROVE_MODULE_EXPORT __declspec(dllexport) #else #define GROVE_MODULE_EXPORT #endif extern "C" { GROVE_MODULE_EXPORT grove::IModule* createModule() { return new grove::BgfxRendererModule(); } GROVE_MODULE_EXPORT void destroyModule(grove::IModule* module) { delete module; } } #endif // GROVE_MODULE_STATIC