WebGPU Voxel Game
at main 11 kB view raw
1use crate::{gfx::Gfx, world::World, ConnectionOnlyOnNative}; 2use egui::{FontId, RichText}; 3use egui_winit::EventResponse; 4use winit::window::Window; 5 6const FPS_AVG_WINDOW: usize = 120; 7pub struct EguiRenderer { 8 state: egui_winit::State, 9 renderer: egui_wgpu::Renderer, 10 frame_started: bool, 11 pub scale_factor: f32, 12 pub chunk_influence: (i32, i32, i32), 13 pub frame_count: u64, 14 pub fps_average: [f64; FPS_AVG_WINDOW], 15} 16 17impl EguiRenderer { 18 // Just a helper 19 pub fn ctx(&self) -> &egui::Context { 20 self.state.egui_ctx() 21 } 22 23 pub fn new( 24 device: &wgpu::Device, 25 output_color_format: wgpu::TextureFormat, 26 output_depth_format: Option<wgpu::TextureFormat>, 27 msaa_samples: u32, 28 window: &Window, 29 ) -> EguiRenderer { 30 let context = egui::Context::default(); 31 let state = egui_winit::State::new( 32 context, 33 egui::viewport::ViewportId::ROOT, 34 &window, 35 Some(window.scale_factor() as f32), 36 None, 37 Some(2 * 1024), // "max texture side" of 2048 38 ); 39 40 let renderer = egui_wgpu::Renderer::new( 41 device, 42 output_color_format, 43 output_depth_format, 44 msaa_samples, 45 true, 46 ); 47 48 #[cfg(target_arch = "wasm32")] 49 { 50 state.egui_ctx().set_pixels_per_point(1.0); 51 state.egui_ctx().set_zoom_factor(1.0); 52 } 53 54 EguiRenderer { 55 state, 56 renderer, 57 frame_started: false, 58 scale_factor: 1.0, 59 chunk_influence: (0, 0, 0), 60 frame_count: 0, 61 fps_average: [0.; FPS_AVG_WINDOW], 62 } 63 } 64 65 pub fn handle_input(&mut self, window: &Window, event: &winit::event::WindowEvent) -> bool { 66 let EventResponse { consumed, .. } = self.state.on_window_event(window, event); 67 consumed 68 } 69 70 pub(crate) fn begin_frame(&mut self, window: &Window) { 71 let raw_input = self.state.take_egui_input(window); 72 self.ctx().begin_pass(raw_input); 73 self.frame_started = true; 74 } 75 76 pub(crate) fn end_frame_and_draw( 77 &mut self, 78 device: &wgpu::Device, 79 queue: &wgpu::Queue, 80 encoder: &mut wgpu::CommandEncoder, 81 window: &Window, 82 surface_view: &wgpu::TextureView, 83 screen_descriptor: egui_wgpu::ScreenDescriptor, 84 ) { 85 if !self.frame_started { 86 panic!("begin_frame must be called before end_frame_and_draw can be called!"); 87 } 88 89 #[cfg(not(target_arch = "wasm32"))] 90 self.ctx() 91 .set_pixels_per_point(screen_descriptor.pixels_per_point); 92 93 let full_output = self.ctx().end_pass(); 94 95 self.state 96 .handle_platform_output(window, full_output.platform_output); 97 98 #[cfg(not(target_arch = "wasm32"))] 99 let tris = self 100 .ctx() 101 .tessellate(full_output.shapes, self.ctx().pixels_per_point()); 102 103 #[cfg(target_arch = "wasm32")] 104 let tris = self.ctx().tessellate(full_output.shapes, 1.0); 105 106 for (id, image_delta) in &full_output.textures_delta.set { 107 self.renderer 108 .update_texture(device, queue, *id, image_delta); 109 } 110 111 self.renderer 112 .update_buffers(device, queue, encoder, &tris, &screen_descriptor); 113 114 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 115 label: Some("EGUI Main Render Pass"), 116 color_attachments: &[Some(wgpu::RenderPassColorAttachment { 117 view: surface_view, 118 resolve_target: None, 119 ops: wgpu::Operations { 120 load: wgpu::LoadOp::Load, 121 store: wgpu::StoreOp::Store, 122 }, 123 })], 124 depth_stencil_attachment: None, 125 timestamp_writes: None, 126 occlusion_query_set: None, 127 }); 128 129 self.renderer.render( 130 &mut render_pass.forget_lifetime(), 131 &tris, 132 &screen_descriptor, 133 ); 134 for x in &full_output.textures_delta.free { 135 self.renderer.free_texture(x) 136 } 137 138 self.frame_started = false; 139 } 140 141 pub fn update( 142 &mut self, 143 gfx: &mut Gfx, 144 world: &mut World, 145 conn: &mut ConnectionOnlyOnNative, 146 dt: instant::Duration, 147 ) { 148 let mut scale_factor = self.scale_factor; 149 let (mut chunk_x, mut chunk_y, mut chunk_z) = self.chunk_influence; 150 let mut camera_load = gfx.camera.controller.load_chunks; 151 152 let dt = dt.as_secs_f32(); 153 self.frame_count += 1; 154 self.fps_average[(self.frame_count % FPS_AVG_WINDOW as u64) as usize] = 1.0_f64 / dt as f64; 155 let mean = self.fps_average.iter().sum::<f64>() / FPS_AVG_WINDOW as f64; 156 157 let ctx = self.ctx(); 158 egui::Window::new("Debug Menu") 159 .resizable(true) 160 .vscroll(true) 161 .default_open(false) 162 .show(ctx, |ui| { 163 ui.heading("Performance debugging stats..."); 164 ui.label(format!( 165 "FPS: {:.1} (smoothed over an interval of {})", 166 mean, FPS_AVG_WINDOW 167 )); 168 ui.label(format!("FPS: {:.1} (jittery)", 1.0 / dt)); 169 // ui.label(format!("Instances: {:?}", gfx.object.instances.len())); 170 // ui.label(format!( 171 // "Vertices (guess): {:?}", 172 // gfx.object.instances.len() as u32 173 // * gfx 174 // .object 175 // .model 176 // .meshes 177 // .iter() 178 // .map(|x| x.num_elements) 179 // .sum::<u32>() 180 // )); 181 182 ui.separator(); 183 184 ui.heading("Debugging toys..."); 185 ui.horizontal(|ui| { 186 ui.label("Draw Color"); 187 188 // Absolutely disgusting code! 189 // I would need a func to convert between wgpu::Color (f64 rgb) 190 // to f32 rgb and back. idk 191 // 192 // Better yet, a generic color type that can convert to wgpu 193 // and others 194 let c = gfx.interact.clear_color; 195 let mut color = [c.r as _, c.g as _, c.b as _]; 196 197 ui.color_edit_button_rgb(&mut color); 198 gfx.interact.clear_color.r = color[0] as _; 199 gfx.interact.clear_color.g = color[1] as _; 200 gfx.interact.clear_color.b = color[2] as _; 201 }); 202 203 ui.checkbox(&mut gfx.interact.wireframe, "Wireframe (PC Only)"); 204 205 ui.separator(); 206 207 ui.add( 208 egui::Slider::new(&mut gfx.camera.controller.speed, 0.1..=1000.0) 209 .text("Cam Speed") 210 .logarithmic(true), 211 ); 212 213 ui.separator(); 214 215 ui.label("Camera input \"bitfield\":"); 216 ui.label( 217 RichText::new(format!("{:?}", gfx.camera.controller.movement)) 218 .font(FontId::monospace(11.0)), 219 ); 220 ui.label(format!( 221 "... which is a movement vector of: {:?}", 222 gfx.camera.controller.movement.vec3() 223 )); 224 225 ui.separator(); 226 227 ui.add( 228 egui::Slider::new(&mut gfx.interact.sun_speed, 0.1..=100.0) 229 .text("Sun rotational speed (radians per second)") 230 .logarithmic(true), 231 ); 232 233 ui.separator(); 234 ui.horizontal(|ui| { 235 ui.label(format!("Pixels per point: {}", ctx.pixels_per_point())); 236 if ui.button("-").clicked() { 237 scale_factor = (scale_factor - 0.1).max(0.3); 238 } 239 if ui.button("+").clicked() { 240 scale_factor = (scale_factor + 0.1).min(3.0); 241 } 242 }); 243 244 ui.separator(); 245 246 ui.heading("World toys..."); 247 248 ui.checkbox(&mut camera_load, "Camera position loads chunks"); 249 // ui.label("Move chunk window... "); 250 // ui.horizontal(|ui| { 251 // ui.add_enabled( 252 // !camera_load, 253 // egui::DragValue::new(&mut map_focus.x) 254 // .speed(0.1) 255 // .update_while_editing(false), 256 // ); 257 // ui.label("x "); 258 259 // ui.add_enabled( 260 // !camera_load, 261 // egui::DragValue::new(&mut map_focus.y) 262 // .speed(0.1) 263 // .update_while_editing(false), 264 // ); 265 // ui.label("y "); 266 267 // ui.add_enabled( 268 // !camera_load, 269 // egui::DragValue::new(&mut map_focus.z) 270 // .speed(0.1) 271 // .update_while_editing(false), 272 // ); 273 // ui.label("z."); 274 // }); 275 276 ui.separator(); 277 ui.horizontal(|ui| { 278 ui.label("Scramble chunk at..."); 279 ui.add(egui::DragValue::new(&mut chunk_x).speed(0.1)); 280 ui.label("x "); 281 282 ui.add(egui::DragValue::new(&mut chunk_y).speed(0.1)); 283 ui.label("y "); 284 285 ui.add(egui::DragValue::new(&mut chunk_z).speed(0.1)); 286 ui.label("z."); 287 }); 288 289 // ui.horizontal(|ui| { 290 // let pos = ivec3(chunk_x, chunk_y, chunk_z); 291 292 // #[cfg(not(target_arch = "wasm32"))] 293 // { 294 // let c = if ui.button("Random").clicked() { 295 // Some(Chunk::generate(pos, ChunkScramble::Random)) 296 // } else if ui.button("Normal").clicked() { 297 // Some(Chunk::generate(pos, ChunkScramble::Normal)) 298 // } else if ui.button("Inverse").clicked() { 299 // Some(Chunk::generate(pos, ChunkScramble::Inverse)) 300 // } else { 301 // None 302 // }; 303 // } 304 // }); 305 306 ui.separator(); 307 308 ui.horizontal(|ui| { 309 if ui.button("Save").clicked() { 310 world.save(conn).unwrap(); 311 } 312 }); 313 }); 314 315 self.scale_factor = scale_factor; 316 self.chunk_influence = (chunk_x, chunk_y, chunk_z); 317 318 gfx.camera.controller.load_chunks = camera_load; 319 } 320}