Refactor to generalize pipeline drawing sequence

This commit is contained in:
Keavon Chambers
2020-05-23 12:36:47 -07:00
parent 9a60ba54fe
commit a9859b4bb4
18 changed files with 628 additions and 337 deletions

View File

@@ -1,12 +1,10 @@
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 crate::color_palette::ColorPalette;
use crate::window_events;
use crate::pipeline::Pipeline;
use crate::texture::Texture;
use crate::resource_cache::ResourceCache;
use crate::draw_command::DrawCommand;
use crate::gui_node::GuiNode;
use winit::event::*;
use winit::event_loop::*;
use winit::window::Window;
@@ -20,12 +18,9 @@ pub struct Application {
pub swap_chain_descriptor: wgpu::SwapChainDescriptor,
pub swap_chain: wgpu::SwapChain,
pub shader_cache: ResourceCache<wgpu::ShaderModule>,
pub bind_group_cache: ResourceCache<wgpu::BindGroup>,
pub pipeline_cache: ResourceCache<Pipeline>,
pub texture_cache: ResourceCache<Texture>,
pub draw_command_queue: VecDeque<DrawCommand>,
pub gui_tree: GuiTree,
pub temp_color_toggle: bool,
pub gui_root: rctree::Node<GuiNode>,
}
impl Application {
@@ -67,17 +62,24 @@ impl Application {
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::<wgpu::ShaderModule>::new();
let bind_group_cache = ResourceCache::<wgpu::BindGroup>::new();
let pipeline_cache = ResourceCache::<Pipeline>::new();
let mut shader_cache = ResourceCache::<wgpu::ShaderModule>::new();
let mut pipeline_cache = ResourceCache::<Pipeline>::new();
let texture_cache = ResourceCache::<Texture>::new();
// Ordered list of draw commands to send to the GPU on the next frame render
let draw_command_queue = VecDeque::new();
// Temporary setup below, TODO: move to appropriate place in architecture
// Data structure maintaining the user interface
let gui_tree = GuiTree::new();
// Window uniform bind group layout
let window_binding_types = vec![wgpu::BindingType::UniformBuffer { dynamic: false }];
let window_bind_group_layout = Pipeline::build_bind_group_layout(&device, &window_binding_types);
// Data structure maintaining the user interface
// let extra_layouts = vec![&window_bind_group_layout];
let gui_rect_pipeline = Pipeline::new(&device, swap_chain_descriptor.format, vec![], &mut shader_cache, ("shaders/shader.vert", "shaders/shader.frag"));
pipeline_cache.set("gui_rect", gui_rect_pipeline);
let gui_root_data = GuiNode::new(swap_chain_descriptor.width, swap_chain_descriptor.height, ColorPalette::get_color_srgb(ColorPalette::Accent));
let gui_root = rctree::Node::new(gui_root_data);
Self {
surface,
adapter,
@@ -86,85 +88,12 @@ impl Application {
swap_chain_descriptor,
swap_chain,
shader_cache,
bind_group_cache,
pipeline_cache,
texture_cache,
draw_command_queue,
gui_tree,
temp_color_toggle: true,
gui_root,
}
}
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));
@@ -182,8 +111,8 @@ impl Application {
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),
// Called once every event is handled and the GUI structure is updated
Event::MainEventsCleared => self.update_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
@@ -196,14 +125,8 @@ impl Application {
}
}
// 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();
}
pub fn update_gui(&mut self, window: &Window) {
}
// Render the queue of pipeline draw commands over the current window
@@ -214,12 +137,21 @@ impl Application {
// 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;
// Build an array of draw commands
let gui_node = self.gui_root.borrow_mut();
let mut nodes = vec![gui_node]; // TODO: Generate the DrawCommands as a list by recursively traversing the gui node tree
let device = &mut self.device;
// let commands: Vec<DrawCommand> = nodes.map(|mut node| node.build_draw_command(dev)).collect();
let mut commands = Vec::<DrawCommand>::with_capacity(nodes.len());
let mut bind_groups = Vec::<Vec<wgpu::BindGroup>>::with_capacity(nodes.len());
for i in 0..nodes.len() {
let new_pipeline = self.pipeline_cache.get("gui_rect").unwrap();
commands.push(nodes[i].build_draw_command(device));
bind_groups.push(nodes[i].build_bind_groups(device, &mut self.queue, new_pipeline, &mut self.texture_cache));
}
// Recording of commands while in "rendering mode" that go into a command buffer
let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -229,34 +161,40 @@ impl Application {
resolve_target: None,
load_op: wgpu::LoadOp::Clear,
store_op: wgpu::StoreOp::Store,
clear_color: color,
clear_color: wgpu::Color::BLACK,
}
],
depth_stencil_attachment: None,
});
let mut current_pipeline = String::new();
// Prepare a variable to cache the pipeline name
let mut bound_pipeline = self.pipeline_cache.get("gui_rect").unwrap(); //nodes[0].get_pipeline(&self.pipeline_cache);
render_pass.set_pipeline(&bound_pipeline.render_pipeline);
// 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();
for i in 0..nodes.len() {
// let command = commands[i];
// If the previously set pipeline can't be reused, send the GPU the new pipeline to draw with
let new_pipeline = self.pipeline_cache.get("gui_rect").unwrap(); //node.get_pipeline(&self.pipeline_cache);
if bound_pipeline.render_pipeline != new_pipeline.render_pipeline {
render_pass.set_pipeline(&new_pipeline.render_pipeline);
bound_pipeline = new_pipeline;
}
// 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);
render_pass.set_vertex_buffer(0, &commands[i].vertex_buffer, 0, 0);
render_pass.set_index_buffer(&commands[i].index_buffer, 0, 0);
// let bind_groups = nodes[i].build_bind_groups(&self.device, &mut self.queue, new_pipeline, &mut self.texture_cache);
// 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, &[]);
for (index, bind_group) in bind_groups[i].iter().enumerate() {
render_pass.set_bind_group(index as u32, bind_group, &[]);
}
// Draw call
render_pass.draw_indexed(0..command.index_count, 0, 0..1);
});
render_pass.draw_indexed(0..commands[i].index_count, 0, 0..1);
};
// Done sending render pass commands so we can give up mutation rights to command_encoder
drop(render_pass);
@@ -264,9 +202,6 @@ impl Application {
// 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]);
}