Refactor phases and RenderPass API

This commit is contained in:
mars 2022-04-18 03:54:29 -06:00
parent 1e54c54407
commit 718d609517
5 changed files with 92 additions and 203 deletions

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
multimap = "0.8"
pollster = "0.2"
rayon = "1"
slab = "^0.4"

View File

@ -51,50 +51,26 @@ impl Renderer {
pub fn render(&mut self) {
let frame_index = 0;
let phase_passes = PhaseMultiMap::<usize>::default();
let phase_passes = multimap::MultiMap::<Phase, usize>::new();
let phase_passes = std::sync::Mutex::new(phase_passes);
self.render_passes
.par_iter_mut()
.enumerate()
.for_each(|(pass_index, rp)| {
let mut phases_buf = PhaseList::default();
let mut phases_buf = Vec::new();
phases_buf.clear();
rp.begin_frame(frame_index, &mut phases_buf);
phase_passes
.lock()
.unwrap()
.insert_multi(&phases_buf, pass_index);
let mut passes = phase_passes.lock().unwrap();
for phase in phases_buf.into_iter() {
passes.insert(phase, pass_index);
}
});
let phase_passes = phase_passes.into_inner().unwrap();
PrePhase::iter().par_bridge().for_each(|phase| {
for pass_index in phase_passes.iter_pre(&phase) {
let pass = &self.render_passes[*pass_index];
let mut encoder = gpu::RenderBundleEncoder;
pass.render_pre(phase, frame_index, &mut encoder);
}
});
let viewport = ViewportData;
ViewportPhase::iter().par_bridge().for_each(|phase| {
for pass_index in phase_passes.iter_viewport(&phase) {
let pass = &self.render_passes[*pass_index];
let mut encoder = gpu::RenderBundleEncoder;
pass.render_viewport(phase, frame_index, &viewport, &mut encoder);
}
});
// Up next is actual rendering to a surface!
}
}
/// Mock GPU API (like Vulkan, WebGPU, or OpenGL) types for prototyping.
pub mod gpu {
/// Thread-safe GPU command recorder that's later executed in a command buffer.
///
/// Also known as a:
/// - RenderBundleEncoder in WebGPU
/// - secondary command buffer in Vulkan
pub struct RenderBundleEncoder;
}

View File

