easy_wgpu/
framebuffer.rs

1use crate::texture::{TexParams, Texture};
2use enum_map::EnumMap;
3
4/// Contains all the render targets used inside a framebuffer and their formats.
5/// Uses [enum-map](https://docs.rs/enum-map/latest/enum_map/struct.EnumMap.html) to create render targets that you can later reference using the enum
6/// # Example
7/// ```no_run
8/// use easy_wgpu::{framebuffer::FrameBufferBuilder, texture::TexParams};
9/// use enum_map::{Enum, EnumMap};
10/// use pollster::FutureExt;
11/// use wgpu;
12///
13/// let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
14/// let adapter = instance
15///     .request_adapter(&wgpu::RequestAdapterOptions::default())
16///     .block_on()
17///     .unwrap();
18/// let (device, queue) = adapter
19///     .request_device(&wgpu::DeviceDescriptor::default())
20///     .block_on()
21///     .unwrap();
22///
23/// #[derive(Debug, Enum)]
24/// pub enum Target {
25///     Albedo,
26///     Depth,
27/// }
28/// let fb_builder = FrameBufferBuilder::<Target>::new(128, 128);
29/// let framebuffer = fb_builder
30///     .add_render_target(
31///         &device,
32///         Target::Albedo,
33///         wgpu::TextureFormat::Rgba8Unorm,
34///         wgpu::TextureUsages::RENDER_ATTACHMENT,
35///         TexParams::default(),
36///     )
37///     .add_render_target(
38///         &device,
39///         Target::Depth,
40///         wgpu::TextureFormat::Depth32Float,
41///         wgpu::TextureUsages::RENDER_ATTACHMENT,
42///         TexParams::default(),
43///     )
44///     .build(&device);
45/// //access gbuffer textures using the enum
46/// let tex = framebuffer.get(Target::Albedo);
47/// ```
48pub struct FrameBuffer<T: enum_map::EnumArray<Option<Texture>>> {
49    targets: EnumMap<T, Option<Texture>>, //acts like a map of keys and values where the keys are elements of the enum and values are the textures
50    pub width: u32,
51    pub height: u32,
52    pub bind_group_layout: wgpu::BindGroupLayout,
53    //TODO binding might not always be necesary if we don't want to read from the textures inside a shader. However for a gbuffer it might be
54    // necessary
55    pub bind_group: Option<wgpu::BindGroup>,
56    // pub requires_clear: bool, //the first time we write to the gbuffer we do loadop clear, all the subsequent times we write within the same frame
57    // we will just write to gbuffer without clearing
58}
59impl<T: enum_map::EnumArray<Option<Texture>> + std::fmt::Debug> FrameBuffer<T> {
60    // dissallow to call new on a gbuffer outside of this module
61    pub(self) fn new(device: &wgpu::Device, targets: EnumMap<T, Option<Texture>>, width: u32, height: u32, create_bind_group: bool) -> Self {
62        //bind layout
63        let mut layout_entries = Vec::new();
64        for (idx, tex) in targets.values().enumerate() {
65            if let Some(tex) = tex {
66                //creates changes the sample type to depth if it was created with depth format
67                let mut sample_type = wgpu::TextureSampleType::Float { filterable: false };
68                if tex.texture.format().is_depth_stencil_format() {
69                    sample_type = wgpu::TextureSampleType::Depth;
70                }
71
72                layout_entries.push(wgpu::BindGroupLayoutEntry {
73                    binding: u32::try_from(idx).unwrap(),
74                    visibility: wgpu::ShaderStages::FRAGMENT.union(wgpu::ShaderStages::COMPUTE),
75                    ty: wgpu::BindingType::Texture {
76                        multisampled: tex.texture.sample_count() > 1,
77                        view_dimension: wgpu::TextureViewDimension::D2,
78                        sample_type,
79                    },
80                    count: None,
81                });
82            }
83        }
84
85        let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
86            label: Some("GBuffer Bind Group Layout"),
87            entries: layout_entries.as_slice(),
88        });
89
90        //we keep this as a associated function as we can call it from within new
91
92        let bind_group = if create_bind_group {
93            Some(Self::create_bind_group(device, &targets, &layout))
94        } else {
95            None
96        };
97
98        Self {
99            targets,
100            width,
101            height,
102            // requires_clear: true,
103            bind_group_layout: layout,
104            bind_group,
105        }
106    }
107
108    pub fn get(&self, target_type: T) -> Option<&Texture> {
109        let tex = self.targets[target_type].as_ref();
110        tex
111    }
112
113    pub fn get_mut(&mut self, target_type: T) -> Option<&mut Texture> {
114        let tex = self.targets[target_type].as_mut();
115        tex
116    }
117
118    #[allow(clippy::missing_panics_doc)] //really should not panic
119    pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
120        // resize all textures
121        // recreate the bind_group
122        for tex in self.targets.values_mut() {
123            let tex = tex.as_mut().unwrap();
124
125            let scale_factor = tex.tex_params.scale_factor;
126            let scaled_width = (width / scale_factor).max(1);
127            let scaled_height = (height / scale_factor).max(1);
128            tex.resize(device, scaled_width, scaled_height);
129        }
130        self.width = width;
131        self.height = height;
132
133        if self.bind_group.is_some() {
134            self.bind_group = Some(Self::create_bind_group(device, &self.targets, &self.bind_group_layout));
135        }
136    }
137
138    //keep as associated function so we can call it from new
139    fn create_bind_group(device: &wgpu::Device, targets: &EnumMap<T, Option<Texture>>, layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup {
140        let mut bind_group_entries = Vec::new();
141        for (idx, tex) in targets.values().enumerate() {
142            //TODO do we need this view when the texture is depth?
143            // wgpu::BindGroupEntry {
144            //         binding: 2,
145            //         resource: wgpu::BindingResource::TextureView(&depth_tex.create_view(
146            //             &wgpu::TextureViewDescriptor {
147            //                 aspect: wgpu::TextureAspect::DepthOnly,
148            //                 ..Default::default()
149            //             },
150            //         )),
151            //     }
152            bind_group_entries.push(wgpu::BindGroupEntry {
153                binding: u32::try_from(idx).unwrap(),
154                resource: wgpu::BindingResource::TextureView(&tex.as_ref().unwrap().view),
155            });
156        }
157
158        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
159            layout,
160            entries: bind_group_entries.as_slice(),
161            label: Some("gbuffer group"),
162        });
163        bind_group
164    }
165}
166
167pub struct FrameBufferBuilder<T: enum_map::EnumArray<Option<Texture>>> {
168    targets: EnumMap<T, Option<Texture>>, //acts like a map of keys and values where the keys are elements of the enum and values are the textures
169    pub width: u32,
170    pub height: u32,
171    pub create_bind_group: bool,
172}
173impl<T: enum_map::EnumArray<Option<Texture>> + std::fmt::Debug> FrameBufferBuilder<T> {
174    pub fn new(width: u32, height: u32) -> Self {
175        let targets = EnumMap::default();
176        Self {
177            targets,
178            width,
179            height,
180            create_bind_group: false,
181        }
182    }
183
184    /// # Panics
185    /// Will panic if texture usage is empty
186    #[must_use]
187    pub fn add_render_target(
188        mut self,
189        device: &wgpu::Device,
190        target_type: T,
191        format: wgpu::TextureFormat,
192        usages: wgpu::TextureUsages,
193        tex_params: TexParams,
194    ) -> Self {
195        assert_ne!(usages, wgpu::TextureUsages::empty(), "Texture usage cannot be empty");
196
197        let scaled_width = self.width / tex_params.scale_factor;
198        let scaled_height = self.height / tex_params.scale_factor;
199
200        let tex = Texture::new(device, scaled_width, scaled_height, format, usages, tex_params);
201        self.targets[target_type] = Some(tex);
202        self
203    }
204
205    #[must_use]
206    pub fn create_bind_group(mut self) -> Self {
207        self.create_bind_group = true;
208        self
209    }
210
211    /// # Panics
212    /// Will panic if no targets were added to the gbuffer
213    pub fn build(self, device: &wgpu::Device) -> FrameBuffer<T> {
214        assert!(
215            self.targets.len() != 0,
216            "You haven't assigned any render targets. You have to add render targets using add_render_target()"
217        );
218        FrameBuffer::new(device, self.targets, self.width, self.height, self.create_bind_group)
219    }
220}