This commit is contained in:
Arkitu 2024-08-22 18:21:34 +02:00
parent 58c158d2e9
commit db1f756544
6 changed files with 534 additions and 168 deletions

47
Cargo.lock generated
View File

@ -248,6 +248,12 @@ dependencies = [
"syn 2.0.75",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
@ -565,6 +571,7 @@ dependencies = [
"env_logger",
"log",
"pollster",
"rand",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@ -1094,6 +1101,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "presser"
version = "0.3.1"
@ -1142,6 +1158,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "range-alloc"
version = "0.1.3"
@ -2246,6 +2292,7 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]

View File

@ -11,6 +11,7 @@ wgpu = "22.1"
cfg-if = "1"
pollster = "0.3"
bytemuck = { version = "1.17", features = [ "derive" ] }
rand = "0.8"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6"

280
src/graphics.rs Normal file
View File

@ -0,0 +1,280 @@
use bytemuck::{Pod, Zeroable};
use wgpu::{include_wgsl, util::DeviceExt, BindGroup, Buffer, Device, Queue, RenderPipeline, Surface, SurfaceConfiguration, VertexBufferLayout};
use winit::{
event::{ElementState, Event, MouseButton, WindowEvent}, event_loop::EventLoop, window::Window
};
use crate::state::State;
#[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod, Debug)]
pub struct Vertex {
pub pos: [f32; 3],
pub color: [f32; 4]
}
impl Vertex {
const DESC: VertexBufferLayout<'static> = VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x4],
};
}
#[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod, Debug)]
pub struct Uniforms {
// [x, y, zoom]
pub camera: [f32; 3],
pub darkness: f32
}
impl Default for Uniforms {
fn default() -> Self {
Self {
camera: [0., 0., 1.],
darkness: 0.
}
}
}
impl Uniforms {
const DESC: VertexBufferLayout<'static> = VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32],
};
}
pub struct Graphics<'a> {
state: State,
window: &'a Window,
surface_config: SurfaceConfiguration,
surface: Surface<'a>,
device: Device,
render_pipeline: RenderPipeline,
queue: Queue,
vertex_buf: Buffer,
index_buf: Buffer,
uniforms_buf: Buffer,
uniforms_bind_group: BindGroup
}
impl<'a> Graphics<'a> {
pub async fn init(window: &'a Window, state: State) -> Self {
let mut size = window.inner_size();
size.width = size.width.max(1);
size.height = size.height.max(1);
let instance = wgpu::Instance::default();
let surface = instance.create_surface(window).unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
// Request an adapter which can render to our surface
compatible_surface: Some(&surface),
})
.await
.expect("Failed to find an appropriate adapter");
// Create the logical device and command queue
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
required_limits: wgpu::Limits::default()
.using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::MemoryUsage,
},
None,
)
.await
.expect("Failed to create device");
let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&state.vertices),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(&state.indices),
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
});
let uniforms_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Uniforms Buffer"),
contents: bytemuck::cast_slice(&[state.uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let uniforms_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
],
label: Some("Uniforms Bind Group Layout"),
});
let uniforms_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &uniforms_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniforms_buf.as_entire_binding(),
}
],
label: Some("Uniforms Bind Group"),
});
// Load the shaders from disk
let shader = device.create_shader_module(include_wgsl!("shader.wgsl"));
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[
&uniforms_bind_group_layout
],
push_constant_ranges: &[],
});
let swapchain_capabilities = surface.get_capabilities(&adapter);
let swapchain_format = swapchain_capabilities.formats[0];
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[
Vertex::DESC
],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
compilation_options: Default::default(),
targets: &[Some(swapchain_format.into())],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let surface_config = surface
.get_default_config(&adapter, size.width, size.height)
.unwrap();
surface.configure(&device, &surface_config);
Self {
state,
window,
surface_config,
surface,
device,
render_pipeline,
queue,
vertex_buf,
index_buf,
uniforms_buf,
uniforms_bind_group
}
}
pub fn run(&mut self, event_loop: EventLoop<()>) {
event_loop.run(move |event, target| {
// Have the closure take ownership of the resources.
// `event_loop.run` never returns, therefore we must do this to ensure
// the resources are properly cleaned up.
let _ = &self;
match event {
Event::WindowEvent {
event: WindowEvent::Resized(new_size),
..
} => {
// Reconfigure the surface with the new size
self.surface_config.width = new_size.width.max(1);
self.surface_config.height = new_size.height.max(1);
self.surface.configure(&self.device, &self.surface_config);
// On macos the window needs to be redrawn manually after resizing
self.window.request_redraw();
},
Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
self.update();
self.render();
},
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => target.exit(),
Event::AboutToWait => {
// RedrawRequested will only trigger once unless we manually
// request it.
self.window.request_redraw();
},
e => self.state.input(e)
}
})
.unwrap();
}
fn render(&self) {
let frame = self.surface
.get_current_texture()
.expect("Failed to acquire next swap chain texture");
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder =
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: None,
});
{
let mut rpass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&self.render_pipeline);
rpass.set_bind_group(0, &self.uniforms_bind_group, &[]);
rpass.set_vertex_buffer(0, self.vertex_buf.slice(..));
rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint32);
rpass.draw_indexed(0..self.state.indices.len() as u32, 0, 0..1);
}
self.queue.submit(Some(encoder.finish()));
frame.present();
}
fn update(&mut self) {
self.state.update();
self.queue.write_buffer(&self.vertex_buf, 0, bytemuck::cast_slice(&self.state.vertices));
self.queue.write_buffer(&self.index_buf, 0, bytemuck::cast_slice(&self.state.indices));
self.queue.write_buffer(&self.uniforms_buf, 0, bytemuck::cast_slice(&[self.state.uniforms]));
}
}

