1use crate::{
2 app::App,
3 assets::{plugin::AssetPlugin, upload::Asset},
4 ecs::plugin::Plugin,
5 wgpu::backend::WGPUBackend,
6};
7
8#[derive(Copy, Clone, PartialEq, Eq, Hash)]
9pub enum MaterialBindingKind {
10 Texture {
11 sample_type: wgpu::TextureSampleType,
12 view_dimension: wgpu::TextureViewDimension,
13 multisampled: bool,
14 },
15 Sampler,
16 ComparisonSampler,
17 UniformBuffer {
18 visibility: wgpu::ShaderStages,
19 has_dynamic_offset: bool,
20 min_binding_size: Option<wgpu::BufferSize>,
21 },
22 StorageBufferReadOnly {
23 visibility: wgpu::ShaderStages,
24 has_dynamic_offset: bool,
25 min_binding_size: Option<wgpu::BufferSize>,
26 },
27 StorageBufferReadWrite {
28 visibility: wgpu::ShaderStages,
29 has_dynamic_offset: bool,
30 min_binding_size: Option<wgpu::BufferSize>,
31 },
32}
33
34impl MaterialBindingKind {
35 pub fn texture_2d() -> Self {
36 Self::Texture {
37 sample_type: wgpu::TextureSampleType::Float { filterable: true },
38 view_dimension: wgpu::TextureViewDimension::D2,
39 multisampled: false,
40 }
41 }
42
43 pub fn texture_2d_array() -> Self {
44 Self::Texture {
45 sample_type: wgpu::TextureSampleType::Float { filterable: true },
46 view_dimension: wgpu::TextureViewDimension::D2Array,
47 multisampled: false,
48 }
49 }
50
51 pub fn texture_cubemap() -> Self {
52 Self::Texture {
53 sample_type: wgpu::TextureSampleType::Float { filterable: true },
54 view_dimension: wgpu::TextureViewDimension::Cube,
55 multisampled: false,
56 }
57 }
58
59 pub fn uniform_buffer() -> Self {
60 Self::UniformBuffer {
61 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
62 has_dynamic_offset: false,
63 min_binding_size: None,
64 }
65 }
66
67 pub fn dynamic_uniform_buffer(element_size: u64) -> Self {
77 Self::UniformBuffer {
78 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
79 has_dynamic_offset: true,
80 min_binding_size: wgpu::BufferSize::new(element_size),
81 }
82 }
83
84 pub fn dynamic_storage_buffer(element_size: u64, read_only: bool) -> Self {
86 let visibility = wgpu::ShaderStages::VERTEX_FRAGMENT;
87 let has_dynamic_offset = true;
88 let min_binding_size = wgpu::BufferSize::new(element_size);
89 if read_only {
90 Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size }
91 } else {
92 Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size }
93 }
94 }
95
96 pub fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
97 match self {
98 MaterialBindingKind::Texture { sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
99 binding,
100 visibility: wgpu::ShaderStages::FRAGMENT,
101 ty: wgpu::BindingType::Texture {
102 sample_type: *sample_type,
103 view_dimension: *view_dimension,
104 multisampled: *multisampled,
105 },
106 count: None,
107 },
108 MaterialBindingKind::Sampler => wgpu::BindGroupLayoutEntry {
109 binding,
110 visibility: wgpu::ShaderStages::FRAGMENT,
111 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
112 count: None,
113 },
114 MaterialBindingKind::ComparisonSampler => wgpu::BindGroupLayoutEntry {
115 binding,
116 visibility: wgpu::ShaderStages::FRAGMENT,
117 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
118 count: None,
119 },
120 MaterialBindingKind::UniformBuffer { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
121 binding,
122 visibility: *visibility,
123 ty: wgpu::BindingType::Buffer {
124 ty: wgpu::BufferBindingType::Uniform,
125 has_dynamic_offset: *has_dynamic_offset,
126 min_binding_size: *min_binding_size,
127 },
128 count: None,
129 },
130 MaterialBindingKind::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
131 binding,
132 visibility: *visibility,
133 ty: wgpu::BindingType::Buffer {
134 ty: wgpu::BufferBindingType::Storage { read_only: true },
135 has_dynamic_offset: *has_dynamic_offset,
136 min_binding_size: *min_binding_size,
137 },
138 count: None,
139 },
140 MaterialBindingKind::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
141 binding,
142 visibility: *visibility,
143 ty: wgpu::BindingType::Buffer {
144 ty: wgpu::BufferBindingType::Storage { read_only: false },
145 has_dynamic_offset: *has_dynamic_offset,
146 min_binding_size: *min_binding_size,
147 },
148 count: None,
149 },
150 }
151 }
152}
153
154#[derive(Clone)]
155pub struct MaterialBindingEntry {
156 pub name: &'static str,
157 pub binding: u32,
160 pub kind: MaterialBindingKind,
161}
162
163pub struct MaterialDescriptor<'a> {
164 pub label: Option<&'a str>,
165 pub shader_source: &'a str,
166 pub vertex_entry: Option<&'a str>,
167 pub fragment_entry: Option<&'a str>,
168 pub vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
169 pub entries: Vec<MaterialBindingEntry>,
170 pub cull_mode: Option<wgpu::Face>,
171 pub depth: Option<wgpu::DepthStencilState>,
172 pub targets: Vec<wgpu::ColorTargetState>,
173 pub polygon_mode: wgpu::PolygonMode,
174 pub own_group: Option<u32>,
177 pub extra_layouts: Vec<super::layout::OwnedGroupLayout>,
182}
183
184pub const DEFAULT_TARGET: [wgpu::ColorTargetState; 1] = [wgpu::ColorTargetState {
185 format: wgpu::TextureFormat::Rgba8Unorm,
186 blend: None,
187 write_mask: wgpu::ColorWrites::ALL,
188}];
189
190impl<'a> Default for MaterialDescriptor<'a> {
191 fn default() -> Self {
192 Self {
193 label: None,
194 shader_source: "",
195 vertex_entry: Some("vs_main"),
196 fragment_entry: Some("fs_main"),
197 vertex_layouts: Vec::new(),
198 entries: Vec::new(),
199 cull_mode: Some(wgpu::Face::Back),
200 depth: None,
201 targets: Vec::new(),
202 own_group: Some(0),
203 extra_layouts: Vec::new(),
204 polygon_mode: wgpu::PolygonMode::Fill,
205 }
206 }
207}
208
209pub fn build_bind_group_layout(
210 device: &wgpu::Device,
211 label: Option<&str>,
212 entries: &[MaterialBindingEntry],
213) -> wgpu::BindGroupLayout {
214 let layout_entries: Vec<_> = entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
215
216 let mut seen = std::collections::HashSet::new();
217 for e in entries {
218 if !seen.insert(e.binding) {
219 panic!(
220 "binding {} assigned more than once building bind group layout{} (entry '{}')",
221 e.binding,
222 label.map(|l| format!(" '{l}'")).unwrap_or_default(),
223 e.name
224 );
225 }
226 }
227
228 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
229 label,
230 entries: &layout_entries,
231 })
232}
233
234pub fn build_material(
235 device: &wgpu::Device,
236 desc: &MaterialDescriptor,
237) -> (wgpu::RenderPipeline, wgpu::BindGroupLayout) {
238 let layout = build_bind_group_layout(device, desc.label, &desc.entries);
239
240 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
241 label: desc.label,
242 source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
243 });
244
245 let mut slots: Vec<super::layout::GroupLayout> = desc
246 .extra_layouts
247 .iter()
248 .map(|g| super::layout::GroupLayout { group: g.group, layout: &g.layout })
249 .collect();
250 if let Some(own_group) = desc.own_group {
251 slots.push(super::layout::GroupLayout { group: own_group, layout: &layout });
252 }
253 let bind_group_layouts = super::layout::assemble_bind_group_layouts(desc.label, slots);
254
255 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
256 label: desc.label,
257 bind_group_layouts: &bind_group_layouts,
258 immediate_size: 0,
259 });
260
261 let targets: Vec<Option<wgpu::ColorTargetState>> =
262 desc.targets.iter().cloned().map(Some).collect();
263
264 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
265 label: desc.label,
266 layout: Some(&pipeline_layout),
267 vertex: wgpu::VertexState {
268 module: &module,
269 entry_point: desc.vertex_entry,
270 compilation_options: Default::default(),
271 buffers: &desc.vertex_layouts,
272 },
273 primitive: wgpu::PrimitiveState {
274 topology: wgpu::PrimitiveTopology::TriangleList,
275 strip_index_format: None,
276 front_face: wgpu::FrontFace::Ccw,
277 cull_mode: desc.cull_mode,
278 unclipped_depth: false,
279 polygon_mode: desc.polygon_mode,
280 conservative: false,
281 },
282 depth_stencil: desc.depth.clone(),
283 multisample: wgpu::MultisampleState::default(),
284 fragment: Some(wgpu::FragmentState {
285 module: &module,
286 entry_point: desc.fragment_entry,
287 compilation_options: Default::default(),
288 targets: &targets,
289 }),
290 multiview_mask: None,
291 cache: None,
292 });
293
294 (pipeline, layout)
295}
296
297pub struct GPUMaterial {
298 pub pipeline: wgpu::RenderPipeline,
299 pub layout: wgpu::BindGroupLayout,
300 pub entries: Vec<MaterialBindingEntry>,
301}
302
303impl Asset<WGPUBackend> for GPUMaterial {
304 type Source = MaterialDescriptor<'static>;
305 type Deps<'a> = ();
306
307 fn upload<'a>(source: &MaterialDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
308 let (pipeline, layout) = build_material(&backend.device, source);
309
310 Some(Self {
311 pipeline,
312 layout,
313 entries: source.entries.to_vec(),
314 })
315 }
316}
317
318#[derive(Default)]
319pub struct MaterialPlugin;
320impl MaterialPlugin {
321 pub fn new() -> Self {
322 Self
323 }
324}
325
326impl Plugin for MaterialPlugin {
327 fn build(&self, app: &mut App) {
328 app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUMaterial>::new());
329 }
330}