Add RenderPass
trait and refactor PixelsBuilder
This commit is contained in:
parent
2ec43fccd1
commit
70306c5cfd
260
src/lib.rs
260
src/lib.rs
|
@ -1,8 +1,8 @@
|
||||||
//! A tiny library providing a GPU-powered pixel frame buffer.
|
//! A tiny library providing a GPU-powered pixel pixel buffer.
|
||||||
//!
|
//!
|
||||||
//! `Pixels` represents a 2D frame buffer with an explicit image resolution,
|
//! [`Pixels`] represents a 2D pixel buffer with an explicit image resolution,
|
||||||
//! making it ideal for prototyping simple pixel-based games, animations, and
|
//! making it ideal for prototyping simple pixel-based games, animations, and
|
||||||
//! emulators. The frame buffer is rendered entirely on the GPU, allowing
|
//! emulators. The pixel buffer is rendered entirely on the GPU, allowing
|
||||||
//! developers to easily incorporate special effects with shaders and a
|
//! developers to easily incorporate special effects with shaders and a
|
||||||
//! customizable pipeline.
|
//! customizable pipeline.
|
||||||
//!
|
//!
|
||||||
|
@ -17,6 +17,9 @@ use std::fmt;
|
||||||
use vk_shader_macros::include_glsl;
|
use vk_shader_macros::include_glsl;
|
||||||
pub use wgpu;
|
pub use wgpu;
|
||||||
|
|
||||||
|
mod render_pass;
|
||||||
|
pub use render_pass::RenderPass;
|
||||||
|
|
||||||
/// 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> {
|
||||||
|
@ -25,29 +28,39 @@ pub struct SurfaceTexture<'a> {
|
||||||
height: u32,
|
height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a 2D frame buffer with an explicit image resolution.
|
/// Represents a 2D pixel buffer with an explicit image resolution.
|
||||||
|
///
|
||||||
|
/// See [`PixelsBuilder`] for building a customized pixel buffer.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pixels {
|
pub struct Pixels {
|
||||||
bind_group: wgpu::BindGroup,
|
|
||||||
device: wgpu::Device,
|
device: wgpu::Device,
|
||||||
queue: wgpu::Queue,
|
queue: wgpu::Queue,
|
||||||
render_pipeline: wgpu::RenderPipeline,
|
renderer: Renderer,
|
||||||
swap_chain: wgpu::SwapChain,
|
swap_chain: wgpu::SwapChain,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder to help create customized frame buffers.
|
/// A builder to help create customized pixel buffers.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PixelsOptions {
|
pub struct PixelsBuilder<'a> {
|
||||||
request_adapter_options: wgpu::RequestAdapterOptions,
|
request_adapter_options: wgpu::RequestAdapterOptions,
|
||||||
device_descriptor: wgpu::DeviceDescriptor,
|
device_descriptor: wgpu::DeviceDescriptor,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
pixel_aspect_ratio: f64,
|
||||||
|
surface_texture: SurfaceTexture<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All the ways in which creating a frame buffer can fail.
|
/// Renderer implements RenderPass.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Renderer {
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
render_pipeline: wgpu::RenderPipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All the ways in which creating a pixel buffer can fail.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// No suitable Adapter found
|
/// No suitable [`wgpu::Adapter`] found
|
||||||
AdapterNotFound,
|
AdapterNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +106,9 @@ impl<'a> SurfaceTexture<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pixels {
|
||||||
|
/// Create a pixel buffer instance with default options.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
@ -102,35 +118,134 @@ impl<'a> SurfaceTexture<'a> {
|
||||||
/// let fb = Pixels::new(320, 240, surface_texture)?;
|
/// let fb = Pixels::new(320, 240, surface_texture)?;
|
||||||
/// # Ok::<(), pixels::Error>(())
|
/// # Ok::<(), pixels::Error>(())
|
||||||
/// ```
|
/// ```
|
||||||
impl Pixels {
|
|
||||||
/// Create a frame buffer instance with default options.
|
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error when a `wgpu::Adapter` cannot be found.
|
/// Returns an error when a [`wgpu::Adapter`] cannot be found.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics when `width` or `height` are 0.
|
/// Panics when `width` or `height` are 0.
|
||||||
pub fn new(width: u32, height: u32, surface_texture: SurfaceTexture) -> Result<Pixels, Error> {
|
pub fn new<'a>(
|
||||||
Pixels::new_with_options(surface_texture, PixelsOptions::new(width, height))
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
surface_texture: SurfaceTexture<'a>,
|
||||||
|
) -> Result<Pixels, Error> {
|
||||||
|
PixelsBuilder::new(width, height, surface_texture).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a frame buffer instance with the given options.
|
// TODO: Support resize
|
||||||
|
|
||||||
|
/// Draw this pixel buffer to the configured [`SurfaceTexture`].
|
||||||
|
pub fn render(&mut self) {
|
||||||
|
// TODO: Center frame buffer in surface
|
||||||
|
let frame = self.swap_chain.get_next_texture();
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 });
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||||
|
attachment: render_target,
|
||||||
|
resolve_target: None,
|
||||||
|
load_op: wgpu::LoadOp::Clear,
|
||||||
|
store_op: wgpu::StoreOp::Store,
|
||||||
|
clear_color: wgpu::Color::GREEN,
|
||||||
|
}],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
rpass.set_pipeline(&self.render_pipeline);
|
||||||
|
rpass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
rpass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PixelsBuilder<'a> {
|
||||||
|
/// Create a builder that can be finalized into a [`Pixels`] pixel buffer.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use pixels::PixelsBuilder;
|
||||||
|
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH);
|
||||||
|
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &surface);
|
||||||
|
/// let fb = PixelsBuilder::new(256, 240, surface_texture)
|
||||||
|
/// .pixel_aspect_ratio(8.0 / 7.0)
|
||||||
|
/// # // TODO: demonstrate adding a render pass here
|
||||||
|
/// # //.render_pass(...)
|
||||||
|
/// .build()?;
|
||||||
|
/// # Ok::<(), pixels::Error>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics when `width` or `height` are 0.
|
||||||
|
pub fn new(width: u32, height: u32, surface_texture: SurfaceTexture<'a>) -> PixelsBuilder<'a> {
|
||||||
|
assert!(width > 0);
|
||||||
|
assert!(height > 0);
|
||||||
|
|
||||||
|
PixelsBuilder {
|
||||||
|
request_adapter_options: wgpu::RequestAdapterOptions::default(),
|
||||||
|
device_descriptor: wgpu::DeviceDescriptor::default(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pixel_aspect_ratio: 1.0,
|
||||||
|
surface_texture,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add options for requesting a [`wgpu::Adapter`].
|
||||||
|
pub fn request_adapter_options(
|
||||||
|
mut self,
|
||||||
|
request_adapter_options: wgpu::RequestAdapterOptions,
|
||||||
|
) -> PixelsBuilder<'a> {
|
||||||
|
self.request_adapter_options = request_adapter_options;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add options for requesting a [`wgpu::Device`].
|
||||||
|
pub fn device_descriptor(
|
||||||
|
mut self,
|
||||||
|
device_descriptor: wgpu::DeviceDescriptor,
|
||||||
|
) -> PixelsBuilder<'a> {
|
||||||
|
self.device_descriptor = device_descriptor;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the pixel aspect ratio to simulate non-square pixels.
|
||||||
|
///
|
||||||
|
/// This setting enables a render pass that horizontally scales the pixel
|
||||||
|
/// buffer by the given factor.
|
||||||
|
///
|
||||||
|
/// E.g. set this to `8.0 / 7.0` for an 8:7 pixel aspect ratio.
|
||||||
|
pub fn pixel_aspect_ratio(mut self, pixel_aspect_ratio: f64) -> PixelsBuilder<'a> {
|
||||||
|
self.pixel_aspect_ratio = pixel_aspect_ratio;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a pixel buffer from the options builder.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error when a `wgpu::Adapter` cannot be found or shaders
|
/// Returns an error when a [`wgpu::Adapter`] cannot be found.
|
||||||
/// are invalid SPIR-V.
|
pub fn build(self) -> Result<Pixels, Error> {
|
||||||
pub fn new_with_options(
|
|
||||||
surface_texture: SurfaceTexture,
|
|
||||||
options: PixelsOptions,
|
|
||||||
) -> Result<Pixels, Error> {
|
|
||||||
// TODO: Create a texture with the dimensions specified in `options`
|
// TODO: Create a texture with the dimensions specified in `options`
|
||||||
|
// TODO: Use `options.pixel_aspect_ratio` to stretch the scaled texture
|
||||||
|
|
||||||
let adapter = wgpu::Adapter::request(&options.request_adapter_options)
|
let adapter =
|
||||||
.ok_or(Error::AdapterNotFound)?;
|
wgpu::Adapter::request(&self.request_adapter_options).ok_or(Error::AdapterNotFound)?;
|
||||||
let (device, queue) = adapter.request_device(&options.device_descriptor);
|
let (device, queue) = adapter.request_device(&self.device_descriptor);
|
||||||
|
|
||||||
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"));
|
||||||
|
@ -179,105 +294,28 @@ impl Pixels {
|
||||||
});
|
});
|
||||||
|
|
||||||
let swap_chain = device.create_swap_chain(
|
let swap_chain = device.create_swap_chain(
|
||||||
surface_texture.surface,
|
self.surface_texture.surface,
|
||||||
&wgpu::SwapChainDescriptor {
|
&wgpu::SwapChainDescriptor {
|
||||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||||
width: surface_texture.width,
|
width: self.surface_texture.width,
|
||||||
height: surface_texture.height,
|
height: self.surface_texture.height,
|
||||||
present_mode: wgpu::PresentMode::Vsync,
|
present_mode: wgpu::PresentMode::Vsync,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Pixels {
|
let renderer = Renderer {
|
||||||
bind_group,
|
bind_group,
|
||||||
|
render_pipeline,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Pixels {
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
render_pipeline,
|
renderer,
|
||||||
swap_chain,
|
swap_chain,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support resize
|
|
||||||
|
|
||||||
/// Draw this frame buffer to the configured `wgpu::Surface`.
|
|
||||||
pub fn render(&mut self) {
|
|
||||||
// TODO: Center frame buffer in surface
|
|
||||||
let frame = self.swap_chain.get_next_texture();
|
|
||||||
let mut encoder = self
|
|
||||||
.device
|
|
||||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 });
|
|
||||||
{
|
|
||||||
let mut rpass = 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: wgpu::Color::GREEN,
|
|
||||||
}],
|
|
||||||
depth_stencil_attachment: None,
|
|
||||||
});
|
|
||||||
rpass.set_pipeline(&self.render_pipeline);
|
|
||||||
rpass.set_bind_group(0, &self.bind_group, &[]);
|
|
||||||
rpass.draw(0..3, 0..1);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.queue.submit(&[encoder.finish()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// # use pixels::PixelsOptions;
|
|
||||||
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH);
|
|
||||||
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, &surface);
|
|
||||||
/// let fb = PixelsOptions::new(320, 240)
|
|
||||||
/// # // TODO: demonstrate adding a render pass here
|
|
||||||
/// # //.render_pass(...)
|
|
||||||
/// .build(surface_texture)?;
|
|
||||||
/// # Ok::<(), pixels::Error>(())
|
|
||||||
/// ```
|
|
||||||
impl PixelsOptions {
|
|
||||||
/// Create a builder that can be finalized into a frame buffer instance.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics when `width` or `height` are 0.
|
|
||||||
pub fn new(width: u32, height: u32) -> PixelsOptions {
|
|
||||||
assert!(width > 0);
|
|
||||||
assert!(height > 0);
|
|
||||||
|
|
||||||
PixelsOptions {
|
|
||||||
request_adapter_options: wgpu::RequestAdapterOptions::default(),
|
|
||||||
device_descriptor: wgpu::DeviceDescriptor::default(),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add options for requesting a `wgpu::Adapter`.
|
|
||||||
pub fn request_adapter_options(mut self, rao: wgpu::RequestAdapterOptions) -> PixelsOptions {
|
|
||||||
self.request_adapter_options = rao;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add options for requesting a `wgpu::Device`.
|
|
||||||
pub fn device_descriptor(mut self, dd: wgpu::DeviceDescriptor) -> PixelsOptions {
|
|
||||||
self.device_descriptor = dd;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a frame buffer from the options builder.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns an error when a `wgpu::Adapter` cannot be found or shaders
|
|
||||||
/// are invalid SPIR-V.
|
|
||||||
pub fn build(self, surface_texture: SurfaceTexture) -> Result<Pixels, Error> {
|
|
||||||
Pixels::new_with_options(surface_texture, self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
|
|
41
src/render_pass.rs
Normal file
41
src/render_pass.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use wgpu::TextureView;
|
||||||
|
|
||||||
|
/// Objects that implement this trait can be added to [`Pixels`] as a render pass.
|
||||||
|
///
|
||||||
|
/// [`Pixels`] always has at least one render pass; a scaling pass that uses a
|
||||||
|
/// nearest-neighbor sampler to preserve pixel edges. Optionally it may also
|
||||||
|
/// have a second scaling pass that transforms the texture to its final size
|
||||||
|
/// (for non-square pixel aspect ratios). During this second pass, the texture
|
||||||
|
/// is stretched horizontally using a linear sampler.
|
||||||
|
///
|
||||||
|
/// Any additional render passes are executed afterward.
|
||||||
|
///
|
||||||
|
/// Each render pass is configured with one [`wgpu::TextureView`] as an input.
|
||||||
|
/// You will probably want to create a binding for this `texture_view` so your
|
||||||
|
/// shaders can sample from it.
|
||||||
|
///
|
||||||
|
/// The render pass will also receive a reference to another
|
||||||
|
/// [`wgpu::TextureView`] when the pass is executed. This texture view is the
|
||||||
|
/// `render_target`.
|
||||||
|
///
|
||||||
|
/// [`Pixels`]: ./struct.Pixels.html
|
||||||
|
pub trait RenderPass {
|
||||||
|
/// The `update_bindings` method will be called when the `texture_view`
|
||||||
|
/// needs to be recreated.
|
||||||
|
///
|
||||||
|
/// It's always called at least once when the default [`Pixels`] render
|
||||||
|
/// passes are created. This can also happen in response to resizing the
|
||||||
|
/// [`SurfaceTexture`].
|
||||||
|
///
|
||||||
|
/// You will typically recreate your bindings here to reference the new
|
||||||
|
/// input `texture_view`.
|
||||||
|
///
|
||||||
|
/// [`Pixels`]: ./struct.Pixels.html
|
||||||
|
/// [`SurfaceTexture`]: ./struct.SurfaceTexture.html
|
||||||
|
fn update_bindings(&mut self, texture_view: &TextureView);
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
}
|
Loading…
Reference in a new issue