1#![allow(dead_code)]
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum LoadOp {
11 Clear,
13 Load,
15 DontCare,
17}
18
19impl LoadOp {
20 #[must_use]
22 pub fn preserves_content(&self) -> bool {
23 matches!(self, Self::Load)
24 }
25
26 #[must_use]
28 pub fn label(&self) -> &'static str {
29 match self {
30 Self::Clear => "clear",
31 Self::Load => "load",
32 Self::DontCare => "dont_care",
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39pub enum StoreOp {
40 Store,
42 Discard,
44 DontCare,
46}
47
48impl StoreOp {
49 #[must_use]
51 pub fn writes_output(&self) -> bool {
52 matches!(self, Self::Store)
53 }
54
55 #[must_use]
57 pub fn label(&self) -> &'static str {
58 match self {
59 Self::Store => "store",
60 Self::Discard => "discard",
61 Self::DontCare => "dont_care",
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub enum AttachmentFormat {
69 Rgba8Unorm,
71 Rgba16Float,
73 Rgba32Float,
75 Depth32FloatStencil8,
77 Depth32Float,
79 Depth24PlusStencil8,
81}
82
83impl AttachmentFormat {
84 #[must_use]
86 pub fn has_depth(&self) -> bool {
87 matches!(
88 self,
89 Self::Depth32FloatStencil8 | Self::Depth32Float | Self::Depth24PlusStencil8
90 )
91 }
92
93 #[must_use]
95 pub fn has_stencil(&self) -> bool {
96 matches!(self, Self::Depth32FloatStencil8 | Self::Depth24PlusStencil8)
97 }
98
99 #[must_use]
101 pub fn bytes_per_texel(&self) -> u32 {
102 match self {
103 Self::Rgba8Unorm => 4,
104 Self::Rgba16Float => 8,
105 Self::Rgba32Float => 16,
106 Self::Depth32FloatStencil8 => 5,
107 Self::Depth32Float => 4,
108 Self::Depth24PlusStencil8 => 4,
109 }
110 }
111}
112
113#[derive(Debug, Clone)]
115pub struct AttachmentConfig {
116 pub format: AttachmentFormat,
118 pub load_op: LoadOp,
120 pub store_op: StoreOp,
122 pub sample_count: u32,
124 pub label: Option<String>,
126}
127
128impl AttachmentConfig {
129 #[must_use]
131 pub fn new(format: AttachmentFormat, load_op: LoadOp, store_op: StoreOp) -> Self {
132 Self {
133 format,
134 load_op,
135 store_op,
136 sample_count: 1,
137 label: None,
138 }
139 }
140
141 #[must_use]
143 pub fn has_depth(&self) -> bool {
144 self.format.has_depth()
145 }
146
147 #[must_use]
149 pub fn is_multisampled(&self) -> bool {
150 self.sample_count > 1
151 }
152
153 #[must_use]
155 pub fn with_sample_count(mut self, count: u32) -> Self {
156 self.sample_count = count;
157 self
158 }
159
160 #[must_use]
162 pub fn with_label(mut self, label: impl Into<String>) -> Self {
163 self.label = Some(label.into());
164 self
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct RenderPassConfig {
171 pub color_attachments: Vec<AttachmentConfig>,
173 pub depth_attachment: Option<AttachmentConfig>,
175 pub label: Option<String>,
177}
178
179impl RenderPassConfig {
180 #[must_use]
182 pub fn attachment_count(&self) -> usize {
183 self.color_attachments.len() + usize::from(self.depth_attachment.is_some())
184 }
185
186 #[must_use]
188 pub fn has_depth_attachment(&self) -> bool {
189 self.depth_attachment.is_some()
190 }
191
192 #[must_use]
194 pub fn has_uniform_color_format(&self) -> bool {
195 let mut iter = self.color_attachments.iter().map(|a| a.format);
196 match iter.next() {
197 None => true,
198 Some(first) => iter.all(|f| f == first),
199 }
200 }
201}
202
203#[derive(Debug, Default)]
205pub struct RenderPassBuilder {
206 color_attachments: Vec<AttachmentConfig>,
207 depth_attachment: Option<AttachmentConfig>,
208 label: Option<String>,
209}
210
211impl RenderPassBuilder {
212 #[must_use]
214 pub fn new() -> Self {
215 Self::default()
216 }
217
218 #[must_use]
220 pub fn add_color_attachment(mut self, attachment: AttachmentConfig) -> Self {
221 self.color_attachments.push(attachment);
222 self
223 }
224
225 pub fn set_depth_attachment(mut self, attachment: AttachmentConfig) -> Result<Self, String> {
229 if !attachment.has_depth() {
230 return Err(format!(
231 "Format {:?} does not contain a depth component",
232 attachment.format
233 ));
234 }
235 self.depth_attachment = Some(attachment);
236 Ok(self)
237 }
238
239 #[must_use]
241 pub fn with_label(mut self, label: impl Into<String>) -> Self {
242 self.label = Some(label.into());
243 self
244 }
245
246 pub fn build(self) -> Result<RenderPassConfig, String> {
250 if self.color_attachments.is_empty() {
251 return Err("RenderPassConfig requires at least one color attachment".into());
252 }
253 Ok(RenderPassConfig {
254 color_attachments: self.color_attachments,
255 depth_attachment: self.depth_attachment,
256 label: self.label,
257 })
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_load_op_preserves_content_load() {
267 assert!(LoadOp::Load.preserves_content());
268 }
269
270 #[test]
271 fn test_load_op_clear_does_not_preserve() {
272 assert!(!LoadOp::Clear.preserves_content());
273 }
274
275 #[test]
276 fn test_store_op_writes_output_store() {
277 assert!(StoreOp::Store.writes_output());
278 }
279
280 #[test]
281 fn test_store_op_discard_no_write() {
282 assert!(!StoreOp::Discard.writes_output());
283 }
284
285 #[test]
286 fn test_attachment_format_has_depth_depth32() {
287 assert!(AttachmentFormat::Depth32Float.has_depth());
288 }
289
290 #[test]
291 fn test_attachment_format_rgba8_no_depth() {
292 assert!(!AttachmentFormat::Rgba8Unorm.has_depth());
293 }
294
295 #[test]
296 fn test_attachment_format_has_stencil_depth24() {
297 assert!(AttachmentFormat::Depth24PlusStencil8.has_stencil());
298 }
299
300 #[test]
301 fn test_attachment_format_bytes_per_texel_rgba32() {
302 assert_eq!(AttachmentFormat::Rgba32Float.bytes_per_texel(), 16);
303 }
304
305 #[test]
306 fn test_attachment_config_has_depth_true() {
307 let a = AttachmentConfig::new(
308 AttachmentFormat::Depth32Float,
309 LoadOp::Clear,
310 StoreOp::Store,
311 );
312 assert!(a.has_depth());
313 }
314
315 #[test]
316 fn test_attachment_config_not_multisampled_by_default() {
317 let a = AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
318 assert!(!a.is_multisampled());
319 }
320
321 #[test]
322 fn test_attachment_config_with_sample_count() {
323 let a = AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store)
324 .with_sample_count(4);
325 assert!(a.is_multisampled());
326 assert_eq!(a.sample_count, 4);
327 }
328
329 #[test]
330 fn test_render_pass_builder_build_ok() {
331 let color =
332 AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
333 let config = RenderPassBuilder::new()
334 .add_color_attachment(color)
335 .build()
336 .expect("operation should succeed in test");
337 assert_eq!(config.attachment_count(), 1);
338 }
339
340 #[test]
341 fn test_render_pass_builder_build_no_color_err() {
342 let result = RenderPassBuilder::new().build();
343 assert!(result.is_err());
344 }
345
346 #[test]
347 fn test_render_pass_builder_with_depth() {
348 let color =
349 AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
350 let depth = AttachmentConfig::new(
351 AttachmentFormat::Depth32Float,
352 LoadOp::Clear,
353 StoreOp::Discard,
354 );
355 let config = RenderPassBuilder::new()
356 .add_color_attachment(color)
357 .set_depth_attachment(depth)
358 .expect("operation should succeed in test")
359 .build()
360 .expect("operation should succeed in test");
361 assert!(config.has_depth_attachment());
362 assert_eq!(config.attachment_count(), 2);
363 }
364
365 #[test]
366 fn test_render_pass_builder_depth_non_depth_format_err() {
367 let bad =
368 AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
369 let result = RenderPassBuilder::new().set_depth_attachment(bad);
370 assert!(result.is_err());
371 }
372
373 #[test]
374 fn test_render_pass_uniform_color_format_true_single() {
375 let color =
376 AttachmentConfig::new(AttachmentFormat::Rgba8Unorm, LoadOp::Clear, StoreOp::Store);
377 let config = RenderPassBuilder::new()
378 .add_color_attachment(color)
379 .build()
380 .expect("operation should succeed in test");
381 assert!(config.has_uniform_color_format());
382 }
383}