#include "glad/glad.h" #include #include #include #include #include #include #include namespace extra2d { RendererModule::RendererModule() = default; RendererModule::~RendererModule() = default; RendererModule::RendererModule(RendererModule &&other) noexcept : commandBuffer_(std::move(other.commandBuffer_)), commandCount_(other.commandCount_), uniformManager_(std::move(other.uniformManager_)), onRenderBeginListener_(std::move(other.onRenderBeginListener_)), onRenderSubmitListener_(std::move(other.onRenderSubmitListener_)), onRenderSetCameraListener_(std::move(other.onRenderSetCameraListener_)), onRenderEndListener_(std::move(other.onRenderEndListener_)), onResizeListener_(std::move(other.onResizeListener_)), onShowListener_(std::move(other.onShowListener_)), glInitialized_(other.glInitialized_), stats_(other.stats_), viewportX_(other.viewportX_), viewportY_(other.viewportY_), viewportWidth_(other.viewportWidth_), viewportHeight_(other.viewportHeight_), viewportAdapter_(std::move(other.viewportAdapter_)), viewProjectionMatrix_(std::move(other.viewProjectionMatrix_)) { other.commandCount_ = 0; other.glInitialized_ = false; other.stats_ = {}; other.viewportX_ = 0; other.viewportY_ = 0; other.viewportWidth_ = 0; other.viewportHeight_ = 0; } RendererModule &RendererModule::operator=(RendererModule &&other) noexcept { if (this != &other) { if (glInitialized_) { uniformManager_.shutdown(); } commandBuffer_ = std::move(other.commandBuffer_); commandCount_ = other.commandCount_; uniformManager_ = std::move(other.uniformManager_); onRenderBeginListener_ = std::move(other.onRenderBeginListener_); onRenderSubmitListener_ = std::move(other.onRenderSubmitListener_); onRenderSetCameraListener_ = std::move(other.onRenderSetCameraListener_); onRenderEndListener_ = std::move(other.onRenderEndListener_); onResizeListener_ = std::move(other.onResizeListener_); onShowListener_ = std::move(other.onShowListener_); glInitialized_ = other.glInitialized_; stats_ = other.stats_; viewportX_ = other.viewportX_; viewportY_ = other.viewportY_; viewportWidth_ = other.viewportWidth_; viewportHeight_ = other.viewportHeight_; viewportAdapter_ = std::move(other.viewportAdapter_); viewProjectionMatrix_ = std::move(other.viewProjectionMatrix_); other.commandCount_ = 0; other.glInitialized_ = false; other.stats_ = {}; other.viewportX_ = 0; other.viewportY_ = 0; other.viewportWidth_ = 0; other.viewportHeight_ = 0; } return *this; } bool RendererModule::init() { E2D_LOG_INFO("Initializing RendererModule..."); onRenderBeginListener_.bind([this]() { onRenderBegin(); }); onRenderSubmitListener_.bind( [this](const RenderCommand &cmd) { onRenderSubmit(cmd); }); onRenderSetCameraListener_.bind( [this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); }); onRenderEndListener_.bind([this]() { onRenderEnd(); }); onResizeListener_.bind([this](int32 w, int32 h) { onResize(w, h); }); onShowListener_.bind([this]() { onWindowShow(); }); E2D_LOG_INFO("RendererModule initialized (waiting for GL context)"); return true; } void RendererModule::onWindowShow() { if (glInitialized_) { return; } E2D_LOG_INFO("Initializing OpenGL context..."); if (!uniformManager_.initialize()) { E2D_LOG_ERROR("Failed to initialize UniformBufferManager"); return; } int windowWidth = 800, windowHeight = 600; SDL_Window *sdlWindow = SDL_GL_GetCurrentWindow(); if (sdlWindow) { SDL_GetWindowSize(sdlWindow, &windowWidth, &windowHeight); E2D_LOG_INFO("Setting initial viewport to window size: {}x{}", windowWidth, windowHeight); } else { E2D_LOG_WARN("Could not get SDL window, using default viewport 800x600"); } setViewport(0, 0, static_cast(windowWidth), static_cast(windowHeight)); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glInitialized_ = true; E2D_LOG_INFO("OpenGL context initialized successfully"); } void RendererModule::shutdown() { E2D_LOG_INFO("Shutting down RendererModule..."); if (glInitialized_) { uniformManager_.shutdown(); } glInitialized_ = false; E2D_LOG_INFO("RendererModule shutdown complete"); } MaterialHandle RendererModule::getDefaultMaterialHandle() const { auto* assets = getAssets(); return assets ? assets->getDefaultMaterial() : MaterialHandle::invalid(); } MeshHandle RendererModule::getDefaultQuadHandle() const { auto* assets = getAssets(); return assets ? assets->getDefaultQuad() : MeshHandle::invalid(); } TextureHandle RendererModule::getDefaultTextureHandle() const { auto* assets = getAssets(); return assets ? assets->getDefaultTexture() : TextureHandle::invalid(); } void RendererModule::setViewport(int32 x, int32 y, int32 width, int32 height) { viewportX_ = x; viewportY_ = y; viewportWidth_ = width; viewportHeight_ = height; viewportAdapter_.update(width, height); auto result = viewportAdapter_.getResult(); glViewport(static_cast(result.viewport.x), static_cast(result.viewport.y), static_cast(result.viewport.w), static_cast(result.viewport.h)); } void RendererModule::clear(const Color &color, uint32 flags) { GLbitfield mask = 0; if (flags & CLEAR_COLOR_FLAG) { glClearColor(color.r, color.g, color.b, color.a); mask |= GL_COLOR_BUFFER_BIT; } if (flags & CLEAR_DEPTH_FLAG) { mask |= GL_DEPTH_BUFFER_BIT; } if (flags & CLEAR_STENCIL_FLAG) { mask |= GL_STENCIL_BUFFER_BIT; } if (mask != 0) { glClear(mask); } } void RendererModule::onRenderBegin() { if (!glInitialized_) { return; } glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); commandCount_ = 0; stats_ = {}; uniformManager_.resetMaterialUBOs(); } void RendererModule::onRenderSubmit(const RenderCommand &cmd) { if (!glInitialized_) { return; } if (commandCount_ >= MAX_RENDER_COMMANDS) { E2D_LOG_WARN("Render command buffer full!"); return; } commandBuffer_[commandCount_++] = cmd; stats_.commandsSubmitted++; } void RendererModule::onRenderSetCamera(const Mat4 &viewProj) { if (!glInitialized_) { return; } viewProjectionMatrix_ = viewProj; uniformManager_.updateGlobalUBO(&viewProj, sizeof(Mat4)); } void RendererModule::onRenderEnd() { if (!glInitialized_) { return; } sortCommands(); batchAndDraw(); E2D_LOG_DEBUG("Render: {} commands, {} draw calls, {} batches", stats_.commandsExecuted, stats_.drawCalls, stats_.batches); } void RendererModule::onResize(int32 width, int32 height) { setViewport(0, 0, width, height); } void RendererModule::sortCommands() { std::sort(commandBuffer_.begin(), commandBuffer_.begin() + commandCount_, [](const RenderCommand &a, const RenderCommand &b) { return a.sortKey < b.sortKey; }); } void RendererModule::batchAndDraw() { auto* assets = getAssets(); if (!assets) { E2D_LOG_ERROR("AssetsModule not available"); return; } MaterialHandle lastMaterial = MaterialHandle::invalid(); MeshHandle lastMesh = MeshHandle::invalid(); uint32_t batchStart = 0; uint32_t batchCount = 0; for (uint32_t i = 0; i < commandCount_; ++i) { const auto &cmd = commandBuffer_[i]; if (cmd.type != RenderCommandType::DrawMesh) { if (batchCount > 0) { drawBatch(batchStart, batchCount, lastMaterial, lastMesh); stats_.batches++; batchCount = 0; } executeCommand(cmd); continue; } if (cmd.drawMesh.material != lastMaterial || cmd.drawMesh.mesh != lastMesh) { if (batchCount > 0) { drawBatch(batchStart, batchCount, lastMaterial, lastMesh); stats_.batches++; } lastMaterial = cmd.drawMesh.material; lastMesh = cmd.drawMesh.mesh; batchStart = i; batchCount = 1; } else { ++batchCount; } stats_.commandsExecuted++; } if (batchCount > 0) { drawBatch(batchStart, batchCount, lastMaterial, lastMesh); stats_.batches++; } } void RendererModule::drawBatch(uint32_t start, uint32_t count, MaterialHandle materialHandle, MeshHandle meshHandle) { auto* assets = getAssets(); if (!assets) return; Material* material = assets->get(materialHandle); if (!material) { material = assets->getDefaultMaterialPtr(); } Mesh* mesh = assets->get(meshHandle); if (!mesh) { mesh = assets->getDefaultQuadPtr(); } if (!material || !mesh) return; Shader* shader = material->getShader().get(); if (!shader) return; shader->bind(); shader->setMat4("uViewProjection", glm::value_ptr(viewProjectionMatrix_)); shader->setVec4("uTintColor", 1.0f, 1.0f, 1.0f, 1.0f); shader->setFloat("uOpacity", 1.0f); const auto &textureSlots = material->getTextures(); bool hasMaterialTexture = false; for (const auto &slot : textureSlots) { if (slot.handle.isValid()) { Texture* texture = assets->get(slot.handle); if (texture) { texture->bind(slot.slot); shader->setInt(slot.uniformName, static_cast(slot.slot)); hasMaterialTexture = true; } } } if (!hasMaterialTexture) { Texture* defaultTexture = assets->getDefaultTexturePtr(); if (defaultTexture) { defaultTexture->bind(0); shader->setInt("uTexture", 0); } } mesh->bind(); for (uint32_t i = 0; i < count; ++i) { Transform transform = commandBuffer_[start + i].getTransform(); float matrix[16]; transform.toMatrix(matrix); shader->setMat4("uModelMatrix", matrix); Color color = commandBuffer_[start + i].getColor(); shader->setVec4("uColor", color.r, color.g, color.b, color.a); mesh->draw(); stats_.drawCalls++; } } void RendererModule::executeCommand(const RenderCommand &cmd) { switch (cmd.type) { case RenderCommandType::SetViewport: setViewport(cmd.viewport.x, cmd.viewport.y, cmd.viewport.width, cmd.viewport.height); break; case RenderCommandType::Clear: clear(cmd.clear.color, cmd.clear.flags); break; default: break; } } } // namespace extra2d