Cześć. Chciałbym renderować tekst używając freetype i freetype-gl. Obejrzałem filmiki z kanału TheChernoProject i stwierdziłem, że mógłbym w ten sam sposób zrobić to w mojej aplikacji.
Problem jest taki, że przy renderowaniu, zamiast tekstu pojawiają się kwadraty, co oznacza brak danych w teksturze. Takie coś dzieje się tylko przy rysowaniu czcionek. Tekstury z formatem .png rysują się bardzo dobrze.

#define RENDERER_MAX_SPRITES	60000
#define RENDERER_SPRITE_SIZE	RENDERER_VERTEX_SIZE * 4
#define RENDERER_BUFFER_SIZE	RENDERER_SPRITE_SIZE * RENDERER_MAX_SPRITES
#define RENDERER_INDICES_SIZE	RENDERER_MAX_SPRITES * 6
#define RENDERER_MAX_TEXTURES	32 

class BatchRenderer
{
public:
	BatchRenderer() = default;
	BatchRenderer() = default;
	
	void Initialize()
	{
		//Generowanie, bindowanie vao, vbo, ibo i dodawanie `CalculateIndices` jako dane do ibo
	}

	void Start()
	{
		glCall(glEnable(GL_BLEND));
		glCall(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));

		m_vertex2D = (Vertex2D*)glMapNamedBufferRange(m_vbo, NULL, RENDERER_BUFFER_SIZE, GL_MAP_WRITE_BIT);
	}

	void DrawString(const std::string& text, const Vector2& position, GLuint color, const Font& font)
	{
		float textureSlot = 0;

		bool found = false;
		for (GLuint i = 0; i < m_textures.size(); ++i)
 		{
			if (m_textures[i] == font.getTexture()) 
			{
				textureSlot = (float)(i + 1);
				found = true;
				break;
			}
		}

		if (!found) 
		{
			if (m_textures.size() >= RENDERER_MAX_TEXTURES) 
			{
				End();
				Render();
				Start();
			}
			m_textures.push_back(font.getTexture());
			textureSlot = (float)(m_textures.size());
		}

		float x = position.x;
		texture_font_t* ftFont = font.FTFont;

		for (GLuint i = 0; i < text.length(); ++i) 
		{
			texture_glyph_t* glyph = texture_font_get_glyph(ftFont, text.c_str() + i);
			if (glyph) 
			{
				if (i > 0) 
				{
					float kerning = texture_glyph_get_kerning(glyph, text.c_str() + i - 1);
					x += kerning;
				}

				float x0 = x + glyph->offset_x;
				float y0 = position.y + glyph->offset_y;
				float x1 = x0 + glyph->width;
				float y1 = y0 - glyph->height;

				float u0 = glyph->s0;
				float v0 = glyph->t0;
				float u1 = glyph->s1;
				float v1 = glyph->t1;

				m_vertex2D->position = Vector2(x0, y0);
				m_vertex2D->texUV = Vector2(u0, v0);
				m_vertex2D->texID = textureSlot;
				m_vertex2D->color = color;
				m_vertex2D++;

				m_vertex2D->position = Vector2(x0, y1);
				m_vertex2D->texUV = Vector2(u0, v1);
				m_vertex2D->texID = textureSlot;
				m_vertex2D->color = color;
				m_vertex2D++;

				m_vertex2D->position = Vector2(x1, y1);
				m_vertex2D->texUV =  Vector2(u1, v1);
				m_vertex2D->texID = textureSlot;
				m_vertex2D->color = color;
				m_vertex2D++;

				m_vertex2D->position = Vector2(x1, y0);
				m_vertex2D->texUV =  Vector2(u1, v0);
				m_vertex2D->texID = textureSlot;
				m_vertex2D->color = color;
				m_vertex2D++;

				m_indexCount += 6;

				x += glyph->advance_x;
			}
		}
	}

	void End()
	{
		glCall(glUnmapNamedBuffer(m_vbo));
	}

	void Render()
	{
		for (GLuint i = 0; i < m_textures.size(); ++i) 
		{
			glCall(glActiveTexture(GL_TEXTURE0 + i));
			glCall(glBindTexture(GL_TEXTURE_2D, m_textures[i]));
		}

		glCall(glBindVertexArray(m_vao));
		glCall(glDrawElements(GL_TRIANGLES, m_indexCount , GL_UNSIGNED_INT, nullptr));
		glCall(glBindVertexArray(0));

		m_indexCount = 0;
		m_textures.clear();
	}

	GLuint* CalculateIndices()
	{
		GLuint* indices = new GLuint[RENDERER_INDICES_SIZE],
		     offset = 0;
		for (int i = 0; i < RENDERER_INDICES_SIZE; i += 6)
		 {
			indices[i + 0] = offset + 0;
			indices[i + 1] = offset + 1;
			indices[i + 2] = offset + 2;
			indices[i + 3] = offset + 2;
			indices[i + 4] = offset + 3;
			indices[i + 5] = offset + 0;

			offset += 4;
		}

		return indices;
	}
	Vertex2D* m_vertex2D;
	GLuint m_vao;
	GLuint m_vbo;
	GLuint m_ibo;
	GLuint m_indexCount;
	std::vector<GLuint> m_textures;
};
int main(int argc, char* args[])
{
	SDL_Init(SDL_INIT_EVERYTHING);

	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	SDL_Window* window = SDL_CreateWindow("Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
	SDL_GLContext context = SDL_GL_CreateContext(window);
	SDL_Event event;

	glewExperimental = true;
	glewInit();

	glViewport(0, 0, 1280, 720);

	Renderer renderer;
	renderer.Initialize();

	Font* font = new Font("font.ttf", 50.0f);

	bool quit = false;
	while (!quit) {
		while (SDL_PollEvent(&event)) {
			if (event.type == SDL_QUIT) {
				quit = true;
			}
		}

		glClearColor(0.5, 0.5, 0.5, 1.0);
		glClear(GL_COLOR_BUFFER_BIT);

		renderer.Start();
		renderer.DrawString("JAKIS TEKST", Vector2(100.0f, 100.0f), 0xffff00ff, *font);
		renderer.End();
		renderer.Render();

		SDL_GL_SwapWindow(window);
	}

	return 0;
}
class Font
{
public:
	Font(const std::string& filename, float size)
	{
		atlas = ftgl::texture_atlas_new(512, 512, 3);
		font = ftgl::texture_font_new_from_file(atlas, size, filename.c_str());

		texture = CreateFromSource(512, 512, GL_RGB, atlas->data);
	}

	GLuint getTexture() const
	{
		UpdateAtlas();
		return texture;
	}

	void UpdateAtlas() const
	{
		UpdateData(texture, 512, 512, GL_RGB, FTAtlas->data);
	}

	ftgl::texture_atlas_t* atlas;
	ftgl::texture_font_t* font;

	GLuint texture = 0;
};

Zwykłe funkcje

GLuint CreateFromSource(int w, int h, int format, const void* pixels)
{
	GLuint texture = 0;
	glCall(glGenTextures(1, &texture));
	glCall(glBindTexture(GL_TEXTURE_2D, texture));

	glCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
	glCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
	glCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
	glCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));

	glCall(glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, nullptr));
	glCall(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, format, GL_UNSIGNED_BYTE, pixels));
	glCall(glGenerateMipmap(GL_TEXTURE_2D));

	glCall(glBindTexture(GL_TEXTURE_2D, 0));

	return texture;
}

