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}