2026-03-02 04:57:44 +08:00
|
|
|
#include "glad/glad.h"
|
2026-03-02 00:25:14 +08:00
|
|
|
#include <algorithm>
|
2026-03-02 22:44:42 +08:00
|
|
|
#include <assets/assets_module.h>
|
2026-03-02 00:25:14 +08:00
|
|
|
#include <event/events.h>
|
2026-03-02 04:50:28 +08:00
|
|
|
#include <glm/gtc/type_ptr.hpp>
|
2026-03-02 00:25:14 +08:00
|
|
|
#include <renderer/renderer_module.h>
|
|
|
|
|
#include <utils/logger.h>
|
|
|
|
|
|
2026-03-02 04:50:28 +08:00
|
|
|
#include <SDL.h>
|
|
|
|
|
|
2026-03-02 00:25:14 +08:00
|
|
|
namespace extra2d {
|
|
|
|
|
|
|
|
|
|
RendererModule::RendererModule() = default;
|
|
|
|
|
|
|
|
|
|
RendererModule::~RendererModule() = default;
|
|
|
|
|
|
|
|
|
|
RendererModule::RendererModule(RendererModule &&other) noexcept
|
2026-03-02 22:44:42 +08:00
|
|
|
: commandBuffer_(std::move(other.commandBuffer_)),
|
2026-03-02 00:25:14 +08:00
|
|
|
commandCount_(other.commandCount_),
|
|
|
|
|
uniformManager_(std::move(other.uniformManager_)),
|
|
|
|
|
onRenderBeginListener_(std::move(other.onRenderBeginListener_)),
|
|
|
|
|
onRenderSubmitListener_(std::move(other.onRenderSubmitListener_)),
|
2026-03-02 22:44:42 +08:00
|
|
|
onRenderSetCameraListener_(std::move(other.onRenderSetCameraListener_)),
|
2026-03-02 00:25:14 +08:00
|
|
|
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_),
|
2026-03-02 22:44:42 +08:00
|
|
|
viewportHeight_(other.viewportHeight_),
|
|
|
|
|
viewportAdapter_(std::move(other.viewportAdapter_)),
|
|
|
|
|
viewProjectionMatrix_(std::move(other.viewProjectionMatrix_)) {
|
2026-03-02 00:25:14 +08:00
|
|
|
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_);
|
2026-03-02 22:44:42 +08:00
|
|
|
onRenderSetCameraListener_ = std::move(other.onRenderSetCameraListener_);
|
2026-03-02 00:25:14 +08:00
|
|
|
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_;
|
2026-03-02 22:44:42 +08:00
|
|
|
viewportAdapter_ = std::move(other.viewportAdapter_);
|
|
|
|
|
viewProjectionMatrix_ = std::move(other.viewProjectionMatrix_);
|
2026-03-02 00:25:14 +08:00
|
|
|
|
|
|
|
|
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); });
|
2026-03-02 04:50:28 +08:00
|
|
|
onRenderSetCameraListener_.bind(
|
|
|
|
|
[this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); });
|
2026-03-02 00:25:14 +08:00
|
|
|
onRenderEndListener_.bind([this]() { onRenderEnd(); });
|
2026-03-02 22:44:42 +08:00
|
|
|
onResizeListener_.bind([this](int32 w, int32 h) { onResize(w, h); });
|
2026-03-02 00:25:14 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 04:50:28 +08:00
|
|
|
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,
|
2026-03-02 22:44:42 +08:00
|
|
|
windowHeight);
|
2026-03-02 04:50:28 +08:00
|
|
|
} else {
|
|
|
|
|
E2D_LOG_WARN("Could not get SDL window, using default viewport 800x600");
|
|
|
|
|
}
|
|
|
|
|
setViewport(0, 0, static_cast<int32>(windowWidth),
|
|
|
|
|
static_cast<int32>(windowHeight));
|
2026-03-02 00:25:14 +08:00
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
MaterialHandle RendererModule::getDefaultMaterialHandle() const {
|
|
|
|
|
auto* assets = getAssets();
|
|
|
|
|
return assets ? assets->getDefaultMaterial() : MaterialHandle::invalid();
|
2026-03-02 00:25:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
MeshHandle RendererModule::getDefaultQuadHandle() const {
|
|
|
|
|
auto* assets = getAssets();
|
|
|
|
|
return assets ? assets->getDefaultQuad() : MeshHandle::invalid();
|
2026-03-02 00:25:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
TextureHandle RendererModule::getDefaultTextureHandle() const {
|
|
|
|
|
auto* assets = getAssets();
|
|
|
|
|
return assets ? assets->getDefaultTexture() : TextureHandle::invalid();
|
2026-03-02 00:25:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RendererModule::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
|
|
|
|
viewportX_ = x;
|
|
|
|
|
viewportY_ = y;
|
|
|
|
|
viewportWidth_ = width;
|
|
|
|
|
viewportHeight_ = height;
|
2026-03-02 04:50:28 +08:00
|
|
|
|
|
|
|
|
viewportAdapter_.update(width, height);
|
|
|
|
|
|
|
|
|
|
auto result = viewportAdapter_.getResult();
|
|
|
|
|
glViewport(static_cast<GLint>(result.viewport.x),
|
|
|
|
|
static_cast<GLint>(result.viewport.y),
|
|
|
|
|
static_cast<GLsizei>(result.viewport.w),
|
|
|
|
|
static_cast<GLsizei>(result.viewport.h));
|
2026-03-02 00:25:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
void RendererModule::clear(const Color &color, uint32 flags) {
|
2026-03-02 00:25:14 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 04:50:28 +08:00
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
|
|
2026-03-02 00:25:14 +08:00
|
|
|
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++;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 04:50:28 +08:00
|
|
|
void RendererModule::onRenderSetCamera(const Mat4 &viewProj) {
|
|
|
|
|
if (!glInitialized_) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
viewProjectionMatrix_ = viewProj;
|
|
|
|
|
|
|
|
|
|
uniformManager_.updateGlobalUBO(&viewProj, sizeof(Mat4));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 00:25:14 +08:00
|
|
|
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() {
|
2026-03-02 22:44:42 +08:00
|
|
|
auto* assets = getAssets();
|
|
|
|
|
if (!assets) {
|
|
|
|
|
E2D_LOG_ERROR("AssetsModule not available");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MaterialHandle lastMaterial = MaterialHandle::invalid();
|
|
|
|
|
MeshHandle lastMesh = MeshHandle::invalid();
|
2026-03-02 00:25:14 +08:00
|
|
|
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) {
|
2026-03-02 22:44:42 +08:00
|
|
|
auto* assets = getAssets();
|
|
|
|
|
if (!assets) return;
|
|
|
|
|
|
|
|
|
|
Material* material = assets->get(materialHandle);
|
2026-03-02 04:50:28 +08:00
|
|
|
if (!material) {
|
2026-03-02 22:44:42 +08:00
|
|
|
material = assets->getDefaultMaterialPtr();
|
2026-03-02 04:50:28 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
Mesh* mesh = assets->get(meshHandle);
|
2026-03-02 04:50:28 +08:00
|
|
|
if (!mesh) {
|
2026-03-02 22:44:42 +08:00
|
|
|
mesh = assets->getDefaultQuadPtr();
|
2026-03-02 04:50:28 +08:00
|
|
|
}
|
2026-03-02 00:25:14 +08:00
|
|
|
|
|
|
|
|
if (!material || !mesh)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
Shader* shader = material->getShader().get();
|
2026-03-02 04:50:28 +08:00
|
|
|
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) {
|
2026-03-02 22:44:42 +08:00
|
|
|
if (slot.handle.isValid()) {
|
|
|
|
|
Texture* texture = assets->get(slot.handle);
|
2026-03-02 04:50:28 +08:00
|
|
|
if (texture) {
|
|
|
|
|
texture->bind(slot.slot);
|
|
|
|
|
shader->setInt(slot.uniformName, static_cast<int>(slot.slot));
|
|
|
|
|
hasMaterialTexture = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
if (!hasMaterialTexture) {
|
|
|
|
|
Texture* defaultTexture = assets->getDefaultTexturePtr();
|
2026-03-02 04:50:28 +08:00
|
|
|
if (defaultTexture) {
|
|
|
|
|
defaultTexture->bind(0);
|
|
|
|
|
shader->setInt("uTexture", 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-02 00:25:14 +08:00
|
|
|
|
|
|
|
|
mesh->bind();
|
|
|
|
|
|
2026-03-02 04:50:28 +08:00
|
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
|
|
|
Transform transform = commandBuffer_[start + i].getTransform();
|
|
|
|
|
float matrix[16];
|
|
|
|
|
transform.toMatrix(matrix);
|
2026-03-02 00:25:14 +08:00
|
|
|
|
2026-03-02 04:50:28 +08:00
|
|
|
shader->setMat4("uModelMatrix", matrix);
|
2026-03-02 00:25:14 +08:00
|
|
|
|
2026-03-02 04:50:28 +08:00
|
|
|
Color color = commandBuffer_[start + i].getColor();
|
|
|
|
|
shader->setVec4("uColor", color.r, color.g, color.b, color.a);
|
2026-03-02 00:25:14 +08:00
|
|
|
|
2026-03-02 04:50:28 +08:00
|
|
|
mesh->draw();
|
|
|
|
|
stats_.drawCalls++;
|
|
|
|
|
}
|
2026-03-02 00:25:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|