wasm: tests/results for 3 modes.

- mode 1: call putImageData from rust/wasm. This involves copying the
  image data via the wasm-bindgen API.
- mode 2: allocating memory in rust/wasm and returning reference to it
  for JS code. JS code calls draw2 using the reference so now image
  data is copying in either direction.
- mode 3: same as mode 2 but the draw3 function treats the memory as
  a vector of u32 instead of a vector of u8.
This commit is contained in:
Joel Martin 2018-07-26 14:19:01 +09:00
parent 9efc362c82
commit 82b8ff1c6a
7 changed files with 173 additions and 39 deletions

View File

@ -1,12 +1,13 @@
# noVNC + wasm # noVNC + wasm
The following example is based on this tutorial: This is a WebAssembly proof-of-concept.
https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/ It is based on information from the following sources:
and this example: * https://hacks.mozilla.org/2018/04/javascript-to-rust-and-back-again-a-wasm-bindgen-tale/
* https://www.hellorust.com/demos/canvas/index.html
https://github.com/rustwasm/wasm-bindgen/blob/master/examples/julia\_set/ * https://github.com/rustwasm/wasm-bindgen/blob/master/examples/julia\_set/
* https://github.com/rustwasm/wasm\_game\_of\_life/
## Prep: ## Prep:
@ -19,10 +20,27 @@ docker run -it -v `pwd`:/novnc -w /novnc/core/wasm -p 8080:8080 rust-wasm bash
npm install npm install
``` ```
Build: ## Build:
``` ```
cargo +nightly build --target wasm32-unknown-unknown npm run build-release # or run build-debug (10x slower code)
wasm-bindgen target/wasm32-unknown-unknown/debug/novnc.wasm --out-dir . npm run serve # then visit localhost:8080 outside the container
npm run serve # then visit localhost:8080 outside the container
``` ```
Note that `run server` will automatically detect modification to
`index.js` and reload the page.
## Preliminary results:
* 2048x1024, draw1, release: 66.7ms
* 2048x1024, draw2, release: 34.1ms ( 21.7ms / 12.4ms)
* 2048x1024, draw3, release: 29.9ms ( 15.1ms / 14.8ms)
* 1024x1024, draw1, release: 47.5ms
* 1024x1024, draw2, release: 21.8ms ( 12.0ms / 9.8ms)
* 1024x1024, draw3, release: 16.7ms ( 6.9ms / 9.8ms)
* 1024x1024, draw1, debug: 376.6ms
* 1024x1024, draw2, debug: 132.4ms (129.1ms / 3.3ms)
* 1024x1024, draw3, debug: 131.4ms (128.8ms / 2.6ms)

11
core/wasm/bootstrap.js vendored Normal file
View File

@ -0,0 +1,11 @@
// Currently WebAssembly modules cannot be synchronously imported in the main
// chunk: https://github.com/webpack/webpack/issues/6615
//
// By dynamically importing index.js webpack will split it into a separate chunk
// automatically, where synchronous imports of WebAssembly is allowed.
const index = import("./index");
index.then(() => {
console.log("Loaded...");
});

View File

@ -3,7 +3,7 @@
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/> <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head> </head>
<body> <body>
<canvas id="target" width="1024" height="768"></canvas> <canvas id="target" width="400" height="400"></canvas>
<script src='./index.js'></script> <script src='./bootstrap.js'></script>
</body> </body>
</html> </html>

View File

@ -1,18 +1,66 @@
const WIDTH = 1024 const WIDTH = 1024
const HEIGHT = 768 const HEIGHT = 1024
const MODE = 3
import('./novnc') import * as novnc from './novnc'
.then(wasm => { import { memory } from './novnc_bg'
const canvas = document.getElementById('target')
const ctx = canvas.getContext('2d')
canvas.addEventListener('click', () => { const canvas = document.getElementById('target')
const startMs = (new Date()).getTime() const ctx = canvas.getContext('2d')
wasm.draw(ctx, WIDTH, HEIGHT,
parseInt(Math.random()*256), canvas.width = WIDTH;
parseInt(Math.random()*256), canvas.height = HEIGHT;
parseInt(Math.random()*256))
console.log("elapsed:", (new Date()).getTime() - startMs) if (MODE === 2 || MODE === 3) {
}); let byteSize = WIDTH * HEIGHT * 4
wasm.draw(ctx, WIDTH, HEIGHT, 50, 150, 150) var pointer = novnc.alloc( byteSize )
})
var u8array = new Uint8ClampedArray(memory.buffer, pointer, byteSize)
var imgData = new ImageData(u8array, WIDTH, HEIGHT)
console.log("imgData:", imgData)
}
let msList1 = []
let msList2 = []
function avg(l) {
return (l.reduce((a,b) => a+b, 0)/l.length).toFixed(2)
}
function update() {
let ms1, ms2
const startMs = (new Date()).getTime()
const red = parseInt(Math.random()*256)
const green = parseInt(Math.random()*256)
const blue = parseInt(Math.random()*256)
console.log(`red: ${red}, green: ${green}, blue: ${blue}`)
if (MODE === 1) {
novnc.draw1(ctx, WIDTH, HEIGHT,
red, green, blue)
ms1 = (new Date()).getTime()
msList1.push(ms1 - startMs)
console.log(`frame elapsed: ${ms1 - startMs} (${avg(msList1)})`)
} else {
if (MODE === 2) {
novnc.draw2(pointer, WIDTH, HEIGHT,
red, green, blue)
} else if (MODE === 3) {
novnc.draw3(pointer, WIDTH, HEIGHT,
red, green, blue)
}
ms1 = (new Date()).getTime()
ctx.putImageData(imgData, 0, 0)
ms2 = (new Date()).getTime()
msList1.push(ms1 - startMs)
msList2.push(ms2 - ms1)
console.log(`draw elapsed: ${ms1 - startMs} (${avg(msList1)}), ` +
`putIMageData elapsed: ${ms2 - ms1} (${avg(msList2)})`)
//window.requestAnimationFrame(update)
}
}
canvas.addEventListener('click', () => {
update()
})
update()

