1use alloc::string::ToString as _;
2use alloc::{
3 borrow::{Cow, ToOwned},
4 boxed::Box,
5 string::String,
6 sync::Arc,
7 vec::Vec,
8};
9use core::{marker::PhantomData, mem::ManuallyDrop, num::NonZeroU32};
10
11use arrayvec::ArrayVec;
12use naga::error::ShaderError;
13use thiserror::Error;
14use wgt::error::{ErrorType, WebGpuError};
15
16pub use crate::pipeline_cache::PipelineCacheValidationError;
17use crate::{
18 api_log,
19 binding_model::{
20 BindGroupLayout, CreateBindGroupLayoutError, CreatePipelineLayoutError,
21 GetBindGroupLayoutError, PipelineLayout,
22 },
23 command::ColorAttachmentError,
24 device::{
25 AttachmentData, Device, DeviceError, MissingDownlevelFlags, MissingFeatures,
26 RenderPassContext,
27 },
28 pipeline_cache,
29 resource::{InvalidResourceError, Labeled, ResourceState, TrackingData},
30 resource_log, validation, FastHashSet, Label, LabelHelpers as _,
31};
32
33#[derive(Debug, Default)]
37pub(crate) struct LateSizedBufferGroup {
38 pub(crate) shader_sizes: Vec<wgt::BufferAddress>,
40}
41
42#[allow(clippy::large_enum_variant)]
43pub enum ShaderModuleSource<'a> {
44 #[cfg(feature = "wgsl")]
45 Wgsl(Cow<'a, str>),
46 #[cfg(feature = "glsl")]
47 Glsl(Cow<'a, str>, naga::front::glsl::Options),
48 #[cfg(feature = "spirv")]
49 SpirV(Cow<'a, [u32]>, naga::front::spv::Options),
50 Naga(Cow<'static, naga::Module>),
51 #[doc(hidden)]
54 Dummy(PhantomData<&'a ()>),
55}
56
57#[derive(Clone, Debug)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub struct ShaderModuleDescriptor<'a> {
60 pub label: Label<'a>,
61 #[cfg_attr(feature = "serde", serde(default))]
62 pub runtime_checks: wgt::ShaderRuntimeChecks,
63}
64
65pub type ShaderModuleDescriptorPassthrough<'a> =
66 wgt::CreateShaderModuleDescriptorPassthrough<'a, Label<'a>>;
67
68#[derive(Debug)]
69pub struct PassthroughInterface {
70 pub entry_point_names: FastHashSet<String>,
71}
72
73#[expect(clippy::large_enum_variant)]
77#[derive(Debug)]
78pub enum ShaderModuleState {
79 NagaModule {
82 shader: hal::NagaShader,
83 runtime_checks: wgt::ShaderRuntimeChecks,
84 },
85 Passthrough {
86 raw: Box<dyn hal::DynShaderModule>,
87 interface: PassthroughInterface,
88 },
89}
90
91#[derive(Debug)]
92pub struct ShaderModule {
93 pub(crate) state: ResourceState<ShaderModuleState>,
94 pub(crate) device: Arc<Device>,
95 pub(crate) label: String,
97 pub(crate) compilation_info: wgt::CompilationInfo,
98}
99
100impl Drop for ShaderModule {
101 #[allow(trivial_casts)]
102 fn drop(&mut self) {
103 profiling::scope!("ShaderModule::drop");
104 api_log!("ShaderModule::drop {:?}", self as *const _);
105 resource_log!("Destroy raw {}", self.error_ident());
106 #[cfg(feature = "trace")]
107 if let Some(t) = self.device.trace.lock().as_mut() {
108 use crate::device::trace::{to_trace, Action};
109
110 t.add(Action::DropShaderModule(unsafe { to_trace(self) }));
111 }
112 let ResourceState::Valid(state) =
113 core::mem::replace(&mut self.state, ResourceState::Invalid)
114 else {
115 return;
116 };
117 match state {
118 ShaderModuleState::NagaModule { .. } => {}
119 ShaderModuleState::Passthrough { raw, .. } => unsafe {
120 self.device.raw().destroy_shader_module(raw);
121 },
122 }
123 }
124}
125
126crate::impl_resource_type!(ShaderModule);
127crate::impl_labeled!(ShaderModule);
128crate::impl_parent_device!(ShaderModule);
129crate::impl_storage_item!(ShaderModule);
130
131impl ShaderModule {
132 pub(crate) fn state(&self) -> Result<&ShaderModuleState, InvalidResourceError> {
133 let ResourceState::Valid(state) = &self.state else {
134 return Err(InvalidResourceError(self.error_ident()));
135 };
136 Ok(state)
137 }
138
139 pub(crate) fn invalid(
140 device: Arc<Device>,
141 label: String,
142 compilation_info: wgt::CompilationInfo,
143 ) -> Arc<Self> {
144 Arc::new(Self {
145 state: ResourceState::Invalid,
146 device,
147 label,
148 compilation_info,
149 })
150 }
151
152 pub fn compilation_info(&self) -> &wgt::CompilationInfo {
153 &self.compilation_info
154 }
155
156 pub(crate) fn finalize_entry_point_name(
173 &self,
174 stage: naga::ShaderStage,
175 entry_point: Option<&str>,
176 ) -> Result<String, validation::StageError> {
177 let state = self.state()?;
178 match state {
179 ShaderModuleState::NagaModule { ref shader, .. } => {
180 finalize_naga_entry_point_name(&shader.module, stage, entry_point)
181 }
182 ShaderModuleState::Passthrough { ref interface, .. } => {
183 finalize_passthrough_entry_point_name(interface, entry_point)
184 }
185 }
186 }
187}
188
189fn finalize_naga_entry_point_name(
190 module: &naga::Module,
191 stage: naga::ShaderStage,
192 entry_point: Option<&str>,
193) -> Result<String, validation::StageError> {
194 let mut candidates = module.entry_points.iter().filter(|ep| ep.stage == stage);
195 if let Some(name) = entry_point {
196 return match candidates.find(|ep| ep.name == name) {
197 Some(ep) => Ok(ep.name.clone()),
198 None => Err(validation::StageError::NoEntryPointWithNameForStage {
199 stage,
200 name: name.to_owned(),
201 }),
202 };
203 }
204
205 let Some(first) = candidates.next() else {
206 return Err(validation::StageError::NoEntryPointForStage(stage));
207 };
208 if candidates.next().is_some() {
209 return Err(validation::StageError::AmbiguousEntryPointForStage(stage));
210 }
211 Ok(first.name.clone())
212}
213
214fn finalize_passthrough_entry_point_name(
215 interface: &PassthroughInterface,
216 entry_point: Option<&str>,
217) -> Result<String, validation::StageError> {
218 if let Some(ep) = entry_point {
219 return if interface.entry_point_names.contains(ep) {
220 Ok(ep.to_owned())
221 } else {
222 Err(validation::StageError::NoEntryPointWithName(ep.to_owned()))
223 };
224 }
225
226 match interface.entry_point_names.len() {
227 0 => Err(validation::StageError::NoEntryPoints),
228 1 => Ok(interface
229 .entry_point_names
230 .iter()
231 .next()
232 .unwrap()
233 .to_owned()),
234 _ => Err(validation::StageError::AmbiguousEntryPoint),
235 }
236}
237
238#[derive(Clone, Debug, Error)]
240#[non_exhaustive]
241pub enum CreateShaderModuleError {
242 #[cfg(feature = "wgsl")]
248 #[error("Shader '{label}' parsing error. Concrete error is available via `get_compilation_info`", label = _0.label.as_deref().unwrap_or_default())]
249 Parsing(ShaderError<naga::front::wgsl::ParseError>),
250
251 #[cfg(feature = "glsl")]
252 #[error("Shader '{label}' parsing error. Concrete error is available via `get_compilation_info`", label = _0.label.as_deref().unwrap_or_default())]
253 ParsingGlsl(ShaderError<naga::front::glsl::ParseErrors>),
254
255 #[cfg(feature = "spirv")]
256 #[error("Shader '{label}' parsing error. Concrete error is available via `get_compilation_info`", label = _0.label.as_deref().unwrap_or_default())]
257 ParsingSpirV(ShaderError<naga::front::spv::Error>),
258
259 #[error("Failed to generate the backend-specific code")]
260 Generation,
261
262 #[error(transparent)]
263 Device(#[from] DeviceError),
264
265 #[error("Shader '{label}' validation error. Concrete error is available via `get_compilation_info`", label = _0.label.as_deref().unwrap_or_default())]
266 Validation(ShaderError<naga::WithSpan<naga::valid::ValidationError>>),
267
268 #[error(transparent)]
269 MissingFeatures(#[from] MissingFeatures),
270
271 #[error(
272 "Shader global {bind:?} uses a group index {group} that exceeds the max_bind_groups limit of {limit}."
273 )]
274 InvalidGroupIndex {
275 bind: naga::ResourceBinding,
276 group: u32,
277 limit: u32,
278 },
279
280 #[error("Generic shader passthrough does not contain any code compatible with this backend.")]
281 NotCompiledForBackend,
282
283 #[error(
284 "Generic passthrough shaders which use GLSL or DXIL must contain exactly one entry point."
285 )]
286 IncorrectPassthroughEntryPointCount,
287}
288
289impl WebGpuError for CreateShaderModuleError {
290 fn webgpu_error_type(&self) -> ErrorType {
291 match self {
292 Self::Device(e) => e.webgpu_error_type(),
293 Self::MissingFeatures(e) => e.webgpu_error_type(),
294
295 Self::Generation => ErrorType::Internal,
296
297 Self::Validation(..)
298 | Self::InvalidGroupIndex { .. }
299 | Self::IncorrectPassthroughEntryPointCount
300 | Self::NotCompiledForBackend => ErrorType::Validation,
301 #[cfg(feature = "wgsl")]
302 Self::Parsing(..) => ErrorType::Validation,
303 #[cfg(feature = "glsl")]
304 Self::ParsingGlsl(..) => ErrorType::Validation,
305 #[cfg(feature = "spirv")]
306 Self::ParsingSpirV(..) => ErrorType::Validation,
307 }
308 }
309}
310
311#[cfg(feature = "wgsl")]
312pub(crate) fn wgsl_to_compilation_info(
313 value: &ShaderError<naga::front::wgsl::ParseError>,
314) -> wgt::CompilationInfo {
315 use alloc::{string::ToString, vec};
316 wgt::CompilationInfo {
317 messages: vec![wgt::CompilationMessage {
318 message: value.to_string(),
319 message_type: wgt::CompilationMessageType::Error,
320 location: value
321 .inner
322 .location(&value.source)
323 .as_ref()
324 .map(naga_to_source_location),
325 }],
326 }
327}
328#[cfg(feature = "glsl")]
329pub(crate) fn glsl_to_compilation_info(
330 value: &ShaderError<naga::front::glsl::ParseErrors>,
331) -> wgt::CompilationInfo {
332 use alloc::string::ToString;
333 let messages = value
334 .inner
335 .errors
336 .iter()
337 .map(|err| wgt::CompilationMessage {
338 message: err.to_string(),
339 message_type: wgt::CompilationMessageType::Error,
340 location: err
341 .location(&value.source)
342 .as_ref()
343 .map(naga_to_source_location),
344 })
345 .collect();
346 wgt::CompilationInfo { messages }
347}
348
349#[cfg(feature = "spirv")]
350pub(crate) fn spirv_to_compilation_info(
351 value: &ShaderError<naga::front::spv::Error>,
352) -> wgt::CompilationInfo {
353 use alloc::{string::ToString, vec};
354 wgt::CompilationInfo {
355 messages: vec![wgt::CompilationMessage {
356 message: value.to_string(),
357 message_type: wgt::CompilationMessageType::Error,
358 location: None,
359 }],
360 }
361}
362
363pub(crate) fn naga_to_compilation_info(
364 value: &ShaderError<naga::WithSpan<naga::valid::ValidationError>>,
365) -> wgt::CompilationInfo {
366 use alloc::{string::ToString, vec};
367 wgt::CompilationInfo {
368 messages: vec![wgt::CompilationMessage {
369 message: value.to_string(),
370 message_type: wgt::CompilationMessageType::Error,
371 location: value
372 .inner
373 .location(&value.source)
374 .as_ref()
375 .map(naga_to_source_location),
376 }],
377 }
378}
379
380fn naga_to_source_location(value: &naga::SourceLocation) -> wgt::SourceLocation {
381 wgt::SourceLocation {
382 length: value.length,
383 offset: value.offset,
384 line_number: value.line_number,
385 line_position: value.line_position,
386 }
387}
388
389pub(crate) fn shader_module_error_into_compilation_info(
390 value: &CreateShaderModuleError,
391) -> wgt::CompilationInfo {
392 match value {
393 #[cfg(feature = "wgsl")]
394 CreateShaderModuleError::Parsing(v) => wgsl_to_compilation_info(v),
395 #[cfg(feature = "glsl")]
396 CreateShaderModuleError::ParsingGlsl(v) => glsl_to_compilation_info(v),
397 #[cfg(feature = "spirv")]
398 CreateShaderModuleError::ParsingSpirV(v) => spirv_to_compilation_info(v),
399 CreateShaderModuleError::Validation(v) => naga_to_compilation_info(v),
400 CreateShaderModuleError::Device(_) | CreateShaderModuleError::Generation => {
403 wgt::CompilationInfo {
404 messages: Vec::new(),
405 }
406 }
407 _ => wgt::CompilationInfo {
409 messages: alloc::vec![wgt::CompilationMessage {
410 message: value.to_string(),
411 message_type: wgt::CompilationMessageType::Error,
412 location: None,
413 }],
414 },
415 }
416}
417
418#[derive(Clone, Debug)]
420#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
421pub struct ProgrammableStageDescriptor<'a, SM = Arc<ShaderModule>> {
423 pub module: SM,
425
426 pub entry_point: Option<Cow<'a, str>>,
434
435 pub constants: naga::back::PipelineConstants,
444
445 pub zero_initialize_workgroup_memory: bool,
452}
453
454#[derive(Clone, Debug, Error)]
455#[non_exhaustive]
456pub enum CreatePipelineError {
457 #[error(transparent)]
458 Device(#[from] DeviceError),
459 #[error("Unable to derive an implicit layout")]
460 Implicit(#[from] ImplicitLayoutError),
461 #[error("Error matching {stage:?} shader requirements against the pipeline")]
462 Stage {
463 stage: wgt::ShaderStages,
464 #[source]
465 error: validation::StageError,
466 },
467 #[error("Internal error in {stage:?} shader: {message}")]
468 Internal {
469 stage: wgt::ShaderStages,
470 message: String,
471 },
472 #[error("Pipeline constant error in {stage:?} shader: {message}")]
473 PipelineConstants {
474 stage: wgt::ShaderStages,
475 message: String,
476 },
477 #[error(transparent)]
478 MissingFeatures(#[from] MissingFeatures),
479 #[error(transparent)]
480 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
481 #[error(transparent)]
482 InvalidResource(#[from] InvalidResourceError),
483 #[error(transparent)]
484 Render(#[from] RenderError),
485}
486
487impl From<ColorAttachmentError> for CreatePipelineError {
488 fn from(error: ColorAttachmentError) -> Self {
489 Self::Render(error.into())
490 }
491}
492
493impl From<DepthStencilStateError> for CreatePipelineError {
494 fn from(error: DepthStencilStateError) -> Self {
495 Self::Render(error.into())
496 }
497}
498
499impl CreatePipelineError {
500 pub(crate) fn from_hal_without_device_loss(error: hal::PipelineError) -> Self {
509 match error {
510 hal::PipelineError::Device(error) => {
511 CreatePipelineError::Device(DeviceError::from_hal(error))
512 }
513 hal::PipelineError::Linkage(stage, message) => {
514 CreatePipelineError::Internal { stage, message }
515 }
516 hal::PipelineError::EntryPoint(stage) => CreatePipelineError::Internal {
517 stage: hal::auxil::map_naga_stage(stage),
518 message: crate::device::ENTRYPOINT_FAILURE_ERROR.to_string(),
519 },
520 hal::PipelineError::PipelineConstants(stage, message) => {
521 CreatePipelineError::PipelineConstants { stage, message }
522 }
523 }
524 }
525}
526
527pub type ImplicitBindGroupCount = u8;
529
530#[derive(Clone, Debug, Error)]
531#[non_exhaustive]
532pub enum ImplicitLayoutError {
533 #[error(transparent)]
534 BindGroup(#[from] CreateBindGroupLayoutError),
535 #[error(transparent)]
536 Pipeline(#[from] CreatePipelineLayoutError),
537 #[error("Unable to create implicit pipeline layout from passthrough shader stage: {0:?}")]
538 Passthrough(wgt::ShaderStages),
539}
540
541impl WebGpuError for ImplicitLayoutError {
542 fn webgpu_error_type(&self) -> ErrorType {
543 match self {
544 Self::BindGroup(e) => e.webgpu_error_type(),
545 Self::Pipeline(e) => e.webgpu_error_type(),
546 Self::Passthrough(_) => ErrorType::Validation,
547 }
548 }
549}
550
551#[derive(Clone, Debug)]
553#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
554pub struct ComputePipelineDescriptor<
556 'a,
557 PLL = Arc<PipelineLayout>,
558 SM = Arc<ShaderModule>,
559 PLC = Arc<PipelineCache>,
560> {
561 pub label: Label<'a>,
562 pub layout: Option<PLL>,
564 pub stage: ProgrammableStageDescriptor<'a, SM>,
566 pub cache: Option<PLC>,
568}
569
570impl WebGpuError for CreatePipelineError {
571 fn webgpu_error_type(&self) -> ErrorType {
572 match self {
573 Self::Device(e) => e.webgpu_error_type(),
574 Self::InvalidResource(e) => e.webgpu_error_type(),
575 Self::MissingFeatures(e) => e.webgpu_error_type(),
576 Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
577 Self::Implicit(e) => e.webgpu_error_type(),
578 Self::Stage { error: e, .. } => e.webgpu_error_type(),
579 Self::Internal { .. } => ErrorType::Internal,
580 Self::PipelineConstants { .. } => ErrorType::Validation,
581 Self::Render(e) => e.webgpu_error_type(),
582 }
583 }
584}
585
586#[derive(Debug)]
587pub struct ComputePipelineState {
588 pub(crate) raw: ManuallyDrop<Box<dyn hal::DynComputePipeline>>,
589 pub(crate) layout: Arc<PipelineLayout>,
590 pub(crate) _shader_module: Arc<ShaderModule>,
591}
592
593#[derive(Debug)]
594pub struct ComputePipeline {
595 pub(crate) state: ResourceState<ComputePipelineState>,
596 pub(crate) device: Arc<Device>,
597 pub(crate) late_sized_buffer_groups: ArrayVec<LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }>,
598 pub(crate) immediate_slots_required: naga::valid::ImmediateSlots,
599 pub(crate) label: String,
601 pub(crate) tracking_data: TrackingData,
602}
603
604impl Drop for ComputePipeline {
605 #[allow(trivial_casts)]
606 fn drop(&mut self) {
607 profiling::scope!("ComputePipeline::drop");
608 api_log!("ComputePipeline::drop {:?}", self as *const _);
609 resource_log!("Destroy raw {}", self.error_ident());
610 #[cfg(feature = "trace")]
611 {
612 use crate::device::trace;
613 if let Some(t) = self.device.trace.lock().as_mut() {
614 t.add(trace::Action::DropComputePipeline(unsafe {
615 trace::to_trace(self)
616 }));
617 }
618 }
619 let ResourceState::Valid(state) = &mut self.state else {
620 return;
621 };
622 let raw = unsafe { ManuallyDrop::take(&mut state.raw) };
624 unsafe {
625 self.device.raw().destroy_compute_pipeline(raw);
626 }
627 }
628}
629
630crate::impl_resource_type!(ComputePipeline);
631crate::impl_labeled!(ComputePipeline);
632crate::impl_parent_device!(ComputePipeline);
633crate::impl_storage_item!(ComputePipeline);
634crate::impl_trackable!(ComputePipeline);
635
636impl ComputePipeline {
637 pub(crate) fn raw(&self) -> Result<&dyn hal::DynComputePipeline, InvalidResourceError> {
638 let ResourceState::Valid(state) = &self.state else {
639 return Err(InvalidResourceError(self.error_ident()));
640 };
641 Ok(state.raw.as_ref())
642 }
643
644 pub(crate) fn layout(&self) -> Result<&Arc<PipelineLayout>, InvalidResourceError> {
645 let ResourceState::Valid(state) = &self.state else {
646 return Err(InvalidResourceError(self.error_ident()));
647 };
648 Ok(&state.layout)
649 }
650
651 pub(crate) fn check_valid(&self) -> Result<(), InvalidResourceError> {
652 let ResourceState::Valid(_) = &self.state else {
653 return Err(InvalidResourceError(self.error_ident()));
654 };
655 Ok(())
656 }
657
658 pub(crate) fn invalid(device: Arc<Device>, label: String) -> Arc<Self> {
659 Arc::new(Self {
660 tracking_data: TrackingData::new(device.tracker_indices.compute_pipelines.clone()),
661 state: ResourceState::Invalid,
662 device,
663 late_sized_buffer_groups: ArrayVec::new(),
664 immediate_slots_required: naga::valid::ImmediateSlots::default(),
665 label,
666 })
667 }
668
669 pub fn get_bind_group_layout_inner(
670 self: &Arc<Self>,
671 index: u32,
672 ) -> Result<Arc<BindGroupLayout>, GetBindGroupLayoutError> {
673 self.layout()?.get_bind_group_layout(index, self.into())
674 }
675
676 pub fn get_bind_group_layout(self: &Arc<Self>, index: u32) -> Arc<BindGroupLayout> {
677 let bgl = self
678 .get_bind_group_layout_inner(index)
679 .unwrap_or_else(|err| {
680 self.device
681 .handle_error_nolabel(err, "ComputePipeline::get_bind_group_layout");
682 BindGroupLayout::invalid(&self.device, String::new())
683 });
684 #[cfg(feature = "trace")]
685 if let Some(ref mut trace) = *self.device.trace.lock() {
686 use crate::device::trace;
687 use trace::IntoTrace;
688 trace.add(trace::Action::GetComputePipelineBindGroupLayout {
689 id: bgl.to_trace(),
690 pipeline: self.to_trace(),
691 index,
692 });
693 };
694 bgl
695 }
696}
697
698#[derive(Clone, Debug, Error)]
699#[non_exhaustive]
700pub enum CreatePipelineCacheError {
701 #[error(transparent)]
702 Device(#[from] DeviceError),
703 #[error("Pipeline cache validation failed")]
704 Validation(#[from] PipelineCacheValidationError),
705 #[error(transparent)]
706 MissingFeatures(#[from] MissingFeatures),
707}
708
709impl WebGpuError for CreatePipelineCacheError {
710 fn webgpu_error_type(&self) -> ErrorType {
711 match self {
712 Self::Device(e) => e.webgpu_error_type(),
713 Self::Validation(e) => e.webgpu_error_type(),
714 Self::MissingFeatures(e) => e.webgpu_error_type(),
715 }
716 }
717}
718
719#[derive(Debug)]
720pub struct PipelineCache {
721 pub(crate) raw: ResourceState<Box<dyn hal::DynPipelineCache>>,
722 pub(crate) device: Arc<Device>,
723 pub(crate) label: String,
725}
726
727impl Drop for PipelineCache {
728 #[allow(trivial_casts)]
729 fn drop(&mut self) {
730 profiling::scope!("PipelineCache::drop");
731 api_log!("PipelineCache::drop {:?}", self as *const _);
732 #[cfg(feature = "trace")]
733 if let Some(t) = self.device.trace.lock().as_mut() {
734 use crate::device::trace::{to_trace, Action};
735 t.add(Action::DropPipelineCache(unsafe { to_trace(self) }));
736 }
737 resource_log!("Destroy raw {}", self.error_ident());
738 if let ResourceState::Valid(raw) = core::mem::replace(&mut self.raw, ResourceState::Invalid)
739 {
740 unsafe {
741 self.device.raw().destroy_pipeline_cache(raw);
742 }
743 }
744 }
745}
746
747crate::impl_resource_type!(PipelineCache);
748crate::impl_labeled!(PipelineCache);
749crate::impl_parent_device!(PipelineCache);
750crate::impl_storage_item!(PipelineCache);
751
752impl PipelineCache {
753 pub(crate) fn raw(&self) -> Result<&dyn hal::DynPipelineCache, InvalidResourceError> {
754 self.raw
755 .as_ref()
756 .valid()
757 .map(|raw| raw.as_ref())
758 .ok_or_else(|| InvalidResourceError(self.error_ident()))
759 }
760
761 pub(crate) fn check_is_valid(&self) -> Result<(), InvalidResourceError> {
762 self.raw().map(|_| ())
763 }
764
765 pub(crate) fn invalid(device: Arc<Device>, desc: &PipelineCacheDescriptor) -> Arc<Self> {
766 Arc::new(Self {
767 raw: ResourceState::Invalid,
768 device,
769 label: desc.label.to_string(),
770 })
771 }
772
773 pub fn get_data(self: &Arc<Self>) -> Option<Vec<u8>> {
774 api_log!("PipelineCache::get_data");
775
776 let ResourceState::Valid(raw) = &self.raw else {
777 return None;
778 };
779
780 if !self.device.is_valid() {
781 return None;
782 }
783 let mut vec = unsafe { self.device.raw().pipeline_cache_get_data(raw.as_ref()) }?;
784 let validation_key = self.device.raw().pipeline_cache_validation_key()?;
785
786 let mut header_contents = [0; pipeline_cache::HEADER_LENGTH];
787 pipeline_cache::add_cache_header(
788 &mut header_contents,
789 &vec,
790 &self.device.adapter.raw.info,
791 validation_key,
792 );
793
794 let deleted = vec.splice(..0, header_contents).collect::<Vec<_>>();
795 debug_assert!(deleted.is_empty());
796
797 Some(vec)
798 }
799}
800
801#[derive(Clone, Debug)]
803#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
804#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
805pub struct VertexBufferLayout<'a> {
806 pub array_stride: wgt::BufferAddress,
808 pub step_mode: wgt::VertexStepMode,
810 pub attributes: Cow<'a, [wgt::VertexAttribute]>,
812}
813
814impl Default for VertexBufferLayout<'_> {
816 fn default() -> Self {
817 Self {
818 array_stride: Default::default(),
819 step_mode: Default::default(),
820 attributes: Cow::Borrowed(&[]),
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
827#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
828pub struct VertexState<'a, SM = Arc<ShaderModule>> {
830 pub stage: ProgrammableStageDescriptor<'a, SM>,
832 pub buffers: Cow<'a, [Option<VertexBufferLayout<'a>>]>,
834}
835
836#[derive(Clone, Debug)]
838#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
839pub struct FragmentState<'a, SM = Arc<ShaderModule>> {
841 pub stage: ProgrammableStageDescriptor<'a, SM>,
843 pub targets: Cow<'a, [Option<wgt::ColorTargetState>]>,
845}
846
847#[derive(Clone, Debug)]
849#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
850pub struct TaskState<'a, SM = Arc<ShaderModule>> {
851 pub stage: ProgrammableStageDescriptor<'a, SM>,
853}
854
855#[derive(Clone, Debug)]
857#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
858pub struct MeshState<'a, SM = Arc<ShaderModule>> {
859 pub stage: ProgrammableStageDescriptor<'a, SM>,
861}
862
863#[doc(hidden)]
871#[derive(Clone, Debug)]
872#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
873pub enum RenderPipelineVertexProcessor<'a, SM = Arc<ShaderModule>> {
874 Vertex(VertexState<'a, SM>),
875 Mesh(Option<TaskState<'a, SM>>, MeshState<'a, SM>),
876}
877
878#[derive(Clone, Debug)]
880#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
881pub struct RenderPipelineDescriptor<
882 'a,
883 PLL = Arc<PipelineLayout>,
884 SM = Arc<ShaderModule>,
885 PLC = Arc<PipelineCache>,
886> {
887 pub label: Label<'a>,
888 pub layout: Option<PLL>,
890 pub vertex: VertexState<'a, SM>,
892 #[cfg_attr(feature = "serde", serde(default))]
894 pub primitive: wgt::PrimitiveState,
895 #[cfg_attr(feature = "serde", serde(default))]
897 pub depth_stencil: Option<wgt::DepthStencilState>,
898 #[cfg_attr(feature = "serde", serde(default))]
900 pub multisample: wgt::MultisampleState,
901 pub fragment: Option<FragmentState<'a, SM>>,
903 pub multiview_mask: Option<NonZeroU32>,
906 pub cache: Option<PLC>,
908}
909#[derive(Clone, Debug)]
911#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
912pub struct MeshPipelineDescriptor<
913 'a,
914 PLL = Arc<PipelineLayout>,
915 SM = Arc<ShaderModule>,
916 PLC = Arc<PipelineCache>,
917> {
918 pub label: Label<'a>,
919 pub layout: Option<PLL>,
921 pub task: Option<TaskState<'a, SM>>,
923 pub mesh: MeshState<'a, SM>,
925 #[cfg_attr(feature = "serde", serde(default))]
927 pub primitive: wgt::PrimitiveState,
928 #[cfg_attr(feature = "serde", serde(default))]
930 pub depth_stencil: Option<wgt::DepthStencilState>,
931 #[cfg_attr(feature = "serde", serde(default))]
933 pub multisample: wgt::MultisampleState,
934 pub fragment: Option<FragmentState<'a, SM>>,
936 pub multiview: Option<NonZeroU32>,
939 pub cache: Option<PLC>,
941}
942
943#[doc(hidden)]
951#[derive(Clone, Debug)]
952#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
953pub struct GeneralRenderPipelineDescriptor<
954 'a,
955 PLL = Arc<PipelineLayout>,
956 SM = Arc<ShaderModule>,
957 PLC = Arc<PipelineCache>,
958> {
959 pub label: Label<'a>,
960 pub layout: Option<PLL>,
962 pub vertex: RenderPipelineVertexProcessor<'a, SM>,
964 #[cfg_attr(feature = "serde", serde(default))]
966 pub primitive: wgt::PrimitiveState,
967 #[cfg_attr(feature = "serde", serde(default))]
969 pub depth_stencil: Option<wgt::DepthStencilState>,
970 #[cfg_attr(feature = "serde", serde(default))]
972 pub multisample: wgt::MultisampleState,
973 pub fragment: Option<FragmentState<'a, SM>>,
975 pub multiview_mask: Option<NonZeroU32>,
978 pub cache: Option<PLC>,
980}
981impl<'a, PLL, SM, PLC> From<RenderPipelineDescriptor<'a, PLL, SM, PLC>>
982 for GeneralRenderPipelineDescriptor<'a, PLL, SM, PLC>
983{
984 fn from(value: RenderPipelineDescriptor<'a, PLL, SM, PLC>) -> Self {
985 Self {
986 label: value.label,
987 layout: value.layout,
988 vertex: RenderPipelineVertexProcessor::Vertex(value.vertex),
989 primitive: value.primitive,
990 depth_stencil: value.depth_stencil,
991 multisample: value.multisample,
992 fragment: value.fragment,
993 multiview_mask: value.multiview_mask,
994 cache: value.cache,
995 }
996 }
997}
998impl<'a, PLL, SM, PLC> From<MeshPipelineDescriptor<'a, PLL, SM, PLC>>
999 for GeneralRenderPipelineDescriptor<'a, PLL, SM, PLC>
1000{
1001 fn from(value: MeshPipelineDescriptor<'a, PLL, SM, PLC>) -> Self {
1002 Self {
1003 label: value.label,
1004 layout: value.layout,
1005 vertex: RenderPipelineVertexProcessor::Mesh(value.task, value.mesh),
1006 primitive: value.primitive,
1007 depth_stencil: value.depth_stencil,
1008 multisample: value.multisample,
1009 fragment: value.fragment,
1010 multiview_mask: value.multiview,
1011 cache: value.cache,
1012 }
1013 }
1014}
1015
1016pub type ResolvedGeneralRenderPipelineDescriptor<'a> =
1020 GeneralRenderPipelineDescriptor<'a, Arc<PipelineLayout>, Arc<ShaderModule>, Arc<PipelineCache>>;
1021
1022#[derive(Clone, Debug)]
1023#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1024pub struct PipelineCacheDescriptor<'a> {
1025 pub label: Label<'a>,
1026 pub data: Option<Cow<'a, [u8]>>,
1027 pub fallback: bool,
1028}
1029
1030#[derive(Clone, Debug, Error)]
1031#[non_exhaustive]
1032pub enum ColorStateError {
1033 #[error("Format {0:?} is not renderable")]
1034 FormatNotRenderable(wgt::TextureFormat),
1035 #[error("Format {0:?} is not blendable")]
1036 FormatNotBlendable(wgt::TextureFormat),
1037 #[error("Format {0:?} does not have a color aspect")]
1038 FormatNotColor(wgt::TextureFormat),
1039 #[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:?}.")]
1040 InvalidSampleCount(u32, wgt::TextureFormat, Vec<u32>, Vec<u32>),
1041 #[error("Output format {pipeline} is incompatible with the shader {shader}")]
1042 IncompatibleFormat {
1043 pipeline: validation::NumericType,
1044 shader: validation::NumericType,
1045 },
1046 #[error("Invalid write mask {0:?}")]
1047 InvalidWriteMask(wgt::ColorWrites),
1048 #[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.")]
1049 BlendFactorOnUnsupportedTarget {
1050 factor: wgt::BlendFactor,
1051 target: u32,
1052 },
1053 #[error("The {which} blend factor {factor:?} is not valid because the shader output does have an alpha channel.")]
1054 InvalidAlphaBlend {
1055 which: &'static str,
1056 factor: wgt::BlendFactor,
1057 },
1058 #[error(
1059 "Blend factor {factor:?} for render target {target} is not valid. Blend factor must be `one` when using min/max blend operations."
1060 )]
1061 InvalidMinMaxBlendFactor {
1062 factor: wgt::BlendFactor,
1063 target: u32,
1064 },
1065 #[error("Shader does not produce an output at this index")]
1066 OutputNotPresent,
1067}
1068
1069#[derive(Clone, Debug, Error)]
1070#[non_exhaustive]
1071pub enum DepthStencilStateError {
1072 #[error("Format {0:?} is not renderable")]
1073 FormatNotRenderable(wgt::TextureFormat),
1074 #[error("Format {0:?} is not a depth/stencil format")]
1075 FormatNotDepthOrStencil(wgt::TextureFormat),
1076 #[error("Format {0:?} does not have a depth aspect, but depth test/write is enabled")]
1077 FormatNotDepth(wgt::TextureFormat),
1078 #[error("Format {0:?} does not have a stencil aspect, but stencil test/write is enabled")]
1079 FormatNotStencil(wgt::TextureFormat),
1080 #[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:?}.")]
1081 InvalidSampleCount(u32, wgt::TextureFormat, Vec<u32>, Vec<u32>),
1082 #[error("Depth bias is not compatible with non-triangle topology {0:?}")]
1083 DepthBiasWithIncompatibleTopology(wgt::PrimitiveTopology),
1084 #[error("Depth compare function must be specified for depth format {0:?}")]
1085 MissingDepthCompare(wgt::TextureFormat),
1086 #[error("Depth write enabled must be specified for depth format {0:?}")]
1087 MissingDepthWriteEnabled(wgt::TextureFormat),
1088}
1089
1090#[derive(Clone, Debug, Error)]
1094#[non_exhaustive]
1095pub enum RenderError {
1096 #[error(transparent)]
1097 ColorAttachment(#[from] ColorAttachmentError),
1098 #[error("Color state [{0}] is invalid")]
1099 ColorState(u8, #[source] ColorStateError),
1100 #[error("Depth/stencil state is invalid")]
1101 DepthStencilState(#[from] DepthStencilStateError),
1102 #[error("Invalid sample count {0}")]
1103 InvalidSampleCount(u32),
1104 #[error("The number of vertex buffers {given} exceeds the limit {limit}")]
1105 TooManyVertexBuffers { given: u32, limit: u32 },
1106 #[error("The number of bind groups + vertex buffers {given} exceeds the limit {limit}")]
1107 TooManyBindGroupsPlusVertexBuffers { given: u32, limit: u32 },
1108 #[error("The number of vertex-stage buffers and acceleration structures {given} exceeds the limit {limit}")]
1109 TooManyBuffersAndAccelerationStructuresInVertexStage { given: u32, limit: u32 },
1110 #[error("The total number of vertex attributes {given} exceeds the limit {limit}")]
1111 TooManyVertexAttributes { given: u32, limit: u32 },
1112 #[error("Vertex attribute location {given} must be less than limit {limit}")]
1113 VertexAttributeLocationTooLarge { given: u32, limit: u32 },
1114 #[error("Vertex buffer {index} stride {given} exceeds the limit {limit}")]
1115 VertexStrideTooLarge { index: u32, given: u32, limit: u32 },
1116 #[error("Vertex attribute at location {location} stride {given} exceeds the limit {limit}")]
1117 VertexAttributeStrideTooLarge {
1118 location: wgt::ShaderLocation,
1119 given: u32,
1120 limit: u32,
1121 },
1122 #[error("Vertex buffer {index} stride {stride} does not respect `VERTEX_ALIGNMENT`")]
1123 UnalignedVertexStride {
1124 index: u32,
1125 stride: wgt::BufferAddress,
1126 },
1127 #[error("Vertex attribute at location {location} has invalid offset {offset}")]
1128 InvalidVertexAttributeOffset {
1129 location: wgt::ShaderLocation,
1130 offset: wgt::BufferAddress,
1131 },
1132 #[error("Two or more vertex attributes were assigned to the same location in the shader: {0}")]
1133 ShaderLocationClash(u32),
1134 #[error("Strip index format was not set to None but to {strip_index_format:?} while using the non-strip topology {topology:?}")]
1135 StripIndexFormatForNonStripTopology {
1136 strip_index_format: Option<wgt::IndexFormat>,
1137 topology: wgt::PrimitiveTopology,
1138 },
1139 #[error("Conservative Rasterization is only supported for wgt::PolygonMode::Fill")]
1140 ConservativeRasterizationNonFillPolygonMode,
1141 #[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.")]
1142 UnalignedShader { group: u32, binding: u32, size: u64 },
1143 #[error("Dual-source blending requires exactly one color target, but {count} color targets are present")]
1144 DualSourceBlendingWithMultipleColorTargets { count: usize },
1145 #[error("{}", concat!(
1146 "At least one color attachment or depth-stencil attachment was expected, ",
1147 "but no render target for the pipeline was specified."
1148 ))]
1149 NoTargetSpecified,
1150}
1151
1152impl WebGpuError for RenderError {
1153 fn webgpu_error_type(&self) -> ErrorType {
1154 match self {
1155 Self::ColorAttachment(_)
1156 | Self::ColorState(_, _)
1157 | Self::DepthStencilState(_)
1158 | Self::InvalidSampleCount(_)
1159 | Self::TooManyVertexBuffers { .. }
1160 | Self::TooManyBindGroupsPlusVertexBuffers { .. }
1161 | Self::TooManyBuffersAndAccelerationStructuresInVertexStage { .. }
1162 | Self::TooManyVertexAttributes { .. }
1163 | Self::VertexAttributeLocationTooLarge { .. }
1164 | Self::VertexStrideTooLarge { .. }
1165 | Self::UnalignedVertexStride { .. }
1166 | Self::InvalidVertexAttributeOffset { .. }
1167 | Self::ShaderLocationClash(_)
1168 | Self::StripIndexFormatForNonStripTopology { .. }
1169 | Self::ConservativeRasterizationNonFillPolygonMode
1170 | Self::UnalignedShader { .. }
1171 | Self::DualSourceBlendingWithMultipleColorTargets { .. }
1172 | Self::NoTargetSpecified
1173 | Self::VertexAttributeStrideTooLarge { .. } => ErrorType::Validation,
1174 }
1175 }
1176}
1177
1178bitflags::bitflags! {
1179 #[repr(transparent)]
1180 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1181 pub struct PipelineFlags: u32 {
1182 const BLEND_CONSTANT = 1 << 0;
1183 const STENCIL_REFERENCE = 1 << 1;
1184 const WRITES_DEPTH = 1 << 2;
1185 const WRITES_STENCIL = 1 << 3;
1186 }
1187}
1188
1189#[derive(Clone, Copy, Debug)]
1191pub struct VertexStep {
1192 pub stride: wgt::BufferAddress,
1194
1195 pub last_stride: wgt::BufferAddress,
1197
1198 pub mode: wgt::VertexStepMode,
1200}
1201
1202impl Default for VertexStep {
1203 fn default() -> Self {
1204 Self {
1205 stride: 0,
1206 last_stride: 0,
1207 mode: wgt::VertexStepMode::Vertex,
1208 }
1209 }
1210}
1211
1212#[derive(Debug)]
1213pub(crate) struct RenderPipelineState {
1214 pub(crate) raw: ManuallyDrop<Box<dyn hal::DynRenderPipeline>>,
1215 pub(crate) layout: Arc<PipelineLayout>,
1216}
1217
1218#[derive(Debug)]
1219pub struct RenderPipeline {
1220 pub(crate) state: ResourceState<RenderPipelineState>,
1221 pub(crate) device: Arc<Device>,
1222 pub(crate) _shader_modules: ArrayVec<Arc<ShaderModule>, { hal::MAX_CONCURRENT_SHADER_STAGES }>,
1223 pub(crate) pass_context: RenderPassContext,
1224 pub(crate) flags: PipelineFlags,
1225 pub(crate) topology: wgt::PrimitiveTopology,
1226 pub(crate) strip_index_format: Option<wgt::IndexFormat>,
1227 pub(crate) vertex_steps: Vec<Option<VertexStep>>,
1228 pub(crate) late_sized_buffer_groups: ArrayVec<LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }>,
1229 pub(crate) immediate_slots_required: naga::valid::ImmediateSlots,
1230 pub(crate) label: String,
1232 pub(crate) tracking_data: TrackingData,
1233 pub(crate) is_mesh: bool,
1235 pub(crate) has_task_shader: bool,
1236}
1237
1238impl Drop for RenderPipeline {
1239 #[allow(trivial_casts)]
1240 fn drop(&mut self) {
1241 profiling::scope!("RenderPipeline::drop");
1242 api_log!("RenderPipeline::drop {:?}", self as *const _);
1243 resource_log!("Destroy raw {}", self.error_ident());
1244 #[cfg(feature = "trace")]
1245 {
1246 use crate::device::trace;
1247 if let Some(t) = self.device.trace.lock().as_mut() {
1248 t.add(trace::Action::DropRenderPipeline(unsafe {
1249 trace::to_trace(self)
1250 }));
1251 }
1252 }
1253 let ResourceState::Valid(state) = &mut self.state else {
1254 return;
1255 };
1256 let raw = unsafe { ManuallyDrop::take(&mut state.raw) };
1258 unsafe {
1259 self.device.raw().destroy_render_pipeline(raw);
1260 }
1261 }
1262}
1263
1264crate::impl_resource_type!(RenderPipeline);
1265crate::impl_labeled!(RenderPipeline);
1266crate::impl_parent_device!(RenderPipeline);
1267crate::impl_storage_item!(RenderPipeline);
1268crate::impl_trackable!(RenderPipeline);
1269
1270impl RenderPipeline {
1271 pub(crate) fn raw(&self) -> Result<&dyn hal::DynRenderPipeline, InvalidResourceError> {
1272 let ResourceState::Valid(state) = &self.state else {
1273 return Err(InvalidResourceError(self.error_ident()));
1274 };
1275 Ok(state.raw.as_ref())
1276 }
1277
1278 pub(crate) fn layout(&self) -> Result<&Arc<PipelineLayout>, InvalidResourceError> {
1279 let ResourceState::Valid(state) = &self.state else {
1280 return Err(InvalidResourceError(self.error_ident()));
1281 };
1282 Ok(&state.layout)
1283 }
1284
1285 pub(crate) fn check_valid(&self) -> Result<(), InvalidResourceError> {
1286 let ResourceState::Valid(_) = &self.state else {
1287 return Err(InvalidResourceError(self.error_ident()));
1288 };
1289 Ok(())
1290 }
1291
1292 pub(crate) fn invalid(device: Arc<Device>, label: String) -> Arc<Self> {
1293 Arc::new(Self {
1294 tracking_data: TrackingData::new(device.tracker_indices.render_pipelines.clone()),
1295 state: ResourceState::Invalid,
1296 device,
1297 _shader_modules: ArrayVec::new(),
1298 pass_context: RenderPassContext {
1299 attachments: AttachmentData {
1300 colors: ArrayVec::new(),
1301 resolves: ArrayVec::new(),
1302 depth_stencil: None,
1303 },
1304 sample_count: 0,
1305 multiview_mask: None,
1306 },
1307 flags: PipelineFlags::empty(),
1308 topology: wgt::PrimitiveTopology::TriangleList,
1309 strip_index_format: None,
1310 vertex_steps: Vec::new(),
1311 late_sized_buffer_groups: ArrayVec::new(),
1312 immediate_slots_required: naga::valid::ImmediateSlots::default(),
1313 label,
1314 is_mesh: false,
1315 has_task_shader: false,
1316 })
1317 }
1318
1319 pub fn get_bind_group_layout_inner(
1320 self: &Arc<Self>,
1321 index: u32,
1322 ) -> Result<Arc<BindGroupLayout>, GetBindGroupLayoutError> {
1323 self.layout()?.get_bind_group_layout(index, self.into())
1324 }
1325
1326 pub fn get_bind_group_layout(self: &Arc<Self>, index: u32) -> Arc<BindGroupLayout> {
1327 let bgl = self
1328 .get_bind_group_layout_inner(index)
1329 .unwrap_or_else(|err| {
1330 self.device
1331 .handle_error_nolabel(err, "RenderPipeline::get_bind_group_layout");
1332 BindGroupLayout::invalid(&self.device, String::new())
1333 });
1334 #[cfg(feature = "trace")]
1335 if let Some(ref mut trace) = *self.device.trace.lock() {
1336 use crate::device::trace;
1337 use trace::IntoTrace;
1338 trace.add(trace::Action::GetRenderPipelineBindGroupLayout {
1339 id: bgl.to_trace(),
1340 pipeline: self.to_trace(),
1341 index,
1342 });
1343 };
1344 bgl
1345 }
1346}
1347
1348#[cfg(test)]
1349mod tests {
1350 use super::*;
1351
1352 fn passthrough_interface(entry_point_names: &[&str]) -> PassthroughInterface {
1353 PassthroughInterface {
1354 entry_point_names: entry_point_names
1355 .iter()
1356 .map(|name| (*name).to_owned())
1357 .collect(),
1358 }
1359 }
1360
1361 #[test]
1362 fn select_implicit_passthrough_entry_point() {
1363 let empty = passthrough_interface(&[]);
1364 assert!(matches!(
1365 finalize_passthrough_entry_point_name(&empty, None),
1366 Err(validation::StageError::NoEntryPoints)
1367 ));
1368
1369 let single = passthrough_interface(&["main"]);
1370 assert_eq!(
1371 finalize_passthrough_entry_point_name(&single, None).unwrap(),
1372 "main"
1373 );
1374
1375 let multiple = passthrough_interface(&["vertex", "fragment"]);
1376 assert!(matches!(
1377 finalize_passthrough_entry_point_name(&multiple, None),
1378 Err(validation::StageError::AmbiguousEntryPoint)
1379 ));
1380 }
1381
1382 #[test]
1383 fn select_explicit_passthrough_entry_point() {
1384 let interface = passthrough_interface(&["main"]);
1385 assert_eq!(
1386 finalize_passthrough_entry_point_name(&interface, Some("main")).unwrap(),
1387 "main"
1388 );
1389 assert!(matches!(
1390 finalize_passthrough_entry_point_name(&interface, Some("missing")),
1391 Err(validation::StageError::NoEntryPointWithName(name)) if name == "missing"
1392 ));
1393 }
1394}