Consolidate Renderer state and support multiple render passes

This commit is contained in:
Jay Oster 2019-10-04 00:58:12 -07:00
parent ac400f6122
commit 26860496a8
2 changed files with 91 additions and 23 deletions

View file

@ -11,6 +11,7 @@
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt; use std::fmt;
use std::rc::Rc;
use vk_shader_macros::include_glsl; use vk_shader_macros::include_glsl;
pub use wgpu; pub use wgpu;
@ -31,13 +32,13 @@ pub struct SurfaceTexture<'a> {
/// See [`PixelsBuilder`] for building a customized pixel buffer. /// See [`PixelsBuilder`] for building a customized pixel buffer.
#[derive(Debug)] #[derive(Debug)]
pub struct Pixels { pub struct Pixels {
device: wgpu::Device, // WGPU state
device: Rc<wgpu::Device>,
queue: wgpu::Queue, queue: wgpu::Queue,
renderer: Renderer,
swap_chain: wgpu::SwapChain, swap_chain: wgpu::SwapChain,
texture_extent: wgpu::Extent3d,
texture: wgpu::Texture, // List of render passes
texture_format_size: u32, renderers: Vec<Box<dyn RenderPass>>,
} }
/// A builder to help create customized pixel buffers. /// A builder to help create customized pixel buffers.
@ -50,13 +51,18 @@ 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 implements RenderPass. /// Renderer implements RenderPass.
#[derive(Debug)] #[derive(Debug)]
struct Renderer { struct Renderer {
device: Rc<wgpu::Device>,
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
render_pipeline: wgpu::RenderPipeline, 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. /// 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`]. /// Draw this pixel buffer to the configured [`SurfaceTexture`].
/// ///
/// This executes all render passes in sequence. See [`RenderPass`].
///
/// # Arguments /// # Arguments
/// ///
/// * `texels` - Byte slice of texture pixels (AKA texels) to draw. The texture format can be /// * `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]) { pub fn render(&mut self, texels: &[u8]) {
// TODO: Center frame buffer in surface // TODO: Center frame buffer in surface
let frame = self.swap_chain.get_next_texture(); let frame = self.swap_chain.get_next_texture();
@ -150,6 +158,31 @@ impl Pixels {
.device .device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); .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<wgpu::Device> {
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 // Update the pixel buffer texture view
let buffer = self let buffer = self
.device .device
@ -175,17 +208,7 @@ impl Pixels {
self.texture_extent, self.texture_extent,
); );
// TODO: Run all render passes in a loop // Draw the updated texture to the render target
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) {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: render_target, attachment: render_target,
@ -200,6 +223,10 @@ impl RenderPass for Renderer {
rpass.set_bind_group(0, &self.bind_group, &[]); rpass.set_bind_group(0, &self.bind_group, &[]);
rpass.draw(0..6, 0..1); rpass.draw(0..6, 0..1);
} }
fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
} }
impl<'a> PixelsBuilder<'a> { impl<'a> PixelsBuilder<'a> {
@ -234,6 +261,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(),
} }
} }
@ -276,17 +304,29 @@ impl<'a> PixelsBuilder<'a> {
self self
} }
/// Add a render pass.
///
/// Render passes are executed in the order they are added.
pub fn add_render_pass<R>(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. /// Create a pixel buffer from the options builder.
/// ///
/// # 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(self) -> Result<Pixels, Error> { pub fn build(mut 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 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"));
@ -408,18 +448,22 @@ impl<'a> PixelsBuilder<'a> {
// Create a renderer that impls `RenderPass` // Create a renderer that impls `RenderPass`
let renderer = Renderer { let renderer = Renderer {
device: device.clone(),
bind_group, bind_group,
render_pipeline, 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 { Ok(Pixels {
device, device,
queue, queue,
renderer,
swap_chain, swap_chain,
texture_extent, renderers: self.renderers,
texture,
texture_format_size,
}) })
} }
} }

View file

@ -1,3 +1,4 @@
use std::fmt;
use wgpu::TextureView; use wgpu::TextureView;
/// Objects that implement this trait can be added to [`Pixels`] as a render pass. /// 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 /// 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`. /// 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)
}
} }