1use arrayvec::ArrayVec;
56use core::ops::Range;
57
58pub type BindGroupBinding = (u32, wgpu::BindGroup);
60
61pub type VertexBufferBinding = (u32, wgpu::Buffer, Range<u64>);
64
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
71pub struct ScissorRect {
72 pub x: u32,
74 pub y: u32,
76 pub width: u32,
78 pub height: u32,
80}
81
82impl ScissorRect {
83 pub fn new(width: u32, height: u32) -> Self {
85 Self {
86 x: 0,
87 y: 0,
88 width,
89 height,
90 }
91 }
92
93 pub fn at(self, x: u32, y: u32) -> Self {
95 Self { x, y, ..self }
96 }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq)]
111pub struct ViewportRect {
112 pub x: f32,
114 pub y: f32,
116 pub width: f32,
118 pub height: f32,
120 pub min_depth: f32,
122 pub max_depth: f32,
124}
125
126impl ViewportRect {
127 pub fn new(width: f32, height: f32) -> Self {
130 Self {
131 x: 0.0,
132 y: 0.0,
133 width,
134 height,
135 min_depth: 0.0,
136 max_depth: 1.0,
137 }
138 }
139
140 pub fn at(self, x: f32, y: f32) -> Self {
142 Self { x, y, ..self }
143 }
144
145 pub fn with_depth(self, min_depth: f32, max_depth: f32) -> Self {
148 Self {
149 min_depth,
150 max_depth,
151 ..self
152 }
153 }
154
155 pub fn fit_aspect(aspect: f32, target_width: u32, target_height: u32) -> Self {
169 let (width, height) = (target_width as f32, target_height as f32);
170 if !aspect.is_finite() || aspect <= 0.0 || width <= 0.0 || height <= 0.0 {
172 return Self::new(width.max(0.0), height.max(0.0));
173 }
174 if width / height > aspect {
175 let fitted = height * aspect;
176 Self::new(fitted, height).at((width - fitted) * 0.5, 0.0)
177 } else {
178 let fitted = width / aspect;
179 Self::new(width, fitted).at(0.0, (height - fitted) * 0.5)
180 }
181 }
182}
183
184#[derive(Clone, Debug)]
186pub enum DrawRange {
187 Vertices {
189 vertices: Range<u32>,
191 instances: Range<u32>,
193 },
194 Indexed {
196 indices: Range<u32>,
198 base_vertex: i32,
200 instances: Range<u32>,
202 },
203}
204
205impl DrawRange {
206 pub fn vertices(vertices: Range<u32>) -> Self {
208 Self::Vertices {
209 vertices,
210 instances: 0..1,
211 }
212 }
213
214 pub fn indexed(indices: Range<u32>) -> Self {
216 Self::Indexed {
217 indices,
218 base_vertex: 0,
219 instances: 0..1,
220 }
221 }
222
223 pub fn with_instances(self, instances: Range<u32>) -> Self {
225 match self {
226 Self::Vertices { vertices, .. } => Self::Vertices {
227 vertices,
228 instances,
229 },
230 Self::Indexed {
231 indices,
232 base_vertex,
233 ..
234 } => Self::Indexed {
235 indices,
236 base_vertex,
237 instances,
238 },
239 }
240 }
241
242 pub fn with_base_vertex(self, base_vertex: i32) -> Self {
245 match self {
246 Self::Indexed {
247 indices, instances, ..
248 } => Self::Indexed {
249 indices,
250 base_vertex,
251 instances,
252 },
253 other => other,
254 }
255 }
256}
257
258#[derive(Clone, Copy, Debug, PartialEq)]
267pub enum DrawOrder {
268 Before,
272 Sorted(f32),
281 After,
286}
287
288#[derive(Clone, Debug)]
301pub struct DrawEntry {
302 pub pipeline: wgpu::RenderPipeline,
304 pub bind_groups: ArrayVec<BindGroupBinding, MAX_BIND_GROUPS>,
308 pub vertex_buffers: ArrayVec<VertexBufferBinding, MAX_VERTEX_BUFFERS>,
310 pub index_buffer: Option<(wgpu::Buffer, Range<u64>, wgpu::IndexFormat)>,
313 pub scissor: Option<ScissorRect>,
322 pub stencil_reference: u32,
327 pub order: DrawOrder,
333 pub range: DrawRange,
335}
336
337impl DrawEntry {
338 pub fn new(pipeline: &wgpu::RenderPipeline, range: DrawRange) -> Self {
342 Self {
343 pipeline: pipeline.clone(),
344 bind_groups: ArrayVec::new(),
345 vertex_buffers: ArrayVec::new(),
346 index_buffer: None,
347 scissor: None,
348 stencil_reference: 0,
349 order: DrawOrder::Before,
350 range,
351 }
352 }
353
354 pub fn with_bind_group(mut self, index: u32, bind_group: &wgpu::BindGroup) -> Self {
356 self.bind_groups.push((index, bind_group.clone()));
357 self
358 }
359
360 pub fn with_vertex_buffer(mut self, slot: u32, buffer: &wgpu::Buffer) -> Self {
362 self.vertex_buffers
363 .push((slot, buffer.clone(), 0..buffer.size()));
364 self
365 }
366
367 pub fn with_vertex_buffer_range(
369 mut self,
370 slot: u32,
371 buffer: &wgpu::Buffer,
372 range: Range<u64>,
373 ) -> Self {
374 self.vertex_buffers.push((slot, buffer.clone(), range));
375 self
376 }
377
378 pub fn with_scissor(mut self, scissor: ScissorRect) -> Self {
381 self.scissor = Some(scissor);
382 self
383 }
384
385 pub fn with_index_buffer(mut self, buffer: &wgpu::Buffer, format: wgpu::IndexFormat) -> Self {
387 self.index_buffer = Some((buffer.clone(), 0..buffer.size(), format));
388 self
389 }
390
391 pub fn with_order(mut self, order: DrawOrder) -> Self {
393 self.order = order;
394 self
395 }
396}
397
398#[derive(Clone, Debug, Default)]
410pub struct Scene {
411 pub draws: Vec<DrawEntry>,
413 pub viewport: Option<ViewportRect>,
420}
421
422impl Scene {
423 pub fn new() -> Self {
425 Self::default()
426 }
427
428 fn resolved_viewport(&self, fallback: Option<ViewportRect>) -> Option<ViewportRect> {
435 self.viewport.or(fallback)
436 }
437
438 pub fn record(&self, pass: &mut wgpu::RenderPass<'_>, fallback: Option<ViewportRect>) {
447 let mut state = PassState::default();
448 if let Some(viewport) = self.resolved_viewport(fallback) {
449 state.set_viewport(pass, viewport);
450 }
451 for draw in &self.draws {
452 record_draw(pass, &mut state, draw);
453 }
454 }
455
456 pub fn record_composited(
474 pass: &mut wgpu::RenderPass<'_>,
475 scenes: &[&Scene],
476 fallback: Option<ViewportRect>,
477 ) {
478 let mut state = PassState::default();
479 record_composited_into(pass, &mut state, scenes, fallback);
480 }
481}
482
483fn record_draw<'a, P: RenderPassInterface<'a>>(
489 pass: &mut P,
490 state: &mut PassState<'a>,
491 draw: &'a DrawEntry,
492) {
493 if state.pipeline != Some(&draw.pipeline) {
494 pass.set_pipeline(&draw.pipeline);
495 state.pipeline = Some(&draw.pipeline);
496 }
497 for (index, bind_group) in &draw.bind_groups {
498 state.set_bind_group(pass, *index, bind_group);
499 }
500 for (slot, buffer, range) in &draw.vertex_buffers {
501 state.set_vertex_buffer(pass, *slot, buffer, range.clone());
502 }
503 if let Some((buffer, range, format)) = &draw.index_buffer {
504 state.set_index_buffer(pass, buffer, range.clone(), *format);
505 }
506 if let Some(scissor) = &draw.scissor {
507 state.set_scissor(pass, *scissor);
508 }
509 state.set_stencil_reference(pass, draw.stencil_reference);
510
511 match &draw.range {
512 DrawRange::Vertices {
513 vertices,
514 instances,
515 } => pass.draw(vertices.clone(), instances.clone()),
516 DrawRange::Indexed {
517 indices,
518 base_vertex,
519 instances,
520 } => pass.draw_indexed(indices.clone(), *base_vertex, instances.clone()),
521 }
522}
523
524struct SortedDraw<'a> {
529 draw: &'a DrawEntry,
530 depth: f32,
531 viewport: Option<ViewportRect>,
532}
533
534fn record_composited_into<'a, P: RenderPassInterface<'a>>(
542 pass: &mut P,
543 state: &mut PassState<'a>,
544 scenes: &[&'a Scene],
545 fallback: Option<ViewportRect>,
546) {
547 for scene in scenes {
551 let viewport = scene.resolved_viewport(fallback);
552 for draw in &scene.draws {
553 match draw.order {
554 DrawOrder::Before => {
555 if let Some(viewport) = viewport {
556 state.set_viewport(pass, viewport);
557 }
558 record_draw(pass, state, draw);
559 }
560 DrawOrder::Sorted(_) | DrawOrder::After => {}
561 }
562 }
563 }
564
565 let mut pool: Vec<SortedDraw<'_>> = Vec::new();
570 for scene in scenes {
571 for draw in &scene.draws {
572 if let DrawOrder::Sorted(depth) = draw.order {
573 pool.push(SortedDraw {
574 draw,
575 depth,
576 viewport: scene.resolved_viewport(fallback),
577 });
578 }
579 }
580 }
581 pool.sort_by(|a, b| b.depth.total_cmp(&a.depth));
582 for sorted in &pool {
583 if let Some(viewport) = sorted.viewport {
584 state.set_viewport(pass, viewport);
585 }
586 record_draw(pass, state, sorted.draw);
587 }
588
589 for scene in scenes {
593 let viewport = scene.resolved_viewport(fallback);
594 for draw in &scene.draws {
595 match draw.order {
596 DrawOrder::After => {
597 if let Some(viewport) = viewport {
598 state.set_viewport(pass, viewport);
599 }
600 record_draw(pass, state, draw);
601 }
602 DrawOrder::Before | DrawOrder::Sorted(_) => {}
603 }
604 }
605 }
606}
607
608pub const MAX_BIND_GROUPS: usize = 8;
613
614pub const MAX_VERTEX_BUFFERS: usize = 16;
616
617trait RenderPassInterface<'a> {
624 fn set_bind_group(&mut self, index: u32, bind_group: &'a wgpu::BindGroup);
626 fn set_vertex_buffer(&mut self, slot: u32, buffer: &'a wgpu::Buffer, range: Range<u64>);
628 fn set_index_buffer(
630 &mut self,
631 buffer: &'a wgpu::Buffer,
632 range: Range<u64>,
633 format: wgpu::IndexFormat,
634 );
635 fn set_scissor_rect(&mut self, scissor: ScissorRect);
637 fn set_viewport_rect(&mut self, viewport: ViewportRect);
639 fn set_stencil_reference(&mut self, reference: u32);
641 fn set_pipeline(&mut self, pipeline: &'a wgpu::RenderPipeline);
643 fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>);
645 fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>);
647}
648
649impl<'a, 'p> RenderPassInterface<'a> for wgpu::RenderPass<'p> {
650 fn set_bind_group(&mut self, index: u32, bind_group: &'a wgpu::BindGroup) {
651 wgpu::RenderPass::set_bind_group(self, index, bind_group, &[]);
652 }
653
654 fn set_vertex_buffer(&mut self, slot: u32, buffer: &'a wgpu::Buffer, range: Range<u64>) {
655 wgpu::RenderPass::set_vertex_buffer(self, slot, buffer.slice(range));
656 }
657
658 fn set_index_buffer(
659 &mut self,
660 buffer: &'a wgpu::Buffer,
661 range: Range<u64>,
662 format: wgpu::IndexFormat,
663 ) {
664 wgpu::RenderPass::set_index_buffer(self, buffer.slice(range), format);
665 }
666
667 fn set_scissor_rect(&mut self, scissor: ScissorRect) {
668 wgpu::RenderPass::set_scissor_rect(
669 self,
670 scissor.x,
671 scissor.y,
672 scissor.width,
673 scissor.height,
674 );
675 }
676
677 fn set_viewport_rect(&mut self, viewport: ViewportRect) {
678 wgpu::RenderPass::set_viewport(
679 self,
680 viewport.x,
681 viewport.y,
682 viewport.width,
683 viewport.height,
684 viewport.min_depth,
685 viewport.max_depth,
686 );
687 }
688
689 fn set_stencil_reference(&mut self, reference: u32) {
690 wgpu::RenderPass::set_stencil_reference(self, reference);
691 }
692
693 fn set_pipeline(&mut self, pipeline: &'a wgpu::RenderPipeline) {
694 wgpu::RenderPass::set_pipeline(self, pipeline);
695 }
696
697 fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
698 wgpu::RenderPass::draw(self, vertices, instances);
699 }
700
701 fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
702 wgpu::RenderPass::draw_indexed(self, indices, base_vertex, instances);
703 }
704}
705
706#[derive(Default)]
715struct PassState<'a> {
716 pipeline: Option<&'a wgpu::RenderPipeline>,
717 bind_groups: ArrayVec<(u32, &'a wgpu::BindGroup), MAX_BIND_GROUPS>,
718 vertex_buffers: ArrayVec<(u32, &'a wgpu::Buffer, Range<u64>), MAX_VERTEX_BUFFERS>,
719 index_buffer: Option<(&'a wgpu::Buffer, Range<u64>, wgpu::IndexFormat)>,
720 scissor: Option<ScissorRect>,
721 viewport: Option<ViewportRect>,
722 stencil_reference: u32,
723}
724
725impl<'a> PassState<'a> {
726 fn set_bind_group(
727 &mut self,
728 pass: &mut impl RenderPassInterface<'a>,
729 index: u32,
730 bind_group: &'a wgpu::BindGroup,
731 ) {
732 if let Some(slot) = self
733 .bind_groups
734 .iter_mut()
735 .find(|(bound, _)| *bound == index)
736 {
737 if slot.1 == bind_group {
738 return;
739 }
740 slot.1 = bind_group;
741 } else {
742 assert!(
743 index < MAX_BIND_GROUPS as u32,
744 "bind group index {index} exceeds MAX_BIND_GROUPS ({MAX_BIND_GROUPS})"
745 );
746 self.bind_groups.push((index, bind_group));
747 }
748 pass.set_bind_group(index, bind_group);
749 }
750
751 fn set_vertex_buffer(
754 &mut self,
755 pass: &mut impl RenderPassInterface<'a>,
756 slot: u32,
757 buffer: &'a wgpu::Buffer,
758 range: Range<u64>,
759 ) {
760 if let Some(bound) = self
761 .vertex_buffers
762 .iter_mut()
763 .find(|(bound, _, _)| *bound == slot)
764 {
765 if core::ptr::eq(bound.1, buffer) && bound.2 == range {
766 return;
767 }
768 *bound = (slot, buffer, range.clone());
769 } else {
770 assert!(
771 slot < MAX_VERTEX_BUFFERS as u32,
772 "vertex buffer slot {slot} exceeds MAX_VERTEX_BUFFERS ({MAX_VERTEX_BUFFERS})"
773 );
774 self.vertex_buffers.push((slot, buffer, range.clone()));
775 }
776 pass.set_vertex_buffer(slot, buffer, range);
777 }
778
779 fn set_index_buffer(
780 &mut self,
781 pass: &mut impl RenderPassInterface<'a>,
782 buffer: &'a wgpu::Buffer,
783 range: Range<u64>,
784 format: wgpu::IndexFormat,
785 ) {
786 if let Some((bound, bound_range, bound_format)) = &self.index_buffer
787 && core::ptr::eq(*bound, buffer)
788 && *bound_range == range
789 && *bound_format == format
790 {
791 return;
792 }
793 pass.set_index_buffer(buffer, range.clone(), format);
794 self.index_buffer = Some((buffer, range, format));
795 }
796
797 fn set_scissor(&mut self, pass: &mut impl RenderPassInterface<'a>, scissor: ScissorRect) {
801 if self.scissor == Some(scissor) {
802 return;
803 }
804 self.scissor = Some(scissor);
805 pass.set_scissor_rect(scissor);
806 }
807
808 fn set_viewport(&mut self, pass: &mut impl RenderPassInterface<'a>, viewport: ViewportRect) {
811 if self.viewport == Some(viewport) {
812 return;
813 }
814 self.viewport = Some(viewport);
815 pass.set_viewport_rect(viewport);
816 }
817
818 fn set_stencil_reference(&mut self, pass: &mut impl RenderPassInterface<'a>, reference: u32) {
821 if self.stencil_reference == reference {
822 return;
823 }
824 self.stencil_reference = reference;
825 pass.set_stencil_reference(reference);
826 }
827}
828
829#[cfg(test)]
830mod tests {
831 use super::*;
832
833 const MINIMAL_WGSL: &str = r#"
836@vertex
837fn vs_main() -> @builtin(position) vec4<f32> {
838 return vec4<f32>(0.0, 0.0, 0.0, 1.0);
839}
840
841@fragment
842fn fs_main() -> @location(0) vec4<f32> {
843 return vec4<f32>(1.0);
844}
845"#;
846
847 fn noop_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
850 let module = crate::util::shader_module(device, MINIMAL_WGSL, "test::shader");
851 let targets = [Some(wgpu::ColorTargetState {
852 format: wgpu::TextureFormat::Rgba8UnormSrgb,
853 blend: None,
854 write_mask: wgpu::ColorWrites::ALL,
855 })];
856 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
857 label: Some("test::pipeline"),
858 layout: None,
859 vertex: wgpu::VertexState {
860 module: &module,
861 entry_point: Some("vs_main"),
862 compilation_options: Default::default(),
863 buffers: &[],
864 },
865 primitive: Default::default(),
866 depth_stencil: None,
867 multisample: Default::default(),
868 fragment: Some(wgpu::FragmentState {
869 module: &module,
870 entry_point: Some("fs_main"),
871 compilation_options: Default::default(),
872 targets: &targets,
873 }),
874 multiview_mask: None,
875 cache: None,
876 })
877 }
878
879 #[test]
880 fn draw_range_builders_compose() {
881 let range = DrawRange::vertices(0..36).with_instances(2..5);
882 match range {
883 DrawRange::Vertices {
884 vertices,
885 instances,
886 } => {
887 assert_eq!(vertices, 0..36);
888 assert_eq!(instances, 2..5);
889 }
890 other => panic!("expected a non-indexed draw, got {other:?}"),
891 }
892
893 let range = DrawRange::indexed(0..12)
894 .with_base_vertex(7)
895 .with_instances(0..3);
896 match range {
897 DrawRange::Indexed {
898 indices,
899 base_vertex,
900 instances,
901 } => {
902 assert_eq!(indices, 0..12);
903 assert_eq!(base_vertex, 7);
904 assert_eq!(instances, 0..3);
905 }
906 other => panic!("expected an indexed draw, got {other:?}"),
907 }
908 }
909
910 #[test]
911 fn base_vertex_is_ignored_for_non_indexed_draws() {
912 let range = DrawRange::vertices(0..3).with_base_vertex(9);
913 assert!(matches!(range, DrawRange::Vertices { .. }));
914 }
915
916 #[test]
917 fn scissor_rect_builders_compose() {
918 let rect = ScissorRect::new(64, 32).at(8, 16);
919 assert_eq!(
920 rect,
921 ScissorRect {
922 x: 8,
923 y: 16,
924 width: 64,
925 height: 32
926 }
927 );
928 assert_eq!(ScissorRect::new(64, 32), rect.at(0, 0));
929 }
930
931 #[test]
932 fn viewport_rect_builders_compose() {
933 let rect = ViewportRect::new(64.0, 32.0).at(8.5, 16.25);
934 assert_eq!(
935 rect,
936 ViewportRect {
937 x: 8.5,
938 y: 16.25,
939 width: 64.0,
940 height: 32.0,
941 min_depth: 0.0,
942 max_depth: 1.0,
943 }
944 );
945 assert_eq!(
946 rect.with_depth(0.5, 1.0).min_depth,
947 0.5,
948 "the depth range is the caller's to narrow"
949 );
950 }
951
952 #[test]
955 fn fitting_an_aspect_letterboxes_within_the_target() {
956 assert_eq!(
958 ViewportRect::fit_aspect(4.0 / 3.0, 800, 600),
959 ViewportRect::new(800.0, 600.0)
960 );
961
962 let wide = ViewportRect::fit_aspect(4.0 / 3.0, 1600, 600);
965 assert_eq!(wide, ViewportRect::new(800.0, 600.0).at(400.0, 0.0));
966 assert!((wide.width / wide.height - 4.0 / 3.0).abs() < 1e-5);
967 assert_eq!(wide.x + wide.width + wide.x, 1600.0, "centred horizontally");
968 assert_eq!(wide.y, 0.0);
969
970 let tall = ViewportRect::fit_aspect(4.0 / 3.0, 600, 900);
972 assert_eq!(tall, ViewportRect::new(600.0, 450.0).at(0.0, 225.0));
973 assert!((tall.width / tall.height - 4.0 / 3.0).abs() < 1e-5);
974 assert_eq!(tall.y + tall.height + tall.y, 900.0, "centred vertically");
975 assert_eq!(tall.x, 0.0);
976
977 assert_eq!((tall.min_depth, tall.max_depth), (0.0, 1.0));
979 }
980
981 #[test]
984 fn an_impossible_letterbox_is_the_whole_target() {
985 for aspect in [0.0, -1.0, f32::NAN, f32::INFINITY] {
986 assert_eq!(
987 ViewportRect::fit_aspect(aspect, 320, 240),
988 ViewportRect::new(320.0, 240.0),
989 "aspect {aspect} has no rectangle to fit"
990 );
991 }
992 for target in [(0, 0), (0, 240), (320, 0)] {
993 let fitted = ViewportRect::fit_aspect(4.0 / 3.0, target.0, target.1);
994 assert!(fitted.width >= 0.0 && fitted.height >= 0.0);
995 assert!(fitted.width.is_finite() && fitted.height.is_finite());
996 }
997 }
998
999 #[test]
1002 fn a_scene_records_only_the_viewport_it_states() {
1003 let own = ViewportRect::new(320.0, 180.0).at(0.0, 20.0);
1004 let scene = Scene {
1005 viewport: Some(own),
1006 ..Scene::default()
1007 };
1008 assert_eq!(scene.viewport, Some(own));
1009
1010 let plain = Scene::new();
1013 assert_eq!(plain.viewport, None);
1014 assert_eq!(plain.resolved_viewport(None), None);
1015 }
1016
1017 #[test]
1021 fn a_fallback_viewport_fills_in_for_a_scene_that_states_none() {
1022 let whole = ViewportRect::new(800.0, 600.0);
1023 let own = ViewportRect::new(320.0, 180.0).at(0.0, 20.0);
1024
1025 let plain = Scene::new();
1026 assert_eq!(plain.resolved_viewport(Some(whole)), Some(whole));
1027
1028 let letterboxed = Scene {
1031 viewport: Some(own),
1032 ..Scene::default()
1033 };
1034 assert_eq!(letterboxed.resolved_viewport(Some(whole)), Some(own));
1035
1036 assert_eq!(letterboxed.resolved_viewport(None), Some(own));
1038 }
1039
1040 #[test]
1043 fn viewport_calls_are_deduplicated() {
1044 let mut pass = MockPass::default();
1045 let mut state = PassState::default();
1046 let first = ViewportRect::new(320.0, 180.0);
1047 let second = ViewportRect::new(160.0, 90.0);
1048
1049 state.set_viewport(&mut pass, first);
1050 state.set_viewport(&mut pass, first);
1052 let nudged = ViewportRect::new(320.0, 180.0).at(0.5, 0.5);
1055 state.set_viewport(&mut pass, nudged);
1056 state.set_viewport(&mut pass, second);
1057
1058 assert_eq!(pass.viewports, vec![first, nudged, second]);
1059 assert_eq!(state.viewport, Some(second));
1060 }
1061
1062 #[test]
1065 fn draw_builders_keep_the_scissor() {
1066 let (device, _queue) = crate::util::test::noop_device();
1067 let pipeline = noop_pipeline(&device);
1068 let draw = DrawEntry::new(&pipeline, DrawRange::indexed(0..6))
1069 .with_scissor(ScissorRect::new(8, 8));
1070 assert_eq!(draw.scissor, Some(ScissorRect::new(8, 8)));
1071 assert!(matches!(
1072 draw.range,
1073 DrawRange::Indexed { ref indices, .. } if *indices == (0..6)
1074 ));
1075
1076 let plain = DrawEntry::new(&pipeline, DrawRange::vertices(0..3));
1078 assert_eq!(plain.scissor, None);
1079 }
1080
1081 #[test]
1084 fn stencil_reference_builder_composes() {
1085 let (device, _queue) = crate::util::test::noop_device();
1086 let pipeline = noop_pipeline(&device);
1087 let mut draw = DrawEntry::new(&pipeline, DrawRange::indexed(0..6))
1088 .with_scissor(ScissorRect::new(8, 8));
1089 draw.stencil_reference = 3;
1090 assert_eq!(draw.stencil_reference, 3);
1091 assert_eq!(draw.scissor, Some(ScissorRect::new(8, 8)));
1092
1093 let plain = DrawEntry::new(&pipeline, DrawRange::vertices(0..3));
1094 assert_eq!(plain.stencil_reference, 0);
1095 }
1096
1097 #[derive(Default)]
1101 struct MockPass {
1102 bind_groups: Vec<u32>,
1103 vertex_buffers: Vec<u32>,
1104 index_buffers: Vec<wgpu::IndexFormat>,
1105 scissors: Vec<ScissorRect>,
1106 viewports: Vec<ViewportRect>,
1107 stencil_references: Vec<u32>,
1108 draws: Vec<(Range<u32>, Range<u32>)>,
1109 indexed_draws: Vec<(Range<u32>, i32, Range<u32>)>,
1110 }
1111
1112 impl<'a> RenderPassInterface<'a> for MockPass {
1113 fn set_bind_group(&mut self, index: u32, _bind_group: &'a wgpu::BindGroup) {
1114 self.bind_groups.push(index);
1115 }
1116
1117 fn set_vertex_buffer(&mut self, slot: u32, _buffer: &'a wgpu::Buffer, _range: Range<u64>) {
1118 self.vertex_buffers.push(slot);
1119 }
1120
1121 fn set_index_buffer(
1122 &mut self,
1123 _buffer: &'a wgpu::Buffer,
1124 _range: Range<u64>,
1125 format: wgpu::IndexFormat,
1126 ) {
1127 self.index_buffers.push(format);
1128 }
1129
1130 fn set_scissor_rect(&mut self, scissor: ScissorRect) {
1131 self.scissors.push(scissor);
1132 }
1133
1134 fn set_viewport_rect(&mut self, viewport: ViewportRect) {
1135 self.viewports.push(viewport);
1136 }
1137
1138 fn set_stencil_reference(&mut self, reference: u32) {
1139 self.stencil_references.push(reference);
1140 }
1141
1142 fn set_pipeline(&mut self, _pipeline: &'a wgpu::RenderPipeline) {}
1143
1144 fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
1145 self.draws.push((vertices, instances));
1146 }
1147
1148 fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
1149 self.indexed_draws.push((indices, base_vertex, instances));
1150 }
1151 }
1152
1153 #[test]
1156 fn scissor_calls_are_deduplicated() {
1157 let mut pass = MockPass::default();
1158 let mut state = PassState::default();
1159 let first = ScissorRect::new(16, 8).at(4, 2);
1160 let second = ScissorRect::new(16, 8).at(0, 0);
1161
1162 state.set_scissor(&mut pass, first);
1163 state.set_scissor(&mut pass, first);
1165 state.set_scissor(&mut pass, second);
1166
1167 assert_eq!(pass.scissors, vec![first, second]);
1168 assert_eq!(state.scissor, Some(second));
1169 }
1170
1171 #[test]
1174 fn stencil_reference_calls_are_deduplicated() {
1175 let mut pass = MockPass::default();
1176 let mut state = PassState::default();
1177
1178 state.set_stencil_reference(&mut pass, 3);
1179 state.set_stencil_reference(&mut pass, 3);
1181 state.set_stencil_reference(&mut pass, 0);
1183
1184 assert_eq!(pass.stencil_references, vec![3, 0]);
1185 assert_eq!(state.stencil_reference, 0);
1186 }
1187
1188 #[test]
1192 fn bound_resources_are_deduplicated() {
1193 let (device, _queue) = crate::util::test::noop_device();
1194 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
1195 label: Some("test::buffer"),
1196 size: 64,
1197 usage: wgpu::BufferUsages::VERTEX
1198 | wgpu::BufferUsages::INDEX
1199 | wgpu::BufferUsages::UNIFORM,
1200 mapped_at_creation: false,
1201 });
1202 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1203 label: Some("test::layout"),
1204 entries: &[wgpu::BindGroupLayoutEntry {
1205 binding: 0,
1206 visibility: wgpu::ShaderStages::VERTEX,
1207 ty: wgpu::BindingType::Buffer {
1208 ty: wgpu::BufferBindingType::Uniform,
1209 has_dynamic_offset: false,
1210 min_binding_size: wgpu::BufferSize::new(buffer.size()),
1211 },
1212 count: None,
1213 }],
1214 });
1215 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1216 label: Some("test::bind_group"),
1217 layout: &layout,
1218 entries: &[wgpu::BindGroupEntry {
1219 binding: 0,
1220 resource: buffer.as_entire_binding(),
1221 }],
1222 });
1223
1224 let mut pass = MockPass::default();
1225 let mut state = PassState::default();
1226 let whole = 0..buffer.size();
1227 let head = 0..32;
1228 let tail = 32..64;
1229
1230 state.set_bind_group(&mut pass, 0, &bind_group);
1231 state.set_bind_group(&mut pass, 0, &bind_group);
1232 state.set_bind_group(&mut pass, 3, &bind_group);
1233
1234 state.set_vertex_buffer(&mut pass, 0, &buffer, whole.clone());
1235 state.set_vertex_buffer(&mut pass, 0, &buffer, whole.clone());
1236 state.set_vertex_buffer(&mut pass, 0, &buffer, head.clone());
1239 state.set_vertex_buffer(&mut pass, 2, &buffer, whole.clone());
1240
1241 state.set_index_buffer(&mut pass, &buffer, head.clone(), wgpu::IndexFormat::Uint16);
1242 state.set_index_buffer(&mut pass, &buffer, head.clone(), wgpu::IndexFormat::Uint16);
1243 state.set_index_buffer(&mut pass, &buffer, tail.clone(), wgpu::IndexFormat::Uint16);
1245 state.set_index_buffer(&mut pass, &buffer, tail.clone(), wgpu::IndexFormat::Uint32);
1246
1247 assert_eq!(pass.bind_groups, vec![0, 3]);
1248 assert_eq!(pass.vertex_buffers, vec![0, 0, 2]);
1251 assert_eq!(
1252 pass.index_buffers,
1253 vec![
1254 wgpu::IndexFormat::Uint16,
1255 wgpu::IndexFormat::Uint16,
1256 wgpu::IndexFormat::Uint32
1257 ]
1258 );
1259 }
1260
1261 #[test]
1262 fn pass_state_capacity_covers_the_wgpu_defaults() {
1263 assert!(MAX_BIND_GROUPS >= wgpu::Limits::default().max_bind_groups as usize);
1266 assert!(MAX_VERTEX_BUFFERS >= wgpu::Limits::default().max_vertex_buffers as usize);
1267 }
1268
1269 #[test]
1275 fn a_composite_orders_before_sorted_and_after() {
1276 let (device, _queue) = crate::util::test::noop_device();
1277 let pipeline = noop_pipeline(&device);
1278 let draw = |range: DrawRange| DrawEntry::new(&pipeline, range);
1279
1280 let meshes = Scene {
1282 draws: vec![
1283 draw(DrawRange::vertices(0..4).with_instances(0..1)),
1284 draw(DrawRange::vertices(0..4).with_instances(1..2))
1285 .with_order(DrawOrder::Sorted(7.0)),
1286 draw(DrawRange::vertices(0..4).with_instances(2..3))
1287 .with_order(DrawOrder::Sorted(2.0)),
1288 ],
1289 ..Scene::default()
1290 };
1291 let lines = Scene {
1293 draws: vec![
1294 draw(DrawRange::vertices(0..4).with_instances(3..4)),
1295 draw(DrawRange::vertices(0..4).with_instances(4..5))
1296 .with_order(DrawOrder::Sorted(4.0)),
1297 ],
1298 ..Scene::default()
1299 };
1300 let ui = Scene {
1302 draws: vec![
1303 draw(DrawRange::vertices(0..4).with_instances(5..6)).with_order(DrawOrder::After),
1304 ],
1305 ..Scene::default()
1306 };
1307
1308 let mut pass = MockPass::default();
1309 record_composited_into(
1310 &mut pass,
1311 &mut PassState::default(),
1312 &[&meshes, &lines, &ui],
1313 None,
1314 );
1315
1316 assert_eq!(
1319 pass.draws,
1320 vec![
1321 (0..4, 0..1),
1322 (0..4, 3..4),
1323 (0..4, 1..2),
1324 (0..4, 4..5),
1325 (0..4, 2..3),
1326 (0..4, 5..6),
1327 ]
1328 );
1329 }
1330
1331 #[test]
1335 fn a_composite_keeps_the_order_of_equal_draws() {
1336 let (device, _queue) = crate::util::test::noop_device();
1337 let pipeline = noop_pipeline(&device);
1338 let draw = |range: DrawRange| DrawEntry::new(&pipeline, range);
1339
1340 let meshes = Scene {
1341 draws: vec![
1342 draw(DrawRange::vertices(0..4).with_instances(0..1))
1343 .with_order(DrawOrder::Sorted(3.0)),
1344 draw(DrawRange::vertices(0..4).with_instances(1..2)),
1345 ],
1346 ..Scene::default()
1347 };
1348 let lines = Scene {
1349 draws: vec![
1350 draw(DrawRange::vertices(0..4).with_instances(2..3))
1351 .with_order(DrawOrder::Sorted(3.0)),
1352 draw(DrawRange::vertices(0..4).with_instances(3..4)).with_order(DrawOrder::After),
1353 ],
1354 ..Scene::default()
1355 };
1356
1357 let mut pass = MockPass::default();
1358 record_composited_into(
1359 &mut pass,
1360 &mut PassState::default(),
1361 &[&meshes, &lines],
1362 None,
1363 );
1364
1365 assert_eq!(
1366 pass.draws,
1367 vec![(0..4, 1..2), (0..4, 0..1), (0..4, 2..3), (0..4, 3..4)]
1368 );
1369 }
1370
1371 #[test]
1375 fn a_composite_without_sorted_draws_keeps_scene_order() {
1376 let (device, _queue) = crate::util::test::noop_device();
1377 let pipeline = noop_pipeline(&device);
1378 let draw = |range: DrawRange| DrawEntry::new(&pipeline, range);
1379
1380 let lines = Scene {
1383 draws: vec![draw(DrawRange::vertices(0..4).with_instances(0..1))],
1384 ..Scene::default()
1385 };
1386 let meshes = Scene {
1387 draws: vec![draw(DrawRange::vertices(0..4).with_instances(1..2))],
1388 ..Scene::default()
1389 };
1390
1391 let mut pass = MockPass::default();
1392 record_composited_into(
1393 &mut pass,
1394 &mut PassState::default(),
1395 &[&lines, &meshes],
1396 None,
1397 );
1398
1399 assert_eq!(pass.draws, vec![(0..4, 0..1), (0..4, 1..2)]);
1400 }
1401
1402 #[test]
1406 fn a_composite_draws_into_each_scenes_own_region() {
1407 let (device, _queue) = crate::util::test::noop_device();
1408 let pipeline = noop_pipeline(&device);
1409 let draw = |range: DrawRange| DrawEntry::new(&pipeline, range);
1410
1411 let letter = ViewportRect::new(320.0, 180.0).at(0.0, 20.0);
1412 let whole = ViewportRect::new(800.0, 600.0);
1413 let meshes = Scene {
1414 viewport: Some(letter),
1415 draws: vec![draw(DrawRange::vertices(0..4).with_instances(0..1))],
1416 };
1417 let lines = Scene {
1418 draws: vec![
1419 draw(DrawRange::vertices(0..4).with_instances(1..2))
1420 .with_order(DrawOrder::Sorted(3.0)),
1421 ],
1422 ..Scene::default()
1423 };
1424 let ui = Scene {
1425 draws: vec![
1426 draw(DrawRange::vertices(0..4).with_instances(2..3)).with_order(DrawOrder::After),
1427 ],
1428 ..Scene::default()
1429 };
1430
1431 let mut pass = MockPass::default();
1432 record_composited_into(
1433 &mut pass,
1434 &mut PassState::default(),
1435 &[&meshes, &lines, &ui],
1436 Some(whole),
1437 );
1438
1439 assert_eq!(pass.draws.len(), 3);
1440 assert_eq!(pass.viewports, vec![letter, whole]);
1444 }
1445}