View File

@ -1,6 +1,10 @@
{ {
"scripts": { "scripts": {
"serve": "webpack-dev-server --host 0.0.0.0" "serve": "webpack-dev-server --host 0.0.0.0",
"build-debug": "cargo +nightly build --target wasm32-unknown-unknown && wasm-bindgen target/wasm32-unknown-unknown/debug/novnc.wasm --out-dir .",
"build-release": "cargo +nightly build --release --target wasm32-unknown-unknown && wasm-bindgen target/wasm32-unknown-unknown/release/novnc.wasm --out-dir .",
"bundle": "npm run build-release && webpack"
}, },
"devDependencies": { "devDependencies": {
"webpack": "^4.16.2", "webpack": "^4.16.2",

View File

@ -2,8 +2,14 @@
extern crate wasm_bindgen; extern crate wasm_bindgen;
use std::mem;
use std::slice;
use std::os::raw::c_void;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
//////////////////////////////////////////////////////////
/// draw1
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
pub type ImageData; pub type ImageData;
@ -28,16 +34,6 @@ extern "C" {
pub fn put_image_data(this: &CanvasRenderingContext2D, image_data: &ImageData, p_1: i32, p_2: i32); pub fn put_image_data(this: &CanvasRenderingContext2D, image_data: &ImageData, p_1: i32, p_2: i32);
} }
#[wasm_bindgen]
pub fn draw(ctx: &CanvasRenderingContext2D, width: u32, height: u32, red: u8, green: u8, blue: u8) {
let data = fill(width, height, red, green, blue);
let uint8_array = Uint8ClampedArray::new(&data);
ctx.put_image_data(&ImageData::new(&uint8_array, width, height), 0, 0);
}
///////////////////////////////////////////////////
pub fn fill(width: u32, height: u32, red: u8, green: u8, blue: u8) -> Vec<u8> { pub fn fill(width: u32, height: u32, red: u8, green: u8, blue: u8) -> Vec<u8> {
let mut data: Vec<u8> = vec![]; let mut data: Vec<u8> = vec![];
@ -52,3 +48,60 @@ pub fn fill(width: u32, height: u32, red: u8, green: u8, blue: u8) -> Vec<u8> {
data data
} }
#[wasm_bindgen]
pub fn draw1(ctx: &CanvasRenderingContext2D, width: u32, height: u32, red: u8, green: u8, blue: u8) {
let data = fill(width, height, red, green, blue);
let uint8_array = Uint8ClampedArray::new(&data);
ctx.put_image_data(&ImageData::new(&uint8_array, width, height), 0, 0);
}
//////////////////////////////////////////////////////////
/// draw2
// In order to work with the memory we expose allocation method
#[wasm_bindgen]
pub fn alloc(size: usize) -> *mut c_void {
let mut buf = Vec::with_capacity(size);
let ptr = buf.as_mut_ptr();
mem::forget(buf);
return ptr as *mut c_void;
}
#[wasm_bindgen]
pub fn draw2(mem: *mut u8, width: usize, height: usize, red: u8, green: u8, blue: u8) {
// pixels are stored in RGBA, so each pixel is 4 bytes
let sl = unsafe { slice::from_raw_parts_mut(mem, width * height * 4) };
for y in 0..height {
for x in 0..width {
let xy = x*4 + y*4*width;
sl[xy + 0] = red;
sl[xy + 1] = green;
sl[xy + 2] = blue;
sl[xy + 3] = 255;
}
}
}
//////////////////////////////////////////////////////////
/// draw3
#[wasm_bindgen]
pub fn draw3(mem: *mut u32, width: usize, height: usize, red: u8, green: u8, blue: u8) {
// pixels are stored in RGBA
let sl = unsafe { slice::from_raw_parts_mut(mem, width * height) };
for y in 0..height {
for x in 0..width {
let color = 0xff000000 |
((blue as u32) << 16) |
((green as u32) << 8) |
((red as u32) << 0);
sl[y*width + x] = color;
}
}
}

View File

@ -1,10 +1,10 @@
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
entry: './index.js', entry: './bootstrap.js',
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'index.js', filename: 'bootstrap.js',
}, },
mode: 'development' mode: 'development'
}; };