diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h b/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h index eaa7dd9..a3e8d65 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h @@ -34,9 +34,9 @@ public: bool isSDF() const override { return useSDF_; } private: - // 图集配置 - static constexpr int ATLAS_WIDTH = 512; - static constexpr int ATLAS_HEIGHT = 512; + // 图集配置 - 增大尺寸以支持更多字符 + static constexpr int ATLAS_WIDTH = 1024; + static constexpr int ATLAS_HEIGHT = 1024; static constexpr int PADDING = 2; // 字形之间的间距 int fontSize_; @@ -55,6 +55,10 @@ private: float ascent_; float descent_; float lineGap_; + + // 预分配字形位图缓冲区,避免每次动态分配 + mutable std::vector glyphBitmapCache_; + mutable std::vector glyphRgbaCache_; void createAtlas(); void cacheGlyph(char32_t codepoint) const; diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h index c925a09..5372881 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h @@ -78,6 +78,10 @@ private: const Texture *currentTexture_; bool currentIsSDF_; glm::mat4 viewProjection_; + + // 缓存上一帧的 viewProjection,避免重复设置 + glm::mat4 cachedViewProjection_; + bool viewProjectionDirty_ = true; uint32_t drawCallCount_; uint32_t spriteCount_; diff --git a/Extra2D/src/graphics/opengl/gl_font_atlas.cpp b/Extra2D/src/graphics/opengl/gl_font_atlas.cpp index 2b03baa..6fd4dd4 100644 --- a/Extra2D/src/graphics/opengl/gl_font_atlas.cpp +++ b/Extra2D/src/graphics/opengl/gl_font_atlas.cpp @@ -97,7 +97,8 @@ Vec2 GLFontAtlas::measureText(const std::string &text) { // 创建图集纹理 - 初始化空白纹理和矩形打包上下文 // ============================================================================ void GLFontAtlas::createAtlas() { - int channels = useSDF_ ? 1 : 4; + // 统一使用 4 通道格式 + int channels = 4; std::vector emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0); texture_ = std::make_unique(ATLAS_WIDTH, ATLAS_HEIGHT, emptyData.data(), channels); @@ -107,6 +108,12 @@ void GLFontAtlas::createAtlas() { packNodes_.resize(ATLAS_WIDTH); stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(), ATLAS_WIDTH); + + // 预分配字形缓冲区 + // 假设最大字形尺寸为 fontSize * fontSize * 4 (RGBA) + size_t maxGlyphSize = static_cast(fontSize_ * fontSize_ * 4 * 4); + glyphBitmapCache_.reserve(maxGlyphSize); + glyphRgbaCache_.reserve(maxGlyphSize); } // ============================================================================ @@ -171,14 +178,23 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const { glyphs_[codepoint] = glyph; + // 将 SDF 单通道数据转换为 RGBA 格式(统一格式) + size_t pixelCount = static_cast(w) * static_cast(h); + glyphRgbaCache_.resize(pixelCount * 4); + for (size_t i = 0; i < pixelCount; ++i) { + uint8_t alpha = sdf[i]; + glyphRgbaCache_[i * 4 + 0] = 255; // R + glyphRgbaCache_[i * 4 + 1] = 255; // G + glyphRgbaCache_[i * 4 + 2] = 255; // B + glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道 + } + + // 直接设置像素对齐为 4,无需查询当前状态 glBindTexture(GL_TEXTURE_2D, texture_->getTextureID()); - GLint prevUnpackAlignment = 4; - glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlignment); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // OpenGL纹理坐标原点在左下角,需要将Y坐标翻转 glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h, - GL_RED, GL_UNSIGNED_BYTE, sdf); - glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlignment); + GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data()); stbtt_FreeSDF(sdf, nullptr); return; @@ -199,9 +215,10 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const { return; } - std::vector bitmap( - static_cast(w) * static_cast(h), 0); - stbtt_MakeCodepointBitmap(&fontInfo_, bitmap.data(), w, h, w, scale_, scale_, + // 使用预分配缓冲区 + size_t pixelCount = static_cast(w) * static_cast(h); + glyphBitmapCache_.resize(pixelCount); + stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w, scale_, scale_, static_cast(codepoint)); // 使用 stb_rect_pack 打包矩形 @@ -244,20 +261,22 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const { glyphs_[codepoint] = glyph; // 将单通道字形数据转换为 RGBA 格式(白色字形,Alpha 通道存储灰度) - std::vector rgbaData(w * h * 4); - for (int i = 0; i < w * h; ++i) { - uint8_t alpha = bitmap[static_cast(i)]; - rgbaData[i * 4 + 0] = 255; // R - rgbaData[i * 4 + 1] = 255; // G - rgbaData[i * 4 + 2] = 255; // B - rgbaData[i * 4 + 3] = alpha; // A + glyphRgbaCache_.resize(pixelCount * 4); + for (size_t i = 0; i < pixelCount; ++i) { + uint8_t alpha = glyphBitmapCache_[i]; + glyphRgbaCache_[i * 4 + 0] = 255; // R + glyphRgbaCache_[i * 4 + 1] = 255; // G + glyphRgbaCache_[i * 4 + 2] = 255; // B + glyphRgbaCache_[i * 4 + 3] = alpha; // A } // 更新纹理 - 将字形数据上传到图集的指定位置 - // OpenGL纹理坐标原点在左下角,需要将Y坐标翻转 + // 直接设置像素对齐为 4,无需查询当前状态 glBindTexture(GL_TEXTURE_2D, texture_->getTextureID()); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + // OpenGL纹理坐标原点在左下角,需要将Y坐标翻转 glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h, - GL_RGBA, GL_UNSIGNED_BYTE, rgbaData.data()); + GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data()); } } // namespace extra2d diff --git a/Extra2D/src/graphics/opengl/gl_renderer.cpp b/Extra2D/src/graphics/opengl/gl_renderer.cpp index 31e620f..2be034c 100644 --- a/Extra2D/src/graphics/opengl/gl_renderer.cpp +++ b/Extra2D/src/graphics/opengl/gl_renderer.cpp @@ -444,6 +444,10 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text, float cursorY = y; float baselineY = cursorY + font.getAscent(); + // 收集所有字符数据用于批处理 + std::vector sprites; + sprites.reserve(text.size()); // 预分配空间 + for (char32_t codepoint : utf8ToUtf32(text)) { if (codepoint == '\n') { cursorX = x; @@ -464,20 +468,24 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text, float xPos = penX + glyph->bearingX; float yPos = baselineY + glyph->bearingY; - Rect destRect(xPos, yPos, glyph->width, glyph->height); - GLSpriteBatch::SpriteData data; - data.position = glm::vec2(destRect.origin.x, destRect.origin.y); - data.size = glm::vec2(destRect.size.width, destRect.size.height); + data.position = glm::vec2(xPos, yPos); + data.size = glm::vec2(glyph->width, glyph->height); data.texCoordMin = glm::vec2(glyph->u0, glyph->v0); data.texCoordMax = glm::vec2(glyph->u1, glyph->v1); data.color = glm::vec4(color.r, color.g, color.b, color.a); data.rotation = 0.0f; data.anchor = glm::vec2(0.0f, 0.0f); data.isSDF = font.isSDF(); - spriteBatch_.draw(*font.getTexture(), data); + + sprites.push_back(data); } } + + // 使用批处理绘制所有字符 + if (!sprites.empty()) { + spriteBatch_.drawBatch(*font.getTexture(), sprites); + } } void GLRenderer::resetStats() { stats_ = Stats{}; } diff --git a/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp b/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp index f61579b..2969e0e 100644 --- a/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp +++ b/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp @@ -49,6 +49,24 @@ private: const TrigLookup::Tables TrigLookup::table_; +// 静态索引生成函数 +static const std::array& getIndices() { + static std::array indices = []() { + std::array arr{}; + for (size_t i = 0; i < GLSpriteBatch::MAX_SPRITES; ++i) { + GLuint base = static_cast(i * GLSpriteBatch::VERTICES_PER_SPRITE); + arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 0] = base + 0; + arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 1] = base + 1; + arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 2] = base + 2; + arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 3] = base + 0; + arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 4] = base + 2; + arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 5] = base + 3; + } + return arr; + }(); + return indices; +} + // 顶点着色器 (GLES 3.2) static const char *SPRITE_VERTEX_SHADER = R"( #version 300 es @@ -70,6 +88,8 @@ void main() { )"; // 片段着色器 (GLES 3.2) +// SDF 常量硬编码:ONEDGE_VALUE=128/255=0.502, PIXEL_DIST_SCALE=255/64=3.98 +// SDF 值存储在 Alpha 通道 static const char *SPRITE_FRAGMENT_SHADER = R"( #version 300 es precision highp float; @@ -78,15 +98,13 @@ in vec4 vColor; uniform sampler2D uTexture; uniform int uUseSDF; -uniform float uSdfOnEdge; -uniform float uSdfScale; out vec4 fragColor; void main() { if (uUseSDF == 1) { - float dist = texture(uTexture, vTexCoord).r; - float sd = (dist - uSdfOnEdge) * uSdfScale; + float dist = texture(uTexture, vTexCoord).a; + float sd = (dist - 0.502) * 3.98; float w = fwidth(sd); float alpha = smoothstep(-w, w, sd); fragColor = vec4(vColor.rgb, vColor.a * alpha); @@ -136,19 +154,8 @@ bool GLSpriteBatch::init() { glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, color)); - // 生成索引缓冲区 - 静态,只需创建一次 - std::vector indices; - indices.reserve(MAX_INDICES); - for (size_t i = 0; i < MAX_SPRITES; ++i) { - GLuint base = static_cast(i * VERTICES_PER_SPRITE); - indices.push_back(base + 0); - indices.push_back(base + 1); - indices.push_back(base + 2); - indices.push_back(base + 0); - indices.push_back(base + 2); - indices.push_back(base + 3); - } - + // 使用编译期生成的静态索引缓冲区 + const auto& indices = getIndices(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), indices.data(), GL_STATIC_DRAW); @@ -196,40 +203,55 @@ bool GLSpriteBatch::needsFlush(const Texture &texture, bool isSDF) const { } void GLSpriteBatch::addVertices(const SpriteData &data) { - // 计算变换后的顶点位置 - glm::vec2 anchorOffset(data.size.x * data.anchor.x, - data.size.y * data.anchor.y); + // 计算锚点偏移 + float anchorOffsetX = data.size.x * data.anchor.x; + float anchorOffsetY = data.size.y * data.anchor.y; // 使用三角函数查表替代 cosf/sinf float cosR = TrigLookup::cosRad(data.rotation); float sinR = TrigLookup::sinRad(data.rotation); - auto transform = [&](float x, float y) -> glm::vec2 { - float rx = x - anchorOffset.x; - float ry = y - anchorOffset.y; - return glm::vec2(data.position.x + rx * cosR - ry * sinR, - data.position.y + rx * sinR + ry * cosR); - }; - glm::vec4 color(data.color.r, data.color.g, data.color.b, data.color.a); - // 添加四个顶点(图片已在加载时翻转,纹理坐标直接使用) - // v0(左上) -- v1(右上) - // | | - // v3(左下) -- v2(右下) - Vertex v0{transform(0, 0), glm::vec2(data.texCoordMin.x, data.texCoordMin.y), - color}; - Vertex v1{transform(data.size.x, 0), - glm::vec2(data.texCoordMax.x, data.texCoordMin.y), color}; - Vertex v2{transform(data.size.x, data.size.y), - glm::vec2(data.texCoordMax.x, data.texCoordMax.y), color}; - Vertex v3{transform(0, data.size.y), - glm::vec2(data.texCoordMin.x, data.texCoordMax.y), color}; - - vertexBuffer_[vertexCount_++] = v0; - vertexBuffer_[vertexCount_++] = v1; - vertexBuffer_[vertexCount_++] = v2; - vertexBuffer_[vertexCount_++] = v3; + // 直接计算变换后的位置 + float rx0 = -anchorOffsetX; + float ry0 = -anchorOffsetY; + float rx1 = data.size.x - anchorOffsetX; + float ry1 = data.size.y - anchorOffsetY; + + // 预计算旋转后的偏移 + float cosRx0 = rx0 * cosR, sinRx0 = rx0 * sinR; + float cosRx1 = rx1 * cosR, sinRx1 = rx1 * sinR; + float cosRy0 = ry0 * cosR, sinRy0 = ry0 * sinR; + float cosRy1 = ry1 * cosR, sinRy1 = ry1 * sinR; + + // v0: (0, 0) -> (rx0, ry0) + vertexBuffer_[vertexCount_++] = { + glm::vec2(data.position.x + cosRx0 - sinRy0, data.position.y + sinRx0 + cosRy0), + glm::vec2(data.texCoordMin.x, data.texCoordMin.y), + color + }; + + // v1: (size.x, 0) -> (rx1, ry0) + vertexBuffer_[vertexCount_++] = { + glm::vec2(data.position.x + cosRx1 - sinRy0, data.position.y + sinRx1 + cosRy0), + glm::vec2(data.texCoordMax.x, data.texCoordMin.y), + color + }; + + // v2: (size.x, size.y) -> (rx1, ry1) + vertexBuffer_[vertexCount_++] = { + glm::vec2(data.position.x + cosRx1 - sinRy1, data.position.y + sinRx1 + cosRy1), + glm::vec2(data.texCoordMax.x, data.texCoordMax.y), + color + }; + + // v3: (0, size.y) -> (rx0, ry1) + vertexBuffer_[vertexCount_++] = { + glm::vec2(data.position.x + cosRx0 - sinRy1, data.position.y + sinRx0 + cosRy1), + glm::vec2(data.texCoordMin.x, data.texCoordMax.y), + color + }; } void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) { @@ -316,8 +338,7 @@ void GLSpriteBatch::flush() { shader_.setMat4("uViewProjection", viewProjection_); shader_.setInt("uTexture", 0); shader_.setInt("uUseSDF", currentIsSDF_ ? 1 : 0); - shader_.setFloat("uSdfOnEdge", 128.0f / 255.0f); - shader_.setFloat("uSdfScale", 255.0f / 64.0f); + // SDF 常量已硬编码到着色器中 // 更新 VBO 数据 - 只更新实际使用的部分 glBindBuffer(GL_ARRAY_BUFFER, vbo_);