Open2
Hello WASM! 🦀
Rustで WebAssembly
Setup
# rust周辺ツールのアップデート
rustup update
# wasm packのインストール
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# cargo generate のインストール
cargo install cargo-generate
# npmのインストール
npm install npm@latest -g
cargo generateでテンプレートを取得
cargo generate --git https://github.com/rustwasm/wasm-pack-template
Project Name を聞かれるので、sample
などとしておく。
ビルド
cd sample
wasm-pack build
webページ化
npm init wasm-app www
webページとして実行
cd www
npm install # 1度だけ
npm run start
マンデルブロ集合を作る
src/logic.rsを作成し編集
src/logic.rs
fn get_n_diverged(x0: f64, y0: f64, max_iter:usize)-> u8 {
let mut xn = 0.0;
let mut yn = 0.0;
for i in 1..max_iter {
let x_next = xn * xn - yn * yn + x0;
let y_next = 2.0 * xn * yn + y0;
xn = x_next;
yn = y_next;
if yn * yn + xn * xn > 4.0 {
return i as u8;
}
}
max_iter as u8
}
pub fn generate_mandelbrot_set (
canvas_w: usize,
canvas_h: usize,
x_min: f64,
x_max: f64,
y_min: f64,
y_max: f64,
max_iter: usize,
) -> Vec<u8> {
let canvas_w_f64 = canvas_w as f64;
let canvas_h_f64 = canvas_h as f64;
// 色情報
let mut data = vec![];
for i in 0..canvas_h {
let i_f64 = i as f64;
let y = y_min + (y_max - y_min) * i_f64 / canvas_h_f64;
for j in 0..canvas_w {
let x = x_min + (x_max - x_min) * j as f64 / canvas_w_f64;
let iter_index = get_n_diverged(x, y, max_iter);
let v = iter_index % 8 * 32;
data.push(v);
data.push(v);
data.push(v);
data.push(255);
}
}
data
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_n_diverged(){
let max_iter = 10;
assert_eq!(get_n_diverged(1.0, 0.0, max_iter), 3);
assert_eq!(get_n_diverged(0.0, 0.0, max_iter), max_iter as u8);
assert_eq!(get_n_diverged(0.0, 1.0, max_iter), max_iter as u8);
}
#[test]
fn test_generate_mandelbrot_set(){
let canvas_w = 2;
let canvas_h = 2;
let x_min = -1.0;
let x_max = 1.0;
let y_min = -1.0;
let y_max = 1.0;
let max_iter = 8;
assert_eq!(
generate_mandelbrot_set(canvas_w,canvas_h,x_min,x_max,y_min,y_max,max_iter),
vec![96,96,96,255,0,0,0,255,0,0,0,255,0,0,0,255]
);
}
}
src/lib.rsを編集
src/lib.rs
mod utils;
mod logic;
use wasm_bindgen::prelude::*;
use wasm_bindgen::{Clamped, JsCast};
use web_sys::ImageData;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace=console)]
fn log(a: &str);
}
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()));
}
macro_rules! measure_elapsed_time {
($t:tt, $s:block) => {{
let window = web_sys::window().expect("should have a window in this context");
let performance = window.performance().expect("performance should be available");
let start = performance.now();
let result = {$s};
let end = performance.now();
console_log!("{}:{}[ms]", $t, end-start);
result
}};
}
#[wasm_bindgen]
pub fn generate_mandelbrot_set(
canvas_w: usize,
canvas_h: usize,
x_min: f64,
x_max: f64,
y_min: f64,
y_max: f64,
max_iter:usize,
) -> Vec<u8> {
measure_elapsed_time!("generate:wasm\telapsed:", {
logic::generate_mandelbrot_set(canvas_w, canvas_h,
x_min, x_max, y_min, y_max, max_iter)
})
}
#[wasm_bindgen]
pub fn draw_mandelbrot_set(){
const CANVAS_ID: &str = "canvas_wasm";
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document.get_element_by_id(CANVAS_ID).unwrap();
// Element型からキャスト
let canvas: web_sys::HtmlCanvasElement = canvas
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
// Object型からCanvasRenderingContext2d型にキャストする。
let context = canvas.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
let canvas_w = canvas.width() as usize;
let canvas_h = canvas.height() as usize;
const X_MIN: f64 = -1.5;
const X_MAX: f64 = 0.5;
const Y_MAX: f64 = -1.0;
const Y_MIN: f64 = 1.0;
const MAX_ITER: usize = 64;
let mut result = measure_elapsed_time!(
"generate:wasm\telapsed:", {
logic::generate_mandelbrot_set(canvas_w, canvas_h, X_MIN,X_MAX,Y_MIN,Y_MAX,MAX_ITER)
}
);
measure_elapsed_time!("draw:wasm\telapsed:", {
let data = ImageData::new_with_u8_clamped_array_and_sh(
Clamped(&mut result),
canvas.width(),
canvas.height()
);
if let Ok(data) = data {
let _ = context.put_image_data(&data, 0.0, 0.0);
}
})
}
www/index.htmlを編集
www/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello wasm-pack!</title>
</head>
<body>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<button id="render">render</button>
<div>
<canvas id="canvas_wasm" height="600" width="600"></canvas>
</div>
<script src="./bootstrap.js"></script>
</body>
</html>
www/index.jsを編集
www/index.js
function draw(ctx, canvas_w, canvas_h, data) {
let img = new ImageData(new Uint8ClampedArray(data), canvas_w, canvas_h);
ctx.putImageData(img, 0, 0);
}
const X_MIN = -1.5;
const X_MAX = 0.5;
const Y_MIN = -1.0;
const Y_MAX = 1.0
const MAX_ITER = 64;
console.log("start loading wasm");
const mandelbrot = import('../pkg')
.catch(console.error);
Promise.all([mandelbrot]).then(async function ([
{ generate_mandelbrot_set, draw_mandelbrot_set }
]) {
console.log("finished loading wasm");
const renderBtn = document.getElementById('render');
renderBtn.addEventListener('click', () => {
draw_mandelbrot_set();
let wasmResult = null;
{
const CANVAS_ID = "canvas_hybrid";
let canvas = document.getElementById(CANVAS_ID);
let context = canvas.getContext("2d");
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const generateStratTime = Date.now();
wasmResult = generate_mandelbrot_set(
canvasWidth, canvasHeight, X_MIN, X_MAX, Y_MIN, Y_MAX, MAX_ITER
);
const generateEndTime = Date.now();
const drawStratTime = Date.now();
draw(context, canvasWidth, canvasHeight, wasmResult);
const drawEndTime = Date.now();
const elapsed = generateEndTime - generateStratTime;
console.log(`\tgenerate:wasm\tgenerate_elapsed:${elapsed}[ms]`);
console.log(`\tdwaw: js\tdraw_elapsed: ${drawEndTime - drawStratTime}[ms]`);
}
})
})
Cargo.tomlを編集
Cargo.toml
[package]
name = "sample"
version = "0.1.0"
authors = #hidden
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.63"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
wee_alloc = { version = "0.4.5", optional = true }
js-sys="0.3.40"
[dev-dependencies]
wasm-bindgen-test = "0.3.13"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
[dependencies.web-sys]
version="0.3.4"
features= [
'CanvasRenderingContext2d',
'Document',
'Element',
'HtmlCanvasElement',
'ImageData',
'Performance',
'Window',
]
ビルド&実行
wasm-pack build
cd www
npm run start