1use bevy_material::descriptor::{
2 BindGroupLayoutDescriptor, CachedComputePipelineId, ComputePipelineDescriptor,
3 MeshPipelineDescriptor, PipelineDescriptor, RenderPipelineDescriptor,
4};
5use smallvec::SmallVec;
6
7use crate::{
8 render_resource::*,
9 renderer::{wgpu_wrapper, RenderDevice},
10 Extract,
11};
12use alloc::{borrow::Cow, sync::Arc};
13use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
14use bevy_ecs::{
15 message::MessageReader,
16 resource::Resource,
17 system::{Res, ResMut},
18};
19use bevy_log::error;
20use bevy_platform::collections::{hash_map::RawEntryMut, HashMap, HashSet};
21use bevy_shader::{
22 CachedPipelineId, Shader, ShaderCache, ShaderCacheError, ShaderCacheSource, ShaderDefVal,
23 ValidateShader,
24};
25use bevy_tasks::Task;
26use bevy_utils::default;
27use core::{future::Future, mem};
28use std::sync::{Mutex, PoisonError};
29use wgpu::{PipelineCompilationOptions, VertexBufferLayout as RawVertexBufferLayout};
30
31#[derive(Debug)]
35pub enum Pipeline {
36 RenderPipeline(RenderPipeline),
37 ComputePipeline(ComputePipeline),
38 MeshPipeline(RenderPipeline),
39}
40
41pub struct CachedPipeline {
42 pub descriptor: PipelineDescriptor,
43 pub state: CachedPipelineState,
44}
45
46#[derive(Debug)]
48pub enum CachedPipelineState {
49 Queued,
51 Creating(Task<Result<Pipeline, ShaderCacheError>>),
53 Ok(Pipeline),
55 Err(ShaderCacheError),
57}
58
59impl CachedPipelineState {
60 pub fn unwrap(&self) -> &Pipeline {
71 match self {
72 CachedPipelineState::Ok(pipeline) => pipeline,
73 CachedPipelineState::Queued => {
74 panic!("Pipeline has not been compiled yet. It is still in the 'Queued' state.")
75 }
76 CachedPipelineState::Creating(..) => {
77 panic!("Pipeline has not been compiled yet. It is still in the 'Creating' state.")
78 }
79 CachedPipelineState::Err(err) => panic!("{}", err),
80 }
81 }
82}
83
84const BIND_GROUP_LAYOUTS_INLINE_CAPACITY: usize = 8;
87
88type ImmediateSize = u32;
89type LayoutCacheKey = (
90 SmallVec<[BindGroupLayoutId; BIND_GROUP_LAYOUTS_INLINE_CAPACITY]>,
91 ImmediateSize,
92);
93
94wgpu_wrapper! {
95 struct WgpuPipelineLayout(PipelineLayout);
96
97 struct WgpuShaderModule(ShaderModule);
98}
99
100#[derive(Default)]
101struct LayoutCache {
102 layouts: HashMap<LayoutCacheKey, Arc<WgpuPipelineLayout>>,
103}
104
105impl LayoutCache {
106 fn get(
107 &mut self,
108 render_device: &RenderDevice,
109 bind_group_layouts: &[BindGroupLayout],
110 immediate_size: u32,
111 ) -> Arc<WgpuPipelineLayout> {
112 let bind_group_ids = bind_group_layouts.iter().map(BindGroupLayout::id).collect();
113 self.layouts
114 .entry((bind_group_ids, immediate_size))
115 .or_insert_with_key(|(_, immediate_size)| {
116 let bind_group_layouts = bind_group_layouts
117 .iter()
118 .map(BindGroupLayout::value)
119 .map(Some)
120 .collect::<SmallVec<[_; BIND_GROUP_LAYOUTS_INLINE_CAPACITY]>>();
121 Arc::new(WgpuPipelineLayout::new(
122 render_device.create_pipeline_layout(&PipelineLayoutDescriptor {
123 bind_group_layouts: &bind_group_layouts,
124 immediate_size: *immediate_size,
125 ..default()
126 }),
127 ))
128 })
129 .clone()
130 }
131}
132
133fn load_module(
134 render_device: &RenderDevice,
135 shader_source: ShaderCacheSource,
136 validate_shader: &ValidateShader,
137) -> Result<WgpuShaderModule, ShaderCacheError> {
138 let shader_source = match shader_source {
139 #[cfg(feature = "shader_format_spirv")]
140 ShaderCacheSource::SpirV(data) => wgpu::util::make_spirv(data),
141 #[cfg(not(feature = "shader_format_spirv"))]
142 ShaderCacheSource::SpirV(_) => {
143 unimplemented!("Enable feature \"shader_format_spirv\" to use SPIR-V shaders")
144 }
145 ShaderCacheSource::Wgsl(src) => ShaderSource::Wgsl(Cow::Owned(src)),
146 };
147 let module_descriptor = ShaderModuleDescriptor {
148 label: None,
149 source: shader_source,
150 };
151
152 let scope = render_device
153 .wgpu_device()
154 .push_error_scope(wgpu::ErrorFilter::Validation);
155
156 let shader_module = WgpuShaderModule::new(match validate_shader {
157 ValidateShader::Enabled => {
158 render_device.create_and_validate_shader_module(module_descriptor)
159 }
160 ValidateShader::Disabled => unsafe {
164 render_device.create_shader_module(module_descriptor)
165 },
166 });
167
168 let error = scope.pop();
169
170 if let Some(Some(wgpu::Error::Validation { description, .. })) =
175 bevy_tasks::futures::now_or_never(error)
176 {
177 return Err(ShaderCacheError::CreateShaderModule(description));
178 }
179
180 Ok(shader_module)
181}
182
183#[derive(Default)]
184struct BindGroupLayoutCache {
185 bgls: HashMap<BindGroupLayoutDescriptor, BindGroupLayout>,
186}
187
188impl BindGroupLayoutCache {
189 fn get(
190 &mut self,
191 render_device: &RenderDevice,
192 descriptor: &BindGroupLayoutDescriptor,
193 ) -> BindGroupLayout {
194 match self.bgls.raw_entry_mut().from_key(descriptor) {
195 RawEntryMut::Occupied(entry) => entry.into_mut(),
196 RawEntryMut::Vacant(slot) => {
197 let created = render_device
198 .create_bind_group_layout(descriptor.label.as_ref(), &descriptor.entries);
199 slot.insert(descriptor.clone(), created).1
200 }
201 }
202 .clone()
203 }
204}
205
206#[derive(Resource)]
219pub struct PipelineCache {
220 layout_cache: Arc<Mutex<LayoutCache>>,
221 bindgroup_layout_cache: Arc<Mutex<BindGroupLayoutCache>>,
222 shader_cache: Arc<Mutex<ShaderCache<WgpuShaderModule, RenderDevice>>>,
223 device: RenderDevice,
224 pipelines: Vec<CachedPipeline>,
225 waiting_pipelines: HashSet<CachedPipelineId>,
226 new_pipelines: Mutex<Vec<CachedPipeline>>,
227 global_shader_defs: Vec<ShaderDefVal>,
228 pub(crate) synchronous_pipeline_compilation: bool,
231 needs_shader_reload: bool,
233}
234
235impl PipelineCache {
236 pub fn pipelines(&self) -> impl Iterator<Item = &CachedPipeline> {
238 self.pipelines.iter()
239 }
240
241 pub fn waiting_pipelines(&self) -> impl Iterator<Item = CachedPipelineId> + '_ {
243 self.waiting_pipelines.iter().copied()
244 }
245
246 pub fn new(device: RenderDevice, synchronous_pipeline_compilation: bool) -> Self {
248 let mut global_shader_defs = Vec::new();
249 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
250 {
251 global_shader_defs.push("NO_ARRAY_TEXTURES_SUPPORT".into());
252 global_shader_defs.push("NO_CUBE_ARRAY_TEXTURES_SUPPORT".into());
253 global_shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into());
254 }
255
256 if cfg!(target_abi = "sim") {
257 global_shader_defs.push("NO_CUBE_ARRAY_TEXTURES_SUPPORT".into());
258 }
259
260 let available_storage_buffer_bindings =
261 device.limits().max_storage_buffers_per_shader_stage;
262 global_shader_defs.push(ShaderDefVal::UInt(
263 "AVAILABLE_STORAGE_BUFFER_BINDINGS".into(),
264 available_storage_buffer_bindings,
265 ));
266 global_shader_defs.push(ShaderDefVal::Bool(
268 "AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_3".into(),
269 available_storage_buffer_bindings >= 3,
270 ));
271 global_shader_defs.push(ShaderDefVal::Bool(
272 "AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_6".into(),
273 available_storage_buffer_bindings >= 6,
274 ));
275
276 Self {
277 shader_cache: Arc::new(Mutex::new(ShaderCache::new(device.clone(), load_module))),
278 device,
279 layout_cache: default(),
280 bindgroup_layout_cache: default(),
281 waiting_pipelines: default(),
282 new_pipelines: default(),
283 pipelines: default(),
284 global_shader_defs,
285 synchronous_pipeline_compilation,
286 needs_shader_reload: true,
287 }
288 }
289
290 #[inline]
294 pub fn get_render_pipeline_state(&self, id: CachedRenderPipelineId) -> &CachedPipelineState {
295 self.pipelines
297 .get(id.id())
298 .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state)
299 }
300
301 #[inline]
305 pub fn get_compute_pipeline_state(&self, id: CachedComputePipelineId) -> &CachedPipelineState {
306 self.pipelines
308 .get(id.id())
309 .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state)
310 }
311
312 #[inline]
319 pub fn get_render_pipeline_descriptor(
320 &self,
321 id: CachedRenderPipelineId,
322 ) -> &RenderPipelineDescriptor {
323 match &self.pipelines[id.id()].descriptor {
324 PipelineDescriptor::RenderPipelineDescriptor(descriptor) => descriptor,
325 PipelineDescriptor::ComputePipelineDescriptor(_)
326 | PipelineDescriptor::MeshPipelineDescriptor(_) => unreachable!(),
327 }
328 }
329
330 #[inline]
337 pub fn get_compute_pipeline_descriptor(
338 &self,
339 id: CachedComputePipelineId,
340 ) -> &ComputePipelineDescriptor {
341 match &self.pipelines[id.id()].descriptor {
342 PipelineDescriptor::RenderPipelineDescriptor(_)
343 | PipelineDescriptor::MeshPipelineDescriptor(_) => unreachable!(),
344 PipelineDescriptor::ComputePipelineDescriptor(descriptor) => descriptor,
345 }
346 }
347
348 #[inline]
355 pub fn get_mesh_pipeline_descriptor(
356 &self,
357 id: CachedRenderPipelineId,
358 ) -> &MeshPipelineDescriptor {
359 match &self.pipelines[id.id()].descriptor {
360 PipelineDescriptor::RenderPipelineDescriptor(_)
361 | PipelineDescriptor::ComputePipelineDescriptor(_) => unreachable!(),
362 PipelineDescriptor::MeshPipelineDescriptor(descriptor) => descriptor,
363 }
364 }
365
366 #[inline]
374 pub fn get_render_pipeline(&self, id: CachedRenderPipelineId) -> Option<&RenderPipeline> {
375 if let CachedPipelineState::Ok(
376 Pipeline::RenderPipeline(pipeline) | Pipeline::MeshPipeline(pipeline),
377 ) = &self.pipelines.get(id.id())?.state
378 {
379 Some(pipeline)
380 } else {
381 None
382 }
383 }
384
385 #[inline]
387 pub fn block_on_render_pipeline(&mut self, id: CachedRenderPipelineId) {
388 if self.pipelines.len() <= id.id() {
389 self.process_queue();
390 }
391
392 let state = &mut self.pipelines[id.id()].state;
393 if let CachedPipelineState::Creating(task) = state {
394 *state = match bevy_tasks::block_on(task) {
395 Ok(p) => CachedPipelineState::Ok(p),
396 Err(e) => CachedPipelineState::Err(e),
397 };
398 }
399 }
400
401 #[inline]
409 pub fn get_compute_pipeline(&self, id: CachedComputePipelineId) -> Option<&ComputePipeline> {
410 if let CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) =
411 &self.pipelines.get(id.id())?.state
412 {
413 Some(pipeline)
414 } else {
415 None
416 }
417 }
418
419 pub fn queue_render_pipeline(
433 &self,
434 descriptor: RenderPipelineDescriptor,
435 ) -> CachedRenderPipelineId {
436 let mut new_pipelines = self
437 .new_pipelines
438 .lock()
439 .unwrap_or_else(PoisonError::into_inner);
440 let id = CachedRenderPipelineId::new(self.pipelines.len() + new_pipelines.len());
441 new_pipelines.push(CachedPipeline {
442 descriptor: PipelineDescriptor::RenderPipelineDescriptor(Box::new(descriptor)),
443 state: CachedPipelineState::Queued,
444 });
445 id
446 }
447
448 pub fn queue_compute_pipeline(
462 &self,
463 descriptor: ComputePipelineDescriptor,
464 ) -> CachedComputePipelineId {
465 let mut new_pipelines = self
466 .new_pipelines
467 .lock()
468 .unwrap_or_else(PoisonError::into_inner);
469 let id = CachedComputePipelineId::new(self.pipelines.len() + new_pipelines.len());
470 new_pipelines.push(CachedPipeline {
471 descriptor: PipelineDescriptor::ComputePipelineDescriptor(Box::new(descriptor)),
472 state: CachedPipelineState::Queued,
473 });
474 id
475 }
476
477 pub fn queue_mesh_pipeline(
491 &self,
492 descriptor: MeshPipelineDescriptor,
493 ) -> CachedRenderPipelineId {
494 let mut new_pipelines = self
495 .new_pipelines
496 .lock()
497 .unwrap_or_else(PoisonError::into_inner);
498 let id = CachedRenderPipelineId::new(self.pipelines.len() + new_pipelines.len());
499 new_pipelines.push(CachedPipeline {
500 descriptor: PipelineDescriptor::MeshPipelineDescriptor(Box::new(descriptor)),
501 state: CachedPipelineState::Queued,
502 });
503 id
504 }
505
506 pub fn get_bind_group_layout(
507 &self,
508 bind_group_layout_descriptor: &BindGroupLayoutDescriptor,
509 ) -> BindGroupLayout {
510 self.bindgroup_layout_cache
511 .lock()
512 .unwrap()
513 .get(&self.device, bind_group_layout_descriptor)
514 }
515
516 pub fn set_shader(&mut self, id: AssetId<Shader>, shader: Shader) {
518 let mut shader_cache = self.shader_cache.lock().unwrap();
519 let pipelines_to_queue = shader_cache.set_shader(id, shader);
520 for cached_pipeline in pipelines_to_queue {
521 self.pipelines[cached_pipeline].state = CachedPipelineState::Queued;
522 self.waiting_pipelines.insert(cached_pipeline);
523 }
524 }
525
526 pub fn remove_shader(&mut self, shader: AssetId<Shader>) {
528 let mut shader_cache = self.shader_cache.lock().unwrap();
529 let pipelines_to_queue = shader_cache.remove(shader);
530 for cached_pipeline in pipelines_to_queue {
531 self.pipelines[cached_pipeline].state = CachedPipelineState::Queued;
532 self.waiting_pipelines.insert(cached_pipeline);
533 }
534 }
535
536 fn start_create_render_pipeline(
537 &mut self,
538 id: CachedPipelineId,
539 descriptor: RenderPipelineDescriptor,
540 ) -> CachedPipelineState {
541 let device = self.device.clone();
542 let shader_cache = self.shader_cache.clone();
543 let layout_cache = self.layout_cache.clone();
544 let mut bindgroup_layout_cache = self.bindgroup_layout_cache.lock().unwrap();
545 let bind_group_layout = descriptor
546 .layout
547 .iter()
548 .map(|bind_group_layout_descriptor| {
549 bindgroup_layout_cache.get(&self.device, bind_group_layout_descriptor)
550 })
551 .collect::<SmallVec<[_; BIND_GROUP_LAYOUTS_INLINE_CAPACITY]>>();
552
553 create_pipeline_task(
554 async move {
555 let mut shader_cache = shader_cache.lock().unwrap();
556 let mut layout_cache = layout_cache.lock().unwrap();
557
558 let vertex_module = match shader_cache.get(
559 id,
560 descriptor.vertex.shader.id(),
561 &descriptor.vertex.shader_defs,
562 ) {
563 Ok(module) => module,
564 Err(err) => return Err(err),
565 };
566
567 let fragment_module = match &descriptor.fragment {
568 Some(fragment) => {
569 match shader_cache.get(id, fragment.shader.id(), &fragment.shader_defs) {
570 Ok(module) => Some(module),
571 Err(err) => return Err(err),
572 }
573 }
574 None => None,
575 };
576
577 let layout = if descriptor.layout.is_empty() && descriptor.immediate_size == 0 {
578 None
579 } else {
580 Some(layout_cache.get(&device, &bind_group_layout, descriptor.immediate_size))
581 };
582
583 drop((shader_cache, layout_cache));
584
585 let vertex_buffer_layouts = descriptor
586 .vertex
587 .buffers
588 .iter()
589 .map(|layout| {
590 Some(RawVertexBufferLayout {
591 array_stride: layout.array_stride,
592 attributes: &layout.attributes,
593 step_mode: layout.step_mode,
594 })
595 })
596 .collect::<Vec<_>>();
597
598 let fragment_data = descriptor.fragment.as_ref().map(|fragment| {
599 (
600 fragment_module.unwrap(),
601 fragment.entry_point.as_deref(),
602 fragment.targets.as_slice(),
603 fragment
604 .constants
605 .iter()
606 .map(|(k, v)| (k.as_ref(), *v))
607 .collect::<Vec<_>>(),
608 )
609 });
610
611 let vertex_constants: Vec<(&str, f64)> = descriptor
612 .vertex
613 .constants
614 .iter()
615 .map(|(k, v)| (k.as_ref(), *v))
616 .collect();
617
618 let descriptor = RawRenderPipelineDescriptor {
619 multiview_mask: None,
620 depth_stencil: descriptor.depth_stencil.clone(),
621 label: descriptor.label.as_deref(),
622 layout: layout.as_ref().map(|layout| -> &PipelineLayout { layout }),
623 multisample: descriptor.multisample,
624 primitive: descriptor.primitive,
625 vertex: RawVertexState {
626 buffers: &vertex_buffer_layouts,
627 entry_point: descriptor.vertex.entry_point.as_deref(),
628 module: &vertex_module,
629 compilation_options: PipelineCompilationOptions {
630 constants: &vertex_constants,
631 zero_initialize_workgroup_memory: descriptor
632 .zero_initialize_workgroup_memory,
633 },
634 },
635 fragment: fragment_data.as_ref().map(
636 |(module, entry_point, targets, constants)| RawFragmentState {
637 entry_point: entry_point.as_deref(),
638 module,
639 targets,
640 compilation_options: PipelineCompilationOptions {
641 constants,
642 zero_initialize_workgroup_memory: descriptor
643 .zero_initialize_workgroup_memory,
644 },
645 },
646 ),
647 cache: None,
648 };
649
650 Ok(Pipeline::RenderPipeline(
651 device.create_render_pipeline(&descriptor),
652 ))
653 },
654 self.synchronous_pipeline_compilation,
655 )
656 }
657
658 fn start_create_compute_pipeline(
659 &mut self,
660 id: CachedPipelineId,
661 descriptor: ComputePipelineDescriptor,
662 ) -> CachedPipelineState {
663 let device = self.device.clone();
664 let shader_cache = self.shader_cache.clone();
665 let layout_cache = self.layout_cache.clone();
666 let mut bindgroup_layout_cache = self.bindgroup_layout_cache.lock().unwrap();
667 let bind_group_layout = descriptor
668 .layout
669 .iter()
670 .map(|bind_group_layout_descriptor| {
671 bindgroup_layout_cache.get(&self.device, bind_group_layout_descriptor)
672 })
673 .collect::<SmallVec<[_; BIND_GROUP_LAYOUTS_INLINE_CAPACITY]>>();
674
675 create_pipeline_task(
676 async move {
677 let mut shader_cache = shader_cache.lock().unwrap();
678 let mut layout_cache = layout_cache.lock().unwrap();
679
680 let compute_module =
681 match shader_cache.get(id, descriptor.shader.id(), &descriptor.shader_defs) {
682 Ok(module) => module,
683 Err(err) => return Err(err),
684 };
685
686 let layout = if descriptor.layout.is_empty() && descriptor.immediate_size == 0 {
687 None
688 } else {
689 Some(layout_cache.get(&device, &bind_group_layout, descriptor.immediate_size))
690 };
691
692 drop((shader_cache, layout_cache));
693
694 let constants: Vec<(&str, f64)> = descriptor
695 .constants
696 .iter()
697 .map(|(k, v)| (k.as_ref(), *v))
698 .collect();
699 let descriptor = RawComputePipelineDescriptor {
700 label: descriptor.label.as_deref(),
701 layout: layout.as_ref().map(|layout| -> &PipelineLayout { layout }),
702 module: &compute_module,
703 entry_point: descriptor.entry_point.as_deref(),
704 compilation_options: PipelineCompilationOptions {
705 constants: &constants,
706 zero_initialize_workgroup_memory: descriptor
707 .zero_initialize_workgroup_memory,
708 },
709 cache: None,
710 };
711
712 Ok(Pipeline::ComputePipeline(
713 device.create_compute_pipeline(&descriptor),
714 ))
715 },
716 self.synchronous_pipeline_compilation,
717 )
718 }
719
720 fn start_create_mesh_pipeline(
721 &mut self,
722 id: CachedPipelineId,
723 descriptor: MeshPipelineDescriptor,
724 ) -> CachedPipelineState {
725 let device = self.device.clone();
726 let shader_cache = self.shader_cache.clone();
727 let layout_cache = self.layout_cache.clone();
728 let mut bindgroup_layout_cache = self.bindgroup_layout_cache.lock().unwrap();
729 let bind_group_layout = descriptor
730 .layout
731 .iter()
732 .map(|bind_group_layout_descriptor| {
733 bindgroup_layout_cache.get(&self.device, bind_group_layout_descriptor)
734 })
735 .collect::<SmallVec<[_; BIND_GROUP_LAYOUTS_INLINE_CAPACITY]>>();
736
737 create_pipeline_task(
738 async move {
739 let mut shader_cache = shader_cache.lock().unwrap();
740 let mut layout_cache = layout_cache.lock().unwrap();
741
742 let mesh_module = match shader_cache.get(
743 id,
744 descriptor.mesh.shader.id(),
745 &descriptor.mesh.shader_defs,
746 ) {
747 Ok(module) => module,
748 Err(err) => return Err(err),
749 };
750
751 let fragment_module = match &descriptor.fragment {
752 Some(fragment) => {
753 match shader_cache.get(id, fragment.shader.id(), &fragment.shader_defs) {
754 Ok(module) => Some(module),
755 Err(err) => return Err(err),
756 }
757 }
758 None => None,
759 };
760
761 let task_module = match &descriptor.task {
762 Some(task) => match shader_cache.get(id, task.shader.id(), &task.shader_defs) {
763 Ok(module) => Some(module),
764 Err(err) => return Err(err),
765 },
766 None => None,
767 };
768
769 let layout = if descriptor.layout.is_empty() && descriptor.immediate_size == 0 {
770 None
771 } else {
772 Some(layout_cache.get(&device, &bind_group_layout, descriptor.immediate_size))
773 };
774
775 drop((shader_cache, layout_cache));
776
777 let fragment_data = descriptor.fragment.as_ref().map(|fragment| {
778 (
779 fragment_module.unwrap(),
780 fragment.entry_point.as_deref(),
781 fragment.targets.as_slice(),
782 fragment
783 .constants
784 .iter()
785 .map(|(k, v)| (k.as_ref(), *v))
786 .collect::<Vec<_>>(),
787 )
788 });
789
790 let mesh_constants: Vec<(&str, f64)> = descriptor
791 .mesh
792 .constants
793 .iter()
794 .map(|(k, v)| (k.as_ref(), *v))
795 .collect();
796
797 let task_data = descriptor.task.as_ref().map(|task| {
798 (
799 task_module.unwrap(),
800 task.entry_point.as_deref(),
801 task.constants
802 .iter()
803 .map(|(k, v)| (k.as_ref(), *v))
804 .collect::<Vec<_>>(),
805 )
806 });
807
808 let descriptor = RawMeshPipelineDescriptor {
809 multiview: None,
810 depth_stencil: descriptor.depth_stencil.clone(),
811 label: descriptor.label.as_deref(),
812 layout: layout.as_ref().map(|layout| -> &PipelineLayout { layout }),
813 multisample: descriptor.multisample,
814 primitive: descriptor.primitive,
815 task: task_data
816 .as_ref()
817 .map(|(module, entry_point, constants)| RawTaskState {
818 entry_point: entry_point.as_deref(),
819 module,
820 compilation_options: PipelineCompilationOptions {
821 constants,
822 zero_initialize_workgroup_memory: descriptor
823 .zero_initialize_workgroup_memory,
824 },
825 }),
826 mesh: RawMeshState {
827 module: &mesh_module,
828 entry_point: descriptor.mesh.entry_point.as_deref(),
829 compilation_options: PipelineCompilationOptions {
830 constants: &mesh_constants,
831 zero_initialize_workgroup_memory: descriptor
832 .zero_initialize_workgroup_memory,
833 },
834 },
835 fragment: fragment_data.as_ref().map(
836 |(module, entry_point, targets, constants)| RawFragmentState {
837 entry_point: entry_point.as_deref(),
838 module,
839 targets,
840 compilation_options: PipelineCompilationOptions {
841 constants,
842 zero_initialize_workgroup_memory: descriptor
843 .zero_initialize_workgroup_memory,
844 },
845 },
846 ),
847 cache: None,
848 };
849
850 Ok(Pipeline::MeshPipeline(
851 device.create_mesh_pipeline(&descriptor),
852 ))
853 },
854 self.synchronous_pipeline_compilation,
855 )
856 }
857 pub fn process_queue(&mut self) {
864 let mut waiting_pipelines = mem::take(&mut self.waiting_pipelines);
865 let mut pipelines = mem::take(&mut self.pipelines);
866
867 {
868 let mut new_pipelines = self
869 .new_pipelines
870 .lock()
871 .unwrap_or_else(PoisonError::into_inner);
872 for new_pipeline in new_pipelines.drain(..) {
873 let id = pipelines.len();
874 pipelines.push(new_pipeline);
875 waiting_pipelines.insert(id);
876 }
877 }
878
879 for id in waiting_pipelines {
880 self.process_pipeline(&mut pipelines[id], id);
881 }
882
883 self.pipelines = pipelines;
884 }
885
886 fn process_pipeline(&mut self, cached_pipeline: &mut CachedPipeline, id: usize) {
887 match &mut cached_pipeline.state {
888 CachedPipelineState::Queued => {
889 cached_pipeline.state = match &cached_pipeline.descriptor {
890 PipelineDescriptor::RenderPipelineDescriptor(descriptor) => {
891 self.start_create_render_pipeline(id, *descriptor.clone())
892 }
893 PipelineDescriptor::ComputePipelineDescriptor(descriptor) => {
894 self.start_create_compute_pipeline(id, *descriptor.clone())
895 }
896 PipelineDescriptor::MeshPipelineDescriptor(descriptor) => {
897 self.start_create_mesh_pipeline(id, *descriptor.clone())
898 }
899 };
900 }
901
902 CachedPipelineState::Creating(task) => match bevy_tasks::futures::check_ready(task) {
903 Some(Ok(pipeline)) => {
904 cached_pipeline.state = CachedPipelineState::Ok(pipeline);
905 return;
906 }
907 Some(Err(err)) => cached_pipeline.state = CachedPipelineState::Err(err),
908 _ => (),
909 },
910
911 CachedPipelineState::Err(err) => match err {
912 ShaderCacheError::ShaderNotLoaded(_)
914 | ShaderCacheError::ShaderImportNotYetAvailable => {
915 bevy_log::debug!("retry processing pipeline {id}: {err}");
916 cached_pipeline.state = CachedPipelineState::Queued;
917 }
918
919 ShaderCacheError::ProcessShaderError(error_detail) => {
921 let error_detail = error_detail.clone();
922 if std::env::var("VERBOSE_SHADER_ERROR")
923 .is_ok_and(|v| !(v.is_empty() || v == "0" || v == "false"))
924 {
925 error!("{}", pipeline_error_context(cached_pipeline));
926 }
927 error!("failed to process shader error:\n{}", error_detail);
928 return;
929 }
930 ShaderCacheError::CreateShaderModule(description) => {
931 error!("failed to create shader module: {}", description);
932 return;
933 }
934 },
935
936 CachedPipelineState::Ok(_) => return,
937 }
938
939 self.waiting_pipelines.insert(id);
941 }
942
943 pub(crate) fn process_pipeline_queue_system(mut cache: ResMut<Self>) {
944 cache.process_queue();
945 }
946
947 pub(crate) fn extract_shaders(
948 mut cache: ResMut<Self>,
949 shaders: Extract<Res<Assets<Shader>>>,
950 mut events: Extract<MessageReader<AssetEvent<Shader>>>,
951 ) {
952 if cache.needs_shader_reload {
953 cache.needs_shader_reload = false;
954 for (id, shader) in shaders.iter() {
955 let mut shader = shader.clone();
956 shader
957 .shader_defs
958 .splice(0..0, cache.global_shader_defs.iter().cloned());
959 cache.set_shader(id, shader);
960 }
961 for _ in events.read() {}
963 return;
964 }
965
966 for event in events.read() {
967 #[expect(
968 clippy::match_same_arms,
969 reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon."
970 )]
971 match event {
972 AssetEvent::Added { id } | AssetEvent::Modified { id } => {
974 if let Some(shader) = shaders.get(*id) {
975 let mut shader = shader.clone();
976 shader
977 .shader_defs
978 .splice(0..0, cache.global_shader_defs.iter().cloned());
979
980 cache.set_shader(*id, shader);
981 }
982 }
983 AssetEvent::Removed { id } => cache.remove_shader(*id),
984 AssetEvent::Unused { .. } => {}
985 AssetEvent::LoadedWithDependencies { .. } => {
986 }
988 }
989 }
990 }
991}
992
993fn pipeline_error_context(cached_pipeline: &CachedPipeline) -> String {
994 fn format(
995 shader: &Handle<Shader>,
996 entry: &Option<Cow<'static, str>>,
997 shader_defs: &[ShaderDefVal],
998 ) -> String {
999 let source = match shader.path() {
1000 Some(path) => path.path().to_string_lossy().to_string(),
1001 None => String::new(),
1002 };
1003 let entry = match entry {
1004 Some(entry) => entry.to_string(),
1005 None => String::new(),
1006 };
1007 let shader_defs = shader_defs
1008 .iter()
1009 .flat_map(|def| match def {
1010 ShaderDefVal::Bool(k, v) if *v => Some(k.to_string()),
1011 ShaderDefVal::Int(k, v) => Some(format!("{k} = {v}")),
1012 ShaderDefVal::UInt(k, v) => Some(format!("{k} = {v}")),
1013 _ => None,
1014 })
1015 .collect::<Vec<_>>()
1016 .join(", ");
1017 format!("{source}:{entry}\nshader defs: {shader_defs}")
1018 }
1019 match &cached_pipeline.descriptor {
1020 PipelineDescriptor::RenderPipelineDescriptor(desc) => {
1021 let vert = &desc.vertex;
1022 let vert_str = format(&vert.shader, &vert.entry_point, &vert.shader_defs);
1023 let Some(frag) = desc.fragment.as_ref() else {
1024 return vert_str;
1025 };
1026 let frag_str = format(&frag.shader, &frag.entry_point, &frag.shader_defs);
1027 format!("vertex {vert_str}\nfragment {frag_str}")
1028 }
1029 PipelineDescriptor::ComputePipelineDescriptor(desc) => {
1030 format(&desc.shader, &desc.entry_point, &desc.shader_defs)
1031 }
1032 PipelineDescriptor::MeshPipelineDescriptor(desc) => {
1033 let mesh = &desc.mesh;
1034 let mesh_str = format(&mesh.shader, &mesh.entry_point, &mesh.shader_defs);
1035
1036 let task = &desc.task;
1037 let task_str = task
1038 .as_ref()
1039 .map(|task| format(&task.shader, &task.entry_point, &task.shader_defs));
1040
1041 let frag = &desc.fragment;
1042 let frag_str = frag
1043 .as_ref()
1044 .map(|frag| format(&frag.shader, &frag.entry_point, &frag.shader_defs));
1045
1046 match (task_str, frag_str) {
1047 (None, None) => format!("mesh {mesh_str}"),
1048 (None, Some(frag_str)) => format!("mesh {mesh_str}\nfragment {frag_str}"),
1049 (Some(task_str), None) => format!("task {task_str}\nmesh {mesh_str}"),
1050 (Some(task_str), Some(frag_str)) => {
1051 format!("task {task_str}\nmesh {mesh_str}\nfragment {frag_str}")
1052 }
1053 }
1054 }
1055 }
1056}
1057
1058#[cfg(all(
1059 not(target_arch = "wasm32"),
1060 not(target_os = "macos"),
1061 feature = "multi_threaded"
1062))]
1063fn create_pipeline_task(
1064 task: impl Future<Output = Result<Pipeline, ShaderCacheError>> + Send + 'static,
1065 sync: bool,
1066) -> CachedPipelineState {
1067 if !sync {
1068 return CachedPipelineState::Creating(bevy_tasks::AsyncComputeTaskPool::get().spawn(task));
1069 }
1070
1071 match bevy_tasks::block_on(task) {
1072 Ok(pipeline) => CachedPipelineState::Ok(pipeline),
1073 Err(err) => CachedPipelineState::Err(err),
1074 }
1075}
1076
1077#[cfg(any(
1078 target_arch = "wasm32",
1079 target_os = "macos",
1080 not(feature = "multi_threaded")
1081))]
1082fn create_pipeline_task(
1083 task: impl Future<Output = Result<Pipeline, ShaderCacheError>> + Send + 'static,
1084 _sync: bool,
1085) -> CachedPipelineState {
1086 match bevy_tasks::block_on(task) {
1087 Ok(pipeline) => CachedPipelineState::Ok(pipeline),
1088 Err(err) => CachedPipelineState::Err(err),
1089 }
1090}