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,
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/// A wrapper for a [`TextureView`] that is used as a depth-only [`RenderPassDepthStencilAttachment`].
89#[derive(Clone)]
90pub struct DepthAttachment {
91    pub view: TextureView,
92    clear_value: Option<f32>,
93    is_first_call: Arc<AtomicBool>,
94}
95
96impl DepthAttachment {
97    pub fn new(view: TextureView, clear_value: Option<f32>) -> Self {
98        Self {
99            view,
100            clear_value,
101            is_first_call: Arc::new(AtomicBool::new(clear_value.is_some())),
102        }
103    }
104
105    /// Get this texture view as an attachment. The attachment will be cleared with a value of
106    /// `clear_value` if this is the first time calling this function with `store` == [`StoreOp::Store`],
107    /// and a clear value was provided, otherwise it will be loaded.
108    pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> {
109        let first_call = self
110            .is_first_call
111            .fetch_and(store != StoreOp::Store, Ordering::Relaxed);
112
113        RenderPassDepthStencilAttachment {
114            view: &self.view,
115            depth_ops: Some(Operations {
116                load: if first_call {
117                    // If first_call is true, then a clear value will always have been provided in the constructor
118                    LoadOp::Clear(self.clear_value.unwrap())
119                } else {
120                    LoadOp::Load
121                },
122                store,
123            }),
124            stencil_ops: None,
125        }
126    }
127
128    /// Marks this depth attachment as unused this frame so that it'll be
129    /// cleared at first use.
130    pub fn prepare_for_new_frame(&self) {
131        self.is_first_call.store(true, Ordering::Relaxed);
132    }
133}
134
135/// A wrapper for a [`TextureView`] that is used as a [`RenderPassColorAttachment`] for a view
136/// target's final output texture.
137#[derive(Clone)]
138pub struct OutputColorAttachment {
139    pub view: TextureView,
140    pub view_format: TextureFormat,
141    is_first_call: Arc<AtomicBool>,
142}
143
144impl OutputColorAttachment {
145    pub fn new(view: TextureView, view_format: TextureFormat) -> Self {
146        Self {
147            view,
148            view_format,
149            is_first_call: Arc::new(AtomicBool::new(true)),
150        }
151    }
152
153    /// Get this texture view as an attachment. The attachment will be cleared with a value of
154    /// the provided `clear_color` if this is the first time calling this function, otherwise it
155    /// will be loaded.
156    pub fn get_attachment(&self, clear_color: Option<LinearRgba>) -> RenderPassColorAttachment<'_> {
157        let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst);
158
159        RenderPassColorAttachment {
160            view: &self.view,
161            depth_slice: None,
162            resolve_target: None,
163            ops: Operations {
164                load: match (clear_color, first_call) {
165                    (Some(clear_color), true) => LoadOp::Clear(clear_color.into()),
166                    (None, _) | (Some(_), false) => LoadOp::Load,
167                },
168                store: StoreOp::Store,
169            },
170        }
171    }
172
173    /// Returns `true` if this attachment has been written to by a render pass.
174    // we re-use is_first_call atomic to track usage, which assumes that calls to get_attachment
175    // are always consumed by a render pass that writes to the attachment
176    pub fn needs_present(&self) -> bool {
177        !self.is_first_call.load(Ordering::SeqCst)
178    }
179}