use crate::gpu_stats::FrameStats;
use std::cell::RefCell;
pub(crate) struct OffscreenTarget {
_texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub width: u32,
pub height: u32,
cached_bind_group: RefCell<Option<wgpu::BindGroup>>,
}
impl OffscreenTarget {
fn new(device: &wgpu::Device, format: wgpu::TextureFormat, width: u32, height: u32) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Offscreen Target"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
_texture: texture,
view,
width,
height,
cached_bind_group: RefCell::new(None),
}
}
fn matches_size(&self, width: u32, height: u32) -> bool {
self.width == width && self.height == height
}
pub fn get_or_create_bind_group(
&self,
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
sampler: &wgpu::Sampler,
) -> std::cell::Ref<'_, wgpu::BindGroup> {
{
let mut slot = self.cached_bind_group.borrow_mut();
if slot.is_none() {
*slot = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Offscreen Texture Bind Group (cached)"),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
}));
}
}
std::cell::Ref::map(self.cached_bind_group.borrow(), |opt| {
opt.as_ref().expect("bind group was just populated")
})
}
}
pub(crate) struct OffscreenPool {
available: Vec<OffscreenTarget>,
format: wgpu::TextureFormat,
max_texture_dim: u32,
}
const MAX_POOLED_TARGETS: usize = 16;
impl OffscreenPool {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
Self {
available: Vec::new(),
format,
max_texture_dim: device.limits().max_texture_dimension_2d,
}
}
#[cfg(test)]
fn new_with_limit(format: wgpu::TextureFormat, max_texture_dim: u32) -> Self {
Self {
available: Vec::new(),
format,
max_texture_dim,
}
}
pub fn max_texture_dim(&self) -> u32 {
self.max_texture_dim
}
pub fn pool_size(&self) -> usize {
self.available.len()
}
pub fn estimated_bytes(&self) -> usize {
self.available
.iter()
.map(|t| (t.width as usize) * (t.height as usize) * 4)
.sum()
}
pub fn acquire(
&mut self,
device: &wgpu::Device,
width: u32,
height: u32,
stats: Option<&FrameStats>,
) -> OffscreenTarget {
let width = width.min(self.max_texture_dim).max(1);
let height = height.min(self.max_texture_dim).max(1);
if let Some(idx) = self
.available
.iter()
.position(|t| t.matches_size(width, height))
{
if let Some(s) = stats {
s.record_offscreen_acquire(width, height, false);
}
self.available.swap_remove(idx)
} else {
if let Some(s) = stats {
s.record_offscreen_acquire(width, height, true);
}
OffscreenTarget::new(device, self.format, width, height)
}
}
pub fn release(&mut self, target: OffscreenTarget) {
if self.available.len() < MAX_POOLED_TARGETS {
self.available.push(target);
}
}
pub fn texture_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Effect Texture Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
})
}
pub fn uniform_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Effect Uniform Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pool_starts_empty() {
let pool = OffscreenPool::new_with_limit(wgpu::TextureFormat::Bgra8Unorm, 8192);
assert!(pool.available.is_empty());
assert_eq!(pool.pool_size(), 0);
}
#[test]
fn max_texture_dimension_stored() {
let pool = OffscreenPool::new_with_limit(wgpu::TextureFormat::Bgra8Unorm, 2048);
assert_eq!(pool.max_texture_dim, 2048);
let pool = OffscreenPool::new_with_limit(wgpu::TextureFormat::Bgra8Unorm, 4096);
assert_eq!(pool.max_texture_dim, 4096);
}
}