use crate::texture::{TexParams, Texture};
use enum_map::EnumMap;
pub struct FrameBuffer<T: enum_map::EnumArray<Option<Texture>>> {
targets: EnumMap<T, Option<Texture>>, pub width: u32,
pub height: u32,
pub bind_group_layout: wgpu::BindGroupLayout,
pub bind_group: Option<wgpu::BindGroup>,
}
impl<T: enum_map::EnumArray<Option<Texture>> + std::fmt::Debug> FrameBuffer<T> {
pub(self) fn new(device: &wgpu::Device, targets: EnumMap<T, Option<Texture>>, width: u32, height: u32, create_bind_group: bool) -> Self {
let mut layout_entries = Vec::new();
for (idx, tex) in targets.values().enumerate() {
if let Some(tex) = tex {
let mut sample_type = wgpu::TextureSampleType::Float { filterable: false };
if tex.texture.format().is_depth_stencil_format() {
sample_type = wgpu::TextureSampleType::Depth;
}
layout_entries.push(wgpu::BindGroupLayoutEntry {
binding: u32::try_from(idx).unwrap(),
visibility: wgpu::ShaderStages::FRAGMENT.union(wgpu::ShaderStages::COMPUTE),
ty: wgpu::BindingType::Texture {
multisampled: tex.texture.sample_count() > 1,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type,
},
count: None,
});
}
}
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("GBuffer Bind Group Layout"),
entries: layout_entries.as_slice(),
});
let bind_group = if create_bind_group {
Some(Self::create_bind_group(device, &targets, &layout))
} else {
None
};
Self {
targets,
width,
height,
bind_group_layout: layout,
bind_group,
}
}
pub fn get(&self, target_type: T) -> Option<&Texture> {
let tex = self.targets[target_type].as_ref();
tex
}
pub fn get_mut(&mut self, target_type: T) -> Option<&mut Texture> {
let tex = self.targets[target_type].as_mut();
tex
}
#[allow(clippy::missing_panics_doc)] pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
for tex in self.targets.values_mut() {
let tex = tex.as_mut().unwrap();
let scale_factor = tex.tex_params.scale_factor;
let scaled_width = (width / scale_factor).max(1);
let scaled_height = (height / scale_factor).max(1);
tex.resize(device, scaled_width, scaled_height);
}
self.width = width;
self.height = height;
if self.bind_group.is_some() {
self.bind_group = Some(Self::create_bind_group(device, &self.targets, &self.bind_group_layout));
}
}
fn create_bind_group(device: &wgpu::Device, targets: &EnumMap<T, Option<Texture>>, layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup {
let mut bind_group_entries = Vec::new();
for (idx, tex) in targets.values().enumerate() {
bind_group_entries.push(wgpu::BindGroupEntry {
binding: u32::try_from(idx).unwrap(),
resource: wgpu::BindingResource::TextureView(&tex.as_ref().unwrap().view),
});
}
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout,
entries: bind_group_entries.as_slice(),
label: Some("gbuffer group"),
});
bind_group
}
}
pub struct FrameBufferBuilder<T: enum_map::EnumArray<Option<Texture>>> {
targets: EnumMap<T, Option<Texture>>, pub width: u32,
pub height: u32,
pub create_bind_group: bool,
}
impl<T: enum_map::EnumArray<Option<Texture>> + std::fmt::Debug> FrameBufferBuilder<T> {
pub fn new(width: u32, height: u32) -> Self {
let targets = EnumMap::default();
Self {
targets,
width,
height,
create_bind_group: false,
}
}
#[must_use]
pub fn add_render_target(
mut self,
device: &wgpu::Device,
target_type: T,
format: wgpu::TextureFormat,
usages: wgpu::TextureUsages,
tex_params: TexParams,
) -> Self {
assert_ne!(usages, wgpu::TextureUsages::empty(), "Texture usage cannot be empty");
let scaled_width = self.width / tex_params.scale_factor;
let scaled_height = self.height / tex_params.scale_factor;
let tex = Texture::new(device, scaled_width, scaled_height, format, usages, tex_params);
self.targets[target_type] = Some(tex);
self
}
#[must_use]
pub fn create_bind_group(mut self) -> Self {
self.create_bind_group = true;
self
}
pub fn build(self, device: &wgpu::Device) -> FrameBuffer<T> {
assert!(
self.targets.len() != 0,
"You haven't assigned any render targets. You have to add render targets using add_render_target()"
);
FrameBuffer::new(device, self.targets, self.width, self.height, self.create_bind_group)
}
}