mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-25 14:12:13 +00:00
155 lines
5.5 KiB
Rust
155 lines
5.5 KiB
Rust
use futures::Future;
|
|
use js_sys::{Promise, Uint8ClampedArray, WebAssembly};
|
|
use rayon::prelude::*;
|
|
use wasm_bindgen::prelude::*;
|
|
use wasm_bindgen::JsCast;
|
|
|
|
macro_rules! console_log {
|
|
($($t:tt)*) => (crate::log(&format_args!($($t)*).to_string()))
|
|
}
|
|
|
|
mod pool;
|
|
|
|
#[wasm_bindgen]
|
|
extern "C" {
|
|
#[wasm_bindgen(js_namespace = console)]
|
|
fn log(s: &str);
|
|
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
|
fn logv(x: &JsValue);
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub struct Scene {
|
|
inner: raytracer::scene::Scene,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
impl Scene {
|
|
/// Creates a new scene from the JSON description in `object`, which we
|
|
/// deserialize here into an actual scene.
|
|
#[wasm_bindgen(constructor)]
|
|
pub fn new(object: &JsValue) -> Result<Scene, JsValue> {
|
|
console_error_panic_hook::set_once();
|
|
Ok(Scene {
|
|
inner: object
|
|
.into_serde()
|
|
.map_err(|e| JsValue::from(e.to_string()))?,
|
|
})
|
|
}
|
|
|
|
/// Renders this scene with the provided concurrency and worker pool.
|
|
///
|
|
/// This will spawn up to `concurrency` workers which are loaded from or
|
|
/// spawned into `pool`. The `RenderingScene` state contains information to
|
|
/// get notifications when the render has completed.
|
|
pub fn render(
|
|
self,
|
|
concurrency: usize,
|
|
pool: &pool::WorkerPool,
|
|
) -> Result<RenderingScene, JsValue> {
|
|
let scene = self.inner;
|
|
let height = scene.height;
|
|
let width = scene.width;
|
|
|
|
// Allocate the pixel data which our threads will be writing into.
|
|
let pixels = (width * height) as usize;
|
|
let mut rgb_data = vec![0; 4 * pixels];
|
|
let base = rgb_data.as_ptr() as usize;
|
|
let len = rgb_data.len();
|
|
|
|
// Configure a rayon thread pool which will pull web workers from
|
|
// `pool`.
|
|
let thread_pool = rayon::ThreadPoolBuilder::new()
|
|
.num_threads(concurrency - 1)
|
|
.spawn_handler(|thread| Ok(pool.run(|| thread.run()).unwrap()))
|
|
.build()
|
|
.unwrap();
|
|
|
|
// And now execute the render! The entire render happens on our worker
|
|
// threads so we don't lock up the main thread, so we ship off a thread
|
|
// which actually does the whole rayon business. When our returned
|
|
// future is resolved we can pull out the final version of the image.
|
|
let done = pool
|
|
.run_notify(move || {
|
|
thread_pool.install(|| {
|
|
rgb_data
|
|
.par_chunks_mut(4)
|
|
.enumerate()
|
|
.for_each(|(i, chunk)| {
|
|
let i = i as u32;
|
|
let x = i % width;
|
|
let y = i / width;
|
|
let ray = raytracer::Ray::create_prime(x, y, &scene);
|
|
let result = raytracer::cast_ray(&scene, &ray, 0).to_rgba();
|
|
chunk[0] = result.data[0];
|
|
chunk[1] = result.data[1];
|
|
chunk[2] = result.data[2];
|
|
chunk[3] = result.data[3];
|
|
});
|
|
});
|
|
rgb_data
|
|
})?
|
|
.map(move |_data| image_data(base, len, width, height).into());
|
|
|
|
Ok(RenderingScene {
|
|
promise: wasm_bindgen_futures::atomics::future_to_promise(done),
|
|
base,
|
|
len,
|
|
height,
|
|
width,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub struct RenderingScene {
|
|
base: usize,
|
|
len: usize,
|
|
promise: Promise,
|
|
width: u32,
|
|
height: u32,
|
|
}
|
|
|
|
// Inline the definition of `ImageData` here because `web_sys` uses
|
|
// `&Clamped<Vec<u8>>`, whereas we want to pass in a JS object here.
|
|
#[wasm_bindgen]
|
|
extern "C" {
|
|
pub type ImageData;
|
|
|
|
#[wasm_bindgen(constructor, catch)]
|
|
fn new(data: &Uint8ClampedArray, width: f64, height: f64) -> Result<ImageData, JsValue>;
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
impl RenderingScene {
|
|
/// Returns the JS promise object which resolves when the render is complete
|
|
pub fn promise(&self) -> Promise {
|
|
self.promise.clone()
|
|
}
|
|
|
|
/// Return a progressive rendering of the image so far
|
|
#[wasm_bindgen(js_name = imageSoFar)]
|
|
pub fn image_so_far(&self) -> ImageData {
|
|
image_data(self.base, self.len, self.width, self.height)
|
|
}
|
|
}
|
|
|
|
fn image_data(base: usize, len: usize, width: u32, height: u32) -> ImageData {
|
|
// Use the raw access available through `memory.buffer`, but be sure to
|
|
// use `slice` instead of `subarray` to create a copy that isn't backed
|
|
// by `SharedArrayBuffer`. Currently `ImageData` rejects a view of
|
|
// `Uint8ClampedArray` that's backed by a shared buffer.
|
|
//
|
|
// FIXME: that this may or may not be UB based on Rust's rules. For example
|
|
// threads may be doing unsynchronized writes to pixel data as we read it
|
|
// off here. In the context of wasm this may or may not be UB, we're
|
|
// unclear! In any case for now it seems to work and produces a nifty
|
|
// progressive rendering. A more production-ready application may prefer to
|
|
// instead use some form of signaling here to request an update from the
|
|
// workers instead of synchronously acquiring an update, and that way we
|
|
// could ensure that even on the Rust side of things it's not UB.
|
|
let mem = wasm_bindgen::memory().unchecked_into::<WebAssembly::Memory>();
|
|
let mem = Uint8ClampedArray::new(&mem.buffer()).slice(base as u32, (base + len) as u32);
|
|
ImageData::new(&mem, width as f64, height as f64).unwrap()
|
|
}
|