WindowsでRust環境を作ってGtk3でOpenGLする

VSCodeのデバッガでbreakすることができて、GTK3も動いた。そのあたりのメモ

* 2018/10
* Windows10(64bit)
* msys2( `mingw-w64-x86_64-gcc` )

![triangle.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/dfb27d79-79a1-25cc-9b61-262fb4f42b45.jpeg)

とりあえず動くようになった。

* https://github.com/ousttrue/rust_gtk_glarea

# rustのインストールとtoolchainはどれにするか

https://rustup.rs/

でインストールする。
選択肢は

* `msvc` (default) と `gnu`
* `stable` (default)と `nightly`

`stable` と `nightly` では、いろいろなところでnightly版を要求された。
 
`msvc` はデバッガに `lldb` を使う( https://github.com/vadimcn/vscode-lldb )ところがうまくいかなかった。
`gnu` は gdbを使い、これはうまく動いた。

`nightly-gnu` で行く。

## インストールの確認

```
>rustup show
Default host: x86_64-pc-windows-msvc

installed toolchains
--------------------

stable-x86_64-pc-windows-gnu
stable-x86_64-pc-windows-msvc
nightly-x86_64-pc-windows-gnu (default) # <= これ
nightly-x86_64-pc-windows-msvc
```

rustupで最初から `nightly-x86_64-pc-windows-gnu` を選択するか
下記のようにして変更する。

```
> rustup install nightly-x86_64-pc-windows-gnu
> rustup default nightly-x86_64-pc-windows-gnu
```

# gccとgdbをインストールして、vscodeから見えるようにする

## msys2のインストール

https://www.msys2.org/

下記のパッケージをインストールする。

* `mingw-w64-x86_64-gcc`
* `mingw-w64-x86_64-gdb`

`vscode` が `gcc` と `gdb` を見つけられるように、Windowsの環境変数 `PATH` に追加する。

例
`D:\msys64\mingw64\bin`

## vscode

動作確認用に `rust` の新しいプロジェクトを作成する。

```
# --binがデフォルトになった
> cargo new hello
```

`hello` フォルダを `vscode` で開く。
vscodeの拡張機能をインストールする。

* `Rust(rls)`。インストール後、rls, racer, rustfmtなどをインストールするぽいのでstatusバーのアップデートが消えるまで、裏でインストールしている？
* `Native Debug`。rustの実行ファイルをgdbでデバッグする

`main.rs` を開いて `ctrl-shift-b` で `cargo build` でビルド。
`Debug` タブで構成を追加して、`GDB` を選択。`launch.json` の `taget` を `./target/debug/hello` に書き換える。F5で実行。 

```js:launch.json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        
        {
            "name": "Debug",
            "type": "gdb",
            "request": "launch",
            "target": "./target/debug/hello",
            "cwd": "${workspaceRoot}"
        }
    ]
}
```

`main.rs` にbreakポイントをセットして、実行を停止できることを確認する。

![break.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/359a1f8b-5788-8a55-c0bc-045c596b11cf.jpeg)

# gtk3でGUI

## msys2

```
$ pacman -Sl | grep gtk3
mingw32 mingw-w64-i686-gtk3 3.24.1-1
mingw32 mingw-w64-i686-webkitgtk3 2.4.11-6
mingw64 mingw-w64-x86_64-gtk3 3.24.1-1
mingw64 mingw-w64-x86_64-webkitgtk3 2.4.11-6
$ pacman -Sy mingw-w64-x86_64-gtk3
```

## vscode
* http://gtk-rs.org/docs/gtk/

のサンプルコードを実行してみる。

```rs:main.rs
extern crate gtk;
use gtk::prelude::*;
use gtk::{ButtonsType, DialogFlags, MessageDialog, MessageType, Window};

fn main() {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return;
    }
    MessageDialog::new(
        None::<&Window>,
        DialogFlags::empty(),
        MessageType::Info,
        ButtonsType::Ok,
        "Hello World",
    )
    .run();
}
```

```toml:Cargo.toml
[dependencies.gtk]
version = "0.1.0"
features = ["v3_16"]
```

ビルドして実行。

![gtk.jpg](https://qiita-image-store.s3.amazonaws.com/0/59664/f520cb5f-d9c8-c8d8-da83-74cff25d99c8.jpeg)

動いた。

# OpgnGLのwidget

`GtkGLArea` を使う

## 参考になるコードを探す
* https://github.com/kion-dgl/GtkGLArea/tree/master/02_add_opengl

これをRustに移植してみたい。
その前に、msysでビルドして実行してみる。

cloneして02_add_openglをvscodeで開いた。

shellをmsys2のbashにする。

```js:.vscode/settings.json
{
    "terminal.integrated.shell.windows": "D:\\msys64\\usr\\bin\\bash.exe",
    "terminal.integrated.env.windows": {
        "MSYSTEM": "MINGW64",
        "CHERE_INVOKING": "1",
        "PATH": "/mingw64/bin:/usr/local/bin:/usr/bin:/bin",
    },
}
```

```js:.vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "make",
            "type": "shell",
            "command": "mingw32-make", // <- 上記のbash上で/mingw64/bin/mingw32-makeする意図
            "problemMatcher": "$gcc",
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}
```

```js:.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug",
            "type": "gdb",
            "request": "launch",
            "target": "./a",
            "cwd": "${workspaceRoot}",
            "preLaunchTask": "make"
        }
    ]
}
```

MakefileをWindows向けに手直し。

```Makefile
# OpenGL32とGLEW32。glewはpacmanで入れる
all:
	gcc `pkg-config --cflags gtk+-3.0` main.c -lOpenGL32 -lGLEW32 `pkg-config --libs gtk+-3.0`
```

msys2で動いた。`Rust` に移植してみよう。

## Rust版

### GLAreaが無い

```rs
let gl_area = gtk::GLArea::new(); // GLAreaが無い　？
```

GLAreaで検索してリポジトリを発見。

* https://github.com/itadinanta/gfx-gtk

こちらは動いた。
プロジェクトを調べる。

```toml:Cargo.toml
[dependencies]
gio = "^0"
gtk = { version = "^0", features=["v3_16"] } # featuresでGtkに含まれる内容が変わる？
```

たぶん `v3_16` から GtkGLArea のサポートが入った。

https://www.phoronix.com/scan.php?page=news_item&px=MTgyNjg

### OpenGLの初期化
C++だとglewとかでやるところ。
こちらも gfx-gtk さんに書いてある通りにしたらできた。

c++だとこれだけらしいのだが、

```cpp
#include <epoxy/gl.h>
#include <epoxy/glx.h>
```

https://github.com/ousttrue/rust_gtk_glarea/blob/3ac54356d34ecab07d44a7007deb9565ee8ea03f/src/gl_loader.rs#L63-L74

`D:\msys64\mingw64\bin\libepoxy-0.dll` から取ってきているぽい？

```rust
pub fn load() {
    let loader = Failover(
        DlProcLoader::current_module(),
        Failover(
            DlProcLoader::open(Path::new("libepoxy-0")),
            Failover(
                DlProcLoader::open(Path::new("libepoxy0")),
                DlProcLoader::open(Path::new("libepoxy")),
            ),
        ),
    );
    epoxy::load_with(fn_from(loader));
    gl::load_with(epoxy::get_proc_addr);
}
```

`epoxy::load_with` で先に関数ポインタを得て、それを `gl::load_with` に渡すということらしい。

### mainとGTK組み立て

`gtk::GLArea::new()` して初期化する。
GtkがOpenGLのコンテキストを作成した後の `connect_realize` で初期化するのが重要。

```rust:main.rs
extern crate epoxy;
extern crate gio;
extern crate gl;
extern crate gtk;

use gio::prelude::*;
use gtk::prelude::*;
use std::env::args;

mod gl_renderer;
mod gl_loader;


fn build_ui(application: &gtk::Application) {
    let window = gtk::ApplicationWindow::new(application);

    window.set_title("Window");
    window.set_default_size(640, 480);
    window.set_border_width(10);
    window.set_position(gtk::WindowPosition::Center);
    window.connect_delete_event(move |win, _| {
        win.destroy();
        Inhibit(false)
    });

    let gl = std::rc::Rc::new(gl_renderer::GlRenderer::new());

    let gl_area = gtk::GLArea::new();
    //gl_area.set_vexpand(true);
    //gtk_widget_set_hexpand(gl_area, TRUE);
    let gl_clone = gl.clone();
    gl_area.connect_realize(move |gl_area| {
        gl_area.make_current();
        /*
        if (gtk_gl_area_get_error(area) != NULL) {
            fprintf(stderr, "Unknown error\n");
            return;
        }
        */
        gl_area.set_has_depth_buffer(true);

        gl_loader::load();

        gl_clone.initialize();
    });

    let gl_clone = gl.clone();
    gl_area.connect_render(move |_area, _context| {
        gl_clone.render();

        Inhibit(true)
    });

    window.add(&gl_area);

    window.show_all();
}

fn main() {
    let application = gtk::Application::new("com.github.basic", gio::ApplicationFlags::empty())
        .expect("Initialization failed...");

    application.connect_startup(|app| {
        build_ui(app);
    });

    application.run(&args().collect::<Vec<_>>());
}
```

libepoxy による初期化のために `epoxy` と `shared_library` への依存を追加。

```toml:Cargo.toml
[dependencies]
gio = "^0"
gtk = { version = "^0", features=["v3_16"] }
gl = "0.10"
epoxy = "0.0.4"
shared_library = "0.1.7"
```

### OpenGL移植メモ

OpenGLそのもののコードに辿り着いた。

https://github.com/ousttrue/rust_gtk_glarea/blob/3ac54356d34ecab07d44a7007deb9565ee8ea03f/src/renderers/basic_renderer.rs

`unsafe` 呼び出しとpointerに不慣れだとちょっと大変。
よく使いそうなところをピックアップしてみる。

#### primitiveのポインタ

```rs
i32 id = 0;

unsafe {
  unsafe_func(&id); // *const i32 が要求されているとして referenceをそのまま渡せる
}
```

#### `*const u8` から文字列

```rs
        let renderer = unsafe {
            let p = gl::GetString(gl::RENDERER);
            CStr::from_ptr(p as *const i8)
        };
        println!("Renderer: {}", renderer.to_string_lossy());
```

#### `void` の nullptr

```rs
let p = std::ptr::null();
```

#### バッファを渡して文字列を得る

`with_capacity` でバッファ長を確保して、
後で `set_len` する。

```rs
            let mut infoLog = Vec::<u8>::with_capacity(infoLen as usize);
            //std::vector<char> infoLog(infoLen);
            gl::GetShaderInfoLog(
                target,
                infoLen,
                &mut infoLen as *mut i32,
                infoLog.as_mut_ptr() as *mut i8,
            );
            infoLog.set_len(infoLen as usize); // <- 重要
            let cs = CString::from_vec_unchecked(infoLog);
            println!("Error compiling shader: {}", cs.to_string_lossy());
```

#### 配列、Vecからポインタを得る

`as_ptr`, `as_mut_ptr`

#### キャストする

```rs
// p: *const u8
p as *const i8
```

#### RefCell
なんか使い方が違うような気がする。

# 参考
## gtk-rs
* http://gtkrsbook.sunnyone.org/

## libepoxy
GTK3が使っているglew的なライブラリ
 
* https://github.com/anholt/libepoxy
* https://github.com/mjkoo/epoxy-rs
* https://github.com/itadinanta/gfx-gtk
* https://github.com/antoyo/servo-gtk/blob/master/src/view.rs

## opengl
unsafeの使い方が参考になる

* https://github.com/nukep/rust-opengl-util