wgpu_core/
pipeline.rs

1#[cfg(feature = "trace")]
2use crate::device::trace;
3use crate::{
4    binding_model::{CreateBindGroupLayoutError, CreatePipelineLayoutError, PipelineLayout},
5    command::ColorAttachmentError,
6    device::{Device, DeviceError, MissingDownlevelFlags, MissingFeatures, RenderPassContext},
7    hal_api::HalApi,
8    id::{PipelineLayoutId, ShaderModuleId},
9    resource::{Resource, ResourceInfo, ResourceType},
10    resource_log, validation, Label,
11};
12use arrayvec::ArrayVec;
13use std::{borrow::Cow, error::Error, fmt, marker::PhantomData, num::NonZeroU32, sync::Arc};
14use thiserror::Error;
15
16/// Information about buffer bindings, which
17/// is validated against the shader (and pipeline)
18/// at draw time as opposed to initialization time.
19#[derive(Debug)]
20pub(crate) struct LateSizedBufferGroup {
21    // The order has to match `BindGroup::late_buffer_binding_sizes`.
22    pub(crate) shader_sizes: Vec<wgt::BufferAddress>,
23}
24
25#[allow(clippy::large_enum_variant)]
26pub enum ShaderModuleSource<'a> {
27    #[cfg(feature = "wgsl")]
28    Wgsl(Cow<'a, str>),
29    #[cfg(feature = "glsl")]
30    Glsl(Cow<'a, str>, naga::front::glsl::Options),
31    #[cfg(feature = "spirv")]
32    SpirV(Cow<'a, [u32]>, naga::front::spv::Options),
33    Naga(Cow<'static, naga::Module>),
34    /// Dummy variant because `Naga` doesn't have a lifetime and without enough active features it
35    /// could be the last one active.
36    #[doc(hidden)]
37    Dummy(PhantomData<&'a ()>),
38}
39
40#[derive(Clone, Debug)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct ShaderModuleDescriptor<'a> {
43    pub label: Label<'a>,
44    #[cfg_attr(feature = "serde", serde(default))]
45    pub shader_bound_checks: wgt::ShaderBoundChecks,
46}
47
48#[derive(Debug)]
49pub struct ShaderModule<A: HalApi> {
50    pub(crate) raw: Option<A::ShaderModule>,
51    pub(crate) device: Arc<Device<A>>,
52    pub(crate) interface: Option<validation::Interface>,
53    pub(crate) info: ResourceInfo<ShaderModule<A>>,
54    pub(crate) label: String,
55}
56
57impl<A: HalApi> Drop for ShaderModule<A> {
58    fn drop(&mut self) {
59        if let Some(raw) = self.raw.take() {
60            resource_log!("Destroy raw ShaderModule {:?}", self.info.label());
61            #[cfg(feature = "trace")]
62            if let Some(t) = self.device.trace.lock().as_mut() {
63                t.add(trace::Action::DestroyShaderModule(self.info.id()));
64            }
65            unsafe {
66                use hal::Device;
67                self.device.raw().destroy_shader_module(raw);
68            }
69        }
70    }
71}
72
73impl<A: HalApi> Resource for ShaderModule<A> {
74    const TYPE: ResourceType = "ShaderModule";
75
76    type Marker = crate::id::markers::ShaderModule;
77
78    fn as_info(&self) -> &ResourceInfo<Self> {
79        &self.info
80    }
81
82    fn as_info_mut(&mut self) -> &mut ResourceInfo<Self> {
83        &mut self.info
84    }
85
86    fn label(&self) -> String {
87        self.label.clone()
88    }
89}
90
91impl<A: HalApi> ShaderModule<A> {
92    pub(crate) fn raw(&self) -> &A::ShaderModule {
93        self.raw.as_ref().unwrap()
94    }
95
96    pub(crate) fn finalize_entry_point_name(
97        &self,
98        stage_bit: wgt::ShaderStages,
99        entry_point: Option<&str>,
100    ) -> Result<String, validation::StageError> {
101        match &self.interface {
102            Some(interface) => interface.finalize_entry_point_name(stage_bit, entry_point),
103            None => entry_point
104                .map(|ep| ep.to_string())
105                .ok_or(validation::StageError::NoEntryPointFound),
106        }
107    }
108}
109
110#[derive(Clone, Debug)]
111pub struct ShaderError<E> {
112    pub source: String,
113    pub label: Option<String>,
114    pub inner: Box<E>,
115}
116#[cfg(feature = "wgsl")]
117impl fmt::Display for ShaderError<naga::front::wgsl::ParseError> {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        let label = self.label.as_deref().unwrap_or_default();
120        let string = self.inner.emit_to_string(&self.source);
121        write!(f, "\nShader '{label}' parsing {string}")
122    }
123}
124#[cfg(feature = "glsl")]
125impl fmt::Display for ShaderError<naga::front::glsl::ParseError> {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        let label = self.label.as_deref().unwrap_or_default();
128        let string = self.inner.emit_to_string(&self.source);
129        write!(f, "\nShader '{label}' parsing {string}")
130    }
131}
132#[cfg(feature = "spirv")]
133impl fmt::Display for ShaderError<naga::front::spv::Error> {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        let label = self.label.as_deref().unwrap_or_default();
136        let string = self.inner.emit_to_string(&self.source);
137        write!(f, "\nShader '{label}' parsing {string}")
138    }
139}
140impl fmt::Display for ShaderError<naga::WithSpan<naga::valid::ValidationError>> {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        use codespan_reporting::{
143            diagnostic::{Diagnostic, Label},
144            files::SimpleFile,
145            term,
146        };
147
148        let label = self.label.as_deref().unwrap_or_default();
149        let files = SimpleFile::new(label, &self.source);
150        let config = term::Config::default();
151        let mut writer = term::termcolor::NoColor::new(Vec::new());
152
153        let diagnostic = Diagnostic::error().with_labels(
154            self.inner
155                .spans()
156                .map(|&(span, ref desc)| {
157                    Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
158                })
159                .collect(),
160        );
161
162        term::emit(&mut writer, &config, &files, &diagnostic).expect("cannot write error");
163
164        write!(
165            f,
166            "\nShader validation {}",
167            String::from_utf8_lossy(&writer.into_inner())
168        )
169    }
170}
171impl<E> Error for ShaderError<E>
172where
173    ShaderError<E>: fmt::Display,
174    E: Error + 'static,
175{
176    fn source(&self) -> Option<&(dyn Error + 'static)> {
177        Some(&self.inner)
178    }
179}
180
181//Note: `Clone` would require `WithSpan: Clone`.
182#[derive(Debug, Error)]
183#[non_exhaustive]
184pub enum CreateShaderModuleError {
185    #[cfg(feature = "wgsl")]
186    #[error(transparent)]
187    Parsing(#[from] ShaderError<naga::front::wgsl::ParseError>),
188    #[cfg(feature = "glsl")]
189    #[error(transparent)]
190    ParsingGlsl(#[from] ShaderError<naga::front::glsl::ParseError>),
191    #[cfg(feature = "spirv")]
192    #[error(transparent)]
193    ParsingSpirV(#[from] ShaderError<naga::front::spv::Error>),
194    #[error("Failed to generate the backend-specific code")]
195    Generation,
196    #[error(transparent)]
197    Device(#[from] DeviceError),
198    #[error(transparent)]
199    Validation(#[from] ShaderError<naga::WithSpan<naga::valid::ValidationError>>),
200    #[error(transparent)]
201    MissingFeatures(#[from] MissingFeatures),
202    #[error(
203        "Shader global {bind:?} uses a group index {group} that exceeds the max_bind_groups limit of {limit}."
204    )]
205    InvalidGroupIndex {
206        bind: naga::ResourceBinding,
207        group: u32,
208        limit: u32,
209    },
210}
211
212impl CreateShaderModuleError {
213    pub fn location(&self, source: &str) -> Option<naga::SourceLocation> {
214        match *self {
215            #[cfg(feature = "wgsl")]
216            CreateShaderModuleError::Parsing(ref err) => err.inner.location(source),
217            CreateShaderModuleError::Validation(ref err) => err.inner.location(source),
218            _ => None,
219        }
220    }
221}
222
223/// Describes a programmable pipeline stage.
224#[derive(Clone, Debug)]
225#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
226pub struct ProgrammableStageDescriptor<'a> {
227    /// The compiled shader module for this stage.
228    pub module: ShaderModuleId,
229    /// The name of the entry point in the compiled shader. The name is selected using the
230    /// following logic:
231    ///
232    /// * If `Some(name)` is specified, there must be a function with this name in the shader.
233    /// * If a single entry point associated with this stage must be in the shader, then proceed as
234    ///   if `Some(…)` was specified with that entry point's name.
235    pub entry_point: Option<Cow<'a, str>>,
236    /// Specifies the values of pipeline-overridable constants in the shader module.
237    ///
238    /// If an `@id` attribute was specified on the declaration,
239    /// the key must be the pipeline constant ID as a decimal ASCII number; if not,
240    /// the key must be the constant's identifier name.
241    ///
242    /// The value may represent any of WGSL's concrete scalar types.
243    pub constants: Cow<'a, naga::back::PipelineConstants>,
244    /// Whether workgroup scoped memory will be initialized with zero values for this stage.
245    ///
246    /// This is required by the WebGPU spec, but may have overhead which can be avoided
247    /// for cross-platform applications
248    pub zero_initialize_workgroup_memory: bool,
249}
250
251/// Number of implicit bind groups derived at pipeline creation.
252pub type ImplicitBindGroupCount = u8;
253
254#[derive(Clone, Debug, Error)]
255#[non_exhaustive]
256pub enum ImplicitLayoutError {
257    #[error("Missing IDs for deriving {0} bind groups")]
258    MissingIds(ImplicitBindGroupCount),
259    #[error("Unable to reflect the shader {0:?} interface")]
260    ReflectionError(wgt::ShaderStages),
261    #[error(transparent)]
262    BindGroup(#[from] CreateBindGroupLayoutError),
263    #[error(transparent)]
264    Pipeline(#[from] CreatePipelineLayoutError),
265}
266
267/// Describes a compute pipeline.
268#[derive(Clone, Debug)]
269#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
270pub struct ComputePipelineDescriptor<'a> {
271    pub label: Label<'a>,
272    /// The layout of bind groups for this pipeline.
273    pub layout: Option<PipelineLayoutId>,
274    /// The compiled compute stage and its entry point.
275    pub stage: ProgrammableStageDescriptor<'a>,
276}
277
278#[derive(Clone, Debug, Error)]
279#[non_exhaustive]
280pub enum CreateComputePipelineError {
281    #[error(transparent)]
282    Device(#[from] DeviceError),
283    #[error("Pipeline layout is invalid")]
284    InvalidLayout,
285    #[error("Unable to derive an implicit layout")]
286    Implicit(#[from] ImplicitLayoutError),
287    #[error("Error matching shader requirements against the pipeline")]
288    Stage(#[from] validation::StageError),
289    #[error("Internal error: {0}")]
290    Internal(String),
291    #[error(transparent)]
292    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
293}
294
295#[derive(Debug)]
296pub struct ComputePipeline<A: HalApi> {
297    pub(crate) raw: Option<A::ComputePipeline>,
298    pub(crate) layout: Arc<PipelineLayout<A>>,
299    pub(crate) device: Arc<Device<A>>,
300    pub(crate) _shader_module: Arc<ShaderModule<A>>,
301    pub(crate) late_sized_buffer_groups: ArrayVec<LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }>,
302    pub(crate) info: ResourceInfo<ComputePipeline<A>>,
303}
304
305impl<A: HalApi> Drop for ComputePipeline<A> {
306    fn drop(&mut self) {
307        if let Some(raw) = self.raw.take() {
308            resource_log!("Destroy raw ComputePipeline {:?}", self.info.label());
309
310            #[cfg(feature = "trace")]
311            if let Some(t) = self.device.trace.lock().as_mut() {
312                t.add(trace::Action::DestroyComputePipeline(self.info.id()));
313            }
314
315            unsafe {
316                use hal::Device;
317                self.device.raw().destroy_compute_pipeline(raw);
318            }
319        }
320    }
321}
322
323impl<A: HalApi> Resource for ComputePipeline<A> {
324    const TYPE: ResourceType = "ComputePipeline";
325
326    type Marker = crate::id::markers::ComputePipeline;
327
328    fn as_info(&self) -> &ResourceInfo<Self> {
329        &self.info
330    }
331
332    fn as_info_mut(&mut self) -> &mut ResourceInfo<Self> {
333        &mut self.info
334    }
335}
336
337impl<A: HalApi> ComputePipeline<A> {
338    pub(crate) fn raw(&self) -> &A::ComputePipeline {
339        self.raw.as_ref().unwrap()
340    }
341}
342
343/// Describes how the vertex buffer is interpreted.
344#[derive(Clone, Debug)]
345#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
346#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
347pub struct VertexBufferLayout<'a> {
348    /// The stride, in bytes, between elements of this buffer.
349    pub array_stride: wgt::BufferAddress,
350    /// How often this vertex buffer is "stepped" forward.
351    pub step_mode: wgt::VertexStepMode,
352    /// The list of attributes which comprise a single vertex.
353    pub attributes: Cow<'a, [wgt::VertexAttribute]>,
354}
355
356/// Describes the vertex process in a render pipeline.
357#[derive(Clone, Debug)]
358#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
359pub struct VertexState<'a> {
360    /// The compiled vertex stage and its entry point.
361    pub stage: ProgrammableStageDescriptor<'a>,
362    /// The format of any vertex buffers used with this pipeline.
363    pub buffers: Cow<'a, [VertexBufferLayout<'a>]>,
364}
365
366/// Describes fragment processing in a render pipeline.
367#[derive(Clone, Debug)]
368#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
369pub struct FragmentState<'a> {
370    /// The compiled fragment stage and its entry point.
371    pub stage: ProgrammableStageDescriptor<'a>,
372    /// The effect of draw calls on the color aspect of the output target.
373    pub targets: Cow<'a, [Option<wgt::ColorTargetState>]>,
374}
375
376/// Describes a render (graphics) pipeline.
377#[derive(Clone, Debug)]
378#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
379pub struct RenderPipelineDescriptor<'a> {
380    pub label: Label<'a>,
381    /// The layout of bind groups for this pipeline.
382    pub layout: Option<PipelineLayoutId>,
383    /// The vertex processing state for this pipeline.
384    pub vertex: VertexState<'a>,
385    /// The properties of the pipeline at the primitive assembly and rasterization level.
386    #[cfg_attr(feature = "serde", serde(default))]
387    pub primitive: wgt::PrimitiveState,
388    /// The effect of draw calls on the depth and stencil aspects of the output target, if any.
389    #[cfg_attr(feature = "serde", serde(default))]
390    pub depth_stencil: Option<wgt::DepthStencilState>,
391    /// The multi-sampling properties of the pipeline.
392    #[cfg_attr(feature = "serde", serde(default))]
393    pub multisample: wgt::MultisampleState,
394    /// The fragment processing state for this pipeline.
395    pub fragment: Option<FragmentState<'a>>,
396    /// If the pipeline will be used with a multiview render pass, this indicates how many array
397    /// layers the attachments will have.
398    pub multiview: Option<NonZeroU32>,
399}
400
401#[derive(Clone, Debug, Error)]
402#[non_exhaustive]
403pub enum ColorStateError {
404    #[error("Format {0:?} is not renderable")]
405    FormatNotRenderable(wgt::TextureFormat),
406    #[error("Format {0:?} is not blendable")]
407    FormatNotBlendable(wgt::TextureFormat),
408    #[error("Format {0:?} does not have a color aspect")]
409    FormatNotColor(wgt::TextureFormat),
410    #[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:?}.")]
411    InvalidSampleCount(u32, wgt::TextureFormat, Vec<u32>, Vec<u32>),
412    #[error("Output format {pipeline} is incompatible with the shader {shader}")]
413    IncompatibleFormat {
414        pipeline: validation::NumericType,
415        shader: validation::NumericType,
416    },
417    #[error("Blend factors for {0:?} must be `One`")]
418    InvalidMinMaxBlendFactors(wgt::BlendComponent),
419    #[error("Invalid write mask {0:?}")]
420    InvalidWriteMask(wgt::ColorWrites),
421}
422
423#[derive(Clone, Debug, Error)]
424#[non_exhaustive]
425pub enum DepthStencilStateError {
426    #[error("Format {0:?} is not renderable")]
427    FormatNotRenderable(wgt::TextureFormat),
428    #[error("Format {0:?} does not have a depth aspect, but depth test/write is enabled")]
429    FormatNotDepth(wgt::TextureFormat),
430    #[error("Format {0:?} does not have a stencil aspect, but stencil test/write is enabled")]
431    FormatNotStencil(wgt::TextureFormat),
432    #[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:?}.")]
433    InvalidSampleCount(u32, wgt::TextureFormat, Vec<u32>, Vec<u32>),
434}
435
436#[derive(Clone, Debug, Error)]
437#[non_exhaustive]
438pub enum CreateRenderPipelineError {
439    #[error(transparent)]
440    ColorAttachment(#[from] ColorAttachmentError),
441    #[error(transparent)]
442    Device(#[from] DeviceError),
443    #[error("Pipeline layout is invalid")]
444    InvalidLayout,
445    #[error("Unable to derive an implicit layout")]
446    Implicit(#[from] ImplicitLayoutError),
447    #[error("Color state [{0}] is invalid")]
448    ColorState(u8, #[source] ColorStateError),
449    #[error("Depth/stencil state is invalid")]
450    DepthStencilState(#[from] DepthStencilStateError),
451    #[error("Invalid sample count {0}")]
452    InvalidSampleCount(u32),
453    #[error("The number of vertex buffers {given} exceeds the limit {limit}")]
454    TooManyVertexBuffers { given: u32, limit: u32 },
455    #[error("The total number of vertex attributes {given} exceeds the limit {limit}")]
456    TooManyVertexAttributes { given: u32, limit: u32 },
457    #[error("Vertex buffer {index} stride {given} exceeds the limit {limit}")]
458    VertexStrideTooLarge { index: u32, given: u32, limit: u32 },
459    #[error("Vertex buffer {index} stride {stride} does not respect `VERTEX_STRIDE_ALIGNMENT`")]
460    UnalignedVertexStride {
461        index: u32,
462        stride: wgt::BufferAddress,
463    },
464    #[error("Vertex attribute at location {location} has invalid offset {offset}")]
465    InvalidVertexAttributeOffset {
466        location: wgt::ShaderLocation,
467        offset: wgt::BufferAddress,
468    },
469    #[error("Two or more vertex attributes were assigned to the same location in the shader: {0}")]
470    ShaderLocationClash(u32),
471    #[error("Strip index format was not set to None but to {strip_index_format:?} while using the non-strip topology {topology:?}")]
472    StripIndexFormatForNonStripTopology {
473        strip_index_format: Option<wgt::IndexFormat>,
474        topology: wgt::PrimitiveTopology,
475    },
476    #[error("Conservative Rasterization is only supported for wgt::PolygonMode::Fill")]
477    ConservativeRasterizationNonFillPolygonMode,
478    #[error(transparent)]
479    MissingFeatures(#[from] MissingFeatures),
480    #[error(transparent)]
481    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
482    #[error("Error matching {stage:?} shader requirements against the pipeline")]
483    Stage {
484        stage: wgt::ShaderStages,
485        #[source]
486        error: validation::StageError,
487    },
488    #[error("Internal error in {stage:?} shader: {error}")]
489    Internal {
490        stage: wgt::ShaderStages,
491        error: String,
492    },
493    #[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.")]
494    UnalignedShader { group: u32, binding: u32, size: u64 },
495    #[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.")]
496    BlendFactorOnUnsupportedTarget {
497        factor: wgt::BlendFactor,
498        target: u32,
499    },
500    #[error("Pipeline expects the shader entry point to make use of dual-source blending.")]
501    PipelineExpectsShaderToUseDualSourceBlending,
502    #[error("Shader entry point expects the pipeline to make use of dual-source blending.")]
503    ShaderExpectsPipelineToUseDualSourceBlending,
504}
505
506bitflags::bitflags! {
507    #[repr(transparent)]
508    #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
509    pub struct PipelineFlags: u32 {
510        const BLEND_CONSTANT = 1 << 0;
511        const STENCIL_REFERENCE = 1 << 1;
512        const WRITES_DEPTH = 1 << 2;
513        const WRITES_STENCIL = 1 << 3;
514    }
515}
516
517/// How a render pipeline will retrieve attributes from a particular vertex buffer.
518#[derive(Clone, Copy, Debug)]
519pub struct VertexStep {
520    /// The byte stride in the buffer between one attribute value and the next.
521    pub stride: wgt::BufferAddress,
522
523    /// The byte size required to fit the last vertex in the stream.
524    pub last_stride: wgt::BufferAddress,
525
526    /// Whether the buffer is indexed by vertex number or instance number.
527    pub mode: wgt::VertexStepMode,
528}
529
530impl Default for VertexStep {
531    fn default() -> Self {
532        Self {
533            stride: 0,
534            last_stride: 0,
535            mode: wgt::VertexStepMode::Vertex,
536        }
537    }
538}
539
540#[derive(Debug)]
541pub struct RenderPipeline<A: HalApi> {
542    pub(crate) raw: Option<A::RenderPipeline>,
543    pub(crate) device: Arc<Device<A>>,
544    pub(crate) layout: Arc<PipelineLayout<A>>,
545    pub(crate) _shader_modules:
546        ArrayVec<Arc<ShaderModule<A>>, { hal::MAX_CONCURRENT_SHADER_STAGES }>,
547    pub(crate) pass_context: RenderPassContext,
548    pub(crate) flags: PipelineFlags,
549    pub(crate) strip_index_format: Option<wgt::IndexFormat>,
550    pub(crate) vertex_steps: Vec<VertexStep>,
551    pub(crate) late_sized_buffer_groups: ArrayVec<LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }>,
552    pub(crate) info: ResourceInfo<RenderPipeline<A>>,
553}
554
555impl<A: HalApi> Drop for RenderPipeline<A> {
556    fn drop(&mut self) {
557        if let Some(raw) = self.raw.take() {
558            resource_log!("Destroy raw RenderPipeline {:?}", self.info.label());
559
560            #[cfg(feature = "trace")]
561            if let Some(t) = self.device.trace.lock().as_mut() {
562                t.add(trace::Action::DestroyRenderPipeline(self.info.id()));
563            }
564
565            unsafe {
566                use hal::Device;
567                self.device.raw().destroy_render_pipeline(raw);
568            }
569        }
570    }
571}
572
573impl<A: HalApi> Resource for RenderPipeline<A> {
574    const TYPE: ResourceType = "RenderPipeline";
575
576    type Marker = crate::id::markers::RenderPipeline;
577
578    fn as_info(&self) -> &ResourceInfo<Self> {
579        &self.info
580    }
581
582    fn as_info_mut(&mut self) -> &mut ResourceInfo<Self> {
583        &mut self.info
584    }
585}
586
587impl<A: HalApi> RenderPipeline<A> {
588    pub(crate) fn raw(&self) -> &A::RenderPipeline {
589        self.raw.as_ref().unwrap()
590    }
591}