1use bevy_material::descriptor::{
2 BindGroupLayoutDescriptor, CachedComputePipelineId, CachedRenderPipelineId,
3 ComputePipelineDescriptor, PipelineDescriptor, RenderPipelineDescriptor,
4};
5
6use crate::{
7 render_resource::*,
8 renderer::{RenderAdapter, RenderDevice, WgpuWrapper},
9 Extract,
10};
11use alloc::{borrow::Cow, sync::Arc};
12use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
13use bevy_ecs::{
14 message::MessageReader,
15 resource::Resource,
16 system::{Res, ResMut},
17};
18use bevy_log::error;
19use bevy_platform::collections::{HashMap, HashSet};
20use bevy_shader::{
21 CachedPipelineId, Shader, ShaderCache, ShaderCacheError, ShaderCacheSource, ShaderDefVal,
22 ValidateShader,
23};
24use bevy_tasks::Task;
25use bevy_utils::default;
26use core::{future::Future, mem};
27use std::sync::{Mutex, PoisonError};
28use wgpu::{PipelineCompilationOptions, VertexBufferLayout as RawVertexBufferLayout};
29
30#[derive(Debug)]
34pub enum Pipeline {
35 RenderPipeline(RenderPipeline),
36 ComputePipeline(ComputePipeline),
37}
38
39pub struct CachedPipeline {
40 pub descriptor: PipelineDescriptor,
41 pub state: CachedPipelineState,
42}
43
44#[derive(Debug)]
46pub enum CachedPipelineState {
47 Queued,
49 Creating(Task<Result<Pipeline, ShaderCacheError>>),
51 Ok(Pipeline),
53 Err(ShaderCacheError),
55}
56
57impl CachedPipelineState {
58 pub fn unwrap(&self) -> &Pipeline {
69 match self {
70 CachedPipelineState::Ok(pipeline) => pipeline,
71 CachedPipelineState::Queued => {
72 panic!("Pipeline has not been compiled yet. It is still in the 'Queued' state.")
73 }
74 CachedPipelineState::Creating(..) => {
75 panic!("Pipeline has not been compiled yet. It is still in the 'Creating' state.")
76 }
77 CachedPipelineState::Err(err) => panic!("{}", err),
78 }
79 }
80}
81
82type ImmediateSize = u32;
83type LayoutCacheKey = (Vec<BindGroupLayoutId>, ImmediateSize);
84#[derive(Default)]
85struct LayoutCache {
86 layouts: HashMap<LayoutCacheKey, Arc<WgpuWrapper<PipelineLayout>>>,
87}
88
89impl LayoutCache {
90 fn get(
91 &mut self,
92 render_device: &RenderDevice,
93 bind_group_layouts: &[BindGroupLayout],
94 immediate_size: u32,
95 ) -> Arc<WgpuWrapper<PipelineLayout>> {
96 let bind_group_ids = bind_group_layouts.iter().map(BindGroupLayout::id).collect();
97 self.layouts
98 .entry((bind_group_ids, immediate_size))
99 .or_insert_with_key(|(_, immediate_size)| {
100 let bind_group_layouts = bind_group_layouts
101 .iter()
102 .map(BindGroupLayout::value)
103 .map(Some)
104 .collect::<Vec<_>>();
105 Arc::new(WgpuWrapper::new(render_device.create_pipeline_layout(
106 &PipelineLayoutDescriptor {
107 bind_group_layouts: &bind_group_layouts,
108 immediate_size: *immediate_size,
109 ..default()
110 },
111 )))
112 })
113 .clone()
114 }
115}
116
117fn load_module(
118 render_device: &RenderDevice,
119 shader_source: ShaderCacheSource,
120 validate_shader: &ValidateShader,
121) -> Result<WgpuWrapper<ShaderModule>, ShaderCacheError> {
122 let shader_source = match shader_source {
123 #[cfg(feature = "shader_format_spirv")]
124 ShaderCacheSource::SpirV(data) => wgpu::util::make_spirv(data),
125 #[cfg(not(feature = "shader_format_spirv"))]
126 ShaderCacheSource::SpirV(_) => {
127 unimplemented!("Enable feature \"shader_format_spirv\" to use SPIR-V shaders")
128 }
129 ShaderCacheSource::Wgsl(src) => ShaderSource::Wgsl(Cow::Owned(src)),
130 #[cfg(not(feature = "decoupled_naga"))]
131 ShaderCacheSource::Naga(src) => ShaderSource::Naga(Cow::Owned(src)),
132 };
133 let module_descriptor = ShaderModuleDescriptor {
134 label: None,
135 source: shader_source,
136 };
137
138 let scope = render_device
139 .wgpu_device()
140 .push_error_scope(wgpu::ErrorFilter::Validation);
141
142 let shader_module = WgpuWrapper::new(match validate_shader {
143 ValidateShader::Enabled => {
144 render_device.create_and_validate_shader_module(module_descriptor)
145 }
146 ValidateShader::Disabled => unsafe {
150 render_device.create_shader_module(module_descriptor)
151 },
152 });
153
154 let error = scope.pop();
155
156 if let Some(Some(wgpu::Error::Validation { description, .. })) =
161 bevy_tasks::futures::now_or_never(error)
162 {
163 return Err(ShaderCacheError::CreateShaderModule(description));
164 }
165
166 Ok(shader_module)
167}
168
169#[derive(Default)]
170struct BindGroupLayoutCache {
171 bgls: HashMap<BindGroupLayoutDescriptor, BindGroupLayout>,
172}
173
174impl BindGroupLayoutCache {
175 fn get(
176 &mut self,
177 render_device: &RenderDevice,
178 descriptor: BindGroupLayoutDescriptor,
179 ) -> BindGroupLayout {
180 self.bgls
181 .entry(descriptor)
182 .or_insert_with_key(|descriptor| {
183 render_device
184 .create_bind_group_layout(descriptor.label.as_ref(), &descriptor.entries)
185 })
186 .clone()
187 }
188}
189
190#[derive(Resource)]
203pub struct PipelineCache {
204 layout_cache: Arc<Mutex<LayoutCache>>,
205 bindgroup_layout_cache: Arc<Mutex<BindGroupLayoutCache>>,
206 shader_cache: Arc<Mutex<ShaderCache<WgpuWrapper<ShaderModule>, RenderDevice>>>,
207 device: RenderDevice,
208 pipelines: Vec<CachedPipeline>,
209 waiting_pipelines: HashSet<CachedPipelineId>,
210 new_pipelines: Mutex<Vec<CachedPipeline>>,
211 global_shader_defs: Vec<ShaderDefVal>,
212 pub(crate) synchronous_pipeline_compilation: bool,
215 needs_shader_reload: bool,
217}
218
219impl PipelineCache {
220 pub fn pipelines(&self) -> impl Iterator<Item = &CachedPipeline> {
222 self.pipelines.iter()
223 }
224
225 pub fn waiting_pipelines(&self) -> impl Iterator<Item = CachedPipelineId> + '_ {
227 self.waiting_pipelines.iter().copied()
228 }
229
230 pub fn new(
232 device: RenderDevice,
233 render_adapter: RenderAdapter,
234 synchronous_pipeline_compilation: bool,
235 ) -> Self {
236 let mut global_shader_defs = Vec::new();
237 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
238 {
239 global_shader_defs.push("NO_ARRAY_TEXTURES_SUPPORT".into());
240 global_shader_defs.push("NO_CUBE_ARRAY_TEXTURES_SUPPORT".into());
241 global_shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into());
242 }
243
244 if cfg!(target_abi = "sim") {
245 global_shader_defs.push("NO_CUBE_ARRAY_TEXTURES_SUPPORT".into());
246 }
247
248 global_shader_defs.push(ShaderDefVal::UInt(
249 String::from("AVAILABLE_STORAGE_BUFFER_BINDINGS"),
250 device.limits().max_storage_buffers_per_shader_stage,
251 ));
252
253 Self {
254 shader_cache: Arc::new(Mutex::new(ShaderCache::new(
255 device.clone(),
256 device.features(),
257 render_adapter.get_downlevel_capabilities().flags,
258 load_module,
259 ))),
260 device,
261 layout_cache: default(),
262 bindgroup_layout_cache: default(),
263 waiting_pipelines: default(),
264 new_pipelines: default(),
265 pipelines: default(),
266 global_shader_defs,
267 synchronous_pipeline_compilation,
268 needs_shader_reload: true,
269 }
270 }
271
272 #[inline]
276 pub fn get_render_pipeline_state(&self, id: CachedRenderPipelineId) -> &CachedPipelineState {
277 self.pipelines
279 .get(id.id())
280 .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state)
281 }
282
283 #[inline]
287 pub fn get_compute_pipeline_state(&self, id: CachedComputePipelineId) -> &CachedPipelineState {
288 self.pipelines
290 .get(id.id())
291 .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state)
292 }
293
294 #[inline]
301 pub fn get_render_pipeline_descriptor(
302 &self,
303 id: CachedRenderPipelineId,
304 ) -> &RenderPipelineDescriptor {
305 match &self.pipelines[id.id()].descriptor {
306 PipelineDescriptor::RenderPipelineDescriptor(descriptor) => descriptor,
307 PipelineDescriptor::ComputePipelineDescriptor(_) => unreachable!(),
308 }
309 }
310
311 #[inline]
318 pub fn get_compute_pipeline_descriptor(
319 &self,
320 id: CachedComputePipelineId,
321 ) -> &ComputePipelineDescriptor {
322 match &self.pipelines[id.id()].descriptor {
323 PipelineDescriptor::RenderPipelineDescriptor(_) => unreachable!(),
324 PipelineDescriptor::ComputePipelineDescriptor(descriptor) => descriptor,
325 }
326 }
327
328 #[inline]
336 pub fn get_render_pipeline(&self, id: CachedRenderPipelineId) -> Option<&RenderPipeline> {
337 if let CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) =
338 &self.pipelines.get(id.id())?.state
339 {
340 Some(pipeline)
341 } else {
342 None
343 }
344 }
345
346 #[inline]
348 pub fn block_on_render_pipeline(&mut self, id: CachedRenderPipelineId) {
349 if self.pipelines.len() <= id.id() {
350 self.process_queue();
351 }
352
353 let state = &mut self.pipelines[id.id()].state;
354 if let CachedPipelineState::Creating(task) = state {
355 *state = match bevy_tasks::block_on(task) {
356 Ok(p) => CachedPipelineState::Ok(p),
357 Err(e) => CachedPipelineState::Err(e),
358 };
359 }
360 }
361
362 #[inline]
370 pub fn get_compute_pipeline(&self, id: CachedComputePipelineId) -> Option<&ComputePipeline> {
371 if let CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) =
372 &self.pipelines.get(id.id())?.state
373 {
374 Some(pipeline)
375 } else {
376 None
377 }
378 }
379
380 pub fn queue_render_pipeline(
394 &self,
395 descriptor: RenderPipelineDescriptor,
396 ) -> CachedRenderPipelineId {
397 let mut new_pipelines = self
398 .new_pipelines
399 .lock()
400 .unwrap_or_else(PoisonError::into_inner);
401 let id = CachedRenderPipelineId::new(self.pipelines.len() + new_pipelines.len());
402 new_pipelines.push(CachedPipeline {
403 descriptor: PipelineDescriptor::RenderPipelineDescriptor(Box::new(descriptor)),
404 state: CachedPipelineState::Queued,
405 });
406 id
407 }
408
409 pub fn queue_compute_pipeline(
423 &self,
424 descriptor: ComputePipelineDescriptor,
425 ) -> CachedComputePipelineId {
426 let mut new_pipelines = self
427 .new_pipelines
428 .lock()
429 .unwrap_or_else(PoisonError::into_inner);
430 let id = CachedComputePipelineId::new(self.pipelines.len() + new_pipelines.len());
431 new_pipelines.push(CachedPipeline {
432 descriptor: PipelineDescriptor::ComputePipelineDescriptor(Box::new(descriptor)),
433 state: CachedPipelineState::Queued,
434 });
435 id
436 }
437
438 pub fn get_bind_group_layout(
439 &self,
440 bind_group_layout_descriptor: &BindGroupLayoutDescriptor,
441 ) -> BindGroupLayout {
442 self.bindgroup_layout_cache
443 .lock()
444 .unwrap()
445 .get(&self.device, bind_group_layout_descriptor.clone())
446 }
447
448 pub fn set_shader(&mut self, id: AssetId<Shader>, shader: Shader) {
450 let mut shader_cache = self.shader_cache.lock().unwrap();
451 let pipelines_to_queue = shader_cache.set_shader(id, shader);
452 for cached_pipeline in pipelines_to_queue {
453 self.pipelines[cached_pipeline].state = CachedPipelineState::Queued;
454 self.waiting_pipelines.insert(cached_pipeline);
455 }
456 }
457
458 pub fn remove_shader(&mut self, shader: AssetId<Shader>) {
460 let mut shader_cache = self.shader_cache.lock().unwrap();
461 let pipelines_to_queue = shader_cache.remove(shader);
462 for cached_pipeline in pipelines_to_queue {
463 self.pipelines[cached_pipeline].state = CachedPipelineState::Queued;
464 self.waiting_pipelines.insert(cached_pipeline);
465 }
466 }
467
468 fn start_create_render_pipeline(
469 &mut self,
470 id: CachedPipelineId,
471 descriptor: RenderPipelineDescriptor,
472 ) -> CachedPipelineState {
473 let device = self.device.clone();
474 let shader_cache = self.shader_cache.clone();
475 let layout_cache = self.layout_cache.clone();
476 let mut bindgroup_layout_cache = self.bindgroup_layout_cache.lock().unwrap();
477 let bind_group_layout = descriptor
478 .layout
479 .iter()
480 .map(|bind_group_layout_descriptor| {
481 bindgroup_layout_cache.get(&self.device, bind_group_layout_descriptor.clone())
482 })
483 .collect::<Vec<_>>();
484
485 create_pipeline_task(
486 async move {
487 let mut shader_cache = shader_cache.lock().unwrap();
488 let mut layout_cache = layout_cache.lock().unwrap();
489
490 let vertex_module = match shader_cache.get(
491 id,
492 descriptor.vertex.shader.id(),
493 &descriptor.vertex.shader_defs,
494 ) {
495 Ok(module) => module,
496 Err(err) => return Err(err),
497 };
498
499 let fragment_module = match &descriptor.fragment {
500 Some(fragment) => {
501 match shader_cache.get(id, fragment.shader.id(), &fragment.shader_defs) {
502 Ok(module) => Some(module),
503 Err(err) => return Err(err),
504 }
505 }
506 None => None,
507 };
508
509 let layout = if descriptor.layout.is_empty() && descriptor.immediate_size == 0 {
510 None
511 } else {
512 Some(layout_cache.get(&device, &bind_group_layout, descriptor.immediate_size))
513 };
514
515 drop((shader_cache, layout_cache));
516
517 let vertex_buffer_layouts = descriptor
518 .vertex
519 .buffers
520 .iter()
521 .map(|layout| RawVertexBufferLayout {
522 array_stride: layout.array_stride,
523 attributes: &layout.attributes,
524 step_mode: layout.step_mode,
525 })
526 .collect::<Vec<_>>();
527
528 let fragment_data = descriptor.fragment.as_ref().map(|fragment| {
529 (
530 fragment_module.unwrap(),
531 fragment.entry_point.as_deref(),
532 fragment.targets.as_slice(),
533 )
534 });
535
536 let compilation_options = PipelineCompilationOptions {
538 constants: &[],
539 zero_initialize_workgroup_memory: descriptor.zero_initialize_workgroup_memory,
540 };
541
542 let descriptor = RawRenderPipelineDescriptor {
543 multiview_mask: None,
544 depth_stencil: descriptor.depth_stencil.clone(),
545 label: descriptor.label.as_deref(),
546 layout: layout.as_ref().map(|layout| -> &PipelineLayout { layout }),
547 multisample: descriptor.multisample,
548 primitive: descriptor.primitive,
549 vertex: RawVertexState {
550 buffers: &vertex_buffer_layouts,
551 entry_point: descriptor.vertex.entry_point.as_deref(),
552 module: &vertex_module,
553 compilation_options: compilation_options.clone(),
555 },
556 fragment: fragment_data
557 .as_ref()
558 .map(|(module, entry_point, targets)| RawFragmentState {
559 entry_point: entry_point.as_deref(),
560 module,
561 targets,
562 compilation_options,
564 }),
565 cache: None,
566 };
567
568 Ok(Pipeline::RenderPipeline(
569 device.create_render_pipeline(&descriptor),
570 ))
571 },
572 self.synchronous_pipeline_compilation,
573 )
574 }
575
576 fn start_create_compute_pipeline(
577 &mut self,
578 id: CachedPipelineId,
579 descriptor: ComputePipelineDescriptor,
580 ) -> CachedPipelineState {
581 let device = self.device.clone();
582 let shader_cache = self.shader_cache.clone();
583 let layout_cache = self.layout_cache.clone();
584 let mut bindgroup_layout_cache = self.bindgroup_layout_cache.lock().unwrap();
585 let bind_group_layout = descriptor
586 .layout
587 .iter()
588 .map(|bind_group_layout_descriptor| {
589 bindgroup_layout_cache.get(&self.device, bind_group_layout_descriptor.clone())
590 })
591 .collect::<Vec<_>>();
592
593 create_pipeline_task(
594 async move {
595 let mut shader_cache = shader_cache.lock().unwrap();
596 let mut layout_cache = layout_cache.lock().unwrap();
597
598 let compute_module =
599 match shader_cache.get(id, descriptor.shader.id(), &descriptor.shader_defs) {
600 Ok(module) => module,
601 Err(err) => return Err(err),
602 };
603
604 let layout = if descriptor.layout.is_empty() && descriptor.immediate_size == 0 {
605 None
606 } else {
607 Some(layout_cache.get(&device, &bind_group_layout, descriptor.immediate_size))
608 };
609
610 drop((shader_cache, layout_cache));
611
612 let descriptor = RawComputePipelineDescriptor {
613 label: descriptor.label.as_deref(),
614 layout: layout.as_ref().map(|layout| -> &PipelineLayout { layout }),
615 module: &compute_module,
616 entry_point: descriptor.entry_point.as_deref(),
617 compilation_options: PipelineCompilationOptions {
619 constants: &[],
620 zero_initialize_workgroup_memory: descriptor
621 .zero_initialize_workgroup_memory,
622 },
623 cache: None,
624 };
625
626 Ok(Pipeline::ComputePipeline(
627 device.create_compute_pipeline(&descriptor),
628 ))
629 },
630 self.synchronous_pipeline_compilation,
631 )
632 }
633
634 pub fn process_queue(&mut self) {
641 let mut waiting_pipelines = mem::take(&mut self.waiting_pipelines);
642 let mut pipelines = mem::take(&mut self.pipelines);
643
644 {
645 let mut new_pipelines = self
646 .new_pipelines
647 .lock()
648 .unwrap_or_else(PoisonError::into_inner);
649 for new_pipeline in new_pipelines.drain(..) {
650 let id = pipelines.len();
651 pipelines.push(new_pipeline);
652 waiting_pipelines.insert(id);
653 }
654 }
655
656 for id in waiting_pipelines {
657 self.process_pipeline(&mut pipelines[id], id);
658 }
659
660 self.pipelines = pipelines;
661 }
662
663 fn process_pipeline(&mut self, cached_pipeline: &mut CachedPipeline, id: usize) {
664 match &mut cached_pipeline.state {
665 CachedPipelineState::Queued => {
666 cached_pipeline.state = match &cached_pipeline.descriptor {
667 PipelineDescriptor::RenderPipelineDescriptor(descriptor) => {
668 self.start_create_render_pipeline(id, *descriptor.clone())
669 }
670 PipelineDescriptor::ComputePipelineDescriptor(descriptor) => {
671 self.start_create_compute_pipeline(id, *descriptor.clone())
672 }
673 };
674 }
675
676 CachedPipelineState::Creating(task) => match bevy_tasks::futures::check_ready(task) {
677 Some(Ok(pipeline)) => {
678 cached_pipeline.state = CachedPipelineState::Ok(pipeline);
679 return;
680 }
681 Some(Err(err)) => cached_pipeline.state = CachedPipelineState::Err(err),
682 _ => (),
683 },
684
685 CachedPipelineState::Err(err) => match err {
686 ShaderCacheError::ShaderNotLoaded(_)
688 | ShaderCacheError::ShaderImportNotYetAvailable => {
689 cached_pipeline.state = CachedPipelineState::Queued;
690 }
691
692 ShaderCacheError::ProcessShaderError(err) => {
694 let error_detail =
695 err.emit_to_string(&self.shader_cache.lock().unwrap().composer);
696 if std::env::var("VERBOSE_SHADER_ERROR")
697 .is_ok_and(|v| !(v.is_empty() || v == "0" || v == "false"))
698 {
699 error!("{}", pipeline_error_context(cached_pipeline));
700 }
701 error!("failed to process shader error:\n{}", error_detail);
702 return;
703 }
704 ShaderCacheError::CreateShaderModule(description) => {
705 error!("failed to create shader module: {}", description);
706 return;
707 }
708 },
709
710 CachedPipelineState::Ok(_) => return,
711 }
712
713 self.waiting_pipelines.insert(id);
715 }
716
717 pub(crate) fn process_pipeline_queue_system(mut cache: ResMut<Self>) {
718 cache.process_queue();
719 }
720
721 pub(crate) fn extract_shaders(
722 mut cache: ResMut<Self>,
723 shaders: Extract<Res<Assets<Shader>>>,
724 mut events: Extract<MessageReader<AssetEvent<Shader>>>,
725 ) {
726 if cache.needs_shader_reload {
727 cache.needs_shader_reload = false;
728 for (id, shader) in shaders.iter() {
729 let mut shader = shader.clone();
730 shader.shader_defs.extend(cache.global_shader_defs.clone());
731 cache.set_shader(id, shader);
732 }
733 for _ in events.read() {}
735 return;
736 }
737
738 for event in events.read() {
739 #[expect(
740 clippy::match_same_arms,
741 reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon."
742 )]
743 match event {
744 AssetEvent::Added { id } | AssetEvent::Modified { id } => {
746 if let Some(shader) = shaders.get(*id) {
747 let mut shader = shader.clone();
748 shader.shader_defs.extend(cache.global_shader_defs.clone());
749
750 cache.set_shader(*id, shader);
751 }
752 }
753 AssetEvent::Removed { id } => cache.remove_shader(*id),
754 AssetEvent::Unused { .. } => {}
755 AssetEvent::LoadedWithDependencies { .. } => {
756 }
758 }
759 }
760 }
761}
762
763fn pipeline_error_context(cached_pipeline: &CachedPipeline) -> String {
764 fn format(
765 shader: &Handle<Shader>,
766 entry: &Option<Cow<'static, str>>,
767 shader_defs: &[ShaderDefVal],
768 ) -> String {
769 let source = match shader.path() {
770 Some(path) => path.path().to_string_lossy().to_string(),
771 None => String::new(),
772 };
773 let entry = match entry {
774 Some(entry) => entry.to_string(),
775 None => String::new(),
776 };
777 let shader_defs = shader_defs
778 .iter()
779 .flat_map(|def| match def {
780 ShaderDefVal::Bool(k, v) if *v => Some(k.to_string()),
781 ShaderDefVal::Int(k, v) => Some(format!("{k} = {v}")),
782 ShaderDefVal::UInt(k, v) => Some(format!("{k} = {v}")),
783 _ => None,
784 })
785 .collect::<Vec<_>>()
786 .join(", ");
787 format!("{source}:{entry}\nshader defs: {shader_defs}")
788 }
789 match &cached_pipeline.descriptor {
790 PipelineDescriptor::RenderPipelineDescriptor(desc) => {
791 let vert = &desc.vertex;
792 let vert_str = format(&vert.shader, &vert.entry_point, &vert.shader_defs);
793 let Some(frag) = desc.fragment.as_ref() else {
794 return vert_str;
795 };
796 let frag_str = format(&frag.shader, &frag.entry_point, &frag.shader_defs);
797 format!("vertex {vert_str}\nfragment {frag_str}")
798 }
799 PipelineDescriptor::ComputePipelineDescriptor(desc) => {
800 format(&desc.shader, &desc.entry_point, &desc.shader_defs)
801 }
802 }
803}
804
805#[cfg(all(
806 not(target_arch = "wasm32"),
807 not(target_os = "macos"),
808 feature = "multi_threaded"
809))]
810fn create_pipeline_task(
811 task: impl Future<Output = Result<Pipeline, ShaderCacheError>> + Send + 'static,
812 sync: bool,
813) -> CachedPipelineState {
814 if !sync {
815 return CachedPipelineState::Creating(bevy_tasks::AsyncComputeTaskPool::get().spawn(task));
816 }
817
818 match bevy_tasks::block_on(task) {
819 Ok(pipeline) => CachedPipelineState::Ok(pipeline),
820 Err(err) => CachedPipelineState::Err(err),
821 }
822}
823
824#[cfg(any(
825 target_arch = "wasm32",
826 target_os = "macos",
827 not(feature = "multi_threaded")
828))]
829fn create_pipeline_task(
830 task: impl Future<Output = Result<Pipeline, ShaderCacheError>> + Send + 'static,
831 _sync: bool,
832) -> CachedPipelineState {
833 match bevy_tasks::block_on(task) {
834 Ok(pipeline) => CachedPipelineState::Ok(pipeline),
835 Err(err) => CachedPipelineState::Err(err),
836 }
837}