I'm facing this issue for months. Every font I load with freetype works fine, I can prove that to myself by writing the glyphs to a BMP file using the stb_write_bmp from the STB library. I can see all hundreds of glyph bitmaps written correctly in my folder.
OpenGL, on the other hand, is unable to load the textures from specific fonts, and I see no explainable reason as to why some load and others don't. I have done everything to try and solve it but it keeps happening. The textures simply don't load at all. I'm doing exactly the same procedure for every font, so I should be getting the same results.
Examples: (with maximum of 0.1 alpha for debugging invisible squares like the last ones)
SourceSansPro-Regular.ttf (bugged - no texture)
RobotoCondensed-Regular.ttf (bugged - no texture)
What do the loaded fonts have in common? It's not the extension, at least, since I can render cantarell's TTF files. All of them, even the ones that the texture don't load, are correctly written as BMP if i send them to the STB function. So why are the last ones not loaded by the GL? This makes no sense to me... there's no consistency except that the ones that don't load, never load, and the ones that load, always load. But I see nothing that could have different effects on different fonts, and this is confusing me a lot. I am also returning the errors to an int on every FT function and it's all clear by their side.
This is my renderer's constructor, where everything relevant to the text rendering and texture uploading is setup:
if (error = FT_Init_FreeType(&ftlib)) { std::cout << "Freetype failed to initialize (exit " << error << ')' << std::endl; } else { std::cout << "Freetype initialized"; } if (error = FT_New_Face(ftlib, fontFile, 0, &face)) { std::cout << "but could not load font " << face->family_name << " (exit " << error << ')' << std::endl; } else { std::cout << " and loaded font " << face->family_name << std::endl; FT_Set_Pixel_Sizes(face, 0, height); /// Determine total length of texture atlas FT_UInt cindex = 0; for ( FT_ULong c = FT_Get_First_Char(face, &cindex); cindex != 0; c = FT_Get_Next_Char(face, c, &cindex) ) { if (error = FT_Load_Glyph(face, cindex, FT_LOAD_BITMAP_METRICS_ONLY)) { std::cout << "Freetype: Could not load glyph " "(exit " << error << "[1])" << std::endl; continue; } else { atlasLength += face->glyph->bitmap.width; if (totalRows < face->glyph->bitmap.rows) { totalRows = face->glyph->bitmap.rows; }; } } bindShaders(); /// Setup VAO and texture object. newVertexArray(); bindVertexArray(vertexArray[0]); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glGenTextures(1, &atlas); glBindTexture(GL_TEXTURE_2D, atlas); glTexImage2D( GL_TEXTURE_2D, 0, GL_RED, atlasLength, totalRows, 0, GL_RED, GL_UNSIGNED_BYTE, 0 ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); glGenerateMipmap(GL_TEXTURE_2D); GLint offset = 0; cindex = 0; /// Start indexing textures for ( FT_ULong c = FT_Get_First_Char(face, &cindex); cindex != 0; c = FT_Get_Next_Char(face, c, &cindex)) { if (error = FT_Load_Glyph(face, cindex, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT)) { std::cout << "Freetype could not load glyph " "(exit " << error << "[2])" << "\n" ; continue; } else { FT_GlyphSlot glyph = face->glyph; glTexSubImage2D( GL_TEXTURE_2D, 0, offset, 0, glyph->bitmap.width, glyph->bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, glyph->bitmap.buffer ); Glyph character = { static_cast<Float>(offset), static_cast<Float>(offset) + glyph->bitmap.width, glm::u16vec2(glyph->bitmap.width, glyph->bitmap.rows), glm::u16vec2(face->glyph->bitmap_left, face->glyph->bitmap_top), glm::u16vec2(face->glyph->advance.x, face->glyph->advance.y), }; characters.insert(std::make_pair(c, character)); } offset += face->glyph->bitmap.width; } } glBindVertexArray(0); bindShaders(0);
Am I doing something wrong? As far as I can see there's nothing that could go wrong here.
Edit: I have found that the font height is playing a part here. For some reason, if I send a value of 12px to FT_Set_Pixel_Sizes, all fonts open. Increasing the height makes Source Sans eventually stop working at some point (I haven't marked exactly at what size it stopped working but somewhere around 20px), and at 42px Roboto stops working too. I don't know why though, since the bitmaps are ok in all these cases. At least, this is a bit less mistifying now.
I don’t know if it gonna help, but there is one problem with your code. You’re ignoring the
FT_Bitmap.pitchfield. The RAM layout of the bitmap depends substantially on that value, yet there’s no way to supply the value to OpenGL. There’sGL_UNPACK_ROW_LENGTHsetting but that value is pixels, not bytes.Try to copy these bitmaps to another buffer with stride equal to glyph width * sizeof(pixel), and then pass that temporary buffer to
glTexSubImage2D. If you’re on Windows call MFCopyImage, otherwise do it manually by callingmemcpy()in a loop.Don’t forget the pitch can be negative, which means the FT bitmap is stored with bottom-to-top row order.
Also, check
FT_Bitmap.pixel_modefield isFT_PIXEL_MODE_GRAY, that’s the only value compatible with your GL texture.