View File

@ -1,162 +1,8 @@
use wgpu::{include_wgsl, Device, Queue, RenderPipeline, Surface, SurfaceConfiguration};
use winit::{
event::{Event, WindowEvent}, event_loop::EventLoop, platform::web::WindowExtWebSys, window::Window
};
struct App<'a> {
window: &'a Window,
surface_config: SurfaceConfiguration,
surface: Surface<'a>,
device: Device,
render_pipeline: RenderPipeline,
queue: Queue
}
impl<'a> App<'a> {
async fn init(window: &'a Window) -> Self {
let mut size = window.inner_size();
size.width = size.width.max(1);
size.height = size.height.max(1);
let instance = wgpu::Instance::default();
let surface = instance.create_surface(window).unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
// Request an adapter which can render to our surface
compatible_surface: Some(&surface),
})
.await
.expect("Failed to find an appropriate adapter");
// Create the logical device and command queue
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::MemoryUsage,
},
None,
)
.await
.expect("Failed to create device");
// Load the shaders from disk
let shader = device.create_shader_module(include_wgsl!("shader.wgsl"));
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let swapchain_capabilities = surface.get_capabilities(&adapter);
let swapchain_format = swapchain_capabilities.formats[0];
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
compilation_options: Default::default(),
targets: &[Some(swapchain_format.into())],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let surface_config = surface
.get_default_config(&adapter, size.width, size.height)
.unwrap();
surface.configure(&device, &surface_config);
Self {
window,
surface_config,
surface,
device,
render_pipeline,
queue
}
}
fn run(&mut self, event_loop: EventLoop<()>) {
event_loop.run(move |event, target| {
// Have the closure take ownership of the resources.
// `event_loop.run` never returns, therefore we must do this to ensure
// the resources are properly cleaned up.
let _ = &self;
if let Event::WindowEvent {
window_id: _,
event,
} = event
{
match event {
WindowEvent::Resized(new_size) => {
// Reconfigure the surface with the new size
self.surface_config.width = new_size.width.max(1);
self.surface_config.height = new_size.height.max(1);
self.surface.configure(&self.device, &self.surface_config);
// On macos the window needs to be redrawn manually after resizing
self.window.request_redraw();
}
WindowEvent::RedrawRequested => {
let frame = self.surface
.get_current_texture()
.expect("Failed to acquire next swap chain texture");
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder =
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: None,
});
{
let mut rpass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&self.render_pipeline);
rpass.draw(0..3, 0..1);
}
self.queue.submit(Some(encoder.finish()));
frame.present();
}
WindowEvent::CloseRequested => target.exit(),
_ => {}
};
}
})
.unwrap();
}
}
mod graphics;
mod state;
use state::State;
use graphics::Graphics;
use winit::event_loop::EventLoop;
pub fn main() {
#[cfg(target_arch = "wasm32")]
@ -174,11 +20,11 @@ pub fn main() {
#[cfg(not(target_arch = "wasm32"))]
{
let mut app = pollster::block_on(App::init(&window));
app.run(event_loop);
pollster::block_on(Graphics::init(&window, State::new())).run(event_loop);
}
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
web_sys::window()
.unwrap()
.document()
@ -188,7 +34,7 @@ pub fn main() {
.append_child(&window.canvas().unwrap())
.unwrap();
wasm_bindgen_futures::spawn_local(async move {
App::init(&window).await.run(event_loop);
Graphics::init(&window, State::new()).await.run(event_loop);
});
}
}

View File

@ -1,11 +1,27 @@
struct Uniforms {
camera: vec3f,
darkness: f32
}
@group(0) @binding(0) var<uniform> uniforms : Uniforms;
struct VertexOutput {
@location(0) color: vec4f,
@builtin(position) pos: vec4f
}
@vertex
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
let x = f32(i32(in_vertex_index) - 1);
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
return vec4<f32>(x, y, 0.0, 1.0);
fn vs_main(
@location(0) pos: vec3f,
@location(1) color: vec4f
) -> VertexOutput {
var out: VertexOutput;
out.color = color;
// out.color[3] -= uniforms.darkness;
out.pos = vec4f(pos.xy-uniforms.camera.xy, pos.z, uniforms.camera.z);
return out;
}
@fragment
fn fs_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
fn fs_main(v: VertexOutput) -> @location(0) vec4f {
return v.color;
}

176
src/state.rs Normal file
View File

@ -0,0 +1,176 @@
use std::time::Instant;
use rand::prelude::*;
use winit::event::{DeviceEvent, ElementState, Event, MouseButton, MouseScrollDelta, WindowEvent};
use crate::graphics::{Uniforms, Vertex};
pub const SQRT_3: f32 = 1.732050807568877293527446341505872367;
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
enum CellKind {
Void,
Sea,
Grass,
}
impl CellKind {
const VALID_CHARS: [char; 3] = ['v', 's', 'g'];
fn color(&self) -> [f32; 4] {
match self {
Self::Void => [0.; 4],
Self::Sea => [0., 0., 1., 1.],
Self::Grass => [0., 1., 0., 1.]
}
}
}
impl From<char> for CellKind {
fn from(value: char) -> Self {
match value {
'v' => Self::Void,
's' => Self::Sea,
'g' => Self::Grass,
_ => panic!("Invalid cell kind")
}
}
}
impl From<u8> for CellKind {
fn from(value: u8) -> Self {
match value {
0 => Self::Void,
1 => Self::Sea,
2 => Self::Grass,
_ => panic!("Invalid cell kind")
}
}
}
struct Cell {
kind: CellKind
}
impl Cell {
const RADIUS: f32 = 1.;
fn new(kind: CellKind) -> Self {
Self {
kind
}
}
}
struct Map {
cells: [Cell; Self::SIZE]
}
impl Map {
const HEIGHT: usize = 10;
const WIDTH: usize = 10;
const SIZE: usize = Self::HEIGHT*Self::WIDTH;
fn new() -> Self {
std::array::from_fn(|_| thread_rng().gen_range(1..=2)).into()
// "sgssv
// ggsvg
// gsvvs
// vgsgs
// ssggs".into()
}
fn enumerate<'a>(&'a self) -> std::iter::Map<std::iter::Enumerate<std::slice::Iter<'a, Cell>>, fn((usize, &Cell)) -> ([usize; 2], &Cell)> {
self.cells.iter().enumerate().map(|(i, c)| ([i % Self::HEIGHT, i / Self::WIDTH], c))
}
}
impl From<&str> for Map {
fn from(value: &str) -> Self {
let mut chars = value.chars().filter(|c| CellKind::VALID_CHARS.contains(c));
let cells = std::array::from_fn(|_| Cell::new(chars.next().expect("Invalid map size").into()));
Self { cells }
}
}
impl From<[u8; Map::SIZE]> for Map {
fn from(value: [u8; Map::SIZE]) -> Self {
Self { cells: value.map(|c| Cell::new(c.into())) }
}
}
pub struct State {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub uniforms: Uniforms,
start: Instant,
map: Map
}
impl State {
pub fn new() -> Self {
let mut s = Self {
vertices: vec![],
indices: vec![],
uniforms: Uniforms::default(),
start: Instant::now(),
map: Map::new()
};
s.update();
s
}
pub fn input(&mut self, event: Event<()>) {
match event {
Event::WindowEvent { event: WindowEvent::MouseInput { state, button, ..}, ..} => {
if let state = ElementState::Pressed {
self.uniforms.camera[2] += match button {
MouseButton::Left => 0.1,
MouseButton::Right => -0.1,
_ => 0.
};
}
},
// Event::WindowEvent { event: WindowEvent::MouseWheel { delta, ..}, ..} => {
// self.uniforms.camera[2] -= match delta {
// MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
// MouseScrollDelta::LineDelta(_, y) => y
// };
// },
Event::DeviceEvent { event: DeviceEvent::MouseWheel { delta }, ..} => {
self.uniforms.camera[2] += match delta {
MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
MouseScrollDelta::LineDelta(_, y) => y
};
},
_ => {}
}
}
pub fn update(&mut self) {
self.vertices = Vec::with_capacity(self.map.cells.len()*6);
self.indices = Vec::with_capacity(self.map.cells.len()*12);
for ([x, y], c) in self.map.enumerate() {
let x = x as f32;
let y = y as f32;
let i = self.vertices.len();
let color = c.kind.color();
let center = [(0.5+x+((y%2.)*0.5)) * (SQRT_3*Cell::RADIUS), -(0.5+y)*(1.5*Cell::RADIUS)];
// self.vertices.push(Vertex { pos: [center[0], center[1], 0.], color });
// self.vertices.push(Vertex { pos: [center[0]+0.1, center[1]+0.1, 0.], color });
// self.vertices.push(Vertex { pos: [center[0]-0.1, center[1]+0.1, 0.], color });
self.vertices.push(Vertex { pos: [center[0], center[1]+Cell::RADIUS, 0.], color: color.clone() });
self.vertices.push(Vertex { pos: [center[0]-(0.5*SQRT_3*Cell::RADIUS), center[1]+(0.5*Cell::RADIUS), 0.], color: color.clone() });
self.vertices.push(Vertex { pos: [center[0]+(0.5*SQRT_3*Cell::RADIUS), center[1]+(0.5*Cell::RADIUS), 0.], color: color.clone() });
self.vertices.push(Vertex { pos: [center[0]-(0.5*SQRT_3*Cell::RADIUS), center[1]-(0.5*Cell::RADIUS), 0.], color: color.clone() });
self.vertices.push(Vertex { pos: [center[0]+(0.5*SQRT_3*Cell::RADIUS), center[1]-(0.5*Cell::RADIUS), 0.], color: color.clone() });
self.vertices.push(Vertex { pos: [center[0], center[1]-Cell::RADIUS, 0.], color: color.clone() });
self.indices.push(i as u32);
self.indices.push((i+1) as u32);
self.indices.push((i+2) as u32);
self.indices.push((i+1) as u32);
self.indices.push((i+3) as u32);
self.indices.push((i+2) as u32);
self.indices.push((i+3) as u32);
self.indices.push((i+4) as u32);
self.indices.push((i+2) as u32);
self.indices.push((i+3) as u32);
self.indices.push((i+5) as u32);
self.indices.push((i+4) as u32);
}
// dbg!(&self.vertices, &self.indices, &self.uniforms);
// dbg!(self.vertices.len(), self.indices.len(), &self.uniforms);
}
}