Skip to main content

bevy_render/render_resource/
pipeline_cache.rs

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/// A pipeline defining the data layout and shader logic for a specific GPU task.
32///
33/// Used to store a heterogenous collection of render and compute pipelines together.
34#[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/// State of a cached pipeline inserted into a [`PipelineCache`].
47#[derive(Debug)]
48pub enum CachedPipelineState {
49    /// The pipeline GPU object is queued for creation.
50    Queued,
51    /// The pipeline GPU object is being created.
52    Creating(Task<Result<Pipeline, ShaderCacheError>>),
53    /// The pipeline GPU object was created successfully and is available (allocated on the GPU).
54    Ok(Pipeline),
55    /// An error occurred while trying to create the pipeline GPU object.
56    Err(ShaderCacheError),
57}
58
59impl CachedPipelineState {
60    /// Convenience method to "unwrap" a pipeline state into its underlying GPU object.
61    ///
62    /// # Returns
63    ///
64    /// The method returns the allocated pipeline GPU object.
65    ///
66    /// # Panics
67    ///
68    /// This method panics if the pipeline GPU object is not available, either because it is
69    /// pending creation or because an error occurred while attempting to create GPU object.
70    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
84// The default webgpu `max_bind_groups` is 4. Most desktop GPUs support 8.
85// Since the element size we used below is small, we inline 8 on the stack.
86const 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        // SAFETY: we are interfacing with shader code, which may contain undefined behavior,
161        // such as indexing out of bounds.
162        // The checks required are prohibitively expensive and a poor default for game engines.
163        ValidateShader::Disabled => unsafe {
164            render_device.create_shader_module(module_descriptor)
165        },
166    });
167
168    let error = scope.pop();
169
170    // `now_or_never` will return Some if the future is ready and None otherwise.
171    // On native platforms, wgpu will yield the error immediately while on wasm it may take longer since the browser APIs are asynchronous.
172    // So to keep the complexity of the ShaderCache low, we will only catch this error early on native platforms,
173    // and on wasm the error will be handled by wgpu and crash the application.
174    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/// Cache for render and compute pipelines.
207///
208/// The cache stores existing render and compute pipelines allocated on the GPU, as well as
209/// pending creation. Pipelines inserted into the cache are identified by a unique ID, which
210/// can be used to retrieve the actual GPU object once it's ready. The creation of the GPU
211/// pipeline object is deferred to the [`RenderSystems::Render`] step, just before the render
212/// graph starts being processed, as this requires access to the GPU.
213///
214/// Note that the cache does not perform automatic deduplication of identical pipelines. It is
215/// up to the user not to insert the same pipeline twice to avoid wasting GPU resources.
216///
217/// [`RenderSystems::Render`]: crate::RenderSystems::Render
218#[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    /// If `true`, disables asynchronous pipeline compilation.
229    /// This has no effect on macOS, wasm, or without the `multi_threaded` feature.
230    pub(crate) synchronous_pipeline_compilation: bool,
231    /// If `true`, the shader cache needs to be repopulated from the main world's `Assets<Shader>`.
232    needs_shader_reload: bool,
233}
234
235impl PipelineCache {
236    /// Returns an iterator over the pipelines in the pipeline cache.
237    pub fn pipelines(&self) -> impl Iterator<Item = &CachedPipeline> {
238        self.pipelines.iter()
239    }
240
241    /// Returns a iterator of the IDs of all currently waiting pipelines.
242    pub fn waiting_pipelines(&self) -> impl Iterator<Item = CachedPipelineId> + '_ {
243        self.waiting_pipelines.iter().copied()
244    }
245
246    /// Create a new pipeline cache associated with the given render device.
247    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        // wesl condcomp flags are booleans, so bake the comparisons in.
267        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    /// Get the state of a cached render pipeline.
291    ///
292    /// See [`PipelineCache::queue_render_pipeline()`].
293    #[inline]
294    pub fn get_render_pipeline_state(&self, id: CachedRenderPipelineId) -> &CachedPipelineState {
295        // If the pipeline id isn't in `pipelines`, it's queued in `new_pipelines`
296        self.pipelines
297            .get(id.id())
298            .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state)
299    }
300
301    /// Get the state of a cached compute pipeline.
302    ///
303    /// See [`PipelineCache::queue_compute_pipeline()`].
304    #[inline]
305    pub fn get_compute_pipeline_state(&self, id: CachedComputePipelineId) -> &CachedPipelineState {
306        // If the pipeline id isn't in `pipelines`, it's queued in `new_pipelines`
307        self.pipelines
308            .get(id.id())
309            .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state)
310    }
311
312    /// Get the render pipeline descriptor a cached render pipeline was inserted from.
313    ///
314    /// See [`PipelineCache::queue_render_pipeline()`].
315    ///
316    /// **Note**: Be careful calling this method. It will panic if called with a pipeline that
317    /// has been queued but has not yet been processed by [`PipelineCache::process_queue()`].
318    #[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    /// Get the compute pipeline descriptor a cached render pipeline was inserted from.
331    ///
332    /// See [`PipelineCache::queue_compute_pipeline()`].
333    ///
334    /// **Note**: Be careful calling this method. It will panic if called with a pipeline that
335    /// has been queued but has not yet been processed by [`PipelineCache::process_queue()`].
336    #[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    /// Get the mesh pipeline descriptor a cached mesh pipeline was inserted from.
349    ///
350    /// See [`PipelineCache::queue_mesh_pipeline()`].
351    ///
352    /// **Note**: Be careful calling this method. It will panic if called with a pipeline that
353    /// has been queued but has not yet been processed by [`PipelineCache::process_queue()`].
354    #[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    /// Try to retrieve a render pipeline GPU object from a cached ID.
367    ///
368    /// # Returns
369    ///
370    /// This method returns a successfully created render pipeline if any, or `None` if the pipeline
371    /// was not created yet or if there was an error during creation. You can check the actual creation
372    /// state with [`PipelineCache::get_render_pipeline_state()`].
373    #[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    /// Wait for a render pipeline to finish compiling.
386    #[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    /// Try to retrieve a compute pipeline GPU object from a cached ID.
402    ///
403    /// # Returns
404    ///
405    /// This method returns a successfully created compute pipeline if any, or `None` if the pipeline
406    /// was not created yet or if there was an error during creation. You can check the actual creation
407    /// state with [`PipelineCache::get_compute_pipeline_state()`].
408    #[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    /// Insert a render pipeline into the cache, and queue its creation.
420    ///
421    /// The pipeline is always inserted and queued for creation. There is no attempt to deduplicate it with
422    /// an already cached pipeline.
423    ///
424    /// # Returns
425    ///
426    /// This method returns the unique render shader ID of the cached pipeline, which can be used to query
427    /// the caching state with [`get_render_pipeline_state()`] and to retrieve the created GPU pipeline once
428    /// it's ready with [`get_render_pipeline()`].
429    ///
430    /// [`get_render_pipeline_state()`]: PipelineCache::get_render_pipeline_state
431    /// [`get_render_pipeline()`]: PipelineCache::get_render_pipeline
432    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    /// Insert a compute pipeline into the cache, and queue its creation.
449    ///
450    /// The pipeline is always inserted and queued for creation. There is no attempt to deduplicate it with
451    /// an already cached pipeline.
452    ///
453    /// # Returns
454    ///
455    /// This method returns the unique compute shader ID of the cached pipeline, which can be used to query
456    /// the caching state with [`get_compute_pipeline_state()`] and to retrieve the created GPU pipeline once
457    /// it's ready with [`get_compute_pipeline()`].
458    ///
459    /// [`get_compute_pipeline_state()`]: PipelineCache::get_compute_pipeline_state
460    /// [`get_compute_pipeline()`]: PipelineCache::get_compute_pipeline
461    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    /// Insert a mesh pipeline into the cache, and queue its creation.
478    ///
479    /// The pipeline is always inserted and queued for creation. There is no attempt to deduplicate it with
480    /// an already cached pipeline.
481    ///
482    /// # Returns
483    ///
484    /// This method returns the unique mesh shader ID of the cached pipeline, which can be used to query
485    /// the caching state with [`get_render_pipeline_state()`] and to retrieve the created GPU pipeline once
486    /// it's ready with [`get_render_pipeline()`].
487    ///
488    /// [`get_render_pipeline_state()`]: PipelineCache::get_render_pipeline_state
489    /// [`get_render_pipeline()`]: PipelineCache::get_render_pipeline
490    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    /// Inserts a [`Shader`] into this cache with the provided [`AssetId`].
517    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    /// Removes a [`Shader`] from this cache if it exists.
527    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    /// Process the pipeline queue and create all pending pipelines if possible.
858    ///
859    /// This is generally called automatically during the [`RenderSystems::Render`] step, but can
860    /// be called manually to force creation at a different time.
861    ///
862    /// [`RenderSystems::Render`]: crate::RenderSystems::Render
863    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                // Retry
913                ShaderCacheError::ShaderNotLoaded(_)
914                | ShaderCacheError::ShaderImportNotYetAvailable => {
915                    bevy_log::debug!("retry processing pipeline {id}: {err}");
916                    cached_pipeline.state = CachedPipelineState::Queued;
917                }
918
919                // Shader could not be processed ... retrying won't help
920                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        // Retry
940        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            // Drain events so we don't double-process shaders we just loaded.
962            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                // PERF: Instead of blocking waiting for the shader cache lock, try again next frame if the lock is currently held
973                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                    // TODO: handle this
987                }
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}