Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
|
876a691d2a | |
|
5372d169cc | |
|
af54d4b95e | |
|
82b8ff1c6a | |
|
9efc362c82 | |
|
62ad00fedf |
|
@ -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"
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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...");
|
||||||
|
});
|
||||||
|
|
|
@ -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>
|
|
@ -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 = "▶"
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './bootstrap.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'bootstrap.js',
|
||||||
|
},
|
||||||
|
mode: 'development'
|
||||||
|
};
|
Loading…
Reference in New Issue