summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-15 17:59:17 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-15 17:59:17 -0700
commit43cae9ac71f2d6933ba3ea25641585bb88ae60ad (patch)
tree554b3ef3ba4ae6e613bd3876d9775851206a596c /src
parentc94749cc88080ef7fef7b285f9072ea26d18e1eb (diff)
working, performance is badkai/native-wgpu
Diffstat (limited to 'src')
-rw-r--r--src/camera.rs140
-rw-r--r--src/config.rs2
-rw-r--r--src/main.rs431
-rw-r--r--src/shader.wgsl29
-rw-r--r--src/ui.rs2
5 files changed, 298 insertions, 306 deletions
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<f32>,
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<Window>,
surface: wgpu::Surface<'static>,
@@ -78,10 +82,8 @@ struct RendererState {
// world pixels
renderer_chunks: Vec<RendererChunk>,
pixels_pipeline: wgpu::RenderPipeline,
- pixels_bind_group: wgpu::BindGroup,
- texture: wgpu::Texture,
- // TODO remove me
- frame_view: Vec<u8>,
+ 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<RendererChunk> = 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<RendererChunk> = 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];
- render_pass.set_pipeline(&self.pixels_pipeline);
- render_pass.set_bind_group(0, &self.pixels_bind_group, &[]);
- render_pass.draw(0..3, 0..1);
+ {
+ 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;
- drop(render_pass);
+ // 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;
+ }
- 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::<f32>()
+ / 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<f32>;
+struct ChunkData {
+ origin: vec2<i32>,
+};
+
+var<immediate> chunk: ChunkData;
+
+struct Camera {
+ scale: vec2f,
+ centre: vec2f,
+};
+
+@group(0) @binding(0) var<uniform> camera: Camera;
+@group(1) @binding(0) var tex: texture_2d<f32>;
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@@ -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<f32> {
- 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,