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(), None)
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 tex.resize(device, width, height);
125 }
126 self.width = width;
127 self.height = height;
128
129 if self.bind_group.is_some() {
130 self.bind_group = Some(Self::create_bind_group(device, &self.targets, &self.bind_group_layout));
131 }
132 }
133
134 //keep as associated function so we can call it from new
135 fn create_bind_group(device: &wgpu::Device, targets: &EnumMap<T, Option<Texture>>, layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup {
136 let mut bind_group_entries = Vec::new();
137 for (idx, tex) in targets.values().enumerate() {
138 //TODO do we need this view when the texture is depth?
139 // wgpu::BindGroupEntry {
140 // binding: 2,
141 // resource: wgpu::BindingResource::TextureView(&depth_tex.create_view(
142 // &wgpu::TextureViewDescriptor {
143 // aspect: wgpu::TextureAspect::DepthOnly,
144 // ..Default::default()
145 // },
146 // )),
147 // }
148 bind_group_entries.push(wgpu::BindGroupEntry {
149 binding: u32::try_from(idx).unwrap(),
150 resource: wgpu::BindingResource::TextureView(&tex.as_ref().unwrap().view),
151 });
152 }
153
154 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
155 layout,
156 entries: bind_group_entries.as_slice(),
157 label: Some("gbuffer group"),
158 });
159 bind_group
160 }
161}
162
163pub struct FrameBufferBuilder<T: enum_map::EnumArray<Option<Texture>>> {
164 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
165 pub width: u32,
166 pub height: u32,
167 pub create_bind_group: bool,
168}
169impl<T: enum_map::EnumArray<Option<Texture>> + std::fmt::Debug> FrameBufferBuilder<T> {
170 pub fn new(width: u32, height: u32) -> Self {
171 let targets = EnumMap::default();
172 Self {
173 targets,
174 width,
175 height,
176 create_bind_group: false,
177 }
178 }
179
180 /// # Panics
181 /// Will panic if texture usage is empty
182 #[must_use]
183 pub fn add_render_target(
184 mut self,
185 device: &wgpu::Device,
186 target_type: T,
187 format: wgpu::TextureFormat,
188 usages: wgpu::TextureUsages,
189 tex_params: TexParams,
190 ) -> Self {
191 assert_ne!(usages, wgpu::TextureUsages::empty(), "Texture usage cannot be empty");
192 // let usages = wgpu::TextureUsages::RENDER_ATTACHMENT
193 // | wgpu::TextureUsages::TEXTURE_BINDING
194 // | additional_usages;
195 let tex = Texture::new(device, self.width, self.height, format, usages, tex_params);
196 self.targets[target_type] = Some(tex);
197 self
198 }
199
200 #[must_use]
201 pub fn create_bind_group(mut self) -> Self {
202 self.create_bind_group = true;
203 self
204 }
205
206 /// # Panics
207 /// Will panic if no targets were added to the gbuffer
208 pub fn build(self, device: &wgpu::Device) -> FrameBuffer<T> {
209 assert!(
210 self.targets.len() != 0,
211 "You haven't assigned any render targets. You have to add render targets using add_render_target()"
212 );
213 FrameBuffer::new(device, self.targets, self.width, self.height, self.create_bind_group)
214 }
215}