diff --git a/core/wasm/README.md b/core/wasm/README.md index b55f449e..ee34d006 100644 --- a/core/wasm/README.md +++ b/core/wasm/README.md @@ -1,12 +1,13 @@ # 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://github.com/rustwasm/wasm-bindgen/blob/master/examples/julia\_set/ +* 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\_game\_of\_life/ ## Prep: @@ -19,10 +20,27 @@ docker run -it -v `pwd`:/novnc -w /novnc/core/wasm -p 8080:8080 rust-wasm bash npm install ``` -Build: +## Build: ``` -cargo +nightly build --target wasm32-unknown-unknown -wasm-bindgen target/wasm32-unknown-unknown/debug/novnc.wasm --out-dir . -npm run serve # then visit localhost:8080 outside the container +npm run build-release # or run build-debug (10x slower code) +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) diff --git a/core/wasm/bootstrap.js b/core/wasm/bootstrap.js new file mode 100644 index 00000000..555057d4 --- /dev/null +++ b/core/wasm/bootstrap.js @@ -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..."); +}); + diff --git a/core/wasm/index.html b/core/wasm/index.html index e4f4ce04..3d5703f0 100644 --- a/core/wasm/index.html +++ b/core/wasm/index.html @@ -3,7 +3,7 @@ - - + + diff --git a/core/wasm/index.js b/core/wasm/index.js index b427e090..2dcda333 100644 --- a/core/wasm/index.js +++ b/core/wasm/index.js @@ -1,18 +1,66 @@ const WIDTH = 1024 -const HEIGHT = 768 +const HEIGHT = 1024 +const MODE = 3 -import('./novnc') - .then(wasm => { - const canvas = document.getElementById('target') - const ctx = canvas.getContext('2d') +import * as novnc from './novnc' +import { memory } from './novnc_bg' - canvas.addEventListener('click', () => { - const startMs = (new Date()).getTime() - wasm.draw(ctx, WIDTH, HEIGHT, - parseInt(Math.random()*256), - parseInt(Math.random()*256), - parseInt(Math.random()*256)) - console.log("elapsed:", (new Date()).getTime() - startMs) - }); - wasm.draw(ctx, WIDTH, HEIGHT, 50, 150, 150) - }) +const canvas = document.getElementById('target') +const ctx = canvas.getContext('2d') + +canvas.width = WIDTH; +canvas.height = HEIGHT; + +if (MODE === 2 || MODE === 3) { + let byteSize = WIDTH * HEIGHT * 4 + 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() diff --git a/core/wasm/package.json b/core/wasm/package.json index 5cd0cb43..996d4be6 100644 --- a/core/wasm/package.json +++ b/core/wasm/package.json @@ -1,6 +1,10 @@ { "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": { "webpack": "^4.16.2", diff --git a/core/wasm/src/lib.rs b/core/wasm/src/lib.rs index db61e8bb..a3c304f9 100644 --- a/core/wasm/src/lib.rs +++ b/core/wasm/src/lib.rs @@ -2,8 +2,14 @@ extern crate wasm_bindgen; +use std::mem; +use std::slice; +use std::os::raw::c_void; use wasm_bindgen::prelude::*; +////////////////////////////////////////////////////////// +/// draw1 + #[wasm_bindgen] extern "C" { 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); } -#[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 { let mut data: Vec = vec![]; @@ -52,3 +48,60 @@ pub fn fill(width: u32, height: u32, red: u8, green: u8, blue: u8) -> Vec { 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; + } + } +} diff --git a/core/wasm/webpack.config.js b/core/wasm/webpack.config.js index dce27149..0c67220c 100644 --- a/core/wasm/webpack.config.js +++ b/core/wasm/webpack.config.js @@ -1,10 +1,10 @@ const path = require('path'); module.exports = { - entry: './index.js', + entry: './bootstrap.js', output: { path: path.resolve(__dirname, 'dist'), - filename: 'index.js', + filename: 'bootstrap.js', }, mode: 'development' };