use ahash::HashMap;
use astrelis_core::profiling::profile_function;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SamplerKey {
pub address_mode_u: wgpu::AddressMode,
pub address_mode_v: wgpu::AddressMode,
pub address_mode_w: wgpu::AddressMode,
pub mag_filter: wgpu::FilterMode,
pub min_filter: wgpu::FilterMode,
pub mipmap_filter: wgpu::FilterMode,
pub lod_min_clamp: u32, pub lod_max_clamp: u32, pub compare: Option<wgpu::CompareFunction>,
pub anisotropy_clamp: u16,
pub border_color: Option<wgpu::SamplerBorderColor>,
}
impl Hash for SamplerKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.address_mode_u.hash(state);
self.address_mode_v.hash(state);
self.address_mode_w.hash(state);
self.mag_filter.hash(state);
self.min_filter.hash(state);
self.mipmap_filter.hash(state);
self.lod_min_clamp.hash(state);
self.lod_max_clamp.hash(state);
self.compare.hash(state);
self.anisotropy_clamp.hash(state);
self.border_color.hash(state);
}
}
impl SamplerKey {
pub fn nearest_repeat() -> Self {
Self {
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
lod_min_clamp: 0.0f32.to_bits(),
lod_max_clamp: f32::MAX.to_bits(),
compare: None,
anisotropy_clamp: 1,
border_color: None,
}
}
pub fn linear_mirror() -> Self {
Self {
address_mode_u: wgpu::AddressMode::MirrorRepeat,
address_mode_v: wgpu::AddressMode::MirrorRepeat,
address_mode_w: wgpu::AddressMode::MirrorRepeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
lod_min_clamp: 0.0f32.to_bits(),
lod_max_clamp: f32::MAX.to_bits(),
compare: None,
anisotropy_clamp: 1,
border_color: None,
}
}
pub fn nearest_mirror() -> Self {
Self {
address_mode_u: wgpu::AddressMode::MirrorRepeat,
address_mode_v: wgpu::AddressMode::MirrorRepeat,
address_mode_w: wgpu::AddressMode::MirrorRepeat,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
lod_min_clamp: 0.0f32.to_bits(),
lod_max_clamp: f32::MAX.to_bits(),
compare: None,
anisotropy_clamp: 1,
border_color: None,
}
}
pub fn from_descriptor(desc: &wgpu::SamplerDescriptor) -> Self {
Self {
address_mode_u: desc.address_mode_u,
address_mode_v: desc.address_mode_v,
address_mode_w: desc.address_mode_w,
mag_filter: desc.mag_filter,
min_filter: desc.min_filter,
mipmap_filter: desc.mipmap_filter,
lod_min_clamp: desc.lod_min_clamp.to_bits(),
lod_max_clamp: desc.lod_max_clamp.to_bits(),
compare: desc.compare,
anisotropy_clamp: desc.anisotropy_clamp,
border_color: desc.border_color,
}
}
pub fn to_descriptor<'a>(&self, label: Option<&'a str>) -> wgpu::SamplerDescriptor<'a> {
wgpu::SamplerDescriptor {
label,
address_mode_u: self.address_mode_u,
address_mode_v: self.address_mode_v,
address_mode_w: self.address_mode_w,
mag_filter: self.mag_filter,
min_filter: self.min_filter,
mipmap_filter: self.mipmap_filter,
lod_min_clamp: f32::from_bits(self.lod_min_clamp),
lod_max_clamp: f32::from_bits(self.lod_max_clamp),
compare: self.compare,
anisotropy_clamp: self.anisotropy_clamp,
border_color: self.border_color,
}
}
pub fn linear() -> Self {
Self {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
lod_min_clamp: 0.0f32.to_bits(),
lod_max_clamp: f32::MAX.to_bits(),
compare: None,
anisotropy_clamp: 1,
border_color: None,
}
}
pub fn nearest() -> Self {
Self {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
lod_min_clamp: 0.0f32.to_bits(),
lod_max_clamp: f32::MAX.to_bits(),
compare: None,
anisotropy_clamp: 1,
border_color: None,
}
}
pub fn linear_repeat() -> Self {
Self {
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
lod_min_clamp: 0.0f32.to_bits(),
lod_max_clamp: f32::MAX.to_bits(),
compare: None,
anisotropy_clamp: 1,
border_color: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ImageSampling {
#[default]
Linear,
Nearest,
LinearRepeat,
NearestRepeat,
LinearMirror,
NearestMirror,
}
impl ImageSampling {
pub fn to_sampler_key(&self) -> SamplerKey {
match self {
Self::Linear => SamplerKey::linear(),
Self::Nearest => SamplerKey::nearest(),
Self::LinearRepeat => SamplerKey::linear_repeat(),
Self::NearestRepeat => SamplerKey::nearest_repeat(),
Self::LinearMirror => SamplerKey::linear_mirror(),
Self::NearestMirror => SamplerKey::nearest_mirror(),
}
}
}
pub struct SamplerCache {
cache: RwLock<HashMap<SamplerKey, Arc<wgpu::Sampler>>>,
}
impl Default for SamplerCache {
fn default() -> Self {
Self::new()
}
}
impl SamplerCache {
pub fn new() -> Self {
Self {
cache: RwLock::new(HashMap::default()),
}
}
pub fn get_or_create(&self, device: &wgpu::Device, key: SamplerKey) -> Arc<wgpu::Sampler> {
profile_function!();
{
let cache = self
.cache
.read()
.expect("SamplerCache lock poisoned - a thread panicked while accessing the cache");
if let Some(sampler) = cache.get(&key) {
return Arc::clone(sampler);
}
}
let mut cache = self
.cache
.write()
.expect("SamplerCache lock poisoned - a thread panicked while accessing the cache");
if let Some(sampler) = cache.get(&key) {
return Arc::clone(sampler);
}
let descriptor = key.to_descriptor(Some("Cached Sampler"));
let sampler = Arc::new(device.create_sampler(&descriptor));
cache.insert(key, Arc::clone(&sampler));
sampler
}
pub fn get_or_create_from_descriptor(
&self,
device: &wgpu::Device,
descriptor: &wgpu::SamplerDescriptor,
) -> Arc<wgpu::Sampler> {
let key = SamplerKey::from_descriptor(descriptor);
self.get_or_create(device, key)
}
pub fn linear(&self, device: &wgpu::Device) -> Arc<wgpu::Sampler> {
self.get_or_create(device, SamplerKey::linear())
}
pub fn nearest(&self, device: &wgpu::Device) -> Arc<wgpu::Sampler> {
self.get_or_create(device, SamplerKey::nearest())
}
pub fn linear_repeat(&self, device: &wgpu::Device) -> Arc<wgpu::Sampler> {
self.get_or_create(device, SamplerKey::linear_repeat())
}
pub fn nearest_repeat(&self, device: &wgpu::Device) -> Arc<wgpu::Sampler> {
self.get_or_create(device, SamplerKey::nearest_repeat())
}
pub fn linear_mirror(&self, device: &wgpu::Device) -> Arc<wgpu::Sampler> {
self.get_or_create(device, SamplerKey::linear_mirror())
}
pub fn nearest_mirror(&self, device: &wgpu::Device) -> Arc<wgpu::Sampler> {
self.get_or_create(device, SamplerKey::nearest_mirror())
}
pub fn from_sampling(
&self,
device: &wgpu::Device,
sampling: ImageSampling,
) -> Arc<wgpu::Sampler> {
self.get_or_create(device, sampling.to_sampler_key())
}
pub fn len(&self) -> usize {
self.cache.read().expect("SamplerCache lock poisoned").len()
}
pub fn is_empty(&self) -> bool {
self.cache
.read()
.expect("SamplerCache lock poisoned")
.is_empty()
}
pub fn clear(&self) {
self.cache
.write()
.expect("SamplerCache lock poisoned")
.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sampler_key_hash_equality() {
let key1 = SamplerKey::linear();
let key2 = SamplerKey::linear();
let key3 = SamplerKey::nearest();
assert_eq!(key1, key2);
assert_ne!(key1, key3);
use std::collections::hash_map::DefaultHasher;
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
key1.hash(&mut hasher1);
key2.hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish());
}
#[test]
fn test_sampler_key_roundtrip() {
let key = SamplerKey::linear();
let desc = key.to_descriptor(Some("Test"));
let key2 = SamplerKey::from_descriptor(&desc);
assert_eq!(key, key2);
}
}