@ -1,7 +1,6 @@
//! Render pass data structures and interfaces.
use crate::gpu;
use crate::phase::*;
use crate::phase::{Phase, PhaseKind};
use std::sync::{Arc, RwLock};
pub mod mesh;
@ -10,6 +9,15 @@ pub mod mesh;
/// entered.
pub struct ViewportData;
/// Data passed to each render pass's recording phases.
pub struct PhaseData<'a, T> {
pub phase: Phase,
pub frame_data: T,
pub viewport: &'a ViewportData,
}
pub type IndexedPhaseData<'a> = PhaseData<'a, usize>;
/// The render pass interface trait.
///
/// Render passes are persistent structures that share GPU resources, user data,
@ -31,26 +39,16 @@ pub trait RenderPass: Send + Sync {
///
/// 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 PhaseList);
fn begin_frame(&mut self, data: &mut Self::FrameData, phases: &mut Vec<Phase>);
/// Renders a phase in the [PrePhase] group.
fn render_pre(
&self,
phase: PrePhase,
data: &Self::FrameData,
cmds: &mut gpu::RenderBundleEncoder,
);
fn record_commands(&self, data: PhaseData<&Self::FrameData>, cmds: &mut wgpu::CommandEncoder);
/// Renders a phase in the [ViewportPhase] group.
///
/// During these render phases, passes may have access to this frame's
/// [ViewportData].
fn render_viewport(
fn record_compute(&self, data: PhaseData<&Self::FrameData>, cmds: &mut wgpu::ComputePass);
fn record_render(
&self,
phase: ViewportPhase,
data: &Self::FrameData,
viewport: &ViewportData,
cmds: &mut gpu::RenderBundleEncoder,
data: PhaseData<&Self::FrameData>,
cmds: &mut wgpu::RenderBundleEncoder,
);
}
@ -60,17 +58,13 @@ pub trait RenderPass: Send + Sync {
/// Functions on this trait map one-to-one with [RenderPass], except that
/// frame data is passed by index and not by reference.
pub trait RenderPassBoxTrait: Send + Sync {
fn begin_frame(&mut self, data_index: usize, phases: &mut PhaseList);
fn begin_frame(&mut self, data_index: usize, phases: &mut Vec<Phase>);
fn render_pre(&self, phase: PrePhase, data_index: usize, cmds: &mut gpu::RenderBundleEncoder);
fn record_commands(&self, data: IndexedPhaseData, cmds: &mut wgpu::CommandEncoder);
fn render_viewport(
&self,
phase: ViewportPhase,
data_index: usize,
viewport: &ViewportData,
cmds: &mut gpu::RenderBundleEncoder,
);
fn record_compute(&self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass);
fn record_render(&self, data: IndexedPhaseData, cmds: &mut wgpu::RenderBundleEncoder);
}
/// A container for a reference-counted render pass instance and its frame data.
@ -97,28 +91,49 @@ impl<T: 'static + RenderPass> RenderPassBox<T> {
}
}
impl<T: RenderPass> RenderPassBox<T> {
fn get_frame_data<'a>(
&'a self,
index: IndexedPhaseData<'a>,
) -> PhaseData<'a, &'a T::FrameData> {
let PhaseData {
phase,
frame_data,
viewport,
} = index;
let frame_data = self.frame_data.get(frame_data).unwrap();
PhaseData {
phase,
frame_data,
viewport,
}
}
}
impl<T: RenderPass> RenderPassBoxTrait for RenderPassBox<T> {
fn begin_frame(&mut self, data_index: usize, phases: &mut PhaseList) {
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)
}
fn render_pre(&self, phase: PrePhase, data_index: usize, cmds: &mut gpu::RenderBundleEncoder) {
let frame_data = &self.frame_data[data_index];
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.render_pre(phase, frame_data, cmds)
render_pass.record_commands(frame_data, cmds)
}
fn render_viewport(
&self,
phase: ViewportPhase,
data_index: usize,
viewport: &ViewportData,
cmds: &mut gpu::RenderBundleEncoder,
) {
let frame_data = &self.frame_data[data_index];
fn record_compute(&self, data: IndexedPhaseData, cmds: &mut wgpu::ComputePass) {
let frame_data = self.get_frame_data(data);
let render_pass = self.render_pass.read().unwrap();
render_pass.render_viewport(phase, frame_data, viewport, cmds)
render_pass.record_compute(frame_data, cmds)
}
fn record_render(&self, data: IndexedPhaseData, cmds: &mut wgpu::RenderBundleEncoder) {
let frame_data = self.get_frame_data(data);
let render_pass = self.render_pass.read().unwrap();
render_pass.record_render(frame_data, cmds)
}
}

View File

