1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::collections::HashMap;
use crate::{assets::singleton_asset::LazyResource, wgpu::backend::WGPUBackend};
/// A named, pre-built sampler configuration. Every variant is built once at
/// startup and shared via [`GlobalSamplers`] — look one up with
/// `GlobalSamplers::get(kind)` rather than creating your own `wgpu::Sampler`.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum SamplerKind {
/// Linear filtering, tiling (`Repeat`) address mode, mipmapped.
LinearRepeat,
/// Linear filtering, edge-clamped, mipmapped.
LinearClamp,
/// Linear filtering, edge-clamped, no mipmapping (`lod_min/max_clamp = 0`).
LinearClampNoMip,
/// Nearest-neighbor filtering, `wgpu`'s default (repeat) address mode.
Nearest,
/// Nearest-neighbor filtering, clamped to a border color. On web this
/// falls back to edge-clamping instead (see [`descriptor`](Self::descriptor)'s
/// docs) since WebGPU has no border-color support.
NearestClampBorder,
/// Linear filtering, edge-clamped, with `CompareFunction::Less` — for
/// shadow-map `textureSampleCompare`.
CompareLess,
}
impl SamplerKind {
/// The `wgpu::SamplerDescriptor` this kind builds.
///
/// `NearestClampBorder` is the one variant that isn't identical across
/// platforms: WebGPU doesn't support `ClampToBorder`/a border color at
/// all, so on `wasm32` this silently downgrades to `ClampToEdge` (no
/// border color) instead — sampling outside `[0, 1]` UV will read edge
/// texels on web instead of the border color you'd get natively. If
/// your material depends on the border being visually distinct from the
/// edge, this is a real cross-platform behavior difference to account
/// for, not just an implementation detail.
pub fn descriptor(&self) -> wgpu::SamplerDescriptor<'static> {
match self {
SamplerKind::LinearRepeat => wgpu::SamplerDescriptor {
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::MipmapFilterMode::Linear,
..Default::default()
},
SamplerKind::LinearClamp => wgpu::SamplerDescriptor {
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::MipmapFilterMode::Linear,
..Default::default()
},
SamplerKind::LinearClampNoMip => wgpu::SamplerDescriptor {
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::MipmapFilterMode::Linear,
lod_min_clamp: 0.0,
lod_max_clamp: 0.0,
..Default::default()
},
SamplerKind::Nearest => wgpu::SamplerDescriptor {
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
},
SamplerKind::NearestClampBorder => {
let address_mode = if cfg!(target_arch = "wasm32") {
wgpu::AddressMode::ClampToEdge
} else {
wgpu::AddressMode::ClampToBorder
};
wgpu::SamplerDescriptor {
address_mode_u: address_mode,
address_mode_v: address_mode,
address_mode_w: address_mode,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
border_color: if cfg!(target_arch = "wasm32") {
None
} else {
Some(wgpu::SamplerBorderColor::OpaqueWhite)
},
..Default::default()
}
}
SamplerKind::CompareLess => wgpu::SamplerDescriptor {
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::MipmapFilterMode::Linear,
compare: Some(wgpu::CompareFunction::Less),
..Default::default()
},
}
}
}
const ALL_SAMPLER_KINDS: [SamplerKind; 6] = [
SamplerKind::LinearRepeat,
SamplerKind::LinearClamp,
SamplerKind::LinearClampNoMip,
SamplerKind::Nearest,
SamplerKind::NearestClampBorder,
SamplerKind::CompareLess,
];
/// Every [`SamplerKind`] built once and shared across all materials, rather
/// than each material instance creating its own duplicate `wgpu::Sampler`.
pub struct GlobalSamplers {
samplers: HashMap<SamplerKind, wgpu::Sampler>,
}
impl GlobalSamplers {
/// Look up a shared sampler by kind. Panics if `kind` is somehow missing
/// — every [`SamplerKind`] variant is built eagerly in [`LazyResource::construct`].
pub fn get(&self, kind: SamplerKind) -> &wgpu::Sampler {
self.samplers
.get(&kind)
.expect("GlobalSamplers: all SamplerKind variants are built at construction")
}
}
impl LazyResource<WGPUBackend> for GlobalSamplers {
type Deps<'a> = ();
fn construct<'a>(backend: &WGPUBackend, _deps: &()) -> Option<Self> {
let samplers = ALL_SAMPLER_KINDS
.iter()
.map(|&kind| (kind, backend.device.create_sampler(&kind.descriptor())))
.collect();
Some(Self { samplers })
}
}