use crate::frame::ContextAttachments;
use crate::pipelines::{DEPTH_FORMAT, SAMPLE_COUNT};
pub struct TargetPool {
device: wgpu::Device,
transient_scratch: bool,
frame: u64,
layers: Shelf<LayerTarget>,
copy_textures: Shelf<CopyTexture>,
filters: Shelf<FilterTarget>,
pyramids: Shelf<PyramidTarget>,
attachments: Shelf<Attachments>,
}
const EVICT_AFTER_FRAMES: u64 = 3;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct PoolKey {
size: [u32; 2],
format: wgpu::TextureFormat,
}
struct Shelf<T> {
free: Vec<Pooled<T>>,
taken: Vec<Pooled<T>>,
}
struct Pooled<T> {
key: PoolKey,
last_used: u64,
value: T,
}
impl<T: Clone> Shelf<T> {
fn new() -> Self {
Self {
free: Vec::new(),
taken: Vec::new(),
}
}
fn take(&mut self, key: PoolKey, frame: u64, create: impl FnOnce() -> T) -> T {
let mut entry = match self.free.iter().position(|entry| entry.key == key) {
Some(index) => self.free.swap_remove(index),
None => Pooled {
key,
last_used: frame,
value: create(),
},
};
entry.last_used = frame;
let value = entry.value.clone();
self.taken.push(entry);
value
}
fn end_frame(&mut self, cutoff: u64) {
self.free.append(&mut self.taken);
self.free.retain(|entry| entry.last_used >= cutoff);
}
fn keys(&self) -> impl Iterator<Item = &PoolKey> {
self.free.iter().chain(&self.taken).map(|entry| &entry.key)
}
}
#[derive(Clone)]
pub struct LayerTarget {
pub msaa: wgpu::TextureView,
pub resolve_texture: wgpu::Texture,
pub resolve: wgpu::TextureView,
pub depth: wgpu::TextureView,
}
#[derive(Clone)]
pub struct CopyTexture {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
}
#[derive(Clone)]
pub struct Attachments {
pub msaa: wgpu::TextureView,
pub depth: wgpu::TextureView,
}
impl Attachments {
pub(crate) fn resolving_into(self, resolve: wgpu::TextureView) -> ContextAttachments {
ContextAttachments {
msaa: self.msaa,
depth: self.depth,
resolve,
}
}
}
#[derive(Clone)]
pub struct FilterTarget {
pub view: wgpu::TextureView,
}
#[derive(Clone)]
pub struct PyramidTarget {
pub view: wgpu::TextureView,
pub levels: Vec<wgpu::TextureView>,
}
impl TargetPool {
pub fn new(device: &wgpu::Device) -> Self {
Self {
device: device.clone(),
transient_scratch: transient_scratch_helps(&device.adapter_info()),
frame: 0,
layers: Shelf::new(),
copy_textures: Shelf::new(),
filters: Shelf::new(),
pyramids: Shelf::new(),
attachments: Shelf::new(),
}
}
pub fn take_layer(&mut self, size: [u32; 2], format: wgpu::TextureFormat) -> LayerTarget {
let key = PoolKey { size, format };
let (device, transient) = (&self.device, self.transient_scratch);
self.layers.take(key, self.frame, || {
new_layer(device, size, format, transient)
})
}
pub fn take_attachments(&mut self, size: [u32; 2], format: wgpu::TextureFormat) -> Attachments {
let key = PoolKey { size, format };
let (device, transient) = (&self.device, self.transient_scratch);
self.attachments.take(key, self.frame, || {
new_scratch(device, size, format, transient)
})
}
pub fn take_copy_texture(
&mut self,
size: [u32; 2],
format: wgpu::TextureFormat,
) -> CopyTexture {
let key = PoolKey { size, format };
let device = &self.device;
self.copy_textures
.take(key, self.frame, || new_copy_texture(device, size, format))
}
pub fn take_filter(&mut self, size: [u32; 2], format: wgpu::TextureFormat) -> FilterTarget {
let key = PoolKey { size, format };
let device = &self.device;
self.filters.take(key, self.frame, || FilterTarget {
view: attachment_texture(device, size, format, 1, AttachmentRole::Sampled)
.create_view(&Default::default()),
})
}
pub fn take_pyramid(&mut self, size: [u32; 2], format: wgpu::TextureFormat) -> PyramidTarget {
let key = PoolKey { size, format };
let device = &self.device;
self.pyramids
.take(key, self.frame, || new_pyramid(device, size, format))
}
pub fn end_frame(&mut self) {
self.frame += 1;
let cutoff = self.frame.saturating_sub(EVICT_AFTER_FRAMES);
self.layers.end_frame(cutoff);
self.copy_textures.end_frame(cutoff);
self.filters.end_frame(cutoff);
self.pyramids.end_frame(cutoff);
self.attachments.end_frame(cutoff);
}
}
fn transient_scratch_helps(adapter: &wgpu::AdapterInfo) -> bool {
match adapter.transient_saves_memory {
Some(saves_memory) => saves_memory,
None => {
adapter.backend == wgpu::Backend::BrowserWebGpu && browser_knows_transient_attachments()
}
}
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
fn browser_knows_transient_attachments() -> bool {
let global = js_sys::global();
js_sys::Reflect::get(&global, &"GPUTextureUsage".into())
.ok()
.filter(|usage| usage.is_object())
.and_then(|usage| js_sys::Reflect::has(&usage, &"TRANSIENT_ATTACHMENT".into()).ok())
.unwrap_or(false)
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
fn browser_knows_transient_attachments() -> bool {
false
}
#[derive(Clone, Copy)]
enum AttachmentRole {
Scratch { transient: bool },
Sampled,
}
impl AttachmentRole {
fn usage(self) -> wgpu::TextureUsages {
let role = match self {
AttachmentRole::Scratch { transient: true } => {
wgpu::TextureUsages::TRANSIENT_ATTACHMENT
}
AttachmentRole::Scratch { transient: false } => wgpu::TextureUsages::empty(),
AttachmentRole::Sampled => {
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC
}
};
wgpu::TextureUsages::RENDER_ATTACHMENT | role
}
}
fn new_layer(
device: &wgpu::Device,
size: [u32; 2],
format: wgpu::TextureFormat,
transient_scratch: bool,
) -> LayerTarget {
let scratch = new_scratch(device, size, format, transient_scratch);
let resolve = attachment_texture(device, size, format, 1, AttachmentRole::Sampled);
LayerTarget {
msaa: scratch.msaa,
resolve: resolve.create_view(&Default::default()),
resolve_texture: resolve,
depth: scratch.depth,
}
}
fn new_scratch(
device: &wgpu::Device,
size: [u32; 2],
format: wgpu::TextureFormat,
transient: bool,
) -> Attachments {
let role = AttachmentRole::Scratch { transient };
let msaa = attachment_texture(device, size, format, SAMPLE_COUNT, role);
let depth = attachment_texture(device, size, DEPTH_FORMAT, SAMPLE_COUNT, role);
Attachments {
msaa: msaa.create_view(&Default::default()),
depth: depth.create_view(&Default::default()),
}
}
fn new_copy_texture(
device: &wgpu::Device,
size: [u32; 2],
format: wgpu::TextureFormat,
) -> CopyTexture {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("valo.copy"),
size: wgpu::Extent3d {
width: size[0],
height: size[1],
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
CopyTexture {
view: texture.create_view(&Default::default()),
texture,
}
}
fn new_pyramid(
device: &wgpu::Device,
size: [u32; 2],
format: wgpu::TextureFormat,
) -> PyramidTarget {
let mip_level_count = crate::mips::full_mip_count(size);
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("valo.pyramid"),
size: wgpu::Extent3d {
width: size[0],
height: size[1],
depth_or_array_layers: 1,
},
mip_level_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let levels = (0..mip_level_count)
.map(|level| {
texture.create_view(&wgpu::TextureViewDescriptor {
base_mip_level: level,
mip_level_count: Some(1),
..Default::default()
})
})
.collect();
PyramidTarget {
view: texture.create_view(&Default::default()),
levels,
}
}
fn attachment_texture(
device: &wgpu::Device,
size: [u32; 2],
format: wgpu::TextureFormat,
samples: u32,
role: AttachmentRole,
) -> wgpu::Texture {
device.create_texture(&wgpu::TextureDescriptor {
label: Some("valo.pooled"),
size: wgpu::Extent3d {
width: size[0],
height: size[1],
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: samples,
dimension: wgpu::TextureDimension::D2,
format,
usage: role.usage(),
view_formats: &[],
})
}
impl TargetPool {
pub(crate) fn report(&self) -> crate::PoolReport {
const FLAT_BPP: u64 = 4;
let scratch_bpp = if self.transient_scratch {
0
} else {
u64::from(SAMPLE_COUNT) * (4 + 4)
};
let layer_bpp = FLAT_BPP + scratch_bpp;
let mut count = 0u32;
let mut bytes = 0u64;
let mut add = |key: &PoolKey, bpp: u64| {
count += 1;
bytes += key.size[0] as u64 * key.size[1] as u64 * bpp;
};
self.layers.keys().for_each(|key| add(key, layer_bpp));
self.copy_textures.keys().for_each(|key| add(key, FLAT_BPP));
self.filters.keys().for_each(|key| add(key, FLAT_BPP));
self.pyramids
.keys()
.for_each(|key| add(key, FLAT_BPP * 4 / 3));
self.attachments
.keys()
.for_each(|key| add(key, scratch_bpp));
crate::PoolReport { count, bytes }
}
}