読者です 読者をやめる 読者になる 読者になる

ゆとりーなの日記

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

gtkmmでglx

gtkmmOpenGLをやるとしたら便利なラッパとしてgtkglextmmというものがありますが敢えてglxを使うとした時にどうすればいいかの雰囲気が掴めたっぽいので纏めておきます。あくまで雰囲気なので悪しからず。
取り敢えずglxを使うにはXのディスプレイとウィンドウIDが必要になるのですがこれはそれぞれgdkx.hにある怪しいマクロがを使えばget_window()でやってくるGlib::RefPtrを元に取得出来るのでこれを使おうぜってのがミソです。多分。
まずはglxを扱うためのwindowをGtk::Windowを継承して作ります。

class glx_window : public Gtk::Window {
 public:
  glx_window() {
    add(draw_area_);
    show_all_children();
  }
  
 private:
  glx_draw_area draw_area_;
};

int main() {
  Gtk::Main kit;
  glx_window window;
  kit.run(window);
  return 0;
}

メンバには実際に描画を行うGtk::DrawingAreaを継承したglx_draw_areaを持っておき、それをコンストラクタで追加します。main函数の方も殆どgtkmmのサンプル通りで大丈夫ですが、3系からGtk::Mainの引数なし版コンストラクタが提供されるようになったので、コマンドライン引数がない時はnullptrを渡すみたいなことをしなくてよくなったみたいです。
で、glx_draw_areaはこんな感じにしてみました。

struct visual_info_delete {
  void operator ()(XVisualInfo *visual) const {
    XFree(visual);
  }
};

class glx_draw_area : public Gtk::DrawingArea {
 protected:
  virtual bool on_delete_event(GdkEventAny *event) {
    const auto display = GDK_WINDOW_XDISPLAY(Glib::unwrap(get_window()));
    glXMakeCurrent(display, 0, nullptr);
    if (context_) {
      glXDestroyContext(display, context_);
    }
    return Gtk::DrawingArea::on_delete_event(event);
  }
  
  virtual void on_realize() {
    Gtk::DrawingArea::on_realize();
    init_glx();
    init_gl();
  }
  
  virtual bool on_draw(const Cairo::RefPtr<Cairo::Context> &) {
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_TRIANGLES);
    glColor3f(1.f, 0.f, 0.f);
    glVertex2f(get_width() / 2.f, 0.f);
    glVertex2f(0.f, get_height());
    glVertex2f(get_width(), get_height());
    glEnd();
    glXSwapBuffers(GDK_WINDOW_XDISPLAY(Glib::unwrap(get_window())), GDK_WINDOW_XID(Glib::unwrap(get_window())));
    return true;
  }

 private:
  void init_glx() {
    GLint glx_attrs[] = {
      GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_DOUBLEBUFFER, None
    };
    const auto display = GDK_WINDOW_XDISPLAY(Glib::unwrap(get_window()));
    const std::unique_ptr<XVisualInfo, visual_info_delete> visual_info(glXChooseVisual(display, DefaultScreen(display), glx_attrs));
    if (!visual_info) {
      throw std::runtime_error("failed glXChooseVisual.");
    }
    context_ = glXCreateContext(display, visual_info.get(), nullptr, True);
    if (!context_) {
      throw std::runtime_error("failed glXCreateContext.");
    }
    if (!glXMakeCurrent(display, GDK_WINDOW_XID(Glib::unwrap(get_window())), context_)) {
      throw std::runtime_error("failed glXMakeCurrent.");
    }
  }
  
  void init_gl() {
    glViewport(0, 0, get_width(), get_height());
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.f, get_width(), get_height(), 0.f, 0.f, 1.f);   
    glClearColor(0.f, 0.f, 0.f, 0.f);
  }
  
  GLXContext context_;
};

XのディスプレイはGDK_WINDOW_XDISPLAY、ウィンドウIDはGDK_WINDOW_XIDを使えばget_windowをアンラップしたものから取得できるようなのでこれをそれぞれglxの函数に渡して初期化してやります。因みに最初調べていたときはウィンドウの方はGDK_WINDOW_XWINDOWというマクロを使う様な記述を見かけたのでそれで試したのですがそのようなマクロはないと怒られたので、代わりのマクロないかなーとヘッダ漁ってたら見つかったのがGDK_WINDOW_XIDだったりします。この辺いかにも怪しそうな香りがしますが取り敢えず上手く動いているのでいいのかなという感じです。
ディスプレイとウィンドウIDさえ手に入れば後はその辺のglxの初期化をしてやればいいだけなのでそこまで難しくはありません。但し初期化するタイミングが重要で、get_windowをしてもウィンドウが実体化される前だと有効なものが返ってこないのでマクロに突っ込んでも無意味です。というわけでウィンドウが実体化された時に呼ばれると思われるon_realize()をオーバーライドしてそれの中でやってやるようにします。init_glx()がそれです。
あとは取得したコンテキストはお行儀よく終了時に解放してやりたいのでこれはメンバに持っておいて、ウィンドウが消滅する時に呼ばれると思われるon_delete()の中で解放やることにしました。この時nullチェックを入れるのはnullのものを突っ込んでglXDestroyContextした時の挙動がイマイチよく分からなかったので念のためです。あとスマポにしたいところでもあるのですが、解放タイミングがデストラクタだと問題ありそうな気もするので取り敢えず生ハンドルにしておきました。
init_gl()は単純にOpenGLの設定をしているだけです。で、on_draw()で三角形を描画するいつものOpenGLコードを書いてやれば晴れて赤い三角形が表示されるわけです。