🦁

WebGLチュートリアルをRustのWebAssemblyで書く 〜その1〜

2023/02/04に公開

https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL

WebGLのチュートリアルをWebAssemblyで書きます。Rustで、wasm-bindgenを使います。今回は環境構築について多めです。

npm, webpack and typescript

wasm-pack new PROJECT_NAME && cd PROJECT_NAME
npm init -y
npm install --save-dev webpack webpack-cli http-server typescript ts-loader

package.json

...
"scripts": {
    "server": "wasm-pack build && webpack && http-server"
  },
...

webpack.config.js

const path = require("path");

const dist = path.resolve(__dirname, "dist");

module.exports = {
  //mode: "production",
  mode: "development",
  entry: {
    index: "./js/index.ts",
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    path: dist,
    filename: "[name].js",
  },
  experiments: {
    asyncWebAssembly: true,
  },
};

asyncWebAssemblyを設定します

tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node"
  }
}

https://webpack.js.org/guides/typescript/

上記を参考にしています

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content="" />
    <title>test server</title>
  </head>

  <body>
    <div id="app"></div>
    <script src="/dist/index.js"></script>
  </body>
</html>

js/wasm.ts

import * as wasm from "../pkg/PROJECT_NAME.wasm";
import { __wbg_set_wasm } from "../pkg/PROJECT_NAME_bg.js";
__wbg_set_wasm(wasm);
export * from "../pkg/PROJECT_NAME_bg.js";

js/index.ts

import { start } from "wasm";

start();

以前はjs/index.tsに下記を書くだけでも動いていたと思いますが、2023/2/4現在上記のようにしないとwasmファイルがwebpackでバンドルされませんでした

//参考
import { start } from "../pkg";
start();

Rust

Cargo.toml

...
[dependencies]
wasm-bindgen = "0.2.63"

console_error_panic_hook = { version = "0.1.6", optional = true }

wee_alloc = { version = "0.4.5", optional = true }

wasm-bindgen-futures = "0.4.33"

[dependencies.web-sys]
version = "0.3.60"
features = [
  "Window",
  "Document",
  "HtmlElement",
  "HtmlCanvasElement",
  "WebGl2RenderingContext",
]
...

.cargo/config.toml

[build]
rustflags = [
  "--cfg=web_sys_unstable_apis",
]

src/lib.rs

use wasm_bindgen::prelude::*;
use web_sys::WebGl2RenderingContext as GL;

#[wasm_bindgen]
pub async fn start() -> Result<(), JsValue> {
    let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window exists"))?;
    let document = window
        .document()
        .ok_or_else(|| JsValue::from_str("should have document"))?;
    let app = document
        .get_element_by_id("app")
        .ok_or_else(|| JsValue::from_str("no #app exists"))?;
    let canvas = document
        .create_element("canvas")?
        .dyn_into::<web_sys::HtmlCanvasElement>()?;
    canvas.set_attribute("width", "640")?;
    canvas.set_attribute("height", "480")?;
    app.append_child(&canvas)?;

    let gl = canvas
        .get_context("webgl2")?
        .ok_or_else(|| JsValue::from_str("fail to get context"))?
        .dyn_into::<web_sys::WebGl2RenderingContext>()?;
    gl.clear_color(0.0, 0.0, 0.0, 1.0);
    gl.clear(GL::COLOR_BUFFER_BIT);

    Ok(())
}
npm run server

Discussion