1087 lines
31 KiB
C++
1087 lines
31 KiB
C++
#include <cmath>
|
||
#include <cstring>
|
||
#include <extra2d/core/service_locator.h>
|
||
#include <extra2d/graphics/backends/opengl/gl_context.h>
|
||
#include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
|
||
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
|
||
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||
#include <extra2d/graphics/batch/sprite_batch.h>
|
||
#include <extra2d/graphics/memory/gpu_context.h>
|
||
#include <extra2d/graphics/memory/vram_manager.h>
|
||
#include <extra2d/graphics/shader/shader_manager.h>
|
||
#include <extra2d/platform/iwindow.h>
|
||
#include <extra2d/services/logger_service.h>
|
||
#include <vector>
|
||
|
||
namespace extra2d {
|
||
|
||
// VBO 初始大小(用于 VRAM 跟踪)
|
||
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float);
|
||
|
||
/**
|
||
* @brief 构造函数,初始化OpenGL渲染器成员变量
|
||
*/
|
||
GLRenderer::GLRenderer()
|
||
: window_(nullptr), shapeVao_(0), lineVao_(0), vsync_(true),
|
||
shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES),
|
||
lineVertexCount_(0), currentLineWidth_(1.0f) {
|
||
resetStats();
|
||
for (auto &v : shapeVertexCache_) {
|
||
v = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||
}
|
||
for (auto &v : lineVertexCache_) {
|
||
v = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 析构函数,调用shutdown释放资源
|
||
*/
|
||
GLRenderer::~GLRenderer() { shutdown(); }
|
||
|
||
/**
|
||
* @brief 初始化OpenGL渲染器
|
||
* @param window 窗口指针
|
||
* @return 初始化成功返回true,失败返回false
|
||
*/
|
||
bool GLRenderer::init(IWindow *window) {
|
||
window_ = window;
|
||
|
||
// 初始化 OpenGL 上下文(Switch 平台已通过 SDL2 + EGL 初始化,GLContext
|
||
// 会处理兼容性)
|
||
if (!GLContext::get().init()) {
|
||
E2D_LOG_ERROR("初始化 OpenGL 上下文失败");
|
||
return false;
|
||
}
|
||
|
||
// 初始化精灵批渲染器
|
||
if (!spriteBatch_.init()) {
|
||
E2D_LOG_ERROR("初始化精灵批处理失败");
|
||
return false;
|
||
}
|
||
|
||
// 初始化形状渲染
|
||
initShapeRendering();
|
||
|
||
// 初始化管线状态管理
|
||
PipelineDesc pipelineDesc;
|
||
pipelineDesc.blendMode = BlendMode::Alpha;
|
||
pipelineDesc.depthTest = false;
|
||
pipelineDesc.depthWrite = false;
|
||
if (!pipeline_.init(pipelineDesc)) {
|
||
E2D_LOG_ERROR("初始化 GLPipeline 失败");
|
||
return false;
|
||
}
|
||
|
||
// 应用初始管线状态
|
||
pipeline_.applyAllStates();
|
||
|
||
// 标记 GPU 上下文为有效
|
||
GPUContext::get().markValid();
|
||
|
||
E2D_LOG_INFO("OpenGL 渲染器初始化成功");
|
||
E2D_LOG_INFO("OpenGL 版本: {}", GLContext::get().getVersionString());
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 关闭渲染器,释放所有GPU资源
|
||
*/
|
||
void GLRenderer::shutdown() {
|
||
// 标记 GPU 上下文为无效
|
||
// 这会在销毁 OpenGL 上下文之前通知所有 GPU 资源
|
||
GPUContext::get().markInvalid();
|
||
|
||
spriteBatch_.shutdown();
|
||
|
||
// 关闭 GLBuffer(自动释放 VBO)
|
||
lineBuffer_.shutdown();
|
||
shapeBuffer_.shutdown();
|
||
|
||
// 删除 VAO(VAO 仍然手动管理)
|
||
if (lineVao_ != 0) {
|
||
glDeleteVertexArrays(1, &lineVao_);
|
||
lineVao_ = 0;
|
||
}
|
||
if (shapeVao_ != 0) {
|
||
glDeleteVertexArrays(1, &shapeVao_);
|
||
shapeVao_ = 0;
|
||
}
|
||
|
||
// 关闭 OpenGL 上下文
|
||
GLContext::get().shutdown();
|
||
}
|
||
|
||
/**
|
||
* @brief 开始新帧,清除颜色缓冲区并重置统计信息
|
||
* @param clearColor 清屏颜色
|
||
*/
|
||
void GLRenderer::beginFrame(const Color &clearColor) {
|
||
// 应用管线状态
|
||
pipeline_.applyAllStates();
|
||
|
||
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
||
glClear(GL_COLOR_BUFFER_BIT);
|
||
resetStats();
|
||
}
|
||
|
||
/**
|
||
* @brief 结束当前帧,刷新所有待处理的渲染批次
|
||
*/
|
||
void GLRenderer::endFrame() {
|
||
// 刷新所有待处理的精灵批次(自动批处理)
|
||
if (autoBatchEnabled_ && batchActive_) {
|
||
flush();
|
||
}
|
||
// 刷新所有待处理的形状批次
|
||
flushShapeBatch();
|
||
// 刷新所有待处理的线条批次
|
||
flushLineBatch();
|
||
}
|
||
|
||
/**
|
||
* @brief 设置视口区域
|
||
* @param x 视口左下角X坐标
|
||
* @param y 视口左下角Y坐标
|
||
* @param width 视口宽度
|
||
* @param height 视口高度
|
||
*/
|
||
void GLRenderer::setViewport(int x, int y, int width, int height) {
|
||
// 使用 GLPipeline 管理视口状态
|
||
pipeline_.setViewport(x, y, width, height);
|
||
}
|
||
|
||
/**
|
||
* @brief 设置垂直同步
|
||
* @param enabled true启用垂直同步,false禁用
|
||
*/
|
||
void GLRenderer::setVSync(bool enabled) {
|
||
vsync_ = enabled;
|
||
// 通过窗口接口设置垂直同步
|
||
if (window_) {
|
||
window_->setVSync(enabled);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 设置混合模式
|
||
* @param mode 混合模式枚举值
|
||
*/
|
||
void GLRenderer::setBlendMode(BlendMode mode) {
|
||
// 使用 GLPipeline 管理混合状态
|
||
pipeline_.setBlendMode(mode);
|
||
}
|
||
|
||
/**
|
||
* @brief 设置视图投影矩阵
|
||
* @param matrix 4x4视图投影矩阵
|
||
*/
|
||
void GLRenderer::setViewProjection(const glm::mat4 &matrix) {
|
||
viewProjection_ = matrix;
|
||
}
|
||
|
||
/**
|
||
* @brief 压入变换矩阵到变换栈
|
||
* @param transform 变换矩阵
|
||
*/
|
||
void GLRenderer::pushTransform(const glm::mat4 &transform) {
|
||
if (transformStack_.empty()) {
|
||
transformStack_.push_back(transform);
|
||
} else {
|
||
transformStack_.push_back(transformStack_.back() * transform);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 从变换栈弹出顶部变换矩阵
|
||
*/
|
||
void GLRenderer::popTransform() {
|
||
if (!transformStack_.empty()) {
|
||
transformStack_.pop_back();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 获取当前累积的变换矩阵
|
||
* @return 当前变换矩阵,如果栈为空则返回单位矩阵
|
||
*/
|
||
glm::mat4 GLRenderer::getCurrentTransform() const {
|
||
if (transformStack_.empty()) {
|
||
return glm::mat4(1.0f);
|
||
}
|
||
return transformStack_.back();
|
||
}
|
||
|
||
/**
|
||
* @brief 创建纹理对象
|
||
* @param width 纹理宽度
|
||
* @param height 纹理高度
|
||
* @param pixels 像素数据指针
|
||
* @param channels 颜色通道数
|
||
* @return 创建的纹理智能指针
|
||
*/
|
||
Ptr<Texture> GLRenderer::createTexture(int width, int height,
|
||
const uint8_t *pixels, int channels) {
|
||
return makePtr<GLTexture>(width, height, pixels, channels);
|
||
}
|
||
|
||
/**
|
||
* @brief 从文件加载纹理
|
||
* @param filepath 纹理文件路径
|
||
* @return 加载的纹理智能指针
|
||
*/
|
||
Ptr<Texture> GLRenderer::loadTexture(const std::string &filepath) {
|
||
return makePtr<GLTexture>(filepath);
|
||
}
|
||
|
||
/**
|
||
* @brief 确保批处理已激活(自动批处理内部使用)
|
||
*/
|
||
void GLRenderer::ensureBatchActive() {
|
||
if (!batchActive_) {
|
||
spriteBatch_.begin(viewProjection_);
|
||
batchActive_ = true;
|
||
currentBatchTexture_ = nullptr;
|
||
pendingSprites_.clear();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 提交待处理的精灵(自动批处理内部使用)
|
||
*/
|
||
void GLRenderer::submitPendingSprites() {
|
||
if (pendingSprites_.empty()) {
|
||
return;
|
||
}
|
||
|
||
// 提交所有待处理的精灵
|
||
spriteBatch_.drawBatch(*currentBatchTexture_, pendingSprites_);
|
||
pendingSprites_.clear();
|
||
currentBatchTexture_ = nullptr;
|
||
}
|
||
|
||
/**
|
||
* @brief 开始手动精灵批处理(高级用法)
|
||
* @note 一般情况下不需要调用,drawSprite/drawText 会自动管理批处理
|
||
*/
|
||
void GLRenderer::beginSpriteBatch() {
|
||
// 如果自动批处理已激活,先提交
|
||
if (autoBatchEnabled_ && batchActive_) {
|
||
flush();
|
||
}
|
||
// 禁用自动批处理,进入手动模式
|
||
autoBatchEnabled_ = false;
|
||
spriteBatch_.begin(viewProjection_);
|
||
batchActive_ = true;
|
||
}
|
||
|
||
/**
|
||
* @brief 绘制精灵(带完整参数)
|
||
* @param texture 纹理引用
|
||
* @param destRect 目标矩形(屏幕坐标)
|
||
* @param srcRect 源矩形(纹理坐标)
|
||
* @param tint 着色颜色
|
||
* @param rotation 旋转角度(度)
|
||
* @param anchor 锚点位置(0-1范围)
|
||
*/
|
||
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
||
const Rect &srcRect, const Color &tint,
|
||
float rotation, const Vec2 &anchor) {
|
||
// 自动批处理模式
|
||
if (autoBatchEnabled_) {
|
||
ensureBatchActive();
|
||
|
||
// 如果纹理变化或缓冲区满,先提交当前批次
|
||
if (currentBatchTexture_ != &texture ||
|
||
pendingSprites_.size() >= MAX_BATCH_SPRITES) {
|
||
submitPendingSprites();
|
||
currentBatchTexture_ = &texture;
|
||
}
|
||
|
||
// 创建精灵数据
|
||
SpriteData data;
|
||
data.position = Vec2(destRect.origin.x, destRect.origin.y);
|
||
data.size = Vec2(destRect.size.width, destRect.size.height);
|
||
|
||
Texture *tex = const_cast<Texture *>(&texture);
|
||
float texW = static_cast<float>(tex->getWidth());
|
||
float texH = static_cast<float>(tex->getHeight());
|
||
|
||
// 纹理坐标计算
|
||
float u1 = srcRect.origin.x / texW;
|
||
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
|
||
float v1 = srcRect.origin.y / texH;
|
||
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
||
|
||
data.uvRect = Rect(Vec2(glm::min(u1, u2), glm::min(v1, v2)),
|
||
Size(glm::abs(u2 - u1), glm::abs(v2 - v1)));
|
||
|
||
data.color = tint;
|
||
data.rotation = rotation * 3.14159f / 180.0f;
|
||
data.pivot = Vec2(anchor.x, anchor.y);
|
||
|
||
// 添加到待处理列表
|
||
pendingSprites_.push_back(data);
|
||
} else {
|
||
// 手动批处理模式
|
||
SpriteData data;
|
||
data.position = Vec2(destRect.origin.x, destRect.origin.y);
|
||
data.size = Vec2(destRect.size.width, destRect.size.height);
|
||
|
||
Texture *tex = const_cast<Texture *>(&texture);
|
||
float texW = static_cast<float>(tex->getWidth());
|
||
float texH = static_cast<float>(tex->getHeight());
|
||
|
||
float u1 = srcRect.origin.x / texW;
|
||
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
|
||
float v1 = srcRect.origin.y / texH;
|
||
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
||
|
||
data.uvRect = Rect(Vec2(glm::min(u1, u2), glm::min(v1, v2)),
|
||
Size(glm::abs(u2 - u1), glm::abs(v2 - v1)));
|
||
|
||
data.color = tint;
|
||
data.rotation = rotation * 3.14159f / 180.0f;
|
||
data.pivot = Vec2(anchor.x, anchor.y);
|
||
|
||
spriteBatch_.draw(texture, data);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 绘制精灵(简化版本)
|
||
* @param texture 纹理引用
|
||
* @param position 绘制位置
|
||
* @param tint 着色颜色
|
||
*/
|
||
void GLRenderer::drawSprite(const Texture &texture, const Vec2 &position,
|
||
const Color &tint) {
|
||
Rect destRect(position.x, position.y, static_cast<float>(texture.getWidth()),
|
||
static_cast<float>(texture.getHeight()));
|
||
Rect srcRect(0, 0, static_cast<float>(texture.getWidth()),
|
||
static_cast<float>(texture.getHeight()));
|
||
drawSprite(texture, destRect, srcRect, tint, 0.0f, Vec2(0, 0));
|
||
}
|
||
|
||
/**
|
||
* @brief 结束手动精灵批处理并提交绘制
|
||
* @note 一般情况下不需要调用
|
||
*/
|
||
void GLRenderer::endSpriteBatch() {
|
||
if (autoBatchEnabled_) {
|
||
// 自动模式下,只是标记批处理结束
|
||
flush();
|
||
} else {
|
||
// 手动模式下,提交批处理并恢复自动模式
|
||
spriteBatch_.end();
|
||
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
||
batchActive_ = false;
|
||
autoBatchEnabled_ = true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 立即提交当前批处理
|
||
* @note 手动控制批处理提交时机,一般情况下不需要调用
|
||
*/
|
||
void GLRenderer::flush() {
|
||
if (autoBatchEnabled_ && batchActive_) {
|
||
submitPendingSprites();
|
||
spriteBatch_.end();
|
||
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
||
batchActive_ = false;
|
||
currentBatchTexture_ = nullptr;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 绘制线段
|
||
* @param start 起点坐标
|
||
* @param end 终点坐标
|
||
* @param color 线条颜色
|
||
* @param width 线条宽度
|
||
*/
|
||
void GLRenderer::drawLine(const Vec2 &start, const Vec2 &end,
|
||
const Color &color, float width) {
|
||
// 如果线宽改变,需要先刷新线条批次
|
||
if (width != currentLineWidth_) {
|
||
flushLineBatch();
|
||
currentLineWidth_ = width;
|
||
}
|
||
|
||
// 添加两个顶点到线条缓冲区
|
||
addLineVertex(start.x, start.y, color);
|
||
addLineVertex(end.x, end.y, color);
|
||
}
|
||
|
||
void GLRenderer::drawRect(const Rect &rect, const Color &color, float width) {
|
||
// 如果线宽改变,需要先刷新线条批次
|
||
if (width != currentLineWidth_) {
|
||
flushLineBatch();
|
||
currentLineWidth_ = width;
|
||
}
|
||
|
||
float x1 = rect.origin.x;
|
||
float y1 = rect.origin.y;
|
||
float x2 = rect.origin.x + rect.size.width;
|
||
float y2 = rect.origin.y + rect.size.height;
|
||
|
||
// 4条线段 = 8个顶点
|
||
// 上边
|
||
addLineVertex(x1, y1, color);
|
||
addLineVertex(x2, y1, color);
|
||
// 右边
|
||
addLineVertex(x2, y1, color);
|
||
addLineVertex(x2, y2, color);
|
||
// 下边
|
||
addLineVertex(x2, y2, color);
|
||
addLineVertex(x1, y2, color);
|
||
// 左边
|
||
addLineVertex(x1, y2, color);
|
||
addLineVertex(x1, y1, color);
|
||
}
|
||
|
||
/**
|
||
* @brief 填充矩形
|
||
* @param rect 矩形区域
|
||
* @param color 填充颜色
|
||
*/
|
||
void GLRenderer::fillRect(const Rect &rect, const Color &color) {
|
||
// 提交当前批次(如果模式不同)
|
||
submitShapeBatch(GL_TRIANGLES);
|
||
|
||
// 添加两个三角形组成矩形(6个顶点)
|
||
float x1 = rect.origin.x;
|
||
float y1 = rect.origin.y;
|
||
float x2 = rect.origin.x + rect.size.width;
|
||
float y2 = rect.origin.y + rect.size.height;
|
||
|
||
// 三角形1: (x1,y1), (x2,y1), (x2,y2)
|
||
addShapeVertex(x1, y1, color);
|
||
addShapeVertex(x2, y1, color);
|
||
addShapeVertex(x2, y2, color);
|
||
|
||
// 三角形2: (x1,y1), (x2,y2), (x1,y2)
|
||
addShapeVertex(x1, y1, color);
|
||
addShapeVertex(x2, y2, color);
|
||
addShapeVertex(x1, y2, color);
|
||
}
|
||
|
||
/**
|
||
* @brief 绘制圆形边框
|
||
* @param center 圆心坐标
|
||
* @param radius 半径
|
||
* @param color 边框颜色
|
||
* @param segments 分段数
|
||
* @param width 线条宽度
|
||
*/
|
||
void GLRenderer::drawCircle(const Vec2 ¢er, float radius,
|
||
const Color &color, int segments, float width) {
|
||
// 限制段数不超过缓存大小
|
||
if (segments > static_cast<int>(MAX_CIRCLE_SEGMENTS)) {
|
||
segments = static_cast<int>(MAX_CIRCLE_SEGMENTS);
|
||
}
|
||
|
||
// 如果线宽改变,需要先刷新线条批次
|
||
if (width != currentLineWidth_) {
|
||
flushLineBatch();
|
||
currentLineWidth_ = width;
|
||
}
|
||
|
||
// 使用线条批处理绘制圆形
|
||
for (int i = 0; i < segments; ++i) {
|
||
float angle1 =
|
||
2.0f * 3.14159f * static_cast<float>(i) / static_cast<float>(segments);
|
||
float angle2 = 2.0f * 3.14159f * static_cast<float>(i + 1) /
|
||
static_cast<float>(segments);
|
||
|
||
addLineVertex(center.x + radius * cosf(angle1),
|
||
center.y + radius * sinf(angle1), color);
|
||
addLineVertex(center.x + radius * cosf(angle2),
|
||
center.y + radius * sinf(angle2), color);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 填充圆形
|
||
* @param center 圆心坐标
|
||
* @param radius 半径
|
||
* @param color 填充颜色
|
||
* @param segments 分段数
|
||
*/
|
||
void GLRenderer::fillCircle(const Vec2 ¢er, float radius,
|
||
const Color &color, int segments) {
|
||
// 限制段数不超过缓存大小
|
||
if (segments > static_cast<int>(MAX_CIRCLE_SEGMENTS)) {
|
||
segments = static_cast<int>(MAX_CIRCLE_SEGMENTS);
|
||
}
|
||
|
||
// 提交当前批次(如果模式不同)
|
||
submitShapeBatch(GL_TRIANGLES);
|
||
|
||
// 使用三角形扇形填充圆
|
||
// 中心点 + 边缘点
|
||
for (int i = 0; i < segments; ++i) {
|
||
float angle1 =
|
||
2.0f * 3.14159f * static_cast<float>(i) / static_cast<float>(segments);
|
||
float angle2 = 2.0f * 3.14159f * static_cast<float>(i + 1) /
|
||
static_cast<float>(segments);
|
||
|
||
// 每个三角形:中心 -> 边缘点1 -> 边缘点2
|
||
addShapeVertex(center.x, center.y, color);
|
||
addShapeVertex(center.x + radius * cosf(angle1),
|
||
center.y + radius * sinf(angle1), color);
|
||
addShapeVertex(center.x + radius * cosf(angle2),
|
||
center.y + radius * sinf(angle2), color);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 绘制三角形边框
|
||
* @param p1 第一个顶点
|
||
* @param p2 第二个顶点
|
||
* @param p3 第三个顶点
|
||
* @param color 边框颜色
|
||
* @param width 线条宽度
|
||
*/
|
||
void GLRenderer::drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||
const Color &color, float width) {
|
||
drawLine(p1, p2, color, width);
|
||
drawLine(p2, p3, color, width);
|
||
drawLine(p3, p1, color, width);
|
||
}
|
||
|
||
/**
|
||
* @brief 填充三角形
|
||
* @param p1 第一个顶点
|
||
* @param p2 第二个顶点
|
||
* @param p3 第三个顶点
|
||
* @param color 填充颜色
|
||
*/
|
||
void GLRenderer::fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||
const Color &color) {
|
||
submitShapeBatch(GL_TRIANGLES);
|
||
|
||
addShapeVertex(p1.x, p1.y, color);
|
||
addShapeVertex(p2.x, p2.y, color);
|
||
addShapeVertex(p3.x, p3.y, color);
|
||
}
|
||
|
||
/**
|
||
* @brief 绘制多边形边框
|
||
* @param points 顶点数组
|
||
* @param color 边框颜色
|
||
* @param width 线条宽度
|
||
*/
|
||
void GLRenderer::drawPolygon(const std::vector<Vec2> &points,
|
||
const Color &color, float width) {
|
||
if (points.size() < 2)
|
||
return;
|
||
|
||
// 如果线宽改变,需要先刷新线条批次
|
||
if (width != currentLineWidth_) {
|
||
flushLineBatch();
|
||
currentLineWidth_ = width;
|
||
}
|
||
|
||
// 绘制所有边
|
||
for (size_t i = 0; i < points.size(); ++i) {
|
||
const Vec2 &p1 = points[i];
|
||
const Vec2 &p2 = points[(i + 1) % points.size()];
|
||
addLineVertex(p1.x, p1.y, color);
|
||
addLineVertex(p2.x, p2.y, color);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 填充多边形
|
||
* @param points 顶点数组
|
||
* @param color 填充颜色
|
||
*/
|
||
void GLRenderer::fillPolygon(const std::vector<Vec2> &points,
|
||
const Color &color) {
|
||
if (points.size() < 3)
|
||
return;
|
||
|
||
submitShapeBatch(GL_TRIANGLES);
|
||
|
||
// 使用三角形扇形填充
|
||
// 从第一个点开始,每两个相邻点组成一个三角形
|
||
for (size_t i = 1; i < points.size() - 1; ++i) {
|
||
addShapeVertex(points[0].x, points[0].y, color);
|
||
addShapeVertex(points[i].x, points[i].y, color);
|
||
addShapeVertex(points[i + 1].x, points[i + 1].y, color);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 创建字体图集
|
||
* @param filepath 字体文件路径
|
||
* @param fontSize 字体大小
|
||
* @param useSDF 是否使用SDF渲染
|
||
* @return 创建的字体图集智能指针
|
||
*/
|
||
Ptr<FontAtlas> GLRenderer::createFontAtlas(const std::string &filepath,
|
||
int fontSize, bool useSDF) {
|
||
return makePtr<GLFontAtlas>(filepath, fontSize, useSDF);
|
||
}
|
||
|
||
/**
|
||
* @brief 绘制文本(使用Vec2位置)
|
||
* @param font 字体图集引用
|
||
* @param text 文本内容
|
||
* @param position 绘制位置
|
||
* @param color 文本颜色
|
||
*/
|
||
void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||
const Vec2 &position, const Color &color) {
|
||
drawText(font, text, position.x, position.y, color);
|
||
}
|
||
|
||
/**
|
||
* @brief 绘制文本(使用浮点坐标)
|
||
* @param font 字体图集引用
|
||
* @param text 文本内容
|
||
* @param x X坐标
|
||
* @param y Y坐标
|
||
* @param color 文本颜色
|
||
*/
|
||
void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||
float x, float y, const Color &color) {
|
||
float cursorX = x;
|
||
float cursorY = y;
|
||
float baselineY = cursorY + font.getAscent();
|
||
|
||
// 检查是否为SDF字体
|
||
bool isSDF = font.isSDF();
|
||
|
||
// 如果是SDF字体,切换到SDF着色器
|
||
if (isSDF && sdfFontShader_) {
|
||
// 先提交当前普通批处理
|
||
if (autoBatchEnabled_ && !pendingSprites_.empty()) {
|
||
submitPendingSprites();
|
||
}
|
||
|
||
// 使用SDF着色器开始新的批处理
|
||
// 设置需要动态计算的uniform值
|
||
UniformValueMap sdfUniformValues;
|
||
sdfUniformValues["u_viewProjection"] = viewProjection_;
|
||
if (font.getTexture()) {
|
||
sdfUniformValues["u_textureSize"] = ShaderUniformValue(
|
||
glm::vec2(static_cast<float>(font.getTexture()->getWidth()),
|
||
static_cast<float>(font.getTexture()->getHeight())));
|
||
}
|
||
// 设置额外的uniform值,让submitBatch使用
|
||
spriteBatch_.setExtraUniforms(sdfUniformValues);
|
||
spriteBatch_.begin(viewProjection_, sdfFontShader_);
|
||
} else {
|
||
// 确保批处理已激活(自动批处理)
|
||
if (autoBatchEnabled_) {
|
||
ensureBatchActive();
|
||
}
|
||
|
||
// 检查纹理变化,如果纹理不同则先提交当前批次
|
||
if (autoBatchEnabled_ && currentBatchTexture_ != nullptr &&
|
||
currentBatchTexture_ != font.getTexture()) {
|
||
submitPendingSprites();
|
||
}
|
||
if (autoBatchEnabled_) {
|
||
currentBatchTexture_ = font.getTexture();
|
||
}
|
||
}
|
||
|
||
// 收集所有字符数据用于批处理
|
||
std::vector<SpriteData> sprites;
|
||
sprites.reserve(text.size()); // 预分配空间
|
||
|
||
for (char c : text) {
|
||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||
if (codepoint == '\n') {
|
||
// 换行时,将当前行添加到待处理列表
|
||
if (!sprites.empty()) {
|
||
if (isSDF && sdfFontShader_) {
|
||
// SDF模式直接提交
|
||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||
} else if (autoBatchEnabled_) {
|
||
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||
sprites.end());
|
||
} else {
|
||
// 手动模式直接提交
|
||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||
}
|
||
sprites.clear();
|
||
}
|
||
cursorX = x;
|
||
cursorY += font.getLineHeight();
|
||
baselineY = cursorY + font.getAscent();
|
||
continue;
|
||
}
|
||
|
||
const Glyph *glyph = font.getGlyph(codepoint);
|
||
if (glyph) {
|
||
float penX = cursorX;
|
||
cursorX += glyph->advance;
|
||
|
||
// 使用 epsilon 比较浮点数,避免精度问题
|
||
constexpr float EPSILON = 0.001f;
|
||
if (glyph->width < EPSILON || glyph->height < EPSILON) {
|
||
continue;
|
||
}
|
||
|
||
// 计算字形位置
|
||
// bearingX: 水平偏移(从左边缘到字形左边缘)
|
||
// bearingY: 垂直偏移(从基线到字形顶部,通常为负值)
|
||
float xPos = penX + glyph->bearingX;
|
||
float yPos = baselineY + glyph->bearingY;
|
||
|
||
SpriteData data;
|
||
// 设置精灵中心位置(精灵批处理使用中心点)
|
||
data.position =
|
||
Vec2(xPos + glyph->width * 0.5f, yPos + glyph->height * 0.5f);
|
||
data.size = Vec2(glyph->width, glyph->height);
|
||
data.uvRect = Rect(Vec2(glyph->u0, glyph->v0),
|
||
Size(glyph->u1 - glyph->u0, glyph->v1 - glyph->v0));
|
||
data.color = color;
|
||
data.rotation = 0.0f;
|
||
// pivot (0.5, 0.5) 表示中心点,这样 position 就是精灵中心
|
||
data.pivot = Vec2(0.5f, 0.5f);
|
||
|
||
sprites.push_back(data);
|
||
|
||
// 自动批处理:如果缓冲区满,先提交当前批次
|
||
if (!isSDF && autoBatchEnabled_ && sprites.size() >= MAX_BATCH_SPRITES) {
|
||
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||
sprites.end());
|
||
sprites.clear();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 提交剩余的字符
|
||
if (!sprites.empty()) {
|
||
if (isSDF && sdfFontShader_) {
|
||
// SDF模式直接提交
|
||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||
} else if (autoBatchEnabled_) {
|
||
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||
sprites.end());
|
||
} else {
|
||
// 手动模式下直接提交
|
||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||
}
|
||
}
|
||
|
||
// 如果是SDF字体,结束批处理并恢复普通着色器
|
||
if (isSDF && sdfFontShader_) {
|
||
spriteBatch_.end();
|
||
// 清除额外的uniform值
|
||
spriteBatch_.clearExtraUniforms();
|
||
// 恢复默认的sprite着色器
|
||
auto defaultShader = ShaderManager::getInstance().getBuiltin("sprite");
|
||
if (defaultShader) {
|
||
spriteBatch_.setShader(defaultShader);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 重置渲染统计信息
|
||
*/
|
||
void GLRenderer::resetStats() { stats_ = Stats{}; }
|
||
|
||
/**
|
||
* @brief 初始化形状渲染所需的OpenGL资源(VAO、VBO、着色器)
|
||
*/
|
||
void GLRenderer::initShapeRendering() {
|
||
// 从ShaderManager获取形状着色器
|
||
shapeShader_ = ShaderManager::getInstance().getBuiltin("shape");
|
||
if (!shapeShader_) {
|
||
E2D_LOG_WARN("获取内置形状着色器失败,尝试从管理器加载");
|
||
if (!ShaderManager::getInstance().isInitialized()) {
|
||
E2D_LOG_ERROR("ShaderManager 未初始化,形状渲染可能失败");
|
||
}
|
||
}
|
||
|
||
// 加载SDF字体着色器
|
||
sdfFontShader_ = ShaderManager::getInstance().getBuiltin("sdf_font");
|
||
if (!sdfFontShader_) {
|
||
E2D_LOG_WARN("获取SDF字体着色器失败,SDF字体将使用普通渲染");
|
||
} else {
|
||
E2D_LOG_INFO("SDF字体着色器加载成功");
|
||
}
|
||
|
||
// 初始化形状 GLBuffer(使用 Dynamic 使用模式,因为每帧都会更新)
|
||
BufferDesc shapeBufferDesc;
|
||
shapeBufferDesc.type = BufferType::Vertex;
|
||
shapeBufferDesc.usage = BufferUsage::Dynamic;
|
||
shapeBufferDesc.size = MAX_SHAPE_VERTICES * sizeof(ShapeVertex);
|
||
if (!shapeBuffer_.init(shapeBufferDesc)) {
|
||
E2D_LOG_ERROR("初始化形状缓冲区失败");
|
||
}
|
||
|
||
// 创建形状 VAO(手动管理,用于顶点属性配置)
|
||
glGenVertexArrays(1, &shapeVao_);
|
||
glBindVertexArray(shapeVao_);
|
||
shapeBuffer_.bind();
|
||
|
||
// 位置属性 (location = 0)
|
||
glEnableVertexAttribArray(0);
|
||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
||
reinterpret_cast<void *>(offsetof(ShapeVertex, x)));
|
||
|
||
// 颜色属性 (location = 1)
|
||
glEnableVertexAttribArray(1);
|
||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
||
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
|
||
|
||
glBindVertexArray(0);
|
||
|
||
// 初始化线条 GLBuffer(使用 Dynamic 使用模式,因为每帧都会更新)
|
||
BufferDesc lineBufferDesc;
|
||
lineBufferDesc.type = BufferType::Vertex;
|
||
lineBufferDesc.usage = BufferUsage::Dynamic;
|
||
lineBufferDesc.size = MAX_LINE_VERTICES * sizeof(ShapeVertex);
|
||
if (!lineBuffer_.init(lineBufferDesc)) {
|
||
E2D_LOG_ERROR("初始化线条缓冲区失败");
|
||
}
|
||
|
||
// 创建线条 VAO(手动管理,用于顶点属性配置)
|
||
glGenVertexArrays(1, &lineVao_);
|
||
glBindVertexArray(lineVao_);
|
||
lineBuffer_.bind();
|
||
|
||
// 位置属性 (location = 0)
|
||
glEnableVertexAttribArray(0);
|
||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
||
reinterpret_cast<void *>(offsetof(ShapeVertex, x)));
|
||
|
||
// 颜色属性 (location = 1)
|
||
glEnableVertexAttribArray(1);
|
||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex),
|
||
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
|
||
|
||
glBindVertexArray(0);
|
||
}
|
||
|
||
/**
|
||
* @brief 添加形状顶点到缓存
|
||
* @param x X坐标
|
||
* @param y Y坐标
|
||
* @param color 顶点颜色
|
||
*/
|
||
void GLRenderer::addShapeVertex(float x, float y, const Color &color) {
|
||
if (shapeVertexCount_ >= MAX_SHAPE_VERTICES) {
|
||
flushShapeBatch();
|
||
}
|
||
|
||
glm::vec4 pos(x, y, 0.0f, 1.0f);
|
||
if (!transformStack_.empty()) {
|
||
pos = transformStack_.back() * pos;
|
||
}
|
||
|
||
ShapeVertex &v = shapeVertexCache_[shapeVertexCount_++];
|
||
v.x = pos.x;
|
||
v.y = pos.y;
|
||
v.r = color.r;
|
||
v.g = color.g;
|
||
v.b = color.b;
|
||
v.a = color.a;
|
||
}
|
||
|
||
/**
|
||
* @brief 添加线条顶点到缓存
|
||
* @param x X坐标
|
||
* @param y Y坐标
|
||
* @param color 顶点颜色
|
||
*/
|
||
void GLRenderer::addLineVertex(float x, float y, const Color &color) {
|
||
if (lineVertexCount_ >= MAX_LINE_VERTICES) {
|
||
flushLineBatch();
|
||
}
|
||
|
||
glm::vec4 pos(x, y, 0.0f, 1.0f);
|
||
if (!transformStack_.empty()) {
|
||
pos = transformStack_.back() * pos;
|
||
}
|
||
|
||
ShapeVertex &v = lineVertexCache_[lineVertexCount_++];
|
||
v.x = pos.x;
|
||
v.y = pos.y;
|
||
v.r = color.r;
|
||
v.g = color.g;
|
||
v.b = color.b;
|
||
v.a = color.a;
|
||
}
|
||
|
||
/**
|
||
* @brief 提交形状批次(如果需要切换绘制模式)
|
||
* @param mode OpenGL绘制模式
|
||
*/
|
||
void GLRenderer::submitShapeBatch(GLenum mode) {
|
||
if (shapeVertexCount_ == 0)
|
||
return;
|
||
|
||
// 如果模式改变,先刷新
|
||
if (currentShapeMode_ != mode && shapeVertexCount_ > 0) {
|
||
flushShapeBatch();
|
||
}
|
||
currentShapeMode_ = mode;
|
||
}
|
||
|
||
/**
|
||
* @brief 刷新形状批次,执行实际的OpenGL绘制调用
|
||
*/
|
||
void GLRenderer::flushShapeBatch() {
|
||
if (shapeVertexCount_ == 0)
|
||
return;
|
||
|
||
if (shapeShader_) {
|
||
shapeShader_->bind();
|
||
|
||
// 只提供需要动态计算的值,其他值使用JSON中定义的默认值
|
||
UniformValueMap uniformValues;
|
||
uniformValues["u_viewProjection"] = viewProjection_;
|
||
|
||
// 使用ShaderManager自动应用uniform值(未提供的值使用JSON中的默认值)
|
||
// 使用着色器自己的名称(从JSON中解析的name字段)
|
||
ShaderManager::getInstance().applyUniforms(
|
||
shapeShader_, shapeShader_->getName(), uniformValues);
|
||
}
|
||
|
||
// 使用 GLBuffer::updateData() 更新缓冲区数据
|
||
shapeBuffer_.updateData(shapeVertexCache_.data(), 0,
|
||
shapeVertexCount_ * sizeof(ShapeVertex));
|
||
|
||
glBindVertexArray(shapeVao_);
|
||
glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_));
|
||
|
||
stats_.drawCalls++;
|
||
stats_.triangleCount += static_cast<uint32_t>(shapeVertexCount_ / 3);
|
||
|
||
shapeVertexCount_ = 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 刷新线条批次,执行实际的OpenGL绘制调用
|
||
*/
|
||
void GLRenderer::flushLineBatch() {
|
||
if (lineVertexCount_ == 0)
|
||
return;
|
||
|
||
// 先刷新形状批次
|
||
flushShapeBatch();
|
||
|
||
glLineWidth(currentLineWidth_);
|
||
if (shapeShader_) {
|
||
shapeShader_->bind();
|
||
|
||
// 只提供需要动态计算的值,其他值使用JSON中定义的默认值
|
||
UniformValueMap uniformValues;
|
||
uniformValues["u_viewProjection"] = viewProjection_;
|
||
|
||
// 使用ShaderManager自动应用uniform值(未提供的值使用JSON中的默认值)
|
||
// 使用着色器自己的名称(从JSON中解析的name字段)
|
||
ShaderManager::getInstance().applyUniforms(
|
||
shapeShader_, shapeShader_->getName(), uniformValues);
|
||
}
|
||
|
||
// 使用 GLBuffer::updateData() 更新缓冲区数据
|
||
lineBuffer_.updateData(lineVertexCache_.data(), 0,
|
||
lineVertexCount_ * sizeof(ShapeVertex));
|
||
|
||
glBindVertexArray(lineVao_);
|
||
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_));
|
||
|
||
stats_.drawCalls++;
|
||
|
||
lineVertexCount_ = 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 创建帧缓冲对象
|
||
* @param desc 帧缓冲描述
|
||
* @return 创建的帧缓冲智能指针
|
||
*/
|
||
Ptr<GLFramebuffer> GLRenderer::createFramebuffer(const FramebufferDesc &desc) {
|
||
auto framebuffer = makePtr<GLFramebuffer>();
|
||
if (!framebuffer->init(desc)) {
|
||
E2D_LOG_ERROR("创建帧缓冲区失败");
|
||
return nullptr;
|
||
}
|
||
return framebuffer;
|
||
}
|
||
|
||
/**
|
||
* @brief 绑定帧缓冲(作为渲染目标)
|
||
* @param framebuffer 帧缓冲对象指针,传入 nullptr 则绑定默认帧缓冲
|
||
*/
|
||
void GLRenderer::bindFramebuffer(GLFramebuffer *framebuffer) {
|
||
// 先刷新所有待处理的渲染批次
|
||
flush();
|
||
flushShapeBatch();
|
||
flushLineBatch();
|
||
|
||
if (framebuffer == nullptr) {
|
||
// 绑定默认帧缓冲(ID 为 0)
|
||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||
currentFramebuffer_ = nullptr;
|
||
E2D_LOG_TRACE("绑定默认帧缓冲区 (0)");
|
||
} else {
|
||
// 绑定自定义帧缓冲
|
||
framebuffer->bind();
|
||
currentFramebuffer_ = framebuffer;
|
||
E2D_LOG_TRACE("绑定自定义帧缓冲区 (ID: {})", framebuffer->getFboID());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 解绑帧缓冲(恢复到默认帧缓冲)
|
||
*/
|
||
void GLRenderer::unbindFramebuffer() { bindFramebuffer(nullptr); }
|
||
|
||
/**
|
||
* @brief 获取默认帧缓冲
|
||
* @return 默认帧缓冲智能指针
|
||
*/
|
||
Ptr<GLFramebuffer> GLRenderer::getDefaultFramebuffer() const {
|
||
if (!defaultFramebuffer_) {
|
||
// 延迟创建默认帧缓冲对象(代表系统默认帧缓冲,ID 为 0)
|
||
defaultFramebuffer_ = makePtr<GLFramebuffer>();
|
||
// 注意:默认帧缓冲不需要显式初始化,它的 FBO ID 为 0
|
||
}
|
||
return defaultFramebuffer_;
|
||
}
|
||
|
||
/**
|
||
* @brief 清除当前绑定的帧缓冲
|
||
* @param color 清除颜色
|
||
* @param clearColor 是否清除颜色缓冲
|
||
* @param clearDepth 是否清除深度缓冲
|
||
* @param clearStencil 是否清除模板缓冲
|
||
*/
|
||
void GLRenderer::clearFramebuffer(const Color &color, bool clearColor,
|
||
bool clearDepth, bool clearStencil) {
|
||
GLbitfield mask = 0;
|
||
|
||
if (clearColor) {
|
||
glClearColor(color.r, color.g, color.b, color.a);
|
||
mask |= GL_COLOR_BUFFER_BIT;
|
||
}
|
||
|
||
if (clearDepth) {
|
||
mask |= GL_DEPTH_BUFFER_BIT;
|
||
}
|
||
|
||
if (clearStencil) {
|
||
mask |= GL_STENCIL_BUFFER_BIT;
|
||
}
|
||
|
||
if (mask != 0) {
|
||
glClear(mask);
|
||
}
|
||
}
|
||
|
||
} // namespace extra2d
|