Android NDK中OpenGL ES 3.2调用glTexSubImage2D触发GL_INVALID_OPERATION问题
问题背景
我正在使用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

