bevy_render/texture/
texture_attachment.rs

1use super::CachedTexture;
2use crate::render_resource::{TextureFormat, TextureView};
3use bevy_color::LinearRgba;
4use std::sync::{
5    atomic::{AtomicBool, Ordering},
6    Arc,
7};
8use wgpu::{
9    LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp,
10};
11
12/// A wrapper for a [`CachedTexture`] that is used as a [`RenderPassColorAttachment`].
13#[derive(Clone)]
14pub struct ColorAttachment {
15    pub texture: CachedTexture,
16    pub resolve_target: Option<CachedTexture>,
17    clear_color: Option<LinearRgba>,
18    is_first_call: Arc<AtomicBool>,
19}
20
21impl ColorAttachment {
22    pub fn new(
23        texture: CachedTexture,
24        resolve_target: Option<CachedTexture>,
25        clear_color: Option<LinearRgba>,
26    ) -> Self {
27        Self {
28            texture,
29            resolve_target,
30            clear_color,
31            is_first_call: Arc::new(AtomicBool::new(true)),
32        }
33    }
34
35    /// Get this texture view as an attachment. The attachment will be cleared with a value of
36    /// `clear_color` if this is the first time calling this function, otherwise it will be loaded.
37    ///
38    /// The returned attachment will always have writing enabled (`store: StoreOp::Load`).
39    pub fn get_attachment(&self) -> RenderPassColorAttachment {
40        if let Some(resolve_target) = self.resolve_target.as_ref() {
41            let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst);
42
43            RenderPassColorAttachment {
44                view: &resolve_target.default_view,
45                resolve_target: Some(&self.texture.default_view),
46                ops: Operations {
47                    load: match (self.clear_color, first_call) {
48                        (Some(clear_color), true) => LoadOp::Clear(clear_color.into()),
49                        (None, _) | (Some(_), false) => LoadOp::Load,
50                    },
51                    store: StoreOp::Store,
52                },
53            }
54        } else {
55            self.get_unsampled_attachment()
56        }
57    }
58
59    /// Get this texture view as an attachment, without the resolve target. The attachment will be cleared with
60    /// a value of `clear_color` if this is the first time calling this function, otherwise it will be loaded.
61    ///
62    /// The returned attachment will always have writing enabled (`store: StoreOp::Load`).
63    pub fn get_unsampled_attachment(&self) -> RenderPassColorAttachment {
64        let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst);
65
66        RenderPassColorAttachment {
67            view: &self.texture.default_view,
68            resolve_target: None,
69            ops: Operations {
70                load: match (self.clear_color, first_call) {
71                    (Some(clear_color), true) => LoadOp::Clear(clear_color.into()),
72                    (None, _) | (Some(_), false) => LoadOp::Load,
73                },
74                store: StoreOp::Store,
75            },
76        }
77    }
78
79    pub(crate) fn mark_as_cleared(&self) {
80        self.is_first_call.store(false, Ordering::SeqCst);
81    }
82}
83
84/// A wrapper for a [`TextureView`] that is used as a depth-only [`RenderPassDepthStencilAttachment`].
85pub struct DepthAttachment {
86    pub view: TextureView,
87    clear_value: Option<f32>,
88    is_first_call: Arc<AtomicBool>,
89}
90
91impl DepthAttachment {
92    pub fn new(view: TextureView, clear_value: Option<f32>) -> Self {
93        Self {
94            view,
95            clear_value,
96            is_first_call: Arc::new(AtomicBool::new(clear_value.is_some())),
97        }
98    }
99
100    /// Get this texture view as an attachment. The attachment will be cleared with a value of
101    /// `clear_value` if this is the first time calling this function with `store` == [`StoreOp::Store`],
102    /// and a clear value was provided, otherwise it will be loaded.
103    pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment {
104        let first_call = self
105            .is_first_call
106            .fetch_and(store != StoreOp::Store, Ordering::SeqCst);
107
108        RenderPassDepthStencilAttachment {
109            view: &self.view,
110            depth_ops: Some(Operations {
111                load: if first_call {
112                    // If first_call is true, then a clear value will always have been provided in the constructor
113                    LoadOp::Clear(self.clear_value.unwrap())
114                } else {
115                    LoadOp::Load
116                },
117                store,
118            }),
119            stencil_ops: None,
120        }
121    }
122}
123
124/// A wrapper for a [`TextureView`] that is used as a [`RenderPassColorAttachment`] for a view
125/// target's final output texture.
126#[derive(Clone)]
127pub struct OutputColorAttachment {
128    pub view: TextureView,
129    pub format: TextureFormat,
130    is_first_call: Arc<AtomicBool>,
131}
132
133impl OutputColorAttachment {
134    pub fn new(view: TextureView, format: TextureFormat) -> Self {
135        Self {
136            view,
137            format,
138            is_first_call: Arc::new(AtomicBool::new(true)),
139        }
140    }
141
142    /// Get this texture view as an attachment. The attachment will be cleared with a value of
143    /// the provided `clear_color` if this is the first time calling this function, otherwise it
144    /// will be loaded.
145    pub fn get_attachment(&self, clear_color: Option<LinearRgba>) -> RenderPassColorAttachment {
146        let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst);
147
148        RenderPassColorAttachment {
149            view: &self.view,
150            resolve_target: None,
151            ops: Operations {
152                load: match (clear_color, first_call) {
153                    (Some(clear_color), true) => LoadOp::Clear(clear_color.into()),
154                    (None, _) | (Some(_), false) => LoadOp::Load,
155                },
156                store: StoreOp::Store,
157            },
158        }
159    }
160}