はじめに
GPUプログラミングといえば、GLSL、HLSL、WGSLなどの専用シェーダー言語を使うのが一般的です。しかし、これらの言語は大規模なコードベースの管理が難しく、現代的なプログラミング言語と比べると機能面で見劣りします。
Rust GPUは、Rustプログラミング言語を使ってGPUシェーダーを記述できるプロジェクトです。今回は実際にRust GPUをクローンして、サンプルシェーダーを実行してみました。
注意: Rust GPUは現在も活発に開発中の初期段階プロジェクトです。シンプルなシェーダーのコンパイルと実行は機能し、コアライブラリの大部分もコンパイルできますが、まだ実装されていない機能も多くあります。技術的には使用可能ですが、本番環境での使用には適していません。
Rust GPUのクローンと実行
以下の手順でRust GPUをクローンし、サンプルを実行します。
# リポジトリをクローン
git clone https://github.com/Rust-GPU/rust-gpu.git
cd rust-gpu
# サンプルシェーダーを実行
cd examples/runners/wgpu
cargo run -- --shader Sky
Skyシェーダーが実行されると、美しい空のレンダリングが表示されます。
他のシェーダーも試してみることができます:
# 最もシンプルなシェーダー
cargo run -- --shader Simplest
# マウスインタラクション可能なシェーダー
cargo run -- --shader Mouse
サンプルコードを見てみる
最もシンプルなシェーダー
examples/shaders/simplest-shader/src/lib.rs
にある最もシンプルなシェーダーのコードを見てみましょう:
#[spirv(fragment)]
pub fn main_fs(output: &mut Vec4) {
*output = vec4(1.0, 0.0, 0.0, 1.0);
}
#[spirv(vertex)]
pub fn main_vs(
#[spirv(vertex_index)] vert_id: i32,
#[spirv(position, invariant)] out_pos: &mut Vec4,
) {
*out_pos = vec4(
(vert_id - 1) as f32,
((vert_id & 1) * 2 - 1) as f32,
0.0,
1.0,
);
}
このシェーダーは非常にシンプルで、フラグメントシェーダーは赤色の出力を行い、頂点シェーダーは画面全体を覆う三角形を生成します。#[spirv(...)]
アトリビュートによって、関数がシェーダーステージとして機能するように指定されています。
空のシェーダー
examples/shaders/sky-shader/src/lib.rs
には、より複雑な空のシェーダーが実装されています。
#[spirv(fragment)]
pub fn main_fs(
#[spirv(frag_coord)] in_frag_coord: Vec4,
#[spirv(push_constant)] constants: &ShaderConstants,
output: &mut Vec4,
// ...
) {
let frag_coord = vec2(in_frag_coord.x, in_frag_coord.y);
let mut uv = (frag_coord - 0.5 * vec2(constants.width as f32, constants.height as f32))
/ constants.height as f32;
uv.y = -uv.y;
let eye_pos = vec3(0.0, 0.0997, 0.2);
let sun_pos = vec3(0.0, 75.0, -1000.0);
let dir = get_ray_dir(uv, eye_pos, sun_pos);
// Preethamの空モデルを評価
let color = sky(dir, sun_pos, sun_intensity_extra_spec_const_factor);
*output = tonemap(color).extend(1.0)
}
このシェーダーはPreethamの空モデルを実装し、リアルな空をレンダリングします。光の散乱とトーンマッピングを計算して美しい空の表現を実現しています。
Rustの強みを活かしたGPUプログラミング
Rust GPUの魅力は、Rustならではの機能をGPUプログラミングに持ち込める点です:
Rustの特徴を活かしたGPUプログラミング
実際のサンプルコードからわかるように、Rust GPUは以下のようなRustの特徴をGPUプログラミングに活かしています:
// マウスシェーダーにおけるトレイトの活用例(examples/shaders/mouse-shader/src/lib.rs)
pub trait Shape: Copy {
fn distance(self, p: Vec2) -> f32;
fn union<S>(self, other: S) -> Union<Self, S> {
Union(self, other)
}
fn intersect<S>(self, other: S) -> Intersect<Self, S> {
Intersect(self, other)
}
// ...
}
マウスシェーダーでは、トレイトを使って図形の操作を抽象化しています。これはRustの型システムの強みを活かした実装例です。
また、CPUとGPU間のデータ共有も簡潔に行えます:
// SharedConstantsの例
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct ShaderConstants {
pub width: u32,
pub height: u32,
pub time: f32,
// ...
}
まとめ
プロジェクトはまだ初期段階であり、リポジトリのREADMEにも記載されているように、本番環境での利用は推奨されていませんが、サンプルシェーダーの実行などの実験的な用途には使えることを確認できました。
興味のある方は、ぜひRust GPU公式リポジトリをチェックしてみてください。
参考リンク
- Rust GPU公式リポジトリ
- Rust GPU Book
- ShaderToy移植プロジェクト - 例が多数あります