Settle on an interface for PixelsBuilder::add_render_pass()

- Accepts a closure (factory function) which itself accepts references to the `wgpu` instances needed for rendering
- This solves a need for creating `RenderPass` objects with a lot of `Option` types and trying to update them dynamically after the fact
- In other words, this is an RAII implementation
- `RenderPass` objects are created during the `PixelsBuilder` build stage
- It should prevent patterns where half-created `RenderPass` objects are necessary
This commit is contained in:
Jay Oster 2019-10-04 20:52:32 -07:00
parent 26860496a8
commit c994b35a5c

View file

@ -9,6 +9,7 @@
//! your convenience. Use a windowing framework or context manager of your choice; //! your convenience. Use a windowing framework or context manager of your choice;
//! [`winit`](https://crates.io/crates/winit) is a good place to start. //! [`winit`](https://crates.io/crates/winit) is a good place to start.
use std::cell::RefCell;
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
@ -19,6 +20,12 @@ pub use wgpu;
mod render_pass; mod render_pass;
pub use render_pass::RenderPass; pub use render_pass::RenderPass;
// Type aliases for RenderPass
type RPObject = Box<dyn RenderPass>;
type RPDevice = Rc<wgpu::Device>;
type RPQueue = Rc<RefCell<wgpu::Queue>>;
type RPTexture = wgpu::TextureView;
/// A logical texture for a window surface. /// A logical texture for a window surface.
#[derive(Debug)] #[derive(Debug)]
pub struct SurfaceTexture<'a> { pub struct SurfaceTexture<'a> {
@ -34,15 +41,14 @@ pub struct SurfaceTexture<'a> {
pub struct Pixels { pub struct Pixels {
// WGPU state // WGPU state
device: Rc<wgpu::Device>, device: Rc<wgpu::Device>,
queue: wgpu::Queue, queue: Rc<RefCell<wgpu::Queue>>,
swap_chain: wgpu::SwapChain, swap_chain: wgpu::SwapChain,
// List of render passes // List of render passes
renderers: Vec<Box<dyn RenderPass>>, renderers: Vec<RPObject>,
} }
/// A builder to help create customized pixel buffers. /// A builder to help create customized pixel buffers.
#[derive(Debug)]
pub struct PixelsBuilder<'a> { pub struct PixelsBuilder<'a> {
request_adapter_options: wgpu::RequestAdapterOptions, request_adapter_options: wgpu::RequestAdapterOptions,
device_descriptor: wgpu::DeviceDescriptor, device_descriptor: wgpu::DeviceDescriptor,
@ -51,7 +57,7 @@ pub struct PixelsBuilder<'a> {
pixel_aspect_ratio: f64, pixel_aspect_ratio: f64,
surface_texture: SurfaceTexture<'a>, surface_texture: SurfaceTexture<'a>,
texture_format: wgpu::TextureFormat, texture_format: wgpu::TextureFormat,
renderers: Vec<Box<dyn RenderPass>>, renderer_factories: Vec<Box<dyn Fn(RPDevice, RPQueue, &RPTexture) -> RPObject>>,
} }
/// Renderer implements RenderPass. /// Renderer implements RenderPass.
@ -160,17 +166,11 @@ impl Pixels {
// Execute all render passes // Execute all render passes
for renderer in self.renderers.iter() { for renderer in self.renderers.iter() {
// TODO: Create a texture chain so that each pass receives the texture drawn by the previous
renderer.render_pass(&mut encoder, &frame.view, texels); renderer.render_pass(&mut encoder, &frame.view, texels);
} }
self.queue.submit(&[encoder.finish()]); self.queue.borrow_mut().submit(&[encoder.finish()]);
}
/// Get a reference to the [`wgpu::Device`].
///
/// This is used for creating a custom [`RenderPass`].
pub fn device(&self) -> Rc<wgpu::Device> {
self.device.clone()
} }
} }
@ -238,10 +238,24 @@ impl<'a> PixelsBuilder<'a> {
/// # use pixels::PixelsBuilder; /// # use pixels::PixelsBuilder;
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH); /// # let surface = wgpu::Surface::create(&pixels_mocks::RWH);
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &surface); /// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &surface);
/// struct MyRenderPass {
/// // ...
/// };
///
/// impl pixels::RenderPass for MyRenderPass {
/// // ...
/// # fn update_bindings(&mut self, _: &wgpu::TextureView) {}
/// # fn render_pass(&self, _: &mut wgpu::CommandEncoder, _: &wgpu::TextureView, _: &[u8]) {}
/// }
///
/// let fb = PixelsBuilder::new(256, 240, surface_texture) /// let fb = PixelsBuilder::new(256, 240, surface_texture)
/// .pixel_aspect_ratio(8.0 / 7.0) /// .pixel_aspect_ratio(8.0 / 7.0)
/// # // TODO: demonstrate adding a render pass here /// .add_render_pass(|device, queue, texture| {
/// # //.render_pass(...) /// // Create reources for MyRenderPass here
/// Box::new(MyRenderPass {
/// // ...
/// })
/// })
/// .build()?; /// .build()?;
/// # Ok::<(), pixels::Error>(()) /// # Ok::<(), pixels::Error>(())
/// ``` /// ```
@ -261,7 +275,7 @@ impl<'a> PixelsBuilder<'a> {
pixel_aspect_ratio: 1.0, pixel_aspect_ratio: 1.0,
surface_texture, surface_texture,
texture_format: wgpu::TextureFormat::Rgba8UnormSrgb, texture_format: wgpu::TextureFormat::Rgba8UnormSrgb,
renderers: Vec::new(), renderer_factories: Vec::new(),
} }
} }
@ -307,11 +321,18 @@ impl<'a> PixelsBuilder<'a> {
/// Add a render pass. /// Add a render pass.
/// ///
/// Render passes are executed in the order they are added. /// Render passes are executed in the order they are added.
pub fn add_render_pass<R>(mut self, render_pass: R) -> PixelsBuilder<'a> ///
where /// # Factory Arguments
R: RenderPass + 'static, ///
{ /// * `device` - A reference-counted [`wgpu::Device`] which allows you to create GPU resources.
self.renderers.push(Box::new(render_pass)); /// * `queue` - A reference-counted [`wgpu::Queue`] which can execute command buffers.
/// * `texture` - A [`wgpu::TextureView`] reference that is used as the texture input for the
/// render pass.
pub fn add_render_pass(
mut self,
factory: impl Fn(RPDevice, RPQueue, &RPTexture) -> RPObject + 'static,
) -> PixelsBuilder<'a> {
self.renderer_factories.push(Box::new(factory));
self self
} }
@ -320,13 +341,14 @@ impl<'a> PixelsBuilder<'a> {
/// # Errors /// # Errors
/// ///
/// Returns an error when a [`wgpu::Adapter`] cannot be found. /// Returns an error when a [`wgpu::Adapter`] cannot be found.
pub fn build(mut self) -> Result<Pixels, Error> { pub fn build(self) -> Result<Pixels, Error> {
// TODO: Use `options.pixel_aspect_ratio` to stretch the scaled texture // TODO: Use `options.pixel_aspect_ratio` to stretch the scaled texture
let adapter = let adapter =
wgpu::Adapter::request(&self.request_adapter_options).ok_or(Error::AdapterNotFound)?; wgpu::Adapter::request(&self.request_adapter_options).ok_or(Error::AdapterNotFound)?;
let (device, queue) = adapter.request_device(&self.device_descriptor); let (device, queue) = adapter.request_device(&self.device_descriptor);
let device = Rc::new(device); let device = Rc::new(device);
let queue = Rc::new(RefCell::new(queue));
let vs_module = device.create_shader_module(include_glsl!("shaders/shader.vert")); let vs_module = device.create_shader_module(include_glsl!("shaders/shader.vert"));
let fs_module = device.create_shader_module(include_glsl!("shaders/shader.frag")); let fs_module = device.create_shader_module(include_glsl!("shaders/shader.frag"));
@ -456,14 +478,19 @@ impl<'a> PixelsBuilder<'a> {
texture_format_size, texture_format_size,
}; };
// Add the default renderer to the head of the render passes list let mut renderers: Vec<Box<dyn RenderPass>> = vec![Box::new(renderer)];
self.renderers.insert(0, Box::new(renderer));
// Create all render passes
renderers.extend(self.renderer_factories.iter().map(|f| {
// TODO: Create a texture chain so that each pass recieves the texture drawn by the previous
f(device.clone(), queue.clone(), &texture_view)
}));
Ok(Pixels { Ok(Pixels {
device, device,
queue, queue,
swap_chain, swap_chain,
renderers: self.renderers, renderers,
}) })
} }
} }