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#[derive(Debug, Default)]
38pub(crate) struct LateSizedBufferGroup {
39 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 #[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 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 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#[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#[derive(Clone, Debug)]
248#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
249pub struct ProgrammableStageDescriptor<'a, SM = Arc<ShaderModule>> {
251 pub module: SM,
253
254 pub entry_point: Option<Cow<'a, str>>,
262
263 pub constants: naga::back::PipelineConstants,
272
273 pub zero_initialize_workgroup_memory: bool,
280}
281
282pub 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#[derive(Clone, Debug)]
311#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312pub struct ComputePipelineDescriptor<
314 'a,
315 PLL = Arc<PipelineLayout>,
316 SM = Arc<ShaderModule>,
317 PLC = Arc<PipelineCache>,
318> {
319 pub label: Label<'a>,
320 pub layout: Option<PLL>,
322 pub stage: ProgrammableStageDescriptor<'a, SM>,
324 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 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 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 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#[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 pub array_stride: wgt::BufferAddress,
583 pub step_mode: wgt::VertexStepMode,
585 pub attributes: Cow<'a, [wgt::VertexAttribute]>,
587}
588
589impl 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#[derive(Clone, Debug)]
602#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
603pub struct VertexState<'a, SM = Arc<ShaderModule>> {
605 pub stage: ProgrammableStageDescriptor<'a, SM>,
607 pub buffers: Cow<'a, [Option<VertexBufferLayout<'a>>]>,
609}
610
611#[derive(Clone, Debug)]
613#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
614pub struct FragmentState<'a, SM = Arc<ShaderModule>> {
616 pub stage: ProgrammableStageDescriptor<'a, SM>,
618 pub targets: Cow<'a, [Option<wgt::ColorTargetState>]>,
620}
621
622#[derive(Clone, Debug)]
624#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
625pub struct TaskState<'a, SM = Arc<ShaderModule>> {
626 pub stage: ProgrammableStageDescriptor<'a, SM>,
628}
629
630#[derive(Clone, Debug)]
632#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
633pub struct MeshState<'a, SM = Arc<ShaderModule>> {
634 pub stage: ProgrammableStageDescriptor<'a, SM>,
636}
637
638#[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#[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 pub layout: Option<PLL>,
665 pub vertex: VertexState<'a, SM>,
667 #[cfg_attr(feature = "serde", serde(default))]
669 pub primitive: wgt::PrimitiveState,
670 #[cfg_attr(feature = "serde", serde(default))]
672 pub depth_stencil: Option<wgt::DepthStencilState>,
673 #[cfg_attr(feature = "serde", serde(default))]
675 pub multisample: wgt::MultisampleState,
676 pub fragment: Option<FragmentState<'a, SM>>,
678 pub multiview_mask: Option<NonZeroU32>,
681 pub cache: Option<PLC>,
683}
684#[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 pub layout: Option<PLL>,
696 pub task: Option<TaskState<'a, SM>>,
698 pub mesh: MeshState<'a, SM>,
700 #[cfg_attr(feature = "serde", serde(default))]
702 pub primitive: wgt::PrimitiveState,
703 #[cfg_attr(feature = "serde", serde(default))]
705 pub depth_stencil: Option<wgt::DepthStencilState>,
706 #[cfg_attr(feature = "serde", serde(default))]
708 pub multisample: wgt::MultisampleState,
709 pub fragment: Option<FragmentState<'a, SM>>,
711 pub multiview: Option<NonZeroU32>,
714 pub cache: Option<PLC>,
716}
717
718#[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 pub layout: Option<PLL>,
737 pub vertex: RenderPipelineVertexProcessor<'a, SM>,
739 #[cfg_attr(feature = "serde", serde(default))]
741 pub primitive: wgt::PrimitiveState,
742 #[cfg_attr(feature = "serde", serde(default))]
744 pub depth_stencil: Option<wgt::DepthStencilState>,
745 #[cfg_attr(feature = "serde", serde(default))]
747 pub multisample: wgt::MultisampleState,
748 pub fragment: Option<FragmentState<'a, SM>>,
750 pub multiview_mask: Option<NonZeroU32>,
753 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
791pub 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#[derive(Clone, Copy, Debug)]
999pub struct VertexStep {
1000 pub stride: wgt::BufferAddress,
1002
1003 pub last_stride: wgt::BufferAddress,
1005
1006 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 pub(crate) label: String,
1040 pub(crate) tracking_data: TrackingData,
1041 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 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}