1use crate::resources::{TextureExt, TextureView};
60use crate::staging::StagingBuffer;
61
62#[derive(Clone, Debug, PartialEq, Eq)]
72pub enum ArrayHandle {
73 Buffer(wgpu::Buffer),
75 Texel(TextureView),
77}
78
79impl ArrayHandle {
80 pub fn binding_resource(&self) -> wgpu::BindingResource<'_> {
82 match self {
83 Self::Buffer(buffer) => buffer.as_entire_binding(),
84 Self::Texel(view) => wgpu::BindingResource::TextureView(view.view()),
85 }
86 }
87
88 pub fn layout_entry(
95 &self,
96 binding: u32,
97 visibility: wgpu::ShaderStages,
98 element_size: u64,
99 ) -> wgpu::BindGroupLayoutEntry {
100 match self {
101 Self::Buffer(_) => wgpu::BindGroupLayoutEntry {
102 binding,
103 visibility,
104 ty: wgpu::BindingType::Buffer {
105 ty: wgpu::BufferBindingType::Storage { read_only: true },
106 has_dynamic_offset: false,
107 min_binding_size: Some(core::num::NonZeroU64::new(element_size).expect(
108 "an element has a non-zero size, which is a valid binding minimum",
109 )),
110 },
111 count: None,
112 },
113 Self::Texel(_) => TexelArrayLayout::binding(binding, visibility),
114 }
115 }
116}
117
118enum ArrayStorage {
120 Buffer {
122 buffer: wgpu::Buffer,
124 staging: StagingBuffer,
126 },
127 Texel(Box<TexelArray>),
133}
134
135pub struct Array {
144 storage: ArrayStorage,
146 element_size: u64,
148 max_dimension: Option<u32>,
150}
151
152impl Array {
153 pub fn new(
164 device: &wgpu::Device,
165 label: Option<&str>,
166 element_size: u64,
167 capacity: u64,
168 max_dimension: Option<u32>,
169 ) -> Self {
170 let capacity = capacity.max(1);
171 let storage = match max_dimension {
172 Some(max_dimension) => ArrayStorage::Texel(Box::new(TexelArray::new(
173 device,
174 label,
175 element_size,
176 capacity,
177 max_dimension,
178 ))),
179 None => ArrayStorage::Buffer {
180 buffer: create_array_buffer(device, label, element_size, capacity),
181 staging: StagingBuffer::new(),
182 },
183 };
184 Self {
185 storage,
186 element_size,
187 max_dimension,
188 }
189 }
190
191 pub fn element_size(&self) -> u64 {
193 self.element_size
194 }
195
196 pub fn capacity(&self) -> u64 {
198 match &self.storage {
199 ArrayStorage::Buffer { buffer, .. } => buffer.size() / self.element_size,
200 ArrayStorage::Texel(texel) => texel.layout().capacity(),
201 }
202 }
203
204 pub fn is_texel(&self) -> bool {
206 matches!(self.storage, ArrayStorage::Texel(_))
207 }
208
209 pub fn handle(&self) -> ArrayHandle {
215 match &self.storage {
216 ArrayStorage::Buffer { buffer, .. } => ArrayHandle::Buffer(buffer.clone()),
217 ArrayStorage::Texel(texel) => ArrayHandle::Texel(texel.view().clone()),
218 }
219 }
220
221 pub fn grow_to(&mut self, device: &wgpu::Device, label: Option<&str>, capacity: u64) -> bool {
232 if capacity <= self.capacity() {
233 return false;
234 }
235 self.storage = match self.max_dimension {
236 Some(max_dimension) => ArrayStorage::Texel(Box::new(TexelArray::new(
237 device,
238 label,
239 self.element_size,
240 capacity,
241 max_dimension,
242 ))),
243 None => ArrayStorage::Buffer {
244 buffer: create_array_buffer(device, label, self.element_size, capacity),
245 staging: StagingBuffer::new(),
246 },
247 };
248 true
249 }
250
251 pub fn upload(
259 &mut self,
260 device: &wgpu::Device,
261 encoder: &mut wgpu::CommandEncoder,
262 elements: &[u8],
263 ) {
264 match &mut self.storage {
265 ArrayStorage::Buffer { buffer, staging } => {
266 staging.write(device, encoder, buffer, 0, elements);
267 }
268 ArrayStorage::Texel(texel) => texel.upload(device, encoder, elements),
269 }
270 }
271
272 pub fn write(&mut self, queue: &wgpu::Queue, elements: &[u8]) {
286 match &mut self.storage {
287 ArrayStorage::Buffer { buffer, .. } => queue.write_buffer(buffer, 0, elements),
288 ArrayStorage::Texel(texel) => texel.write(queue, elements),
289 }
290 }
291}
292
293fn create_array_buffer(
295 device: &wgpu::Device,
296 label: Option<&str>,
297 element_size: u64,
298 capacity: u64,
299) -> wgpu::Buffer {
300 device.create_buffer(&wgpu::BufferDescriptor {
301 label,
302 size: capacity.max(1) * element_size,
303 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
304 mapped_at_creation: false,
305 })
306}
307
308#[derive(Clone, Copy, Debug, PartialEq, Eq)]
313pub struct TexelArrayLayout {
314 pub texel_format: wgpu::TextureFormat,
316 pub lanes_per_texel: u32,
318 pub element_size: u64,
320 pub elements_per_row: u32,
322 pub width: u32,
324 pub height: u32,
326}
327
328impl TexelArrayLayout {
329 pub fn new(element_size: u64, capacity: u64, max_dimension: u32) -> Self {
341 assert!(
342 element_size > 0 && element_size.is_multiple_of(size_of::<f32>() as u64),
343 "a texel array element of {element_size} bytes is not a whole number of `f32` lanes"
344 );
345 let lanes_per_texel = if element_size.is_multiple_of(16) {
349 4
350 } else {
351 1
352 };
353 let texel_format = match lanes_per_texel {
354 4 => wgpu::TextureFormat::Rgba32Float,
355 _ => wgpu::TextureFormat::R32Float,
356 };
357 let texel_bytes = u64::from(lanes_per_texel) * size_of::<f32>() as u64;
358 let texels_per_element = element_size / texel_bytes;
359 assert!(
363 texels_per_element <= u64::from(max_dimension),
364 "a {element_size}-byte element spans {texels_per_element} texels, \
365 wider than the {max_dimension}-texel texture limit"
366 );
367
368 let capacity = capacity.max(1);
369 let elements_per_row =
370 row_elements(element_size, texels_per_element, capacity, max_dimension);
371 let width = u32::try_from(u64::from(elements_per_row) * texels_per_element)
372 .expect("a row width within `max_dimension` fits a `u32`");
373 let height = u32::try_from(capacity.div_ceil(u64::from(elements_per_row)))
374 .expect("a height within `max_dimension` fits a `u32`")
375 .max(1);
376
377 Self {
378 texel_format,
379 lanes_per_texel,
380 element_size,
381 elements_per_row,
382 width,
383 height,
384 }
385 }
386
387 pub fn row_bytes(&self) -> u64 {
391 u64::from(self.elements_per_row) * self.element_size
392 }
393
394 pub fn capacity(&self) -> u64 {
396 u64::from(self.elements_per_row) * u64::from(self.height)
397 }
398
399 pub fn texture_size(&self) -> wgpu::Extent3d {
401 wgpu::Extent3d {
402 width: self.width,
403 height: self.height,
404 depth_or_array_layers: 1,
405 }
406 }
407
408 fn rows_for(&self, element_count: u64) -> u32 {
410 u32::try_from(element_count.div_ceil(u64::from(self.elements_per_row)))
411 .expect("no more rows than the texture has")
412 .max(1)
413 }
414
415 pub fn pack(&self, elements: &[u8], out: &mut Vec<u8>) {
429 assert!(
430 (elements.len() as u64).is_multiple_of(self.element_size),
431 "{} bytes are not a whole number of {}-byte elements",
432 elements.len(),
433 self.element_size
434 );
435 assert!(
436 elements.len() as u64 <= self.capacity() * self.element_size,
437 "{} bytes hold more than the {} elements the array has room for",
438 elements.len(),
439 self.capacity()
440 );
441 let element_count = elements.len() as u64 / self.element_size;
442 let used = u64::from(self.rows_for(element_count)) * self.row_bytes();
443 out.clear();
444 out.extend_from_slice(elements);
445 out.resize(used as usize, 0);
446 }
447
448 pub fn binding(binding: u32, visibility: wgpu::ShaderStages) -> wgpu::BindGroupLayoutEntry {
450 wgpu::BindGroupLayoutEntry {
451 binding,
452 visibility,
453 ty: wgpu::BindingType::Texture {
454 sample_type: wgpu::TextureSampleType::Float { filterable: false },
457 view_dimension: wgpu::TextureViewDimension::D2,
458 multisampled: false,
459 },
460 count: None,
461 }
462 }
463}
464
465fn row_elements(
468 element_size: u64,
469 texels_per_element: u64,
470 capacity: u64,
471 max_dimension: u32,
472) -> u32 {
473 let alignment = u64::from(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
474 let base = alignment / gcd(element_size, alignment);
478 let max_elements_per_row = (u64::from(max_dimension) / texels_per_element).max(1);
481 assert!(
482 base <= max_elements_per_row,
483 "a {element_size}-byte element needs a {}-texel row, wider than the \
484 {max_dimension}-texel texture limit",
485 base * texels_per_element
486 );
487
488 let mut elements_per_row = base;
489 while capacity.div_ceil(elements_per_row) > u64::from(max_dimension) {
490 assert!(
491 elements_per_row < max_elements_per_row,
492 "{capacity} elements of {element_size} bytes do not fit in a \
493 {max_dimension}x{max_dimension} texture"
494 );
495 elements_per_row = (elements_per_row * 2).min(max_elements_per_row);
497 }
498 u32::try_from(elements_per_row).expect("a row within `max_dimension` fits a `u32`")
499}
500
501fn gcd(mut a: u64, mut b: u64) -> u64 {
503 while b != 0 {
504 (a, b) = (b, a % b);
505 }
506 a
507}
508
509fn create_texture(
511 device: &wgpu::Device,
512 label: Option<&str>,
513 layout: &TexelArrayLayout,
514) -> wgpu::Texture {
515 device.create_texture(&wgpu::TextureDescriptor {
516 label,
517 size: layout.texture_size(),
518 mip_level_count: 1,
519 sample_count: 1,
520 dimension: wgpu::TextureDimension::D2,
521 format: layout.texel_format,
522 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
523 view_formats: &[],
524 })
525}
526
527pub struct TexelArray {
531 texture: wgpu::Texture,
533 view: TextureView,
535 layout: TexelArrayLayout,
537 staging: StagingBuffer,
540 scratch: Vec<u8>,
543}
544
545impl TexelArray {
546 pub fn new(
557 device: &wgpu::Device,
558 label: Option<&str>,
559 element_size: u64,
560 capacity: u64,
561 max_dimension: u32,
562 ) -> Self {
563 let layout = TexelArrayLayout::new(element_size, capacity, max_dimension);
564 let texture = create_texture(device, label, &layout);
565 let view = TextureExt::create_view(&texture, &wgpu::TextureViewDescriptor::default());
566 Self {
567 texture,
568 view,
569 layout,
570 staging: StagingBuffer::new(),
571 scratch: Vec::new(),
572 }
573 }
574
575 pub fn texture(&self) -> &wgpu::Texture {
577 &self.texture
578 }
579
580 pub fn view(&self) -> &TextureView {
582 &self.view
583 }
584
585 pub fn layout(&self) -> &TexelArrayLayout {
587 &self.layout
588 }
589
590 pub fn binding_resource(&self) -> wgpu::BindingResource<'_> {
592 wgpu::BindingResource::TextureView(self.view.view())
593 }
594
595 pub fn upload(
606 &mut self,
607 device: &wgpu::Device,
608 encoder: &mut wgpu::CommandEncoder,
609 elements: &[u8],
610 ) {
611 self.layout.pack(elements, &mut self.scratch);
612 let layout = wgpu::TexelCopyBufferLayout {
613 offset: 0,
614 bytes_per_row: Some(
615 u32::try_from(self.layout.row_bytes())
616 .expect("a row no wider than the texture limit fits a `u32`"),
617 ),
618 rows_per_image: Some(self.layout.height),
619 };
620 let size = wgpu::Extent3d {
624 height: self
625 .layout
626 .rows_for(elements.len() as u64 / self.layout.element_size),
627 ..self.layout.texture_size()
628 };
629 self.staging
630 .write_texture(device, encoder, &self.texture, layout, size, &self.scratch);
631 }
632
633 pub fn write(&mut self, queue: &wgpu::Queue, elements: &[u8]) {
643 self.layout.pack(elements, &mut self.scratch);
644 let size = wgpu::Extent3d {
647 height: self
648 .layout
649 .rows_for(elements.len() as u64 / self.layout.element_size),
650 ..self.layout.texture_size()
651 };
652 queue.write_texture(
653 wgpu::TexelCopyTextureInfo {
654 texture: &self.texture,
655 mip_level: 0,
656 origin: wgpu::Origin3d::ZERO,
657 aspect: wgpu::TextureAspect::All,
658 },
659 &self.scratch,
660 wgpu::TexelCopyBufferLayout {
661 offset: 0,
662 bytes_per_row: Some(
663 u32::try_from(self.layout.row_bytes())
664 .expect("a row no wider than the texture limit fits a `u32`"),
665 ),
666 rows_per_image: Some(self.layout.height),
667 },
668 size,
669 );
670 }
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676 use crate::util::test::noop_device;
677
678 const MATRIX_ELEMENT: u64 = 64;
680 const STRUCT_ELEMENT: u64 = 48;
683 const SCALAR_ELEMENT: u64 = 4;
685 const ROOMY: u32 = 2048;
687
688 #[test]
689 fn a_matrix_element_reads_as_four_texels_of_four_lanes() {
690 let layout = TexelArrayLayout::new(MATRIX_ELEMENT, 8, ROOMY);
691
692 assert_eq!(layout.texel_format, wgpu::TextureFormat::Rgba32Float);
693 assert_eq!(layout.lanes_per_texel, 4);
694 assert_eq!(layout.elements_per_row, 4, "a row is 256 bytes");
695 assert_eq!(layout.row_bytes(), 256);
696 assert_eq!(layout.width, 16, "four elements of four texels each");
697 assert_eq!(layout.height, 2);
698 }
699
700 #[test]
701 fn a_scalar_element_reads_as_one_lane_per_texel() {
702 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 128, ROOMY);
703
704 assert_eq!(layout.texel_format, wgpu::TextureFormat::R32Float);
705 assert_eq!(layout.lanes_per_texel, 1);
706 assert_eq!(layout.elements_per_row, 64, "a row is 256 bytes");
707 assert_eq!(layout.width, 64);
708 assert_eq!(layout.height, 2);
709 }
710
711 #[test]
712 fn a_48_byte_element_still_reaches_the_row_alignment() {
713 let layout = TexelArrayLayout::new(STRUCT_ELEMENT, 32, ROOMY);
716
717 assert_eq!(layout.texel_format, wgpu::TextureFormat::Rgba32Float);
718 assert_eq!(layout.elements_per_row, 16);
719 assert_eq!(layout.row_bytes(), 768);
720 assert!(
721 layout
722 .row_bytes()
723 .is_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as u64)
724 );
725 assert_eq!(layout.width, 48, "sixteen elements of three texels each");
726 assert_eq!(layout.height, 2);
727 }
728
729 #[test]
730 fn every_element_size_produces_an_aligned_row() {
731 for element_size in [4u64, 8, 12, 16, 20, 32, 36, 48, 64, 96, 128, 256] {
732 let layout = TexelArrayLayout::new(element_size, 4, ROOMY);
733 assert!(
734 layout
735 .row_bytes()
736 .is_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as u64),
737 "a {element_size}-byte element gave a {}-byte row",
738 layout.row_bytes()
739 );
740 assert_eq!(
741 layout.width % layout.lanes_per_texel,
742 0,
743 "a {element_size}-byte element's row holds whole texels"
744 );
745 }
746 }
747
748 #[test]
749 fn an_element_that_is_not_a_whole_number_of_lanes_panics() {
750 let result = std::panic::catch_unwind(|| TexelArrayLayout::new(6, 1, ROOMY));
751
752 assert!(
753 result.is_err(),
754 "6 bytes is not a whole number of `f32` lanes"
755 );
756 }
757
758 #[test]
759 fn an_element_wider_than_the_texture_limit_panics() {
760 let widest = u64::from(ROOMY) * 16;
763 assert!(std::panic::catch_unwind(|| TexelArrayLayout::new(widest, 1, ROOMY)).is_ok());
764
765 let result = std::panic::catch_unwind(|| TexelArrayLayout::new(widest + 16, 1, ROOMY));
766
767 assert!(result.is_err());
768 }
769
770 #[test]
771 fn an_empty_array_still_has_one_row() {
772 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 0, ROOMY);
773
774 assert_eq!(layout.height, 1, "a texture cannot have a zero-height axis");
775 assert_eq!(layout.capacity(), 64);
776 }
777
778 #[test]
779 fn a_capacity_beyond_one_column_grows_the_height() {
780 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 64 * 8, ROOMY);
781
782 assert_eq!(
783 layout.elements_per_row, 64,
784 "the row stays at one alignment"
785 );
786 assert_eq!(layout.width, 64);
787 assert_eq!(layout.height, 8, "the rows carry the extra elements");
788 }
789
790 #[test]
791 fn a_capacity_beyond_the_texture_limit_widens_the_row() {
792 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 64 * u64::from(ROOMY) + 1, ROOMY);
795
796 assert_eq!(layout.elements_per_row, 128, "the row doubled");
797 assert_eq!(layout.width, 128);
798 assert_eq!(layout.height, 1025, "and the elements now fit");
799 assert!(layout.height <= ROOMY);
800 }
801
802 #[test]
803 fn a_capacity_that_cannot_fit_at_all_panics() {
804 let fit = u64::from(ROOMY) * u64::from(ROOMY);
807 assert!(
808 std::panic::catch_unwind(|| TexelArrayLayout::new(SCALAR_ELEMENT, fit, ROOMY)).is_ok()
809 );
810
811 let result =
812 std::panic::catch_unwind(|| TexelArrayLayout::new(SCALAR_ELEMENT, fit + 1, ROOMY));
813
814 assert!(result.is_err());
815 }
816
817 #[test]
818 fn packing_pads_only_to_the_last_populated_row() {
819 let layout = TexelArrayLayout::new(STRUCT_ELEMENT, 32, ROOMY);
820 let elements: Vec<u8> = (0..6 * STRUCT_ELEMENT).map(|i| i as u8).collect();
821 let mut out = Vec::new();
822
823 layout.pack(&elements, &mut out);
824
825 assert_eq!(
826 out.len() as u64,
827 layout.row_bytes(),
828 "six of the sixteen elements in one row need one row's bytes"
829 );
830 assert_eq!(
831 &out[..elements.len()],
832 &elements[..],
833 "the elements are contiguous from the start"
834 );
835 assert!(
836 out[elements.len()..].iter().all(|byte| *byte == 0),
837 "the unused tail of the last row is zeroed"
838 );
839 }
840
841 #[test]
842 fn a_partial_upload_copies_only_its_populated_rows() {
843 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 256, ROOMY);
844 let elements = [7u8; 65 * SCALAR_ELEMENT as usize];
845 let mut scratch = Vec::new();
846 layout.pack(&elements, &mut scratch);
847 let rows = layout.rows_for(elements.len() as u64 / SCALAR_ELEMENT);
848
849 assert_eq!(rows, 2);
850 assert_eq!(scratch.len() as u64, u64::from(rows) * layout.row_bytes());
851 assert!(u64::from(rows) < u64::from(layout.height));
852 }
853
854 #[test]
855 fn packing_a_full_row_writes_exactly_one_row() {
856 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 128, ROOMY);
857
858 let mut out = Vec::new();
859 layout.pack(&vec![7; 64 * SCALAR_ELEMENT as usize], &mut out);
860
861 assert_eq!(out.len() as u64, layout.row_bytes());
862 assert_eq!(out.len(), 256);
863 }
864
865 #[test]
866 fn packing_more_than_one_row_spans_the_rows_it_needs() {
867 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 128, ROOMY);
868
869 let mut out = Vec::new();
870 layout.pack(&vec![7; 65 * SCALAR_ELEMENT as usize], &mut out);
871
872 assert_eq!(out.len(), 512, "the 65th element starts a second row");
873 }
874
875 #[test]
876 fn packing_again_shorter_clears_the_previous_tail() {
877 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 128, ROOMY);
878 let mut out = Vec::new();
879 layout.pack(&[0xff; 64 * SCALAR_ELEMENT as usize], &mut out);
880
881 layout.pack(&[1; 2 * SCALAR_ELEMENT as usize], &mut out);
882
883 assert!(
884 out[2 * SCALAR_ELEMENT as usize..]
885 .iter()
886 .all(|byte| *byte == 0),
887 "an element the shorter upload does not reach is cleared"
888 );
889 }
890
891 #[test]
892 fn packing_reuses_the_same_allocation() {
893 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 16, ROOMY);
894 let mut out = Vec::new();
895 layout.pack(&[1; 16], &mut out);
896 let capacity = out.capacity();
897
898 layout.pack(&[2; 16], &mut out);
899
900 assert_eq!(out.capacity(), capacity, "the scratch buffer is reused");
901 }
902
903 #[test]
904 #[should_panic(expected = "not a whole number")]
905 fn packing_a_partial_element_panics() {
906 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 16, ROOMY);
907 layout.pack(&[0; 6], &mut Vec::new());
908 }
909
910 #[test]
911 #[should_panic(expected = "room for")]
912 fn packing_more_than_capacity_panics() {
913 let layout = TexelArrayLayout::new(SCALAR_ELEMENT, 4, ROOMY);
914 let over = (layout.capacity() + 1) * SCALAR_ELEMENT;
915 layout.pack(&vec![0; over as usize], &mut Vec::new());
916 }
917
918 #[test]
919 fn an_array_creates_a_texture_a_bind_group_can_bind() {
920 let (device, _queue) = noop_device();
921 let array = TexelArray::new(&device, Some("test::texel_array"), MATRIX_ELEMENT, 4, ROOMY);
922
923 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
924 label: Some("test::texel_array"),
925 entries: &[TexelArrayLayout::binding(0, wgpu::ShaderStages::VERTEX)],
926 });
927 let _bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
928 label: Some("test::texel_array"),
929 layout: &layout,
930 entries: &[wgpu::BindGroupEntry {
931 binding: 0,
932 resource: array.binding_resource(),
933 }],
934 });
935 }
936
937 #[test]
938 fn an_upload_records_a_copy_the_device_accepts() {
939 let (device, _queue) = noop_device();
940 let mut array =
941 TexelArray::new(&device, Some("test::texel_array"), MATRIX_ELEMENT, 4, ROOMY);
942 let mut encoder = device.create_command_encoder(&Default::default());
943
944 array.upload(&device, &mut encoder, &[0; 2 * MATRIX_ELEMENT as usize]);
945
946 let _ = encoder.finish();
947 }
948
949 #[test]
950 fn an_upload_of_an_empty_array_records_a_copy_too() {
951 let (device, _queue) = noop_device();
952 let mut array =
953 TexelArray::new(&device, Some("test::texel_array"), MATRIX_ELEMENT, 4, ROOMY);
954 let mut encoder = device.create_command_encoder(&Default::default());
955
956 array.upload(&device, &mut encoder, &[]);
957
958 let _ = encoder.finish();
959 }
960
961 #[test]
962 fn an_array_holds_a_buffer_or_a_texture_by_its_limit() {
963 let (device, _queue) = noop_device();
964
965 let buffered = Array::new(&device, Some("test"), SCALAR_ELEMENT, 4, None);
966 assert!(!buffered.is_texel());
967 assert_eq!(buffered.capacity(), 4);
968 assert!(matches!(buffered.handle(), ArrayHandle::Buffer(_)));
969
970 let texel = Array::new(&device, Some("test"), SCALAR_ELEMENT, 4, Some(ROOMY));
971 assert!(texel.is_texel());
972 assert_eq!(texel.capacity(), 64, "a row of 64 holds the four elements");
973 assert!(matches!(texel.handle(), ArrayHandle::Texel(_)));
974 }
975
976 #[test]
977 fn an_array_uploads_the_same_bytes_on_either_path() {
978 let (device, _queue) = noop_device();
979 let elements = [1u8; 2 * SCALAR_ELEMENT as usize];
980
981 let mut buffered = Array::new(&device, Some("test"), SCALAR_ELEMENT, 4, None);
982 let mut texel = Array::new(&device, Some("test"), SCALAR_ELEMENT, 4, Some(ROOMY));
983 let mut encoder = device.create_command_encoder(&Default::default());
984 buffered.upload(&device, &mut encoder, &elements);
985 texel.upload(&device, &mut encoder, &elements);
986 let _ = encoder.finish();
987 }
988
989 #[test]
990 fn growing_an_array_replaces_its_resource_only_when_it_is_too_small() {
991 let (device, _queue) = noop_device();
992 let mut array = Array::new(&device, Some("test"), SCALAR_ELEMENT, 4, None);
993
994 assert!(
995 !array.grow_to(&device, Some("test"), 4),
996 "already has the room"
997 );
998 assert!(
999 array.grow_to(&device, Some("test"), 8),
1000 "the array outgrew it"
1001 );
1002 assert_eq!(array.capacity(), 8);
1003 }
1004
1005 #[test]
1006 fn growing_a_texel_array_stays_on_the_texel_path() {
1007 let (device, _queue) = noop_device();
1008 let mut array = Array::new(&device, Some("test"), MATRIX_ELEMENT, 4, Some(ROOMY));
1009 assert!(array.is_texel());
1010
1011 assert!(array.grow_to(&device, Some("test"), 64));
1012
1013 assert!(
1014 array.is_texel(),
1015 "a grown array keeps the resource it can read"
1016 );
1017 assert_eq!(array.capacity(), 64);
1018 }
1019
1020 #[test]
1021 fn a_layout_entry_follows_the_resource_the_array_holds() {
1022 let (device, _queue) = noop_device();
1023 let buffered = Array::new(&device, Some("test"), MATRIX_ELEMENT, 4, None);
1024 let texel = Array::new(&device, Some("test"), MATRIX_ELEMENT, 4, Some(ROOMY));
1025
1026 let buffer_entry =
1027 buffered
1028 .handle()
1029 .layout_entry(2, wgpu::ShaderStages::VERTEX, MATRIX_ELEMENT);
1030 assert!(matches!(buffer_entry.ty, wgpu::BindingType::Buffer { .. }));
1031
1032 let texel_entry =
1033 texel
1034 .handle()
1035 .layout_entry(2, wgpu::ShaderStages::VERTEX, MATRIX_ELEMENT);
1036 assert!(matches!(texel_entry.ty, wgpu::BindingType::Texture { .. }));
1037 }
1038
1039 #[test]
1040 fn a_bind_group_accepts_an_array_of_either_path() {
1041 let (device, _queue) = noop_device();
1042 for max_dimension in [None, Some(ROOMY)] {
1043 let array = Array::new(&device, Some("test"), MATRIX_ELEMENT, 4, max_dimension);
1044 let entry = array
1045 .handle()
1046 .layout_entry(0, wgpu::ShaderStages::VERTEX, MATRIX_ELEMENT);
1047 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1048 label: Some("test::array"),
1049 entries: &[entry],
1050 });
1051 let _bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1052 label: Some("test::array"),
1053 layout: &layout,
1054 entries: &[wgpu::BindGroupEntry {
1055 binding: 0,
1056 resource: array.handle().binding_resource(),
1057 }],
1058 });
1059 }
1060 }
1061}