void UpdateData(GLuint tex, int w, int h, int format, const void* pixels)
{
	glCall(glBindTexture(GL_TEXTURE_2D, tex));
	glCall(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, format, GL_UNSIGNED_BYTE, pixels));
}

Vertex shader

#version 430 core

layout (location = 0) in vec3 v3position;
layout (location = 1) in vec4 v4color;
layout (location = 2) in vec2 v2UV;
layout (location = 3) in float ftexID;

out vec3 position;
out vec4 color;
out vec2 UV;
out float texID;

uniform mat4 mvp;

void main()
{
	gl_Position = mvp * vec4(v3position, 1.0);
	
	position = v3position;
	color = v4color;
	texUV = vec2(v2UV.x, 1 - v2UV.y);
	texID = ftexID;
}

pixel

#version 430 core

out vec4 outColor;

in vec2 position;
in vec4 color;
in vec2 UV;
in float texID;

uniform sampler2D textureSampler[32];

void main()
{
	vec4 texColor = color;

	if (texID > 0.0) {
		int itexID = int(texID - 0.5);
		texColor = color * texture(textureSampler[itexID], UV);
	}

	outColor = texColor;
}

Jak już wspominałem na początku przy teksturach typu .png, .bmp wszystko ładnie, pięknie, ale gdy chcę narysować tekst wtedy pojawiają się same kwadraty bez tekstury. Zmieniłem w shadere pixeli żeby zamiast int itexID = int(texID - 0.5f); było po prostu int itexID = int(texID); i pojawił się tekst, ale zawsze jest jakieś ale:
a) zniknęły tekstury typu .png
b) tekst był nie w tym miejsu
c) była w okół niego czarna tekstura (czyli jakby nie działało blendowanie, ale prędzej nie było z tym problemu).

Dlaczego tak się dzieje w moim przykładzie?
W jaki sposób to naprawić?