#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum AttachmentFormat {
Rgba8Unorm,
Rgba16Float,
R11G11B10Float,
Bgra8UnormSrgb,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum DepthFormat {
Depth32Float,
Depth24Stencil8,
Depth16Unorm,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum LoadOp {
Clear([f32; 4]),
Load,
DontCare,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum StoreOp {
Store,
Discard,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ColorAttachment {
pub format: AttachmentFormat,
pub load_op: LoadOp,
pub store_op: StoreOp,
pub sample_count: u32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DepthAttachment {
pub format: DepthFormat,
pub depth_load_op: LoadOp,
pub depth_store_op: StoreOp,
pub stencil_load_op: LoadOp,
pub stencil_store_op: StoreOp,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RenderPassDescriptor {
pub label: String,
pub color_attachments: Vec<ColorAttachment>,
pub depth_attachment: Option<DepthAttachment>,
pub sample_count: u32,
}
impl RenderPassDescriptor {
#[allow(dead_code)]
pub fn shadow_pass() -> Self {
RenderPassDescriptor {
label: "shadow_pass".to_string(),
color_attachments: vec![],
depth_attachment: Some(DepthAttachment {
format: DepthFormat::Depth32Float,
depth_load_op: LoadOp::Clear([1.0, 0.0, 0.0, 0.0]),
depth_store_op: StoreOp::Store,
stencil_load_op: LoadOp::DontCare,
stencil_store_op: StoreOp::Discard,
}),
sample_count: 1,
}
}
#[allow(dead_code)]
pub fn gbuffer_pass() -> Self {
RenderPassDescriptor {
label: "gbuffer_pass".to_string(),
color_attachments: vec![
ColorAttachment {
format: AttachmentFormat::Rgba8Unorm,
load_op: LoadOp::Clear([0.0, 0.0, 0.0, 1.0]),
store_op: StoreOp::Store,
sample_count: 1,
},
ColorAttachment {
format: AttachmentFormat::Rgba16Float,
load_op: LoadOp::Clear([0.0, 0.0, 1.0, 0.0]),
store_op: StoreOp::Store,
sample_count: 1,
},
ColorAttachment {
format: AttachmentFormat::Rgba8Unorm,
load_op: LoadOp::Clear([0.0, 0.0, 0.0, 0.0]),
store_op: StoreOp::Store,
sample_count: 1,
},
],
depth_attachment: Some(DepthAttachment {
format: DepthFormat::Depth24Stencil8,
depth_load_op: LoadOp::Clear([1.0, 0.0, 0.0, 0.0]),
depth_store_op: StoreOp::Store,
stencil_load_op: LoadOp::Clear([0.0, 0.0, 0.0, 0.0]),
stencil_store_op: StoreOp::Discard,
}),
sample_count: 1,
}
}
#[allow(dead_code)]
pub fn lighting_pass() -> Self {
RenderPassDescriptor {
label: "lighting_pass".to_string(),
color_attachments: vec![ColorAttachment {
format: AttachmentFormat::Rgba16Float,
load_op: LoadOp::Clear([0.0, 0.0, 0.0, 1.0]),
store_op: StoreOp::Store,
sample_count: 1,
}],
depth_attachment: Some(DepthAttachment {
format: DepthFormat::Depth32Float,
depth_load_op: LoadOp::Load,
depth_store_op: StoreOp::Discard,
stencil_load_op: LoadOp::DontCare,
stencil_store_op: StoreOp::Discard,
}),
sample_count: 1,
}
}
#[allow(dead_code)]
pub fn post_process_pass() -> Self {
RenderPassDescriptor {
label: "post_process_pass".to_string(),
color_attachments: vec![ColorAttachment {
format: AttachmentFormat::Bgra8UnormSrgb,
load_op: LoadOp::DontCare,
store_op: StoreOp::Store,
sample_count: 1,
}],
depth_attachment: None,
sample_count: 1,
}
}
#[allow(dead_code)]
pub fn ui_pass() -> Self {
RenderPassDescriptor {
label: "ui_pass".to_string(),
color_attachments: vec![ColorAttachment {
format: AttachmentFormat::Bgra8UnormSrgb,
load_op: LoadOp::Load,
store_op: StoreOp::Store,
sample_count: 1,
}],
depth_attachment: None,
sample_count: 1,
}
}
}
#[allow(dead_code)]
pub fn total_attachment_count(desc: &RenderPassDescriptor) -> usize {
desc.color_attachments.len()
+ if desc.depth_attachment.is_some() {
1
} else {
0
}
}
#[allow(dead_code)]
pub fn is_depth_only(desc: &RenderPassDescriptor) -> bool {
desc.color_attachments.is_empty() && desc.depth_attachment.is_some()
}
#[allow(dead_code)]
pub fn attachment_format_bytes(fmt: &AttachmentFormat) -> u32 {
match fmt {
AttachmentFormat::Rgba8Unorm => 4,
AttachmentFormat::Rgba16Float => 8,
AttachmentFormat::R11G11B10Float => 4,
AttachmentFormat::Bgra8UnormSrgb => 4,
}
}
#[allow(dead_code)]
pub fn depth_format_bytes(fmt: &DepthFormat) -> u32 {
match fmt {
DepthFormat::Depth32Float => 4,
DepthFormat::Depth24Stencil8 => 4,
DepthFormat::Depth16Unorm => 2,
}
}
#[allow(dead_code)]
pub fn render_pass_summary(desc: &RenderPassDescriptor) -> String {
format!(
"RenderPass '{}': {} color, depth={}, samples={}",
desc.label,
desc.color_attachments.len(),
desc.depth_attachment.is_some(),
desc.sample_count
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shadow_pass_is_depth_only() {
let p = RenderPassDescriptor::shadow_pass();
assert!(is_depth_only(&p));
}
#[test]
fn gbuffer_pass_three_color_attachments() {
let p = RenderPassDescriptor::gbuffer_pass();
assert_eq!(p.color_attachments.len(), 3);
}
#[test]
fn gbuffer_pass_has_depth() {
let p = RenderPassDescriptor::gbuffer_pass();
assert!(p.depth_attachment.is_some());
}
#[test]
fn lighting_pass_hdr_format() {
let p = RenderPassDescriptor::lighting_pass();
assert_eq!(p.color_attachments.len(), 1);
assert_eq!(p.color_attachments[0].format, AttachmentFormat::Rgba16Float);
}
#[test]
fn post_process_pass_no_depth() {
let p = RenderPassDescriptor::post_process_pass();
assert!(p.depth_attachment.is_none());
}
#[test]
fn format_bytes_rgba8() {
assert_eq!(attachment_format_bytes(&AttachmentFormat::Rgba8Unorm), 4);
}
#[test]
fn format_bytes_rgba16float() {
assert_eq!(attachment_format_bytes(&AttachmentFormat::Rgba16Float), 8);
}
#[test]
fn depth_bytes_depth32() {
assert_eq!(depth_format_bytes(&DepthFormat::Depth32Float), 4);
}
#[test]
fn depth_bytes_depth16() {
assert_eq!(depth_format_bytes(&DepthFormat::Depth16Unorm), 2);
}
#[test]
fn total_count_shadow() {
let p = RenderPassDescriptor::shadow_pass();
assert_eq!(total_attachment_count(&p), 1);
}
#[test]
fn total_count_gbuffer() {
let p = RenderPassDescriptor::gbuffer_pass();
assert_eq!(total_attachment_count(&p), 4);
}
#[test]
fn summary_non_empty() {
let p = RenderPassDescriptor::lighting_pass();
let s = render_pass_summary(&p);
assert!(!s.is_empty());
assert!(s.contains("lighting_pass"));
}
#[test]
fn all_passes_valid_sample_count() {
let passes = vec![
RenderPassDescriptor::shadow_pass(),
RenderPassDescriptor::gbuffer_pass(),
RenderPassDescriptor::lighting_pass(),
RenderPassDescriptor::post_process_pass(),
RenderPassDescriptor::ui_pass(),
];
for p in &passes {
assert!(
p.sample_count == 1 || p.sample_count == 4,
"invalid sample_count in {}",
p.label
);
}
}
#[test]
fn ui_pass_no_depth() {
let p = RenderPassDescriptor::ui_pass();
assert!(p.depth_attachment.is_none());
}
#[test]
fn format_bytes_r11g11b10() {
assert_eq!(
attachment_format_bytes(&AttachmentFormat::R11G11B10Float),
4
);
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum RenderPassStage {
Depth,
Opaque,
Transparent,
PostProcess,
UI,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RenderPassConfig {
pub stage: RenderPassStage,
pub enabled: bool,
pub clear_depth: bool,
pub clear_color: bool,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct RenderPassList {
passes: Vec<RenderPassConfig>,
}
#[allow(dead_code)]
pub fn default_render_pass_config(stage: RenderPassStage) -> RenderPassConfig {
RenderPassConfig {
stage,
enabled: true,
clear_depth: false,
clear_color: false,
}
}
#[allow(dead_code)]
pub fn new_render_pass_list() -> RenderPassList {
RenderPassList::default()
}
#[allow(dead_code)]
pub fn rpl_add_pass(list: &mut RenderPassList, config: RenderPassConfig) {
list.passes.push(config);
}
#[allow(dead_code)]
pub fn rpl_remove_pass(list: &mut RenderPassList, index: usize) {
if index < list.passes.len() {
list.passes.remove(index);
}
}
#[allow(dead_code)]
pub fn rpl_count(list: &RenderPassList) -> usize {
list.passes.len()
}
#[allow(dead_code)]
pub fn rpl_get(list: &RenderPassList, index: usize) -> Option<&RenderPassConfig> {
list.passes.get(index)
}
#[allow(dead_code)]
pub fn rpl_enabled_count(list: &RenderPassList) -> usize {
list.passes.iter().filter(|p| p.enabled).count()
}
#[allow(dead_code)]
pub fn rpl_stage_name(stage: &RenderPassStage) -> &'static str {
match stage {
RenderPassStage::Depth => "depth",
RenderPassStage::Opaque => "opaque",
RenderPassStage::Transparent => "transparent",
RenderPassStage::PostProcess => "post_process",
RenderPassStage::UI => "ui",
}
}
#[allow(dead_code)]
pub fn rpl_to_json(list: &RenderPassList) -> String {
format!(
r#"{{"pass_count":{},"enabled_count":{}}}"#,
list.passes.len(),
rpl_enabled_count(list)
)
}
#[cfg(test)]
mod rpl_tests {
use super::*;
#[test]
fn test_new_list_empty() {
let l = new_render_pass_list();
assert_eq!(rpl_count(&l), 0);
}
#[test]
fn test_add_pass() {
let mut l = new_render_pass_list();
rpl_add_pass(&mut l, default_render_pass_config(RenderPassStage::Opaque));
assert_eq!(rpl_count(&l), 1);
}
#[test]
fn test_remove_pass() {
let mut l = new_render_pass_list();
rpl_add_pass(&mut l, default_render_pass_config(RenderPassStage::Depth));
rpl_remove_pass(&mut l, 0);
assert_eq!(rpl_count(&l), 0);
}
#[test]
fn test_get_pass() {
let mut l = new_render_pass_list();
rpl_add_pass(&mut l, default_render_pass_config(RenderPassStage::UI));
let p = rpl_get(&l, 0);
assert!(p.is_some());
assert_eq!(p.expect("should succeed").stage, RenderPassStage::UI);
}
#[test]
fn test_enabled_count() {
let mut l = new_render_pass_list();
rpl_add_pass(&mut l, default_render_pass_config(RenderPassStage::Opaque));
let mut cfg = default_render_pass_config(RenderPassStage::Transparent);
cfg.enabled = false;
rpl_add_pass(&mut l, cfg);
assert_eq!(rpl_enabled_count(&l), 1);
}
#[test]
fn test_stage_name() {
assert_eq!(rpl_stage_name(&RenderPassStage::Depth), "depth");
assert_eq!(
rpl_stage_name(&RenderPassStage::PostProcess),
"post_process"
);
assert_eq!(rpl_stage_name(&RenderPassStage::UI), "ui");
}
#[test]
fn test_to_json() {
let l = new_render_pass_list();
let j = rpl_to_json(&l);
assert!(j.contains("pass_count"));
assert!(j.contains("enabled_count"));
}
#[test]
fn test_default_config_enabled() {
let cfg = default_render_pass_config(RenderPassStage::Opaque);
assert!(cfg.enabled);
assert_eq!(cfg.stage, RenderPassStage::Opaque);
}
}