PR

Rust と WebAssembly と wgpu を使ってみる

1. はじめに

WebGPU は、WebGL の後継として、より高性能なグラフィクス API を提供することを目的としています。
wgpu は、Rust で書かれた WebGPU という Webブラウザ向けのグラフィクス API の実装です。

要は、wgpu を使えば、Rust で書いたコードを Webブラウザ上で動かすことができるということのようです。

2. ログの出力

Cargo.toml に以下のように記述します。

[lib]
crate-type = ["cdylib"]

[dependencies]
log = "0.4.27"
console_error_panic_hook = "0.1.7"
console_log = { version ="1.0.0", features = ["color"] }
wasm-bindgen = "0.2.100"
wasm-bindgen-futures = "0.4.50"
web-sys = { version = "0.3.77", features = ['console', 'Document', 'Element', 'HtmlCanvasElement', 'Window'] }
  • cdylib は、Rust のライブラリを WebAssembly にコンパイルするためのターゲットです。
  • log は、Rust のロギングライブラリです。
  • console_error_panic_hook は、エラーが発生したときに、コンソールにエラーメッセージを表示するためのライブラリです。
  • console_log は、Rust のログを JavaScript のコンソールに出力するためのライブラリです。
  • wasm-bindgen は、Rust と JavaScript の間でデータをやり取りするためのライブラリです。
  • wasm-bindgen-futures は、非同期処理を行うためのライブラリです。
  • web-sys は、Web API を Rust から使うためのライブラリです。

lib.rs に以下のように記述します。

use wasm_bindgen::prelude::*;
use web_sys::*;

#[wasm_bindgen]
pub fn start() {
    std::panic::set_hook(Box::new(console_error_panic_hook::hook));
    console_log::init_with_level(log::Level::Debug).expect("error initializing log");

    log::info!("start function called");

    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id("Canvas").unwrap();
    let canvas: web_sys::HtmlCanvasElement = canvas
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .map_err(|_| ())
        .unwrap();

    log::info!("{}", &format!("width: {}", canvas.width()));
    log::info!("{}", &format!("height: {}", canvas.height()));
}

#[wasm_bindgen(start)] は、WASM が読み込まれたときに呼び出される関数です。
関数が呼び出されるタイミングを指定するために、通常のエクスポート関数として定義するため #[wasm_bindgen] に変更します。

index.html に以下のように記述します。

<body>
    <h1>WASM Example</h1>
    <canvas id="Canvas" width="640" height="480"></canvas>
    <script type="module">
        import init, { start } from './wasm_wgpu.js';

        async function run() {
            await init();
            console.log("WASM initialized");
            start();
        }

        run();
    </script>
</body>

上記のコードでブラウザを開くと、コンソールに以下のようなログが出力されます。

WASM initialized // JavaScript の初期化が完了したことを示すログ
[INFO] start function called // Rust の start 関数が呼び出されたことを示すログ
[INFO] width: 640 // canvas の幅を示すログ
[INFO] height: 480 // canvas の高さを示すログ

3. wgpu の設定周り

次に、wgpu を使ってみます。
Crate wgpumore-code を参考します。

Cargo.toml に以下を追加します。

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wgpu = "25.0.0"
bytemuck = "1.22.0"
  • rlib は、Rust のライブラリとしてコンパイルするためのオプションです。
  • wgpu は、Rust で書かれた WebGPU の実装です。
  • bytemuck は、Rust のバイト列を扱うためのライブラリです。

3.1. ラッパー関数の作成

後述の Adapter を使うために、ラッパー関数に変更します。

use wasm_bindgen::prelude::*;
use web_sys::*;
use wgpu::*;

#[wasm_bindgen]
pub fn start() {
    // ラッパー関数を呼び出す
    wasm_bindgen_futures::spawn_local(async {
        start_async().await;
    });
}

