#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LoadOp {
Clear,
Load,
DontCare,
}
impl LoadOp {
#[must_use]
pub fn preserves_content(&self) -> bool {
matches!(self, Self::Load)
}
#[must_use]
pub fn label(&self) -> &'static str {
match self {
Self::Clear => "clear",
Self::Load => "load",
Self::DontCare => "dont_care",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StoreOp {
Store,
Discard,
DontCare,
}
impl StoreOp {
#[must_use]
pub fn writes_output(&self) -> bool {
matches!(self, Self::Store)
}
#[must_use]
pub fn label(&self) -> &'static str {
match self {
Self::Store => "store",
Self::Discard => "discard",
Self::DontCare => "dont_care",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttachmentFormat {
Rgba8Unorm,
Rgba16Float,
Rgba32Float,
Depth32FloatStencil8,
Depth32Float,
Depth24PlusStencil8,
}
impl AttachmentFormat {
#[must_use]
pub fn has_depth(&self) -> bool {
matches!(
self,
Self::Depth32FloatStencil8 | Self::Depth32Float | Self::Depth24PlusStencil8
)
}
#[must_use]
pub fn has_stencil(&self) -> bool {
matches!(self, Self::Depth32FloatStencil8 | Self::Depth24PlusStencil8)
}
#[must_use]
pub fn bytes_per_texel(&self) -> u32 {
match self {
Self::Rgba8Unorm => 4,
Self::Rgba16Float => 8,
Self::Rgba32Float => 16,
Self::Depth32FloatStencil8 => 5,
Self::Depth32Float => 4,
Self::Depth24PlusStencil8 => 4,
}
}
}
#[derive(Debug, Clone)]
pub struct AttachmentConfig {
pub format: AttachmentFormat,
pub load_op: LoadOp,
pub store_op: StoreOp,
pub sample_count: u32,
pub label: Option<String>,
}
impl AttachmentConfig {
#[must_use]
pub fn new(format: AttachmentFormat, load_op: LoadOp, store_op: StoreOp) -> Self {
Self {
format,
load_op,
store_op,
sample_count: 1,
label: None,
}
}
#[must_use]
pub fn has_depth(&self) -> bool {
self.format.has_depth()
}
#[must_use]
pub fn is_multisampled(&self) -> bool {
self.sample_count > 1
}
#[must_use]
pub fn with_sample_count(mut self, count: u32) -> Self {
self.sample_count = count;
self
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
}
#[derive(Debug, Clone)]
pub struct RenderPassConfig {
pub color_attachments: Vec<AttachmentConfig>,
pub depth_attachment: Option<AttachmentConfig>,
pub label: Option<String>,
}
impl RenderPassConfig {
#[must_use]
pub fn attachment_count(&self) -> usize {
self.color_attachments.len() + usize::from(self.depth_attachment.is_some())
}
#[must_use]
pub fn has_depth_attachment(&self) -> bool {
self.depth_attachment.is_some()
}
#[must_use]
pub fn has_uniform_color_format(&self) -> bool {
let mut iter = self.color_attachments.iter().map(|a| a.format);
match iter.next() {
None => true,
Some(first) => iter.all(|f| f == first),
}
}
}
#[derive(Debug, Default)]
pub struct RenderPassBuilder {
color_attachments: Vec<AttachmentConfig>,
depth_attachment: Option<AttachmentConfig>,
label: Option<String>,
}
impl RenderPassBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn add_color_attachment(mut self, attachment: AttachmentConfig) -> Self {
self.color_attachments.push(attachment);
self
}
pub fn set_depth_attachment(mut self, attachment: AttachmentConfig) -> Result<Self, String> {
if !attachment.has_depth() {
return Err(format!(
"Format {:?} does not contain a depth component",
attachment.format
));
}
self.depth_attachment = Some(attachment);
Ok(self)
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn build(self) -> Result<RenderPassConfig, String> {
if self.color_attachments.is_empty() {
return Err("RenderPassConfig requires at least one color attachment".into());
}
Ok(RenderPassConfig {
color_attachments: self.color_attachments,
depth_attachment: self.depth_attachment,
label: self.label,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_op_preserves_content_load() {
assert!(LoadOp::Load.preserves_content());
}
#[test]
fn test_load_op_clear_does_not_preserve() {
assert!(!LoadOp::Clear.preserves_content());
}
#[test]
fn test_store_op_writes_output_store() {
assert!(StoreOp::Store.writes_output());
}
#[test]
fn test_store_op_discard_no_write() {
assert!(!StoreOp::Discard.writes_output());
}
#[test]
fn test_attachment_format_has_depth_depth32() {
assert!(AttachmentFormat::Depth32Float.has_depth());
}
#[test]
fn test_attachment_format_rgba8_no_depth() {
assert!(!AttachmentFormat::Rgba8Unorm.has_depth());
}
#[test]
fn test_attachment_format_has_stencil_depth24() {
assert!(AttachmentFormat::Depth24PlusStencil8.has_stencil());
}
#[test]
fn test_attachment_format_bytes_per_texel_rgba32() {
assert_eq!(AttachmentFormat::Rgba32Float.bytes_per_texel(), 16);
}
#[test]
fn test_attachment_config_has_depth_true() {
let a = AttachmentConfig::new(
AttachmentFormat::Depth32Float,
LoadOp::Clear,
StoreOp::Store,
);
assert!(a.has_depth());
}
#[test]
fn test_attachment_config_not_multisampled_by_default() {
let a = AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
assert!(!a.is_multisampled());
}
#[test]
fn test_attachment_config_with_sample_count() {
let a = AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store)
.with_sample_count(4);
assert!(a.is_multisampled());
assert_eq!(a.sample_count, 4);
}
#[test]
fn test_render_pass_builder_build_ok() {
let color =
AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
let config = RenderPassBuilder::new()
.add_color_attachment(color)
.build()
.expect("operation should succeed in test");
assert_eq!(config.attachment_count(), 1);
}
#[test]
fn test_render_pass_builder_build_no_color_err() {
let result = RenderPassBuilder::new().build();
assert!(result.is_err());
}
#[test]
fn test_render_pass_builder_with_depth() {
let color =
AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
let depth = AttachmentConfig::new(
AttachmentFormat::Depth32Float,
LoadOp::Clear,
StoreOp::Discard,
);
let config = RenderPassBuilder::new()
.add_color_attachment(color)
.set_depth_attachment(depth)
.expect("operation should succeed in test")
.build()
.expect("operation should succeed in test");
assert!(config.has_depth_attachment());
assert_eq!(config.attachment_count(), 2);
}
#[test]
fn test_render_pass_builder_depth_non_depth_format_err() {
let bad =
AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
let result = RenderPassBuilder::new().set_depth_attachment(bad);
assert!(result.is_err());
}
#[test]
fn test_render_pass_uniform_color_format_true_single() {
let color =
AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
let config = RenderPassBuilder::new()
.add_color_attachment(color)
.build()
.expect("operation should succeed in test");
assert!(config.has_uniform_color_format());
}
}