use super::color_palette::ColorPalette; use super::window_events; use super::pipeline::Pipeline; use super::texture::Texture; use super::shader_stage::compile_from_glsl; use super::resource_cache::ResourceCache; use super::draw_command::DrawCommand; use super::gui_tree::GuiTree; use std::collections::VecDeque; use winit::event::*; use winit::event_loop::*; use winit::window::Window; use futures::executor::block_on; pub struct Application { pub surface: wgpu::Surface, pub adapter: wgpu::Adapter, pub device: wgpu::Device, pub queue: wgpu::Queue, pub swap_chain_descriptor: wgpu::SwapChainDescriptor, pub swap_chain: wgpu::SwapChain, pub shader_cache: ResourceCache, pub bind_group_cache: ResourceCache, pub pipeline_cache: ResourceCache, pub texture_cache: ResourceCache, pub draw_command_queue: VecDeque, pub gui_tree: GuiTree, pub temp_color_toggle: bool, } impl Application { pub fn new(window: &Window) -> Self { // Window as understood by WGPU for rendering onto let surface = wgpu::Surface::create(window); // Represents a GPU, exposes the real GPU device and queue let adapter = block_on(wgpu::Adapter::request( &wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::Default, compatible_surface: Some(&surface), }, wgpu::BackendBit::PRIMARY, )).unwrap(); // Requests the device and queue from the adapter let requested_device = block_on(adapter.request_device(&wgpu::DeviceDescriptor { extensions: wgpu::Extensions { anisotropic_filtering: false }, limits: Default::default(), })); // Connection to the physical GPU let device = requested_device.0; // Represents the GPU command queue, to submit CommandBuffers let queue = requested_device.1; // Properties for the swap chain frame buffers let swap_chain_descriptor = wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, format: wgpu::TextureFormat::Bgra8UnormSrgb, width: window.inner_size().width, height: window.inner_size().height, present_mode: wgpu::PresentMode::Fifo, }; // Series of frame buffers with images presented to the surface let swap_chain = device.create_swap_chain(&surface, &swap_chain_descriptor); // Resource caches that own the application's shaders, pipelines, and textures let shader_cache = ResourceCache::::new(); let bind_group_cache = ResourceCache::::new(); let pipeline_cache = ResourceCache::::new(); let texture_cache = ResourceCache::::new(); // Ordered list of draw commands to send to the GPU on the next frame render let draw_command_queue = VecDeque::new(); // Data structure maintaining the user interface let gui_tree = GuiTree::new(); Self { surface, adapter, device, queue, swap_chain_descriptor, swap_chain, shader_cache, bind_group_cache, pipeline_cache, texture_cache, draw_command_queue, gui_tree, temp_color_toggle: true, } } pub fn example(&mut self) { // Example vertex data const VERTICES: &[[f32; 2]] = &[ [-0.5, 0.5], [0.5, 0.5], [0.5, 1.0], [-0.5, 1.0], ]; const INDICES: &[u16] = &[ 0, 1, 2, 0, 2, 3, ]; // If uncached, construct a vertex shader loaded from its source code file let vertex_shader_path = "shaders/shader.vert"; if self.shader_cache.get(vertex_shader_path).is_none() { let vertex_shader_module = compile_from_glsl(&self.device, vertex_shader_path, glsl_to_spirv::ShaderType::Vertex).unwrap(); self.shader_cache.set(vertex_shader_path, vertex_shader_module); } // If uncached, construct a fragment shader loaded from its source code file let fragment_shader_path = "shaders/shader.frag"; if self.shader_cache.get(fragment_shader_path).is_none() { let fragment_shader_module = compile_from_glsl(&self.device, fragment_shader_path, glsl_to_spirv::ShaderType::Fragment).unwrap(); self.shader_cache.set(fragment_shader_path, fragment_shader_module); } // Get the shader pair let vertex_shader = self.shader_cache.get(vertex_shader_path).unwrap(); let fragment_shader = self.shader_cache.get(fragment_shader_path).unwrap(); // If uncached, construct a pipeline from the shader pair let pipeline_name = "example-pipeline"; if self.pipeline_cache.get(pipeline_name).is_none() { let bind_group_layout_binding_types = vec![ wgpu::BindingType::SampledTexture { dimension: wgpu::TextureViewDimension::D2, component_type: wgpu::TextureComponentType::Float, multisampled: false, }, // ty: wgpu::BindingType::Sampler, ]; let pipeline = Pipeline::new(&self.device, vertex_shader, fragment_shader, bind_group_layout_binding_types); self.pipeline_cache.set(pipeline_name, pipeline); } let example_pipeline = self.pipeline_cache.get(pipeline_name).unwrap(); // If uncached, construct a texture loaded from the image file let texture_path = "textures/grid.png"; if self.texture_cache.get(texture_path).is_none() { let texture = Texture::from_filepath(&self.device, &mut self.queue, texture_path).unwrap(); self.texture_cache.set(texture_path, texture); } let grid_texture = self.texture_cache.get(texture_path).unwrap(); // If uncached, construct a bind group with resources matching the pipeline's bind group layout let bind_group_name = "example-bindgroup"; if self.bind_group_cache.get(bind_group_name).is_none() { let binding_resources = vec![ wgpu::BindingResource::TextureView(&grid_texture.texture_view), ]; let bind_group = example_pipeline.build_bind_group(&self.device, binding_resources); self.bind_group_cache.set(bind_group_name, bind_group); } // Create a draw command with the vertex data and bind group and push it to the GPU command queue let draw_command = DrawCommand::new(&self.device, pipeline_name, bind_group_name, VERTICES, INDICES); self.draw_command_queue.push_back(draw_command); } // Initializes the event loop for rendering and event handling pub fn begin_lifecycle(mut self, event_loop: EventLoop<()>, window: Window) { event_loop.run(move |event, _, control_flow| self.main_event_loop(event, control_flow, &window)); } // Called every time by the event loop pub fn main_event_loop(&mut self, event: Event<'_, T>, control_flow: &mut ControlFlow, window: &Window) { // Wait for the next event to cause a subsequent event loop run, instead of looping instantly as a game would need *control_flow = ControlFlow::Wait; match event { // Handle all window events (like input and resize) in sequence Event::WindowEvent { window_id, ref event } if window_id == window.id() => window_events::window_event(self, control_flow, event), // Handle raw hardware-related events not related to a window Event::DeviceEvent { .. } => (), // Handle custom-dispatched events Event::UserEvent(_) => (), // Once every event is handled and the GUI structure is updated, this requests a new sequence of draw commands Event::MainEventsCleared => self.redraw_gui(window), // Resizing or calling `window.request_redraw()` renders the GUI with the queued draw commands Event::RedrawRequested(_) => self.render(), // Once all windows have been redrawn Event::RedrawEventsCleared => (), Event::NewEvents(_) => (), Event::Suspended => (), Event::Resumed => (), Event::LoopDestroyed => (), _ => (), } } // Traverse dirty GUI elements and turn GUI changes into draw commands added to the render pipeline queue pub fn redraw_gui(&mut self, window: &Window) { self.example(); // If any draw commands were actually added, ask the window to dispatch a redraw event if !self.draw_command_queue.is_empty() { window.request_redraw(); } } // Render the queue of pipeline draw commands over the current window pub fn render(&mut self) { // Get a frame buffer to render on let frame = self.swap_chain.get_next_texture().expect("Timeout getting frame buffer texture"); // Generates a render pass that commands are applied to, then generates a command buffer when finished let mut command_encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") }); // Temporary way to swap clear color every render let color = match self.temp_color_toggle { true => ColorPalette::get_color_linear(ColorPalette::MildBlack), false => ColorPalette::get_color_linear(ColorPalette::NearBlack), }; self.temp_color_toggle = !self.temp_color_toggle; // Recording of commands while in "rendering mode" that go into a command buffer let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment: &frame.view, resolve_target: None, load_op: wgpu::LoadOp::Clear, store_op: wgpu::StoreOp::Store, clear_color: color, } ], depth_stencil_attachment: None, }); let mut current_pipeline = String::new(); // Turn the queue of pipelines each into a command buffer and submit it to the render queue self.draw_command_queue.iter().for_each(|command| { // Tell the GPU which pipeline to draw in this render pass if current_pipeline != command.pipeline_name { let pipeline = self.pipeline_cache.get(&command.pipeline_name).unwrap(); render_pass.set_pipeline(&pipeline.render_pipeline); current_pipeline = command.pipeline_name.clone(); } // Send the GPU the vertices and triangle indices render_pass.set_vertex_buffer(0, &command.vertex_buffer, 0, 0); render_pass.set_index_buffer(&command.index_buffer, 0, 0); // Send the GPU the bind group resources let bind_group = self.bind_group_cache.get(&command.bind_group_name).unwrap(); render_pass.set_bind_group(0, bind_group, &[]); // Draw call render_pass.draw_indexed(0..command.index_count, 0, 0..1); }); // Done sending render pass commands so we can give up mutation rights to command_encoder drop(render_pass); // Turn the recording of commands into a complete command buffer let command_buffer = command_encoder.finish(); // After the draw command queue has been iterated through and used, empty it for use next frame self.draw_command_queue.clear(); // Submit the command buffer to the GPU command queue self.queue.submit(&[command_buffer]); } }