diff --git a/Cargo.toml b/Cargo.toml index e49778e..50d1fa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,9 @@ edition = "2021" [features] default = [] egui_gui = ["egui-wgpu", "egui-winit", "egui"] -egui_extra = ["egui_gui", "egui_extras", "egui_plot"] +egui_extra = ["egui_gui", "egui_extras"] egui_demo = ["egui_gui", "egui_demo_lib", "egui_demo_lib/syntect"] egui_persistence = ["egui_gui", "egui_demo", "egui/persistence", "egui_demo_lib/serde"] -trace = ["wgpu/trace"] wgpu_serde = ["wgpu/serde"] [dependencies] @@ -19,31 +18,29 @@ indexmap = "2.2" pollster = "0.3" image = "0.25" bytemuck = { version = "1.16", features = ["derive"] } -wgpu = { version = "0.20", default-features = true, features = ["naga-ir"] } -winit = { version = "0.29", features = [] } -glam = "0.27" +wgpu = { version = "22.1.0", features = ["naga-ir"] } +winit = { version = "0.30" } path-clean = "1.0.1" notify = "6.1" flume = "0.11" log = "0.4" # Optional Egui -egui = { version = "0.28", optional = true } -egui-wgpu = { version = "0.28", optional = true } -egui-winit = { version = "0.28", optional = true } -egui_extras = { version = "0.28", optional = true } -egui_demo_lib = { version = "0.28", optional = true } -egui_plot = { version = "0.28", optional = true } +egui = { version = "0.29", optional = true } +egui-wgpu = { version = "0.29", optional = true } +egui-winit = { version = "0.29", optional = true } +egui_extras = { version = "0.29", optional = true } +egui_demo_lib = { version = "0.29", optional = true } [dev-dependencies] -egui_demo_lib = { version = "0.28" } -winit_input_helper = "0.16" -rapier2d = { version = "0.21.0", features = ["default", "debug-render"] } +rapier2d = { version = "0.22", features = ["default", "debug-render"] } rand = "0.8" +glam = "0.29" [lints.clippy] blocks_in_conditions = "allow" field_reassign_with_default = "allow" +self_named_constructors = "allow" [profile.dev] opt-level = 3 @@ -100,15 +97,6 @@ required-features = ["egui_gui", "egui_demo"] name = "Egui Gui" description = "Example that runs an egui demo app" -[[example]] -name = "fluid_sim" -path = "examples/fluid_sim/main.rs" -required-features = ["egui_gui"] - -[package.metadata.example.fluid_sim] -name = "Fluid Sim" -description = "Example that runs a fluid simulation using glass" - [[example]] name = "lines" path = "examples/lines.rs" @@ -121,7 +109,7 @@ description = "Example that draws lines" [[example]] name = "sand" path = "examples/sand/main.rs" -required-features = ["egui_gui"] +required-features = [] [package.metadata.example.sand] name = "Sand Sim" diff --git a/README.md b/README.md index 3471789..ae91799 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Glass + ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) ![CI](https://github.com/hakolao/glass/workflows/CI/badge.svg) @@ -11,48 +12,23 @@ to skip annoying _wgpu_ boilerplate, _winit_ boilerplate and _window_ organizati render or compute pipelines and organize your app how you like. Example: + ```rust fn main() { - Glass::new(MyApp, GlassConfig::default()).run(); - // Or if you want to avoid Device dependent Option types within your app - Glass::new_and_run(GlassConfig::default(), |e, context| MyApp::new(context)); + Glass::run(GlassConfig::default(), |_| Box::new(YourApp)) } // Organize your app in anyway you like -struct MyApp; +struct YourApp; // Implement methods that you need (to render or read inputs) -impl GlassApp for MyApp {} +impl GlassApp for YourApp {} ``` See `example` folder for more. -```rust -pub trait GlassApp { - /// Run at start - fn start(&mut self, _event_loop: &EventLoop<()>, _context: &mut GlassContext) {} - /// Run on each event received from winit - fn input( - &mut self, - _context: &mut GlassContext, - _event_loop: &EventLoopWindowTarget<()>, - _event: &Event<()>, - ) { - } - /// Run each frame - fn update(&mut self, _context: &mut GlassContext) {} - /// Run each frame for each window after update - fn render(&mut self, _context: &GlassContext, _render_data: RenderData) {} - /// Run each frame for each window after post processing - fn after_render(&mut self, _context: &GlassContext) {} - /// Run each frame last - fn end_of_frame(&mut self, _context: &mut GlassContext) {} - /// Run at exit - fn end(&mut self, _context: &mut GlassContext) {} -} -``` - # For whom + - People who want to learn rendering - People annoyed at complexities of game engines, and wanting to have more control over their app - People who wish to go back to the roots of coding (simplicity, and no magic) \ No newline at end of file diff --git a/examples/egui_gui.rs b/examples/egui_gui.rs index 2484240..b77e4be 100644 --- a/examples/egui_gui.rs +++ b/examples/egui_gui.rs @@ -6,24 +6,29 @@ use glass::{ window::GlassWindow, Glass, GlassApp, GlassConfig, GlassContext, GlassError, RenderData, }; use wgpu::{CommandBuffer, CommandEncoder, StoreOp, TextureView}; -use winit::{event::Event, event_loop::EventLoopWindowTarget}; +use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::WindowId}; fn main() -> Result<(), GlassError> { - Glass::new_and_run(GlassConfig::default(), |event_loop, context| { + Glass::run(GlassConfig::default(), |_| { Box::new(GuiApp { - gui: GuiState::new(event_loop, context), + gui: None, }) }) } impl GlassApp for GuiApp { - fn input( + fn start(&mut self, event_loop: &ActiveEventLoop, context: &mut GlassContext) { + self.gui = Some(GuiState::new(event_loop, context)); + } + + fn window_input( &mut self, context: &mut GlassContext, - _event_loop: &EventLoopWindowTarget<()>, - event: &Event<()>, + _event_loop: &ActiveEventLoop, + window_id: WindowId, + event: &WindowEvent, ) { - update_egui_with_winit_event(self, context, event); + update_egui_with_winit_event(self, context, window_id, event); } fn render( @@ -36,7 +41,7 @@ impl GlassApp for GuiApp { } struct GuiApp { - gui: GuiState, + gui: Option, } struct GuiState { @@ -48,7 +53,7 @@ struct GuiState { } impl GuiState { - fn new(event_loop: &EventLoopWindowTarget<()>, context: &mut GlassContext) -> GuiState { + fn new(event_loop: &ActiveEventLoop, context: &mut GlassContext) -> GuiState { let ctx = egui::Context::default(); let pixels_per_point = context.primary_render_window().window().scale_factor() as f32; let egui_winit = egui_winit::State::new( @@ -56,6 +61,7 @@ impl GuiState { ViewportId::ROOT, event_loop, Some(pixels_per_point), + None, Some(context.device().limits().max_texture_dimension_2d as usize), ); let renderer = egui_wgpu::Renderer::new( @@ -63,6 +69,7 @@ impl GuiState { GlassWindow::default_surface_format(), None, 1, + true, ); GuiState { egui_ctx: ctx, @@ -74,27 +81,27 @@ impl GuiState { } } -fn update_egui_with_winit_event(app: &mut GuiApp, context: &mut GlassContext, event: &Event<()>) { - match event { - Event::WindowEvent { - window_id, - event, - .. - } => { - let gui = &mut app.gui; - if let Some(window) = context.render_window(*window_id) { - let EventResponse { - consumed, - repaint, - } = gui.egui_winit.on_window_event(window.window(), event); - gui.repaint = repaint; - // Skip input if event was consumed by egui - if consumed { - return; - } - } +fn update_egui_with_winit_event( + app: &mut GuiApp, + context: &mut GlassContext, + window_id: WindowId, + event: &WindowEvent, +) { + let gui = &mut app.gui; + if let Some(window) = context.render_window(window_id) { + let EventResponse { + consumed, + repaint, + } = gui + .as_mut() + .unwrap() + .egui_winit + .on_window_event(window.window(), event); + gui.as_mut().unwrap().repaint = repaint; + // Skip input if event was consumed by egui + if consumed { + return; } - _ => {} } } @@ -124,7 +131,7 @@ fn render_egui( egui_winit, ui_app, .. - } = &mut app.gui; + } = &mut app.gui.as_mut().unwrap(); let raw_input = egui_winit.take_egui_input(window.window()); let FullOutput { shapes, @@ -162,7 +169,7 @@ fn render_egui( // Render { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, @@ -178,7 +185,11 @@ fn render_egui( }); // Here you would render your scene // Render Egui - renderer.render(&mut render_pass, &*clipped_primitives, &screen_descriptor); + renderer.render( + &mut render_pass.forget_lifetime(), + &*clipped_primitives, + &screen_descriptor, + ); } for id in &textures_delta.free { diff --git a/examples/fluid_sim/app.rs b/examples/fluid_sim/app.rs deleted file mode 100644 index c014db5..0000000 --- a/examples/fluid_sim/app.rs +++ /dev/null @@ -1,352 +0,0 @@ -use glam::Vec2; -use glass::{ - egui::Color32, - pipelines::QuadPipeline, - texture::Texture, - wgpu::{BlendState, ColorTargetState, ColorWrites, Extent3d, TextureFormat, TextureUsages}, - window::GlassWindow, - winit::{ - event::Event, - event_loop::{EventLoop, EventLoopWindowTarget}, - }, - GlassApp, GlassContext, RenderData, -}; -use wgpu::{CommandBuffer, StoreOp}; -use winit::{event::MouseButton, keyboard::KeyCode}; -use winit_input_helper::WinitInputHelper; - -use crate::{ - camera::Camera, circle_pipeline::CirclePipeline, color::Color, fluid_sim::FluidScene, - post_processing::PostProcessing, rectangle_pipeline::RectanglePipeline, timer::Timer, -}; - -pub const WIDTH: u32 = 1920; -pub const HEIGHT: u32 = 1080; - -pub struct FluidSimApp { - circle_pipeline: Option, - rectangle_pipeline: Option, - quad_pipeline: Option, - post_processing: Option, - render_target: Option, - camera: Camera, - input: WinitInputHelper, - fluid_scene: FluidScene, - timer: Timer, -} - -impl FluidSimApp { - pub fn new() -> FluidSimApp { - FluidSimApp { - circle_pipeline: None, - rectangle_pipeline: None, - quad_pipeline: None, - render_target: None, - post_processing: None, - camera: Camera::new([WIDTH as f32, HEIGHT as f32]), - input: WinitInputHelper::default(), - fluid_scene: FluidScene::new(WIDTH as f32, HEIGHT as f32), - timer: Timer::new(), - } - } -} - -impl GlassApp for FluidSimApp { - fn start(&mut self, _event_loop: &EventLoop<()>, context: &mut GlassContext) { - self.render_target = Some(create_render_target(context)); - self.circle_pipeline = Some(CirclePipeline::new(context.device(), ColorTargetState { - format: TextureFormat::Rgba16Float, - blend: Some(BlendState::ALPHA_BLENDING), - write_mask: ColorWrites::ALL, - })); - self.rectangle_pipeline = - Some(RectanglePipeline::new(context.device(), ColorTargetState { - format: TextureFormat::Rgba16Float, - blend: Some(BlendState::ALPHA_BLENDING), - write_mask: ColorWrites::ALL, - })); - self.quad_pipeline = Some(QuadPipeline::new(context.device(), ColorTargetState { - format: GlassWindow::default_surface_format(), - blend: Some(BlendState::REPLACE), - write_mask: ColorWrites::ALL, - })); - self.post_processing = Some(PostProcessing::new(context)); - } - - fn input( - &mut self, - _context: &mut GlassContext, - _event_loop: &EventLoopWindowTarget<()>, - event: &Event<()>, - ) { - self.input.update(event); - } - - fn update(&mut self, context: &mut GlassContext) { - context - .primary_render_window() - .window() - .set_title(&format!("FPS: {:.3}", self.timer.avg_fps())); - let (_, scroll_diff) = self.input.scroll_diff(); - if scroll_diff > 0.0 { - self.camera.set_scale(self.camera.scale() / 1.05); - } else if scroll_diff < 0.0 { - self.camera.set_scale(self.camera.scale() * 1.05); - } - if self.input.window_resized().is_some() || self.input.scale_factor_changed().is_some() { - self.resize(context); - } - // Read inputs state - if self.input.key_pressed(KeyCode::Space) { - self.fluid_scene.toggle_pause(); - } - if self.input.key_pressed(KeyCode::KeyR) { - self.fluid_scene.reset(); - } - if self.input.key_pressed(KeyCode::KeyG) { - self.fluid_scene.toggle_grid(); - } - if self.input.key_pressed(KeyCode::KeyP) { - self.fluid_scene.toggle_particles(); - } - if self.input.key_pressed(KeyCode::KeyF) { - self.fluid_scene.toggle_gravity(); - } - if let Some((x, y)) = self.input.cursor() { - let screen_size = context.primary_render_window().surface_size(); - let scale_factor = context.primary_render_window().window().scale_factor() as f32; - let pos = cursor_to_world( - Vec2::new(x, y) / scale_factor, - &[ - screen_size[0] as f32 / scale_factor, - screen_size[1] as f32 / scale_factor, - ], - &self.camera, - ); - if self.input.mouse_pressed(MouseButton::Left) { - self.fluid_scene.drag(pos, true); - } - if self.input.mouse_held(MouseButton::Left) { - self.fluid_scene.drag(pos, false); - } - if self.input.mouse_released(MouseButton::Left) { - self.fluid_scene.end_drag(); - } - } - // Simulate - self.fluid_scene.simulate(); - } - - fn render( - &mut self, - context: &GlassContext, - render_data: RenderData, - ) -> Option> { - // Render on render target - // Paste render target over swapchain image - let FluidSimApp { - circle_pipeline, - rectangle_pipeline, - quad_pipeline, - post_processing, - render_target, - camera, - input, - fluid_scene, - .. - } = self; - let circle_pipeline = circle_pipeline.as_ref().unwrap(); - let rectangle_pipeline = rectangle_pipeline.as_ref().unwrap(); - let quad_pipeline = quad_pipeline.as_ref().unwrap(); - let post_processing = post_processing.as_ref().unwrap(); - let render_target = render_target.as_ref().unwrap(); - let window = context.primary_render_window(); - let window_size = window.surface_size(); - let scale_factor = input.scale_factor().unwrap_or(1.0) as f32; - let window_size_f32 = [ - window_size[0] as f32 * scale_factor, - window_size[1] as f32 * scale_factor, - ]; - let RenderData { - encoder, - frame, - .. - } = render_data; - // We don't need to submit our commands, because they get submitted after `render`. - - let rt_view = render_target - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - // Draw scene to render target - { - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &rt_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - let view_proj = camera.view_proj().to_cols_array_2d(); - // Draw bounds - rectangle_pipeline.draw( - &mut rpass, - view_proj, - [0.0, 0.0], - Color32::RED.into(), - WIDTH as f32, - HEIGHT as f32, - 2.0 / HEIGHT as f32, - 0.01, - ); - - // Draw circle(s) - if fluid_scene.show_particles { - let radius = fluid_scene.render_radius(); - for i in 0..fluid_scene.fluid.num_particles { - let tank_pos = fluid_scene.fluid.particle_pos[i]; - let pos = fluid_scene.render_pos(tank_pos); - let color = fluid_scene.fluid.particle_color[i]; - circle_pipeline.draw( - &mut rpass, - view_proj, - pos.into(), - Color { - color: [color.x, color.y, color.z, 1.0], - }, - radius, - radius, - 0.01, - ); - } - } - - if fluid_scene.show_grid { - let size = fluid_scene.render_cell_size(); - for x in 0..fluid_scene.fluid.f_num_x { - for y in 0..fluid_scene.fluid.f_num_y { - let fluid_pos = Vec2::new( - (x as f32 + 0.5) * fluid_scene.fluid.h, - (y as f32 + 0.5) * fluid_scene.fluid.h, - ); - let pos = fluid_scene.render_pos(fluid_pos); - let i = x * fluid_scene.fluid.f_num_y + y; - let color = fluid_scene.fluid.cell_color[i]; - rectangle_pipeline.draw( - &mut rpass, - view_proj, - pos.into(), - Color { - color: [color.x, color.y, color.z, 0.3], - }, - size, - size, - size * 0.5, - 0.01, - ); - } - } - } - - // Obstacle - let pos = fluid_scene.render_pos(fluid_scene.obstacle_pos); - let radius = fluid_scene.render_obstacle_radius(); - circle_pipeline.draw( - &mut rpass, - view_proj, - pos.into(), - Color { - color: [1.0, 0.0, 0.0, 1.0], - }, - radius, - radius, - 0.01, - ); - } - - // Post Processing - post_processing.run(context, encoder, render_target); - let post_processed_target = post_processing.output(); - - let main_view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let render_target_bind_group = quad_pipeline.create_bind_group( - context.device(), - &post_processed_target.views[0], - &post_processed_target.sampler, - ); - // Draw render target over swapchain image - { - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &main_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - quad_pipeline.draw( - &mut rpass, - &render_target_bind_group, - // Center - [0.0, 0.0, 0.0, 0.0], - camera.centered_projection().to_cols_array_2d(), - window_size_f32, - 1.0, - ); - } - None - } - - fn end_of_frame(&mut self, _context: &mut GlassContext) { - self.timer.update(); - } -} - -impl FluidSimApp { - fn resize(&mut self, context: &GlassContext) { - let window_size = context.primary_render_window().surface_size(); - self.render_target = Some(create_render_target(context)); - self.camera - .update(&[window_size[0] as f32, window_size[1] as f32]); - } -} - -pub fn create_render_target(context: &GlassContext) -> Texture { - Texture::empty( - context.device(), - "Render Target", - Extent3d { - width: WIDTH, - height: HEIGHT, - depth_or_array_layers: 1, - }, - 1, - TextureFormat::Rgba16Float, - &Default::default(), - TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, - ) -} - -pub fn cursor_to_world(cursor_pos: Vec2, screen_size: &[f32; 2], camera: &Camera) -> Vec2 { - (cursor_pos - Vec2::new(screen_size[0] / 2.0, screen_size[1] / 2.0)) - * camera.scale() - // Invert y here, because we want world positions to grow up, and right - * Vec2::new(1.0, -1.0) - + Vec2::new(camera.pos.x, camera.pos.y) -} diff --git a/examples/fluid_sim/camera.rs b/examples/fluid_sim/camera.rs deleted file mode 100644 index 561332e..0000000 --- a/examples/fluid_sim/camera.rs +++ /dev/null @@ -1,108 +0,0 @@ -use glam::{Vec2, Vec3}; - -#[rustfmt::skip] -pub const OPENGL_TO_WGPU: glam::Mat4 = glam::Mat4::from_cols_array(&[ - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.0, 0.0, 0.5, 1.0, -]); - -pub const Z_POS: f32 = -10.0; - -#[derive(Copy, Clone)] -pub struct Camera { - pub pos: Vec2, - left: f32, - right: f32, - bottom: f32, - top: f32, - near: f32, - far: f32, - scale: f32, -} - -impl Default for Camera { - fn default() -> Self { - Camera::new([1920.0, 1080.0]) - } -} - -impl Camera { - pub fn new(screen_size: [f32; 2]) -> Camera { - let half_width = screen_size[0] / 2.0; - let half_height = screen_size[1] / 2.0; - Camera { - pos: Vec2::ZERO, - left: -half_width, - right: half_width, - bottom: -half_height, - top: half_height, - near: 0.0, - far: 1000.0, - scale: 1.0, - } - } - - pub fn centered_projection(&self) -> glam::Mat4 { - OPENGL_TO_WGPU - * glam::Mat4::orthographic_rh( - self.left, - self.right, - self.bottom, - self.top, - self.near, - self.far, - ) - } - - #[allow(unused)] - pub fn set_scale(&mut self, scale: f32) { - self.scale = scale; - } - - #[allow(unused)] - pub fn translate(&mut self, delta: Vec2) { - self.pos += delta; - } - - pub fn update(&mut self, screen_size: &[f32; 2]) { - let half_width = screen_size[0] / 2.0; - let half_height = screen_size[1] / 2.0; - self.left = -half_width; - self.right = half_width; - self.bottom = -half_height; - self.top = half_height; - } - - pub fn view(&self) -> glam::Mat4 { - OPENGL_TO_WGPU - } - - pub fn translation(&self) -> glam::Mat4 { - glam::Mat4::look_to_rh( - Vec2::new(self.pos.x, self.pos.y).extend(Z_POS), - Vec3::new(0.0, 0.0, -1.0), - Vec3::new(0.0, 1.0, 0.0), - ) - } - - pub fn proj(&self) -> glam::Mat4 { - glam::Mat4::orthographic_rh( - self.left * self.scale, - self.right * self.scale, - self.bottom * self.scale, - self.top * self.scale, - self.near, - self.far, - ) - } - - pub fn view_proj(&self) -> glam::Mat4 { - self.view() * self.proj() * self.translation() - } - - pub fn scale(&self) -> f32 { - self.scale - } -} diff --git a/examples/fluid_sim/circle.wgsl b/examples/fluid_sim/circle.wgsl deleted file mode 100644 index 1e1a3f6..0000000 --- a/examples/fluid_sim/circle.wgsl +++ /dev/null @@ -1,50 +0,0 @@ -struct PushConstants { - view_proj: mat4x4, - color: vec4, - pos: vec2, - radius: f32, - thickness: f32, - smoothness: f32, -} -var pc: PushConstants; - -struct VertexInput { - @location(0) position: vec2, - @location(1) tex_coords: vec2, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) color: vec4, - @location(1) uv: vec2, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - let vertex_pos = model.position; - let pos = pc.pos; - let size = pc.radius * 2.0; - let world_position = vec4(size, size, 0.0, 1.0) * - vec4(vertex_pos, 0.0, 1.0) + - // Offset - vec4(pos, 0.0, 0.0); - out.clip_position = pc.view_proj * world_position; - out.color = pc.color; - out.uv = model.tex_coords; - return out; -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let size = 2.0 * pc.radius; - let uv = in.uv - vec2(0.5, 0.5); - let dist = length(uv); - let half_thickness = pc.thickness / 2.0; - let outer_val = smoothstep(0.5, 0.5 - pc.smoothness, dist); - let inner_val = smoothstep(0.5 - pc.thickness, 0.5 - pc.thickness - pc.smoothness, dist); - let val = outer_val - inner_val; - return in.color * val; -} \ No newline at end of file diff --git a/examples/fluid_sim/circle_pipeline.rs b/examples/fluid_sim/circle_pipeline.rs deleted file mode 100644 index 045be8a..0000000 --- a/examples/fluid_sim/circle_pipeline.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::borrow::Cow; - -use bytemuck::{Pod, Zeroable}; -use glass::wgpu::{ - util::DeviceExt, BindGroup, Buffer, Device, PushConstantRange, RenderPass, RenderPipeline, - ShaderStages, -}; - -use crate::{ - color::Color, - simple_vertex::{SimpleVertex, SIMPLE_QUAD_INDICES, SIMPLE_QUAD_VERTICES}, -}; - -pub struct CirclePipeline { - pipeline: RenderPipeline, - vertices: Buffer, - indices: Buffer, - bind_group: BindGroup, -} - -impl CirclePipeline { - pub fn new(device: &Device, color_target_state: wgpu::ColorTargetState) -> CirclePipeline { - let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(SIMPLE_QUAD_VERTICES), - usage: wgpu::BufferUsages::VERTEX, - }); - let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(SIMPLE_QUAD_INDICES), - usage: wgpu::BufferUsages::INDEX, - }); - let pipeline = Self::new_render_pipeline(device, color_target_state); - let bind_group_layout = pipeline.get_bind_group_layout(0); - // Must match layout :), but no inputs so easy... - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &bind_group_layout, - entries: &[], - label: Some("bind_group"), - }); - Self { - pipeline, - vertices, - indices, - bind_group, - } - } - - pub fn new_render_pipeline( - device: &Device, - color_target_state: wgpu::ColorTargetState, - ) -> RenderPipeline { - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[], - label: Some("circle_bind_group_layout"), - }); - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("circle.wgsl"))), - }); - let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Circle Pipeline Layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[PushConstantRange { - stages: ShaderStages::VERTEX_FRAGMENT, - range: 0..std::mem::size_of::() as u32, - }], - }); - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Circle Render Pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - compilation_options: Default::default(), - buffers: &[SimpleVertex::desc()], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - compilation_options: Default::default(), - targets: &[Some(color_target_state)], - }), - 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 { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); - pipeline - } - - pub fn draw<'r>( - &'r self, - rpass: &mut RenderPass<'r>, - view_proj: [[f32; 4]; 4], - pos: [f32; 2], - color: Color, - radius: f32, - thickness: f32, - smoothness: f32, - ) { - rpass.set_pipeline(&self.pipeline); - rpass.set_bind_group(0, &self.bind_group, &[]); - rpass.set_vertex_buffer(0, self.vertices.slice(..)); - rpass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint16); - rpass.set_push_constants( - ShaderStages::VERTEX_FRAGMENT, - 0, - bytemuck::cast_slice(&[CirclePushConstants::new( - view_proj, - pos, - color.color, - radius, - thickness, - smoothness, - )]), - ); - rpass.draw_indexed(0..(SIMPLE_QUAD_INDICES.len() as u32), 0, 0..1); - } -} - -#[repr(C)] -#[derive(Copy, Clone, Pod, Zeroable)] -pub struct CirclePushConstants { - pub view_proj: [[f32; 4]; 4], - pub color: [f32; 4], - pub pos: [f32; 2], - pub radius: f32, - pub thickness: f32, - pub smoothness: f32, -} - -impl CirclePushConstants { - pub fn new( - view_proj: [[f32; 4]; 4], - pos: [f32; 2], - color: [f32; 4], - radius: f32, - thickness: f32, - smoothness: f32, - ) -> CirclePushConstants { - CirclePushConstants { - view_proj, - pos, - color, - radius, - thickness, - smoothness, - } - } -} diff --git a/examples/fluid_sim/color.rs b/examples/fluid_sim/color.rs deleted file mode 100644 index fcb097d..0000000 --- a/examples/fluid_sim/color.rs +++ /dev/null @@ -1,24 +0,0 @@ -use egui::Color32; - -#[derive(Default, Copy, Clone)] -pub struct Color { - pub color: [f32; 4], -} - -impl Color { - #[allow(unused)] - pub fn new(r: u8, g: u8, b: u8, a: u8) -> Color { - let color = Color32::from_rgba_premultiplied(r, g, b, a).to_normalized_gamma_f32(); - Color { - color, - } - } -} - -impl From for Color { - fn from(value: Color32) -> Self { - Color { - color: value.to_normalized_gamma_f32(), - } - } -} diff --git a/examples/fluid_sim/fluid_sim.rs b/examples/fluid_sim/fluid_sim.rs deleted file mode 100644 index 4b9c7d1..0000000 --- a/examples/fluid_sim/fluid_sim.rs +++ /dev/null @@ -1,786 +0,0 @@ -use glam::{IVec2, Vec2, Vec3}; - -// Fluid sim based on https://github.com/matthias-research/pages/blob/master/tenMinutePhysics/18-flip.html - -#[derive(Debug)] -pub struct FluidScene { - pub width: f32, - pub height: f32, - tank_width: f32, - tank_height: f32, - gravity: Vec2, - dt: f32, - flip_ratio: f32, - num_pressure_iters: usize, - num_particle_iters: usize, - over_relaxation: f32, - compensate_drift: bool, - paused: bool, - pub obstacle_pos: Vec2, - pub obstacle_radius: f32, - obstacle_vel: Vec2, - pub show_particles: bool, - pub show_grid: bool, - pub fluid: FluidSim, -} - -impl FluidScene { - pub fn new(width: f32, height: f32) -> FluidScene { - let res = 100.0; - let sim_height = 3.0; - let c_scale = height / sim_height; - let sim_width = width / c_scale; - let tank_height = 1.0 * sim_height; - let tank_width = 1.0 * sim_width; - let h = tank_height / res; - let rel_water_height = 0.8; - let rel_water_width = 0.6; - - // particle radius w.r.t. cell size - let r = 0.3 * h; - let dx = 2.0 * r; - let dy = 3.0_f32.sqrt() / 2.0 * dx; - let num_x = ((rel_water_width * tank_width - 2.0 * h - 2.0 * r) / dx).floor() as usize; - let num_y = ((rel_water_height * tank_height - 2.0 * h - 2.0 * r) / dy).floor() as usize; - let num_particles = num_x * num_y; - let mut fluid = FluidSim::new(tank_width, tank_height, h, r, num_particles); - // Setup particles - let mut i = 0; - for x in 0..num_x { - for y in 0..num_y { - fluid.particle_pos[i].x = h + r + dx * x as f32 + if y % 2 == 0 { 0.0 } else { r }; - fluid.particle_pos[i].y = h + r + dy * y as f32; - i += 1; - } - } - - // Setup grid cells - for x in 0..fluid.f_num_x { - for y in 0..fluid.f_num_y { - // fluid - let mut s = 1.0; - if x == 0 || x == fluid.f_num_x - 1 || y == 0 || y == fluid.f_num_y - 1 { - // solid - s = 0.0; - } - fluid.s[x * fluid.f_num_y + y] = s; - } - } - let obstacle_radius = 0.15; - - FluidScene { - width, - height, - tank_width, - tank_height, - gravity: Vec2::new(0.0, -9.81), - dt: 1.0 / 60.0, - flip_ratio: 0.9, - num_pressure_iters: 50, - num_particle_iters: 2, - over_relaxation: 1.9, - compensate_drift: true, - paused: true, - obstacle_pos: Vec2::new(-obstacle_radius, -obstacle_radius), - obstacle_vel: Vec2::ZERO, - show_particles: true, - show_grid: false, - fluid, - obstacle_radius, - } - } - - pub fn reset(&mut self) { - *self = FluidScene::new(self.width, self.height); - } - - pub fn toggle_pause(&mut self) { - self.paused = !self.paused; - } - - pub fn toggle_grid(&mut self) { - self.show_grid = !self.show_grid; - } - - pub fn toggle_particles(&mut self) { - self.show_particles = !self.show_particles; - } - - pub fn toggle_gravity(&mut self) { - if self.gravity == Vec2::ZERO { - self.gravity = Vec2::new(0.0, -9.81); - } else { - self.gravity = Vec2::ZERO; - } - } - - pub fn render_pos(&self, pos: Vec2) -> Vec2 { - pos * Vec2::new(self.width / self.tank_width, self.height / self.tank_height) - - Vec2::new(self.width * 0.5, self.height * 0.5) - } - - pub fn render_radius(&self) -> f32 { - self.fluid.particle_radius * self.width / self.tank_width - } - - pub fn render_cell_size(&self) -> f32 { - self.fluid.h * self.width / self.tank_width - } - - pub fn render_obstacle_radius(&self) -> f32 { - self.obstacle_radius * self.width / self.tank_width - } - - pub fn simulate(&mut self) { - if !self.paused { - self.fluid.simulate( - self.dt, - self.gravity, - self.flip_ratio, - self.num_pressure_iters, - self.num_particle_iters, - self.over_relaxation, - self.compensate_drift, - self.obstacle_pos, - self.obstacle_vel, - self.obstacle_radius, - ); - } - } - - pub fn drag(&mut self, cursor_world_pos: Vec2, is_start: bool) { - let canvas_world_pos = cursor_world_pos + Vec2::new(self.width / 2.0, self.height / 2.0); - let c_scale = self.width / self.tank_width; - let tank_pos = canvas_world_pos / c_scale; - self.set_obstacle(tank_pos, is_start); - } - - pub fn end_drag(&mut self) { - self.obstacle_vel = Vec2::ZERO; - } - - pub fn set_obstacle(&mut self, pos: Vec2, reset: bool) { - let mut v = Vec2::ZERO; - - let pos = pos.clamp( - Vec2::new( - self.obstacle_radius + self.fluid.h, - self.obstacle_radius + self.fluid.h, - ), - Vec2::new( - self.tank_width - self.obstacle_radius - self.fluid.h, - self.tank_height - self.obstacle_radius - self.fluid.h, - ), - ); - if !reset { - v = (pos - self.obstacle_pos) / self.dt; - } - - self.obstacle_pos = pos; - self.obstacle_vel = v; - } -} - -#[derive(Debug)] -pub struct FluidSim { - // Fluid - pub h: f32, - f_inv_spacing: f32, - pub f_num_x: usize, - pub f_num_y: usize, - pub f_num_cells: usize, - u: Vec, - v: Vec, - du: Vec, - dv: Vec, - prev_u: Vec, - prev_v: Vec, - s: Vec, - cell_type: Vec, - pub cell_color: Vec, - // Particles - p_inv_spacing: f32, - pub particle_radius: f32, - pub num_particles: usize, - pub particle_pos: Vec, - pub particle_color: Vec, - particle_vel: Vec, - particle_density: Vec, - particle_rest_density: f32, - p_num_x: usize, - p_num_y: usize, - particle_num_cells: usize, - num_cell_particles: Vec, - first_cell_particle: Vec, - cell_particle_ids: Vec, -} - -impl FluidSim { - pub fn new( - width: f32, - height: f32, - spacing: f32, - particle_radius: f32, - num_particles: usize, - ) -> FluidSim { - let fluid_num_x = (width / spacing).floor() as usize + 1; - let fluid_num_y = (height / spacing).floor() as usize; - let h = (width / fluid_num_x as f32).max(height / fluid_num_y as f32); - let fluid_inv_spacing = 1.0 / h; - let fluid_num_cells = fluid_num_x * fluid_num_y; - let particle_inv_spacing = 1.0 / (2.2 * particle_radius); - let particle_num_x = (width * particle_inv_spacing).floor() as usize + 1; - let particle_num_y = (height * particle_inv_spacing).floor() as usize + 1; - let particle_num_cells = particle_num_x * particle_num_y; - let mut particle_color = vec![Vec3::ZERO; 3 * num_particles]; - for c in particle_color.iter_mut() { - c.z = 1.0; - } - FluidSim { - h, - f_inv_spacing: fluid_inv_spacing, - f_num_x: fluid_num_x, - f_num_y: fluid_num_y, - f_num_cells: fluid_num_cells, - u: vec![0.0; fluid_num_cells], - v: vec![0.0; fluid_num_cells], - du: vec![0.0; fluid_num_cells], - dv: vec![0.0; fluid_num_cells], - prev_u: vec![0.0; fluid_num_cells], - prev_v: vec![0.0; fluid_num_cells], - s: vec![0.0; fluid_num_cells], - cell_type: vec![CellType::Air; fluid_num_cells], - cell_color: vec![Vec3::ZERO; fluid_num_cells], - p_inv_spacing: particle_inv_spacing, - particle_radius, - num_particles, - particle_pos: vec![Vec2::ZERO; num_particles], - particle_color, - particle_vel: vec![Vec2::ZERO; num_particles], - particle_density: vec![0.0; fluid_num_cells], - particle_rest_density: 0.0, - p_num_x: particle_num_x, - p_num_y: particle_num_y, - particle_num_cells, - num_cell_particles: vec![0; particle_num_cells], - first_cell_particle: vec![0; particle_num_cells + 1], - cell_particle_ids: vec![0; num_particles], - } - } - - pub fn simulate( - &mut self, - dt: f32, - gravity: Vec2, - flip_ratio: f32, - num_pressure_iters: usize, - num_particle_iters: usize, - over_relaxation: f32, - compensate_drift: bool, - obstacle_pos: Vec2, - obstacle_vel: Vec2, - obstacle_radius: f32, - ) { - let sub_steps = 1; - let sdt = dt / sub_steps as f32; - - for _ in 0..sub_steps { - self.integrate_particles(sdt, gravity); - self.push_particles_apart(num_particle_iters); - self.handle_particle_collisions(obstacle_pos, obstacle_vel, obstacle_radius); - self.transfer_velocities(true, flip_ratio); - self.update_particle_density(); - self.solve_incompressibility(num_pressure_iters, over_relaxation, compensate_drift); - self.transfer_velocities(false, flip_ratio); - } - - self.update_particle_colors(); - self.update_cell_colors(); - } - - fn integrate_particles(&mut self, sdt: f32, gravity: Vec2) { - for i in 0..self.num_particles { - self.particle_vel[i] += gravity * sdt; - let vel = self.particle_vel[i]; - self.particle_pos[i] += vel * sdt; - } - } - - fn push_particles_apart(&mut self, num_particle_iters: usize) { - // count particles per cell - self.num_cell_particles.fill(0); - for i in 0..self.num_particles { - let pos = self.particle_pos[i]; - let cell_number = - pos_to_cell_index(pos, self.p_inv_spacing, self.p_num_x, self.p_num_y); - self.num_cell_particles[cell_number] += 1; - } - - // partial sums - let mut first = 0; - for i in 0..self.particle_num_cells { - first += self.num_cell_particles[i]; - self.first_cell_particle[i] = first; - } - self.first_cell_particle[self.particle_num_cells] = first; - - // fill particles into cells - - for i in 0..self.num_particles { - let pos = self.particle_pos[i]; - let cell_number = - pos_to_cell_index(pos, self.p_inv_spacing, self.p_num_x, self.p_num_y); - self.first_cell_particle[cell_number] -= 1; - self.cell_particle_ids[self.first_cell_particle[cell_number]] = i; - } - // push particles apart - let min_dist = 2.0 * self.particle_radius; - let min_dist2 = min_dist * min_dist; - - for _ in 0..num_particle_iters { - for i in 0..self.num_particles { - let pos = self.particle_pos[i]; - let pxi = (pos.x * self.p_inv_spacing).floor() as i32; - let pyi = (pos.y * self.p_inv_spacing).floor() as i32; - let x0 = (pxi - 1).clamp(0, self.p_num_x as i32 - 1) as usize; - let y0 = (pyi - 1).clamp(0, self.p_num_y as i32 - 1) as usize; - let x1 = (pxi + 1).clamp(0, self.p_num_x as i32 - 1) as usize; - let y1 = (pyi + 1).clamp(0, self.p_num_y as i32 - 1) as usize; - for xi in x0..=x1 { - for yi in y0..=y1 { - let cell_number = xi * self.p_num_y + yi; - let first = self.first_cell_particle[cell_number]; - let last = self.first_cell_particle[cell_number + 1]; - for j in first..last { - let id = self.cell_particle_ids[j]; - if id == i { - continue; - } - let q = self.particle_pos[id]; - let mut d = q - pos; - let d2 = d.x * d.x + d.y * d.y; - if d2 > min_dist2 || d2 == 0.0 { - continue; - } - let dsqrt = d2.sqrt(); - let s = 0.5 * (min_dist - dsqrt) / dsqrt; - d *= s; - self.particle_pos[i] -= d; - self.particle_pos[id] += d; - } - } - } - } - } - } - - fn handle_particle_collisions( - &mut self, - obstacle_pos: Vec2, - obstacle_vel: Vec2, - obstacle_radius: f32, - ) { - let h = 1.0 / self.f_inv_spacing; - let r = self.particle_radius; - let min_x = h + r; - let max_x = (self.f_num_x - 1) as f32 * h - r; - let min_y = h + r; - let max_y = (self.f_num_y - 1) as f32 * h - r; - - let min_dist = obstacle_radius + r; - let min_dist2 = min_dist * min_dist; - - for i in 0..self.num_particles { - let mut pos = self.particle_pos[i]; - let od = pos - obstacle_pos; - let d2 = od.x * od.x + od.y * od.y; - if d2 < min_dist2 { - self.particle_vel[i] = obstacle_vel; - } - - if pos.x < min_x { - pos.x = min_x; - self.particle_vel[i].x = 0.0; - } - if pos.x > max_x { - pos.x = max_x; - self.particle_vel[i].x = 0.0; - } - if pos.y < min_y { - pos.y = min_y; - self.particle_vel[i].y = 0.0; - } - if pos.y > max_y { - pos.y = max_y; - self.particle_vel[i].y = 0.0; - } - self.particle_pos[i] = pos; - } - } - - fn transfer_velocities(&mut self, to_grid: bool, flip_ratio: f32) { - let n = self.f_num_y; - let h = self.h; - let h1 = self.f_inv_spacing; - let h2 = 0.5 * h; - - if to_grid { - self.prev_u = self.u.clone(); - self.prev_v = self.v.clone(); - - self.u.fill(0.0); - self.v.fill(0.0); - self.du.fill(0.0); - self.dv.fill(0.0); - - for i in 0..self.f_num_cells { - self.cell_type[i] = if self.s[i] == 0.0 { - CellType::Solid - } else { - CellType::Air - }; - } - - for i in 0..self.num_particles { - let pos = self.particle_pos[i]; - let cell_number = - pos_to_cell_index(pos, self.f_inv_spacing, self.f_num_x, self.f_num_y); - let cell_type = &mut self.cell_type[cell_number]; - if *cell_type == CellType::Air { - *cell_type = CellType::Fluid; - } - } - } - - for component in 0..2 { - let (dx, dy, f, prev_f, d, offset) = if component == 0 { - (0.0, h2, &mut self.u, &self.prev_u, &mut self.du, n) - } else { - (h2, 0.0, &mut self.v, &self.prev_v, &mut self.dv, 1) - }; - for i in 0..self.num_particles { - let pos = self.particle_pos[i]; - let x = pos.x.clamp(h, (self.f_num_x - 1) as f32 * h); - let y = pos.y.clamp(h, (self.f_num_y - 1) as f32 * h); - - let x0 = (((x - dx) * h1).floor() as i32).min(self.f_num_x as i32 - 2); - let tx = ((x - dx) - x0 as f32 * h) * h1; - let x1 = (x0 + 1).min(self.f_num_x as i32 - 2); - - let y0 = (((y - dy) * h1).floor() as i32).min(self.f_num_y as i32 - 2); - let ty = ((y - dy) - y0 as f32 * h) * h1; - let y1 = (y0 + 1).min(self.f_num_y as i32 - 2); - - let sx = 1.0 - tx; - let sy = 1.0 - ty; - - let d0 = sx * sy; - let d1 = tx * sy; - let d2 = tx * ty; - let d3 = sx * ty; - - let nr0 = x0 as usize * n + y0 as usize; - let nr1 = x1 as usize * n + y0 as usize; - let nr2 = x1 as usize * n + y1 as usize; - let nr3 = x0 as usize * n + y1 as usize; - - let v = if component == 0 { - &mut self.particle_vel[i].x - } else { - &mut self.particle_vel[i].y - }; - if to_grid { - f[nr0] += *v * d0; - f[nr1] += *v * d1; - f[nr2] += *v * d2; - f[nr3] += *v * d3; - d[nr0] += d0; - d[nr1] += d1; - d[nr2] += d2; - d[nr3] += d3; - } else { - let valid0 = if !self.cell_type[nr0].is_air() - || !self.cell_type[nr0 - offset].is_air() - { - 1.0 - } else { - 0.0 - }; - let valid1 = if !self.cell_type[nr1].is_air() - || !self.cell_type[nr1 - offset].is_air() - { - 1.0 - } else { - 0.0 - }; - let valid2 = if !self.cell_type[nr2].is_air() - || !self.cell_type[nr2 - offset].is_air() - { - 1.0 - } else { - 0.0 - }; - let valid3 = if !self.cell_type[nr3].is_air() - || !self.cell_type[nr3 - offset].is_air() - { - 1.0 - } else { - 0.0 - }; - let d = valid0 * d0 + valid1 * d1 + valid2 * d2 + valid3 * d3; - if d > 0.0 { - let pic_v = (valid0 * d0 * f[nr0] - + valid1 * d1 * f[nr1] - + valid2 * d2 * f[nr2] - + valid3 * d3 * f[nr3]) - / d; - let corr = (valid0 * d0 * (f[nr0] - prev_f[nr0]) - + valid1 * d1 * (f[nr1] - prev_f[nr1]) - + valid2 * d2 * (f[nr2] - prev_f[nr2]) - + valid3 * d3 * (f[nr3] - prev_f[nr3])) - / d; - let flip_v = *v + corr; - *v = (1.0 - flip_ratio) * pic_v + flip_ratio * flip_v; - } - } - } - - if to_grid { - for i in 0..f.len() { - if d[i] > 0.0 { - f[i] /= d[i]; - } - } - } - } - if to_grid { - for i in 0..self.f_num_x { - for j in 0..self.f_num_y { - let solid = self.cell_type[i * n + j] == CellType::Solid; - if solid { - self.u[i * n + j] = self.prev_u[i * n + j]; - self.v[i * n + j] = self.prev_v[i * n + j]; - } else { - if i > 0 && self.cell_type[(i - 1) * n + j] == CellType::Solid { - self.u[i * n + j] = self.prev_u[i * n + j]; - } - if j > 0 && self.cell_type[i * n + j - 1] == CellType::Solid { - self.v[i * n + j] = self.prev_v[i * n + j]; - } - } - } - } - } - } - - fn update_particle_density(&mut self) { - let n = self.f_num_y; - let h = self.h; - let h1 = self.f_inv_spacing; - let h2 = 0.5 * h; - let d = &mut self.particle_density; - d.fill(0.0); - - for i in 0..self.num_particles { - let pos = self.particle_pos[i]; - - let x = pos.x.clamp(h, (self.f_num_x - 1) as f32 * h); - let y = pos.y.clamp(h, (self.f_num_y - 1) as f32 * h); - - let x0 = ((x - h2) * h1).floor() as i32; - let tx = ((x - h2) - x0 as f32 * h) * h1; - let x1 = (x0 + 1).min(self.f_num_x as i32 - 2); - - let y0 = ((y - h2) * h1).floor() as i32; - let ty = ((y - h2) - y0 as f32 * h) * h1; - let y1 = (y0 + 1).min(self.f_num_y as i32 - 2); - - let sx = 1.0 - tx; - let sy = 1.0 - ty; - - if x0 < self.f_num_x as i32 && y0 < self.f_num_y as i32 { - d[x0 as usize * n + y0 as usize] += sx * sy; - } - if x1 < self.f_num_x as i32 && y0 < self.f_num_y as i32 { - d[x1 as usize * n + y0 as usize] += tx * sy; - } - if x1 < self.f_num_x as i32 && y1 < self.f_num_y as i32 { - d[x1 as usize * n + y1 as usize] += tx * ty; - } - if x0 < self.f_num_x as i32 && y1 < self.f_num_y as i32 { - d[x0 as usize * n + y1 as usize] += sx * ty; - } - } - - if self.particle_rest_density == 0.0 { - let mut sum = 0.0; - let mut num_fluid_cells = 0.0; - for i in 0..self.f_num_cells { - if self.cell_type[i] == CellType::Fluid { - sum += d[i]; - num_fluid_cells += 1.0; - } - } - - if num_fluid_cells > 0.0 { - self.particle_rest_density = sum / num_fluid_cells; - } - } - } - - fn solve_incompressibility( - &mut self, - num_pressure_iters: usize, - over_relaxation: f32, - compensate_drift: bool, - ) { - self.prev_u = self.u.clone(); - self.prev_v = self.v.clone(); - let n = self.f_num_y; - - for _ in 0..num_pressure_iters { - for i in 1..(self.f_num_x - 1) { - for j in 1..(self.f_num_y - 1) { - if self.cell_type[i * n + j] != CellType::Fluid { - continue; - } - let center = i * n + j; - let left = (i - 1) * n + j; - let right = (i + 1) * n + j; - let bottom = i * n + j - 1; - let top = i * n + j + 1; - - let sx0 = self.s[left]; - let sx1 = self.s[right]; - let sy0 = self.s[bottom]; - let sy1 = self.s[top]; - let s = sx0 + sx1 + sy0 + sy1; - if s == 0.0 { - continue; - } - let mut div = self.u[right] - self.u[center] + self.v[top] - self.v[center]; - if self.particle_rest_density > 0.0 && compensate_drift { - let k = 1.0; - let compression = - self.particle_density[i * n + j] - self.particle_rest_density; - if compression > 0.0 { - div = div - k * compression; - } - } - let p = (-div / s) * over_relaxation; - self.u[center] -= sx0 * p; - self.u[right] += sx1 * p; - self.v[center] -= sy0 * p; - self.v[top] += sy1 * p; - } - } - } - } - - fn update_particle_colors(&mut self) { - let d0 = self.particle_rest_density; - for i in 0..self.num_particles { - let pos = self.particle_pos[i]; - let s = 0.01; - let color = &mut self.particle_color[i]; - color.x = (color.x - s).clamp(0.0, 1.0); - color.y = (color.y - s).clamp(0.0, 1.0); - color.z = (color.z + s).clamp(0.0, 1.0); - - if d0 > 0.0 { - let cell_number = - pos_to_cell_index(pos, self.f_inv_spacing, self.f_num_x, self.f_num_y); - let rel_density = self.particle_density[cell_number] / d0; - if rel_density < 0.7 { - let s = 0.8; - color.x = s; - color.y = s; - color.z = 1.0; - } - } - } - } - - fn update_cell_colors(&mut self) { - self.cell_color.fill(Vec3::ZERO); - for i in 0..self.f_num_cells { - if self.cell_type[i] == CellType::Solid { - let color = &mut self.cell_color[i]; - color.x = 0.5; - color.y = 0.5; - color.z = 0.5; - } else if self.cell_type[i] == CellType::Fluid { - let mut d = self.particle_density[i]; - if self.particle_rest_density > 0.0 { - d /= self.particle_rest_density; - } - self.set_sci_color(i, d, 0.0, 2.0); - } - } - } - - fn set_sci_color(&mut self, cell_number: usize, density: f32, min: f32, max: f32) { - let val = density.max(min).min(max - 0.0001); - let d = max - min; - let val = if d == 0.0 { 0.5 } else { (val - min) / d }; - let m = 0.25; - let num = (val / m).floor(); - let s = (val - num * m) / m; - let mut r = 0.0; - let mut g = 0.0; - let mut b = 0.0; - match num as i32 { - 0 => { - r = 0.0; - g = s; - b = 1.0; - } - 1 => { - r = 0.0; - g = 1.0; - b = 1.0 - s; - } - 2 => { - r = s; - g = 1.0; - b = 0.0; - } - 3 => { - r = 1.0; - g = 1.0 - s; - b = 0.0; - } - _ => (), - } - let color = &mut self.cell_color[cell_number]; - color.x = r; - color.y = g; - color.z = b; - } -} - -fn pos_to_cell_pos(pos: Vec2, inv_spacing: f32, num_x: usize, num_y: usize) -> IVec2 { - (pos * inv_spacing).floor().as_ivec2().clamp( - IVec2::new(0, 0), - IVec2::new(num_x as i32 - 1, num_y as i32 - 1), - ) -} - -fn pos_to_cell_index(pos: Vec2, inv_spacing: f32, num_x: usize, num_y: usize) -> usize { - let xyi = pos_to_cell_pos(pos, inv_spacing, num_x, num_y); - xyi.x as usize * num_y + xyi.y as usize -} - -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] -#[repr(u32)] -pub enum CellType { - Fluid, - Air, - Solid, -} - -impl CellType { - pub fn is_air(&self) -> bool { - *self == CellType::Air - } -} diff --git a/examples/fluid_sim/main.rs b/examples/fluid_sim/main.rs deleted file mode 100644 index 6e7cb28..0000000 --- a/examples/fluid_sim/main.rs +++ /dev/null @@ -1,46 +0,0 @@ -mod app; -mod camera; -mod circle_pipeline; -mod color; -mod fluid_sim; -mod post_processing; -mod rectangle_pipeline; -mod simple_vertex; -mod timer; - -use glass::{ - device_context::DeviceConfig, - wgpu::{Backends, Limits, PowerPreference, PresentMode}, - window::WindowConfig, - Glass, GlassConfig, GlassError, -}; -use wgpu::InstanceFlags; - -use crate::app::{FluidSimApp, HEIGHT, WIDTH}; - -fn config() -> GlassConfig { - GlassConfig { - device_config: DeviceConfig { - power_preference: PowerPreference::HighPerformance, - features: wgpu::Features::PUSH_CONSTANTS - | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, - limits: Limits { - max_push_constant_size: 128, - ..Limits::default() - }, - backends: Backends::all(), - instance_flags: InstanceFlags::from_build_config(), - }, - window_configs: vec![WindowConfig { - width: WIDTH, - height: HEIGHT, - exit_on_esc: true, - present_mode: PresentMode::AutoVsync, - ..WindowConfig::default() - }], - } -} - -fn main() -> Result<(), GlassError> { - Glass::new(FluidSimApp::new(), config()).run() -} diff --git a/examples/fluid_sim/post_processing.rs b/examples/fluid_sim/post_processing.rs deleted file mode 100644 index bf48dc6..0000000 --- a/examples/fluid_sim/post_processing.rs +++ /dev/null @@ -1,59 +0,0 @@ -use glam::UVec2; -use glass::{ - pipelines::{BloomPipeline, BloomSettings, ColorGrading, TonemappingPipeline}, - texture::Texture, - wgpu::CommandEncoder, - GlassContext, -}; - -use crate::app::{create_render_target, HEIGHT, WIDTH}; - -pub struct PostProcessing { - bloom_pipeline: BloomPipeline, - tonemap_pipeline: TonemappingPipeline, - post_processed_image: Texture, -} - -impl PostProcessing { - pub fn new(context: &GlassContext) -> PostProcessing { - let post_processed_image = create_render_target(context); - PostProcessing { - bloom_pipeline: BloomPipeline::new( - context.device(), - BloomSettings::default(), - WIDTH, - HEIGHT, - ), - tonemap_pipeline: TonemappingPipeline::new(context.device()), - post_processed_image, - } - } - - pub fn output(&self) -> &Texture { - &self.post_processed_image - } - - pub fn run(&self, context: &GlassContext, encoder: &mut CommandEncoder, input_image: &Texture) { - self.bloom_pipeline.bloom( - context.device(), - encoder, - input_image, - UVec2::new(0, 0), - UVec2::new(WIDTH, HEIGHT), - ); - - self.tonemap_pipeline.tonemap( - context.device(), - encoder, - input_image, - &self.post_processed_image, - ColorGrading { - off: false, - exposure: 1.0, - gamma: 1.0, - pre_saturation: 1.05, - post_saturation: 1.05, - }, - ); - } -} diff --git a/examples/fluid_sim/rect.wgsl b/examples/fluid_sim/rect.wgsl deleted file mode 100644 index 7ed8299..0000000 --- a/examples/fluid_sim/rect.wgsl +++ /dev/null @@ -1,56 +0,0 @@ -struct PushConstants { - view_proj: mat4x4, - color: vec4, - pos: vec2, - width: f32, - height: f32, - thickness: f32, - smoothness: f32, -} -var pc: PushConstants; - -struct VertexInput { - @location(0) position: vec2, - @location(1) tex_coords: vec2, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) color: vec4, - @location(1) uv: vec2, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - let vertex_pos = model.position; - let pos = pc.pos; - let world_position = vec4(pc.width, pc.height, 0.0, 1.0) * - vec4(vertex_pos, 0.0, 1.0) + - // Offset - vec4(pos, 0.0, 0.0); - out.clip_position = pc.view_proj * world_position; - out.color = pc.color; - out.uv = model.tex_coords; - return out; -} - -fn box_sdf(position: vec2, half_size: vec2, corner_radius: f32) -> f32 { - let q = abs(position) - half_size + corner_radius; - return min(max(q.x, q.y), 0.0) + length(vec2(max(q.x, 0.0), max(q.y, 0.0))) - corner_radius; -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let uv = in.uv - vec2(0.5, 0.5); - let half_size = vec2(0.5, 0.5); - let ar = pc.width / pc.height; - let d = box_sdf(uv, half_size, 0.0); - let d2 = box_sdf(uv, half_size - vec2(pc.thickness) * vec2(1.0, ar), 0.0); - let val1 = 1.0 - sign(d); - let val2 = 1.0 - sign(d2); - let val = val1 - val2; - return in.color * val; -} \ No newline at end of file diff --git a/examples/fluid_sim/rectangle_pipeline.rs b/examples/fluid_sim/rectangle_pipeline.rs deleted file mode 100644 index 962a5ce..0000000 --- a/examples/fluid_sim/rectangle_pipeline.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::borrow::Cow; - -use bytemuck::{Pod, Zeroable}; -use glass::wgpu::{ - util::DeviceExt, BindGroup, Buffer, Device, PushConstantRange, RenderPass, RenderPipeline, - ShaderStages, -}; - -use crate::{ - color::Color, - simple_vertex::{SimpleVertex, SIMPLE_QUAD_INDICES, SIMPLE_QUAD_VERTICES}, -}; - -pub struct RectanglePipeline { - pipeline: RenderPipeline, - vertices: Buffer, - indices: Buffer, - bind_group: BindGroup, -} - -impl RectanglePipeline { - pub fn new(device: &Device, color_target_state: wgpu::ColorTargetState) -> RectanglePipeline { - let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(SIMPLE_QUAD_VERTICES), - usage: wgpu::BufferUsages::VERTEX, - }); - let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(SIMPLE_QUAD_INDICES), - usage: wgpu::BufferUsages::INDEX, - }); - let pipeline = Self::new_render_pipeline(device, color_target_state); - let bind_group_layout = pipeline.get_bind_group_layout(0); - // Must match layout :), but no inputs so easy... - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &bind_group_layout, - entries: &[], - label: Some("bind_group"), - }); - Self { - pipeline, - vertices, - indices, - bind_group, - } - } - - pub fn new_render_pipeline( - device: &Device, - color_target_state: wgpu::ColorTargetState, - ) -> RenderPipeline { - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[], - label: Some("rect_bind_group_layout"), - }); - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("rect.wgsl"))), - }); - let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Rect Pipeline Layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[PushConstantRange { - stages: ShaderStages::VERTEX_FRAGMENT, - range: 0..std::mem::size_of::() as u32, - }], - }); - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Rect Render Pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - compilation_options: Default::default(), - buffers: &[SimpleVertex::desc()], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - compilation_options: Default::default(), - targets: &[Some(color_target_state)], - }), - 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 { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); - pipeline - } - - pub fn draw<'r>( - &'r self, - rpass: &mut RenderPass<'r>, - view_proj: [[f32; 4]; 4], - pos: [f32; 2], - color: Color, - width: f32, - height: f32, - thickness: f32, - smoothness: f32, - ) { - rpass.set_pipeline(&self.pipeline); - rpass.set_bind_group(0, &self.bind_group, &[]); - rpass.set_vertex_buffer(0, self.vertices.slice(..)); - rpass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint16); - rpass.set_push_constants( - ShaderStages::VERTEX_FRAGMENT, - 0, - bytemuck::cast_slice(&[RectanglePushConstants::new( - view_proj, - pos, - color.color, - width, - height, - thickness, - smoothness, - )]), - ); - rpass.draw_indexed(0..(SIMPLE_QUAD_INDICES.len() as u32), 0, 0..1); - } -} - -#[repr(C)] -#[derive(Copy, Clone, Pod, Zeroable)] -pub struct RectanglePushConstants { - pub view_proj: [[f32; 4]; 4], - pub color: [f32; 4], - pub pos: [f32; 2], - pub width: f32, - pub height: f32, - pub thickness: f32, - pub smoothness: f32, -} - -impl RectanglePushConstants { - pub fn new( - view_proj: [[f32; 4]; 4], - pos: [f32; 2], - color: [f32; 4], - width: f32, - height: f32, - thickness: f32, - smoothness: f32, - ) -> RectanglePushConstants { - RectanglePushConstants { - view_proj, - pos, - color, - width, - height, - thickness, - smoothness, - } - } -} diff --git a/examples/fluid_sim/simple_vertex.rs b/examples/fluid_sim/simple_vertex.rs deleted file mode 100644 index b7124c3..0000000 --- a/examples/fluid_sim/simple_vertex.rs +++ /dev/null @@ -1,52 +0,0 @@ -use bytemuck::{Pod, Zeroable}; - -/// A vertex with position -#[repr(C)] -#[derive(Copy, Clone, Debug, Pod, Zeroable)] -pub struct SimpleVertex { - pub position: [f32; 2], - pub tex_coords: [f32; 2], -} - -impl SimpleVertex { - pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - use std::mem; - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x2, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - }, - ], - } - } -} - -pub const SIMPLE_QUAD_VERTICES: &[SimpleVertex] = &[ - SimpleVertex { - position: [-0.5, -0.5], - tex_coords: [0.0, 1.0], - }, - SimpleVertex { - position: [-0.5, 0.5], - tex_coords: [0.0, 0.0], - }, - SimpleVertex { - position: [0.5, 0.5], - tex_coords: [1.0, 0.0], - }, - SimpleVertex { - position: [0.5, -0.5], - tex_coords: [1.0, 1.0], - }, -]; - -pub const SIMPLE_QUAD_INDICES: &[u16] = &[0, 2, 1, 0, 3, 2]; diff --git a/examples/fluid_sim/timer.rs b/examples/fluid_sim/timer.rs deleted file mode 100644 index 8f7492d..0000000 --- a/examples/fluid_sim/timer.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::time::Instant; - -const NUM_TIME_SAMPLES: usize = 100; - -/// A simple performance timer -#[derive(Debug, Copy, Clone)] -pub struct Timer { - time: Instant, - time_sum: f64, - avg_ms: f64, - num_updates: usize, - delta: f64, -} - -impl Timer { - pub fn new() -> Self { - Self { - time: Instant::now(), - time_sum: 0.0, - avg_ms: 0.0, - num_updates: 0, - delta: 0.0, - } - } - - #[allow(unused)] - pub fn start(&mut self) { - self.time = Instant::now() - } - - #[allow(unused)] - pub fn end(&self) -> f64 { - self.time.elapsed().as_nanos() as f64 * 1.0e-6 - } - - #[allow(unused)] - pub fn time_since_last_update_sec(&self) -> f64 { - self.end() / 1000.0 - } - - pub fn update_with_diff(&mut self, diff: f64) { - self.delta = diff; - if self.num_updates >= NUM_TIME_SAMPLES { - self.avg_ms = self.time_sum / self.num_updates as f64; - // reset - self.time_sum = 0.0; - self.num_updates = 0; - } - self.time_sum += diff; - self.num_updates += 1; - } - - pub fn update(&mut self) { - let diff = self.time.elapsed().as_nanos() as f64 * 1.0e-6; - self.update_with_diff(diff); - self.time = Instant::now(); - } - - #[allow(unused)] - pub fn dt(&self) -> f64 { - self.delta - } - - #[allow(unused)] - pub fn dt_sec(&self) -> f64 { - self.delta / 1000.0 - } - - #[allow(unused)] - pub fn avg_ms(&self) -> f64 { - self.avg_ms - } - - pub fn avg_fps(&self) -> f64 { - 1000.0 / self.avg_ms - } -} - -impl Default for Timer { - fn default() -> Self { - Timer::new() - } -} diff --git a/examples/game_of_life/main.rs b/examples/game_of_life/main.rs index 81e7a46..cfc9343 100644 --- a/examples/game_of_life/main.rs +++ b/examples/game_of_life/main.rs @@ -12,12 +12,13 @@ use glass::{ use wgpu::{ AddressMode, Backends, BindGroup, BindGroupDescriptor, CommandBuffer, CommandEncoder, ComputePassDescriptor, ComputePipeline, ComputePipelineDescriptor, Extent3d, FilterMode, - InstanceFlags, Limits, PowerPreference, PresentMode, PushConstantRange, SamplerDescriptor, - ShaderStages, StorageTextureAccess, StoreOp, TextureFormat, TextureUsages, + InstanceFlags, Limits, MemoryHints, PowerPreference, PresentMode, PushConstantRange, + SamplerDescriptor, ShaderStages, StorageTextureAccess, StoreOp, TextureFormat, TextureUsages, }; use winit::{ - event::{ElementState, Event, MouseButton, WindowEvent}, - event_loop::{EventLoop, EventLoopWindowTarget}, + event::{ElementState, MouseButton, WindowEvent}, + event_loop::ActiveEventLoop, + window::WindowId, }; const WIDTH: u32 = 1024; @@ -35,6 +36,7 @@ fn config() -> GlassConfig { GlassConfig { device_config: DeviceConfig { power_preference: PowerPreference::HighPerformance, + memory_hints: MemoryHints::Performance, features: wgpu::Features::PUSH_CONSTANTS | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, limits: Limits { @@ -43,6 +45,7 @@ fn config() -> GlassConfig { }, backends: Backends::all(), instance_flags: InstanceFlags::from_build_config(), + trace_path: None, }, window_configs: vec![WindowConfig { width: WIDTH, @@ -55,7 +58,7 @@ fn config() -> GlassConfig { } fn main() -> Result<(), GlassError> { - Glass::new(GameOfLifeApp::default(), config()).run() + Glass::run(config(), |_| Box::new(GameOfLifeApp::default())) } // Think of this like reading a "table of contents". @@ -64,7 +67,7 @@ fn main() -> Result<(), GlassError> { // - Update is run every frame // - Render is run for each window after update every frame impl GlassApp for GameOfLifeApp { - fn start(&mut self, _event_loop: &EventLoop<()>, context: &mut GlassContext) { + fn start(&mut self, _event_loop: &ActiveEventLoop, context: &mut GlassContext) { // Create pipelines let (init_pipeline, game_of_life_pipeline, draw_pipeline) = create_game_of_life_pipeline(context); @@ -89,11 +92,12 @@ impl GlassApp for GameOfLifeApp { init_game_of_life(self, context); } - fn input( + fn window_input( &mut self, _context: &mut GlassContext, - _event_loop: &EventLoopWindowTarget<()>, - event: &Event<()>, + _event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: &WindowEvent, ) { handle_inputs(self, event); } @@ -247,26 +251,21 @@ fn render(app: &mut GameOfLifeApp, render_data: RenderData) -> Option) { - if let Event::WindowEvent { - event, .. - } = event - { - match event { - WindowEvent::CursorMoved { - position, .. - } => { - app.cursor_pos = Vec2::new(position.x as f32, position.y as f32); - } - WindowEvent::MouseInput { - button: MouseButton::Left, - state, - .. - } => { - app.draw = state == &ElementState::Pressed; - } - _ => (), +fn handle_inputs(app: &mut GameOfLifeApp, event: &WindowEvent) { + match event { + WindowEvent::CursorMoved { + position, .. + } => { + app.cursor_pos = Vec2::new(position.x as f32, position.y as f32); + } + WindowEvent::MouseInput { + button: MouseButton::Left, + state, + .. + } => { + app.draw = state == &ElementState::Pressed; } + _ => (), } } @@ -563,6 +562,7 @@ fn create_game_of_life_pipeline( module: &game_of_life_shader, entry_point: "init", compilation_options: Default::default(), + cache: None, }); let game_of_life_layout = @@ -584,6 +584,7 @@ fn create_game_of_life_pipeline( module: &game_of_life_shader, entry_point: "update", compilation_options: Default::default(), + cache: None, }); let draw_layout = context @@ -604,6 +605,7 @@ fn create_game_of_life_pipeline( module: &brush_shader, entry_point: "main", compilation_options: Default::default(), + cache: None, }); (init_pipeline, update_pipeline, draw_pipeline) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index f5172ca..5ce92b1 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,7 +1,7 @@ use glass::{Glass, GlassApp, GlassConfig, GlassError}; fn main() -> Result<(), GlassError> { - Glass::new(HelloWorld, GlassConfig::default()).run() + Glass::run(GlassConfig::default(), |_| Box::new(HelloWorld)) } struct HelloWorld; diff --git a/examples/lines.rs b/examples/lines.rs index 4c7e3a6..2113288 100644 --- a/examples/lines.rs +++ b/examples/lines.rs @@ -7,7 +7,7 @@ use glass::{ }; use rapier2d::prelude::*; use wgpu::{util::DeviceExt, Buffer, CommandBuffer, Features, Limits, StoreOp}; -use winit::event_loop::EventLoop; +use winit::event_loop::ActiveEventLoop; const WIDTH: u32 = 1920; const HEIGHT: u32 = 1080; @@ -35,11 +35,11 @@ fn config() -> GlassConfig { } fn main() -> Result<(), GlassError> { - Glass::new(LineApp::new(), config()).run() + Glass::run(config(), |context| Box::new(LineApp::new(context))) } struct LineApp { - line_pipeline: Option, + line_pipeline: LinePipeline, physics_pipeline: PhysicsPipeline, physics_world: PhysicsWorld, view_proj: Mat4, @@ -48,9 +48,13 @@ struct LineApp { } impl LineApp { - fn new() -> LineApp { + fn new(context: &mut GlassContext) -> LineApp { LineApp { - line_pipeline: None, + line_pipeline: LinePipeline::new(context.device(), wgpu::ColorTargetState { + format: GlassWindow::default_surface_format(), + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + }), physics_pipeline: PhysicsPipeline::new(), physics_world: PhysicsWorld::new(Vec2::new(0.0, -9.81)), view_proj: camera_projection([WIDTH as f32, HEIGHT as f32]), @@ -61,15 +65,7 @@ impl LineApp { } impl GlassApp for LineApp { - fn start(&mut self, _event_loop: &EventLoop<()>, context: &mut GlassContext) { - self.line_pipeline = Some(LinePipeline::new( - context.device(), - wgpu::ColorTargetState { - format: GlassWindow::default_surface_format(), - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - }, - )); + fn start(&mut self, _event_loop: &ActiveEventLoop, context: &mut GlassContext) { // Add ground let collider = ColliderBuilder::cuboid(100.0, 0.1) .translation(vector![0.0, -5.0]) @@ -88,18 +84,18 @@ impl GlassApp for LineApp { &mut self.physics_world.rigid_body_set, ); let mut some_lines = vec![]; - let line1 = Line::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(512.0, 512.0, 0.0), [ - 1.0, 0.0, 0.0, 1.0, - ]); - let line2 = Line::new(Vec3::new(512.0, 0.0, 0.0), Vec3::new(512.0, 512.0, 0.0), [ - 0.0, 1.0, 0.0, 1.0, - ]); - let line3 = Line::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(512.0, 0.0, 0.0), [ - 0.0, 0.0, 1.0, 1.0, - ]); + let line1 = Line::new([0.0, 0.0, 0.0], [512.0, 512.0, 0.0], [1.0, 0.0, 0.0, 1.0]); + let line2 = Line::new([512.0, 0.0, 0.0], [512.0, 512.0, 0.0], [0.0, 1.0, 0.0, 1.0]); + let line3 = Line::new([0.0, 0.0, 0.0], [512.0, 0.0, 0.0], [0.0, 0.0, 1.0, 1.0]); for line in [line1, line2, line3] { - some_lines.push(ColoredVertex::new_2d(line.start.truncate(), line.color)); - some_lines.push(ColoredVertex::new_2d(line.end.truncate(), line.color)); + some_lines.push(ColoredVertex::new_2d( + [line.start[0], line.start[1]], + line.color, + )); + some_lines.push(ColoredVertex::new_2d( + [line.end[0], line.end[1]], + line.color, + )); } let line_vertices = context @@ -196,7 +192,6 @@ impl GlassApp for LineApp { timestamp_writes: None, occlusion_query_set: None, }); - let line_pipeline = line_pipeline.as_ref().unwrap(); for line in lines.lines.iter() { line_pipeline.draw(&mut rpass, view_proj.to_cols_array_2d(), *line); } @@ -240,8 +235,8 @@ impl DebugRenderBackend for DebugLines { color: [f32; 4], ) { let line = Line::new( - Vec3::new(a.x, a.y, 0.0) * PHYSICS_TO_PIXELS, - Vec3::new(b.x, b.y, 0.0) * PHYSICS_TO_PIXELS, + (Vec3::new(a.x, a.y, 0.0) * PHYSICS_TO_PIXELS).into(), + (Vec3::new(b.x, b.y, 0.0) * PHYSICS_TO_PIXELS).into(), color, ); self.add_line(line); diff --git a/examples/multiple_windows/main.rs b/examples/multiple_windows/main.rs index 38af042..03960a9 100644 --- a/examples/multiple_windows/main.rs +++ b/examples/multiple_windows/main.rs @@ -3,8 +3,8 @@ use glass::{ }; use wgpu::{Color, CommandBuffer, StoreOp}; use winit::{ - event::{DeviceEvent, ElementState, Event}, - event_loop::{EventLoop, EventLoopWindowTarget}, + event::{DeviceEvent, DeviceId, ElementState}, + event_loop::ActiveEventLoop, keyboard::{KeyCode, PhysicalKey}, window::WindowId, }; @@ -13,7 +13,9 @@ const WIDTH: u32 = 256; const HEIGHT: u32 = 256; fn main() -> Result<(), GlassError> { - Glass::new(MultiWindowApp::default(), GlassConfig::windowless()).run() + Glass::run(GlassConfig::windowless(), |_| { + Box::new(MultiWindowApp::default()) + }) } const CLEAR_COLORS: [Color; 5] = [ @@ -31,7 +33,7 @@ struct MultiWindowApp { } impl GlassApp for MultiWindowApp { - fn start(&mut self, event_loop: &EventLoop<()>, context: &mut GlassContext) { + fn start(&mut self, event_loop: &ActiveEventLoop, context: &mut GlassContext) { println!("Press space to create windows, esc to close all but last"); self.window_ids.push( context @@ -45,17 +47,14 @@ impl GlassApp for MultiWindowApp { ); } - fn input( + fn device_input( &mut self, context: &mut GlassContext, - event_loop: &EventLoopWindowTarget<()>, - event: &Event<()>, + event_loop: &ActiveEventLoop, + _device_id: DeviceId, + event: &DeviceEvent, ) { - if let Event::DeviceEvent { - event: DeviceEvent::Key(input), - .. - } = event - { + if let DeviceEvent::Key(input) = event { if input.physical_key == PhysicalKey::Code(KeyCode::Space) && input.state == ElementState::Pressed { diff --git a/examples/quad/main.rs b/examples/quad/main.rs index e41a87f..f53f70d 100644 --- a/examples/quad/main.rs +++ b/examples/quad/main.rs @@ -9,7 +9,7 @@ use wgpu::{ AddressMode, BindGroup, CommandBuffer, FilterMode, Limits, SamplerDescriptor, StoreOp, TextureFormat, TextureUsages, }; -use winit::event_loop::EventLoop; +use winit::event_loop::ActiveEventLoop; const WIDTH: u32 = 1920; const HEIGHT: u32 = 1080; @@ -22,7 +22,7 @@ const OPENGL_TO_WGPU: glam::Mat4 = glam::Mat4::from_cols_array(&[ ]); fn main() -> Result<(), GlassError> { - Glass::new(TreeApp::default(), config()).run() + Glass::run(config(), |_| Box::new(TreeApp::default())) } fn config() -> GlassConfig { @@ -52,7 +52,7 @@ struct TreeApp { } impl GlassApp for TreeApp { - fn start(&mut self, _event_loop: &EventLoop<()>, context: &mut GlassContext) { + fn start(&mut self, _event_loop: &ActiveEventLoop, context: &mut GlassContext) { let quad_pipeline = QuadPipeline::new(context.device(), wgpu::ColorTargetState { format: GlassWindow::default_surface_format(), blend: Some(wgpu::BlendState { diff --git a/examples/sand/grid.rs b/examples/sand/grid.rs index 63afe0a..c7794bf 100644 --- a/examples/sand/grid.rs +++ b/examples/sand/grid.rs @@ -79,7 +79,7 @@ impl Grid { self.rgba.put_pixel( x as u32, self.height - y as u32 - 1, - sand.color.to_srgba_unmultiplied().into(), + [sand.color[0], sand.color[1], sand.color[2], 255].into(), ); self.changed = true; @@ -94,7 +94,7 @@ impl Grid { let curr = self.data[curr_index]; let curr_sand = curr.sand; // Below - if !curr_sand.is_empty() && y as i32 - 1 >= 0 { + if !curr_sand.is_empty() && y as i32 > 0 { let below_index = self.index(x as i32, y as i32 - 1); let below_sand = self.data[below_index].sand; let below = self.data[below_index]; @@ -106,7 +106,7 @@ impl Grid { } else { let p = rand::random::(); let mut is_swap = false; - if p > 0.5 && x as i32 - 1 >= 0 { + if p > 0.5 && x as i32 > 0 { let left_diag_index = self.index(x as i32 - 1, y as i32 - 1); let left_diag_sand = self.data[left_diag_index].sand; let left = self.data[left_diag_index]; @@ -131,7 +131,7 @@ impl Grid { } if !is_swap && curr_sand.is_water() { let p = rand::random::(); - if p > 0.5 && x as i32 - 1 >= 0 { + if p > 0.5 && x as i32 > 0 { let left_index = self.index(x as i32 - 1, y as i32); let left_sand = self.data[left_index].sand; let left = self.data[left_index]; diff --git a/examples/sand/main.rs b/examples/sand/main.rs index 7624399..454c89b 100644 --- a/examples/sand/main.rs +++ b/examples/sand/main.rs @@ -16,8 +16,9 @@ use wgpu::{ RenderPassDescriptor, StoreOp, TextureViewDescriptor, }; use winit::{ - event::{ElementState, Event, MouseButton, WindowEvent}, - event_loop::EventLoopWindowTarget, + event::{ElementState, MouseButton, WindowEvent}, + event_loop::ActiveEventLoop, + window::WindowId, }; use crate::{grid::Grid, sand::SandType, timer::Timer}; @@ -26,7 +27,7 @@ const CANVAS_SIZE: u32 = 512; const CANVAS_SCALE: u32 = 2; fn main() -> Result<(), GlassError> { - Glass::new_and_run(config(), |_e, context| { + Glass::run(config(), |context| { Box::new(SandSim::new(context)) as Box }) } @@ -65,45 +66,41 @@ impl SandSim { } impl GlassApp for SandSim { - fn input( + fn window_input( &mut self, _context: &mut GlassContext, - _event_loop: &EventLoopWindowTarget<()>, - event: &Event<()>, + _event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: &WindowEvent, ) { - if let Event::WindowEvent { - event, .. - } = event - { - match event { - WindowEvent::CursorMoved { - position, .. - } => { - self.cursor_pos = Vec2::new(position.x as f32, position.y as f32); - } - WindowEvent::MouseInput { - button: MouseButton::Left, - state, - .. - } => { - self.draw_sand = state == &ElementState::Pressed; - } - WindowEvent::MouseInput { - button: MouseButton::Right, - state, - .. - } => { - self.draw_empty = state == &ElementState::Pressed; - } - WindowEvent::MouseInput { - button: MouseButton::Middle, - state, - .. - } => { - self.draw_water = state == &ElementState::Pressed; - } - _ => (), + match event { + WindowEvent::CursorMoved { + position, .. + } => { + self.cursor_pos = Vec2::new(position.x as f32, position.y as f32); + } + WindowEvent::MouseInput { + button: MouseButton::Left, + state, + .. + } => { + self.draw_sand = state == &ElementState::Pressed; + } + WindowEvent::MouseInput { + button: MouseButton::Right, + state, + .. + } => { + self.draw_empty = state == &ElementState::Pressed; + } + WindowEvent::MouseInput { + button: MouseButton::Middle, + state, + .. + } => { + self.draw_water = state == &ElementState::Pressed; } + _ => (), } } diff --git a/examples/sand/sand.rs b/examples/sand/sand.rs index ec93997..f247359 100644 --- a/examples/sand/sand.rs +++ b/examples/sand/sand.rs @@ -1,4 +1,3 @@ -use egui::{Color32, Rgba}; use glam::Vec2; #[derive(Copy, Clone, Eq, PartialEq)] @@ -21,7 +20,7 @@ impl SandType { #[derive(Copy, Clone)] pub struct Sand { pub sand: SandType, - pub color: Rgba, + pub color: [u8; 3], } impl Sand { @@ -35,25 +34,24 @@ impl Sand { pub fn sand(pos: Vec2) -> Sand { let rand_color = variate_color([0xc2, 0xb2, 0x80], 0.1, pos); - let color = Color32::from_rgb(rand_color[0], rand_color[1], rand_color[2]); Sand { sand: SandType::Sand, - color: Rgba::from(color), + color: rand_color, } } pub fn water() -> Sand { - let color = Color32::from_rgb(0x23, 0x89, 0xda); + let color = [0x23, 0x89, 0xda]; Sand { sand: SandType::Water, - color: Rgba::from(color), + color, } } pub fn empty() -> Sand { Sand { sand: SandType::Empty, - color: Rgba::BLACK, + color: [0; 3], } } } diff --git a/examples/shader_with_includes/main.rs b/examples/shader_with_includes/main.rs index 599a16b..266c373 100644 --- a/examples/shader_with_includes/main.rs +++ b/examples/shader_with_includes/main.rs @@ -7,17 +7,15 @@ use wgpu::{ CommandBuffer, MultisampleState, PipelineLayoutDescriptor, PrimitiveState, RenderPipeline, RenderPipelineDescriptor, ShaderModuleDescriptor, StoreOp, TextureFormat, }; -use winit::event_loop::EventLoop; +use winit::event_loop::ActiveEventLoop; const WIDTH: u32 = 1920; const HEIGHT: u32 = 1080; fn main() -> Result<(), GlassError> { - Glass::new( - TriangleApp::default(), - GlassConfig::performance(WIDTH, HEIGHT), - ) - .run() + Glass::run(GlassConfig::performance(WIDTH, HEIGHT), |_| { + Box::new(TriangleApp::default()) + }) } #[derive(Default)] @@ -26,7 +24,7 @@ struct TriangleApp { } impl GlassApp for TriangleApp { - fn start(&mut self, _event_loop: &EventLoop<()>, context: &mut GlassContext) { + fn start(&mut self, _event_loop: &ActiveEventLoop, context: &mut GlassContext) { self.triangle_pipeline = Some(create_triangle_pipeline(context)); } @@ -126,6 +124,7 @@ fn create_triangle_pipeline(context: &GlassContext) -> RenderPipeline { depth_stencil: None, multisample: MultisampleState::default(), multiview: None, + cache: None, }); pipeline } diff --git a/examples/triangle/main.rs b/examples/triangle/main.rs index beb2d84..4341bc2 100644 --- a/examples/triangle/main.rs +++ b/examples/triangle/main.rs @@ -5,17 +5,15 @@ use wgpu::{ CommandBuffer, MultisampleState, PipelineLayoutDescriptor, PrimitiveState, RenderPipeline, RenderPipelineDescriptor, ShaderModuleDescriptor, StoreOp, TextureFormat, }; -use winit::event_loop::EventLoop; +use winit::event_loop::ActiveEventLoop; const WIDTH: u32 = 1920; const HEIGHT: u32 = 1080; fn main() -> Result<(), GlassError> { - Glass::new( - TriangleApp::default(), - GlassConfig::performance(WIDTH, HEIGHT), - ) - .run() + Glass::run(GlassConfig::performance(WIDTH, HEIGHT), |_| { + Box::new(TriangleApp::default()) + }) } #[derive(Default)] @@ -24,7 +22,7 @@ struct TriangleApp { } impl GlassApp for TriangleApp { - fn start(&mut self, _event_loop: &EventLoop<()>, context: &mut GlassContext) { + fn start(&mut self, _event_loop: &ActiveEventLoop, context: &mut GlassContext) { self.triangle_pipeline = Some(create_triangle_pipeline(context)); } @@ -97,6 +95,7 @@ fn create_triangle_pipeline(context: &GlassContext) -> RenderPipeline { depth_stencil: None, multisample: MultisampleState::default(), multiview: None, + cache: None, }); pipeline } diff --git a/run_all_examples.ps1 b/run_all_examples.ps1 index 82fee33..df5580f 100644 --- a/run_all_examples.ps1 +++ b/run_all_examples.ps1 @@ -6,4 +6,3 @@ cargo run --example multiple_windows cargo run --example game_of_life cargo run --example lines cargo run --example egui_gui --features "egui_gui egui_demo" -cargo run --example fluid_sim --features "egui_gui" diff --git a/run_all_examples.sh b/run_all_examples.sh index 82fee33..df5580f 100755 --- a/run_all_examples.sh +++ b/run_all_examples.sh @@ -6,4 +6,3 @@ cargo run --example multiple_windows cargo run --example game_of_life cargo run --example lines cargo run --example egui_gui --features "egui_gui egui_demo" -cargo run --example fluid_sim --features "egui_gui" diff --git a/src/device_context.rs b/src/device_context.rs index d5d1910..359ba21 100644 --- a/src/device_context.rs +++ b/src/device_context.rs @@ -1,30 +1,33 @@ -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use wgpu::{ Adapter, Backends, Device, DeviceDescriptor, Instance, InstanceDescriptor, InstanceFlags, - Limits, PowerPreference, Queue, RequestAdapterOptions, Surface, + Limits, MemoryHints, PowerPreference, Queue, RequestAdapterOptions, Surface, }; -use winit::window::Window; -use crate::{utils::wait_async, window::WindowConfig, GlassError}; +use crate::{utils::wait_async, GlassError}; #[derive(Debug, Clone)] pub struct DeviceConfig { pub power_preference: PowerPreference, + pub memory_hints: MemoryHints, pub features: wgpu::Features, pub limits: Limits, pub backends: Backends, pub instance_flags: InstanceFlags, + pub trace_path: Option, } impl DeviceConfig { pub fn performance() -> DeviceConfig { DeviceConfig { power_preference: PowerPreference::HighPerformance, + memory_hints: MemoryHints::Performance, features: wgpu::Features::empty(), limits: Limits::default(), backends: Backends::all(), instance_flags: InstanceFlags::from_build_config(), + trace_path: None, } } } @@ -33,10 +36,12 @@ impl Default for DeviceConfig { fn default() -> Self { DeviceConfig { power_preference: PowerPreference::default(), + memory_hints: MemoryHints::Performance, features: wgpu::Features::empty(), limits: Limits::default(), backends: Backends::all(), instance_flags: InstanceFlags::from_build_config(), + trace_path: None, } } } @@ -55,32 +60,17 @@ unsafe impl Send for DeviceContext {} unsafe impl Sync for DeviceContext {} impl DeviceContext { - pub fn new( - config: &DeviceConfig, - initial_windows: &[(WindowConfig, Arc)], - ) -> Result { + pub fn new(config: &DeviceConfig) -> Result { let instance = Instance::new(InstanceDescriptor { backends: config.backends, flags: config.instance_flags, ..Default::default() }); - // Ensure render context is compatible with our window... - let surface_maybe = if let Some((_c, w)) = initial_windows.first() { - Some(match instance.create_surface(w.clone()) { - Ok(s) => s, - Err(e) => return Err(GlassError::SurfaceError(e)), - }) - } else { - None - }; - let (adapter, device, queue) = match Self::create_adapter_device_and_queue( - config, - &instance, - surface_maybe.as_ref(), - ) { - Ok(adq) => adq, - Err(e) => return Err(e), - }; + let (adapter, device, queue) = + match Self::create_adapter_device_and_queue(config, &instance, None) { + Ok(adq) => adq, + Err(e) => return Err(e), + }; Ok(Self { config: config.clone(), instance, @@ -121,16 +111,16 @@ impl DeviceContext { None => return Err(GlassError::AdapterError), }; - let trace_env = std::env::var("WGPU_TRACE").ok(); - let path = trace_env.as_ref().map(std::path::Path::new); + let path = config.trace_path.as_deref(); // Create the logical device and command queue let (device, queue) = match wait_async(adapter.request_device( &DeviceDescriptor { label: None, required_features: config.features, required_limits: config.limits.clone(), + memory_hints: config.memory_hints.clone(), }, - if cfg!(feature = "trace") { path } else { None }, + path, )) { Ok(dq) => dq, Err(e) => return Err(GlassError::DeviceError(e)), diff --git a/src/glass.rs b/src/glass.rs index 039c171..02d7339 100644 --- a/src/glass.rs +++ b/src/glass.rs @@ -7,9 +7,10 @@ use wgpu::{ SurfaceConfiguration, }; use winit::{ + application::ApplicationHandler, error::{EventLoopError, OsError}, - event::{ElementState, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, + event::{DeviceEvent, DeviceId, ElementState, StartCause, WindowEvent}, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, keyboard::{Key, NamedKey}, window::{Fullscreen, Window, WindowId}, }; @@ -27,203 +28,257 @@ use crate::{ /// which render using wgpu. Just impl [`GlassApp`] for your application (of any type) and you /// are good to go. pub struct Glass { - app: Box, config: GlassConfig, + app: Box, + context: GlassContext, + runner_state: RunnerState, } impl Glass { - pub fn new(app: impl GlassApp + 'static, config: GlassConfig) -> Glass { - Glass { - app: Box::new(app), - config, - } - } - - pub fn run(self) -> Result<(), GlassError> { - let event_loop = match EventLoop::new() { - Ok(e) => e, - Err(e) => return Err(GlassError::EventLoopError(e)), - }; - let context = GlassContext::new(&event_loop, self.config.clone())?; - self.run_inner(event_loop, context) - } - - /// This is useful, if you want to avoid `Option` within your app for anything that depends - /// on `Device` context - pub fn new_and_run( + pub fn run( config: GlassConfig, - app_create_fn: impl FnOnce(&EventLoop<()>, &mut GlassContext) -> Box, + app_create_fn: impl FnOnce(&mut GlassContext) -> Box, ) -> Result<(), GlassError> { + let mut context = GlassContext::new(config.clone())?; + let app = app_create_fn(&mut context); + let mut glass = Glass { + app, + context, + config, + runner_state: RunnerState::default(), + }; let event_loop = match EventLoop::new() { Ok(e) => e, Err(e) => return Err(GlassError::EventLoopError(e)), }; - let mut context = GlassContext::new(&event_loop, config.clone())?; - Glass { - app: app_create_fn(&event_loop, &mut context), - config, - } - .run_inner(event_loop, context) + event_loop + .run_app(&mut glass) + .map_err(GlassError::EventLoopError) } +} - fn run_inner( - mut self, - event_loop: EventLoop<()>, - mut context: GlassContext, - ) -> Result<(), GlassError> { - self.app.start(&event_loop, &mut context); - let mut runner_state = RunnerState::default(); - // Run update at start - runner_state.run_update = true; - - match event_loop.run(move |event, event_loop| { +impl ApplicationHandler for Glass { + fn new_events(&mut self, event_loop: &ActiveEventLoop, _cause: StartCause) { + // Ensure we're poll + if event_loop.control_flow() != ControlFlow::Poll { event_loop.set_control_flow(ControlFlow::Poll); + } + + let Glass { + app, + context, + .. + } = self; + app.before_input(context, event_loop); + } - // Run input fn - if !event_loop.exiting() { - self.app.input(&mut context, event_loop, &event); + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let Glass { + app, + config, + context, + runner_state, + .. + } = self; + // Initial windows + if !runner_state.is_init { + // Create windows from initial configs + let mut winit_windows = vec![]; + for &window_config in config.window_configs.iter() { + winit_windows.push(( + window_config, + GlassContext::create_winit_window(event_loop, &window_config).unwrap(), + )) } + for (window_config, window) in winit_windows { + let id = context.add_window(window_config, window).unwrap(); + // Configure window surface with size + let window = context.windows.get_mut(&id).unwrap(); + window.configure_surface_with_size( + context.device_context.device(), + window.window().inner_size(), + ); + } + app.start(event_loop, context); + runner_state.is_init = true; + } + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ) { + let Glass { + app, + context, + runner_state, + .. + } = self; + app.window_input(context, event_loop, window_id, &event); - runner_state.run_update = false; + let mut is_extra_update = false; + if let Some(window) = context.windows.get_mut(&window_id) { match event { - Event::WindowEvent { - window_id, - event: window_event, + WindowEvent::Resized(physical_size) => { + // On windows, minimized app can have 0,0 size + if physical_size.width > 0 && physical_size.height > 0 { + window.configure_surface_with_size( + context.device_context.device(), + physical_size, + ); + is_extra_update = true; + } + } + WindowEvent::ScaleFactorChanged { .. } => { - if let Some(window) = context.windows.get_mut(&window_id) { - match window_event { - WindowEvent::Resized(physical_size) => { - // On windows, minimized app can have 0,0 size - if physical_size.width > 0 && physical_size.height > 0 { - window.configure_surface_with_size( - context.device_context.device(), - physical_size, - ); - runner_state.run_update = true; - } - } - WindowEvent::ScaleFactorChanged { - .. - } => { - let size = window.window().inner_size(); - window.configure_surface_with_size( - context.device_context.device(), - size, - ); - runner_state.run_update = true; - } - WindowEvent::KeyboardInput { - event, - is_synthetic, - .. - } => { - if event.logical_key == Key::Named(NamedKey::Escape) - && !is_synthetic - && window.exit_on_esc() - && window.is_focused() - && event.state == ElementState::Pressed - { - runner_state.request_window_close = true; - runner_state.remove_windows.push(window_id); - } - } - WindowEvent::Focused(has_focus) => { - window.set_focus(has_focus); - } - WindowEvent::CloseRequested => { - runner_state.request_window_close = true; - runner_state.remove_windows.push(window_id); - } - - _ => (), - } + let size = window.window().inner_size(); + window.configure_surface_with_size(context.device_context.device(), size); + is_extra_update = true; + } + WindowEvent::KeyboardInput { + event, + is_synthetic, + .. + } => { + if event.logical_key == Key::Named(NamedKey::Escape) + && !is_synthetic + && window.exit_on_esc() + && window.is_focused() + && event.state == ElementState::Pressed + { + runner_state.request_window_close = true; + runner_state.remove_windows.push(window_id); } } - Event::AboutToWait => { - runner_state.run_update = true; + WindowEvent::Focused(has_focus) => { + window.set_focus(has_focus); } - Event::LoopExiting => { - // Run end - self.app.end(&mut context); + WindowEvent::CloseRequested => { + runner_state.request_window_close = true; + runner_state.remove_windows.push(window_id); } - _ => {} - } - if !event_loop.exiting() && runner_state.run_update { - self.run_update(event_loop, &mut context, &mut runner_state); + _ => (), } - }) { - Err(e) => Err(GlassError::EventLoopError(e)), - Ok(a) => Ok(a), + } + // Update immediately, because about_to_wait isn't triggered during resize. If it did, + // this would not be needed. + + // Winit recommends running rendering inside `RequestRedraw`, but that doesn't really + // seem good to me, because I want render to take place immediately after update, and + // running entire app's update within one window's `RequestRedraw` doesn't make sense + // to me. + + // This ensures resizing's effect is instant. Kinda ugly on performance, but that doesn't + // matter, because resize is a rare event. + if is_extra_update { + run_update(event_loop, app, context, runner_state); } } - fn run_update( + fn device_event( &mut self, - event_loop: &EventLoopWindowTarget<()>, - context: &mut GlassContext, - runner_state: &mut RunnerState, + event_loop: &ActiveEventLoop, + device_id: DeviceId, + event: DeviceEvent, ) { - self.app.update(context); + let Glass { + app, + context, + .. + } = self; + app.device_input(context, event_loop, device_id, &event); + } - if runner_state.request_window_close || context.exit { - for window in runner_state.remove_windows.iter() { - context.windows.swap_remove(window); - } - runner_state.remove_windows.clear(); - runner_state.request_window_close = false; - // Exit - if context.windows.is_empty() || context.exit { - event_loop.exit(); - } - } + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + let Glass { + app, + context, + runner_state, + .. + } = self; + run_update(event_loop, app, context, runner_state); + } - self.render(context); + fn exiting(&mut self, _event_loop: &ActiveEventLoop) { + let Glass { + app, + context, + .. + } = self; + app.end(context); + } +} - self.app.end_of_frame(context); +fn run_update( + event_loop: &ActiveEventLoop, + app: &mut Box, + context: &mut GlassContext, + runner_state: &mut RunnerState, +) { + if context.exit { + context.windows.clear(); + event_loop.exit(); + return; + } + if runner_state.request_window_close { + for window in runner_state.remove_windows.iter() { + context.windows.swap_remove(window); + } + runner_state.remove_windows.clear(); + runner_state.request_window_close = false; + // Exit + if context.windows.is_empty() { + context.exit(); + return; + } } + app.update(context); - fn render(&mut self, context: &mut GlassContext) { - for (_, window) in context.windows.iter() { - match window.surface().get_current_texture() { - Ok(frame) => { - let mut encoder = context.device_context.device().create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("Render Commands"), - }, - ); - - // Run render - let mut buffers = self - .app - .render(context, RenderData { - encoder: &mut encoder, - window, - frame: &frame, - }) - .unwrap_or_default(); - buffers.push(encoder.finish()); - context.device_context.queue().submit(buffers); - - frame.present(); - - self.app.after_render(context); - } - Err(error) => { - if error == wgpu::SurfaceError::OutOfMemory { - panic!("Swapchain error: {error}. Rendering cannot continue.") - } + render(app, context); + + app.end_of_frame(context); +} + +fn render(app: &mut Box, context: &mut GlassContext) { + for (_, window) in context.windows.iter() { + match window.surface().get_current_texture() { + Ok(frame) => { + let mut encoder = context.device_context.device().create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("Render Commands"), + }, + ); + + // Run render + let mut buffers = app + .render(context, RenderData { + encoder: &mut encoder, + window, + frame: &frame, + }) + .unwrap_or_default(); + buffers.push(encoder.finish()); + context.device_context.queue().submit(buffers); + + frame.present(); + } + Err(error) => { + if error == wgpu::SurfaceError::OutOfMemory { + panic!("Swapchain error: {error}. Rendering cannot continue.") } } - window.window().request_redraw(); } + window.window().request_redraw(); } } #[derive(Default)] struct RunnerState { - run_update: bool, + is_init: bool, request_window_close: bool, remove_windows: Vec, } @@ -302,15 +357,7 @@ pub struct GlassContext { } impl GlassContext { - pub fn new(event_loop: &EventLoop<()>, mut config: GlassConfig) -> Result { - // Create windows from initial configs - let mut winit_windows = vec![]; - for &window_config in config.window_configs.iter() { - winit_windows.push(( - window_config, - Self::create_winit_window(event_loop, &window_config)?, - )) - } + pub fn new(mut config: GlassConfig) -> Result { // Modify features & limits needed for common pipelines // Add push constants feature for common pipelines config.device_config.features |= wgpu::Features::PUSH_CONSTANTS @@ -318,26 +365,13 @@ impl GlassContext { config.device_config.limits = wgpu::Limits { ..config.device_config.limits }; - let device_context = DeviceContext::new( - &config.device_config, - // Needed to ensure our queue families are compatible with surface - &winit_windows, - )?; - let mut app = Self { + let device_context = DeviceContext::new(&config.device_config)?; + + Ok(Self { device_context, windows: IndexMap::default(), exit: false, - }; - for (window_config, window) in winit_windows { - let id = app.add_window(window_config, window)?; - // Configure window surface with size - let window = app.windows.get_mut(&id).unwrap(); - window.configure_surface_with_size( - app.device_context.device(), - window.window().inner_size(), - ); - } - Ok(app) + }) } #[allow(unused)] @@ -395,7 +429,7 @@ impl GlassContext { pub fn create_window( &mut self, - event_loop: &EventLoopWindowTarget<()>, + event_loop: &ActiveEventLoop, config: WindowConfig, ) -> Result { let reconfigure_device = self.windows.is_empty(); @@ -430,59 +464,59 @@ impl GlassContext { } fn create_winit_window( - event_loop: &EventLoopWindowTarget<()>, + event_loop: &ActiveEventLoop, config: &WindowConfig, ) -> Result, GlassError> { - let mut window_builder = winit::window::WindowBuilder::new() + let mut window_attributes = Window::default_attributes() .with_inner_size(winit::dpi::LogicalSize::new(config.width, config.height)) .with_title(config.title); // Min size if let Some(inner_size) = config.min_size { - window_builder = window_builder.with_min_inner_size(inner_size); + window_attributes = window_attributes.with_min_inner_size(inner_size); } // Max size if let Some(inner_size) = config.max_size { - window_builder = window_builder.with_max_inner_size(inner_size); + window_attributes = window_attributes.with_max_inner_size(inner_size); } - window_builder = match &config.pos { - WindowPos::Maximized => window_builder.with_maximized(true), + window_attributes = match &config.pos { + WindowPos::Maximized => window_attributes.with_maximized(true), WindowPos::FullScreen => { if let Some(monitor) = event_loop.primary_monitor() { - window_builder + window_attributes .with_fullscreen(Some(Fullscreen::Exclusive(get_best_videomode(&monitor)))) } else { - window_builder + window_attributes } } WindowPos::SizedFullScreen => { if let Some(monitor) = event_loop.primary_monitor() { - window_builder.with_fullscreen(Some(Fullscreen::Exclusive( + window_attributes.with_fullscreen(Some(Fullscreen::Exclusive( get_fitting_videomode(&monitor, config.width, config.height), ))) } else { - window_builder + window_attributes } } - WindowPos::FullScreenBorderless => window_builder + WindowPos::FullScreenBorderless => window_attributes .with_fullscreen(Some(Fullscreen::Borderless(event_loop.primary_monitor()))), - WindowPos::Pos(pos) => window_builder.with_position(*pos), + WindowPos::Pos(pos) => window_attributes.with_position(*pos), WindowPos::Centered => { if let Some(monitor) = event_loop.primary_monitor() { - window_builder.with_position(get_centered_window_position( + window_attributes.with_position(get_centered_window_position( &monitor, config.width, config.height, )) } else { - window_builder + window_attributes } } }; - match window_builder.build(event_loop) { + match event_loop.create_window(window_attributes) { Ok(w) => Ok(Arc::new(w)), Err(e) => Err(GlassError::WindowError(e)), } diff --git a/src/glass_app.rs b/src/glass_app.rs index df1c2f9..897c62d 100644 --- a/src/glass_app.rs +++ b/src/glass_app.rs @@ -1,7 +1,8 @@ use wgpu::{CommandBuffer, CommandEncoder, StoreOp, SurfaceTexture}; use winit::{ - event::Event, - event_loop::{EventLoop, EventLoopWindowTarget}, + event::{DeviceEvent, DeviceId, WindowEvent}, + event_loop::ActiveEventLoop, + window::WindowId, }; use crate::{window::GlassWindow, GlassContext}; @@ -20,16 +21,28 @@ pub struct RenderData<'a> { /// table of contents of your app flow. pub trait GlassApp { /// Run at start - fn start(&mut self, _event_loop: &EventLoop<()>, _context: &mut GlassContext) {} - /// Run on each event received from winit - fn input( + fn start(&mut self, _event_loop: &ActiveEventLoop, _context: &mut GlassContext) {} + /// Run on winit's `new_events` + fn before_input(&mut self, _context: &mut GlassContext, _event_loop: &ActiveEventLoop) {} + /// Run on each device event from winit + fn device_input( &mut self, _context: &mut GlassContext, - _event_loop: &EventLoopWindowTarget<()>, - _event: &Event<()>, + _event_loop: &ActiveEventLoop, + _device_id: DeviceId, + _event: &DeviceEvent, ) { } - /// Run each frame + /// Run on each window event from winit + fn window_input( + &mut self, + _context: &mut GlassContext, + _event_loop: &ActiveEventLoop, + _window_id: WindowId, + _event: &WindowEvent, + ) { + } + /// Run each frame, called within winit's `about_to_wait`. fn update(&mut self, _context: &mut GlassContext) {} /// Run each frame for each window after update fn render( @@ -64,9 +77,6 @@ pub trait GlassApp { None } - - /// Run each frame for each window after render - fn after_render(&mut self, _context: &GlassContext) {} /// Run each frame last fn end_of_frame(&mut self, _context: &mut GlassContext) {} /// Run at exit diff --git a/src/lib.rs b/src/lib.rs index 0249773..ee39061 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,6 @@ pub use egui; pub use egui_demo_lib; #[cfg(all(feature = "egui_extra", feature = "egui_gui"))] pub use egui_extras; -#[cfg(all(feature = "egui_extra", feature = "egui_gui"))] -pub use egui_plot; #[cfg(feature = "egui_gui")] pub use egui_wgpu; #[cfg(feature = "egui_gui")] diff --git a/src/pipelines/bloom/pipeline.rs b/src/pipelines/bloom/pipeline.rs index 94c221e..34ec0b9 100644 --- a/src/pipelines/bloom/pipeline.rs +++ b/src/pipelines/bloom/pipeline.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use bytemuck::{Pod, Zeroable}; -use glam::{UVec2, UVec4, Vec4}; use wgpu::{ util::DeviceExt, AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, @@ -142,6 +141,7 @@ impl BloomPipeline { depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, + cache: None, }); let downsample_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Bloom Downsample Pipeline"), @@ -166,6 +166,7 @@ impl BloomPipeline { depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, + cache: None, }); let color_blend = match bloom_settings.composite_mode { @@ -211,6 +212,7 @@ impl BloomPipeline { depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, + cache: None, }); let final_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { @@ -243,6 +245,7 @@ impl BloomPipeline { depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, + cache: None, }); let (downsampling_bind_groups, upsampling_bind_groups) = Self::create_bind_groups( @@ -334,16 +337,15 @@ impl BloomPipeline { device: &Device, encoder: &mut CommandEncoder, bloom_target: &Texture, - viewport_origin: UVec2, - viewport_size: UVec2, + viewport_origin: [u32; 2], + viewport_size: [u32; 2], ) { let size = bloom_target.size; - let push_constants = BloomPushConstants::new( - &self.settings, - viewport_origin, - viewport_size, - UVec2::new(size[0] as u32, size[1] as u32), - ); + let push_constants = + BloomPushConstants::new(&self.settings, viewport_origin, viewport_size, [ + size[0] as u32, + size[1] as u32, + ]); // First downsample pass (main image) let downsampling_first_bind_group = device.create_bind_group(&BindGroupDescriptor { label: Some("bloom_downsampling_first_bind_group"), @@ -486,10 +488,10 @@ impl BloomPipeline { ); upsampling_final_pass.set_vertex_buffer(0, self.vertices.slice(..)); upsampling_final_pass.set_viewport( - viewport_origin.x as f32, - viewport_origin.y as f32, - viewport_size.x as f32, - viewport_size.y as f32, + viewport_origin[0] as f32, + viewport_origin[1] as f32, + viewport_size[0] as f32, + viewport_size[1] as f32, 0.0, 1.0, ); @@ -522,37 +524,27 @@ pub struct BloomPushConstants { impl BloomPushConstants { pub fn new( settings: &BloomSettings, - viewport_origin: UVec2, - viewport_size: UVec2, - target_image_size: UVec2, + viewport_origin: [u32; 2], + viewport_size: [u32; 2], + target_image_size: [u32; 2], ) -> BloomPushConstants { let threshold = settings.prefilter_settings.threshold; let threshold_softness = settings.prefilter_settings.threshold_softness; let knee = threshold * threshold_softness.clamp(0.0, 1.0); BloomPushConstants { - threshold_precomputations: Vec4::new( + threshold_precomputations: [ threshold, threshold - knee, 2.0 * knee, 0.25 / (knee + 0.00001), - ) - .into(), - viewport: (UVec4::new( - viewport_origin.x, - viewport_origin.y, - viewport_size.x, - viewport_size.y, - ) - .as_vec4() - / UVec4::new( - target_image_size.x, - target_image_size.y, - target_image_size.x, - target_image_size.y, - ) - .as_vec4()) - .into(), - aspect: viewport_size.x as f32 / viewport_size.y as f32, + ], + viewport: [ + viewport_origin[0] as f32 / target_image_size[0] as f32, + viewport_origin[1] as f32 / target_image_size[1] as f32, + viewport_size[0] as f32 / target_image_size[0] as f32, + viewport_size[1] as f32 / target_image_size[1] as f32, + ], + aspect: viewport_size[0] as f32 / viewport_size[1] as f32, use_treshold: (threshold > 0.0) as u32, } } diff --git a/src/pipelines/line/pipeline.rs b/src/pipelines/line/pipeline.rs index d6648ed..1fd082e 100644 --- a/src/pipelines/line/pipeline.rs +++ b/src/pipelines/line/pipeline.rs @@ -1,7 +1,6 @@ use std::{borrow::Cow, ops::Range}; use bytemuck::{Pod, Zeroable}; -use glam::{Vec2, Vec3}; use wgpu::{ util::DeviceExt, Buffer, Device, PushConstantRange, RenderPass, RenderPipeline, ShaderStages, }; @@ -17,7 +16,7 @@ impl LinePipeline { pub fn new(device: &Device, color_target_state: wgpu::ColorTargetState) -> LinePipeline { let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&[ColoredVertex::new_2d(Vec2::ONE, [1.0; 4]); 2]), + contents: bytemuck::cast_slice(&[ColoredVertex::new_2d([1.0, 1.0], [1.0; 4]); 2]), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, }); let pipeline = Self::new_render_pipeline(device, color_target_state); @@ -74,6 +73,7 @@ impl LinePipeline { alpha_to_coverage_enabled: false, }, multiview: None, + cache: None, }); pipeline } @@ -121,8 +121,8 @@ impl LinePushConstants { pub fn new(view_proj: [[f32; 4]; 4], line: Line) -> LinePushConstants { LinePushConstants { view_proj, - start: line.start.extend(1.0).to_array(), - end: line.end.extend(1.0).to_array(), + start: [line.start[0], line.start[1], line.start[2], 1.0], + end: [line.end[0], line.end[1], line.end[2], 1.0], color: line.color, } } @@ -130,8 +130,8 @@ impl LinePushConstants { pub fn buffer(view_proj: [[f32; 4]; 4]) -> LinePushConstants { LinePushConstants { view_proj, - start: Vec3::ONE.extend(1.0).to_array(), - end: Vec3::ONE.extend(1.0).to_array(), + start: [1.0, 1.0, 1.0, 1.0], + end: [1.0, 1.0, 1.0, 1.0], color: [1.0; 4], } } @@ -139,13 +139,13 @@ impl LinePushConstants { #[derive(Default, Copy, Clone, Debug)] pub struct Line { - pub start: Vec3, - pub end: Vec3, + pub start: [f32; 3], + pub end: [f32; 3], pub color: [f32; 4], } impl Line { - pub fn new(start: Vec3, end: Vec3, color: [f32; 4]) -> Line { + pub fn new(start: [f32; 3], end: [f32; 3], color: [f32; 4]) -> Line { Line { start, end, diff --git a/src/pipelines/paste/pipeline.rs b/src/pipelines/paste/pipeline.rs index 5600bb9..8a8ba69 100644 --- a/src/pipelines/paste/pipeline.rs +++ b/src/pipelines/paste/pipeline.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use bytemuck::{Pod, Zeroable}; -use glam::Vec2; use wgpu::{ util::DeviceExt, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, Buffer, Color, ColorTargetState, @@ -108,6 +107,7 @@ impl PastePipeline { depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, + cache: None, }); PastePipeline { @@ -126,21 +126,21 @@ impl PastePipeline { input: &Texture, output: &Texture, tint: [f32; 4], - size: Vec2, - offset: Vec2, + size: [f32; 2], + offset: [f32; 2], flip_x: bool, flip_y: bool, ) { - let image_size = Vec2::new(size.x / output.size[0], size.y / output.size[1]); + let image_size = [size[0] / output.size[0], size[1] / output.size[1]]; let push_constants: PastePushConstants = PastePushConstants { tint, scale: [ - image_size.x * if flip_x { -1.0 } else { 1.0 }, - image_size.y * if flip_y { -1.0 } else { 1.0 }, + image_size[0] * if flip_x { -1.0 } else { 1.0 }, + image_size[1] * if flip_y { -1.0 } else { 1.0 }, ], offset: [ - (2.0 * offset.x - output.size[0]) / output.size[0], - -(2.0 * offset.y - output.size[1]) / output.size[1], + (2.0 * offset[0] - output.size[0]) / output.size[0], + -(2.0 * offset[1] - output.size[1]) / output.size[1], ], }; let bind_group = device.create_bind_group(&BindGroupDescriptor { diff --git a/src/pipelines/quad/pipeline.rs b/src/pipelines/quad/pipeline.rs index ea15d44..c6b7a4e 100644 --- a/src/pipelines/quad/pipeline.rs +++ b/src/pipelines/quad/pipeline.rs @@ -106,6 +106,7 @@ impl QuadPipeline { alpha_to_coverage_enabled: false, }, multiview: None, + cache: None, }); pipeline } diff --git a/src/pipelines/tonemapping/pipeline.rs b/src/pipelines/tonemapping/pipeline.rs index 490adfb..bec2139 100644 --- a/src/pipelines/tonemapping/pipeline.rs +++ b/src/pipelines/tonemapping/pipeline.rs @@ -87,6 +87,7 @@ impl TonemappingPipeline { depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, + cache: None, }); TonemappingPipeline { diff --git a/src/pipelines/vertex.rs b/src/pipelines/vertex.rs index 7ff0770..d732c71 100644 --- a/src/pipelines/vertex.rs +++ b/src/pipelines/vertex.rs @@ -1,5 +1,4 @@ use bytemuck::{Pod, Zeroable}; -use glam::Vec2; /// A vertex with texture coordinates #[repr(C)] @@ -12,9 +11,8 @@ pub struct TexturedVertex { impl TexturedVertex { pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - use std::mem; wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, + array_stride: size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { @@ -23,12 +21,12 @@ impl TexturedVertex { format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, + offset: size_of::<[f32; 4]>() as wgpu::BufferAddress, shader_location: 1, format: wgpu::VertexFormat::Float32x4, }, wgpu::VertexAttribute { - offset: 2 * mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, + offset: 2 * size_of::<[f32; 4]>() as wgpu::BufferAddress, shader_location: 2, format: wgpu::VertexFormat::Float32x2, }, @@ -74,9 +72,9 @@ pub struct ColoredVertex { } impl ColoredVertex { - pub fn new_2d(pos: Vec2, color: [f32; 4]) -> ColoredVertex { + pub fn new_2d(pos: [f32; 2], color: [f32; 4]) -> ColoredVertex { ColoredVertex { - position: [pos.x, pos.y, 0.0, 1.0], + position: [pos[0], pos[1], 0.0, 1.0], color, } } diff --git a/src/window.rs b/src/window.rs index f115abd..cf9e09f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use glam::IVec2; use wgpu::{ CompositeAlphaMode, CreateSurfaceError, Device, PresentMode, Surface, SurfaceConfiguration, TextureFormat, @@ -233,17 +232,17 @@ pub fn get_centered_window_position( window_height: u32, ) -> PhysicalPosition { let size = monitor.size(); - let center = IVec2::new(size.width as i32, size.height as i32) / 2; let window_size = PhysicalSize::new(window_width, window_height); - let left_top = center - IVec2::new(window_size.width as i32, window_size.height as i32) / 2; - PhysicalPosition::new(left_top.x, left_top.y) + let lt_x = size.width as i32 / 2 - window_size.width as i32 / 2; + let lt_y = size.height as i32 / 2 - window_size.height as i32 / 2; + PhysicalPosition::new(lt_x, lt_y) } pub fn get_fitting_videomode( monitor: &winit::monitor::MonitorHandle, width: u32, height: u32, -) -> winit::monitor::VideoMode { +) -> winit::monitor::VideoModeHandle { let mut modes = monitor.video_modes().collect::>(); fn abs_diff(a: u32, b: u32) -> u32 { @@ -271,7 +270,9 @@ pub fn get_fitting_videomode( modes.first().unwrap().clone() } -pub fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::monitor::VideoMode { +pub fn get_best_videomode( + monitor: &winit::monitor::MonitorHandle, +) -> winit::monitor::VideoModeHandle { let mut modes = monitor.video_modes().collect::>(); modes.sort_by(|a, b| { use std::cmp::Ordering::*;