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:
parent
9efc362c82
commit
82b8ff1c6a
|
@ -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)
|
||||||
|
|
|
@ -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...");
|
||||||
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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'
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue