From 43cae9ac71f2d6933ba3ea25641585bb88ae60ad Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Sat, 15 Aug 2026 17:59:17 -0700 Subject: working, performance is bad --- src/camera.rs | 140 ++++++------------ src/config.rs | 2 +- src/main.rs | 435 ++++++++++++++++++++++++++++++-------------------------- src/shader.wgsl | 29 +++- src/ui.rs | 2 +- 5 files changed, 300 insertions(+), 308 deletions(-) (limited to 'src') diff --git a/src/camera.rs b/src/camera.rs index 55a698c..0b69833 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,18 +1,25 @@ -use crate::{ - Input, - config::{CAMERA_MOVEMENT_SPEED, CHUNK_SIZE}, - sim::{chunk::Chunk, world::World}, -}; +use crate::{Input, config::CAMERA_MOVEMENT_SPEED}; + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct CameraUniform { + pub scale: [f32; 2], + pub centre: [f32; 2], +} pub struct Camera { - // centre coords - pub x: f64, - pub y: f64, - // zoom scale factor, 1.0 = board <-> window - pub zoom: f64, + pub zoom: f32, + pub centre: (f32, f32), + screen_size: (i32, i32), } impl Camera { + fn scale(&self) -> (f32, f32) { + let half_w = self.zoom * self.screen_size.0 as f32 / 2.0; + let half_h = self.zoom * self.screen_size.1 as f32 / 2.0; + (1.0 / half_w, -1.0 / half_h) + } + pub fn handle_camera_input(&mut self, input: &Input, delta_time: f32) { // wasd movement let x: f32 = if input.is_left_pressed { @@ -38,105 +45,38 @@ impl Camera { let adjusted_x = x / magnitude * self.zoom as f32 * CAMERA_MOVEMENT_SPEED * delta_time; let adjusted_y = y / magnitude * self.zoom as f32 * CAMERA_MOVEMENT_SPEED * delta_time; - self.x += adjusted_x as f64; - self.y += adjusted_y as f64; + self.centre.0 += adjusted_x as f32; + self.centre.1 += adjusted_y as f32; } - pub fn screen_position_to_world(&self, screen_x: f64, screen_y: f64) -> (f64, f64) { - let camera_width = self.zoom * f64::from(1600); - let camera_height = self.zoom * f64::from(1200); - let camera_start_x = self.x - camera_width / 2.0; - let camera_start_y = self.y - camera_height / 2.0; - ( - (screen_x / 1600 as f64 * camera_width) + camera_start_x, - (screen_y / 1200 as f64 * camera_height) + camera_start_y, - ) - } - - pub fn write_frame_view(&self, frame: &mut [u8], world: &World) { - puffin::profile_function!(); + pub fn screen_position_to_world(&self, x: f32, y: f32) -> (f32, f32) { + let (scale_x, scale_y) = self.scale(); - // for i in 0..frame.len() / 4 { - // let idx = i * 4; - // frame[idx] = (i / 1000) as u8; - // frame[idx + 1] = 0x00; - // frame[idx + 2] = 0x00; - // frame[idx + 3] = 0xFF; - // } - // return; - - const BG: [u8; 4] = [0x00, 0x00, 0x00, 0xFF]; - for px in frame.chunks_exact_mut(4) { - px.copy_from_slice(&BG); - } + let ndc_x = x / self.screen_size.0 as f32 * 2.0 - 1.0; + let world_x = ndc_x / scale_x + self.centre.0; + let ndc_y = y / self.screen_size.1 as f32 * 2.0 - 1.0; + let world_y = ndc_y / -scale_y + self.centre.1; - // world-space rect covered by the screen - let (xl, yl) = self.screen_position_to_world(0.0, 0.0); - let (xu, yu) = self.screen_position_to_world(1600 as f64, 1200 as f64); - - let c = CHUNK_SIZE; - let cx0 = (xl.floor() as i32).div_euclid(c); - let cx1 = (xu.ceil() as i32).div_euclid(c); - let cy0 = (yl.floor() as i32).div_euclid(c); - let cy1 = (yu.ceil() as i32).div_euclid(c); - - let scale = 1.0 / self.zoom; // pixels per cell + (world_x, world_y) + } - for cy in cy0..=cy1 { - for cx in cx0..=cx1 { - let Some(&idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) else { - continue; - }; - self.write_chunk(frame, &world.chunks[idx], cx, cy, xl, yl, scale); - } + pub fn to_uniform(&self) -> CameraUniform { + let (scale_x, scale_y) = self.scale(); + CameraUniform { + scale: [scale_x, scale_y], + centre: [self.centre.0, self.centre.1], } } - fn write_chunk( - &self, - frame: &mut [u8], - chunk: &Chunk, - cx: i32, - cy: i32, - xl: f64, - yl: f64, - scale: f64, - ) { - let c = CHUNK_SIZE; - let w = 1600 as i32; - let h = 1200 as i32; - - for ly in 0..c { - let wy = (cy * c + ly) as f64; - let sy0 = (((wy - yl) * scale).floor() as i32).max(0); - let sy1 = (((wy + 1.0 - yl) * scale).floor() as i32).min(h); - if sy0 >= sy1 { - continue; - } - - for lx in 0..c { - let wx = (cx * c + lx) as f64; - let sx0 = (((wx - xl) * scale).floor() as i32).max(0); - let sx1 = (((wx + 1.0 - xl) * scale).floor() as i32).min(w); - if sx0 >= sx1 { - continue; - } - - let color = chunk - .get_cell_at_local_position(lx as u8, ly as u8) - .material - .def() - .color; - let rgba = [color.0, color.1, color.2, color.3]; + pub fn resize(&mut self, screen_size: (i32, i32)) -> () { + self.screen_size = screen_size; + } - for sy in sy0..sy1 { - let start = (sy * w + sx0) as usize * 4; - let end = (sy * w + sx1) as usize * 4; - for px in frame[start..end].chunks_exact_mut(4) { - px.copy_from_slice(&rgba); - } - } - } + pub fn new(screen_size: (i32, i32)) -> Self { + Camera { + zoom: 0.2, + centre: (0.0, 0.0), + screen_size, } } } diff --git a/src/config.rs b/src/config.rs index 8e8f7a1..54ec6b5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,4 +3,4 @@ pub const WINDOW_TITLE: &str = "pxs"; pub const CHUNK_SIZE: i32 = 32; pub const CELLS_IN_CHUNK: usize = (CHUNK_SIZE * CHUNK_SIZE) as usize; -pub const CAMERA_MOVEMENT_SPEED: f32 = 10.0; +pub const CAMERA_MOVEMENT_SPEED: f32 = 40.0; diff --git a/src/main.rs b/src/main.rs index d248a75..420fb1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,8 @@ use egui_wgpu::{RendererOptions, ScreenDescriptor}; use egui_winit::egui::{self, Context}; use futures::executor; use rand::random_range; -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; -use wgpu::{Origin3d, TextureUsages}; +use std::{collections::VecDeque, sync::Arc, time::Instant}; +use wgpu::Origin3d; use winit::{ application::ApplicationHandler, event::{ @@ -26,7 +23,7 @@ use winit::{ use crate::{ camera::Camera, - config::WINDOW_TITLE, + config::{CELLS_IN_CHUNK, CHUNK_SIZE, WINDOW_TITLE}, sim::{cell::Cell, materials::MaterialId, sim::sim_tick, world::World}, ui::draw_egui, }; @@ -54,6 +51,7 @@ struct Input { } struct Diagnostics { + frame_times: VecDeque, fps: f32, } @@ -62,6 +60,12 @@ struct RendererChunk { bind_group: wgpu::BindGroup, } +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct ChunkData { + origin: [i32; 2], +} + struct RendererState { window: Arc, surface: wgpu::Surface<'static>, @@ -78,10 +82,8 @@ struct RendererState { // world pixels renderer_chunks: Vec, pixels_pipeline: wgpu::RenderPipeline, - pixels_bind_group: wgpu::BindGroup, - texture: wgpu::Texture, - // TODO remove me - frame_view: Vec, + camera_uniform_buffer: wgpu::Buffer, + camera_uniform_bind_group: wgpu::BindGroup, } impl RendererState { @@ -107,10 +109,19 @@ impl RendererState { ); let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor::default()) + .request_device(&wgpu::DeviceDescriptor { + required_features: wgpu::Features::default() | wgpu::Features::IMMEDIATES, + required_limits: wgpu::Limits { + max_immediate_size: 16, + ..wgpu::Limits::defaults() + }, + ..wgpu::DeviceDescriptor::default() + }) .await .unwrap(); + device.on_uncaptured_error(Arc::new(|err| panic!("{err}"))); + let surface_caps = surface.get_capabilities(&adapter); let surface_format = surface_caps .formats @@ -124,7 +135,7 @@ impl RendererState { format: surface_format, width: size.width, height: size.height, - present_mode: wgpu::PresentMode::AutoVsync, + present_mode: wgpu::PresentMode::AutoNoVsync, alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], desired_maximum_frame_latency: 2, @@ -146,50 +157,32 @@ impl RendererState { } }); - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: None, - mip_level_count: 1, - sample_count: 1, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - format: wgpu_types::TextureFormat::Rgba8UnormSrgb, - size: wgpu::Extent3d { - width: size.width, - height: size.height, - depth_or_array_layers: 1, - }, - dimension: wgpu::TextureDimension::D2, - view_formats: &[], - }); - - let pixels_bind_group_layout = + let camera_uniform_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[wgpu::BindGroupLayoutEntry { - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, }, binding: 0, count: None, - visibility: wgpu::ShaderStages::FRAGMENT, + visibility: wgpu::ShaderStages::VERTEX, }], }); - let pixels_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, - layout: &pixels_bind_group_layout, - entries: &[wgpu::BindGroupEntry { + entries: &[wgpu::BindGroupLayoutEntry { + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.create_view( - &wgpu::TextureViewDescriptor { - dimension: Some(wgpu::TextureViewDimension::D2), - usage: Some( - wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - ), - ..wgpu::TextureViewDescriptor::default() - }, - )), + count: None, + visibility: wgpu::ShaderStages::FRAGMENT, }], }); @@ -201,8 +194,11 @@ impl RendererState { let pixels_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, - immediate_size: 0, - bind_group_layouts: &[Some(&pixels_bind_group_layout)], + immediate_size: 16, + bind_group_layouts: &[ + Some(&camera_uniform_bind_group_layout), + Some(&bind_group_layout), + ], }); let pixels_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { @@ -225,7 +221,7 @@ impl RendererState { compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, + topology: wgpu::PrimitiveTopology::TriangleStrip, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, cull_mode: None, @@ -239,8 +235,23 @@ impl RendererState { cache: None, }); - let mut renderer_chunks: Vec = Vec::new(); + let camera_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Camera uniform buffer"), + size: 16, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let camera_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &camera_uniform_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera_uniform_buffer.as_entire_binding(), + }], + }); + let mut renderer_chunks: Vec = Vec::new(); // match number of world chunks // TODO refactor so that this implicit for _ in -10..10 { @@ -260,21 +271,6 @@ impl RendererState { view_formats: &[], }); - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[wgpu::BindGroupLayoutEntry { - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - binding: 0, - count: None, - visibility: wgpu::ShaderStages::FRAGMENT, - }], - }); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, @@ -312,11 +308,10 @@ impl RendererState { egui_state, egui_renderer, - renderer_chunks: Vec::new(), - texture, - frame_view: vec![0; size.width as usize * size.height as usize * 4], + renderer_chunks, pixels_pipeline, - pixels_bind_group, + camera_uniform_bind_group, + camera_uniform_buffer, } } @@ -331,8 +326,9 @@ impl RendererState { pub fn render( &mut self, - config: &mut Config, + world: &mut World, camera: &mut Camera, + config: &mut Config, diagnostics: &Diagnostics, input: &Input, ) { @@ -344,21 +340,24 @@ impl RendererState { return; } - let output = match self.surface.get_current_texture() { - wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture, - wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => surface_texture, - wgpu::CurrentSurfaceTexture::Timeout - | wgpu::CurrentSurfaceTexture::Occluded - | wgpu::CurrentSurfaceTexture::Validation => { - // Skip this frame - return; - } - wgpu::CurrentSurfaceTexture::Outdated => { - self.surface.configure(&self.device, &self.config); - return; - } - wgpu::CurrentSurfaceTexture::Lost => { - panic!("Lost device?"); + let output = { + puffin::profile_scope!("Get current surface texture"); + match self.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture, + wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => surface_texture, + wgpu::CurrentSurfaceTexture::Timeout + | wgpu::CurrentSurfaceTexture::Occluded + | wgpu::CurrentSurfaceTexture::Validation => { + // Skip this frame + return; + } + wgpu::CurrentSurfaceTexture::Outdated => { + self.surface.configure(&self.device, &self.config); + return; + } + wgpu::CurrentSurfaceTexture::Lost => { + panic!("Lost device?"); + } } }; @@ -411,103 +410,131 @@ impl RendererState { &screen_descriptor, ); - // let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - // label: Some("Render Pass"), - // color_attachments: &[Some(wgpu::RenderPassColorAttachment { - // view: &view, - // resolve_target: None, - // depth_slice: None, - // ops: wgpu::Operations { - // load: wgpu::LoadOp::Clear(wgpu::Color { - // r: 0.1, - // g: 0.2, - // b: 0.3, - // a: 1.0, - // }), - // store: wgpu::StoreOp::Store, - // }, - // })], - // depth_stencil_attachment: None, - // occlusion_query_set: None, - // timestamp_writes: None, - // multiview_mask: None, - // }); - - // drop(render_pass); - - self.queue.write_texture( - wgpu::TexelCopyTextureInfo { - texture: &self.texture, - aspect: wgpu::TextureAspect::All, - mip_level: 0, - origin: Origin3d::ZERO, - }, - &self.frame_view, - wgpu::TexelCopyBufferLayout { - bytes_per_row: Some(size.width * 4), - offset: 0, - rows_per_image: Some(size.height), - }, - wgpu::Extent3d { - width: size.width, - height: size.height, - depth_or_array_layers: 1, - }, + // write the camera buffer + self.queue.write_buffer( + &self.camera_uniform_buffer, + 0, + bytemuck::bytes_of(&camera.to_uniform()), ); - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - depth_slice: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - multiview_mask: None, - }); + // write the chunk textures + let mut chunk_buffer: [u8; (CELLS_IN_CHUNK * 4) as usize] = + [0; (CELLS_IN_CHUNK * 4) as usize]; + + { + puffin::profile_scope!("Upload chunk textures"); + for &idx in world.chunk_position_to_chunk_idx.values() { + let chunk = &mut world.chunks[idx]; + if !chunk.needs_texture_update { + continue; + } + chunk.needs_texture_update = false; + + // TODO use material palette to improve bandwidth of upload + for i in 0..CELLS_IN_CHUNK { + let material = chunk.cells[i].material.def(); + chunk_buffer[i * 4] = material.color.0; + chunk_buffer[i * 4 + 1] = material.color.1; + chunk_buffer[i * 4 + 2] = material.color.2; + chunk_buffer[i * 4 + 3] = material.color.3; + } - render_pass.set_pipeline(&self.pixels_pipeline); - render_pass.set_bind_group(0, &self.pixels_bind_group, &[]); - render_pass.draw(0..3, 0..1); - - drop(render_pass); - - let mut egui_pass = encoder - .begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("egui pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - depth_slice: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, + let render_chunk = &self.renderer_chunks[idx]; + self.queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture: &render_chunk.texture, + aspect: wgpu::TextureAspect::All, + mip_level: 0, + origin: Origin3d::ZERO, }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - multiview_mask: None, - }) - .forget_lifetime(); + &chunk_buffer, + wgpu::TexelCopyBufferLayout { + bytes_per_row: Some(CHUNK_SIZE as u32 * 4), + offset: 0, + rows_per_image: Some(CHUNK_SIZE as u32), + }, + wgpu::Extent3d { + width: CHUNK_SIZE as u32, + height: CHUNK_SIZE as u32, + depth_or_array_layers: 1, + }, + ); + } + } + + { + puffin::profile_scope!("Main render pass"); + let mut render_pass: wgpu::RenderPass<'_> = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, + }); + + render_pass.set_pipeline(&self.pixels_pipeline); + + render_pass.set_bind_group(0, &self.camera_uniform_bind_group, &[]); + + // TODO only visible chunks + for cx in -10..10 { + for cy in -10..10 { + if let Some(idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) { + let render_chunk = &self.renderer_chunks[*idx]; + render_pass.set_bind_group(1, &render_chunk.bind_group, &[]); + render_pass + .set_immediates(0, bytemuck::bytes_of(&ChunkData { origin: [cx, cy] })); + render_pass.draw(0..4, 0..1); + } + } + } + } + + { + puffin::profile_scope!("Egui render pass"); + let mut egui_pass = encoder + .begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("egui pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: None, + }) + .forget_lifetime(); - self.egui_renderer - .render(&mut egui_pass, &clipped_primitives, &screen_descriptor); - drop(egui_pass); + self.egui_renderer + .render(&mut egui_pass, &clipped_primitives, &screen_descriptor); + } - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); + { + puffin::profile_scope!("Submit queue and present"); + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + } } } @@ -527,8 +554,6 @@ struct App { sim_paused: bool, ignore_pause_next_tick: bool, - // used to wait for drawing - last_frame_requested: Instant, // used to compute delta_time last_frame_real: Instant, @@ -553,11 +578,7 @@ impl Default for App { is_right_pressed: false, }, - camera: Some(Camera { - x: 0.0, - y: 0.0, - zoom: 1.0, - }), + camera: None, world: Some(World::from_default_size()), @@ -565,7 +586,6 @@ impl Default for App { sim_paused: false, ignore_pause_next_tick: false, - last_frame_requested: Instant::now(), last_frame_real: Instant::now(), config: Config { @@ -574,7 +594,10 @@ impl Default for App { brush_radius: 10, brush_material: MaterialId::Sand, }, - diagnostics: Diagnostics { fps: 0.0 }, + diagnostics: Diagnostics { + fps: 0.0, + frame_times: VecDeque::new(), + }, } } } @@ -590,6 +613,9 @@ impl ApplicationHandler for App { self.window = Some(window.clone()); self.renderer_state = Some(executor::block_on(RendererState::new(window.clone()))); + let size = window.inner_size(); + self.camera = Some(Camera::new((size.width as i32, size.height as i32))); + // let surface = SurfaceTexture::new(size.width, size.height, window_ref); // let mut pixels = Pixels::new(PIXEL_BUFFER_WIDTH, PIXEL_BUFFER_HEIGHT, surface).unwrap(); @@ -646,7 +672,8 @@ impl ApplicationHandler for App { } WindowEvent::CursorMoved { position, .. } => { self.input.last_mouse_pos_on_screen = Some((position.x, position.y)); - let world_pos = camera.screen_position_to_world(position.x, position.y); + let world_pos = + camera.screen_position_to_world(position.x as f32, position.y as f32); self.input.last_mouse_pos_on_board = Some((world_pos.0 as i32, world_pos.1 as i32)) } @@ -656,8 +683,8 @@ impl ApplicationHandler for App { } } WindowEvent::Resized(size) => { - // Important: resize the surface when the window's size change renderer_state.resize(size.width, size.height); + camera.resize((size.width as i32, size.height as i32)); } WindowEvent::CloseRequested => { event_loop.exit(); @@ -665,15 +692,26 @@ impl ApplicationHandler for App { WindowEvent::RedrawRequested => { #[cfg(feature = "profiler")] puffin::GlobalProfiler::lock().new_frame(); + puffin::profile_scope!("redraw_requested"); // compute frame delta let now = Instant::now(); let secs_since_last_frame = (now - self.last_frame_real).as_secs_f32(); let delta_time = secs_since_last_frame / (1.0 / 60.0); self.last_frame_real = now; - let instantaneous_fps = 1.0 / secs_since_last_frame; - // TODO: can smooth and round this - self.diagnostics.fps = instantaneous_fps; + + self.diagnostics + .frame_times + .push_back(secs_since_last_frame); + + if self.diagnostics.frame_times.len() > 30 { + self.diagnostics.frame_times.pop_front(); + } + + let average_frame_time = self.diagnostics.frame_times.iter().sum::() + / self.diagnostics.frame_times.len() as f32; + + self.diagnostics.fps = 1.0 / average_frame_time; // apply inputs camera.handle_camera_input(&self.input, delta_time); @@ -716,11 +754,10 @@ impl ApplicationHandler for App { self.ignore_pause_next_tick = false; } - camera.write_frame_view(&mut renderer_state.frame_view, world); - renderer_state.render( - &mut self.config, + world, camera, + &mut self.config, &self.diagnostics, &mut self.input, ); @@ -731,16 +768,12 @@ impl ApplicationHandler for App { } fn about_to_wait(&mut self, _: &ActiveEventLoop) { - let now = Instant::now(); - let frame_duration: Duration = Duration::from_micros(1_000_000 / self.config.fps as u64); + // let frame_duration: Duration = Duration::from_micros(1_000_000 / self.config.fps as u64); // limit our internal redraw requests to (fps) - if now - self.last_frame_requested >= frame_duration { - self.last_frame_requested = now; - if let Some(window) = &self.window { - window.request_redraw(); - } else { - panic!("No window!") - } + if let Some(window) = &self.window { + window.request_redraw(); + } else { + panic!("No window!") } } } diff --git a/src/shader.wgsl b/src/shader.wgsl index e545dac..6adcda8 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,4 +1,16 @@ -@group(0) @binding(0) var tex: texture_2d; +struct ChunkData { + origin: vec2, +}; + +var chunk: ChunkData; + +struct Camera { + scale: vec2f, + centre: vec2f, +}; + +@group(0) @binding(0) var camera: Camera; +@group(1) @binding(0) var tex: texture_2d; struct VertexOutput { @builtin(position) clip_position: vec4, @@ -10,16 +22,23 @@ fn vs_main( @builtin(vertex_index) i: u32, ) -> VertexOutput { var out: VertexOutput; - let uv = vec2f(f32((i << 1u) & 2u), f32(i & 2u)); - out.clip_position = vec4f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 0.0, 1.0); - out.uv = uv; + // (0,0),(1,0),(0,1),(1,1) + let corner = vec2f(f32(i & 1u), f32(i >> 1u)); + + // CHUNK_SIZE + let world = (vec2f(chunk.origin) + corner) * 32.0; + let ndc = (world - camera.centre) * camera.scale; + + out.clip_position = vec4f(ndc, 0.0, 1.0); + out.uv = corner; return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let dims = vec2f(textureDimensions(tex)); + // CHUNK_SIZE + let dims = vec2f(32.0, 32.0); return textureLoad(tex, vec2i(in.uv * dims), 0); // return vec4f(0.0, 1.0, 0.0, 1.0); } \ No newline at end of file diff --git a/src/ui.rs b/src/ui.rs index 9d13f74..8608447 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,6 +1,6 @@ use egui::{Color32, Stroke, Ui, epaint::CircleShape}; -use crate::{Config, Diagnostics, Input, camera::Camera, sim::materials::MaterialId}; +use crate::{Camera, Config, Diagnostics, Input, sim::materials::MaterialId}; pub fn draw_egui<'a>( ui: &mut Ui, -- cgit v1.3.1