async fn start_async() {
    std::panic::set_hook(Box::new(console_error_panic_hook::hook));
    console_log::init_with_level(log::Level::Debug).expect("error initializing log");

    log::info!("start function called");

    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id("Canvas").unwrap();
    let canvas: web_sys::HtmlCanvasElement = canvas
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .map_err(|_| ())
        .unwrap();

    log::info!("{}", &format!("width: {}", canvas.width()));
    log::info!("{}", &format!("height: {}", canvas.height()));

    ...
}

Adapter で .await を使うために、非同期関数に変更します。
wasm_bindgen_futures::spawn_local を使うことで、非同期関数を呼び出すことができます。
以降 start_async 関数に追記していきます。

3.2. GPU インスタンスの作成

    let instance = wgpu::Instance::new(
        &wgpu::InstanceDescriptor {
            backends: wgpu::Backends::all(),
            ..Default::default()
        },
    );

wgpu::Backends::all() は、すべてのバックエンドを使用することを示します。
..Default::default() は、他のフィールドはデフォルトの設定を使用することを示します。

3.3. Surface の作成

    let surface_target = wgpu::SurfaceTarget::Canvas(canvas.clone());
    let surface = instance
        .create_surface(surface_target)
        .expect("Failed to create surface");

surface_target を作成して、その対象を HTML の canvas 要素に設定しています。
Surface は、GPU に描画するウィンドウやキャンバスのようなもの。バックバッファを持ち、後で swap_chainsurface_texture などを通して描画される対象になります。
expect(...) は、Surface の作成に失敗した場合にエラーを出してクラッシュさせるためのエラーハンドリング。

3.4. Adapter の取得

    let adapter = instance
        .request_adapter(
            &wgpu::RequestAdapterOptions {
                compatible_surface: Some(&surface),
                power_preference: wgpu::PowerPreference::default(),
                force_fallback_adapter: false,
            }
        )
        .await
        .expect("Failed to find an appropriate adapter");

wgpu::RequestAdapterOptions で、アダプタを取得するためのオプションを指定し、request_adapter を呼び出すことで、アダプタを取得しています。
compatible_surface で、surface に適合するアダプタを取得しています。
power_preference で、デフォルトの電力設定を使用することを示します。
force_fallback_adapter で、false を設定し、フォールバックアダプタを使用しないことを示します。
非同期関数であり、非同期処理の完了を待つ必要があるため .await を使います。

3.5. Device と Queue を作成

    let (device, queue) = adapter
        .request_device(
            &wgpu::DeviceDescriptor {
                label: None,
                required_features: wgpu::Features::empty(),
                required_limits: wgpu::Limits::downlevel_webgl2_defaults(),
                memory_hints: Default::default(),
                trace: wgpu::Trace::default(),
            },
        )
        .await
        .expect("Failed to create device");

wgpu::DeviceDescriptor で、デバイスを取得するためのオプションを指定し、request_device を呼び出すことで、デバイスとキューを取得しています。
label は、デバイスのラベルを指定するためのオプションです。デバッグ時に識別しやすくなります。
required_features は、デバイスで有効にする必要がある機能のオプションです。デフォルトでは機能を指定しない場合、空の値を指定します。
required_limits は、デバイスが満たすべきリソース制限です。WebGL2 の下位互換性を持つ制限を指定しています。
memory_hints は、デバイスのメモリに関するヒントを指定するためのオプションです。デフォルト設定しています。
trace トレース(デバッグ用の記録)を有効にするかどうか。デフォルトを設定 false しています。
非同期関数であり、非同期処理の完了を待つ必要があるため .await を使います。

3.6. Surface の設定

    let surface_capabilities = surface.get_capabilities(&adapter);

    let surface_format = surface_capabilities.formats.iter()
        .find(|f| f.is_srgb())
        .copied()
        .unwrap_or(surface_capabilities.formats[0]);

    let config = wgpu::SurfaceConfiguration {
        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
        format: surface_format,
        width: canvas.width() as u32,
        height: canvas.height() as u32,
        present_mode: wgpu::PresentMode::Fifo,
        alpha_mode: wgpu::CompositeAlphaMode::Auto,
        view_formats: vec![],
        desired_maximum_frame_latency: 2,
    };
    surface.configure(&device, &config);

