1use core::hash::Hash;
28use std::sync::Arc;
29
30use arrayvec::ArrayVec;
31use hashbrown::HashMap;
32use smallvec::SmallVec;
33
34use crate::render_attachments::RenderAttachments;
35use crate::scene::MAX_VERTEX_BUFFERS;
36use crate::util::Hashed;
37
38pub trait PipelineDescriptor<P>: Clone {
49 fn create(&self, device: &wgpu::Device) -> P;
51}
52
53#[derive(Clone, Debug)]
60pub struct SpecializedPipeline<P, D> {
61 pub pipeline: P,
63 pub descriptor: D,
65}
66
67impl<P, D: PipelineDescriptor<P>> SpecializedPipeline<P, D> {
68 pub fn create(device: &wgpu::Device, descriptor: D) -> Self {
70 Self {
71 pipeline: descriptor.create(device),
72 descriptor,
73 }
74 }
75
76 pub fn descriptor(&self) -> &D {
78 &self.descriptor
79 }
80}
81
82pub trait PipelineVariant<P>: Clone + Hash + Eq + 'static {
94 type Descriptor: PipelineDescriptor<P>;
96
97 fn descriptor(&self, device: &wgpu::Device) -> Self::Descriptor;
102}
103
104pub struct Variants<P, V: PipelineVariant<P>> {
112 device: wgpu::Device,
114 keys: HashMap<V, u32>,
116 variants: Vec<SpecializedPipeline<P, V::Descriptor>>,
118}
119
120impl<P, V: PipelineVariant<P>> Variants<P, V> {
121 pub fn new(device: &wgpu::Device) -> Self {
123 Self {
124 device: device.clone(),
125 keys: HashMap::new(),
126 variants: Vec::new(),
127 }
128 }
129
130 pub fn specialize(&mut self, variant: V) -> u32 {
135 if let Some(&index) = self.keys.get(&variant) {
136 return index;
137 }
138
139 let descriptor = variant.descriptor(&self.device);
140 let specialized = SpecializedPipeline::create(&self.device, descriptor);
141 let index = u32::try_from(self.variants.len()).expect("variant count fits in u32");
142 self.variants.push(specialized);
143 self.keys.insert(variant, index);
144 index
145 }
146
147 pub fn get(&self, index: u32) -> &SpecializedPipeline<P, V::Descriptor> {
152 &self.variants[index as usize]
153 }
154}
155
156pub type VertexAttributes = SmallVec<[wgpu::VertexAttribute; 8]>;
161
162#[derive(Clone, Debug, PartialEq, Eq, Hash)]
169pub struct VertexBufferLayoutDesc {
170 pub array_stride: wgpu::BufferAddress,
172 pub step_mode: wgpu::VertexStepMode,
174 pub attributes: VertexAttributes,
176}
177
178impl VertexBufferLayoutDesc {
179 pub fn from_wgpu(layout: &wgpu::VertexBufferLayout<'_>) -> Self {
181 Self {
182 array_stride: layout.array_stride,
183 step_mode: layout.step_mode,
184 attributes: layout.attributes.into(),
185 }
186 }
187
188 pub(crate) fn as_wgpu(&self) -> wgpu::VertexBufferLayout<'_> {
191 wgpu::VertexBufferLayout {
192 array_stride: self.array_stride,
193 step_mode: self.step_mode,
194 attributes: &self.attributes,
195 }
196 }
197}
198
199#[derive(Clone, Debug, PartialEq, Eq, Hash)]
212pub struct VertexLayout {
213 inner: Arc<Hashed<ArrayVec<(u32, VertexBufferLayoutDesc), MAX_VERTEX_BUFFERS>>>,
219}
220
221impl VertexLayout {
222 pub fn new(buffers: impl IntoIterator<Item = (u32, VertexBufferLayoutDesc)>) -> Self {
229 let buffers: ArrayVec<_, MAX_VERTEX_BUFFERS> = buffers.into_iter().collect();
230 Self {
231 inner: Arc::new(Hashed::new(buffers)),
232 }
233 }
234}
235
236impl Default for VertexLayout {
237 fn default() -> Self {
239 Self::new([])
240 }
241}
242
243impl core::ops::Deref for VertexLayout {
244 type Target = [(u32, VertexBufferLayoutDesc)];
245
246 fn deref(&self) -> &Self::Target {
247 &self.inner
248 }
249}
250
251#[derive(Clone, Debug, PartialEq)]
253pub struct CompilationOptions {
254 pub constants: Vec<(String, f64)>,
256 pub zero_initialize_workgroup_memory: bool,
258}
259
260impl CompilationOptions {
261 fn from_wgpu(options: &wgpu::PipelineCompilationOptions<'_>) -> Self {
263 Self {
264 constants: options
265 .constants
266 .iter()
267 .map(|(name, value)| ((*name).to_owned(), *value))
268 .collect(),
269 zero_initialize_workgroup_memory: options.zero_initialize_workgroup_memory,
270 }
271 }
272}
273
274impl Default for CompilationOptions {
275 fn default() -> Self {
276 Self {
277 constants: Vec::new(),
278 zero_initialize_workgroup_memory: true,
279 }
280 }
281}
282
283#[derive(Clone, Debug, PartialEq)]
285pub struct VertexStateDesc {
286 pub module: wgpu::ShaderModule,
288 pub entry_point: Option<String>,
290 pub compilation_options: CompilationOptions,
292 pub buffers: Vec<Option<VertexBufferLayoutDesc>>,
294}
295
296impl VertexStateDesc {
297 fn from_wgpu(state: &wgpu::VertexState<'_>) -> Self {
299 Self {
300 module: state.module.clone(),
301 entry_point: state.entry_point.map(str::to_owned),
302 compilation_options: CompilationOptions::from_wgpu(&state.compilation_options),
303 buffers: state
304 .buffers
305 .iter()
306 .map(|buffer| buffer.as_ref().map(VertexBufferLayoutDesc::from_wgpu))
307 .collect(),
308 }
309 }
310}
311
312#[derive(Clone, Debug, PartialEq)]
314pub struct FragmentStateDesc {
315 pub module: wgpu::ShaderModule,
317 pub entry_point: Option<String>,
319 pub compilation_options: CompilationOptions,
321 pub targets: Vec<Option<wgpu::ColorTargetState>>,
323}
324
325impl FragmentStateDesc {
326 fn from_wgpu(state: &wgpu::FragmentState<'_>) -> Self {
328 Self {
329 module: state.module.clone(),
330 entry_point: state.entry_point.map(str::to_owned),
331 compilation_options: CompilationOptions::from_wgpu(&state.compilation_options),
332 targets: state.targets.to_vec(),
333 }
334 }
335}
336
337#[derive(Clone, Debug, PartialEq)]
344pub struct RenderPipelineDesc {
345 pub label: Option<String>,
347 pub layout: Option<wgpu::PipelineLayout>,
349 pub vertex: VertexStateDesc,
351 pub primitive: wgpu::PrimitiveState,
353 pub depth_stencil: Option<wgpu::DepthStencilState>,
355 pub multisample: wgpu::MultisampleState,
357 pub fragment: Option<FragmentStateDesc>,
359 pub multiview_mask: Option<core::num::NonZeroU32>,
361 pub cache: Option<wgpu::PipelineCache>,
363}
364
365impl RenderPipelineDesc {
366 pub fn from_wgpu(descriptor: &wgpu::RenderPipelineDescriptor<'_>) -> Self {
369 Self {
370 label: descriptor.label.map(str::to_owned),
371 layout: descriptor.layout.cloned(),
372 vertex: VertexStateDesc::from_wgpu(&descriptor.vertex),
373 primitive: descriptor.primitive,
374 depth_stencil: descriptor.depth_stencil.clone(),
375 multisample: descriptor.multisample,
376 fragment: descriptor
377 .fragment
378 .as_ref()
379 .map(FragmentStateDesc::from_wgpu),
380 multiview_mask: descriptor.multiview_mask,
381 cache: descriptor.cache.cloned(),
382 }
383 }
384}
385
386impl PipelineDescriptor<wgpu::RenderPipeline> for RenderPipelineDesc {
387 fn create(&self, device: &wgpu::Device) -> wgpu::RenderPipeline {
392 let buffers: Vec<Option<wgpu::VertexBufferLayout<'_>>> = self
393 .vertex
394 .buffers
395 .iter()
396 .map(|buffer| buffer.as_ref().map(VertexBufferLayoutDesc::as_wgpu))
397 .collect();
398 let vertex_constants: Vec<(&str, f64)> = self
399 .vertex
400 .compilation_options
401 .constants
402 .iter()
403 .map(|(name, value)| (name.as_str(), *value))
404 .collect();
405 let fragment_constants: Vec<(&str, f64)> = self
406 .fragment
407 .as_ref()
408 .map(|fragment| {
409 fragment
410 .compilation_options
411 .constants
412 .iter()
413 .map(|(name, value)| (name.as_str(), *value))
414 .collect()
415 })
416 .unwrap_or_default();
417
418 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
419 label: self.label.as_deref(),
420 layout: self.layout.as_ref(),
421 vertex: wgpu::VertexState {
422 module: &self.vertex.module,
423 entry_point: self.vertex.entry_point.as_deref(),
424 compilation_options: wgpu::PipelineCompilationOptions {
425 constants: &vertex_constants,
426 zero_initialize_workgroup_memory: self
427 .vertex
428 .compilation_options
429 .zero_initialize_workgroup_memory,
430 },
431 buffers: &buffers,
432 },
433 primitive: self.primitive,
434 depth_stencil: self.depth_stencil.clone(),
435 multisample: self.multisample,
436 fragment: self.fragment.as_ref().map(|fragment| wgpu::FragmentState {
437 module: &fragment.module,
438 entry_point: fragment.entry_point.as_deref(),
439 compilation_options: wgpu::PipelineCompilationOptions {
440 constants: &fragment_constants,
441 zero_initialize_workgroup_memory: fragment
442 .compilation_options
443 .zero_initialize_workgroup_memory,
444 },
445 targets: &fragment.targets,
446 }),
447 multiview_mask: self.multiview_mask,
448 cache: self.cache.as_ref(),
449 })
450 }
451}
452
453#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
458pub struct SurfaceKey {
459 pub color_format: wgpu::TextureFormat,
461 pub depth_stencil_format: Option<wgpu::TextureFormat>,
463 pub sample_count: u32,
465}
466
467impl SurfaceKey {
468 pub fn from_attachments(attachments: &RenderAttachments) -> Self {
476 Self {
477 color_format: attachments
478 .color_format()
479 .unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb),
480 depth_stencil_format: attachments.depth_stencil_format(),
481 sample_count: attachments.sample_count(),
482 }
483 }
484}
485
486pub trait SurfaceTarget {
494 fn set_surface(&mut self, surface: SurfaceKey);
496}
497
498impl SurfaceTarget for RenderPipelineDesc {
499 fn set_surface(&mut self, surface: SurfaceKey) {
508 self.multisample.count = surface.sample_count;
509 if let Some(fragment) = &mut self.fragment {
510 for target in fragment.targets.iter_mut().flatten() {
511 target.format = surface.color_format;
512 }
513 }
514 match surface.depth_stencil_format {
515 Some(format) => {
516 if let Some(depth_stencil) = &mut self.depth_stencil {
517 depth_stencil.format = format;
518 }
519 }
520 None => self.depth_stencil = None,
521 }
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use std::collections::hash_map::DefaultHasher;
528 use std::hash::Hasher;
529
530 use super::*;
531
532 #[derive(Clone, Debug, PartialEq)]
534 struct TestDescriptor {
535 base: u32,
537 rewritten: u32,
539 }
540
541 type TestPipeline = u64;
544
545 impl PipelineDescriptor<TestPipeline> for TestDescriptor {
546 fn create(&self, _device: &wgpu::Device) -> TestPipeline {
547 u64::from(self.rewritten)
548 }
549 }
550
551 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
553 struct TestVariant {
554 id: u32,
555 }
556
557 impl PipelineVariant<TestPipeline> for TestVariant {
558 type Descriptor = TestDescriptor;
559
560 fn descriptor(&self, _device: &wgpu::Device) -> TestDescriptor {
561 TestDescriptor {
562 base: 7,
563 rewritten: self.id,
564 }
565 }
566 }
567
568 fn variants() -> Variants<TestPipeline, TestVariant> {
569 let (device, _queue) = crate::util::test::noop_device();
570 Variants::new(&device)
571 }
572
573 fn created<P, V: PipelineVariant<P>>(variants: &Variants<P, V>) -> usize {
575 variants.variants.len()
576 }
577
578 #[test]
579 fn variant_is_created_once_per_variant() {
580 let mut variants = variants();
581
582 let first = variants.specialize(TestVariant { id: 1 });
583 let again = variants.specialize(TestVariant { id: 1 });
584 assert_eq!(first, again, "the same variant reuses its pipeline");
585 assert_eq!(created(&variants), 1);
586
587 let second = variants.specialize(TestVariant { id: 2 });
588 assert_ne!(first, second, "a different variant gets a new pipeline");
589 assert_eq!(created(&variants), 2);
590 }
591
592 #[test]
593 fn each_variant_keeps_its_own_descriptor() {
594 let mut variants = variants();
595
596 let first = variants.specialize(TestVariant { id: 1 });
597 let second = variants.specialize(TestVariant { id: 2 });
598
599 assert_eq!(
602 variants.get(first).descriptor(),
603 &TestDescriptor {
604 base: 7,
605 rewritten: 1,
606 }
607 );
608 assert_eq!(
609 variants.get(second).descriptor(),
610 &TestDescriptor {
611 base: 7,
612 rewritten: 2,
613 }
614 );
615 }
616
617 const MINIMAL_WGSL: &str = "\
618@vertex
619fn vs_main() -> @builtin(position) vec4<f32> {
620 return vec4<f32>(0.0, 0.0, 0.0, 1.0);
621}
622
623@fragment
624fn fs_main() -> @location(0) vec4<f32> {
625 return vec4<f32>(1.0);
626}
627";
628
629 const COMPUTE_WGSL: &str = "\
631@compute @workgroup_size(1)
632fn main() {
633}
634";
635
636 fn source_descriptor<'a>(
638 module: &'a wgpu::ShaderModule,
639 targets: &'a [Option<wgpu::ColorTargetState>],
640 ) -> wgpu::RenderPipelineDescriptor<'a> {
641 wgpu::RenderPipelineDescriptor {
642 label: Some("test"),
643 layout: None,
644 vertex: wgpu::VertexState {
645 module,
646 entry_point: Some("vs_main"),
647 compilation_options: Default::default(),
648 buffers: &[],
649 },
650 primitive: Default::default(),
651 depth_stencil: None,
652 multisample: Default::default(),
653 fragment: Some(wgpu::FragmentState {
654 module,
655 entry_point: Some("fs_main"),
656 compilation_options: Default::default(),
657 targets,
658 }),
659 multiview_mask: None,
660 cache: None,
661 }
662 }
663
664 #[test]
665 fn render_pipeline_desc_survives_its_source() {
666 let (device, _queue) = crate::util::test::noop_device();
667 let module = crate::util::shader_module(&device, MINIMAL_WGSL, "test::shader");
668
669 let owned = {
670 let targets = [Some(wgpu::ColorTargetState {
671 format: wgpu::TextureFormat::Rgba8UnormSrgb,
672 blend: None,
673 write_mask: wgpu::ColorWrites::ALL,
674 })];
675 let source = source_descriptor(&module, &targets);
676 RenderPipelineDesc::from_wgpu(&source)
677 };
678
679 let _pipeline = owned.create(&device);
682 }
683
684 #[test]
685 fn vertex_buffer_layout_desc_round_trips() {
686 let attributes = [
687 wgpu::VertexAttribute {
688 format: wgpu::VertexFormat::Float32x3,
689 offset: 0,
690 shader_location: 0,
691 },
692 wgpu::VertexAttribute {
693 format: wgpu::VertexFormat::Unorm8x4,
694 offset: 12,
695 shader_location: 1,
696 },
697 ];
698 let layout = wgpu::VertexBufferLayout {
699 array_stride: 16,
700 step_mode: wgpu::VertexStepMode::Vertex,
701 attributes: &attributes,
702 };
703
704 let owned = VertexBufferLayoutDesc::from_wgpu(&layout);
705 let round_tripped = owned.as_wgpu();
706
707 assert_eq!(round_tripped.array_stride, layout.array_stride);
708 assert_eq!(round_tripped.step_mode, layout.step_mode);
709 assert_eq!(round_tripped.attributes, layout.attributes);
710 }
711
712 #[test]
716 fn a_compute_pipeline_variant_is_cached() {
717 #[derive(Clone, Debug, PartialEq)]
720 struct ComputeDesc {
721 workgroup: u32,
722 }
723
724 impl PipelineDescriptor<wgpu::ComputePipeline> for ComputeDesc {
725 fn create(&self, device: &wgpu::Device) -> wgpu::ComputePipeline {
726 let module = crate::util::shader_module(device, COMPUTE_WGSL, "test::compute");
727 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
728 label: Some("test::compute::layout"),
729 bind_group_layouts: &[],
730 immediate_size: 0,
731 });
732 let _ = self.workgroup;
733 device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
734 label: Some("test::compute"),
735 layout: Some(&layout),
736 module: &module,
737 entry_point: Some("main"),
738 compilation_options: Default::default(),
739 cache: None,
740 })
741 }
742 }
743
744 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
747 struct WorkgroupVariant {
748 workgroup: u32,
749 }
750
751 impl PipelineVariant<wgpu::ComputePipeline> for WorkgroupVariant {
752 type Descriptor = ComputeDesc;
753
754 fn descriptor(&self, _device: &wgpu::Device) -> ComputeDesc {
755 ComputeDesc {
756 workgroup: self.workgroup,
757 }
758 }
759 }
760
761 let (device, _queue) = crate::util::test::noop_device();
762 let mut variants = Variants::new(&device);
763
764 let first = variants.specialize(WorkgroupVariant { workgroup: 8 });
765 assert_eq!(
766 variants.specialize(WorkgroupVariant { workgroup: 8 }),
767 first,
768 "one variant per workgroup size"
769 );
770 assert_ne!(
771 variants.specialize(WorkgroupVariant { workgroup: 16 }),
772 first
773 );
774 assert_eq!(created(&variants), 2);
775 }
776
777 fn layout(array_stride: u64) -> VertexBufferLayoutDesc {
778 VertexBufferLayoutDesc {
779 array_stride,
780 step_mode: wgpu::VertexStepMode::Vertex,
781 attributes: VertexAttributes::new(),
782 }
783 }
784
785 fn hash_of(layout: &VertexLayout) -> u64 {
786 let mut hasher = DefaultHasher::new();
787 layout.hash(&mut hasher);
788 hasher.finish()
789 }
790
791 #[test]
792 fn layouts_with_equal_buffers_are_equal_and_hash_alike() {
793 let one = VertexLayout::new([(0, layout(12)), (1, layout(24))]);
794 let other = VertexLayout::new([(0, layout(12)), (1, layout(24))]);
795
796 assert_eq!(one, other, "separately built layouts compare equal");
797 assert_eq!(
798 hash_of(&one),
799 hash_of(&other),
800 "equal layouts must hash alike, since the hash is precomputed"
801 );
802 }
803
804 #[test]
805 fn layouts_that_differ_are_not_equal() {
806 let one = VertexLayout::new([(0, layout(12))]);
807 let other = VertexLayout::new([(0, layout(16))]);
808
809 assert_ne!(one, other);
810 }
811
812 #[test]
816 fn an_empty_layout_is_not_equal_to_a_populated_one() {
817 assert_ne!(
818 VertexLayout::default(),
819 VertexLayout::new([(0, layout(12))])
820 );
821 }
822}