Skip to main content

bevy_render/texture/
texture_attachment.rs

1use super::CachedTexture;
2use crate::render_resource::{TextureFormat, TextureView};
3use alloc::sync::Arc;
4use bevy_color::LinearRgba;
5use core::sync::atomic::{AtomicBool, Ordering};
6use wgpu::{
7    Color as WgpuColor, LoadOp, Operations, RenderPassColorAttachment,
8    RenderPassDepthStencilAttachment, StoreOp, TextureViewDescriptor,
9};
10
11/// A wrapper for a [`CachedTexture`] that is used as a [`RenderPassColorAttachment`].
12#[derive(Clone)]
13pub struct ColorAttachment {
14    pub texture: CachedTexture,
15    pub resolve_target: Option<CachedTexture>,
16    pub previous_frame_texture: Option<CachedTexture>,
17    clear_color: Option<WgpuColor>,
18    is_first_call: Arc<AtomicBool>,
19}
20
21impl ColorAttachment {
22    pub fn new(
23        texture: CachedTexture,
24        resolve_target: Option<CachedTexture>,
25        previous_frame_texture: Option<CachedTexture>,
26        clear_color: Option<WgpuColor>,
27    ) -> Self {
28        Self {
29            texture,
30            resolve_target,
31            previous_frame_texture,
32            clear_color,
33            is_first_call: Arc::new(AtomicBool::new(true)),
34        }
35    }
36
37    /// Get this texture view as an attachment. The attachment will be cleared with a value of
38    /// `clear_color` if this is the first time calling this function, otherwise it will be loaded.
39    ///
40    /// The returned attachment will always have writing enabled (`store: StoreOp::Load`).
41    pub fn get_attachment(&self) -> RenderPassColorAttachment<'_> {
42        if let Some(resolve_target) = self.resolve_target.as_ref() {
43            let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst);
44
45            RenderPassColorAttachment {
46                view: &resolve_target.default_view,
47                depth_slice: None,
48                resolve_target: Some(&self.texture.default_view),
49                ops: Operations {
50                    load: match (self.clear_color, first_call) {
51                        (Some(clear_color), true) => LoadOp::Clear(clear_color),
52                        (None, _) | (Some(_), false) => LoadOp::Load,
53                    },
54                    store: StoreOp::Store,
55                },
56            }
57        } else {
58            self.get_unsampled_attachment()
59        }
60    }
61
62    /// Get this texture view as an attachment, without the resolve target. The attachment will be cleared with
63    /// a value of `clear_color` if this is the first time calling this function, otherwise it will be loaded.
64    ///
65    /// The returned attachment will always have writing enabled (`store: StoreOp::Load`).
66    pub fn get_unsampled_attachment(&self) -> RenderPassColorAttachment<'_> {
67        let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst);
68
69        RenderPassColorAttachment {
70            view: &self.texture.default_view,
71            depth_slice: None,
72            resolve_target: None,
73            ops: Operations {
74                load: match (self.clear_color, first_call) {
75                    (Some(clear_color), true) => LoadOp::Clear(clear_color),
76                    (None, _) | (Some(_), false) => LoadOp::Load,
77                },
78                store: StoreOp::Store,
79            },
80        }
81    }
82
83    pub(crate) fn mark_as_cleared(&self) {
84        self.is_first_call.store(false, Ordering::SeqCst);
85    }
86}
87
88#[derive(Clone)]
89enum DepthStencilData {
90    DepthOnly {
91        depth_clear_value: Option<f32>,
92        depth_is_first_call: Arc<AtomicBool>,
93    },
94    StencilOnly {
95        stencil_clear_value: Option<u32>,
96        stencil_is_first_call: Arc<AtomicBool>,
97    },
98    DepthStencil {
99        depth_clear_value: Option<f32>,
100        stencil_clear_value: Option<u32>,
101        depth_is_first_call: Arc<AtomicBool>,
102        stencil_is_first_call: Arc<AtomicBool>,
103    },
104}
105
106/// Depth and stencil views of a depth texture.
107///
108/// Texture views with single depth or stencil aspect can be read in shaders.
109/// Texture views with all aspect is renderable.
110/// See <https://gpuweb.github.io/gpuweb/#depth-formats>
111/// and <https://gpuweb.github.io/gpuweb/#abstract-opdef-renderable-texture-view>.
112#[derive(Clone)]
113pub enum DepthStencilViews {
114    /// The texture only has depth aspect.
115    DepthOnly { depth_view: TextureView },
116    /// The texture only has stencil aspect.
117    StencilOnly { stencil_view: TextureView },
118    /// The texture has combined depth and stencil.
119    DepthStencil {
120        /// A texture view with both depth and stencil aspects, renderable, but can't be used as a binding resource.
121        combined_view: TextureView,
122        /// A texture view with depth only aspect, sample type `unfilterable-float` or `depth` in shaders, not renderable.
123        depth_view: TextureView,
124        /// A texture view with stencil only aspect, sample type `uint` in shaders, not renderable.
125        stencil_view: TextureView,
126    },
127}
128
129impl DepthStencilViews {
130    /// Return [`DepthStencilViews::DepthStencil::depth_view`] or [`DepthStencilViews::DepthOnly::depth_view`] or `None`.
131    pub fn depth_only_view(&self) -> Option<&TextureView> {
132        match self {
133            DepthStencilViews::DepthStencil { depth_view, .. }
134            | DepthStencilViews::DepthOnly { depth_view } => Some(depth_view),
135            DepthStencilViews::StencilOnly { .. } => None,
136        }
137    }
138
139    /// Return [`DepthStencilViews::DepthStencil::stencil_view`] or [`DepthStencilViews::StencilOnly::stencil_view`] or `None`.
140    pub fn stencil_only_view(&self) -> Option<&TextureView> {
141        match self {
142            DepthStencilViews::DepthOnly { .. } => None,
143            DepthStencilViews::DepthStencil { stencil_view, .. }
144            | DepthStencilViews::StencilOnly { stencil_view } => Some(stencil_view),
145        }
146    }
147
148    /// Return [`DepthStencilViews::DepthStencil::combined_view`] or [`DepthStencilViews::DepthOnly::depth_view`] or [`DepthStencilViews::StencilOnly::stencil_view`].
149    pub fn attachment_view(&self) -> &TextureView {
150        match self {
151            DepthStencilViews::DepthOnly { depth_view } => depth_view,
152            DepthStencilViews::StencilOnly { stencil_view } => stencil_view,
153            DepthStencilViews::DepthStencil { combined_view, .. } => combined_view,
154        }
155    }
156
157    /// Create the appropriate views for a depth-stencil texture based on its texture format.
158    fn from_texture(texture: &CachedTexture) -> Self {
159        let depth_view = || {
160            texture.texture.create_view(&TextureViewDescriptor {
161                aspect: wgpu::TextureAspect::DepthOnly,
162                ..Default::default()
163            })
164        };
165
166        let stencil_view = || {
167            texture.texture.create_view(&TextureViewDescriptor {
168                aspect: wgpu::TextureAspect::StencilOnly,
169                ..Default::default()
170            })
171        };
172
173        match texture.texture.format().channels() {
174            wgpu_types::TextureChannel::DEPTH_STENCIL => DepthStencilViews::DepthStencil {
175                combined_view: texture
176                    .texture
177                    .create_view(&TextureViewDescriptor::default()),
178                depth_view: depth_view(),
179                stencil_view: stencil_view(),
180            },
181            wgpu_types::TextureChannel::DEPTH => DepthStencilViews::DepthOnly {
182                depth_view: depth_view(),
183            },
184            wgpu_types::TextureChannel::STENCIL => DepthStencilViews::StencilOnly {
185                stencil_view: stencil_view(),
186            },
187            _ => {
188                panic!(
189                    "Can't create depth attachment. Texture format is not a depth-stencil format."
190                )
191            }
192        }
193    }
194}
195
196/// A wrapper for a [`CachedTexture`] that is used as a depth [`RenderPassDepthStencilAttachment`].
197#[derive(Clone)]
198pub struct DepthStencilAttachment {
199    pub texture: CachedTexture,
200    pub previous_frame_texture: Option<CachedTexture>,
201    depth_stencil_view_attachment: DepthStencilViewAttachment,
202    pub previous_frame_depth_stencil_views: Option<DepthStencilViews>,
203}
204
205impl DepthStencilAttachment {
206    pub fn depth_stencil_views(&self) -> &DepthStencilViews {
207        &self.depth_stencil_view_attachment.depth_stencil_views
208    }
209
210    pub fn new(
211        texture: CachedTexture,
212        previous_frame_texture: Option<CachedTexture>,
213        depth_clear_value: Option<f32>,
214        stencil_clear_value: Option<u32>,
215    ) -> Self {
216        let depth_stencil_views = DepthStencilViews::from_texture(&texture);
217
218        let previous_frame_depth_stencil_views = previous_frame_texture
219            .as_ref()
220            .map(DepthStencilViews::from_texture);
221
222        Self {
223            texture,
224            previous_frame_texture,
225            depth_stencil_view_attachment: DepthStencilViewAttachment::new(
226                depth_stencil_views,
227                depth_clear_value,
228                stencil_clear_value,
229            ),
230            previous_frame_depth_stencil_views,
231        }
232    }
233
234    /// Get this texture view as an attachment. The attachment will be cleared with a value of
235    /// `clear_value` if this is the first time calling this function with `store` == [`StoreOp::Store`],
236    /// and a clear value was provided, otherwise it will be loaded.
237    pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> {
238        self.depth_stencil_view_attachment.get_attachment(store)
239    }
240
241    /// Marks this depth attachment as unused this frame so that it'll be
242    /// cleared at first use.
243    pub fn prepare_for_new_frame(&self) {
244        self.depth_stencil_view_attachment.prepare_for_new_frame();
245    }
246}
247
248/// A wrapper for a [`TextureView`] that is used as a [`RenderPassDepthStencilAttachment`].
249#[derive(Clone)]
250pub struct DepthStencilViewAttachment {
251    pub depth_stencil_views: DepthStencilViews,
252    depth_stencil_data: DepthStencilData,
253}
254
255impl DepthStencilViewAttachment {
256    pub fn new(
257        depth_stencil_views: DepthStencilViews,
258        depth_clear_value: Option<f32>,
259        stencil_clear_value: Option<u32>,
260    ) -> Self {
261        let depth_stencil_data = match &depth_stencil_views {
262            DepthStencilViews::DepthStencil { .. } => DepthStencilData::DepthStencil {
263                depth_clear_value,
264                stencil_clear_value,
265                depth_is_first_call: Arc::new(AtomicBool::new(depth_clear_value.is_some())),
266                stencil_is_first_call: Arc::new(AtomicBool::new(stencil_clear_value.is_some())),
267            },
268            DepthStencilViews::DepthOnly { .. } => DepthStencilData::DepthOnly {
269                depth_clear_value,
270                depth_is_first_call: Arc::new(AtomicBool::new(depth_clear_value.is_some())),
271            },
272            DepthStencilViews::StencilOnly { .. } => DepthStencilData::StencilOnly {
273                stencil_clear_value,
274                stencil_is_first_call: Arc::new(AtomicBool::new(stencil_clear_value.is_some())),
275            },
276        };
277        Self {
278            depth_stencil_views,
279            depth_stencil_data,
280        }
281    }
282
283    /// Get this texture view as an attachment. The attachment will be cleared with a value of
284    /// `clear_value` if this is the first time calling this function with `store` == [`StoreOp::Store`],
285    /// and a clear value was provided, otherwise it will be loaded.
286    pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> {
287        match (&self.depth_stencil_data, &self.depth_stencil_views) {
288            (
289                DepthStencilData::DepthStencil {
290                    depth_clear_value,
291                    stencil_clear_value,
292                    depth_is_first_call,
293                    stencil_is_first_call,
294                },
295                DepthStencilViews::DepthStencil { combined_view, .. },
296            ) => {
297                let depth_is_first_call =
298                    depth_is_first_call.fetch_and(store != StoreOp::Store, Ordering::Relaxed);
299                let stencil_is_first_call =
300                    stencil_is_first_call.fetch_and(store != StoreOp::Store, Ordering::Relaxed);
301                RenderPassDepthStencilAttachment {
302                    view: combined_view,
303                    depth_ops: Some(Operations {
304                        load: if depth_is_first_call {
305                            // If first_call is true, then a clear value will always have been provided in the constructor
306                            LoadOp::Clear(depth_clear_value.unwrap())
307                        } else {
308                            LoadOp::Load
309                        },
310                        store,
311                    }),
312                    stencil_ops: Some(Operations {
313                        load: if stencil_is_first_call {
314                            // If first_call is true, then a clear value will always have been provided in the constructor
315                            LoadOp::Clear(stencil_clear_value.unwrap())
316                        } else {
317                            LoadOp::Load
318                        },
319                        store,
320                    }),
321                }
322            }
323            (
324                DepthStencilData::DepthOnly {
325                    depth_clear_value,
326                    depth_is_first_call,
327                },
328                DepthStencilViews::DepthOnly { depth_view },
329            ) => {
330                let depth_is_first_call =
331                    depth_is_first_call.fetch_and(store != StoreOp::Store, Ordering::Relaxed);
332                RenderPassDepthStencilAttachment {
333                    view: depth_view,
334                    depth_ops: Some(Operations {
335                        load: if depth_is_first_call {
336                            // If first_call is true, then a clear value will always have been provided in the constructor
337                            LoadOp::Clear(depth_clear_value.unwrap())
338                        } else {
339                            LoadOp::Load
340                        },
341                        store,
342                    }),
343                    stencil_ops: None,
344                }
345            }
346            (
347                DepthStencilData::StencilOnly {
348                    stencil_clear_value,
349                    stencil_is_first_call,
350                },
351                DepthStencilViews::StencilOnly { stencil_view },
352            ) => {
353                let stencil_is_first_call =
354                    stencil_is_first_call.fetch_and(store != StoreOp::Store, Ordering::Relaxed);
355                RenderPassDepthStencilAttachment {
356                    view: stencil_view,
357                    depth_ops: None,
358                    stencil_ops: Some(Operations {
359                        load: if stencil_is_first_call {
360                            // If first_call is true, then a clear value will always have been provided in the constructor
361                            LoadOp::Clear(stencil_clear_value.unwrap())
362                        } else {
363                            LoadOp::Load
364                        },
365                        store,
366                    }),
367                }
368            }
369
370            _ => unreachable!("Mismatched depth stencil data and views"),
371        }
372    }
373
374    /// Marks this depth attachment as unused this frame so that it'll be
375    /// cleared at first use.
376    pub fn prepare_for_new_frame(&self) {
377        match &self.depth_stencil_data {
378            DepthStencilData::DepthStencil {
379                depth_is_first_call,
380                stencil_is_first_call,
381                ..
382            } => {
383                depth_is_first_call.store(true, Ordering::Relaxed);
384                stencil_is_first_call.store(true, Ordering::Relaxed);
385            }
386            DepthStencilData::DepthOnly {
387                depth_is_first_call,
388                ..
389            } => {
390                depth_is_first_call.store(true, Ordering::Relaxed);
391            }
392            DepthStencilData::StencilOnly {
393                stencil_is_first_call,
394                ..
395            } => {
396                stencil_is_first_call.store(true, Ordering::Relaxed);
397            }
398        }
399    }
400}
401
402/// A wrapper for a [`TextureView`] that is used as a [`RenderPassColorAttachment`] for a view
403/// target's final output texture.
404#[derive(Clone)]
405pub struct OutputColorAttachment {
406    pub view: TextureView,
407    pub view_format: TextureFormat,
408    is_first_call: Arc<AtomicBool>,
409}
410
411impl OutputColorAttachment {
412    pub fn new(view: TextureView, view_format: TextureFormat) -> Self {
413        Self {
414            view,
415            view_format,
416            is_first_call: Arc::new(AtomicBool::new(true)),
417        }
418    }
419
420    /// Get this texture view as an attachment. The attachment will be cleared with a value of
421    /// the provided `clear_color` if this is the first time calling this function, otherwise it
422    /// will be loaded.
423    pub fn get_attachment(&self, clear_color: Option<LinearRgba>) -> RenderPassColorAttachment<'_> {
424        let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst);
425
426        RenderPassColorAttachment {
427            view: &self.view,
428            depth_slice: None,
429            resolve_target: None,
430            ops: Operations {
431                load: match (clear_color, first_call) {
432                    (Some(clear_color), true) => LoadOp::Clear(clear_color.into()),
433                    (None, _) | (Some(_), false) => LoadOp::Load,
434                },
435                store: StoreOp::Store,
436            },
437        }
438    }
439
440    /// Returns `true` if this attachment has been written to by a render pass.
441    // we re-use is_first_call atomic to track usage, which assumes that calls to get_attachment
442    // are always consumed by a render pass that writes to the attachment
443    pub fn needs_present(&self) -> bool {
444        !self.is_first_call.load(Ordering::SeqCst)
445    }
446}