get_capabilities は、Surface オブジェクトに対して、指定された Adapter がサポートする描画の設定やフォーマットの情報を取得します。
surface_capabilities.formats の中から SRGB フォーマットを優先的に選択し、見つからない場合はデフォルトのフォーマットを使用します。
wgpu::SurfaceConfiguration で、Surface の設定を行います。
usage で、描画に使用するテクスチャの用途を指定します。wgpu::TextureUsages::RENDER_ATTACHMENT で、レンダーターゲットとして使用します。
format で、選択したフォーマットを指定します。
width で、描画対象の幅(ピクセル単位)を指定します。canvas の幅を指定します。
height で、描画対象の高さ(ピクセル単位)を指定します。canvas の高さを指定します。
present_mode で、描画の更新方法を指定します。wgpu::PresentMode::Fifo で、FIFO モード(垂直同期を有効にしたモード)を指定します。
alpha_mode は、描画の合成方法を指定します。wgpu::CompositeAlphaMode::Auto で、ブラウザが自動的に合成方法を決定します。
view_formats で、描画対象のフォーマットを指定します。空のベクタを指定しています。
desired_maximum_frame_latency は、描画のフレームレートを指定します。2 フレームのレイテンシを指定しています。

4. wgpu の描画周り

続いて頂点データから画面表示までのGPU描画処理の一連の流れの説明になります。

4.1. Vertex

#[repr(C)]
#[derive(Copy, Clone, Debug)]
struct Vertex {
    position: [f32; 3],
    color: [f32; 3],
}

unsafe impl bytemuck::Pod for Vertex {}
unsafe impl bytemuck::Zeroable for Vertex {}

const VERTICES: &[Vertex] = &[
    Vertex { position: [0.0, 0.5, 0.0], color: [1.0, 0.0, 0.0] },
    Vertex { position: [-0.5, -0.5, 0.0], color: [0.0, 1.0, 0.0] },
    Vertex { position: [0.5, -0.5, 0.0], color: [0.0, 0.0, 1.0] },
];

#[repr(C)] は、構造体のメモリレイアウトを C言語と同じ順序・アライメントでメモリに配置されます。これにより、RustコードをCやWebGPUのような外部ライブラリと安全にやり取りできるようになります。
position: [f32; 3] は、頂点の3次元空間での位置を表します。[f32; 3] は3つの32ビット浮動小数点数の配列です。
color: [f32; 3] は、頂点の色をRGB形式で表します。[f32; 3] は赤、緑、青の3つの成分を持つ配列です。

Vertex に対して、bytemuck クレートが提供するトレイト PodZeroable を実装しています。ただし、これらのトレイトは「unsafe(安全ではない)」としてマークされているため、unsafe キーワードを使用して明示的に実装する必要があります。

    let vertex_buffer = device.create_buffer_init(
        &wgpu::util::BufferInitDescriptor {
            label: Some("Vertex Buffer"),
            contents: bytemuck::cast_slice(VERTICES),
            usage: wgpu::BufferUsages::VERTEX,
        }
    );

頂点データを格納するためのバッファ(vertex_buffer)を作成します。
label は、デバッグ用のラベルを指定します。
contents は、バッファに格納するデータを指定します。bytemuck::cast_slice は、Rustの型をバイト列に変換するためのユーティリティ関数で、ここではVERTICESという配列をバイト列に変換しています。
usage は、バッファの用途を指定します。ここでは wgpu::BufferUsages::VERTEX が指定されており、このバッファが頂点データ用であることを示しています。

4.2. Shader

WebGPU Shading Language (WGSL) を使用して記述されたシェーダーを使い、GPU に描画するためのシェーダーコードを記述するためのものです。

shader.wgsl

struct VertexInput {
    @location(0) position: vec3<f32>,
    @location(1) color: vec3<f32>,
};

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) color: vec3<f32>,
};