@ -27,27 +27,23 @@ impl RenderPass for MeshPass {
FrameData {}
}
fn begin_frame(&mut self, data: &mut FrameData, phases: &mut PhaseList) {
fn begin_frame(&mut self, data: &mut FrameData, phases: &mut Vec<Phase>) {
println!("MeshPass::begin_frame()");
phases.insert_pre(PrePhase::VertexSkinning);
phases.insert_viewport(ViewportPhase::Depth);
phases.insert_viewport(ViewportPhase::Opaque);
phases.insert_viewport(ViewportPhase::Transparent);
phases.push(Phase::Depth);
phases.push(Phase::Opaque);
phases.push(Phase::Transparent);
}
fn render_pre(&self, phase: PrePhase, data: &FrameData, cmds: &mut gpu::RenderBundleEncoder) {
println!("MeshPass::render_pre(phase: {:?})", phase);
fn record_commands(&self, data: PhaseData<&FrameData>, cmds: &mut wgpu::CommandEncoder) {
println!("MeshPass::record_commands(phase: {:?})", data.phase);
}
fn render_viewport(
&self,
phase: ViewportPhase,
data: &FrameData,
viewport: &ViewportData,
cmds: &mut gpu::RenderBundleEncoder,
) {
println!("MeshPass::render_viewport(phase: {:?})", phase);
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) {
println!("MeshPass::record_render(phase: {:?})", data.phase);
}
}

View File

@ -1,126 +1,27 @@
//! Definitions and containers for render phase identifiers.
//!
//! Rendering phases are organized into different groups, which represent
//! different kinds of external data becoming available as the frame progresses.
//! For example, OpenXR encourages applications to acquire its viewport poses
//! as late into the frame as possible, to maximize the accuracy of pose
//! prediction in the time before the frame is presented.
//!
//! The definitions in this module are meant to be expanded upon over time, so
//! the containers make heavy use of generics to abstract functionality across
//! different groups of phases.
//! Render phase definitions.
use std::collections::HashMap;
/// The rendering pre-phase group; before viewport acquisition.
/// The main render phase definitions.
///
/// These variants are temporary and for testing purposes.
#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, strum::EnumIter)]
pub enum PrePhase {
VertexSkinning,
}
/// The main viewport rendering phase group.
///
/// These variants are temporary and for testing purposes.
#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, strum::EnumIter)]
pub enum ViewportPhase {
CullLights,
CullPolys,
ShadowRendering,
pub enum Phase {
Depth,
DepthMips,
AO,
Opaque,
Transparent,
Bloom,
Overlay,
Composite,
}
/// A container of phases of each group.
///
/// Can be used with the [PhaseMultiMap] container to insert a value into
/// multiple phases at once.
#[derive(Debug, Default)]
pub struct PhaseList {
pre: Vec<PrePhase>,
viewport: Vec<ViewportPhase>,
}
impl PhaseList {
/// Clears the list, removing all phases.
pub fn clear(&mut self) {
self.pre.clear();
self.viewport.clear();
}
/// Inserts a [PrePhase].
pub fn insert_pre(&mut self, phase: PrePhase) {
self.pre.push(phase);
}
/// Inserts a [ViewportPhase].
pub fn insert_viewport(&mut self, phase: ViewportPhase) {
self.viewport.push(phase);
}
}
/// Maps phases of each group to sets of values.
///
/// Used by the frame scheduler to organize which phases execute which render
/// passes.
#[derive(Debug, Default)]
pub struct PhaseMultiMap<T> {
pre: HashMap<PrePhase, Vec<T>>,
viewport: HashMap<ViewportPhase, Vec<T>>,
}
impl<T: Copy + PartialEq> PhaseMultiMap<T> {
/// Inserts a value into multiple phases at once, keyed with a [PhaseList].
pub fn insert_multi(&mut self, keys: &PhaseList, val: T) {
for pre in keys.pre.iter() {
Self::insert(&mut self.pre, pre, val);
}
for viewport in keys.viewport.iter() {
Self::insert(&mut self.viewport, viewport, val);
}
}
/// Iterates over all values matching a [PrePhase].
pub fn iter_pre<'a>(&'a self, phase: &'a PrePhase) -> std::slice::Iter<'a, T> {
Self::iter(&self.pre, phase)
}
/// Iterates over all values matching a [ViewportPhase].
pub fn iter_viewport<'a>(&'a self, phase: &'a ViewportPhase) -> std::slice::Iter<'a, T> {
Self::iter(&self.viewport, phase)
}
/// Generic helper function for inserting a single value into a phase group map.
fn insert<K>(map: &mut HashMap<K, Vec<T>>, key: &K, val: T)
where
K: std::hash::Hash + Eq + Copy,
{
if let Some(existing) = map.get_mut(key) {
if !existing.contains(&val) {
existing.push(val);
}
} else {
map.insert(*key, vec![val]);
}
}
/// Generic helper function that iterates over a group's phase, if available.
fn iter<'a, K>(map: &'a HashMap<K, Vec<T>>, key: &'a K) -> std::slice::Iter<'a, T>
where
K: std::hash::Hash + Eq + Copy,
{
if let Some(vals) = map.get(key) {
vals.iter()
} else {
[].iter()
impl Phase {
pub fn get_kind(&self) -> PhaseKind {
use Phase::*;
match self {
Depth | Opaque | Transparent => PhaseKind::Render,
}
}
}
/// The different kinds of phases.
pub enum PhaseKind {
Command,
Compute,
Render,
}