wgpu_core/
pipeline.rs

1use alloc::{
2    borrow::{Cow, ToOwned},
3    boxed::Box,
4    string::String,
5    sync::Arc,
6    vec::Vec,
7};
8use core::{marker::PhantomData, mem::ManuallyDrop, num::NonZeroU32};
9
10use arrayvec::ArrayVec;
11use naga::error::ShaderError;
12use thiserror::Error;
13use wgt::error::{ErrorType, WebGpuError};
14
15pub use crate::pipeline_cache::PipelineCacheValidationError;
16use crate::{
17    api_log,
18    binding_model::{
19        BindGroupLayout, CreateBindGroupLayoutError, CreatePipelineLayoutError,
20        GetBindGroupLayoutError, PipelineLayout,
21    },
22    command::ColorAttachmentError,
23    device::{
24        AttachmentData, Device, DeviceError, MissingDownlevelFlags, MissingFeatures,
25        RenderPassContext,
26    },
27    pipeline_cache,
28    resource::{InvalidResourceError, Labeled, ResourceState, TrackingData},
29    resource_log,
30    validation::{self, ShaderMetaData},
31    Label, LabelHelpers as _,
32};
33
34/// Information about buffer bindings, which
35/// is validated against the shader (and pipeline)
36/// at draw time as opposed to initialization time.
37#[derive(Debug, Default)]
38pub(crate) struct LateSizedBufferGroup {
39    // The order has to match `BindGroup::late_buffer_binding_sizes`.
40    pub(crate) shader_sizes: Vec<wgt::BufferAddress>,
41}
42
43#[allow(clippy::large_enum_variant)]
44pub enum ShaderModuleSource<'a> {
45    #[cfg(feature = "wgsl")]
46    Wgsl(Cow<'a, str>),
47    #[cfg(feature = "glsl")]
48    Glsl(Cow<'a, str>, naga::front::glsl::Options),
49    #[cfg(feature = "spirv")]
50    SpirV(Cow<'a, [u32]>, naga::front::spv::Options),
51    Naga(Cow<'static, naga::Module>),
52    /// Dummy variant because `Naga` doesn't have a lifetime and without enough active features it
53    /// could be the last one active.
54    #[doc(hidden)]
55    Dummy(PhantomData<&'a ()>),
56}
57
58#[derive(Clone, Debug)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60pub struct ShaderModuleDescriptor<'a> {
61    pub label: Label<'a>,
62    #[cfg_attr(feature = "serde", serde(default))]
63    pub runtime_checks: wgt::ShaderRuntimeChecks,
64}
65
66pub type ShaderModuleDescriptorPassthrough<'a> =
67    wgt::CreateShaderModuleDescriptorPassthrough<'a, Label<'a>>;
68
69#[derive(Debug)]
70pub(crate) struct ShaderModuleState {
71    pub(crate) raw: Box<dyn hal::DynShaderModule>,
72    pub(crate) interface: ShaderMetaData,
73}
74
75#[derive(Debug)]
76pub struct ShaderModule {
77    pub(crate) state: ResourceState<ShaderModuleState>,
78    pub(crate) device: Arc<Device>,
79    /// The `label` from the descriptor used to create the resource.
80    pub(crate) label: String,
81}
82
83impl Drop for ShaderModule {
84    #[allow(trivial_casts)]
85    fn drop(&mut self) {
86        profiling::scope!("ShaderModule::drop");
87        api_log!("ShaderModule::drop {:?}", self as *const _);
88        resource_log!("Destroy raw {}", self.error_ident());
89        #[cfg(feature = "trace")]
90        if let Some(t) = self.device.trace.lock().as_mut() {
91            use crate::device::trace::{to_trace, Action};
92
93            t.add(Action::DropShaderModule(unsafe { to_trace(self) }));
94        }
95        let ResourceState::Valid(state) =
96            core::mem::replace(&mut self.state, ResourceState::Invalid)
97        else {
98            return;
99        };
100        unsafe {
101            self.device.raw().destroy_shader_module(state.raw);
102        }
103    }
104}
105
106crate::impl_resource_type!(ShaderModule);
107crate::impl_labeled!(ShaderModule);
108crate::impl_parent_device!(ShaderModule);
109crate::impl_storage_item!(ShaderModule);
110
111impl ShaderModule {
112    pub(crate) fn state(&self) -> Result<&ShaderModuleState, InvalidResourceError> {
113        let ResourceState::Valid(state) = &self.state else {
114            return Err(InvalidResourceError(self.error_ident()));
115        };
116        Ok(state)
117    }
118
119    pub(crate) fn invalid(device: Arc<Device>, label: String) -> Arc<Self> {
120        Arc::new(Self {
121            state: ResourceState::Invalid,
122            device,
123            label,
124        })
125    }
126
127    /// Select an entry point name, given an optional name and a shader stage.
128    ///
129    /// This function takes care of turning the `Option<&str>`
130    /// [`ProgrammableStageDescriptor::entry_point`][ep] into a specific name.
131    ///
132    /// For non-passthrough shaders, if `entry_point` is `Some`, then return it
133    /// as a `String`. Otherwise, return the name of the unique entry point in
134    /// `self`'s module for `stage`; if there is not exactly one such entry
135    /// point, return an error.
136    ///
137    /// The non-passthrough case counts on `Interface::check_stage` to verify
138    /// that an entry point with the given name actually exists.
139    ///
140    /// For passthrough shaders, if `entry_point` is `Some`, verify that an
141    /// entry point by that name exists (returning an error if not), and return
142    /// it as a `String`. Otherwise, if `entry_point` is `None`, then check that
143    /// this module has exactly one entry point, and return its name.
144    ///
145    /// [ep]: crate::pipeline::ProgrammableStageDescriptor::entry_point
146    pub(crate) fn finalize_entry_point_name(
147        &self,
148        stage: naga::ShaderStage,
149        entry_point: Option<&str>,
150    ) -> Result<String, validation::StageError> {
151        let state = self.state()?;
152        match state.interface {
153            ShaderMetaData::Interface(ref interface) => {
154                interface.finalize_entry_point_name(stage, entry_point)
155            }
156            ShaderMetaData::Passthrough(ref interface) => {
157                if let Some(ep) = entry_point {
158                    if interface.entry_point_names.contains(ep) {
159                        Ok(ep.to_owned())
160                    } else {
161                        Err(validation::StageError::MissingEntryPoint(ep.to_owned()))
162                    }
163                } else {
164                    if interface.entry_point_names.len() != 1 {
165                        return Err(validation::StageError::MultipleEntryPointsFound);
166                    }
167                    Ok(interface
168                        .entry_point_names
169                        .iter()
170                        .next()
171                        .unwrap()
172                        .to_owned())
173                }
174            }
175        }
176    }
177}
178
179//Note: `Clone` would require `WithSpan: Clone`.
180#[derive(Clone, Debug, Error)]
181#[non_exhaustive]
182pub enum CreateShaderModuleError {
183    #[cfg(feature = "wgsl")]
184    #[error(transparent)]
185    Parsing(#[from] ShaderError<naga::front::wgsl::ParseError>),
186    #[cfg(feature = "glsl")]
187    #[error(transparent)]
188    ParsingGlsl(#[from] ShaderError<naga::front::glsl::ParseErrors>),
189    #[cfg(feature = "spirv")]
190    #[error(transparent)]
191    ParsingSpirV(#[from] ShaderError<naga::front::spv::Error>),
192    #[error("Failed to generate the backend-specific code")]
193    Generation,
194    #[error(transparent)]
195    Device(#[from] DeviceError),
196    #[error(transparent)]
197    Validation(#[from] ShaderError<naga::WithSpan<naga::valid::ValidationError>>),
198    #[error(transparent)]
199    MissingFeatures(#[from] MissingFeatures),
200    #[error(
201        "Shader global {bind:?} uses a group index {group} that exceeds the max_bind_groups limit of {limit}."
202    )]
203    InvalidGroupIndex {
204        bind: naga::ResourceBinding,
205        group: u32,
206        limit: u32,
207    },
208    #[error("Generic shader passthrough does not contain any code compatible with this backend.")]
209    NotCompiledForBackend,
210    #[error(
211        "Generic passthrough shaders which use GLSL or DXIL must contain exactly one entry point."
212    )]
213    IncorrectPassthroughEntryPointCount,
214}
215
216impl WebGpuError for CreateShaderModuleError {
217    fn webgpu_error_type(&self) -> ErrorType {
218        match self {
219            Self::Device(e) => e.webgpu_error_type(),
220            Self::MissingFeatures(e) => e.webgpu_error_type(),
221
222            Self::Generation => ErrorType::Internal,
223
224            Self::Validation(..)
225            | Self::InvalidGroupIndex { .. }
226            | Self::IncorrectPassthroughEntryPointCount
227            | Self::NotCompiledForBackend => ErrorType::Validation,
228            #[cfg(feature = "wgsl")]
229            Self::Parsing(..) => ErrorType::Validation,
230            #[cfg(feature = "glsl")]
231            Self::ParsingGlsl(..) => ErrorType::Validation,
232            #[cfg(feature = "spirv")]
233            Self::ParsingSpirV(..) => ErrorType::Validation,
234        }
235    }
236}
237
238/// Describes a programmable pipeline stage.
239#[derive(Clone, Debug)]
240#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
241/// cbindgen:ignore
242pub struct ProgrammableStageDescriptor<'a, SM = Arc<ShaderModule>> {
243    /// The compiled shader module for this stage.
244    pub module: SM,
245
246    /// The name of the entry point in `module` that this stage should use.
247    ///
248    /// - If this is `Some(name)`, `module` must contain an entry point with the
249    ///   given name.
250    ///
251    /// - If this is `None`, `module` must have only one entry point for this
252    ///   stage; we use that one.
253    pub entry_point: Option<Cow<'a, str>>,
254
255    /// Values for pipeline-overridable constants in `module` that this stage
256    /// should use.
257    ///
258    /// If an `@id` attribute was specified on the declaration,
259    /// the key must be the pipeline constant ID as a decimal ASCII number; if not,
260    /// the key must be the constant's identifier name.
261    ///
262    /// The value may represent any of WGSL's concrete scalar types.
263    pub constants: naga::back::PipelineConstants,
264
265    /// Whether variables in the workgroup address space will be initialized
266    /// with zero values for this stage.
267    ///
268    /// The WebGPU spec requires variables in the workgroup address space to be
269    /// zeroed. However, initialization does impose some overhead, and
270    /// non-browser applications may not need it.
271    pub zero_initialize_workgroup_memory: bool,
272}
273
274/// Number of implicit bind groups derived at pipeline creation.
275pub type ImplicitBindGroupCount = u8;
276
277#[derive(Clone, Debug, Error)]
278#[non_exhaustive]
279pub enum ImplicitLayoutError {
280    #[error("Unable to reflect the shader {0:?} interface")]
281    ReflectionError(wgt::ShaderStages),
282    #[error(transparent)]
283    BindGroup(#[from] CreateBindGroupLayoutError),
284    #[error(transparent)]
285    Pipeline(#[from] CreatePipelineLayoutError),
286    #[error("Unable to create implicit pipeline layout from passthrough shader stage: {0:?}")]
287    Passthrough(wgt::ShaderStages),
288}
289
290impl WebGpuError for ImplicitLayoutError {
291    fn webgpu_error_type(&self) -> ErrorType {
292        match self {
293            Self::ReflectionError(_) => ErrorType::Validation,
294            Self::BindGroup(e) => e.webgpu_error_type(),
295            Self::Pipeline(e) => e.webgpu_error_type(),
296            Self::Passthrough(_) => ErrorType::Validation,
297        }
298    }
299}
300
301/// Describes a compute pipeline.
302#[derive(Clone, Debug)]
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304/// cbindgen:ignore
305pub struct ComputePipelineDescriptor<
306    'a,
307    PLL = Arc<PipelineLayout>,
308    SM = Arc<ShaderModule>,
309    PLC = Arc<PipelineCache>,
310> {
311    pub label: Label<'a>,
312    /// The layout of bind groups for this pipeline.
313    pub layout: Option<PLL>,
314    /// The compiled compute stage and its entry point.
315    pub stage: ProgrammableStageDescriptor<'a, SM>,
316    /// The pipeline cache to use when creating this pipeline.
317    pub cache: Option<PLC>,
318}
319
320#[derive(Clone, Debug, Error)]
321#[non_exhaustive]
322pub enum CreateComputePipelineError {
323    #[error(transparent)]
324    Device(#[from] DeviceError),
325    #[error("Unable to derive an implicit layout")]
326    Implicit(#[from] ImplicitLayoutError),
327    #[error("Error matching shader requirements against the pipeline")]
328    Stage(#[from] validation::StageError),
329    #[error("Internal error: {0}")]
330    Internal(String),
331    #[error("Pipeline constant error: {0}")]
332    PipelineConstants(String),
333    #[error(transparent)]
334    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
335    #[error(transparent)]
336    InvalidResource(#[from] InvalidResourceError),
337}
338
339impl WebGpuError for CreateComputePipelineError {
340    fn webgpu_error_type(&self) -> ErrorType {
341        match self {
342            Self::Device(e) => e.webgpu_error_type(),
343            Self::InvalidResource(e) => e.webgpu_error_type(),
344            Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
345            Self::Implicit(e) => e.webgpu_error_type(),
346            Self::Stage(e) => e.webgpu_error_type(),
347            Self::Internal(_) => ErrorType::Internal,
348            Self::PipelineConstants(_) => ErrorType::Validation,
349        }
350    }
351}
352
353#[derive(Debug)]
354pub struct ComputePipelineState {
355    pub(crate) raw: ManuallyDrop<Box<dyn hal::DynComputePipeline>>,
356    pub(crate) layout: Arc<PipelineLayout>,
357    pub(crate) _shader_module: Arc<ShaderModule>,
358}
359
360#[derive(Debug)]
361pub struct ComputePipeline {
362    pub(crate) state: ResourceState<ComputePipelineState>,
363    pub(crate) device: Arc<Device>,
364    pub(crate) late_sized_buffer_groups: ArrayVec<LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }>,
365    pub(crate) immediate_slots_required: naga::valid::ImmediateSlots,
366    /// The `label` from the descriptor used to create the resource.
367    pub(crate) label: String,
368    pub(crate) tracking_data: TrackingData,
369}
370
371impl Drop for ComputePipeline {
372    #[allow(trivial_casts)]
373    fn drop(&mut self) {
374        profiling::scope!("ComputePipeline::drop");
375        api_log!("ComputePipeline::drop {:?}", self as *const _);
376        resource_log!("Destroy raw {}", self.error_ident());
377        #[cfg(feature = "trace")]
378        {
379            use crate::device::trace;
380            if let Some(t) = self.device.trace.lock().as_mut() {
381                t.add(trace::Action::DropComputePipeline(unsafe {
382                    trace::to_trace(self)
383                }));
384            }
385        }
386        let ResourceState::Valid(state) = &mut self.state else {
387            return;
388        };
389        // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point.
390        let raw = unsafe { ManuallyDrop::take(&mut state.raw) };
391        unsafe {
392            self.device.raw().destroy_compute_pipeline(raw);
393        }
394    }
395}
396
397crate::impl_resource_type!(ComputePipeline);
398crate::impl_labeled!(ComputePipeline);
399crate::impl_parent_device!(ComputePipeline);
400crate::impl_storage_item!(ComputePipeline);
401crate::impl_trackable!(ComputePipeline);
402
403impl ComputePipeline {
404    pub(crate) fn raw(&self) -> Result<&dyn hal::DynComputePipeline, InvalidResourceError> {
405        let ResourceState::Valid(state) = &self.state else {
406            return Err(InvalidResourceError(self.error_ident()));
407        };
408        Ok(state.raw.as_ref())
409    }
410
411    pub(crate) fn layout(&self) -> Result<&Arc<PipelineLayout>, InvalidResourceError> {
412        let ResourceState::Valid(state) = &self.state else {
413            return Err(InvalidResourceError(self.error_ident()));
414        };
415        Ok(&state.layout)
416    }
417
418    pub(crate) fn check_valid(&self) -> Result<(), InvalidResourceError> {
419        let ResourceState::Valid(_) = &self.state else {
420            return Err(InvalidResourceError(self.error_ident()));
421        };
422        Ok(())
423    }
424
425    pub(crate) fn invalid(device: Arc<Device>, label: String) -> Arc<Self> {
426        Arc::new(Self {
427            tracking_data: TrackingData::new(device.tracker_indices.compute_pipelines.clone()),
428            state: ResourceState::Invalid,
429            device,
430            late_sized_buffer_groups: ArrayVec::new(),
431            immediate_slots_required: naga::valid::ImmediateSlots::default(),
432            label,
433        })
434    }
435
436    pub fn get_bind_group_layout_inner(
437        self: &Arc<Self>,
438        index: u32,
439    ) -> Result<Arc<BindGroupLayout>, GetBindGroupLayoutError> {
440        self.layout()?.get_bind_group_layout(index, self.into())
441    }
442
443    pub fn get_bind_group_layout(
444        self: &Arc<Self>,
445        index: u32,
446    ) -> (Arc<BindGroupLayout>, Option<GetBindGroupLayoutError>) {
447        let (bgl, error) = match self.get_bind_group_layout_inner(index) {
448            Ok(bgl) => (bgl, None),
449            Err(e) => (
450                BindGroupLayout::invalid(&self.device, String::new()),
451                Some(e),
452            ),
453        };
454        #[cfg(feature = "trace")]
455        if let Some(ref mut trace) = *self.device.trace.lock() {
456            use crate::device::trace;
457            use trace::IntoTrace;
458            trace.add(trace::Action::GetComputePipelineBindGroupLayout {
459                id: bgl.to_trace(),
460                pipeline: self.to_trace(),
461                index,
462            });
463        };
464        (bgl, error)
465    }
466}
467
468#[derive(Clone, Debug, Error)]
469#[non_exhaustive]
470pub enum CreatePipelineCacheError {
471    #[error(transparent)]
472    Device(#[from] DeviceError),
473    #[error("Pipeline cache validation failed")]
474    Validation(#[from] PipelineCacheValidationError),
475    #[error(transparent)]
476    MissingFeatures(#[from] MissingFeatures),
477}
478
479impl WebGpuError for CreatePipelineCacheError {
480    fn webgpu_error_type(&self) -> ErrorType {
481        match self {
482            Self::Device(e) => e.webgpu_error_type(),
483            Self::Validation(e) => e.webgpu_error_type(),
484            Self::MissingFeatures(e) => e.webgpu_error_type(),
485        }
486    }
487}
488
489#[derive(Debug)]
490pub struct PipelineCache {
491    pub(crate) raw: ResourceState<Box<dyn hal::DynPipelineCache>>,
492    pub(crate) device: Arc<Device>,
493    /// The `label` from the descriptor used to create the resource.
494    pub(crate) label: String,
495}
496
497impl Drop for PipelineCache {
498    #[allow(trivial_casts)]
499    fn drop(&mut self) {
500        profiling::scope!("PipelineCache::drop");
501        api_log!("PipelineCache::drop {:?}", self as *const _);
502        #[cfg(feature = "trace")]
503        if let Some(t) = self.device.trace.lock().as_mut() {
504            use crate::device::trace::{to_trace, Action};
505            t.add(Action::DropPipelineCache(unsafe { to_trace(self) }));
506        }
507        resource_log!("Destroy raw {}", self.error_ident());
508        if let ResourceState::Valid(raw) = core::mem::replace(&mut self.raw, ResourceState::Invalid)
509        {
510            unsafe {
511                self.device.raw().destroy_pipeline_cache(raw);
512            }
513        }
514    }
515}
516
517crate::impl_resource_type!(PipelineCache);
518crate::impl_labeled!(PipelineCache);
519crate::impl_parent_device!(PipelineCache);
520crate::impl_storage_item!(PipelineCache);
521
522impl PipelineCache {
523    pub(crate) fn raw(&self) -> Result<&dyn hal::DynPipelineCache, InvalidResourceError> {
524        self.raw
525            .as_ref()
526            .valid()
527            .map(|raw| raw.as_ref())
528            .ok_or_else(|| InvalidResourceError(self.error_ident()))
529    }
530
531    pub(crate) fn check_is_valid(&self) -> Result<(), InvalidResourceError> {
532        self.raw().map(|_| ())
533    }
534
535    pub(crate) fn invalid(device: Arc<Device>, desc: &PipelineCacheDescriptor) -> Arc<Self> {
536        Arc::new(Self {
537            raw: ResourceState::Invalid,
538            device,
539            label: desc.label.to_string(),
540        })
541    }
542
543    pub fn get_data(self: &Arc<Self>) -> Option<Vec<u8>> {
544        api_log!("PipelineCache::get_data");
545
546        let ResourceState::Valid(raw) = &self.raw else {
547            return None;
548        };
549
550        if !self.device.is_valid() {
551            return None;
552        }
553        let mut vec = unsafe { self.device.raw().pipeline_cache_get_data(raw.as_ref()) }?;
554        let validation_key = self.device.raw().pipeline_cache_validation_key()?;
555
556        let mut header_contents = [0; pipeline_cache::HEADER_LENGTH];
557        pipeline_cache::add_cache_header(
558            &mut header_contents,
559            &vec,
560            &self.device.adapter.raw.info,
561            validation_key,
562        );
563
564        let deleted = vec.splice(..0, header_contents).collect::<Vec<_>>();
565        debug_assert!(deleted.is_empty());
566
567        Some(vec)
568    }
569}
570
571/// Describes how the vertex buffer is interpreted.
572#[derive(Clone, Debug)]
573#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
574#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
575pub struct VertexBufferLayout<'a> {
576    /// The stride, in bytes, between elements of this buffer.
577    pub array_stride: wgt::BufferAddress,
578    /// How often this vertex buffer is "stepped" forward.
579    pub step_mode: wgt::VertexStepMode,
580    /// The list of attributes which comprise a single vertex.
581    pub attributes: Cow<'a, [wgt::VertexAttribute]>,
582}
583
584/// A null vertex buffer layout that may be placed in unused slots.
585impl Default for VertexBufferLayout<'_> {
586    fn default() -> Self {
587        Self {
588            array_stride: Default::default(),
589            step_mode: Default::default(),
590            attributes: Cow::Borrowed(&[]),
591        }
592    }
593}
594
595/// Describes the vertex process in a render pipeline.
596#[derive(Clone, Debug)]
597#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
598/// cbindgen:ignore
599pub struct VertexState<'a, SM = Arc<ShaderModule>> {
600    /// The compiled vertex stage and its entry point.
601    pub stage: ProgrammableStageDescriptor<'a, SM>,
602    /// The format of any vertex buffers used with this pipeline.
603    pub buffers: Cow<'a, [Option<VertexBufferLayout<'a>>]>,
604}
605
606/// Describes fragment processing in a render pipeline.
607#[derive(Clone, Debug)]
608#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
609/// cbindgen:ignore
610pub struct FragmentState<'a, SM = Arc<ShaderModule>> {
611    /// The compiled fragment stage and its entry point.
612    pub stage: ProgrammableStageDescriptor<'a, SM>,
613    /// The effect of draw calls on the color aspect of the output target.
614    pub targets: Cow<'a, [Option<wgt::ColorTargetState>]>,
615}
616
617/// Describes the task shader in a mesh shader pipeline.
618#[derive(Clone, Debug)]
619#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
620pub struct TaskState<'a, SM = Arc<ShaderModule>> {
621    /// The compiled task stage and its entry point.
622    pub stage: ProgrammableStageDescriptor<'a, SM>,
623}
624
625/// Describes the mesh shader in a mesh shader pipeline.
626#[derive(Clone, Debug)]
627#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
628pub struct MeshState<'a, SM = Arc<ShaderModule>> {
629    /// The compiled mesh stage and its entry point.
630    pub stage: ProgrammableStageDescriptor<'a, SM>,
631}
632
633/// Describes a vertex processor for either a conventional or mesh shading
634/// pipeline architecture.
635///
636/// This is not a public API. It is for use by `player` only. The public APIs
637/// are [`VertexState`], [`TaskState`], and [`MeshState`].
638///
639/// cbindgen:ignore
640#[doc(hidden)]
641#[derive(Clone, Debug)]
642#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
643pub enum RenderPipelineVertexProcessor<'a, SM = Arc<ShaderModule>> {
644    Vertex(VertexState<'a, SM>),
645    Mesh(Option<TaskState<'a, SM>>, MeshState<'a, SM>),
646}
647
648/// Describes a render (graphics) pipeline.
649#[derive(Clone, Debug)]
650#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
651pub struct RenderPipelineDescriptor<
652    'a,
653    PLL = Arc<PipelineLayout>,
654    SM = Arc<ShaderModule>,
655    PLC = Arc<PipelineCache>,
656> {
657    pub label: Label<'a>,
658    /// The layout of bind groups for this pipeline.
659    pub layout: Option<PLL>,
660    /// The vertex processing state for this pipeline.
661    pub vertex: VertexState<'a, SM>,
662    /// The properties of the pipeline at the primitive assembly and rasterization level.
663    #[cfg_attr(feature = "serde", serde(default))]
664    pub primitive: wgt::PrimitiveState,
665    /// The effect of draw calls on the depth and stencil aspects of the output target, if any.
666    #[cfg_attr(feature = "serde", serde(default))]
667    pub depth_stencil: Option<wgt::DepthStencilState>,
668    /// The multi-sampling properties of the pipeline.
669    #[cfg_attr(feature = "serde", serde(default))]
670    pub multisample: wgt::MultisampleState,
671    /// The fragment processing state for this pipeline.
672    pub fragment: Option<FragmentState<'a, SM>>,
673    /// If the pipeline will be used with a multiview render pass, this indicates how many array
674    /// layers the attachments will have.
675    pub multiview_mask: Option<NonZeroU32>,
676    /// The pipeline cache to use when creating this pipeline.
677    pub cache: Option<PLC>,
678}
679/// Describes a mesh shader pipeline.
680#[derive(Clone, Debug)]
681#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
682pub struct MeshPipelineDescriptor<
683    'a,
684    PLL = Arc<PipelineLayout>,
685    SM = Arc<ShaderModule>,
686    PLC = Arc<PipelineCache>,
687> {
688    pub label: Label<'a>,
689    /// The layout of bind groups for this pipeline.
690    pub layout: Option<PLL>,
691    /// The task processing state for this pipeline.
692    pub task: Option<TaskState<'a, SM>>,
693    /// The mesh processing state for this pipeline
694    pub mesh: MeshState<'a, SM>,
695    /// The properties of the pipeline at the primitive assembly and rasterization level.
696    #[cfg_attr(feature = "serde", serde(default))]
697    pub primitive: wgt::PrimitiveState,
698    /// The effect of draw calls on the depth and stencil aspects of the output target, if any.
699    #[cfg_attr(feature = "serde", serde(default))]
700    pub depth_stencil: Option<wgt::DepthStencilState>,
701    /// The multi-sampling properties of the pipeline.
702    #[cfg_attr(feature = "serde", serde(default))]
703    pub multisample: wgt::MultisampleState,
704    /// The fragment processing state for this pipeline.
705    pub fragment: Option<FragmentState<'a, SM>>,
706    /// If the pipeline will be used with a multiview render pass, this indicates how many array
707    /// layers the attachments will have.
708    pub multiview: Option<NonZeroU32>,
709    /// The pipeline cache to use when creating this pipeline.
710    pub cache: Option<PLC>,
711}
712
713/// Describes a render (graphics) pipeline, with either conventional or mesh
714/// shading architecture.
715///
716/// This is not a public API. It is for use by `player` only. The public APIs
717/// are [`RenderPipelineDescriptor`] and [`MeshPipelineDescriptor`].
718///
719/// cbindgen:ignore
720#[doc(hidden)]
721#[derive(Clone, Debug)]
722#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
723pub struct GeneralRenderPipelineDescriptor<
724    'a,
725    PLL = Arc<PipelineLayout>,
726    SM = Arc<ShaderModule>,
727    PLC = Arc<PipelineCache>,
728> {
729    pub label: Label<'a>,
730    /// The layout of bind groups for this pipeline.
731    pub layout: Option<PLL>,
732    /// The vertex processing state for this pipeline.
733    pub vertex: RenderPipelineVertexProcessor<'a, SM>,
734    /// The properties of the pipeline at the primitive assembly and rasterization level.
735    #[cfg_attr(feature = "serde", serde(default))]
736    pub primitive: wgt::PrimitiveState,
737    /// The effect of draw calls on the depth and stencil aspects of the output target, if any.
738    #[cfg_attr(feature = "serde", serde(default))]
739    pub depth_stencil: Option<wgt::DepthStencilState>,
740    /// The multi-sampling properties of the pipeline.
741    #[cfg_attr(feature = "serde", serde(default))]
742    pub multisample: wgt::MultisampleState,
743    /// The fragment processing state for this pipeline.
744    pub fragment: Option<FragmentState<'a, SM>>,
745    /// If the pipeline will be used with a multiview render pass, this indicates how many array
746    /// layers the attachments will have.
747    pub multiview_mask: Option<NonZeroU32>,
748    /// The pipeline cache to use when creating this pipeline.
749    pub cache: Option<PLC>,
750}
751impl<'a, PLL, SM, PLC> From<RenderPipelineDescriptor<'a, PLL, SM, PLC>>
752    for GeneralRenderPipelineDescriptor<'a, PLL, SM, PLC>
753{
754    fn from(value: RenderPipelineDescriptor<'a, PLL, SM, PLC>) -> Self {
755        Self {
756            label: value.label,
757            layout: value.layout,
758            vertex: RenderPipelineVertexProcessor::Vertex(value.vertex),
759            primitive: value.primitive,
760            depth_stencil: value.depth_stencil,
761            multisample: value.multisample,
762            fragment: value.fragment,
763            multiview_mask: value.multiview_mask,
764            cache: value.cache,
765        }
766    }
767}
768impl<'a, PLL, SM, PLC> From<MeshPipelineDescriptor<'a, PLL, SM, PLC>>
769    for GeneralRenderPipelineDescriptor<'a, PLL, SM, PLC>
770{
771    fn from(value: MeshPipelineDescriptor<'a, PLL, SM, PLC>) -> Self {
772        Self {
773            label: value.label,
774            layout: value.layout,
775            vertex: RenderPipelineVertexProcessor::Mesh(value.task, value.mesh),
776            primitive: value.primitive,
777            depth_stencil: value.depth_stencil,
778            multisample: value.multisample,
779            fragment: value.fragment,
780            multiview_mask: value.multiview,
781            cache: value.cache,
782        }
783    }
784}
785
786/// Not a public API. For use by `player` only.
787///
788/// cbindgen:ignore
789pub type ResolvedGeneralRenderPipelineDescriptor<'a> =
790    GeneralRenderPipelineDescriptor<'a, Arc<PipelineLayout>, Arc<ShaderModule>, Arc<PipelineCache>>;
791
792#[derive(Clone, Debug)]
793#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
794pub struct PipelineCacheDescriptor<'a> {
795    pub label: Label<'a>,
796    pub data: Option<Cow<'a, [u8]>>,
797    pub fallback: bool,
798}
799
800#[derive(Clone, Debug, Error)]
801#[non_exhaustive]
802pub enum ColorStateError {
803    #[error("Format {0:?} is not renderable")]
804    FormatNotRenderable(wgt::TextureFormat),
805    #[error("Format {0:?} is not blendable")]
806    FormatNotBlendable(wgt::TextureFormat),
807    #[error("Format {0:?} does not have a color aspect")]
808    FormatNotColor(wgt::TextureFormat),
809    #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarantees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")]
810    InvalidSampleCount(u32, wgt::TextureFormat, Vec<u32>, Vec<u32>),
811    #[error("Output format {pipeline} is incompatible with the shader {shader}")]
812    IncompatibleFormat {
813        pipeline: validation::NumericType,
814        shader: validation::NumericType,
815    },
816    #[error("Invalid write mask {0:?}")]
817    InvalidWriteMask(wgt::ColorWrites),
818    #[error("Using the blend factor {factor:?} for render target {target} is not possible. Only the first render target may be used when dual-source blending.")]
819    BlendFactorOnUnsupportedTarget {
820        factor: wgt::BlendFactor,
821        target: u32,
822    },
823    #[error("The {which} blend factor {factor:?} is not valid because the shader output does have an alpha channel.")]
824    InvalidAlphaBlend {
825        which: &'static str,
826        factor: wgt::BlendFactor,
827    },
828    #[error(
829        "Blend factor {factor:?} for render target {target} is not valid. Blend factor must be `one` when using min/max blend operations."
830    )]
831    InvalidMinMaxBlendFactor {
832        factor: wgt::BlendFactor,
833        target: u32,
834    },
835    #[error("Shader does not produce an output at this index")]
836    OutputNotPresent,
837}
838
839#[derive(Clone, Debug, Error)]
840#[non_exhaustive]
841pub enum DepthStencilStateError {
842    #[error("Format {0:?} is not renderable")]
843    FormatNotRenderable(wgt::TextureFormat),
844    #[error("Format {0:?} is not a depth/stencil format")]
845    FormatNotDepthOrStencil(wgt::TextureFormat),
846    #[error("Format {0:?} does not have a depth aspect, but depth test/write is enabled")]
847    FormatNotDepth(wgt::TextureFormat),
848    #[error("Format {0:?} does not have a stencil aspect, but stencil test/write is enabled")]
849    FormatNotStencil(wgt::TextureFormat),
850    #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarantees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")]
851    InvalidSampleCount(u32, wgt::TextureFormat, Vec<u32>, Vec<u32>),
852    #[error("Depth bias is not compatible with non-triangle topology {0:?}")]
853    DepthBiasWithIncompatibleTopology(wgt::PrimitiveTopology),
854    #[error("Depth compare function must be specified for depth format {0:?}")]
855    MissingDepthCompare(wgt::TextureFormat),
856    #[error("Depth write enabled must be specified for depth format {0:?}")]
857    MissingDepthWriteEnabled(wgt::TextureFormat),
858}
859
860#[derive(Clone, Debug, Error)]
861#[non_exhaustive]
862pub enum CreateRenderPipelineError {
863    #[error(transparent)]
864    ColorAttachment(#[from] ColorAttachmentError),
865    #[error(transparent)]
866    Device(#[from] DeviceError),
867    #[error("Unable to derive an implicit layout")]
868    Implicit(#[from] ImplicitLayoutError),
869    #[error("Color state [{0}] is invalid")]
870    ColorState(u8, #[source] ColorStateError),
871    #[error("Depth/stencil state is invalid")]
872    DepthStencilState(#[from] DepthStencilStateError),
873    #[error("Invalid sample count {0}")]
874    InvalidSampleCount(u32),
875    #[error("The number of vertex buffers {given} exceeds the limit {limit}")]
876    TooManyVertexBuffers { given: u32, limit: u32 },
877    #[error("The number of bind groups + vertex buffers {given} exceeds the limit {limit}")]
878    TooManyBindGroupsPlusVertexBuffers { given: u32, limit: u32 },
879    #[error("The number of vertex-stage buffers and acceleration structures {given} exceeds the limit {limit}")]
880    TooManyBuffersAndAccelerationStructuresInVertexStage { given: u32, limit: u32 },
881    #[error("The total number of vertex attributes {given} exceeds the limit {limit}")]
882    TooManyVertexAttributes { given: u32, limit: u32 },
883    #[error("Vertex attribute location {given} must be less than limit {limit}")]
884    VertexAttributeLocationTooLarge { given: u32, limit: u32 },
885    #[error("Vertex buffer {index} stride {given} exceeds the limit {limit}")]
886    VertexStrideTooLarge { index: u32, given: u32, limit: u32 },
887    #[error("Vertex attribute at location {location} stride {given} exceeds the limit {limit}")]
888    VertexAttributeStrideTooLarge {
889        location: wgt::ShaderLocation,
890        given: u32,
891        limit: u32,
892    },
893    #[error("Vertex buffer {index} stride {stride} does not respect `VERTEX_ALIGNMENT`")]
894    UnalignedVertexStride {
895        index: u32,
896        stride: wgt::BufferAddress,
897    },
898    #[error("Vertex attribute at location {location} has invalid offset {offset}")]
899    InvalidVertexAttributeOffset {
900        location: wgt::ShaderLocation,
901        offset: wgt::BufferAddress,
902    },
903    #[error("Two or more vertex attributes were assigned to the same location in the shader: {0}")]
904    ShaderLocationClash(u32),
905    #[error("Strip index format was not set to None but to {strip_index_format:?} while using the non-strip topology {topology:?}")]
906    StripIndexFormatForNonStripTopology {
907        strip_index_format: Option<wgt::IndexFormat>,
908        topology: wgt::PrimitiveTopology,
909    },
910    #[error("Conservative Rasterization is only supported for wgt::PolygonMode::Fill")]
911    ConservativeRasterizationNonFillPolygonMode,
912    #[error(transparent)]
913    MissingFeatures(#[from] MissingFeatures),
914    #[error(transparent)]
915    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
916    #[error("Error matching {stage:?} shader requirements against the pipeline")]
917    Stage {
918        stage: wgt::ShaderStages,
919        #[source]
920        error: validation::StageError,
921    },
922    #[error("Internal error in {stage:?} shader: {error}")]
923    Internal {
924        stage: wgt::ShaderStages,
925        error: String,
926    },
927    #[error("Pipeline constant error in {stage:?} shader: {error}")]
928    PipelineConstants {
929        stage: wgt::ShaderStages,
930        error: String,
931    },
932    #[error("In the provided shader, the type given for group {group} binding {binding} has a size of {size}. As the device does not support `DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED`, the type must have a size that is a multiple of 16 bytes.")]
933    UnalignedShader { group: u32, binding: u32, size: u64 },
934    #[error("Dual-source blending requires exactly one color target, but {count} color targets are present")]
935    DualSourceBlendingWithMultipleColorTargets { count: usize },
936    #[error("{}", concat!(
937        "At least one color attachment or depth-stencil attachment was expected, ",
938        "but no render target for the pipeline was specified."
939    ))]
940    NoTargetSpecified,
941    #[error(transparent)]
942    InvalidResource(#[from] InvalidResourceError),
943}
944
945impl WebGpuError for CreateRenderPipelineError {
946    fn webgpu_error_type(&self) -> ErrorType {
947        match self {
948            Self::Device(e) => e.webgpu_error_type(),
949            Self::InvalidResource(e) => e.webgpu_error_type(),
950            Self::MissingFeatures(e) => e.webgpu_error_type(),
951            Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
952
953            Self::Internal { .. } => ErrorType::Internal,
954
955            Self::ColorAttachment(_)
956            | Self::Implicit(_)
957            | Self::ColorState(_, _)
958            | Self::DepthStencilState(_)
959            | Self::InvalidSampleCount(_)
960            | Self::TooManyVertexBuffers { .. }
961            | Self::TooManyBindGroupsPlusVertexBuffers { .. }
962            | Self::TooManyBuffersAndAccelerationStructuresInVertexStage { .. }
963            | Self::TooManyVertexAttributes { .. }
964            | Self::VertexAttributeLocationTooLarge { .. }
965            | Self::VertexStrideTooLarge { .. }
966            | Self::UnalignedVertexStride { .. }
967            | Self::InvalidVertexAttributeOffset { .. }
968            | Self::ShaderLocationClash(_)
969            | Self::StripIndexFormatForNonStripTopology { .. }
970            | Self::ConservativeRasterizationNonFillPolygonMode
971            | Self::Stage { .. }
972            | Self::UnalignedShader { .. }
973            | Self::DualSourceBlendingWithMultipleColorTargets { .. }
974            | Self::NoTargetSpecified
975            | Self::PipelineConstants { .. }
976            | Self::VertexAttributeStrideTooLarge { .. } => ErrorType::Validation,
977        }
978    }
979}
980
981bitflags::bitflags! {
982    #[repr(transparent)]
983    #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
984    pub struct PipelineFlags: u32 {
985        const BLEND_CONSTANT = 1 << 0;
986        const STENCIL_REFERENCE = 1 << 1;
987        const WRITES_DEPTH = 1 << 2;
988        const WRITES_STENCIL = 1 << 3;
989    }
990}
991
992/// How a render pipeline will retrieve attributes from a particular vertex buffer.
993#[derive(Clone, Copy, Debug)]
994pub struct VertexStep {
995    /// The byte stride in the buffer between one attribute value and the next.
996    pub stride: wgt::BufferAddress,
997
998    /// The byte size required to fit the last vertex in the stream.
999    pub last_stride: wgt::BufferAddress,
1000
1001    /// Whether the buffer is indexed by vertex number or instance number.
1002    pub mode: wgt::VertexStepMode,
1003}
1004
1005impl Default for VertexStep {
1006    fn default() -> Self {
1007        Self {
1008            stride: 0,
1009            last_stride: 0,
1010            mode: wgt::VertexStepMode::Vertex,
1011        }
1012    }
1013}
1014
1015#[derive(Debug)]
1016pub(crate) struct RenderPipelineState {
1017    pub(crate) raw: ManuallyDrop<Box<dyn hal::DynRenderPipeline>>,
1018    pub(crate) layout: Arc<PipelineLayout>,
1019}
1020
1021#[derive(Debug)]
1022pub struct RenderPipeline {
1023    pub(crate) state: ResourceState<RenderPipelineState>,
1024    pub(crate) device: Arc<Device>,
1025    pub(crate) _shader_modules: ArrayVec<Arc<ShaderModule>, { hal::MAX_CONCURRENT_SHADER_STAGES }>,
1026    pub(crate) pass_context: RenderPassContext,
1027    pub(crate) flags: PipelineFlags,
1028    pub(crate) topology: wgt::PrimitiveTopology,
1029    pub(crate) strip_index_format: Option<wgt::IndexFormat>,
1030    pub(crate) vertex_steps: Vec<Option<VertexStep>>,
1031    pub(crate) late_sized_buffer_groups: ArrayVec<LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }>,
1032    pub(crate) immediate_slots_required: naga::valid::ImmediateSlots,
1033    /// The `label` from the descriptor used to create the resource.
1034    pub(crate) label: String,
1035    pub(crate) tracking_data: TrackingData,
1036    /// Whether this is a mesh shader pipeline
1037    pub(crate) is_mesh: bool,
1038    pub(crate) has_task_shader: bool,
1039}
1040
1041impl Drop for RenderPipeline {
1042    #[allow(trivial_casts)]
1043    fn drop(&mut self) {
1044        profiling::scope!("RenderPipeline::drop");
1045        api_log!("RenderPipeline::drop {:?}", self as *const _);
1046        resource_log!("Destroy raw {}", self.error_ident());
1047        #[cfg(feature = "trace")]
1048        {
1049            use crate::device::trace;
1050            if let Some(t) = self.device.trace.lock().as_mut() {
1051                t.add(trace::Action::DropRenderPipeline(unsafe {
1052                    trace::to_trace(self)
1053                }));
1054            }
1055        }
1056        let ResourceState::Valid(state) = &mut self.state else {
1057            return;
1058        };
1059        // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point.
1060        let raw = unsafe { ManuallyDrop::take(&mut state.raw) };
1061        unsafe {
1062            self.device.raw().destroy_render_pipeline(raw);
1063        }
1064    }
1065}
1066
1067crate::impl_resource_type!(RenderPipeline);
1068crate::impl_labeled!(RenderPipeline);
1069crate::impl_parent_device!(RenderPipeline);
1070crate::impl_storage_item!(RenderPipeline);
1071crate::impl_trackable!(RenderPipeline);
1072
1073impl RenderPipeline {
1074    pub(crate) fn raw(&self) -> Result<&dyn hal::DynRenderPipeline, InvalidResourceError> {
1075        let ResourceState::Valid(state) = &self.state else {
1076            return Err(InvalidResourceError(self.error_ident()));
1077        };
1078        Ok(state.raw.as_ref())
1079    }
1080
1081    pub(crate) fn layout(&self) -> Result<&Arc<PipelineLayout>, InvalidResourceError> {
1082        let ResourceState::Valid(state) = &self.state else {
1083            return Err(InvalidResourceError(self.error_ident()));
1084        };
1085        Ok(&state.layout)
1086    }
1087
1088    pub(crate) fn check_valid(&self) -> Result<(), InvalidResourceError> {
1089        let ResourceState::Valid(_) = &self.state else {
1090            return Err(InvalidResourceError(self.error_ident()));
1091        };
1092        Ok(())
1093    }
1094
1095    pub(crate) fn invalid(device: Arc<Device>, label: String) -> Arc<Self> {
1096        Arc::new(Self {
1097            tracking_data: TrackingData::new(device.tracker_indices.render_pipelines.clone()),
1098            state: ResourceState::Invalid,
1099            device,
1100            _shader_modules: ArrayVec::new(),
1101            pass_context: RenderPassContext {
1102                attachments: AttachmentData {
1103                    colors: ArrayVec::new(),
1104                    resolves: ArrayVec::new(),
1105                    depth_stencil: None,
1106                },
1107                sample_count: 0,
1108                multiview_mask: None,
1109            },
1110            flags: PipelineFlags::empty(),
1111            topology: wgt::PrimitiveTopology::TriangleList,
1112            strip_index_format: None,
1113            vertex_steps: Vec::new(),
1114            late_sized_buffer_groups: ArrayVec::new(),
1115            immediate_slots_required: naga::valid::ImmediateSlots::default(),
1116            label,
1117            is_mesh: false,
1118            has_task_shader: false,
1119        })
1120    }
1121
1122    pub fn get_bind_group_layout_inner(
1123        self: &Arc<Self>,
1124        index: u32,
1125    ) -> Result<Arc<BindGroupLayout>, GetBindGroupLayoutError> {
1126        self.layout()?.get_bind_group_layout(index, self.into())
1127    }
1128
1129    pub fn get_bind_group_layout(
1130        self: &Arc<Self>,
1131        index: u32,
1132    ) -> (Arc<BindGroupLayout>, Option<GetBindGroupLayoutError>) {
1133        let (bgl, error) = match self.get_bind_group_layout_inner(index) {
1134            Ok(bgl) => (bgl, None),
1135            Err(e) => (
1136                BindGroupLayout::invalid(&self.device, String::new()),
1137                Some(e),
1138            ),
1139        };
1140        #[cfg(feature = "trace")]
1141        if let Some(ref mut trace) = *self.device.trace.lock() {
1142            use crate::device::trace;
1143            use trace::IntoTrace;
1144            trace.add(trace::Action::GetRenderPipelineBindGroupLayout {
1145                id: bgl.to_trace(),
1146                pipeline: self.to_trace(),
1147                index,
1148            });
1149        };
1150        (bgl, error)
1151    }
1152}