summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs629
1 files changed, 456 insertions, 173 deletions
diff --git a/src/main.rs b/src/main.rs
index 313a7a8..b5c19e0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,9 +6,12 @@ mod ui;
use egui::Id;
use egui_wgpu::{RendererOptions, ScreenDescriptor};
use egui_winit::egui::{self, Context};
-use pixels::{Pixels, ScalingMode, SurfaceTexture};
-use rand::random_range;
-use std::time::{Duration, Instant};
+use futures::executor;
+use std::{
+ sync::Arc,
+ time::{Duration, Instant},
+};
+use wgpu::{Origin3d, TextureUsages};
use winit::{
application::ApplicationHandler,
event::{
@@ -22,8 +25,8 @@ use winit::{
use crate::{
camera::Camera,
- config::{PIXEL_BUFFER_HEIGHT, PIXEL_BUFFER_WIDTH, WINDOW_TITLE},
- sim::{cell::Cell, materials::MaterialId, sim::sim_tick, world::World},
+ config::WINDOW_TITLE,
+ sim::{materials::MaterialId, sim::sim_tick, world::World},
ui::draw_egui,
};
@@ -53,13 +56,394 @@ struct Diagnostics {
fps: f32,
}
+struct RendererState {
+ window: Arc<Window>,
+ surface: wgpu::Surface<'static>,
+ device: wgpu::Device,
+ queue: wgpu::Queue,
+ config: wgpu::SurfaceConfiguration,
+ is_surface_configured: bool,
+
+ // egui
+ egui_context: egui::Context,
+ egui_state: egui_winit::State,
+ egui_renderer: egui_wgpu::Renderer,
+
+ pixels_pipeline: wgpu::RenderPipeline,
+ pixels_bind_group: wgpu::BindGroup,
+ texture: wgpu::Texture,
+ // TODO remove me
+ frame_view: Vec<u8>,
+}
+
+impl RendererState {
+ // https://sotrh.github.io/learn-wgpu/beginner/tutorial1-window
+ pub async fn new(window: Arc<Window>) -> Self {
+ let size = window.inner_size();
+ println!("Got window! ({}x{})", size.width, size.height);
+
+ let instance = wgpu::Instance::default();
+ let surface = instance.create_surface(window.clone()).unwrap();
+
+ let adapter = instance
+ .request_adapter(&wgpu::RequestAdapterOptions {
+ compatible_surface: Some(&surface),
+ ..wgpu::RequestAdapterOptions::default()
+ })
+ .await
+ .unwrap();
+
+ println!(
+ "Initialized adapter, using GPU '{}'",
+ adapter.get_info().name
+ );
+
+ let (device, queue) = adapter
+ .request_device(&wgpu::DeviceDescriptor::default())
+ .await
+ .unwrap();
+
+ let surface_caps = surface.get_capabilities(&adapter);
+ let surface_format = surface_caps
+ .formats
+ .iter()
+ .find(|f| f.is_srgb())
+ .copied()
+ .unwrap_or(surface_caps.formats[0]);
+
+ let config = wgpu::SurfaceConfiguration {
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+ format: surface_format,
+ width: size.width,
+ height: size.height,
+ present_mode: wgpu::PresentMode::AutoVsync,
+ alpha_mode: surface_caps.alpha_modes[0],
+ view_formats: vec![],
+ desired_maximum_frame_latency: 2,
+ };
+
+ let egui_context = Context::default();
+ let egui_state = egui_winit::State::new(
+ egui_context.clone(),
+ egui_context.viewport_id(),
+ &window,
+ None,
+ None,
+ None,
+ );
+ let egui_renderer = egui_wgpu::Renderer::new(&device, surface_format, {
+ RendererOptions {
+ msaa_samples: 1,
+ ..RendererOptions::default()
+ }
+ });
+
+ 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: surface_format,
+ 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 =
+ 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 pixels_pipeline_layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: None,
+ immediate_size: 0,
+ bind_group_layouts: &[Some(&pixels_bind_group_layout)],
+ });
+
+ let pixels_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: None,
+ layout: &pixels_bind_group_layout,
+ entries: &[wgpu::BindGroupEntry {
+ 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()
+ },
+ )),
+ }],
+ });
+
+ let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("Shader"),
+ source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
+ });
+
+ let pixels_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: None,
+ layout: Some(&pixels_pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: Some("vs_main"),
+ buffers: &[],
+ compilation_options: wgpu::PipelineCompilationOptions::default(),
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: Some("fs_main"),
+ targets: &[Some(wgpu::ColorTargetState {
+ format: config.format,
+ blend: Some(wgpu::BlendState::REPLACE),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ compilation_options: wgpu::PipelineCompilationOptions::default(),
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ strip_index_format: None,
+ front_face: wgpu::FrontFace::Ccw,
+ cull_mode: None,
+ polygon_mode: wgpu::PolygonMode::Fill,
+ unclipped_depth: false,
+ conservative: false,
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState::default(),
+ multiview_mask: None,
+ cache: None,
+ });
+
+ RendererState {
+ window,
+ surface,
+ device,
+ queue,
+ config,
+ is_surface_configured: false,
+
+ egui_context,
+ egui_state,
+ egui_renderer,
+
+ texture,
+ frame_view: vec![0; size.width as usize * size.height as usize * 4],
+ pixels_pipeline,
+ pixels_bind_group,
+ }
+ }
+
+ pub fn resize(&mut self, width: u32, height: u32) {
+ if width > 0 && height > 0 {
+ self.config.width = width;
+ self.config.height = height;
+ self.surface.configure(&self.device, &self.config);
+ self.is_surface_configured = true;
+ }
+ }
+
+ pub fn render(
+ &mut self,
+ config: &mut Config,
+ camera: &mut Camera,
+ diagnostics: &Diagnostics,
+ input: &Input,
+ ) {
+ puffin::profile_function!();
+
+ self.window.request_redraw();
+
+ if !self.is_surface_configured {
+ 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 view = output
+ .texture
+ .create_view(&wgpu::TextureViewDescriptor::default());
+
+ let mut encoder = self
+ .device
+ .create_command_encoder(&wgpu::CommandEncoderDescriptor {
+ label: Some("Render Encoder"),
+ });
+
+ let raw_input = self.egui_state.take_egui_input(&self.window);
+ let full_output = self.egui_context.run_ui(raw_input, |ui| {
+ let right_panel = egui::Panel::right(Id::new("right_panel"));
+ right_panel
+ .resizable(false)
+ // TODO collapse button
+ .show_collapsible(ui, &mut true, |panel_ui| {
+ draw_egui(panel_ui, config, camera, diagnostics, input)
+ });
+ });
+
+ self.egui_state
+ .handle_platform_output(&self.window, full_output.platform_output);
+
+ let clipped_primitives = self
+ .egui_context
+ .tessellate(full_output.shapes, full_output.pixels_per_point);
+
+ let pixels_per_point = full_output.pixels_per_point;
+
+ let size = self.window.inner_size();
+ let screen_descriptor = ScreenDescriptor {
+ size_in_pixels: [size.width, size.height],
+ pixels_per_point,
+ };
+
+ for (id, delta) in &full_output.textures_delta.set {
+ self.egui_renderer
+ .update_texture(&self.device, &self.queue, *id, delta);
+ }
+
+ self.egui_renderer.update_buffers(
+ &self.device,
+ &self.queue,
+ &mut encoder,
+ &clipped_primitives,
+ &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,
+ },
+ );
+
+ 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,
+ });
+
+ 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,
+ },
+ })],
+ 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.queue.submit(std::iter::once(encoder.finish()));
+ output.present();
+ }
+}
+
struct App {
- // core
- egui_renderer: Option<egui_wgpu::Renderer>,
- egui_state: Option<egui_winit::State>,
- egui_context: Option<egui::Context>,
- window: Option<&'static Window>,
- pixels: Option<Pixels<'static>>,
+ window: Option<Arc<Window>>,
+ renderer_state: Option<RendererState>,
input: Input,
@@ -85,11 +469,8 @@ struct App {
impl Default for App {
fn default() -> Self {
Self {
- egui_renderer: None,
- egui_state: None,
- egui_context: None,
window: None,
- pixels: None,
+ renderer_state: None,
input: Input {
last_mouse_pos_on_screen: None,
@@ -130,40 +511,20 @@ impl Default for App {
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
- let window = event_loop
- .create_window(Window::default_attributes().with_title(WINDOW_TITLE))
- .unwrap();
-
- let size = window.inner_size();
- let window_ref: &'static Window = Box::leak(Box::new(window));
- let surface = SurfaceTexture::new(size.width, size.height, window_ref);
+ let window = Arc::new(
+ event_loop
+ .create_window(Window::default_attributes().with_title(WINDOW_TITLE))
+ .unwrap(),
+ );
- let mut pixels = Pixels::new(PIXEL_BUFFER_WIDTH, PIXEL_BUFFER_HEIGHT, surface).unwrap();
+ self.window = Some(window.clone());
+ self.renderer_state = Some(executor::block_on(RendererState::new(window.clone())));
- pixels.set_scaling_mode(ScalingMode::Fill);
+ // let surface = SurfaceTexture::new(size.width, size.height, window_ref);
- let egui_context = Context::default();
- let egui_state = egui_winit::State::new(
- egui_context.clone(),
- egui_context.viewport_id(),
- window_ref,
- None,
- None,
- None,
- );
- let egui_renderer =
- egui_wgpu::Renderer::new(pixels.device(), pixels.render_texture_format(), {
- RendererOptions {
- msaa_samples: 1,
- ..RendererOptions::default()
- }
- });
+ // let mut pixels = Pixels::new(PIXEL_BUFFER_WIDTH, PIXEL_BUFFER_HEIGHT, surface).unwrap();
- self.egui_context = Some(egui_context);
- self.egui_state = Some(egui_state);
- self.egui_renderer = Some(egui_renderer);
- self.window = Some(window_ref);
- self.pixels = Some(pixels);
+ // pixels.set_scaling_mode(ScalingMode::Fill);
}
fn window_event(
@@ -172,15 +533,12 @@ impl ApplicationHandler for App {
_: winit::window::WindowId,
event: WindowEvent,
) {
- if let Some(pixels) = &mut self.pixels
- && let Some(window) = self.window
- && let Some(egui_context) = &self.egui_context
- && let Some(egui_state) = &mut self.egui_state
- && let Some(egui_renderer) = &mut self.egui_renderer
+ if let Some(window) = &self.window
+ && let Some(renderer_state) = &mut self.renderer_state
&& let Some(world) = &mut self.world
&& let Some(camera) = &mut self.camera
{
- let egui_response = egui_state.on_window_event(window, &event);
+ let egui_response = renderer_state.egui_state.on_window_event(window, &event);
// if egui consumed the event, it means we shouldn't treat any e.g., mouse clicks
if egui_response.consumed {
return;
@@ -218,11 +576,11 @@ impl ApplicationHandler for App {
}
WindowEvent::CursorMoved { position, .. } => {
self.input.last_mouse_pos_on_screen = Some((position.x, position.y));
- self.input.last_mouse_pos_on_board = pixels
- .window_pos_to_pixel((position.x as f32, position.y as f32))
- .map(|v| camera.screen_position_to_world(v.0 as f64, v.1 as f64))
- .map(|v| (v.0 as i32, v.1 as i32))
- .ok();
+ // self.input.last_mouse_pos_on_board = pixels
+ // .window_pos_to_pixel((position.x as f32, position.y as f32))
+ // .map(|v| camera.screen_position_to_world(v.0 as f64, v.1 as f64))
+ // .map(|v| (v.0 as i32, v.1 as i32))
+ // .ok();
}
WindowEvent::MouseInput { state, button, .. } => {
if button == MouseButton::Left {
@@ -231,7 +589,7 @@ impl ApplicationHandler for App {
}
WindowEvent::Resized(size) => {
// Important: resize the surface when the window's size change
- pixels.resize_surface(size.width, size.height).unwrap();
+ renderer_state.resize(size.width, size.height);
}
WindowEvent::CloseRequested => {
event_loop.exit();
@@ -252,40 +610,36 @@ impl ApplicationHandler for App {
// apply inputs
camera.handle_camera_input(&self.input, delta_time);
- // pixels/camera logic
- let frame = pixels.frame_mut();
- frame.fill(0);
+ // // --TEST DRAWING--
+ // if self.input.is_lmb_pressed
+ // && let Some(lm) = self.input.last_mouse_pos_on_board
+ // {
+ // // start with the bounding box of the drawing brush circle + some margin
+ // // clamp the bounding box to the board sie
+ // // TODO better way to do this without unwrap?
+ // let bb_xl = lm.0 - self.config.brush_radius as i32;
+ // let bb_xu = lm.0 + self.config.brush_radius as i32;
+ // let bb_yl = lm.1 - self.config.brush_radius as i32;
+ // let bb_yu = lm.1 + self.config.brush_radius as i32;
- // --TEST DRAWING--
- if self.input.is_lmb_pressed
- && let Some(lm) = self.input.last_mouse_pos_on_board
- {
- // start with the bounding box of the drawing brush circle + some margin
- // clamp the bounding box to the board sie
- // TODO better way to do this without unwrap?
- let bb_xl = lm.0 - self.config.brush_radius as i32;
- let bb_xu = lm.0 + self.config.brush_radius as i32;
- let bb_yl = lm.1 - self.config.brush_radius as i32;
- let bb_yu = lm.1 + self.config.brush_radius as i32;
-
- // for each point, check if the distance is less than the brush size and write the pixel
- for x in bb_xl..bb_xu {
- for y in bb_yl..bb_yu {
- let r = random_range(0.0..1.0);
- if ((x - lm.0).pow(2) + (y - lm.1).pow(2))
- < (self.config.brush_radius as i32).pow(2)
- && r > 0.9
- {
- let mut cell = Cell::from_material(self.config.brush_material);
- cell.flags = (self.sim_seqno as u8) & 0b1;
- world.set_cell_from_game_position(
- x, y, cell, // wake the chunk
- false,
- );
- }
- }
- }
- }
+ // // for each point, check if the distance is less than the brush size and write the pixel
+ // for x in bb_xl..bb_xu {
+ // for y in bb_yl..bb_yu {
+ // let r = random_range(0.0..1.0);
+ // if ((x - lm.0).pow(2) + (y - lm.1).pow(2))
+ // < (self.config.brush_radius as i32).pow(2)
+ // && r > 0.9
+ // {
+ // let mut cell = Cell::from_material(self.config.brush_material);
+ // cell.flags = (self.sim_seqno as u8) & 0b1;
+ // world.set_cell_from_game_position(
+ // x, y, cell, // wake the chunk
+ // false,
+ // );
+ // }
+ // }
+ // }
+ // }
// TODO check if we need to run another sim tick given the sim speed + delta_time
// SIM logic
@@ -295,87 +649,14 @@ impl ApplicationHandler for App {
self.ignore_pause_next_tick = false;
}
- // let get_overlay =
- // create_compute_combined_overlay_offset(&world, &self.config, &self.input);
- camera.write_frame_view(frame, &world);
-
- // egui logic
- let raw_input = egui_state.take_egui_input(window);
- let full_output = egui_context.run_ui(raw_input, |ui| {
- let right_panel = egui::Panel::right(Id::new("right_panel"));
- right_panel
- .resizable(false)
- // TODO collapse button
- .show_collapsible(ui, &mut true, |panel_ui| {
- draw_egui(
- panel_ui,
- &mut self.config,
- camera,
- &self.diagnostics,
- &self.input,
- )
- });
- });
-
- egui_state.handle_platform_output(window, full_output.platform_output);
-
- let clipped_primitives =
- egui_context.tessellate(full_output.shapes, full_output.pixels_per_point);
-
- let pixels_per_point = full_output.pixels_per_point;
-
- let size = window.inner_size();
- let screen_descriptor = ScreenDescriptor {
- size_in_pixels: [size.width, size.height],
- pixels_per_point,
- };
-
- let queue = pixels.queue();
- let device = pixels.device();
-
- for (id, delta) in &full_output.textures_delta.set {
- egui_renderer.update_texture(&device, &queue, *id, delta);
- }
-
- let _ = pixels.render_with(|encoder, render_target, context| {
- context.scaling_renderer.render(encoder, render_target);
-
- egui_renderer.update_buffers(
- device,
- &queue,
- encoder,
- &clipped_primitives,
- &screen_descriptor,
- );
+ camera.write_frame_view(&mut renderer_state.frame_view, world);
- let mut egui_pass = encoder
- .begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("egui pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: render_target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: wgpu::StoreOp::Store,
- },
- depth_slice: None,
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- multiview_mask: None,
- })
- .forget_lifetime();
-
- egui_renderer.render(
- &mut egui_pass,
- &clipped_primitives,
- &screen_descriptor,
- );
- drop(egui_pass);
-
- Ok(())
- });
+ renderer_state.render(
+ &mut self.config,
+ camera,
+ &self.diagnostics,
+ &mut self.input,
+ );
}
_ => {}
}
@@ -388,9 +669,11 @@ impl ApplicationHandler for App {
// limit our internal redraw requests to (fps)
if now - self.last_frame_requested >= frame_duration {
self.last_frame_requested = now;
- self.window
- .expect("Bug - Window should exist")
- .request_redraw();
+ if let Some(window) = &self.window {
+ window.request_redraw();
+ } else {
+ panic!("No window!")
+ }
}
}
}