androidc++opengl-esandroid-ndk

glTexSubImage2D throws GL_INVALID_OPERATION in OpenGL ES 3.2 on Android NDK with FreeType


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;
    }

Solution

  • 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.