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