@vertex
fn vs_main(vin: VertexInput) -> VertexOutput {
    var vout: VertexOutput;
    vout.color = vin.color;
    vout.clip_position = vec4<f32>(vin.position, 1.0);
    return vout;
}

@fragment
fn fs_main(fin: VertexOutput) -> @location(0) vec4<f32> {
    return vec4<f32>(fin.color, 1.0);
}

頂点シェーダー (vs_main)
vs_main は頂点シェーダーのエントリーポイントです。この関数は、GPU に送られる頂点データを処理し、クリップ空間座標に変換します。入力として VertexInput 構造体を受け取り、出力として VertexOutput 構造体を返します。

  • VertexInput 構造体: 頂点データの入力形式を定義します。
    @location(0)@location(1) は、それぞれ頂点の位置 (position) と色 (color) を示しています。これらは vec3<f32> 型で、3次元ベクトルを表します。
  • VertexOutput 構造体: 頂点シェーダーの出力形式を定義します。
    @builtin(position) はクリップ空間座標 (clip_position) を示し、@location(0) は色データを保持します。
    関数内では、入力の position を vec4<f32> 型に変換し、クリップ空間座標として設定します。また、入力の color をそのまま出力に渡します。

フラグメントシェーダー (fs_main)
fs_main はフラグメントシェーダーのエントリーポイントです。この関数は、頂点シェーダーから渡されたデータを受け取り、ピクセルの色を計算します。

  • 入力は VertexOutput 構造体を受け取り、出力として @location(0) に対応する色データを返します。
  • 出力は vec4<f32> 型で、RGB 色成分 (fin.color) にアルファ値 1.0 を追加して最終的な色を生成します。
    let shader = device.create_shader_module(
        wgpu::ShaderModuleDescriptor {
            label: Some("Shader"),
            source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
        }
    );

device.create_shader_module は、GPU デバイス上で使用するシェーダーモジュールを作成するためのメソッドです。このモジュールは、シェーダーコードを GPU にロードし、後でパイプラインに組み込むために使用されます。

wgpu::ShaderModuleDescriptor は、シェーダーモジュールの設定を指定します。
label は、デバッグ用のラベルを指定します。
source は、シェーダーコードのソースを指定します。

4.3. Pipeline

GPU パイプラインで使用するレイアウトを作成します。シェーダーがどのようにリソースにアクセスするかを定義します。

    let pipeline_layout = device.create_pipeline_layout(
        &wgpu::PipelineLayoutDescriptor {
            label: Some("Pipeline Layout"),
            bind_group_layouts: &[],
            push_constant_ranges: &[],
        }
    );

label は、デバッグ用のラベルを指定します。
bind_group_layouts は、シェーダーが使用するバインドグループレイアウトの配列を指定します。
バインドグループは、シェーダーがアクセスするリソース(例: ユニフォームバッファやテクスチャ)をグループ化したものです。ここではリソースを使用しない設定になっています。
push_constant_ranges は、プッシュコンスタントの範囲を指定します。
プッシュコンスタントは、シェーダーに小さなデータを効率的に渡すための仕組みです。ここではプッシュコンスタントを使用しない設定になっています。

4.4. Render

