feat: use css scaling, ~1,600x less memory written per frame
This commit is contained in:
parent
e7b44ba112
commit
2ee196f43d
1 changed files with 39 additions and 106 deletions
145
cgol/src/lib.rs
145
cgol/src/lib.rs
|
|
@ -219,12 +219,18 @@ impl AppState {
|
|||
}
|
||||
|
||||
fn resize_canvas_and_universe(&mut self) {
|
||||
let width = self.window.inner_width().unwrap().as_f64().unwrap();
|
||||
let height = self.window.inner_height().unwrap().as_f64().unwrap();
|
||||
let css_w = self.window.inner_width().unwrap().as_f64().unwrap();
|
||||
let css_h = self.window.inner_height().unwrap().as_f64().unwrap();
|
||||
|
||||
let cols = (css_w as u32 / CELL_SIZE).max(1);
|
||||
let rows = (css_h as u32 / CELL_SIZE).max(1);
|
||||
|
||||
// Set canvas to grid resolution (1 pixel per cell), CSS scales it up
|
||||
self.canvas_width = cols;
|
||||
self.canvas_height = rows;
|
||||
self.canvas.set_width(cols);
|
||||
self.canvas.set_height(rows);
|
||||
|
||||
let dpr = self.window.device_pixel_ratio();
|
||||
let css_w = width;
|
||||
let css_h = height;
|
||||
let element = self.canvas.dyn_ref::<web_sys::Element>().unwrap();
|
||||
element
|
||||
.set_attribute(
|
||||
|
|
@ -235,88 +241,35 @@ impl AppState {
|
|||
),
|
||||
)
|
||||
.ok();
|
||||
|
||||
self.canvas_width = (css_w * dpr) as u32;
|
||||
self.canvas_height = (css_h * dpr) as u32;
|
||||
self.canvas.set_width(self.canvas_width);
|
||||
self.canvas.set_height(self.canvas_height);
|
||||
|
||||
// Disable image smoothing after resize
|
||||
self.ctx.set_image_smoothing_enabled(false);
|
||||
|
||||
// Clear canvas
|
||||
self.ctx.set_fill_style_str("black");
|
||||
self.ctx.fill_rect(0.0, 0.0, self.canvas_width as f64, self.canvas_height as f64);
|
||||
|
||||
let cols = (self.canvas_width / CELL_SIZE).max(1);
|
||||
let rows = (self.canvas_height / CELL_SIZE).max(1);
|
||||
self.ctx.set_image_smoothing_enabled(false);
|
||||
|
||||
self.universe = Universe::new(cols, rows);
|
||||
|
||||
// Allocate pixel buffer for the universe size (1 pixel per cell)
|
||||
// We'll draw at universe resolution and let CSS scale it up
|
||||
let buffer_size = (cols * rows * 4) as usize;
|
||||
self.pixel_buffer = vec![0u8; buffer_size];
|
||||
self.pixel_buffer = vec![0u8; (cols * rows * 4) as usize];
|
||||
}
|
||||
|
||||
fn draw_scaled(&mut self) {
|
||||
let grid_width = self.universe.width;
|
||||
let grid_height = self.universe.height;
|
||||
let cell_w = CELL_SIZE;
|
||||
let cell_h = CELL_SIZE;
|
||||
|
||||
// Fill pixel buffer at full canvas resolution
|
||||
let canvas_w = self.canvas_width as usize;
|
||||
let canvas_h = self.canvas_height as usize;
|
||||
|
||||
// Resize buffer if needed
|
||||
let needed_size = canvas_w * canvas_h * 4;
|
||||
if self.pixel_buffer.len() != needed_size {
|
||||
self.pixel_buffer.resize(needed_size, 0);
|
||||
}
|
||||
|
||||
// Fill with black first (for dead cells)
|
||||
for chunk in self.pixel_buffer.chunks_exact_mut(4) {
|
||||
chunk[0] = 0;
|
||||
chunk[1] = 0;
|
||||
chunk[2] = 0;
|
||||
chunk[3] = 255;
|
||||
}
|
||||
|
||||
// Draw each cell as a CELL_SIZE × CELL_SIZE block
|
||||
for row in 0..grid_height {
|
||||
for col in 0..grid_width {
|
||||
let cell_idx = self.universe.index(row, col);
|
||||
|
||||
if let Cell::Alive { hue } = self.universe.cells[cell_idx] {
|
||||
fn draw(&mut self) {
|
||||
let total = (self.universe.width * self.universe.height) as usize;
|
||||
|
||||
for i in 0..total {
|
||||
let px = i * 4;
|
||||
match self.universe.cells[i] {
|
||||
Cell::Alive { hue } => {
|
||||
let (r, g, b) = hue_to_rgb(hue);
|
||||
|
||||
let start_x = (col * cell_w) as usize;
|
||||
let start_y = (row * cell_h) as usize;
|
||||
|
||||
for py in 0..cell_h as usize {
|
||||
let y = start_y + py;
|
||||
if y >= canvas_h {
|
||||
break;
|
||||
}
|
||||
|
||||
for px in 0..cell_w as usize {
|
||||
let x = start_x + px;
|
||||
if x >= canvas_w {
|
||||
break;
|
||||
}
|
||||
|
||||
let pixel_idx = (y * canvas_w + x) * 4;
|
||||
self.pixel_buffer[pixel_idx] = r;
|
||||
self.pixel_buffer[pixel_idx + 1] = g;
|
||||
self.pixel_buffer[pixel_idx + 2] = b;
|
||||
self.pixel_buffer[pixel_idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
self.pixel_buffer[px] = r;
|
||||
self.pixel_buffer[px + 1] = g;
|
||||
self.pixel_buffer[px + 2] = b;
|
||||
self.pixel_buffer[px + 3] = 255;
|
||||
}
|
||||
Cell::Dead => {
|
||||
self.pixel_buffer[px] = 0;
|
||||
self.pixel_buffer[px + 1] = 0;
|
||||
self.pixel_buffer[px + 2] = 0;
|
||||
self.pixel_buffer[px + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Single putImageData call with full canvas
|
||||
|
||||
if let Ok(image_data) = ImageData::new_with_u8_clamped_array_and_sh(
|
||||
Clamped(&self.pixel_buffer),
|
||||
self.canvas_width,
|
||||
|
|
@ -332,22 +285,15 @@ pub fn start() -> Result<(), JsValue> {
|
|||
let state = AppState::new()?;
|
||||
let state_rc = Rc::new(RefCell::new(state));
|
||||
|
||||
let canvas = state_rc.borrow().canvas.clone();
|
||||
let window = state_rc.borrow().window.clone();
|
||||
let document = window.document().unwrap();
|
||||
|
||||
// Mouse move handler
|
||||
let state_for_mouse = state_rc.clone();
|
||||
let canvas_for_mouse = canvas.clone();
|
||||
let window_for_mouse = window.clone();
|
||||
let mouse_closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||
let rect = canvas_for_mouse.get_bounding_client_rect();
|
||||
let dpr = window_for_mouse.device_pixel_ratio();
|
||||
let x = (event.client_x() as f64 - rect.left()) * dpr;
|
||||
let y = (event.client_y() as f64 - rect.top()) * dpr;
|
||||
let mut s = state_for_mouse.borrow_mut();
|
||||
s.cursor_col = (x / CELL_SIZE as f64) as i32;
|
||||
s.cursor_row = (y / CELL_SIZE as f64) as i32;
|
||||
s.cursor_col = event.client_x() / CELL_SIZE as i32;
|
||||
s.cursor_row = event.client_y() / CELL_SIZE as i32;
|
||||
s.cursor_active = true;
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
||||
|
|
@ -356,18 +302,12 @@ pub fn start() -> Result<(), JsValue> {
|
|||
|
||||
// Touch move handler
|
||||
let state_for_touch_move = state_rc.clone();
|
||||
let canvas_for_touch_move = canvas.clone();
|
||||
let window_for_touch_move = window.clone();
|
||||
let touch_move_closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
||||
event.prevent_default();
|
||||
if let Some(touch) = event.touches().get(0) {
|
||||
let rect = canvas_for_touch_move.get_bounding_client_rect();
|
||||
let dpr = window_for_touch_move.device_pixel_ratio();
|
||||
let x = (touch.client_x() as f64 - rect.left()) * dpr;
|
||||
let y = (touch.client_y() as f64 - rect.top()) * dpr;
|
||||
let mut s = state_for_touch_move.borrow_mut();
|
||||
s.cursor_col = (x / CELL_SIZE as f64) as i32;
|
||||
s.cursor_row = (y / CELL_SIZE as f64) as i32;
|
||||
s.cursor_col = touch.client_x() / CELL_SIZE as i32;
|
||||
s.cursor_row = touch.client_y() / CELL_SIZE as i32;
|
||||
s.cursor_active = true;
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
|
@ -378,18 +318,12 @@ pub fn start() -> Result<(), JsValue> {
|
|||
|
||||
// Touch start handler
|
||||
let state_for_touch_start = state_rc.clone();
|
||||
let canvas_for_touch_start = canvas.clone();
|
||||
let window_for_touch_start = window.clone();
|
||||
let touch_start_closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
||||
event.prevent_default();
|
||||
if let Some(touch) = event.touches().get(0) {
|
||||
let rect = canvas_for_touch_start.get_bounding_client_rect();
|
||||
let dpr = window_for_touch_start.device_pixel_ratio();
|
||||
let x = (touch.client_x() as f64 - rect.left()) * dpr;
|
||||
let y = (touch.client_y() as f64 - rect.top()) * dpr;
|
||||
let mut s = state_for_touch_start.borrow_mut();
|
||||
s.cursor_col = (x / CELL_SIZE as f64) as i32;
|
||||
s.cursor_row = (y / CELL_SIZE as f64) as i32;
|
||||
s.cursor_col = touch.client_x() / CELL_SIZE as i32;
|
||||
s.cursor_row = touch.client_y() / CELL_SIZE as i32;
|
||||
s.cursor_active = true;
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
|
@ -448,8 +382,7 @@ pub fn start() -> Result<(), JsValue> {
|
|||
s.universe.set_alive_block(cursor_row, cursor_col, 2, hue);
|
||||
}
|
||||
|
||||
// Draw every frame
|
||||
s.draw_scaled();
|
||||
s.draw();
|
||||
}
|
||||
|
||||
window_for_anim
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue