使用 GLEW 和 GLFW 在 freetype C++ 中动态更改字体
Change font dynamically in freetype C++ using GLEW and GLFW
我正在构建一个应用程序,它可以基于从 TCP 套接字获取的 JSON 对象在屏幕上显示文本。 JSON 对象包含文本(base64 编码)、字体、字体大小等信息。我已经实现了其中的一些东西,但我一直坚持更改字体。我有一个名为 fonts
的文件夹。该程序在此文件夹中查找字体,如果找到,它会清除字符映射表并使用所选字体文件构建另一个字符映射表。问题是它无法创建纹理 glCreateTextures(GL_TEXTURE_2D, 1, &texture);
并且程序崩溃。
这是侦听来自 TCP 套接字的 JSON 消息的函数:
void listen_for_connection(void * aArg) {
while (true) {
try {
asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 8080));
tcp::socket socket(io_context);
acceptor.accept(socket);
for (;;) {
// JSON command
string message = read_(socket);
json command = json::parse(message);
//get the font
try {
cout << "Getting the font ..." << endl;
string font = command["font"].get < string > ();
cout << "Got the font: " << font << endl;
ifstream ifile;
ifile.open("./fonts/" + font + ".ttf");
cout << "Checking if the font file exists ..." << endl;
if (ifile) { // only change font if the font exists
cout << "Found the font file :)" << endl;
text_mutex.lock();
ifile.close();
font = "./fonts/" + font + ".ttf";
cout << "Got the mutex" << endl;
char c[font.size() + 1];
font.copy(c, font.size() + 1);
c[font.size()] = '[=10=]';
cout << "Setting the font and executing GL Setup" << endl;
FONT = c;
initializeFreeType();
cout << "Done!" << endl;
text_mutex.unlock();
}
} catch (exception & e) {
cout << "Error while trying to get the font: " << e.what() << endl;
}
// get the text to be shown
try {
list < wstring > result = getTextFromCommand(command);
text_mutex.lock();
TEXT.clear();
for (wstring s: result) {
TEXT.push_back(s);
}
text_mutex.unlock();
} catch (exception & e) {
cout << "Error while trying to get the text: " << e.what() << endl;
}
// get the text size
try {
int font_size = command["font_size"].get < int > ();
text_mutex.lock();
TEXT_SCALE = ((float) font_size) / ((float) FONT_SIZE);
text_mutex.unlock();
} catch (exception & e) {
cout << "Error while trying to get the font size: " << e.what() << endl;
}
try {
int monitor = command["monitor"].get < int > ();
if (monitor < TOTAL_MONITORS && monitor >= 0 && monitor != MONITOR_TO_CHANGE) {
monitor_event_mutex.lock();
SHOULD_CHANGE_MONITOR = true;
MONITOR_TO_CHANGE = monitor;
monitor_event_mutex.unlock();
}
} catch (exception & e) {
cout << "Error while trying to get the monitor: " << e.what() << endl;
}
}
} catch (exception & e) {
cout << "Another exception has occurred: " << e.what() << endl;
}
}
}
这里是 initializeFreeType()
函数:
void initializeFreeType() {
cout << "Initializing FreeType ..." << endl;
int error;
FT_Library ft;
error = FT_Init_FreeType( & ft);
cout << "Checking for errors ..." << endl;
if (error) {
printf("Error while trying to initialize freetype library: %d\n", error);
}
FT_Face face;
error = FT_New_Face(ft, FONT, 0, & face);
if (error == FT_Err_Unknown_File_Format) {
printf("Error: File format not supported\n");
exit(1);
} else if (error) {
printf("Error while trying to initialize face: %d\n", error);
}
cout << "Done setting the font " << endl;
FT_Set_Pixel_Sizes(face, 0, FONT_SIZE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (!Characters.empty()) {
cout << "Clearing the Characters" << endl;
Characters.clear();
}
for (GLuint c = 0; c < 512; c++) {
FT_Load_Char(face, (wchar_t) c, FT_LOAD_RENDER);
GLuint texture = 0;
cout << "Creating textures..." << endl;
glCreateTextures(GL_TEXTURE_2D, 1, & texture);
cout << "Done creating textures" << endl;
cout << "Storage 2D" << endl;
glTextureStorage2D(texture, 1, GL_R8, face -> glyph -> bitmap.width, face -> glyph -> bitmap.rows);
cout << "Done Storage 2D" << endl;
glTextureSubImage2D(texture, 0, 0, 0, face -> glyph -> bitmap.width, face -> glyph -> bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, face -> glyph -> bitmap.buffer);
cout << "Done SubImage 2D" << endl;
cout << "The rest ..." << endl;
glBindTexture(GL_TEXTURE_2D, texture);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
cout << "Done with the rest :)" << endl;
Character character = {
texture,
glm::ivec2(face -> glyph -> bitmap.width, face -> glyph -> bitmap.rows),
glm::ivec2(face -> glyph -> bitmap_left, face -> glyph -> bitmap_top),
face -> glyph -> advance.x
};
cout << "Adding character ..." << endl;
Characters.insert(pair < wchar_t, Character > ((wchar_t) c, character));
cout << "Done adding character" << endl;
}
cout << "Done inserting the Characters :)" << endl;
FT_Done_Face(face);
cout << "Done face" << endl;
FT_Done_FreeType(ft);
cout << "Done freetype" << endl;
}
这是glSetup()
函数(在代码开头执行,无关紧要,但我选择写在这里完成):
void glSetup() {
glewInit();
glDebugMessageCallback(GLDebugMessageCallback, NULL);
glEnable(GL_CULL_FACE);
glEnable(GL_DEBUG_OUTPUT);
glViewport(0, 0, DEFAULT_MONITOR.maxResolution.width, DEFAULT_MONITOR.maxResolution.height);
GLuint shader = CompileShaders(true, false, false, false, true);
glUseProgram(shader);
initializeFreeType();
glm::mat4 projection = glm::ortho(0.0 f, (float) DEFAULT_MONITOR.maxResolution.width, 0.0 f, (float) DEFAULT_MONITOR.maxResolution.height);
glUniformMatrix4fv(1, 1, GL_FALSE, glm::value_ptr(projection));
GLuint vao;
glCreateVertexArrays(1, & vao);
glBindVertexArray(vao);
glCreateBuffers(1, & buffer);
glNamedBufferStorage(buffer, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_STORAGE_BIT);
glVertexArrayVertexBuffer(vao, 0, buffer, 0, sizeof(GLfloat) * 4);
glVertexArrayAttribFormat(vao, 0, 4, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 0, 0);
glEnableVertexArrayAttrib(vao, 0);
glUniform3f(6, 0.88 f, 0.59 f, 0.07 f);
}
我有很多 cout
以便我可以调试(我不能使用调试器,它总是崩溃或什么都不显示)。当它得到 JSON 来更改字体时,它总是在 Creating textures...
之后崩溃。有人可以帮我指出我正在做的错误吗?非常感谢!
PS: 你可以找到整个项目here。它很容易构建,尽管它还没有自述文件(还)。您只需要使用 Code::Blocks 打开项目(所有依赖项都在存储库中)
正如您在评论中所说,您对所有内容都使用一个线程,对套接字使用一个线程。
默认情况下,OpenGL 无法在多线程中工作。您必须先将上下文交给该线程,然后等待另一个线程。
这就是为什么您不能简单地在套接字线程中调用 initializeFreeType
的原因,因为它会调用 OpenGL 函数。
可以使用多个上下文并在它们之间共享数据。也可以使用 Vulkan,较低级别的图形 API,允许您使用许多线程进行加载和渲染。
您可以做的最简单的更改是设置字体以加载下一帧。
当然,您将需要某种原子变量或锁。
std::atomic<char const*> FONT;
你的 listen_for_connection
函数应该是这样的:
FONT = c;
// initializeFreeType(); // Don't load immediately
cout << "Done!" << endl;
你的加载函数是这样的:
void initializeFreeType() {
// Get the value and set to null in one atomic operation
auto font_to_load = FONT.exchange(nullptr);
if (not font_to_load) {
return; // If null, do nothing
}
// Load the font ...
}
原理是这样的:当FONT
变量为null时,什么都不做。如果它有值,再次将它设置为 null 并使用它所具有的值加载字体。只有套接字线程可以设置 FONT
值。
这样你就可以在每一帧调用 initializeFreeType
并且只在必要时才做一些事情。
当然,您可以在程序开始时设置一个值(在启动套接字之前和第一帧之前),这样您就有一个字体开始。
我正在构建一个应用程序,它可以基于从 TCP 套接字获取的 JSON 对象在屏幕上显示文本。 JSON 对象包含文本(base64 编码)、字体、字体大小等信息。我已经实现了其中的一些东西,但我一直坚持更改字体。我有一个名为 fonts
的文件夹。该程序在此文件夹中查找字体,如果找到,它会清除字符映射表并使用所选字体文件构建另一个字符映射表。问题是它无法创建纹理 glCreateTextures(GL_TEXTURE_2D, 1, &texture);
并且程序崩溃。
这是侦听来自 TCP 套接字的 JSON 消息的函数:
void listen_for_connection(void * aArg) {
while (true) {
try {
asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 8080));
tcp::socket socket(io_context);
acceptor.accept(socket);
for (;;) {
// JSON command
string message = read_(socket);
json command = json::parse(message);
//get the font
try {
cout << "Getting the font ..." << endl;
string font = command["font"].get < string > ();
cout << "Got the font: " << font << endl;
ifstream ifile;
ifile.open("./fonts/" + font + ".ttf");
cout << "Checking if the font file exists ..." << endl;
if (ifile) { // only change font if the font exists
cout << "Found the font file :)" << endl;
text_mutex.lock();
ifile.close();
font = "./fonts/" + font + ".ttf";
cout << "Got the mutex" << endl;
char c[font.size() + 1];
font.copy(c, font.size() + 1);
c[font.size()] = '[=10=]';
cout << "Setting the font and executing GL Setup" << endl;
FONT = c;
initializeFreeType();
cout << "Done!" << endl;
text_mutex.unlock();
}
} catch (exception & e) {
cout << "Error while trying to get the font: " << e.what() << endl;
}
// get the text to be shown
try {
list < wstring > result = getTextFromCommand(command);
text_mutex.lock();
TEXT.clear();
for (wstring s: result) {
TEXT.push_back(s);
}
text_mutex.unlock();
} catch (exception & e) {
cout << "Error while trying to get the text: " << e.what() << endl;
}
// get the text size
try {
int font_size = command["font_size"].get < int > ();
text_mutex.lock();
TEXT_SCALE = ((float) font_size) / ((float) FONT_SIZE);
text_mutex.unlock();
} catch (exception & e) {
cout << "Error while trying to get the font size: " << e.what() << endl;
}
try {
int monitor = command["monitor"].get < int > ();
if (monitor < TOTAL_MONITORS && monitor >= 0 && monitor != MONITOR_TO_CHANGE) {
monitor_event_mutex.lock();
SHOULD_CHANGE_MONITOR = true;
MONITOR_TO_CHANGE = monitor;
monitor_event_mutex.unlock();
}
} catch (exception & e) {
cout << "Error while trying to get the monitor: " << e.what() << endl;
}
}
} catch (exception & e) {
cout << "Another exception has occurred: " << e.what() << endl;
}
}
}
这里是 initializeFreeType()
函数:
void initializeFreeType() {
cout << "Initializing FreeType ..." << endl;
int error;
FT_Library ft;
error = FT_Init_FreeType( & ft);
cout << "Checking for errors ..." << endl;
if (error) {
printf("Error while trying to initialize freetype library: %d\n", error);
}
FT_Face face;
error = FT_New_Face(ft, FONT, 0, & face);
if (error == FT_Err_Unknown_File_Format) {
printf("Error: File format not supported\n");
exit(1);
} else if (error) {
printf("Error while trying to initialize face: %d\n", error);
}
cout << "Done setting the font " << endl;
FT_Set_Pixel_Sizes(face, 0, FONT_SIZE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (!Characters.empty()) {
cout << "Clearing the Characters" << endl;
Characters.clear();
}
for (GLuint c = 0; c < 512; c++) {
FT_Load_Char(face, (wchar_t) c, FT_LOAD_RENDER);
GLuint texture = 0;
cout << "Creating textures..." << endl;
glCreateTextures(GL_TEXTURE_2D, 1, & texture);
cout << "Done creating textures" << endl;
cout << "Storage 2D" << endl;
glTextureStorage2D(texture, 1, GL_R8, face -> glyph -> bitmap.width, face -> glyph -> bitmap.rows);
cout << "Done Storage 2D" << endl;
glTextureSubImage2D(texture, 0, 0, 0, face -> glyph -> bitmap.width, face -> glyph -> bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, face -> glyph -> bitmap.buffer);
cout << "Done SubImage 2D" << endl;
cout << "The rest ..." << endl;
glBindTexture(GL_TEXTURE_2D, texture);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
cout << "Done with the rest :)" << endl;
Character character = {
texture,
glm::ivec2(face -> glyph -> bitmap.width, face -> glyph -> bitmap.rows),
glm::ivec2(face -> glyph -> bitmap_left, face -> glyph -> bitmap_top),
face -> glyph -> advance.x
};
cout << "Adding character ..." << endl;
Characters.insert(pair < wchar_t, Character > ((wchar_t) c, character));
cout << "Done adding character" << endl;
}
cout << "Done inserting the Characters :)" << endl;
FT_Done_Face(face);
cout << "Done face" << endl;
FT_Done_FreeType(ft);
cout << "Done freetype" << endl;
}
这是glSetup()
函数(在代码开头执行,无关紧要,但我选择写在这里完成):
void glSetup() {
glewInit();
glDebugMessageCallback(GLDebugMessageCallback, NULL);
glEnable(GL_CULL_FACE);
glEnable(GL_DEBUG_OUTPUT);
glViewport(0, 0, DEFAULT_MONITOR.maxResolution.width, DEFAULT_MONITOR.maxResolution.height);
GLuint shader = CompileShaders(true, false, false, false, true);
glUseProgram(shader);
initializeFreeType();
glm::mat4 projection = glm::ortho(0.0 f, (float) DEFAULT_MONITOR.maxResolution.width, 0.0 f, (float) DEFAULT_MONITOR.maxResolution.height);
glUniformMatrix4fv(1, 1, GL_FALSE, glm::value_ptr(projection));
GLuint vao;
glCreateVertexArrays(1, & vao);
glBindVertexArray(vao);
glCreateBuffers(1, & buffer);
glNamedBufferStorage(buffer, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_STORAGE_BIT);
glVertexArrayVertexBuffer(vao, 0, buffer, 0, sizeof(GLfloat) * 4);
glVertexArrayAttribFormat(vao, 0, 4, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, 0, 0);
glEnableVertexArrayAttrib(vao, 0);
glUniform3f(6, 0.88 f, 0.59 f, 0.07 f);
}
我有很多 cout
以便我可以调试(我不能使用调试器,它总是崩溃或什么都不显示)。当它得到 JSON 来更改字体时,它总是在 Creating textures...
之后崩溃。有人可以帮我指出我正在做的错误吗?非常感谢!
PS: 你可以找到整个项目here。它很容易构建,尽管它还没有自述文件(还)。您只需要使用 Code::Blocks 打开项目(所有依赖项都在存储库中)
正如您在评论中所说,您对所有内容都使用一个线程,对套接字使用一个线程。
默认情况下,OpenGL 无法在多线程中工作。您必须先将上下文交给该线程,然后等待另一个线程。
这就是为什么您不能简单地在套接字线程中调用 initializeFreeType
的原因,因为它会调用 OpenGL 函数。
可以使用多个上下文并在它们之间共享数据。也可以使用 Vulkan,较低级别的图形 API,允许您使用许多线程进行加载和渲染。
您可以做的最简单的更改是设置字体以加载下一帧。
当然,您将需要某种原子变量或锁。
std::atomic<char const*> FONT;
你的 listen_for_connection
函数应该是这样的:
FONT = c;
// initializeFreeType(); // Don't load immediately
cout << "Done!" << endl;
你的加载函数是这样的:
void initializeFreeType() {
// Get the value and set to null in one atomic operation
auto font_to_load = FONT.exchange(nullptr);
if (not font_to_load) {
return; // If null, do nothing
}
// Load the font ...
}
原理是这样的:当FONT
变量为null时,什么都不做。如果它有值,再次将它设置为 null 并使用它所具有的值加载字体。只有套接字线程可以设置 FONT
值。
这样你就可以在每一帧调用 initializeFreeType
并且只在必要时才做一些事情。
当然,您可以在程序开始时设置一个值(在启动套接字之前和第一帧之前),这样您就有一个字体开始。