From 5c0464daa501c1e6011e81c876eaf6442537d2ca Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Fri, 16 Jun 2023 23:58:56 +0200 Subject: [PATCH] Rule as buffer --- Cargo.lock | 1 + Cargo.toml | 1 + src/game-of-life.compute.wgsl | 11 +-- src/lib.rs | 169 ++++++++++++++++++++++++++++++++-- 4 files changed, 170 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0707f9..62bf364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1590,6 +1590,7 @@ dependencies = [ "log", "pollster", "rand", + "rand_chacha", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index bd131af..776a188 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ env_logger = "*" log = "*" pollster = "*" rand = "*" +rand_chacha = "*" wgpu = "*" winit = { git = "https://github.com/rust-windowing/winit", rev = "47488909353bb6d8e52801b8824b0e2352e4ab63" } diff --git a/src/game-of-life.compute.wgsl b/src/game-of-life.compute.wgsl index a3fb58b..ed30de7 100644 --- a/src/game-of-life.compute.wgsl +++ b/src/game-of-life.compute.wgsl @@ -1,6 +1,7 @@ @binding(0) @group(0) var current: array; @binding(1) @group(0) var next: array; @binding(2) @group(0) var size: vec2; +@binding(3) @group(0) var rule: vec2; fn getIndex(x: i32, y: i32) -> u32 { let w = i32(size.x); @@ -21,10 +22,8 @@ fn main(@builtin(global_invocation_id) grid: vec3) { let x = i32(grid.x); let y = i32(grid.y); let n = countNeighbors(x, y); - next[getIndex(x, y)] = // Game of Life: - select(u32(n == 3u), u32(n == 2u || n == 3u), getCell(x, y) == 1u); - // Day and night: https://conwaylife.com/wiki/OCA:Day_%26_Night - // select(u32(n == 3u || n == 6u || n == 7u || n == 8u), u32(n == 3u || n == 4u || n == 6u || n == 7u || n == 8u), getCell(x, y) == 1u); - // Highlife: - // select(u32(n == 3u || n == 6u), u32(n == 2u || n == 3u), getCell(x, y) == 1u); + let will_be_born = u32(((1 << n) & rule.x) > 0); + let will_survive = u32(((1 << n) & rule.y) > 0); + let cell_lives = getCell(x, y) == 1u; + next[getIndex(x, y)] = select(will_be_born, will_survive, cell_lives); } diff --git a/src/lib.rs b/src/lib.rs index acb83ec..bad2952 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ mod event_loop; use rand::prelude::*; +use rand_chacha::ChaCha20Rng; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -12,6 +13,61 @@ use winit::{ window::{Window, WindowBuilder}, }; +struct Rule { + born: u16, + survives: u16, + name: &'static str, +} + +impl Rule { + fn rule_array(&self) -> [u32; 2] { + [u32::from(self.born), u32::from(self.survives)] + } +} + +static RULES: [Rule; 8] = [ + Rule { + born: 0b1000, + survives: 0b1100, + name: "Conway's Life", + }, + Rule { + born: 0b0_1000_1000, + survives: 0b0_0001_1110, + name: "Mazectric with Mice", + }, + Rule { + born: 0b1, + survives: 0b1, + name: "Gnarl", + }, + Rule { + born: 0b1_1100_1000, + survives: 0b1_1101_1000, + name: "Day & Night", + }, + Rule { + born: 0b0_0010_1000, + survives: 0b1_1011_1100, + name: "Land Rush", + }, + Rule { + born: 0b0_0100_1000, + survives: 0b1_1011_1100, + name: "Land Rush 2", + }, + Rule { + born: 0b1_1100_1000, + survives: 0b1_1110_1100, + name: "Stains", + }, + Rule { + born: 0b1_1110_0000, + survives: 0b1_1111_0000, + name: "Vote", + }, +]; + #[cfg(target_arch = "wasm32")] fn setup_html_canvas() -> web_sys::HtmlCanvasElement { use web_sys::HtmlCanvasElement; @@ -76,9 +132,12 @@ pub struct State<'a> { computer_factory: ComputerFactory, computer: Computer, frame_count: u64, + seed: u32, renderer_factory: RendererFactory<'a>, renderer: Renderer, size_buffer: wgpu::Buffer, + rule_buffer: wgpu::Buffer, + rule_idx: usize, cells_width: usize, cells_height: usize, cursor_position: PhysicalPosition, @@ -321,6 +380,16 @@ impl ComputerFactory { }, count: None, }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, ], label: Some("compute_bind_group_layout"), }); @@ -336,8 +405,10 @@ impl ComputerFactory { cells_width: usize, cells_height: usize, size_buffer: &wgpu::Buffer, + rule_buffer: &wgpu::Buffer, + seed: u32, ) -> Computer { - let mut rng = rand::thread_rng(); + let mut rng = ChaCha20Rng::seed_from_u64(u64::from(seed)); let mut cells_vec = vec![0_u32; cells_width * cells_height]; for cell in cells_vec.iter_mut() { if rng.gen::() < 0.50 { @@ -386,6 +457,14 @@ impl ComputerFactory { size: None, }), }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: rule_buffer, + offset: 0, + size: None, + }), + }, ], label: Some("compute_bind_group_0"), } @@ -418,6 +497,14 @@ impl ComputerFactory { size: None, }), }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: rule_buffer, + offset: 0, + size: None, + }), + }, ], label: Some("compute_bind_group_1"), } @@ -542,8 +629,8 @@ impl<'a> State<'a> { view_formats: vec![], }; - let cells_width = 256; - let cells_height = 256; + let cells_width = 1024; + let cells_height = cells_width; let size_array = [cells_width as u32, cells_height as u32]; let size_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { @@ -555,8 +642,29 @@ impl<'a> State<'a> { | wgpu::BufferUsages::VERTEX, }); + let rule_idx = 0; + let rule = &RULES[rule_idx]; + let rule_array = rule.rule_array(); + let rule_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("rule_buffer"), + contents: bytemuck::cast_slice(&rule_array), + usage: wgpu::BufferUsages::STORAGE + | wgpu::BufferUsages::UNIFORM + | wgpu::BufferUsages::COPY_DST + | wgpu::BufferUsages::VERTEX, + }); + + let seed = 1_u32; + let computer_factory = ComputerFactory::new(&device); - let computer = computer_factory.create(&device, cells_width, cells_height, &size_buffer); + let computer = computer_factory.create( + &device, + cells_width, + cells_height, + &size_buffer, + &rule_buffer, + seed, + ); let renderer_factory = RendererFactory::new(&device); let renderer = renderer_factory.create( @@ -569,6 +677,9 @@ impl<'a> State<'a> { ); Ok(Self { + seed, + rule_idx, + rule_buffer, size_buffer, renderer_factory, renderer, @@ -601,11 +712,30 @@ impl<'a> State<'a> { } } + fn change_rule(&mut self, next: bool) { + self.rule_idx = if next { + (self.rule_idx + 1) % RULES.len() + } else if self.rule_idx == 0 { + RULES.len() - 1 + } else { + self.rule_idx - 1 + }; + let rule = &RULES[self.rule_idx]; + self.queue.write_buffer( + &self.rule_buffer, + 0, + bytemuck::cast_slice(&rule.rule_array()), + ); + self.reset_with_cells_width(self.cells_width, self.cells_height); + } + fn reset_with_cells_width(&mut self, new_cells_width: usize, new_cells_height: usize) { self.cells_width = new_cells_width; self.cells_height = new_cells_height; - self.window - .set_title(&format!("{} x {}", new_cells_width, new_cells_height)); + self.window.set_title(&format!( + "{} {}x{} {}", + RULES[self.rule_idx].name, new_cells_width, new_cells_height, self.seed + )); let size_array = [self.cells_width as u32, self.cells_height as u32]; self.queue @@ -618,6 +748,8 @@ impl<'a> State<'a> { self.cells_width, self.cells_height, &self.size_buffer, + &self.rule_buffer, + self.seed, ); self.renderer = self.renderer_factory.create( @@ -660,6 +792,30 @@ impl<'a> State<'a> { fn input(&mut self, event: &WindowEvent) -> bool { match event { + WindowEvent::KeyboardInput { + event: + winit::event::KeyEvent { + logical_key: winit::keyboard::Key::ArrowRight, + state: winit::event::ElementState::Pressed, + .. + }, + .. + } => { + self.change_rule(true); + true + } + WindowEvent::KeyboardInput { + event: + winit::event::KeyEvent { + logical_key: winit::keyboard::Key::ArrowLeft, + state: winit::event::ElementState::Pressed, + .. + }, + .. + } => { + self.change_rule(false); + true + } WindowEvent::KeyboardInput { event: winit::event::KeyEvent { @@ -677,6 +833,7 @@ impl<'a> State<'a> { .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None))); } } else if c == "r" || c == "R" { + self.seed = rand::thread_rng().next_u32(); self.reset_with_cells_width(self.cells_width, self.cells_height); } else if c == "-" && self.cells_width < 2048 { self.reset_with_cells_width(self.cells_width + 128, self.cells_height + 128);