From 94a05e0ef558b53dc9a1dfad10aa86f22d05e764 Mon Sep 17 00:00:00 2001 From: Brandon Kase Date: Fri, 1 Mar 2019 02:24:37 -0800 Subject: [PATCH] Canvas image blitting 60FPS on iPhone A Rust Vec controls the canvas at a full 60fps even on an iPhone --- Cargo.toml | 13 +++++ src/lib.rs | 132 +++++++++++++++++++++++++++++++++++++++++++------ www/index.html | 3 ++ www/index.js | 4 +- 4 files changed, 134 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 211f18b..084bb77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,19 @@ console_error_panic_hook = { version = "0.1.1", optional = true } # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. wee_alloc = { version = "0.4.2", optional = true } +[dependencies.web-sys] +version = "0.3.4" +features = [ + 'Document', + 'Element', + 'HtmlElement', + 'HtmlCanvasElement', + 'CanvasRenderingContext2d', + 'ImageData', + 'Node', + 'Window', +] + [dev-dependencies] wasm-bindgen-test = "0.2" diff --git a/src/lib.rs b/src/lib.rs index ecf252c..5f46219 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,27 +1,127 @@ -extern crate cfg_if; -extern crate wasm_bindgen; +use std::cell::RefCell; +use std::rc::Rc; -mod utils; +extern crate wasm_bindgen; -use cfg_if::cfg_if; use wasm_bindgen::prelude::*; +use wasm_bindgen::{JsCast, Clamped}; + +fn window() -> web_sys::Window { + web_sys::window().expect("no global `window` exists") +} + +fn request_animation_frame(f: &Closure) { + window() + .request_animation_frame(f.as_ref().unchecked_ref()) + .expect("should register `requestAnimationFrame` OK"); +} -cfg_if! { - // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global - // allocator. - if #[cfg(feature = "wee_alloc")] { - extern crate wee_alloc; - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +fn document() -> web_sys::Document { + window() + .document() + .expect("should have a document on window") +} + +fn draw(data: &mut Vec, width: u32, height: u32, i: u32) { + for row in 0..height { + for col in 0..width { + let idx : usize = (row*width*4 + col*4) as usize; + data[idx+0] = i as u8; + data[idx+1] = i as u8; + data[idx+2] = i as u8; + data[idx+3] = 255; + } } } + #[wasm_bindgen] -extern { - fn alert(s: &str); +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + + #[wasm_bindgen(js_namespace = performance, js_name = now)] + fn performance_now() -> f64; } -#[wasm_bindgen] -pub fn greet() { - alert("Hello, gameboy!"); +// This function is automatically invoked after the wasm module is instantiated. +#[wasm_bindgen(start)] +pub fn run() -> Result<(), JsValue> { + let canvas = document().get_element_by_id("canvas").unwrap(); + let canvas: web_sys::HtmlCanvasElement = canvas + .dyn_into::() + .map_err(|_| ()) + .unwrap(); + + let width = canvas.width() as u32; + let height = ((144.0 / 160.0) * width as f64).ceil() as u32; + canvas.set_height(height); + + log(&format!("Height {} and width {}", height, width)); + + let ctx = canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(); + + // Here we want to call `requestAnimationFrame` in a loop, but only a fixed + // number of times. After it's done we want all our resources cleaned up. To + // achieve this we're using an `Rc`. The `Rc` will eventually store the + // closure we want to execute on each frame, but to start out it contains + // `None`. + // + // After the `Rc` is made we'll actually create the closure, and the closure + // will reference one of the `Rc` instances. The other `Rc` reference is + // used to store the closure, request the first frame, and then is dropped + // by this function. + // + // Inside the closure we've got a persistent `Rc` reference, which we use + // for all future iterations of the loop + let f = Rc::new(RefCell::new(None)); + let g = f.clone(); + + let mut i = 0; + let mut data = Vec::new(); + for _ in 0 .. height { + for _ in 0 .. width { + for _ in 0 .. 4 { + data.push(0); + } + } + } + let mut last = performance_now(); + *g.borrow_mut() = Some(Closure::wrap(Box::new(move || { + if i > 500 { + // body().set_text_content(Some("All done!")); + + // Drop our handle to this closure so that it will get cleaned + // up once we return. + let _ = f.borrow_mut().take(); + return; + } + + let now = performance_now(); + let diff = now - last; + last = now; + + draw(&mut data, width, height, i); + let data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(Clamped(&mut data), width, height).expect("u8 clamped array"); + ctx.put_image_data(&data, 0.0, 0.0).expect("put_image_data"); + ctx.fill_text(&format!("FPS {}", (1000.0 / diff).ceil()), 10.0, 50.0).expect("fill_text"); + + // Set the body's text content to how many times this + // requestAnimationFrame callback has fired. + i += 1; + + // Schedule ourself for another requestAnimationFrame callback. + request_animation_frame(f.borrow().as_ref().unwrap()); + }) as Box)); + + request_animation_frame(g.borrow().as_ref().unwrap()); + Ok(()) } + diff --git a/www/index.html b/www/index.html index ab7dfaf..e42142f 100644 --- a/www/index.html +++ b/www/index.html @@ -5,6 +5,9 @@ Hello wasm-pack! +
+ +
diff --git a/www/index.js b/www/index.js index 4052d96..4c183c7 100644 --- a/www/index.js +++ b/www/index.js @@ -1,3 +1,3 @@ -import * as wasm from "gameboy"; +import('gameboy') + .catch(console.error); -wasm.greet();