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 {
74 Self::UniformBuffer {
75 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
76 has_dynamic_offset: true,
77 min_binding_size: wgpu::BufferSize::new(element_size),
78 }
79 }
80
81 pub fn dynamic_storage_buffer(element_size: u64, read_only: bool) -> Self {
83 let visibility = wgpu::ShaderStages::VERTEX_FRAGMENT;
84 let has_dynamic_offset = true;
85 let min_binding_size = wgpu::BufferSize::new(element_size);
86 if read_only {
87 Self::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size }
88 } else {
89 Self::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size }
90 }
91 }
92
93 pub fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
94 match self {
95 MaterialBindingKind::Texture { sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
96 binding,
97 visibility: wgpu::ShaderStages::FRAGMENT,
98 ty: wgpu::BindingType::Texture {
99 sample_type: *sample_type,
100 view_dimension: *view_dimension,
101 multisampled: *multisampled,
102 },
103 count: None,
104 },
105 MaterialBindingKind::Sampler => wgpu::BindGroupLayoutEntry {
106 binding,
107 visibility: wgpu::ShaderStages::FRAGMENT,
108 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
109 count: None,
110 },
111 MaterialBindingKind::ComparisonSampler => wgpu::BindGroupLayoutEntry {
112 binding,
113 visibility: wgpu::ShaderStages::FRAGMENT,
114 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
115 count: None,
116 },
117 MaterialBindingKind::UniformBuffer { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
118 binding,
119 visibility: *visibility,
120 ty: wgpu::BindingType::Buffer {
121 ty: wgpu::BufferBindingType::Uniform,
122 has_dynamic_offset: *has_dynamic_offset,
123 min_binding_size: *min_binding_size,
124 },
125 count: None,
126 },
127 MaterialBindingKind::StorageBufferReadOnly { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
128 binding,
129 visibility: *visibility,
130 ty: wgpu::BindingType::Buffer {
131 ty: wgpu::BufferBindingType::Storage { read_only: true },
132 has_dynamic_offset: *has_dynamic_offset,
133 min_binding_size: *min_binding_size,
134 },
135 count: None,
136 },
137 MaterialBindingKind::StorageBufferReadWrite { visibility, has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
138 binding,
139 visibility: *visibility,
140 ty: wgpu::BindingType::Buffer {
141 ty: wgpu::BufferBindingType::Storage { read_only: false },
142 has_dynamic_offset: *has_dynamic_offset,
143 min_binding_size: *min_binding_size,
144 },
145 count: None,
146 },
147 }
148 }
149}
150
151#[derive(Clone)]
152pub struct MaterialBindingEntry {
153 pub name: &'static str,
154 pub kind: MaterialBindingKind,
155}
156
157pub struct MaterialDescriptor<'a> {
158 pub label: Option<&'a str>,
159 pub shader_source: &'a str,
160 pub vertex_entry: Option<&'a str>,
161 pub fragment_entry: Option<&'a str>,
162 pub vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
163 pub entries: Vec<MaterialBindingEntry>,
164 pub cull_mode: Option<wgpu::Face>,
165 pub depth: Option<wgpu::DepthStencilState>,
166 pub targets: Vec<wgpu::ColorTargetState>,
167 pub polygon_mode: wgpu::PolygonMode,
168 pub extra_layouts: Vec<wgpu::BindGroupLayout>,
169}
170
171pub const DEFAULT_TARGET: [wgpu::ColorTargetState; 1] = [wgpu::ColorTargetState {
172 format: wgpu::TextureFormat::Rgba8Unorm,
173 blend: None,
174 write_mask: wgpu::ColorWrites::ALL,
175}];
176
177impl<'a> Default for MaterialDescriptor<'a> {
178 fn default() -> Self {
179 Self {
180 label: None,
181 shader_source: "",
182 vertex_entry: Some("vs_main"),
183 fragment_entry: Some("fs_main"),
184 vertex_layouts: Vec::new(),
185 entries: Vec::new(),
186 cull_mode: Some(wgpu::Face::Back),
187 depth: None,
188 targets: Vec::new(),
189 extra_layouts: Vec::new(),
190 polygon_mode: wgpu::PolygonMode::Fill,
191 }
192 }
193}
194
195pub fn build_bind_group_layout(
196 device: &wgpu::Device,
197 label: Option<&str>,
198 entries: &[MaterialBindingEntry],
199) -> wgpu::BindGroupLayout {
200 let layout_entries: Vec<_> = entries
201 .iter()
202 .enumerate()
203 .map(|(i, e)| e.kind.layout_entry(i as u32))
204 .collect();
205
206 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
207 label,
208 entries: &layout_entries,
209 })
210}
211
212pub fn build_material(
213 device: &wgpu::Device,
214 desc: &MaterialDescriptor,
215) -> (wgpu::RenderPipeline, wgpu::BindGroupLayout) {
216 let layout = build_bind_group_layout(device, desc.label, &desc.entries);
217
218 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
219 label: desc.label,
220 source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
221 });
222
223 let mut bind_group_layouts: Vec<&wgpu::BindGroupLayout> = desc.extra_layouts.iter().collect();
224 bind_group_layouts.push(&layout);
225 let bind_group_layouts: Vec<Option<&wgpu::BindGroupLayout>> =
226 bind_group_layouts.into_iter().map(Some).collect();
227
228 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
229 label: desc.label,
230 bind_group_layouts: &bind_group_layouts,
231 immediate_size: 0,
232 });
233
234 let targets: Vec<Option<wgpu::ColorTargetState>> =
235 desc.targets.iter().cloned().map(Some).collect();
236
237 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
238 label: desc.label,
239 layout: Some(&pipeline_layout),
240 vertex: wgpu::VertexState {
241 module: &module,
242 entry_point: desc.vertex_entry,
243 compilation_options: Default::default(),
244 buffers: &desc.vertex_layouts,
245 },
246 primitive: wgpu::PrimitiveState {
247 topology: wgpu::PrimitiveTopology::TriangleList,
248 strip_index_format: None,
249 front_face: wgpu::FrontFace::Ccw,
250 cull_mode: desc.cull_mode,
251 unclipped_depth: false,
252 polygon_mode: desc.polygon_mode,
253 conservative: false,
254 },
255 depth_stencil: desc.depth.clone(),
256 multisample: wgpu::MultisampleState::default(),
257 fragment: Some(wgpu::FragmentState {
258 module: &module,
259 entry_point: desc.fragment_entry,
260 compilation_options: Default::default(),
261 targets: &targets,
262 }),
263 multiview_mask: None,
264 cache: None,
265 });
266
267 (pipeline, layout)
268}
269
270pub struct GPUMaterial {
271 pub pipeline: wgpu::RenderPipeline,
272 pub layout: wgpu::BindGroupLayout,
273 pub entries: Vec<MaterialBindingEntry>,
274}
275
276impl Asset<WGPUBackend> for GPUMaterial {
277 type Source = MaterialDescriptor<'static>;
278 type Deps<'a> = ();
279
280 fn upload<'a>(source: &MaterialDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
281 let (pipeline, layout) = build_material(&backend.device, source);
282
283 Some(Self {
284 pipeline,
285 layout,
286 entries: source.entries.to_vec(),
287 })
288 }
289}
290
291#[derive(Default)]
292pub struct MaterialPlugin;
293impl MaterialPlugin {
294 pub fn new() -> Self {
295 Self
296 }
297}
298
299impl Plugin for MaterialPlugin {
300 fn build(&self, app: &mut App) {
301 app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUMaterial>::new());
302 }
303}