I'm porting a game to android from PC using NDK and use FreeType for text rendering. The original code works fine on PC with OpenGL 1.2, in order to work on Android I decided to change the shader program to OpenGL ES 3.2, but unfortunately after line glTexSubImage2D I got error code 1282 GL_INVALID_OPERATION, what could be the reason for this ? Shaders:
const char *vertexShaderSource =
"#version 320 es\n"
"precision highp float;"
"in vec4 coord;"
"in vec4 color;"
"out vec2 texpos;"
"out vec4 colormod;"
"uniform vec2 screensize;"
"uniform int yoffset;"
"void main(void)"
"{"
" float x = ((coord.x - 400.0) / screensize.x) * (screensize.y / 300.0);"
" float y = ((300.0 - (coord.y + float(yoffset))) / screensize.y) * (screensize.y / 300.0);"
" gl_Position = vec4(x, y, 0.0, 1.0);"
" texpos = coord.zw;"
" colormod = color;"
"}";
const char *fragmentShaderSource =
"#version 320 es\n"
"precision highp float;"
"in vec2 texpos;"
"in vec4 colormod;"
"out vec4 FragColor;"
"uniform sampler2D texture;"
"uniform vec2 atlassize;"
"uniform int fontregion;"
"void main(void)"
"{"
" if (texpos.y == 0.0)"
" {"
" FragColor = colormod;"
" }"
" else if (texpos.y <= float(fontregion))"
" {"
" FragColor = vec4(1.0, 1.0, 1.0, texture(texture, texpos / atlassize).r) * colormod;"
" }"
" else"
" {"
" FragColor = texture(texture, texpos / atlassize).bgra * "
"colormod;"
" }"
"}";
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");
Vertex Buffer Object:
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);
Reading Font from Android system path: /system/fonts/Roboto-Regular.ttf
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 load Fonts:
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;
}
Per glTexSubImage2D reference:
GL_INVALID_OPERATION
is generated if the combination of internalFormat of the previously specified texture array, format and type is not valid.
In your code, glTexImage2D
call specifies internalFormat
of GL_RGBA
, per glTexImage2D reference (Table 1) GL_RGBA
internal format supports only GL_RGBA
format with several types (GL_UNSIGNED_BYTE
, GL_UNSIGNED_SHORT_4_4_4_4
, GL_UNSIGNED_SHORT_5_5_5_1
)
GL_RED
you use in glTexSubImage2D
is not one of the supported formats, so operation results in GL_INVALID_OPERATION
.
For your usage GL_RED
is indeed a valid format for glTexSubImage2D
, because FreeType renders in 8-bit grayscale bitmap by default, but you should initialize texture with compatible formats (internal format GL_R8
, format GL_RED
, type GL_UNSIGNED_BYTE
) and use your fragment shader to extract red component and produce blendable output.