1use iridia_interface::{AddressMode, SamplerState};
16use primase_foundations::{
17 sampling::Address, sampling::Decoded, sampling::Levels, sampling::Subtexel, sampling::Transfer,
18 sampling::footprint_lod,
19};
20
21use crate::{Image, Lod};
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Filtering {
26 BaseBilinear {
29 subtexel: Subtexel,
31 },
32 Point,
35 Bilinear,
38 Trilinear,
40}
41
42impl Filtering {
43 fn level_of_detail(
49 lod: Lod,
50 size: [u32; 2],
51 sampler_bias: f64,
52 footprintless: Option<f32>,
53 ) -> Result<f64, String> {
54 let [w, h] = size;
55 let grad = |g: [[f32; 2]; 2]| g.map(|d| d.map(f64::from));
56 let program = |b: f32| f64::from(b.clamp(-16.0, 15.99));
57 let without = || {
58 footprintless
59 .map(f64::from)
60 .ok_or_else(|| "an implicit level without a quad's footprint".to_owned())
61 };
62 Ok(match lod {
63 Lod::Gradients(g) => footprint_lod(w, h, grad(g), sampler_bias),
64 Lod::GradientsBias(g, b) => footprint_lod(w, h, grad(g), program(b) + sampler_bias),
65 Lod::Level(l) => f64::from(l) + sampler_bias,
66 Lod::Implicit => without()? + sampler_bias,
67 Lod::Bias(b) => without()? + program(b) + sampler_bias,
68 })
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum Unstated {
75 Refuse,
77 Assume(Address),
79}
80
81#[derive(Clone, Copy, Debug, PartialEq)]
84pub struct SamplerUse {
85 pub address: [Address; 2],
87 pub mip_lod_bias: Option<f32>,
89}
90
91impl SamplerUse {
92 pub fn addressed(address: [Address; 2]) -> SamplerUse {
94 SamplerUse {
95 address,
96 mip_lod_bias: None,
97 }
98 }
99
100 pub fn of(state: &SamplerState, unstated: Unstated) -> Result<SamplerUse, String> {
104 let axis = |k: usize| -> Result<Address, String> {
105 let name = ["u", "v"].get(k).copied().unwrap_or("w");
106 Ok(match state.wrap.get(k).copied().flatten() {
107 Some(AddressMode::Repeat) => Address::Wrap,
108 Some(AddressMode::Mirror) => Address::Mirror,
109 Some(AddressMode::Clamp) => Address::Clamp,
110 Some(AddressMode::MirrorOnce) => Address::MirrorOnce,
111 Some(AddressMode::Border) => {
112 return Err(format!(
113 "border addressing along {name}: the source stores no border colour"
114 ));
115 }
116 None => match unstated {
117 Unstated::Assume(a) => a,
118 Unstated::Refuse => {
119 return Err(format!("the sampler states no addressing along {name}"));
120 }
121 },
122 })
123 };
124 if let Some(b) = state.mip_lod_bias
125 && !b.is_finite()
126 {
127 return Err(format!("sampler bias {b} is not finite"));
128 }
129 Ok(SamplerUse {
130 address: [axis(0)?, axis(1)?],
131 mip_lod_bias: state.mip_lod_bias,
132 })
133 }
134
135 pub fn assumed(state: &SamplerState) -> bool {
137 state.wrap[0].is_none() || state.wrap[1].is_none()
138 }
139}
140
141#[derive(Clone, Debug)]
143pub struct SampledTexture {
144 levels: Levels,
145 transfer: Transfer,
146 sampler: SamplerUse,
147 filtering: Filtering,
148 footprintless: Option<f32>,
150 base: Option<Decoded>,
152}
153
154impl SampledTexture {
155 pub fn new(
158 levels: Levels,
159 transfer: Transfer,
160 sampler: SamplerUse,
161 filtering: Filtering,
162 ) -> Result<SampledTexture, String> {
163 if levels.levels.is_empty() {
164 return Err("a texture with no stored level".into());
165 }
166 if let Some(b) = sampler.mip_lod_bias
167 && !b.is_finite()
168 {
169 return Err(format!("sampler bias {b} is not finite"));
170 }
171 let base = match filtering {
172 Filtering::BaseBilinear { .. } => {
173 Some(levels.decoded(0, transfer).map_err(|e| format!("level 0: {e}"))?)
174 }
175 Filtering::Point | Filtering::Bilinear | Filtering::Trilinear => None,
176 };
177 Ok(SampledTexture {
178 levels,
179 transfer,
180 sampler,
181 filtering,
182 footprintless: None,
183 base,
184 })
185 }
186
187 pub fn with_footprintless_level(mut self, level: f32) -> Result<SampledTexture, String> {
191 if !level.is_finite() {
192 return Err(format!("footprintless level {level} is not finite"));
193 }
194 self.footprintless = Some(level);
195 Ok(self)
196 }
197
198 pub fn footprintless_level(&self) -> Option<f32> {
200 self.footprintless
201 }
202
203 pub fn levels(&self) -> &Levels {
205 &self.levels
206 }
207
208 pub fn transfer(&self) -> Transfer {
210 self.transfer
211 }
212
213 pub fn sampler(&self) -> SamplerUse {
215 self.sampler
216 }
217
218 pub fn address(&self) -> [Address; 2] {
220 self.sampler.address
221 }
222
223 pub fn filtering(&self) -> Filtering {
225 self.filtering
226 }
227
228 pub fn dimensions(&self) -> [usize; 2] {
230 [self.levels.width as usize, self.levels.height as usize]
231 }
232
233 pub fn decoded_bytes(&self) -> usize {
235 self.levels
236 .levels
237 .iter()
238 .map(Vec::len)
239 .fold(0usize, usize::saturating_add)
240 .saturating_add(self.base.as_ref().map_or(0, Decoded::decoded_bytes))
241 }
242
243 pub fn unhonoured(&self) -> Option<&'static str> {
246 match (self.filtering, self.sampler.mip_lod_bias) {
247 (Filtering::BaseBilinear { .. }, Some(b)) if b != 0.0 => {
248 Some("stated sampler bias not applied (base level)")
249 }
250 _ => None,
251 }
252 }
253}
254
255impl Image for SampledTexture {
256 fn sample(&self, uv: [f32; 2], lod: Lod) -> Result<[f32; 4], String> {
257 let address = self.sampler.address;
258 let level = || {
259 Filtering::level_of_detail(
260 lod,
261 [self.levels.width, self.levels.height],
262 self.sampler.mip_lod_bias.map_or(0.0, f64::from),
263 self.footprintless,
264 )
265 };
266 let (at, levels, transfer) = ([f64::from(uv[0]), f64::from(uv[1])], &self.levels, self.transfer);
267 let v = match (self.filtering, &self.base) {
268 (Filtering::BaseBilinear { subtexel }, Some(base)) => {
269 return base.bilinear_at(uv, address, subtexel).map_err(|e| e.to_string());
270 }
271 (Filtering::BaseBilinear { .. }, None) => {
272 return Err("a base-level texture whose base was not decoded when it was made".into());
273 }
274 (Filtering::Point, _) => levels
275 .nearest_level(level()?)
276 .and_then(|l| levels.point(l, at, address, transfer)),
277 (Filtering::Bilinear, _) => levels
278 .nearest_level(level()?)
279 .and_then(|l| levels.bilinear(l, at, address, transfer)),
280 (Filtering::Trilinear, _) => levels.trilinear(at, level()?, address, transfer),
281 }
282 .map_err(|e| e.to_string())?;
283 Ok(v.map(crate::narrow))
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 fn state(u: Option<AddressMode>, v: Option<AddressMode>, bias: Option<f32>) -> SamplerState {
292 SamplerState {
293 wrap: [u, v, None],
294 mip_lod_bias: bias,
295 ..SamplerState::default()
296 }
297 }
298
299 fn two_texels() -> Levels {
301 Levels::one(2, 1, vec![0, 0, 0, 255, 255, 255, 255, 255]).unwrap()
302 }
303
304 #[test]
307 fn the_sampler_states_the_addressing() {
308 let clamp = SamplerUse::of(
309 &state(Some(AddressMode::Clamp), Some(AddressMode::Clamp), None),
310 Unstated::Refuse,
311 )
312 .unwrap();
313 assert_eq!(clamp.address, [Address::Clamp; 2]);
314 let base = Filtering::BaseBilinear {
315 subtexel: Subtexel::D3d11,
316 };
317 let clamped = SampledTexture::new(two_texels(), Transfer::Stored, clamp, base).unwrap();
318 let wrapped = SampledTexture::new(
319 two_texels(),
320 Transfer::Stored,
321 SamplerUse::addressed([Address::Wrap; 2]),
322 base,
323 )
324 .unwrap();
325 assert_eq!(clamped.sample([0.0, 0.5], Lod::Implicit).unwrap()[0], 0.0);
327 assert_eq!(wrapped.sample([0.0, 0.5], Lod::Implicit).unwrap()[0], 0.5);
328 assert!(SamplerUse::of(&state(None, Some(AddressMode::Clamp), None), Unstated::Refuse).is_err());
329 let assumed = SamplerUse::of(
330 &state(None, Some(AddressMode::Clamp), None),
331 Unstated::Assume(Address::Wrap),
332 )
333 .unwrap();
334 assert_eq!(assumed.address, [Address::Wrap, Address::Clamp]);
335 assert!(SamplerUse::assumed(&state(None, Some(AddressMode::Clamp), None)));
336 assert!(
337 SamplerUse::of(
338 &state(Some(AddressMode::Border), Some(AddressMode::Clamp), None),
339 Unstated::Refuse
340 )
341 .is_err()
342 );
343 assert!(
344 SamplerUse::of(
345 &state(Some(AddressMode::Clamp), Some(AddressMode::Clamp), Some(f32::NAN)),
346 Unstated::Refuse
347 )
348 .is_err()
349 );
350 }
351
352 #[test]
355 fn a_stated_bias_is_applied_or_named() {
356 let levels = Levels {
358 width: 2,
359 height: 2,
360 levels: vec![[0, 0, 0, 255].repeat(4), vec![255; 4]],
361 };
362 let make = |bias: Option<f32>, filtering| {
363 SampledTexture::new(
364 levels.clone(),
365 Transfer::Stored,
366 SamplerUse {
367 address: [Address::Wrap; 2],
368 mip_lod_bias: bias,
369 },
370 filtering,
371 )
372 .unwrap()
373 };
374 let footprint = Lod::Gradients([[0.25, 0.0], [0.0, 0.25]]);
377 let plain = make(None, Filtering::Trilinear);
378 assert_eq!(plain.sample([0.5, 0.5], footprint).unwrap()[0], 0.0);
379 let biased = make(Some(2.0), Filtering::Trilinear);
380 assert_eq!(biased.sample([0.5, 0.5], footprint).unwrap()[0], 1.0);
381 assert_eq!(biased.unhonoured(), None);
382 assert_eq!(biased.sample([0.5, 0.5], Lod::Level(-1.0)).unwrap()[0], 1.0);
384 assert_eq!(plain.sample([0.5, 0.5], Lod::Level(-1.0)).unwrap()[0], 0.0);
385 assert_eq!(plain.sample([0.5, 0.5], Lod::Level(1.0)).unwrap()[0], 1.0);
386 assert!(plain.sample([0.5, 0.5], Lod::Implicit).is_err());
387 let base = make(
388 Some(2.0),
389 Filtering::BaseBilinear {
390 subtexel: Subtexel::D3d11,
391 },
392 );
393 assert_eq!(base.sample([0.5, 0.5], footprint).unwrap()[0], 0.0);
394 assert_eq!(
395 base.unhonoured(),
396 Some("stated sampler bias not applied (base level)")
397 );
398 }
399
400 #[test]
404 fn nearest_level_filters_and_a_stated_footprintless_level() {
405 let levels = Levels {
407 width: 2,
408 height: 2,
409 levels: vec![[[0, 0, 0, 255], [255; 4]].concat().repeat(2), vec![128; 4]],
410 };
411 let make = |filtering| {
412 SampledTexture::new(
413 levels.clone(),
414 Transfer::Stored,
415 SamplerUse::addressed([Address::Wrap; 2]),
416 filtering,
417 )
418 .unwrap()
419 };
420 let grey = 128.0 / 255.0;
421 let point = make(Filtering::Point);
422 assert_eq!(point.sample([0.25, 0.25], Lod::Level(0.0)).unwrap()[0], 0.0);
423 assert_eq!(point.sample([0.75, 0.25], Lod::Level(0.49)).unwrap()[0], 1.0);
424 assert_eq!(point.sample([0.25, 0.25], Lod::Level(0.5)).unwrap()[0], grey);
425 let bilinear = make(Filtering::Bilinear);
426 assert_eq!(bilinear.sample([0.5, 0.5], Lod::Level(0.4)).unwrap()[0], 0.5);
427 assert_eq!(bilinear.sample([0.5, 0.5], Lod::Level(0.6)).unwrap()[0], grey);
428 assert!(point.sample([0.25, 0.25], Lod::Implicit).is_err());
429 assert!(point.sample([0.25, 0.25], Lod::Bias(0.0)).is_err());
430 let stated = make(Filtering::Point).with_footprintless_level(1.0).unwrap();
431 assert_eq!(stated.footprintless_level(), Some(1.0));
432 assert_eq!(stated.sample([0.25, 0.25], Lod::Implicit).unwrap()[0], grey);
433 assert_eq!(stated.sample([0.25, 0.25], Lod::Bias(-1.0)).unwrap()[0], 0.0);
434 assert!(make(Filtering::Point).with_footprintless_level(f32::NAN).is_err());
435 }
436}