GPU 上でグラフィックスを描画する際の一連の設定を定義するもので、頂点シェーダーやフラグメントシェーダー、プリミティブの描画方法などを含むレンダリングパイプラインを作成する部分です。

    let render_pipeline = device.create_render_pipeline(
        &wgpu::RenderPipelineDescriptor {
            label: Some("Render Pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vs_main"),
                buffers: &[
                    wgpu::VertexBufferLayout {
                        array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
                        step_mode: wgpu::VertexStepMode::Vertex,
                        attributes: &[
                            wgpu::VertexAttribute {
                                format: wgpu::VertexFormat::Float32x3,
                                offset: 0,
                                shader_location: 0, // `position` に対応
                            },
                            wgpu::VertexAttribute {
                                format: wgpu::VertexFormat::Float32x3,
                                offset: 12,
                                shader_location: 1, // `color` に対応
                            },
                        ],
                    },
                ],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fs_main"),
                targets: &[Some(surface_format.into())],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState {
                count: 1,
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
            multiview: None,
            cache: None,
        }
    );

wgpu::RenderPipelineDescriptor で、パイプラインの詳細な設定を指定します。
label は、デバッグ用のラベルを指定します。
layout は、このパイプラインで使用するパイプラインレイアウトを指定します。

vertex は、頂点シェーダーと頂点バッファの設定を定義します。
module は、頂点シェーダーモジュールを指定します。この例では、shader に格納されたシェーダーモジュールを使用しています。
entry_point は、頂点シェーダーのエントリーポイント(関数名)を指定します。この例では vs_main を指定しています。
buffers は、頂点バッファのレイアウトを定義します。ここでは、Vertex 構造体のサイズを array_stride として指定し、2つの属性(position と color)を定義しています。
shader_location の 0 は、WGSL シェーダー内の @location(0) に対応し、頂点の位置データを示します。
shader_location の 1 は、@location(1) に対応し、頂点の色データを示します。
compilation_options は、wgpu::PipelineCompilationOptions 型のフィールドで、シェーダーのコンパイル時に使用されるオプションを指定します。これは、特別な設定を行わずにデフォルトのコンパイルオプションを使用することを意味します。

fragment は、フラグメントシェーダーの設定を定義します。
module は、フラグメントシェーダーモジュールを指定します。頂点シェーダーと同じ shader を使用しています。
entry_point は、フラグメントシェーダーのエントリーポイント(関数名)を指定します。この例では fs_main を指定しています。
targets は、出力ターゲット(レンダーターゲット)のフォーマットを指定します。ここでは、surface_format を使用してスワップチェーンのフォーマットを指定しています。
compilation_options は、wgpu::PipelineCompilationOptions 型のフィールドで、シェーダーのコンパイル時に使用されるオプションを指定します。これは、特別な設定を行わずにデフォルトのコンパイルオプションを使用することを意味します。

primitive は、描画するプリミティブの設定を定義します。
topology は、頂点データの接続方法を指定します。この場合、wgpu::PrimitiveTopology::TriangleList を設定しており、頂点が三角形リストとして解釈されます。
strip_index_format は、ストリップトポロジーで使用されるインデックスフォーマットを指定します。ここでは None を指定しており、ストリップトポロジーは使用されていません。
front_face は、ポリゴンの表面がどちらを向いているかを指定します。wgpu::FrontFace::Ccw は反時計回りの頂点順序を表面として扱います。
cull_mode は、裏面カリングの設定です。ここでは Some(wgpu::Face::Back) が指定されており、裏面(カメラから見えない面)が描画されないようにしています。
polygon_mode は、ポリゴンの描画モードを指定します。wgpu::PolygonMode::Fill はポリゴンを塗りつぶして描画する設定です。
unclipped_depth は、深度値がクリッピングされるかどうかを制御します。ここでは false となっており、深度値はクリッピングされます。
conservative は、保守的なラスタライズを有効にするかどうかを指定します。ここでは false で無効化されています。

depth_stencil は、深度ステンシルバッファの設定を行います。ここでは None となっており、深度ステンシルテストは使用されていません。

multisampleは、マルチサンプリングの設定を定義する wgpu::MultisampleState 構造体を指定しています。
count は、サンプリング数を指定します。ここでは 1 が設定されており、マルチサンプリングは無効です。
mask は、サンプルマスクを指定します。!0 はすべてのサンプルを有効にする設定です。
alpha_to_coverage_enabled は、アルファ値を使用したカバレッジマスクを有効にするかどうかを指定します。ここでは false で無効化されています。

multiview は、マルチビューの設定を行いますが、ここでは None となっており、マルチビューは使用されていません。
cache は、パイプラインキャッシュの設定を行いますが、ここでは None となっており、キャッシュは使用されていません。

4.5. Output

現在の描画対象テクスチャを取得し、そのテクスチャに対するビューを作成します。
このビューは、後続のシェーダーやレンダーパイプラインに渡し、実際の描画処理を行う際に使用します。

    let output = surface.get_current_texture().unwrap();
    let output_view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());

