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 { visibility: wgpu::ShaderStages },
18 StorageBufferReadOnly { visibility: wgpu::ShaderStages },
19 StorageBufferReadWrite { visibility: wgpu::ShaderStages },
20}
21
22impl MaterialBindingKind {
23 pub fn texture_2d() -> Self {
24 Self::Texture {
25 sample_type: wgpu::TextureSampleType::Float { filterable: true },
26 view_dimension: wgpu::TextureViewDimension::D2,
27 multisampled: false,
28 }
29 }
30
31 pub fn texture_2d_array() -> Self {
32 Self::Texture {
33 sample_type: wgpu::TextureSampleType::Float { filterable: true },
34 view_dimension: wgpu::TextureViewDimension::D2Array,
35 multisampled: false,
36 }
37 }
38
39 pub fn texture_cubemap() -> Self {
40 Self::Texture {
41 sample_type: wgpu::TextureSampleType::Float { filterable: true },
42 view_dimension: wgpu::TextureViewDimension::Cube,
43 multisampled: false,
44 }
45 }
46
47 pub fn uniform_buffer() -> Self {
48 Self::UniformBuffer { visibility: wgpu::ShaderStages::VERTEX_FRAGMENT }
49 }
50
51 pub fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
52 match self {
53 MaterialBindingKind::Texture { sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
54 binding,
55 visibility: wgpu::ShaderStages::FRAGMENT,
56 ty: wgpu::BindingType::Texture {
57 sample_type: *sample_type,
58 view_dimension: *view_dimension,
59 multisampled: *multisampled,
60 },
61 count: None,
62 },
63 MaterialBindingKind::Sampler => wgpu::BindGroupLayoutEntry {
64 binding,
65 visibility: wgpu::ShaderStages::FRAGMENT,
66 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
67 count: None,
68 },
69 MaterialBindingKind::ComparisonSampler => wgpu::BindGroupLayoutEntry {
70 binding,
71 visibility: wgpu::ShaderStages::FRAGMENT,
72 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
73 count: None,
74 },
75 MaterialBindingKind::UniformBuffer { visibility } => wgpu::BindGroupLayoutEntry {
76 binding,
77 visibility: *visibility,
78 ty: wgpu::BindingType::Buffer {
79 ty: wgpu::BufferBindingType::Uniform,
80 has_dynamic_offset: false,
81 min_binding_size: None,
82 },
83 count: None,
84 },
85 MaterialBindingKind::StorageBufferReadOnly { visibility } => wgpu::BindGroupLayoutEntry {
86 binding,
87 visibility: *visibility,
88 ty: wgpu::BindingType::Buffer {
89 ty: wgpu::BufferBindingType::Storage { read_only: true },
90 has_dynamic_offset: false,
91 min_binding_size: None,
92 },
93 count: None,
94 },
95 MaterialBindingKind::StorageBufferReadWrite { visibility } => wgpu::BindGroupLayoutEntry {
96 binding,
97 visibility: *visibility,
98 ty: wgpu::BindingType::Buffer {
99 ty: wgpu::BufferBindingType::Storage { read_only: false },
100 has_dynamic_offset: false,
101 min_binding_size: None,
102 },
103 count: None,
104 },
105 }
106 }
107}
108
109#[derive(Clone)]
110pub struct MaterialBindingEntry {
111 pub name: &'static str,
112 pub kind: MaterialBindingKind,
113}
114
115pub struct MaterialDescriptor<'a> {
116 pub label: Option<&'a str>,
117 pub shader_source: &'a str,
118 pub vertex_entry: Option<&'a str>,
119 pub fragment_entry: Option<&'a str>,
120 pub vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
121 pub entries: Vec<MaterialBindingEntry>,
122 pub cull_mode: Option<wgpu::Face>,
123 pub depth: Option<wgpu::DepthStencilState>,
124 pub targets: Vec<wgpu::ColorTargetState>,
125 pub polygon_mode: wgpu::PolygonMode,
126 pub extra_layouts: Vec<wgpu::BindGroupLayout>,
127}
128
129pub const DEFAULT_TARGET: [wgpu::ColorTargetState; 1] = [wgpu::ColorTargetState {
130 format: wgpu::TextureFormat::Rgba8Unorm,
131 blend: None,
132 write_mask: wgpu::ColorWrites::ALL,
133}];
134
135impl<'a> Default for MaterialDescriptor<'a> {
136 fn default() -> Self {
137 Self {
138 label: None,
139 shader_source: "",
140 vertex_entry: Some("vs_main"),
141 fragment_entry: Some("fs_main"),
142 vertex_layouts: Vec::new(),
143 entries: Vec::new(),
144 cull_mode: Some(wgpu::Face::Back),
145 depth: None,
146 targets: Vec::new(),
147 extra_layouts: Vec::new(),
148 polygon_mode: wgpu::PolygonMode::Fill,
149 }
150 }
151}
152
153pub fn build_bind_group_layout(
154 device: &wgpu::Device,
155 label: Option<&str>,
156 entries: &[MaterialBindingEntry],
157) -> wgpu::BindGroupLayout {
158 let layout_entries: Vec<_> = entries
159 .iter()
160 .enumerate()
161 .map(|(i, e)| e.kind.layout_entry(i as u32))
162 .collect();
163
164 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
165 label,
166 entries: &layout_entries,
167 })
168}
169
170pub fn build_material(
171 device: &wgpu::Device,
172 desc: &MaterialDescriptor,
173) -> (wgpu::RenderPipeline, wgpu::BindGroupLayout) {
174 let layout = build_bind_group_layout(device, desc.label, &desc.entries);
175
176 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
177 label: desc.label,
178 source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
179 });
180
181 let mut bind_group_layouts: Vec<&wgpu::BindGroupLayout> = desc.extra_layouts.iter().collect();
182 bind_group_layouts.push(&layout);
183 let bind_group_layouts: Vec<Option<&wgpu::BindGroupLayout>> =
184 bind_group_layouts.into_iter().map(Some).collect();
185
186 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
187 label: desc.label,
188 bind_group_layouts: &bind_group_layouts,
189 immediate_size: 0,
190 });
191
192 let targets: Vec<Option<wgpu::ColorTargetState>> =
193 desc.targets.iter().cloned().map(Some).collect();
194
195 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
196 label: desc.label,
197 layout: Some(&pipeline_layout),
198 vertex: wgpu::VertexState {
199 module: &module,
200 entry_point: desc.vertex_entry,
201 compilation_options: Default::default(),
202 buffers: &desc.vertex_layouts,
203 },
204 primitive: wgpu::PrimitiveState {
205 topology: wgpu::PrimitiveTopology::TriangleList,
206 strip_index_format: None,
207 front_face: wgpu::FrontFace::Ccw,
208 cull_mode: desc.cull_mode,
209 unclipped_depth: false,
210 polygon_mode: desc.polygon_mode,
211 conservative: false,
212 },
213 depth_stencil: desc.depth.clone(),
214 multisample: wgpu::MultisampleState::default(),
215 fragment: Some(wgpu::FragmentState {
216 module: &module,
217 entry_point: desc.fragment_entry,
218 compilation_options: Default::default(),
219 targets: &targets,
220 }),
221 multiview_mask: None,
222 cache: None,
223 });
224
225 (pipeline, layout)
226}
227
228pub struct GPUMaterial {
229 pub pipeline: wgpu::RenderPipeline,
230 pub layout: wgpu::BindGroupLayout,
231 pub entries: Vec<MaterialBindingEntry>,
232}
233
234impl Asset<WGPUBackend> for GPUMaterial {
235 type Source = MaterialDescriptor<'static>;
236 type Deps<'a> = ();
237
238 fn upload<'a>(source: &MaterialDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
239 let (pipeline, layout) = build_material(&backend.device, source);
240
241 Some(Self {
242 pipeline,
243 layout,
244 entries: source.entries.to_vec(),
245 })
246 }
247}
248
249#[derive(Default)]
250pub struct MaterialPlugin;
251impl MaterialPlugin {
252 pub fn new() -> Self {
253 Self
254 }
255}
256
257impl Plugin for MaterialPlugin {
258 fn build(&self, app: &mut App) {
259 app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUMaterial>::new());
260 }
261}