You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android NDK中OpenGL ES 3.2调用glTexSubImage2D触发GL_INVALID_OPERATION问题

OpenGL ES 3.2中glTexSubImage2D触发GL_INVALID_OPERATION(1282)的原因分析

问题背景

我正在使用NDK将一款PC端游戏移植到Android平台,采用FreeType实现文本渲染。原代码在PC端OpenGL 1.2环境下运行正常,适配Android将着色器改为OpenGL ES 3.2后,调用glTexSubImage2D时触发错误码1282(GL_INVALID_OPERATION)。

着色器代码

const char *vertexShaderSource =
        "#version 320 es\n"
        "precision highp float;\n"
        "in vec4 coord;\n"
        "in vec4 color;\n"
        "out vec2 texpos;\n"
        "out vec4 colormod;\n"
        "uniform vec2 screensize;\n"
        "uniform int yoffset;\n"

        "void main(void)\n"
        "{\n"
        "   float x = ((coord.x - 400.0) / screensize.x) * (screensize.y / 300.0);\n"
        "   float y = ((300.0 - (coord.y + float(yoffset))) / screensize.y) * (screensize.y / 300.0);\n"
        "   gl_Position = vec4(x, y, 0.0, 1.0);\n"
        "   texpos = coord.zw;\n"
        "   colormod = color;\n"
        "}\n";

const char *fragmentShaderSource =
        "#version 320 es\n"
        "precision highp float;\n"
        "in vec2 texpos;\n"
        "in vec4 colormod;\n"
        "out vec4 FragColor;\n"
        "uniform sampler2D texture;\n"
        "uniform vec2 atlassize;\n"
        "uniform int fontregion;\n"

        "void main(void)\n"
        "{\n"
        "   if (texpos.y == 0.0)\n"
        "   {\n"
        "       FragColor = colormod;\n"
        "   }\n"
        "   else if (texpos.y <= float(fontregion))\n"
        "   {\n"
        "       FragColor = vec4(1.0, 1.0, 1.0, texture(texture, texpos / atlassize).r) * colormod;\n"
        "   }\n"
        "   else\n"
        "   {\n"
        "       FragColor = texture(texture, texpos / atlassize).bgra * colormod;\n"
        "   }\n"
        "}\n";

着色器变量获取代码

GLint attribute_coord_;
GLint attribute_color_;
GLint uniform_texture_;
GLint uniform_atlas_size_;
GLint uniform_screen_size_;
GLint uniform_yoffset;
GLint uniform_font_region_;

attribute_coord_ = glGetAttribLocation(shader_program_, "coord");
attribute_color_ = glGetAttribLocation(shader_program_, "color");
uniform_texture_ = glGetUniformLocation(shader_program_, "texture");
uniform_atlas_size_ = glGetUniformLocation(shader_program_, "atlassize");
uniform_screen_size_ = glGetUniformLocation(shader_program_, "screensize");
uniform_yoffset = glGetUniformLocation(shader_program_, "yoffset");
uniform_font_region_ = glGetUniformLocation(shader_program_, "fontregion");

顶点缓冲对象代码

glGenBuffers(1, &VBO_);

glGenTextures(1, &atlas_);
glBindTexture(GL_TEXTURE_2D, atlas_);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D,
             0,
             GL_RGBA,
             ATLASW,
             ATLASH,
             0,
             GL_RGBA,
             GL_UNSIGNED_BYTE,
             nullptr);

font_border.set_y(1);

从Android系统路径读取字体

const std::string FONT_NORMAL = Setting<FontPathNormal>().get().load();
const char *FONT_NORMAL_STR = FONT_NORMAL.c_str();
add_font(FONT_NORMAL_STR, Text::Font::A11M, 0, 11);

FreeType加载字体代码

