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: &'a [wgpu::VertexBufferLayout<'a>],
89 pub entries: &'a [MaterialBindingEntry],
90 pub cull_mode: Option<wgpu::Face>,
91 pub depth: Option<wgpu::DepthStencilState>,
92 pub targets: &'a [wgpu::ColorTargetState],
93 pub polygon_mode: wgpu::PolygonMode,
94 pub extra_layouts: &'a [&'a wgpu::BindGroupLayout],
95}
96
97const 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: &[],
111 entries: &[],
112 cull_mode: Some(wgpu::Face::Back),
113 depth: None,
114 targets: &[],
115 extra_layouts: &[],
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<_> = desc.extra_layouts.iter().map(|l| Some(*l)).collect();
143 bind_group_layouts.push(Some(&layout));
144
145 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
146 label: desc.label,
147 bind_group_layouts: &bind_group_layouts,
148 immediate_size: 0,
149 });
150
151 let targets: Vec<_> = desc.targets.iter().cloned().map(Some).collect();
152
153 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
154 label: desc.label,
155 layout: Some(&pipeline_layout),
156 vertex: wgpu::VertexState {
157 module: &module,
158 entry_point: desc.vertex_entry,
159 compilation_options: Default::default(),
160 buffers: desc.vertex_layouts,
161 },
162 primitive: wgpu::PrimitiveState {
163 topology: wgpu::PrimitiveTopology::TriangleList,
164 strip_index_format: None,
165 front_face: wgpu::FrontFace::Ccw,
166 cull_mode: desc.cull_mode,
167 unclipped_depth: false,
168 polygon_mode: desc.polygon_mode,
169 conservative: false,
170 },
171 depth_stencil: desc.depth.clone(),
172 multisample: wgpu::MultisampleState::default(),
173 fragment: Some(wgpu::FragmentState {
174 module: &module,
175 entry_point: desc.fragment_entry,
176 compilation_options: Default::default(),
177 targets: &targets,
178 }),
179 multiview_mask: None,
180 cache: None,
181 });
182
183 (pipeline, layout)
184}
185
186pub struct GPUMaterial {
187 pub pipeline: wgpu::RenderPipeline,
188 pub layout: wgpu::BindGroupLayout,
189 pub entries: Vec<MaterialBindingEntry>,
190}
191
192impl Asset<WGPUBackend> for GPUMaterial {
193 type Source = MaterialDescriptor<'static>;
194 type Deps<'a> = ();
195
196 fn upload<'a>(source: &MaterialDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
197 let (pipeline, layout) = build_material(&backend.device, &source);
198
199 Some(Self {
200 pipeline,
201 layout,
202 entries: source.entries.to_vec(),
203 })
204 }
205}
206
207pub struct MaterialPlugin;
208impl MaterialPlugin {
209 pub fn new() -> Self {
210 Self
211 }
212}
213
214impl Plugin for MaterialPlugin {
215 fn build(&self, app: &mut App) {
216 app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUMaterial>::new());
217 }
218}