Refactor phases and RenderPass API
This commit is contained in:
parent
1e54c54407
commit
718d609517
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
multimap = "0.8"
|
||||
pollster = "0.2"
|
||||
rayon = "1"
|
||||
slab = "^0.4"
|
||||
|
|
40
src/lib.rs
40
src/lib.rs
|
@ -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;
|
||||
}
|
||||
|
|
97
src/pass.rs
97
src/pass.rs
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
129
src/phase.rs
129
src/phase.rs
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue