使用 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 并且只在必要时才做一些事情。

当然,您可以在程序开始时设置一个值(在启动套接字之前和第一帧之前),这样您就有一个字体开始。