Skip to main content

pebble/wgpu/
samplers.rs

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