bool GraphicsGL::add_font(const char *name,
                          Text::Font id,
                          FT_UInt pixelw,
                          FT_UInt pixelh) {
    FT_Face face;

    if (FT_New_Face(ft_library_, name, 0, &face)) {
        std::cerr << "Error: Failed to create new face." << std::endl;
        return false;
    }

    if (FT_Set_Pixel_Sizes(face, pixelw, pixelh)) {
        std::cerr << "Error: Failed to set pixel sizes." << std::endl;
        return false;
    }

    FT_GlyphSlot g = face->glyph;

    GLshort width = 0;
    GLshort height = 0;

    for (uint32_t c = 32; c < 256; c++) {
        if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
            std::cerr << "Error: Failed to load a glyph into the glyph slot of "
                         "a face object."
                      << std::endl;
            continue;
        }

        auto w = static_cast<GLshort>(g->bitmap.width);
        auto h = static_cast<GLshort>(g->bitmap.rows);

        width += w;

        if (h > height) {
            height = h;
        }
    }

    if (font_border.x() + width > ATLASW) {
        font_border.set_x(0);
        font_border.set_y(font_ymax);
        font_ymax = 0;
    }

    GLshort x = font_border.x();
    GLshort y = font_border.y();

    font_border.shift_x(width);

    if (height > font_ymax) {
        font_ymax = height;
    }

    fonts_[id] = Font(width, height);

    GLshort ox = x;
    GLshort oy = y;

    for (uint32_t c = 32; c < 256; c++) {
        if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
            std::cerr << "Error: Failed to load a glyph into the glyph slot of "
                         "a face object."
                      << std::endl;
            continue;
        }

        auto ax = static_cast<GLshort>(g->advance.x >> 6);
        auto ay = static_cast<GLshort>(g->advance.y >> 6);
        auto l = static_cast<GLshort>(g->bitmap.left);
        auto t = static_cast<GLshort>(g->bitmap.top);
        auto w = static_cast<GLshort>(g->bitmap.width);
        auto h = static_cast<GLshort>(g->bitmap.rows);

        glTexSubImage2D(GL_TEXTURE_2D,
                        0,
                        ox,
                        oy,
                        w,
                        h,
                        GL_RED,
                        GL_UNSIGNED_BYTE,
                        g->bitmap.buffer);
        GLenum error = glGetError();

        Offset offset = Offset(ox, oy, w, h);
        fonts_[id].chars[c] = { ax, ay, w, h, l, t, offset };

        ox += w;
    }

    return true;
}

可能的原因及解决方案

1. 纹理格式不匹配(最可能原因)

创建纹理时用GL_RGBA作为内部格式和数据格式,但调用glTexSubImage2D时传入GL_RED格式。PC端OpenGL 1.2对格式兼容性要求宽松,允许隐式转换,但OpenGL ES 3.2严格要求子图像的格式必须与纹理初始格式匹配。

解决方案:
创建纹理时改用单通道格式,和FreeType输出的灰度图匹配:

glTexImage2D(GL_TEXTURE_2D,
             0,
             GL_RED,
             ATLASW,
             ATLASH,
             0,
             GL_RED,
             GL_UNSIGNED_BYTE,
             nullptr);

2. 纹理区域越界

检查ox + w是否超过ATLASW,oy + h是否超过ATLASH。font_border的换行逻辑中,font_ymax的更新可能存在问题,导致子图像区域超出纹理范围。

解决方案:
在调用glTexSubImage2D前添加边界检查:

if (ox + w > ATLASW || oy + h > ATLASH) {
    std::cerr << "Error: Texture subimage out of bounds." << std::endl;
    continue;
}

同时核对font_border和font_ymax的计算逻辑,确保换行后的y坐标不会让纹理区域越界。

3. 纹理绑定状态异常

调用glTexSubImage2D时,可能没有正确绑定目标纹理atlas_。虽然初始化时绑定过,但后续操作可能修改了绑定状态。

解决方案:
在调用glTexSubImage2D前显式绑定纹理:

glBindTexture(GL_TEXTURE_2D, atlas_);

4. 像素对齐问题

FreeType生成的bitmap的行间距(g->bitmap.pitch)可能与OpenGL的像素对齐设置不匹配。虽然设置了glPixelStorei(GL_UNPACK_ALIGNMENT, 1),但如果pitch不等于w(单通道下应该相等),仍可能触发错误。

解决方案:
检查g->bitmap.pitch的值,确保等于w,如果不等,可以手动调整数据,或者设置更宽松的对齐参数(单通道下pitch默认应等于宽度)。

内容的提问来源于stack exchange,提问作者k0ala Xing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 18:09:51