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