4.6. Encoder

GPUコマンドを記録するためのコマンドエンコーダを作成します。
wgpuでは、GPUに送信するコマンドは直接実行されるのではなく、まずコマンドエンコーダに記録されます。
その後、記録されたコマンドはCommandBufferに変換され、GPUに送信されます。
この設計により、複数のコマンドを効率的にまとめて送信することが可能になります。

    let mut encoder = device.create_command_encoder(
        &wgpu::CommandEncoderDescriptor {
            label: Some("Command Encoder"),
        }
    );

device.create_command_encoder は、device から新しいコマンドエンコーダを作成します。
wgpu::CommandEncoderDescriptor は、コマンドエンコーダの作成時に必要な設定を指定します。
label は、デバッグ用のラベルを指定します。

4.7. Command

レンダリングパスを設定し、描画コマンドを発行します。
Rustの所有権と借用ルールにより、encoder が借用されている間は、所有権を移動する操作(finish()など)を行うことはできないため、render_pass のスコープを明示的に終了させることで、encoder の借用を解放し、その後に encoder.finish() を呼び出せるようにしています。

    // 限定スコープを作成
    {
        // render pass descriptor
        let mut render_pass = encoder.begin_render_pass(
            &wgpu::RenderPassDescriptor {
                label: Some("Render Pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: &output_view,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
                        store: wgpu::StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                occlusion_query_set: None,
                timestamp_writes: None,
            }
        );

        render_pass.set_pipeline(&render_pipeline);
        render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
        render_pass.draw(0..VERTICES.len() as u32, 0..1);
    }

    // submit the command buffer
    queue.submit(Some(encoder.finish()));

wgpu::RenderPassDescriptor 構造体は、レンダリングパスの設定を行います。
label は、デバッグ用のラベルを指定します。
color_attachments は、カラーバッファの設定を行います。wgpu::RenderPassColorAttachment を使用して、以下の設定をしています
view 出力先のテクスチャビュー(output_view)を指定。
resolve_target マルチサンプリングの解決先を指定します。ここでは None
ops カラーバッファの操作を指定します。
load バッファをクリアし、黒(wgpu::Color::BLACK)で初期化。
store 描画結果を保存する設定。
その他のプロパティ(depth_stencil_attachmentocclusion_query_settimestamp_writes)は使用していないため、None に設定されています。

set_pipeline 使用するレンダリングパイプライン(render_pipeline)を設定します。このパイプラインにはシェーダーや描画設定が含まれています。
set_vertex_buffer 頂点バッファ(vertex_buffer)を設定します。ここでは、バッファ全体をスライスして渡しています。
draw 描画コマンドを発行します。
0..VERTICES.len() as u32 描画する頂点の範囲を指定しています。VERTICES.len() は頂点数を表します。
0..1 インスタンスの範囲を指定しています。ここでは1つのインスタンスを描画します。

スコープを抜けるた後は、encoder.finish() でコマンドバッファを終了し、それを queue.submit でGPUに送信します。この操作により、GPUが実際に描画処理を実行します。

4.8. Present

描画結果を画面に表示します。

    output.present();

5. 表示

以下が表示されます。(Chromeで確認)

6. まとめ

いろいろと参考にさせてもらいましたが、バージョンの問題か写経しても動かないところがあり、Github Copilot にはずいぶんと助けてもらいました。
描画するための一連の流れを理解することができました。
今後は各機能毎に整理しつつ、いろんな機能を試していきたいと思います。

今回の記事は、Github Copilot と ChatGPT を使い9割くらい書いてくれました。

A. 参考サイト

Crate wgpu
100日後にRustをちょっと知ってる人になる: [Day 23]wasm-pack テンプレート Deep Dive
Learn Wgpu
Rustのグラフィクス周りメモ/wgpuとその使い方
Rust/wgpuをWebブラウザで動かす
Rust wgpuで3DCGに挑戦する

B. 参考書籍

コメント