Compare commits

...

6 Commits
master ... wasm

Author SHA1 Message Date
Joel Martin 876a691d2a wasm: use port 7080, use kanaka/rust-wasm image 2018-07-31 11:02:37 -05:00
Joel Martin 5372d169cc wasm: simplify down to just use MODE 3 (draw3).
draw3 was the pretty clear winner performance-wise.
2018-07-26 16:05:55 +09:00
Joel Martin af54d4b95e wasm: use requestAnimationFrame and report FPS. 2018-07-26 15:38:33 +09:00
Joel Martin 82b8ff1c6a 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.
2018-07-26 14:19:01 +09:00
Joel Martin 9efc362c82 wasm: draw/fill a canvas with solid color. 2018-07-25 16:48:48 +09:00
Joel Martin 62ad00fedf wasm: hello world with rust, wasm-bindgen, webpack 2018-07-25 16:02:40 +09:00
9 changed files with 242 additions and 0 deletions

10
core/wasm/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "novnc"
version = "0.1.0"
authors = ["Joel Martin <github@martintribe.org>"]
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"

9
core/wasm/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM rustlang/rust:nightly
RUN rustup target add wasm32-unknown-unknown --toolchain nightly
RUN cargo install wasm-bindgen-cli
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
RUN apt -y install nodejs

36
core/wasm/README.md Normal file
View File

@ -0,0 +1,36 @@
# noVNC + wasm
This is a WebAssembly proof-of-concept.
It is based on information from the following sources:
* 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:
```
docker build -t kanaka/rust-wasm ./core/wasm
# OR
docker pull kanaka/rust-wasm
docker run -it -v `pwd`:/novnc -w /novnc/core/wasm -p 7080:7080 kanaka/rust-wasm bash
npm install
```
## Build:
Run the following inside the container:
```
npm run build-release # or run build-debug (10x slower code)
npm run serve # then visit localhost:7080 outside the container
```
Note that `run server` will automatically detect modification to
`index.js` and reload the page.

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...");
});

11
core/wasm/index.html Normal file
View File

@ -0,0 +1,11 @@
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<button id="play-pause"></button>
<pre id="fps">Frames per Second:</pre>
<canvas id="target" width="400" height="400"></canvas>
<script src='./bootstrap.js'></script>
</body>
</html>

105
core/wasm/index.js Normal file
View File

@ -0,0 +1,105 @@
const WIDTH = 2048
const HEIGHT = 1024
import * as novnc from './novnc'
import { memory } from './novnc_bg'
const canvas = document.getElementById('target')
const ctx = canvas.getContext('2d')
canvas.width = WIDTH
canvas.height = HEIGHT
let byteSize = WIDTH * HEIGHT * 4
let pointer = novnc.alloc( byteSize )
let u8array = new Uint8ClampedArray(memory.buffer, pointer, byteSize)
let imgData = new ImageData(u8array, WIDTH, HEIGHT)
let frame = -1
function renderLoop() {
fps.render()
frame += 1
//const tms1 = (new Date()).getTime()
novnc.draw(pointer, WIDTH, HEIGHT, frame)
//const tms2 = (new Date()).getTime()
ctx.putImageData(imgData, 0, 0)
//const tms3 = (new Date()).getTime()
//console.log("elapsed 1:", tms2 - tms1, "elapsed 2:", tms3 - tms2)
animationId = requestAnimationFrame(renderLoop)
}
//////////////////////////////////////////////////////////////////////////////
// From: https://github.com/rustwasm/wasm_game_of_life/blob/3253fa3a1557bdb9525f3b5c134b58efa1041c55/index.js#L27
let animationId = null
const fps = new class {
constructor() {
this.fps = document.getElementById("fps")
this.frames = []
this.lastFrameTimeStamp = performance.now()
}
render() {
const now = performance.now()
const delta = now - this.lastFrameTimeStamp
this.lastFrameTimeStamp = now
const fps = 1 / delta * 1000
this.frames.push(fps)
if (this.frames.length > 100) {
this.frames.shift()
}
let min = Infinity
let max = -Infinity
let sum = 0
for (let i = 0; i < this.frames.length; i++) {
sum += this.frames[i]
min = Math.min(this.frames[i], min)
max = Math.max(this.frames[i], max)
}
let mean = sum / this.frames.length
this.fps.textContent = `
Frames per Second:
latest = ${Math.round(fps)}
avg of last 100 = ${Math.round(mean)}
min of last 100 = ${Math.round(min)}
max of last 100 = ${Math.round(max)}
`.trim()
}
}
const playPauseButton = document.getElementById("play-pause")
const isPaused = () => {
return animationId === null
}
const play = () => {
playPauseButton.textContent = "⏸"
renderLoop()
}
const pause = () => {
playPauseButton.textContent = "▶"
cancelAnimationFrame(animationId)
animationId = null
}
playPauseButton.addEventListener("click", event => {
if (isPaused()) {
play()
} else {
pause()
}
})
playPauseButton.textContent = "▶"
//////////////////////////////////////////////////////////////////////////////

13
core/wasm/package.json Normal file
View File

@ -0,0 +1,13 @@
{
"scripts": {
"serve": "webpack-dev-server --host 0.0.0.0 --port 7080",
"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",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.0"
}
}

37
core/wasm/src/lib.rs Normal file
View File

@ -0,0 +1,37 @@
#![feature(wasm_custom_section, wasm_import_module, use_extern_macros)]
extern crate wasm_bindgen;
use std::mem;
use std::slice;
use std::os::raw::c_void;
use wasm_bindgen::prelude::*;
// 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 draw(mem: *mut u32, width: usize, height: usize, frame: u32) {
// 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 r = if (x%512) < 256 {x%256} else {255-(x%256)};
let g = if (y%512) < 256 {y%256} else {255-(y%256)};
let b = if (frame%512) < 256 {frame%256} else {255-(frame%256)};
let color = 0xff000000 |
(b << 16) |
((g as u32) << 8) |
((r as u32) << 0);
sl[y*width + x] = color;
}
}
}

View File

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