ゆとりーなの日記

日記的な事を書いて行くと思はれる

OpenGLとcairommで文字列描画

今回はLinux環境のOpenGLで文字を書くならこれかなーという話です。ubuntuとかのディストリビューションならgtkmmがデフォで入っているっぽくて、その中にcairommもいるっぽいのでクライアント側で何か導入してもらう必要がないのもいい感じですかね。あとなんといってもcairommはCのcairoのC++ラッパなので自前でRAIIラッパとかを書く必要がないのも美味しいです。
取り敢えずxlibとglxとimlib2で画像表示 - 名古屋313の日記とかで窓とOpenGLは初期化されているとすると、文字列を描く部分は大体こんな感じになります。

GLuint text_id;
Cairo::TextExtents text_extents;

void init_text_texture() {
  constexpr double size = 32.0;
  const std::string text = "c++";
  // サイズ計算用
  const auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, 0, 0);
  const auto size_context = Cairo::Context::create(surface);
  // フォントを設定
  size_context->select_font_face("",
      Cairo::FONT_SLANT_NORMAL,
      Cairo::FONT_WEIGHT_NORMAL);
  size_context->set_font_size(size);
  // 描画サイズ計算
  size_context->get_text_extents(text, text_extents);
  // 実際に描画するサーフェイス
  const auto text_surface = Cairo::ImageSurface::create(
                                 Cairo::FORMAT_ARGB32,
                                 text_extents.width,
                                 text_extents.height);
  // 実際に描画するコンテキスト
  const auto text_context(Cairo::Context::create(text_surface));
  // フォントを設定
  text_context->select_font_face("",
      Cairo::FONT_SLANT_NORMAL,
      Cairo::FONT_WEIGHT_NORMAL);
  text_context->set_font_size(size);
  text_context->set_source_rgba(1.0, 1.0, 1.0, 1.0);
  text_context->move_to(-text_extents.x_bearing, -text_extents.y_bearing);
  // 描画
  text_context->show_text("c++");
  glEnable(GL_TEXTURE_2D);
  glGenTextures(1, &text_id);
  glBindTexture(GL_TEXTURE_2D, text_id);
  // 描画結果をテクスチャに書き込む
  gluBuild2DMipmaps(GL_TEXTURE_2D,
                    GL_RGBA,
                    text_surface->get_width(),
                    text_surface->get_height(),
                    GL_RGBA,
                    GL_UNSIGNED_BYTE,
                    text_surface->get_data());
}

void draw_text() {
  const vertex v[4] = {
      {0, 0, 0, 1, 1, 1, 1, 0, 0},
      {static_cast<float>(text_extents.width), 0, 0, 1, 1, 1, 1, 1, 0},
      {0, static_cast<float>(text_extents.height), 0, 1, 1, 1, 1, 0, 1},
      {static_cast<float>(text_extents.width), static_cast<float>(text_extents.height), 0, 1, 1, 1, 1, 1, 1}
    };
    glBindTexture(GL_TEXTURE_2D, text_id);
    glVertexPointer(3, GL_FLOAT, sizeof(v[0]), &v[0].x);
    glColorPointer(4, GL_FLOAT, sizeof(v[0]), &v[0].red);
    glTexCoordPointer(2, GL_FLOAT, sizeof(v[0]), &v[0].u);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
} 

サイズ計算用に空のサーフェイスとコンテキストを用意しているのが若干無駄な感はありますがとは言っても毎回巨大なサーフェイスを作るのもなんだかなーと思うので、これは最初に一つ作っておいて、文字を描画するときはそれを毎回流用してサイズ計算すればいいのではないかなとか思ってます。マルチスレッドになると排他制御が必要になりそうですがその辺はご愛嬌と言うことで。