1use wgpu::{
2 BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BlendState, ColorTargetState,
3 ColorWrites, CompareFunction, DepthStencilState, FragmentState, MultisampleState,
4 PipelineLayoutDescriptor, PrimitiveState, PrimitiveTopology, RenderPipeline,
5 RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StencilState, TextureFormat,
6 VertexBufferLayout, VertexState,
7};
8
9use crate::device::{FeatureRequest, GpuContext, MissingGpuCapability};
10use crate::{DepthConvention, DepthMode};
11
12pub struct MaterialSpec<'a> {
13 pub label: &'a str,
14 pub shader: &'a str,
15 pub vertex_entry: &'a str,
16 pub fragment_entry: &'a str,
17 pub vertex_layouts: &'a [VertexBufferLayout<'a>],
18 pub resources: &'a [&'a [BindGroupLayoutEntry]],
20 pub features: FeatureRequest,
21 pub topology: PrimitiveTopology,
22 pub blend: Option<BlendState>,
23 pub depth: DepthMode,
24 pub convention: DepthConvention,
25 pub standard_z_compare: CompareFunction,
26}
27
28pub struct MaterialPipeline {
29 pub pipeline: RenderPipeline,
30 pub bind_group_layouts: Vec<BindGroupLayout>,
31 convention: DepthConvention,
32}
33
34impl MaterialPipeline {
35 pub fn convention(&self) -> DepthConvention {
36 self.convention
37 }
38
39 pub fn group_layout(&self, group: usize) -> Option<&BindGroupLayout> {
40 self.bind_group_layouts.get(group)
41 }
42}
43
44impl MaterialSpec<'_> {
45 pub fn build(
47 &self,
48 gpu: &GpuContext,
49 target: TextureFormat,
50 ) -> Result<MaterialPipeline, MissingGpuCapability> {
51 let device = &gpu.device;
52 self.features.resolve(device.features(), &device.limits())?;
53
54 let module = device.create_shader_module(ShaderModuleDescriptor {
55 label: Some(self.label),
56 source: ShaderSource::Wgsl(self.shader.into()),
57 });
58 let bind_group_layouts: Vec<BindGroupLayout> = self
59 .resources
60 .iter()
61 .map(|entries| {
62 device.create_bind_group_layout(&BindGroupLayoutDescriptor {
63 label: Some(self.label),
64 entries,
65 })
66 })
67 .collect();
68 let borrowed: Vec<&BindGroupLayout> = bind_group_layouts.iter().collect();
69 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
70 label: Some(self.label),
71 bind_group_layouts: &borrowed,
72 push_constant_ranges: &[],
73 });
74 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
75 label: Some(self.label),
76 layout: Some(&pipeline_layout),
77 vertex: VertexState {
78 module: &module,
79 entry_point: Some(self.vertex_entry),
80 buffers: self.vertex_layouts,
81 compilation_options: Default::default(),
82 },
83 fragment: Some(FragmentState {
84 module: &module,
85 entry_point: Some(self.fragment_entry),
86 targets: &[Some(ColorTargetState {
87 format: target,
88 blend: self.blend,
89 write_mask: ColorWrites::ALL,
90 })],
91 compilation_options: Default::default(),
92 }),
93 primitive: PrimitiveState {
94 topology: self.topology,
95 ..Default::default()
96 },
97 depth_stencil: self.depth.format().map(|format| DepthStencilState {
98 format,
99 depth_write_enabled: self.depth.writes(),
100 depth_compare: self.convention.compare(self.standard_z_compare),
101 stencil: StencilState::default(),
102 bias: wgpu::DepthBiasState::default(),
103 }),
104 multisample: MultisampleState {
105 count: 1,
106 ..Default::default()
107 },
108 multiview: None,
109 cache: None,
110 });
111 Ok(MaterialPipeline {
112 pipeline,
113 bind_group_layouts,
114 convention: self.convention,
115 })
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use wgpu::{BindingType, BufferBindingType, Features, Limits, ShaderStages};
122
123 use super::*;
124 use crate::device::noop_context;
125
126 const UNIFORM_AT_ZERO: [BindGroupLayoutEntry; 1] = [BindGroupLayoutEntry {
127 binding: 0,
128 visibility: ShaderStages::VERTEX_FRAGMENT,
129 ty: BindingType::Buffer {
130 ty: BufferBindingType::Uniform,
131 has_dynamic_offset: false,
132 min_binding_size: None,
133 },
134 count: None,
135 }];
136
137 const PROBE_WGSL: &str = r#"
138@vertex
139fn vs_probe(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> {
140 return vec4<f32>(f32(vid), 0.0, 0.0, 1.0);
141}
142
143@fragment
144fn fs_probe() -> @location(0) vec4<f32> {
145 return vec4<f32>(1.0);
146}
147"#;
148
149 fn spec(features: FeatureRequest) -> MaterialSpec<'static> {
150 MaterialSpec {
151 label: "material probe",
152 shader: PROBE_WGSL,
153 vertex_entry: "vs_probe",
154 fragment_entry: "fs_probe",
155 vertex_layouts: &[],
156 resources: &[&UNIFORM_AT_ZERO],
157 features,
158 topology: PrimitiveTopology::TriangleList,
159 blend: None,
160 depth: DepthMode::Off,
161 convention: DepthConvention::StandardZ,
162 standard_z_compare: CompareFunction::Less,
163 }
164 }
165
166 #[test]
167 fn a_material_the_device_cannot_serve_is_refused_by_the_feature_it_names() {
168 let gpu = noop_context();
169 let wanted = Features::SHADER_F16 - gpu.device.features();
170 assert!(!wanted.is_empty(), "pick a feature this device lacks");
171 let refused = spec(FeatureRequest {
172 required_features: wanted,
173 optional_features: Features::empty(),
174 required_limits: Limits::downlevel_webgl2_defaults(),
175 })
176 .build(&gpu, TextureFormat::Rgba8Unorm);
177 assert_eq!(refused.err(), Some(MissingGpuCapability::Feature(wanted)));
178 }
179}