Posted at

Rust を使って Win32 な OpenGL プログラミングを始める


はじめに

この記事は


  • Rust を使って Win32 な OpenGL プログラミングをしたい

という人を対象にした、忘却録的な記事です(・∀・)。

特に理由はないですが、glut を使用しないWin32API だけ で実現します(・∀・)。

特に理由はないです(・∀・)w

(大事ではないですが2回いいました)

開発環境には rustc 1.36.0 (a53f9df32 2019-07-03) にて確認しました(・∀・)。

Rust の知名度が上がってきているけど、何故かまだまだ Windows 向けの情報が少ないようなので、書いておけば誰かの役に立つかもしれないと思って書いておきます(・∀・)。


Win32API を使用


Hello World 以前

まずは Win32API を使用するビルドを行います(・∀・)。

簡単にコードをあげておきますと


main.rs


// main ではなく WinMain をエントリーポイントにするためこれが必要(・∀・)。
#![no_main]

extern crate winapi;

use libc::EXIT_SUCCESS;
use std::os::raw::{c_char, c_void, c_int};

// 関数の命名規則がスネークケースじゃないよって指示(・∀・)。
#[allow(non_snake_case)]
#[no_mangle]
pub extern "system" fn WinMain(_ : *const c_void, _ : *const c_void, _ : *const c_char, _ : c_int) -> c_int {

return EXIT_SUCCESS
}



Cargo.toml


[package]
name = "cpmap"
version = "0.1.0"
authors = ["mao <mao.lembryo@gmail.com>"]
edition = "2018"

[dependencies]
libc = "0.2.60"

[dependencies.winapi]
version = "0.3.7"


のようになります(・∀・)。

Win32API を使用するにはwinapi クレートを使用します(・∀・)。

他のクレートでも使用できるかも知れませんが、未調査です(・∀・)。


どうも、Microsoft が Rust を気に入っているようなので、そのうち公式のクレートが作成されるかも知れませんね(・∀・)。


libc クレートは EXIT_SUCCESS を使用したいがために extern してます(・∀・)。

余計な依存関係を増やしたくない場合は Cargo.toml から外して 0 を返してあげればよいでしょうに。


画面を表示

MessageBox の呼び出しは私の過去の記事とかにあるので割愛(・∀・)。

(対して難しくない)

ここでは CreateWindowEx を呼び出して Windows っぽく Window を生成して表示してみます(・∀・)。

コードは以下のような感じになります(・∀・)。


main.rs


unsafe {
let hwnd = CreateWindowExW(
0,
"STATIC".to_unicode().as_ptr(),
"テスト".to_unicode().as_ptr(),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
null_mut(),
null_mut(),
null_mut(),
null_mut());

ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);

let mut msg = zeroed::<MSG>();
while GetMessageW(&mut msg, null_mut(), 0, 0) != 0 {
TranslateMessage(&mut msg);
DispatchMessageW(&mut msg);
}
};


winapi クレートには何故か CreateWindow 関数がないようでした(・∀・)。

まあ CreateWindowEx 関数の最初の引数に 0 を渡せば CreateWindow と同じ動作なので問題はありません(・∀・)。


注意:エラー処理は省いています(・∀・)。


構造体の初期化は mem::zeroed::<*> を使用しています(・∀・)。

どこにでもある Hello World! アプリケーションです(・∀・)。

use は省略します(・∀・)。

注意点は str::to_unicode() なる関数です(・∀・)。

これは以下のように実装しています(・∀・)。


main.rs


pub(crate) trait Win32Helper {
fn to_unicode(&self) -> Vec<u16>;
}

impl Win32Helper for str {
fn to_unicode(&self) -> Vec<u16> {
self.encode_utf16().chain(Some(0)).collect()
}
}


標準の str 型に to_unicode なるトレイトを追加して、クレート内ならごく普通に文字列リテラルを書くとそのインスタンスに to_unicode を追加できます(・∀・)。

注意としては、このコードを実行すると、なんのメッセージも処理をしていないので デフォルトの動作さえしないため終了すら出来ません(・∀・)。

IDE からデバッグ実行するなどして、デバッガから強制終了できるような環境で実行してください(・∀・)。


OpenGL を使用する

お待ちかねの OpenGL です(・∀・)。

まずは GDI を使用するために Cargo.toml を修正します(・∀・)。


Cargo.toml


[dependencies.winapi]
version = "0.3.7"
features = ["winuser", "wingdi"]


ここでは最低限の動作として先ほど作成した Window の STATIC の部分を ピンク色 にするだけにします(・∀・)。


main.rs


// CreateWindowExW で生成した Window のハンドルからデバイスコンテキストを取得(・∀・)。
let dc = GetDC(hwnd);

// PIXELFORMATDESCRIPTOR を初期化(・∀・)。
let mut pomd = zeroed::<PIXELFORMATDESCRIPTOR>();
pomd.nSize = size_of::<PIXELFORMATDESCRIPTOR>() as u16;
pomd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pomd.cColorBits = 32;
pomd.cDepthBits = 32;

SetPixelFormat(dc, ChoosePixelFormat(dc, &pomd), &pomd);
let mut context = wglCreateContext(dc);
wglMakeCurrent(dc, context);


構造体の初期化には mem::zeroed を使用しています(・∀・)。

これを使用すると指定した構造体の中身を 0 で埋め尽くしたインスタンスを作成してくれいます(・∀・)。

PIXELFORMATDESCRIPTOR 構造体の設定自体は最低限の設定をしています(・∀・)。

・OpenGl を使用する(PFD_SUPPORT_OPENGL

・ダブルバッファリングを行う(PFD_DOUBLEBUFFER

・32ビットカラーを使用する(cColorBits / cDepthBits )

wglCreateContextwglMakeCurrent などの関数は winapi::um::wingdi にあります(・∀・)。

描画処理は以下のとおりです(・∀・)。


main.rs


while GetMessageW(&mut msg, null_mut(), 0, 0) != 0 {
TranslateMessage(&mut msg);
DispatchMessageW(&mut msg);

// 面倒なのでメッセージループの中に描画処理を入れちゃう(・∀・)w
glColor3f(1.0f32, 0.0f32, 1.0f32);
glRecti(1, 1, -1, -1);
SwapBuffers(dc);
}


実際にはちゃんと考えたシーケンスで描画処理を行ってください(・∀・)w

さて glColor3fglRecti ですが、Win32 向けに公開されているクレートが見つかりませんでした(・∀・)。

大抵の OpenGL のサンプルは GLUT を使用したり Windows 向けではなかったりするようですね(・∀・)。

Windows 10 ユーザーとしては息苦しい世の中です(・∀・)w

なので


main.rs


pub type GLint = c_int;
pub type GLfloat = c_float;
extern "C" {
fn glColor3f(red: GLfloat, green: GLfloat, blue: GLfloat);
fn glRecti(x1: GLint, y1: GLint, x2: GLint, y2: GLint);
}


のようにC言語の関数レベルで宣言を行ってあげます(・∀・)。

実態はリンクされる Win32API のライブラリに含まれているため実行には差し支えありません(・∀・)。


最後に

Rust で可能な限り少ない依存関係で Win32API レベルの OpenGL プログラミングをしたいひとが、少しでも参考にしてくれたらと思います(・∀・)。

(そんなひとがいるのか知らないけどw)