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