1use std::collections::BTreeMap;
23use std::sync::Arc;
24
25use primase_foundations::{sampling::Address, sampling::Decoded, sampling::Subtexel};
26
27use crate::{Dim, ImageLoad, ImageQuery, ImageSample, Load, Lod, QueryKind, Sample, Sampler};
28
29pub trait Image: Send + Sync + std::fmt::Debug {
32 fn sample(&self, uv: [f32; 2], lod: Lod) -> Result<[f32; 4], String>;
34}
35
36#[derive(Clone, Debug)]
38pub enum View {
39 Uniform([f32; 4]),
41 Constant([f32; 4]),
44 Texture2d(Decoded),
46 Texture3d(Decoded),
48 Image(Arc<dyn Image>),
50 Typed(Arc<[[u32; 4]]>),
52 Structured {
54 stride: u32,
56 words: Vec<u32>,
58 },
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum ViewFilter {
64 Point,
66 Linear {
68 subtexel: Subtexel,
70 },
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub struct ViewSampler {
76 pub filter: ViewFilter,
78 pub address: [Address; 3],
80}
81
82#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
85pub enum Absent {
86 #[default]
89 Zero,
90 Refuse,
92}
93
94#[derive(Clone, Debug, Default)]
97pub struct Views {
98 pub views: BTreeMap<u32, View>,
100 pub samplers: BTreeMap<u32, ViewSampler>,
102 pub absent: Absent,
104}
105
106impl Views {
107 pub fn refusing(views: BTreeMap<u32, View>) -> Self {
109 Views {
110 views,
111 samplers: BTreeMap::new(),
112 absent: Absent::Refuse,
113 }
114 }
115
116 fn sampler(&self, slot: u32) -> Result<ViewSampler, String> {
117 self.samplers
118 .get(&slot)
119 .copied()
120 .ok_or_else(|| format!("s{slot}: no sampler state bound"))
121 }
122
123 fn absent<T: Default>(&self, why: impl FnOnce() -> String) -> Result<T, String> {
125 match self.absent {
126 Absent::Zero => Ok(T::default()),
127 Absent::Refuse => Err(why()),
128 }
129 }
130
131 fn filtered(
133 &self,
134 texture: u32,
135 sampler: u32,
136 dim: Dim,
137 coord: [f32; 3],
138 lod: Lod,
139 ) -> Result<[f32; 4], String> {
140 let state = || self.sampler(sampler);
141 let error = |e: primase_foundations::sampling::Error| format!("t{texture}: {e}");
142 match (self.views.get(&texture), dim) {
143 (None, _) => self.absent(|| format!("t{texture} has no texture")),
144 (Some(View::Uniform(c) | View::Constant(c)), _) => Ok(*c),
145 (Some(View::Image(image)), Dim::D2) => image.sample([coord[0], coord[1]], lod),
146 (Some(View::Texture2d(image)), Dim::D2) => {
147 let state = state()?;
148 let (uv, address) = ([coord[0], coord[1]], [state.address[0], state.address[1]]);
149 match state.filter {
150 ViewFilter::Point => image.point(uv, address),
151 ViewFilter::Linear { subtexel } => image.bilinear_at(uv, address, subtexel),
152 }
153 .map_err(error)
154 }
155 (Some(View::Texture3d(image)), Dim::D3) => {
156 let state = state()?;
157 match state.filter {
158 ViewFilter::Point => image.point_3d(coord, state.address),
159 ViewFilter::Linear { subtexel } => image.linear_3d(coord, state.address, subtexel),
160 }
161 .map_err(error)
162 }
163 (Some(View::Typed(_) | View::Structured { .. }), _) => {
164 Err(format!("t{texture}: a sample of a buffer"))
165 }
166 (Some(View::Texture2d(_) | View::Texture3d(_) | View::Image(_)), dim) => Err(format!(
167 "t{texture}: a {dim:?} sample of a view of another dimension"
168 )),
169 }
170 }
171}
172
173impl Sampler for &Views {
174 fn sample(&mut self, r: Sample) -> Result<[f32; 4], String> {
175 match self.views.get(&r.texture) {
176 Some(View::Texture2d(_)) if r.layer.is_some_and(|l| l.round_ties_even() != 0.0) => {
178 return Err(format!("t{}: layer {:?} of a 2D view", r.texture, r.layer));
179 }
180 Some(View::Image(_)) if r.layer.is_some() => {
181 return Err(format!("t{}: an array layer of an image", r.texture));
182 }
183 _ => {}
184 }
185 self.filtered(r.texture, r.sampler, Dim::D2, [r.uv[0], r.uv[1], 0.0], r.lod)
186 }
187
188 fn sample_image(&mut self, r: ImageSample<u32>) -> Result<[f32; 4], String> {
189 if r.gather.is_some() || r.depth_ref.is_some() || r.depth || r.offset != [0; 3] {
190 return Err(format!(
191 "t{}: a gather, a comparison or a texel offset, which these views do not answer",
192 r.texture
193 ));
194 }
195 match r.dim {
196 Dim::D2 => self.sample(Sample {
197 texture: r.texture,
198 sampler: r.sampler,
199 uv: [r.coord[0], r.coord[1]],
200 layer: r.layer,
201 lod: Lod::from(r.lod),
202 }),
203 Dim::D3 => self.filtered(r.texture, r.sampler, Dim::D3, r.coord, Lod::from(r.lod)),
204 dim => Err(format!(
205 "t{}: a {dim:?} sample, which these views do not answer",
206 r.texture
207 )),
208 }
209 }
210
211 fn load(&mut self, r: Load) -> Result<[u32; 4], String> {
212 let [x, y, z] = r.texel.map(i64::from);
213 let bits = |c: [f32; 4]| c.map(f32::to_bits);
214 let outside = || {
215 format!(
216 "t{}: texel {:?} of level {} outside the view",
217 r.texture, r.texel, r.mip
218 )
219 };
220 match self.views.get(&r.texture) {
221 None => self.absent(|| format!("t{} has no texture", r.texture)),
222 Some(View::Uniform(c)) if (x, y, z, r.mip) == (0, 0, 0, 0) => Ok(bits(*c)),
223 Some(View::Uniform(_)) => self.absent(outside),
224 Some(View::Constant(c)) => Ok(bits(*c)),
225 Some(View::Texture2d(image) | View::Texture3d(image)) if r.mip == 0 => {
226 match image.texel(x, y, z) {
227 Some(texel) => Ok(bits(texel)),
228 None => self.absent(outside),
229 }
230 }
231 Some(View::Texture2d(_) | View::Texture3d(_)) => self.absent(outside),
232 Some(View::Image(_)) => Err(format!("t{}: a load of an image", r.texture)),
233 Some(View::Typed(elements)) => match usize::try_from(x).ok().and_then(|i| elements.get(i)) {
235 Some(element) => Ok(*element),
236 None => {
237 self.absent(|| format!("t{}: element {} of {}", r.texture, r.texel[0], elements.len()))
238 }
239 },
240 Some(View::Structured { .. }) => {
241 Err(format!("t{}: a texel load of a structured buffer", r.texture))
242 }
243 }
244 }
245
246 fn load_image(&mut self, r: ImageLoad<u32>) -> Result<[u32; 4], String> {
247 if r.storage || r.sample.is_some() {
248 return Err(format!(
249 "t{}: an unordered-access or multisample load, which these views do not answer",
250 r.texture
251 ));
252 }
253 if !matches!(r.dim, Dim::D2 | Dim::D3) {
254 return Err(format!(
255 "t{}: a {:?} load, which these views do not answer",
256 r.texture, r.dim
257 ));
258 }
259 self.load(Load {
260 texture: r.texture,
261 texel: [r.texel[0], r.texel[1], r.layer.unwrap_or(r.texel[2])],
262 mip: r.mip.unwrap_or(0),
263 })
264 }
265
266 fn structured(&mut self, slot: u32, element: u32, offset: u32) -> Result<u32, String> {
267 match self.views.get(&slot) {
268 None => self.absent(|| format!("t{slot} has no texture")),
269 Some(View::Structured { stride, .. }) if offset.saturating_add(4) > *stride => Err(format!(
270 "t{slot}: a structured read at byte {offset} of an element of {stride} bytes (undefined)"
271 )),
272 Some(View::Structured { stride, words }) => {
273 let byte = u64::from(element)
274 .saturating_mul(u64::from(*stride))
275 .saturating_add(u64::from(offset));
276 match usize::try_from(byte / 4).ok().and_then(|w| words.get(w)) {
277 Some(word) => Ok(*word),
278 None => self.absent(|| format!("t{slot}: element {element} past the end")),
279 }
280 }
281 Some(_) => Err(format!(
282 "t{slot}: a structured read of a view that is not a structured buffer"
283 )),
284 }
285 }
286
287 fn buffer_elements(&mut self, slot: u32) -> Option<u32> {
288 if self.absent == Absent::Refuse {
289 return None;
290 }
291 match self.views.get(&slot) {
292 None => Some(0),
293 Some(View::Typed(e)) => u32::try_from(e.len()).ok(),
294 Some(View::Structured { stride, words }) if *stride > 0 => u32::try_from(
295 (words.len() as u64)
296 .saturating_mul(4)
297 .checked_div(u64::from(*stride))?,
298 )
299 .ok(),
300 Some(_) => None,
301 }
302 }
303
304 fn query_image(&mut self, r: ImageQuery<u32>) -> Result<[u32; 4], String> {
305 let size = match self.views.get(&r.texture) {
306 None => return self.absent(|| format!("t{} has no texture", r.texture)),
307 Some(View::Uniform(_)) => [1, 1, 1],
308 Some(View::Texture2d(image) | View::Texture3d(image)) => {
309 let [w, h] = image.dimensions();
310 [w, h, image.depth()].map(|n| u32::try_from(n).unwrap_or(u32::MAX))
311 }
312 Some(View::Constant(_) | View::Image(_)) => {
313 return Err(format!(
314 "t{}: a size query of a texture whose size is not stated",
315 r.texture
316 ));
317 }
318 Some(View::Typed(_) | View::Structured { .. }) => {
319 return Err(format!("t{}: a texture query of a buffer", r.texture));
320 }
321 };
322 match r.kind {
323 QueryKind::Size if r.level.unwrap_or(0) == 0 => Ok([size[0], size[1], size[2], 0]),
324 QueryKind::Size => {
326 self.absent(|| format!("t{}: level {:?} past the view's one", r.texture, r.level))
327 }
328 QueryKind::Levels | QueryKind::Layers | QueryKind::Samples => Ok([1, 0, 0, 0]),
329 }
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use crate::{ImageLod, Lod};
337
338 fn views(filter: ViewFilter, address: Address) -> Views {
340 let plane = (0..4)
341 .map(|i| [(i % 2) as f32, (i / 2) as f32, 0.0, 1.0])
342 .collect();
343 let volume = (0..8)
344 .map(|i| [(i % 2) as f32, (i / 2 % 2) as f32, (i / 4) as f32, 1.0])
345 .collect();
346 Views {
347 views: BTreeMap::from([
348 (3, View::Texture2d(Decoded::from_texels(2, 2, plane).unwrap())),
349 (4, View::Texture3d(Decoded::volume(2, 2, 2, volume).unwrap())),
350 (5, View::Typed(Arc::from([[7; 4], [8; 4]]))),
351 (
352 6,
353 View::Structured {
354 stride: 8,
355 words: vec![10, 11, 12, 13],
356 },
357 ),
358 (7, View::Uniform([0.25; 4])),
359 ]),
360 samplers: BTreeMap::from([(
361 0,
362 ViewSampler {
363 filter,
364 address: [address; 3],
365 },
366 )]),
367 absent: Absent::Zero,
368 }
369 }
370
371 fn sample(mut v: &Views, texture: u32, uv: [f32; 2]) -> Result<[f32; 4], String> {
372 v.sample(Sample {
373 texture,
374 sampler: 0,
375 uv,
376 layer: None,
377 lod: Lod::Implicit,
378 })
379 }
380
381 fn sample_3d(mut v: &Views, texture: u32, coord: [f32; 3]) -> Result<[f32; 4], String> {
382 v.sample_image(ImageSample {
383 texture,
384 sampler: 0,
385 dim: Dim::D3,
386 coord,
387 layer: None,
388 lod: ImageLod::Implicit,
389 offset: [0; 3],
390 gather: None,
391 depth_ref: None,
392 depth: false,
393 })
394 }
395
396 fn load(mut v: &Views, texture: u32, texel: [i32; 3], mip: i32) -> Result<[u32; 4], String> {
397 v.load(Load { texture, texel, mip })
398 }
399
400 #[test]
401 fn samples_are_filtered_as_their_sampler_states() {
402 let point = ViewFilter::Point;
403 let linear = ViewFilter::Linear {
404 subtexel: Subtexel::Exact,
405 };
406 let v = &views(point, Address::Clamp);
407 assert_eq!(sample(v, 3, [0.75, 0.25]), Ok([1.0, 0.0, 0.0, 1.0]));
408 assert_eq!(sample(v, 3, [1.75, -3.0]), Ok([1.0, 0.0, 0.0, 1.0]));
409 assert_eq!(sample_3d(v, 4, [0.25, 0.75, 0.75]), Ok([0.0, 1.0, 1.0, 1.0]));
410 let w = &views(point, Address::Wrap);
411 assert_eq!(sample(w, 3, [-0.25, 0.25]), Ok([1.0, 0.0, 0.0, 1.0]));
412 let l = &views(linear, Address::Clamp);
413 assert_eq!(sample(l, 3, [0.5, 0.5]), Ok([0.5, 0.5, 0.0, 1.0]));
414 assert_eq!(sample_3d(l, 4, [0.5; 3]), Ok([0.5, 0.5, 0.5, 1.0]));
415 assert_eq!(sample(l, 7, [9.0, -9.0]), Ok([0.25; 4]));
417 assert_eq!(sample_3d(l, 7, [0.5; 3]), Ok([0.25; 4]));
418 }
419
420 #[test]
421 fn what_is_not_bound_reads_zero() {
422 let mut v = &views(ViewFilter::Point, Address::Clamp);
423 assert_eq!(sample(v, 9, [0.5, 0.5]), Ok([0.0; 4]));
424 assert_eq!(sample_3d(v, 9, [0.5; 3]), Ok([0.0; 4]));
425 assert_eq!(load(v, 9, [0, 0, 0], 0), Ok([0; 4]));
426 assert_eq!(v.structured(9, 0, 0), Ok(0));
427 assert_eq!(v.buffer_elements(9), Some(0));
428 let query = ImageQuery {
429 texture: 9,
430 dim: Dim::D2,
431 kind: QueryKind::Size,
432 level: None,
433 };
434 assert_eq!(v.query_image(query), Ok([0; 4]));
435 assert_eq!(
436 v.query_image(ImageQuery { texture: 4, ..query }),
437 Ok([2, 2, 2, 0])
438 );
439 }
440
441 #[test]
442 fn a_load_outside_its_view_reads_zero() {
443 let mut v = &views(ViewFilter::Point, Address::Clamp);
444 let one = 1f32.to_bits();
445 assert_eq!(load(v, 3, [1, 1, 0], 0), Ok([one, one, 0, one]));
446 assert_eq!(load(v, 3, [2, 0, 0], 0), Ok([0; 4]));
447 assert_eq!(load(v, 3, [0, -1, 0], 0), Ok([0; 4]));
448 assert_eq!(load(v, 3, [0, 0, 1], 0), Ok([0; 4]), "a layer past the view");
449 assert_eq!(load(v, 3, [0, 0, 0], 1), Ok([0; 4]), "a level past the view");
450 assert_eq!(load(v, 4, [1, 0, 1], 0), Ok([one, 0, one, one]));
451 assert_eq!(load(v, 4, [0, 0, 2], 0), Ok([0; 4]));
452 assert_eq!(load(v, 5, [1, 0, 0], 0), Ok([8; 4]));
453 assert_eq!(load(v, 5, [2, 0, 0], 0), Ok([0; 4]));
454 assert_eq!(load(v, 7, [0, 0, 0], 0), Ok([0.25f32.to_bits(); 4]));
455 assert_eq!(load(v, 7, [1, 0, 0], 0), Ok([0; 4]));
456 assert_eq!(v.structured(6, 1, 4), Ok(13));
458 assert_eq!(v.structured(6, 2, 0), Ok(0));
459 assert!(v.structured(6, 0, 8).is_err());
460 assert_eq!(v.buffer_elements(6), Some(2));
461 assert_eq!(v.buffer_elements(5), Some(2));
462 }
463
464 #[test]
465 fn what_the_views_do_not_answer_is_refused() {
466 let mut v = &views(ViewFilter::Point, Address::Clamp);
467 assert!(
469 v.sample(Sample {
470 texture: 3,
471 sampler: 1,
472 uv: [0.5; 2],
473 layer: None,
474 lod: Lod::Implicit,
475 })
476 .is_err()
477 );
478 assert!(sample_3d(v, 3, [0.5; 3]).is_err());
480 assert!(sample(v, 4, [0.5; 2]).is_err());
481 assert!(sample(v, 5, [0.5; 2]).is_err());
482 assert!(
484 v.sample(Sample {
485 texture: 3,
486 sampler: 0,
487 uv: [0.5; 2],
488 layer: Some(1.0),
489 lod: Lod::Implicit,
490 })
491 .is_err()
492 );
493 let gather = ImageSample {
494 texture: 3,
495 sampler: 0,
496 dim: Dim::D2,
497 coord: [0.5; 3],
498 layer: None,
499 lod: ImageLod::Implicit,
500 offset: [0; 3],
501 gather: Some(0),
502 depth_ref: None,
503 depth: false,
504 };
505 assert!(v.sample_image(gather).is_err());
506 assert!(
507 v.sample_image(ImageSample {
508 gather: None,
509 offset: [1, 0, 0],
510 ..gather
511 })
512 .is_err()
513 );
514 assert!(v.structured(3, 0, 0).is_err());
515 assert!(
516 v.load(Load {
517 texture: 6,
518 texel: [0; 3],
519 mip: 0
520 })
521 .is_err()
522 );
523 }
524
525 #[derive(Debug)]
527 struct Echo;
528 impl Image for Echo {
529 fn sample(&self, uv: [f32; 2], lod: Lod) -> Result<[f32; 4], String> {
530 let level = match lod {
531 Lod::Level(l) => l,
532 _ => -1.0,
533 };
534 Ok([uv[0], uv[1], level, 1.0])
535 }
536 }
537
538 #[test]
542 fn a_refusing_table_refuses_what_it_does_not_hold() {
543 let mut v = &Views::refusing(BTreeMap::from([
544 (0, View::Constant([0.5; 4])),
545 (1, View::Image(Arc::new(Echo))),
546 (2, View::Typed(Arc::from([[1, 2, 3, 4]]))),
547 ]));
548 assert_eq!(sample(v, 0, [9.0, 9.0]), Ok([0.5; 4]));
549 assert_eq!(load(v, 0, [5, 6, 0], 2), Ok([0.5f32.to_bits(); 4]));
550 let level = ImageSample {
551 texture: 1,
552 sampler: 0,
553 dim: Dim::D2,
554 coord: [0.25, 0.75, 0.0],
555 layer: None,
556 lod: ImageLod::Level(2.0),
557 offset: [0; 3],
558 gather: None,
559 depth_ref: None,
560 depth: false,
561 };
562 assert_eq!(v.sample_image(level), Ok([0.25, 0.75, 2.0, 1.0]));
563 assert_eq!(
564 v.sample(Sample {
565 texture: 1,
566 sampler: 0,
567 uv: [0.5; 2],
568 layer: Some(0.0),
569 lod: Lod::Implicit,
570 }),
571 Err("t1: an array layer of an image".into())
572 );
573 assert_eq!(load(v, 1, [0; 3], 0), Err("t1: a load of an image".into()));
574 assert_eq!(load(v, 2, [0; 3], 0), Ok([1, 2, 3, 4]));
575 assert_eq!(load(v, 2, [1, 0, 0], 0), Err("t2: element 1 of 1".into()));
576 assert_eq!(sample(v, 9, [0.5; 2]), Err("t9 has no texture".into()));
577 assert_eq!(load(v, 9, [0; 3], 0), Err("t9 has no texture".into()));
578 assert_eq!(v.structured(9, 0, 0), Err("t9 has no texture".into()));
579 assert_eq!(v.buffer_elements(2), None);
580 let query = ImageQuery {
581 texture: 0,
582 dim: Dim::D2,
583 kind: QueryKind::Size,
584 level: None,
585 };
586 assert!(v.query_image(query).is_err());
587 assert!(v.query_image(ImageQuery { texture: 9, ..query }).is_err());
588 }
589}