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