From 26860496a88bb490def2967c814c4481b6fc2dfb Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Fri, 4 Oct 2019 00:58:12 -0700 Subject: [PATCH] Consolidate `Renderer` state and support multiple render passes --- src/lib.rs | 88 ++++++++++++++++++++++++++++++++++------------ src/render_pass.rs | 26 +++++++++++++- 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b7dd55e..ee3130a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ use std::error::Error as StdError; use std::fmt; +use std::rc::Rc; use vk_shader_macros::include_glsl; pub use wgpu; @@ -31,13 +32,13 @@ pub struct SurfaceTexture<'a> { /// See [`PixelsBuilder`] for building a customized pixel buffer. #[derive(Debug)] pub struct Pixels { - device: wgpu::Device, + // WGPU state + device: Rc, queue: wgpu::Queue, - renderer: Renderer, swap_chain: wgpu::SwapChain, - texture_extent: wgpu::Extent3d, - texture: wgpu::Texture, - texture_format_size: u32, + + // List of render passes + renderers: Vec>, } /// A builder to help create customized pixel buffers. @@ -50,13 +51,18 @@ pub struct PixelsBuilder<'a> { pixel_aspect_ratio: f64, surface_texture: SurfaceTexture<'a>, texture_format: wgpu::TextureFormat, + renderers: Vec>, } /// Renderer implements RenderPass. #[derive(Debug)] struct Renderer { + device: Rc, bind_group: wgpu::BindGroup, render_pipeline: wgpu::RenderPipeline, + texture: wgpu::Texture, + texture_extent: wgpu::Extent3d, + texture_format_size: u32, } /// All the ways in which creating a pixel buffer can fail. @@ -139,10 +145,12 @@ impl Pixels { /// Draw this pixel buffer to the configured [`SurfaceTexture`]. /// + /// This executes all render passes in sequence. See [`RenderPass`]. + /// /// # Arguments /// /// * `texels` - Byte slice of texture pixels (AKA texels) to draw. The texture format can be - /// configured with `PixelsBuilder`. + /// configured with [`PixelsBuilder`]. pub fn render(&mut self, texels: &[u8]) { // TODO: Center frame buffer in surface let frame = self.swap_chain.get_next_texture(); @@ -150,6 +158,31 @@ impl Pixels { .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + // Execute all render passes + for renderer in self.renderers.iter() { + renderer.render_pass(&mut encoder, &frame.view, texels); + } + + self.queue.submit(&[encoder.finish()]); + } + + /// Get a reference to the [`wgpu::Device`]. + /// + /// This is used for creating a custom [`RenderPass`]. + pub fn device(&self) -> Rc { + self.device.clone() + } +} + +impl RenderPass for Renderer { + fn update_bindings(&mut self, _texture_view: &wgpu::TextureView) {} + + fn render_pass( + &self, + encoder: &mut wgpu::CommandEncoder, + render_target: &wgpu::TextureView, + texels: &[u8], + ) { // Update the pixel buffer texture view let buffer = self .device @@ -175,17 +208,7 @@ impl Pixels { self.texture_extent, ); - // TODO: Run all render passes in a loop - self.renderer.render_pass(&mut encoder, &frame.view); - - self.queue.submit(&[encoder.finish()]); - } -} - -impl RenderPass for Renderer { - fn update_bindings(&mut self, _texture_view: &wgpu::TextureView) {} - - fn render_pass(&self, encoder: &mut wgpu::CommandEncoder, render_target: &wgpu::TextureView) { + // Draw the updated texture to the render target let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { attachment: render_target, @@ -200,6 +223,10 @@ impl RenderPass for Renderer { rpass.set_bind_group(0, &self.bind_group, &[]); rpass.draw(0..6, 0..1); } + + fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } } impl<'a> PixelsBuilder<'a> { @@ -234,6 +261,7 @@ impl<'a> PixelsBuilder<'a> { pixel_aspect_ratio: 1.0, surface_texture, texture_format: wgpu::TextureFormat::Rgba8UnormSrgb, + renderers: Vec::new(), } } @@ -276,17 +304,29 @@ impl<'a> PixelsBuilder<'a> { self } + /// Add a render pass. + /// + /// Render passes are executed in the order they are added. + pub fn add_render_pass(mut self, render_pass: R) -> PixelsBuilder<'a> + where + R: RenderPass + 'static, + { + self.renderers.push(Box::new(render_pass)); + self + } + /// Create a pixel buffer from the options builder. /// /// # Errors /// /// Returns an error when a [`wgpu::Adapter`] cannot be found. - pub fn build(self) -> Result { + pub fn build(mut self) -> Result { // TODO: Use `options.pixel_aspect_ratio` to stretch the scaled texture let adapter = wgpu::Adapter::request(&self.request_adapter_options).ok_or(Error::AdapterNotFound)?; let (device, queue) = adapter.request_device(&self.device_descriptor); + let device = Rc::new(device); let vs_module = device.create_shader_module(include_glsl!("shaders/shader.vert")); let fs_module = device.create_shader_module(include_glsl!("shaders/shader.frag")); @@ -408,18 +448,22 @@ impl<'a> PixelsBuilder<'a> { // Create a renderer that impls `RenderPass` let renderer = Renderer { + device: device.clone(), bind_group, render_pipeline, + texture, + texture_extent, + texture_format_size, }; + // Add the default renderer to the head of the render passes list + self.renderers.insert(0, Box::new(renderer)); + Ok(Pixels { device, queue, - renderer, swap_chain, - texture_extent, - texture, - texture_format_size, + renderers: self.renderers, }) } } diff --git a/src/render_pass.rs b/src/render_pass.rs index 9ab1e1d..b242253 100644 --- a/src/render_pass.rs +++ b/src/render_pass.rs @@ -1,3 +1,4 @@ +use std::fmt; use wgpu::TextureView; /// Objects that implement this trait can be added to [`Pixels`] as a render pass. @@ -30,5 +31,28 @@ pub trait RenderPass { /// Called when it is time to execute this render pass. Use the `encoder` to encode all /// commands related to this render pass. The result must be stored to the `render_target`. - fn render_pass(&self, encoder: &mut wgpu::CommandEncoder, render_target: &TextureView); + /// + /// # Arguments + /// * `encoder` - Command encoder for the render pass + /// * `render_target` - A reference to the output texture + /// * `texels` - The byte slice passed to `Pixels::render` + fn render_pass( + &self, + encoder: &mut wgpu::CommandEncoder, + render_target: &TextureView, + texels: &[u8], + ); + + /// This function implements [`Debug`](fmt::Debug) for trait objects. + /// + /// You are encouraged to override the default impl to provide better debug messages. + fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "dyn RenderPass") + } +} + +impl fmt::Debug for dyn RenderPass + 'static { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug(f) + } }