Render a triangle!

- Refactor `RenderPass` and `RenderPassBox` APIs
- Add basic `mesh_shader.wgsl`
- Decouple `MeshLayoutBindingIndices` from `MeshLayoutBindings`
- Create `MeshPass` render pipeline
- `MeshPass` creates an example mesh
- `MeshPass` flushes `MeshPool`
- `MeshPass` actually draws meshes
This commit is contained in:
mars 2022-04-19 01:48:05 -06:00
parent 257acf56d2
commit f590edb77f
8 changed files with 320 additions and 76 deletions

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
bytemuck = { version = "^1.0", features = ["derive"] }
multimap = "0.8"
pollster = "0.2"
rayon = "1"

View File

@ -36,11 +36,11 @@ impl Renderer {
}
pub fn add_pass<T: 'static + RenderPass>(&mut self, pass: T) {
let pass = Arc::new(RwLock::new(pass));
let pass = Arc::new(pass);
self.add_pass_arc(pass);
}
pub fn add_pass_arc<T: 'static + RenderPass>(&mut self, pass: Arc<RwLock<T>>) {
pub fn add_pass_arc<T: 'static + RenderPass>(&mut self, pass: Arc<T>) {
let pass = RenderPassBox::new(pass, self.frames_in_flight);
self.add_pass_box(pass);
}
@ -99,32 +99,26 @@ impl Renderer {
});
}
let opaque_cmds = Mutex::new(Vec::new());
if let Some(opaque) = phase_passes.get_vec(&Phase::Opaque) {
opaque.par_iter().for_each(|pass_index| {
let pass = &self.render_passes[*pass_index];
let frame_data = IndexedPhaseData {
phase: Phase::Opaque,
frame_data: frame_index,
viewport: &viewport,
};
if let Some(cmd) = pass.record_render(frame_data) {
opaque_cmds.lock().unwrap().push(cmd);
}
})
}
let opaque_cmds = opaque_cmds.into_inner().unwrap();
{
let mut opaque_cmds = Vec::new();
if let Some(opaque) = phase_passes.get_vec(&Phase::Opaque) {
opaque.iter().for_each(|pass_index| {
let frame_data = IndexedPhaseData {
phase: Phase::Opaque,
frame_data: frame_index,
viewport: &viewport,
};
let pass = &self.render_passes[*pass_index];
let mut cmds = self.device.create_render_bundle_encoder(
&wgpu::RenderBundleEncoderDescriptor {
label: Some("Opaque Pass Render Bundle"),
color_formats: &[format],
depth_stencil: None,
sample_count: 1,
multiview: None,
},
);
pass.record_render(frame_data, &mut cmds);
opaque_cmds.push(cmds.finish(&wgpu::RenderBundleDescriptor::default()));
})
}
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[wgpu::RenderPassColorAttachment {

View File

@ -73,7 +73,7 @@ fn main() {
let (mut renderer, mut viewport) = pollster::block_on(make_window_renderer(&window));
let device = renderer.get_device();
let mesh_pass = pass::mesh::MeshPass::new(device.to_owned());
let mesh_pass = pass::mesh::MeshPass::new(device.to_owned(), viewport.config.format);
renderer.add_pass(mesh_pass);
event_loop.run(move |event, _, control_flow| match event {

View File

@ -133,10 +133,10 @@ pub struct AttrAllocKey {
/// Info about an array of attributes that has been allocated in an [AttrPool].
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct AttrAlloc {
offset: usize,
count: usize,
offset_bytes: usize,
count_bytes: usize,
pub offset: usize,
pub count: usize,
pub offset_bytes: usize,
pub count_bytes: usize,
}
/// An unused space range in an [AttrPool].
@ -305,17 +305,21 @@ impl AttrPool {
Ok(())
}
/// Gets the offset (in elements) of an allocation, passed by key.
pub fn get_offset(&self, key: usize) -> Result<usize, PoolError> {
let alloc = self.allocs.get(key).ok_or(PoolError::InvalidIndex)?;
Ok(alloc.offset)
/// Retrieves an [AttrAlloc] by key.
pub fn get(&self, key: usize) -> Option<AttrAlloc> {
self.allocs.get(key).copied()
}
/// Gets this pool's internal GPU buffer.
pub fn get_buffer(&self) -> &wgpu::Buffer {
&self.buffer
}
/// Gets a [CopyDest] for an allocation, by key.
pub fn get_copy_dest(&self, key: usize) -> Result<CopyDest, PoolError> {
let offset = self.get_offset(key)?;
let offset = self.get(key).ok_or(PoolError::InvalidIndex)?.offset_bytes;
Ok(CopyDest {
buffer: &self.buffer,
buffer: self.get_buffer(),
offset,
})
}
@ -347,17 +351,31 @@ pub type MeshLayoutDesc = smallmap::Set<AttrId>;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct MeshLayoutId(usize);
/// Mappings of the attributes in a [MeshLayout] to specific pool indices.
pub type MeshLayoutBindingIndices = smallmap::Map<AttrId, usize>;
/// Mappings of the attributes in a [MeshLayout] to specific pools.
pub type MeshLayoutBindings = smallmap::Map<AttrId, usize>;
pub struct MeshLayoutBindings<'a> {
lock: std::sync::RwLockReadGuard<'a, HashMap<AttrId, Vec<AttrPool>>>,
indices: MeshLayoutBindingIndices,
}
impl<'a> MeshLayoutBindings<'a> {
pub fn get(&self, attr: AttrId) -> Option<&AttrPool> {
let pool_id = self.indices.get(&attr)?;
let pools = self.lock.get(&attr)?;
pools.get(*pool_id)
}
}
/// Mappings of the attributes in a [MeshAlloc] to specific pool offsets.
pub type MeshAllocOffsets = SmallVec<[(AttrId, usize); MAX_MESH_INLINE_ATTRIBUTES]>;
pub type MeshAllocInfos = SmallVec<[(AttrId, AttrAlloc); MAX_MESH_INLINE_ATTRIBUTES]>;
/// A set of mesh instances fitting a common [MeshLayoutDesc].
#[derive(Default)]
pub struct MeshLayoutInstances<T> {
pub bindings: MeshLayoutBindings,
pub instances: Vec<(T, MeshAllocOffsets)>,
pub bindings: MeshLayoutBindingIndices,
pub instances: Vec<(T, MeshAllocInfos)>,
}
/// A mesh data pool.
@ -476,7 +494,7 @@ impl MeshPool {
) -> Result<Vec<MeshLayoutInstances<T>>, PoolError>
where
I: Iterator<Item = T>,
F: Fn(&T) -> &'static MeshHandle,
F: Fn(&T) -> &MeshHandle,
{
let layout = self
.mesh_layouts
@ -511,8 +529,8 @@ impl MeshPool {
continue;
}
let mut layout_bindings = MeshLayoutBindings::default();
let mut alloc_offsets = MeshAllocOffsets::default();
let mut layout_bindings = MeshLayoutBindingIndices::default();
let mut alloc_infos = MeshAllocInfos::default();
for alloc in attr_allocs.iter() {
let pools_read = self.pools.read().unwrap();
let pools = pools_read
@ -520,13 +538,13 @@ impl MeshPool {
.ok_or(PoolError::AttrUnregistered)?;
let pool = pools.get(alloc.pool).ok_or(PoolError::InvalidIndex)?;
let alloc_offset = pool.get_offset(alloc.alloc)?;
let alloc_info = pool.get(alloc.alloc).ok_or(PoolError::InvalidIndex)?;
layout_bindings.insert(alloc.attr, alloc.pool);
alloc_offsets.push((alloc.attr, alloc_offset));
alloc_infos.push((alloc.attr, alloc_info));
}
let instance = (mesh, alloc_offsets);
let instance = (mesh, alloc_infos);
match layouts
.iter_mut()
@ -542,4 +560,9 @@ impl MeshPool {
Ok(layouts)
}
pub fn get_bindings<'a>(&'a self, indices: MeshLayoutBindingIndices) -> MeshLayoutBindings<'a> {
let lock = self.pools.read().unwrap();
MeshLayoutBindings { lock, indices }
}
}

View File

@ -32,24 +32,24 @@ pub trait RenderPass: Send + Sync {
/// Sets up a new instance of [Self::FrameData].
///
/// Called when a render pass is added to [crate::Renderer].
fn create_frame_data(&mut self) -> Self::FrameData;
fn create_frame_data(&self) -> Self::FrameData;
/// Initializes this frame's [Self::FrameData], and queries the pass for
/// which phases to execute.
///
/// This is the only opportunity the render pass has to mutate this frame's
/// data this frame, so all setup for future phases should be done here.
fn begin_frame(&mut self, data: &mut Self::FrameData, phases: &mut Vec<Phase>);
fn begin_frame(&self, data: &mut Self::FrameData, phases: &mut Vec<Phase>);
fn record_commands(&self, data: PhaseData<&Self::FrameData>, cmds: &mut wgpu::CommandEncoder);
fn record_compute(&self, data: PhaseData<&Self::FrameData>, cmds: &mut wgpu::ComputePass);
fn record_render(
&self,
fn record_compute<'a>(
&'a self,
data: PhaseData<&Self::FrameData>,
cmds: &mut wgpu::RenderBundleEncoder,
cmds: &mut wgpu::ComputePass<'a>,
);
fn record_render(&self, data: PhaseData<&Self::FrameData>) -> Option<wgpu::RenderBundle>;
}
/// The interface trait for [RenderPassBox], allowing use of a statically-sized
@ -62,14 +62,14 @@ pub trait RenderPassBoxTrait: Send + Sync {
fn record_commands(&self, data: IndexedPhaseData, cmds: &mut wgpu::CommandEncoder);
fn record_compute(&self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass);
fn record_compute<'a>(&'a self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass<'a>);
fn record_render(&self, data: IndexedPhaseData, cmds: &mut wgpu::RenderBundleEncoder);
fn record_render(&self, data: IndexedPhaseData) -> Option<wgpu::RenderBundle>;
}
/// A container for a reference-counted render pass instance and its frame data.
pub struct RenderPassBox<T: RenderPass> {
render_pass: Arc<RwLock<T>>,
render_pass: Arc<T>,
frame_data: Vec<T::FrameData>,
}
@ -78,10 +78,11 @@ impl<T: 'static + RenderPass> RenderPassBox<T> {
///
/// Calls to [RenderPassBoxTrait] functions with frame indices greater
/// than or equal to `frame_num` are out-of-bounds and will panic.
pub fn new(render_pass: Arc<RwLock<T>>, frame_num: usize) -> Box<dyn RenderPassBoxTrait> {
pub fn new(render_pass: Arc<T>, frame_num: usize) -> Box<dyn RenderPassBoxTrait> {
let frame_data = {
let mut lock = render_pass.write().unwrap();
(0..frame_num).map(|_| lock.create_frame_data()).collect()
(0..frame_num)
.map(|_| render_pass.create_frame_data())
.collect()
};
Box::new(Self {
@ -115,25 +116,22 @@ impl<T: RenderPass> RenderPassBox<T> {
impl<T: RenderPass> RenderPassBoxTrait for RenderPassBox<T> {
fn begin_frame(&mut self, data_index: usize, phases: &mut Vec<Phase>) {
let frame_data = &mut self.frame_data[data_index];
let mut render_pass = self.render_pass.write().unwrap();
render_pass.begin_frame(frame_data, phases)
self.render_pass.begin_frame(frame_data, phases)
}
fn record_commands(&self, data: IndexedPhaseData, cmds: &mut wgpu::CommandEncoder) {
let frame_data = self.get_frame_data(data);
let render_pass = self.render_pass.read().unwrap();
render_pass.record_commands(frame_data, cmds)
self.render_pass.record_commands(frame_data, cmds)
}
fn record_compute(&self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass) {
fn record_compute<'a>(&'a self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass<'a>) {
let frame_data = self.get_frame_data(data);
let render_pass = self.render_pass.read().unwrap();
render_pass.record_compute(frame_data, cmds)
self.render_pass.record_compute(frame_data, cmds)
}
fn record_render(&self, data: IndexedPhaseData, cmds: &mut wgpu::RenderBundleEncoder) {
fn record_render(&self, data: IndexedPhaseData) -> Option<wgpu::RenderBundle> {
let frame_data = self.get_frame_data(data);
let render_pass = self.render_pass.read().unwrap();
render_pass.record_render(frame_data, cmds)
// let render_pass = self.render_pass.read().unwrap();
self.render_pass.record_render(frame_data)
}
}

View File

@ -1,21 +1,154 @@
use super::*;
use crate::mesh::*;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
pub position: [f32; 3],
pub color: [f32; 3],
}
impl Attribute for Vertex {
fn get_usages() -> wgpu::BufferUsages {
wgpu::BufferUsages::VERTEX
}
}
const VERTEX_ATTRS: &[wgpu::VertexAttribute] =
&wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3];
impl Vertex {
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: VERTEX_ATTRS,
}
}
}
pub type Index = u32;
pub struct FrameData {}
pub struct MeshPass {
device: Arc<wgpu::Device>,
attr_store: Arc<AttrStore>,
mesh_pool: Arc<MeshPool>,
vertex_attr_id: AttrId,
index_attr_id: AttrId,
mesh_layout_id: MeshLayoutId,
example_mesh: MeshHandle,
opaque_pipeline: wgpu::RenderPipeline,
target_format: wgpu::TextureFormat,
}
impl MeshPass {
pub fn new(device: Arc<wgpu::Device>) -> Self {
pub fn new(device: Arc<wgpu::Device>, target_format: wgpu::TextureFormat) -> Self {
let attr_store = AttrStore::new();
let mesh_pool = MeshPool::new(device, attr_store.to_owned());
let mesh_pool = MeshPool::new(device.clone(), attr_store.to_owned());
let vertex_attr_id = attr_store.get_type::<Vertex>();
let index_attr_id = attr_store.add(AttrInfo {
layout: AttrLayout {
size: std::mem::size_of::<Index>(),
},
usages: wgpu::BufferUsages::INDEX,
default_pool_size: 1_000_000,
});
let mut mesh_layout = MeshLayoutDesc::new();
mesh_layout.insert(vertex_attr_id, ());
mesh_layout.insert(index_attr_id, ());
let mesh_layout_id = mesh_pool.add_layout(mesh_layout).unwrap();
let example_vertices = vec![
Vertex {
position: [-0.5, 0.5, 0.0],
color: [1.0, 0.0, 0.0],
},
Vertex {
position: [0.5, 0.5, 0.0],
color: [0.0, 1.0, 0.0],
},
Vertex {
position: [0.0, -0.5, 0.0],
color: [0.0, 0.0, 1.0],
},
];
let example_vertices = AttrBuffer {
id: vertex_attr_id,
count: example_vertices.len(),
data: bytemuck::cast_slice(&example_vertices).to_vec(),
};
let example_indices = vec![0u32, 1u32, 2u32];
let example_indices = AttrBuffer {
id: index_attr_id,
count: example_indices.len(),
data: bytemuck::cast_slice(&example_indices).to_vec(),
};
let mut example_mesh = MeshBuffer::default();
example_mesh.attributes.push(example_vertices);
example_mesh.attributes.push(example_indices);
let example_mesh = mesh_pool.load(example_mesh).unwrap();
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let shader = device.create_shader_module(&wgpu::include_wgsl!("mesh_shader.wgsl"));
let opaque_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format: target_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
}],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None, // Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
Self {
device,
attr_store,
mesh_pool,
index_attr_id,
vertex_attr_id,
mesh_layout_id,
example_mesh,
opaque_pipeline,
target_format,
}
}
}
@ -23,13 +156,14 @@ impl MeshPass {
impl RenderPass for MeshPass {
type FrameData = FrameData;
fn create_frame_data(&mut self) -> FrameData {
fn create_frame_data(&self) -> FrameData {
FrameData {}
}
fn begin_frame(&mut self, data: &mut FrameData, phases: &mut Vec<Phase>) {
fn begin_frame(&self, data: &mut FrameData, phases: &mut Vec<Phase>) {
println!("MeshPass::begin_frame()");
phases.push(Phase::Upload);
phases.push(Phase::Depth);
phases.push(Phase::Opaque);
phases.push(Phase::Transparent);
@ -37,13 +171,74 @@ impl RenderPass for MeshPass {
fn record_commands(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::CommandEncoder) {
println!("MeshPass::record_commands(phase: {:?})", data.phase);
match data.phase {
Phase::Upload => self.mesh_pool.flush(cmds),
_ => {}
}
}
fn record_compute(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::ComputePass) {
println!("MeshPass::record_compute(phase: {:?})", data.phase);
}
fn record_render(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::RenderBundleEncoder) {
fn record_render(&self, data: PhaseData<&FrameData>) -> Option<wgpu::RenderBundle> {
println!("MeshPass::record_render(phase: {:?})", data.phase);
let mut cmds =
self.device
.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
label: Some("Opaque Pass Render Bundle"),
color_formats: &[self.target_format],
depth_stencil: None,
sample_count: 1,
multiview: None,
});
let pipeline = match data.phase {
Phase::Opaque => &self.opaque_pipeline,
_ => return None,
};
let meshes = &[&self.example_mesh, &self.example_mesh];
// yikes
let mesh_bindings: Vec<(MeshLayoutBindings, Vec<(&&MeshHandle, MeshAllocInfos)>)> = self
.mesh_pool
.iter_meshes(self.mesh_layout_id, meshes.iter(), |v| v)
.unwrap()
.into_iter()
.map(
|MeshLayoutInstances {
bindings,
instances,
}| (self.mesh_pool.get_bindings(bindings), instances),
)
.collect();
cmds.set_pipeline(pipeline);
for (bindings, instances) in mesh_bindings.iter() {
let vertices_pool = bindings.get(self.vertex_attr_id).unwrap();
let indices_pool = bindings.get(self.index_attr_id).unwrap();
cmds.set_vertex_buffer(0, vertices_pool.get_buffer().slice(..));
cmds.set_index_buffer(
indices_pool.get_buffer().slice(..),
wgpu::IndexFormat::Uint32,
);
for (mesh, infos) in instances {
let vertices = infos.iter().find(|i| i.0 == self.vertex_attr_id).unwrap().1;
let indices = infos.iter().find(|i| i.0 == self.index_attr_id).unwrap().1;
let is_start = indices.offset as u32;
let is_end = is_start + indices.count as u32;
cmds.draw_indexed(is_start..is_end, vertices.offset as i32, 0..1);
println!("drew a mesh! {}..{} + {}", is_start, is_end, vertices.offset);
}
}
Some(cmds.finish(&wgpu::RenderBundleDescriptor::default()))
}
}

30
src/pass/mesh_shader.wgsl Normal file
View File

@ -0,0 +1,30 @@
struct VertexInput {
[[location(0)]] position: vec3<f32>;
[[location(1)]] color: vec3<f32>;
};
struct VertexOutput {
[[builtin(position)]] clip_position: vec4<f32>;
[[location(0)]] position: vec3<f32>;
[[location(1)]] color: vec3<f32>;
};
[[stage(vertex)]]
fn vs_main(
[[builtin(instance_index)]] mesh_idx: u32,
[[builtin(vertex_index)]] vertex_idx: u32,
vertex: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.clip_position = vec4<f32>(vertex.position, 1.0);
out.position = vertex.position;
out.color = vertex.color;
return out;
}
[[stage(fragment)]]
fn fs_main(
frag: VertexOutput,
) -> [[location(0)]] vec4<f32> {
return vec4<f32>(frag.color, 1.0);
}

View File

@ -64,6 +64,9 @@ impl<T: Clone> StagingPool<T> {
on_complete(copy.target);
}
}
drop(src_view);
src.unmap();
}
pub fn queue_copies(&self, copies: Vec<CopyBuffer<T>>) {