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 kind: MaterialBindingKind,
158}
159
160pub struct MaterialDescriptor<'a> {
161 pub label: Option<&'a str>,
162 pub shader_source: &'a str,
163 pub vertex_entry: Option<&'a str>,
164 pub fragment_entry: Option<&'a str>,
165 pub vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
166 pub entries: Vec<MaterialBindingEntry>,
167 pub cull_mode: Option<wgpu::Face>,
168 pub depth: Option<wgpu::DepthStencilState>,
169 pub targets: Vec<wgpu::ColorTargetState>,
170 pub polygon_mode: wgpu::PolygonMode,
171 pub extra_layouts: Vec<wgpu::BindGroupLayout>,
172}
173
174pub const DEFAULT_TARGET: [wgpu::ColorTargetState; 1] = [wgpu::ColorTargetState {
175 format: wgpu::TextureFormat::Rgba8Unorm,
176 blend: None,
177 write_mask: wgpu::ColorWrites::ALL,
178}];
179
180impl<'a> Default for MaterialDescriptor<'a> {
181 fn default() -> Self {
182 Self {
183 label: None,
184 shader_source: "",
185 vertex_entry: Some("vs_main"),
186 fragment_entry: Some("fs_main"),
187 vertex_layouts: Vec::new(),
188 entries: Vec::new(),
189 cull_mode: Some(wgpu::Face::Back),
190 depth: None,
191 targets: Vec::new(),
192 extra_layouts: Vec::new(),
193 polygon_mode: wgpu::PolygonMode::Fill,
194 }
195 }
196}
197
198pub fn build_bind_group_layout(
199 device: &wgpu::Device,
200 label: Option<&str>,
201 entries: &[MaterialBindingEntry],
202) -> wgpu::BindGroupLayout {
203 let layout_entries: Vec<_> = entries
204 .iter()
205 .enumerate()
206 .map(|(i, e)| e.kind.layout_entry(i as u32))
207 .collect();
208
209 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
210 label,
211 entries: &layout_entries,
212 })
213}
214
215pub fn build_material(
216 device: &wgpu::Device,
217 desc: &MaterialDescriptor,
218) -> (wgpu::RenderPipeline, wgpu::BindGroupLayout) {
219 let layout = build_bind_group_layout(device, desc.label, &desc.entries);
220
221 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
222 label: desc.label,
223 source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
224 });
225
226 let mut bind_group_layouts: Vec<&wgpu::BindGroupLayout> = desc.extra_layouts.iter().collect();
227 bind_group_layouts.push(&layout);
228 let bind_group_layouts: Vec<Option<&wgpu::BindGroupLayout>> =
229 bind_group_layouts.into_iter().map(Some).collect();
230
231 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
232 label: desc.label,
233 bind_group_layouts: &bind_group_layouts,
234 immediate_size: 0,
235 });
236
237 let targets: Vec<Option<wgpu::ColorTargetState>> =
238 desc.targets.iter().cloned().map(Some).collect();
239
240 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
241 label: desc.label,
242 layout: Some(&pipeline_layout),
243 vertex: wgpu::VertexState {
244 module: &module,
245 entry_point: desc.vertex_entry,
246 compilation_options: Default::default(),
247 buffers: &desc.vertex_layouts,
248 },
249 primitive: wgpu::PrimitiveState {
250 topology: wgpu::PrimitiveTopology::TriangleList,
251 strip_index_format: None,
252 front_face: wgpu::FrontFace::Ccw,
253 cull_mode: desc.cull_mode,
254 unclipped_depth: false,
255 polygon_mode: desc.polygon_mode,
256 conservative: false,
257 },
258 depth_stencil: desc.depth.clone(),
259 multisample: wgpu::MultisampleState::default(),
260 fragment: Some(wgpu::FragmentState {
261 module: &module,
262 entry_point: desc.fragment_entry,
263 compilation_options: Default::default(),
264 targets: &targets,
265 }),
266 multiview_mask: None,
267 cache: None,
268 });
269
270 (pipeline, layout)
271}
272
273pub struct GPUMaterial {
274 pub pipeline: wgpu::RenderPipeline,
275 pub layout: wgpu::BindGroupLayout,
276 pub entries: Vec<MaterialBindingEntry>,
277}
278
279impl Asset<WGPUBackend> for GPUMaterial {
280 type Source = MaterialDescriptor<'static>;
281 type Deps<'a> = ();
282
283 fn upload<'a>(source: &MaterialDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
284 let (pipeline, layout) = build_material(&backend.device, source);
285
286 Some(Self {
287 pipeline,
288 layout,
289 entries: source.entries.to_vec(),
290 })
291 }
292}
293
294#[derive(Default)]
295pub struct MaterialPlugin;
296impl MaterialPlugin {
297 pub fn new() -> Self {
298 Self
299 }
300}
301
302impl Plugin for MaterialPlugin {
303 fn build(&self, app: &mut App) {
304 app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUMaterial>::new());
305 }
306}