1use std::collections::HashSet;
2
3use glam::{uvec4, vec3, vec4, Vec3, Vec4};
4use indexmap::IndexMap;
5use log::{error, warn};
6use xc3_model::{
7 material::assignments::{Assignment, AssignmentValue, OutputAssignments},
8 ImageTexture, IndexMapExt,
9};
10
11use crate::{
12 pipeline::{Output5Type, PipelineKey},
13 shadergen::{
14 generate_alpha_test_wgsl, generate_assignments_wgsl, generate_layering_wgsl,
15 generate_normal_intensity_wgsl,
16 },
17 texture::create_default_black_texture,
18 DeviceBufferExt, MonolibShaderTextures,
19};
20
21#[derive(Debug)]
22pub(crate) struct Material {
23 pub name: String,
24 pub bind_group2: crate::shader::model::bind_groups::BindGroup2,
25
26 pub pipeline_key: PipelineKey,
29
30 pub fur_shell_instance_count: Option<u32>,
31}
32
33#[allow(clippy::too_many_arguments)]
34#[tracing::instrument(skip_all)]
35pub fn create_material(
36 device: &wgpu::Device,
37 queue: &wgpu::Queue,
38 pipelines: &mut HashSet<PipelineKey>,
39 material: &xc3_model::material::Material,
40 textures: &[wgpu::Texture],
41 samplers: &[wgpu::Sampler],
42 image_textures: &[ImageTexture],
43 monolib_shader: &MonolibShaderTextures,
44 is_instanced_static: bool,
45) -> Material {
46 let default_black = create_default_black_texture(device, queue)
48 .create_view(&wgpu::TextureViewDescriptor::default());
49
50 let default_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
51 address_mode_u: wgpu::AddressMode::Repeat,
52 address_mode_v: wgpu::AddressMode::Repeat,
53 min_filter: wgpu::FilterMode::Linear,
54 mag_filter: wgpu::FilterMode::Linear,
55 ..Default::default()
56 });
57
58 let mut name_to_index: IndexMap<_, _> = (0..material.textures.len())
61 .map(|i| (format!("s{i}").into(), i))
62 .collect();
63
64 let material_assignments = material.output_assignments(image_textures);
65 let assignments = output_assignments(&material_assignments);
66
67 if let Some(a) = &material.alpha_test {
69 name_to_index.entry_index(format!("s{}", a.texture_index).into());
70 }
71
72 let assignments_wgsl = generate_assignments_wgsl(&material_assignments, &mut name_to_index);
73 let output_layers_wgsl = generate_layering_wgsl(&material_assignments);
74
75 let alpha_test_wgsl = material
77 .alpha_test
78 .as_ref()
79 .map(|a| generate_alpha_test_wgsl(a, &mut name_to_index))
80 .unwrap_or_default();
81
82 let normal_intensity_wgsl = material_assignments
83 .normal_intensity
84 .as_ref()
85 .map(|i| generate_normal_intensity_wgsl(*i))
86 .unwrap_or_default();
87
88 let mut texture_views: [Option<_>; 16] = std::array::from_fn(|_| None);
89
90 for (name, i) in &name_to_index {
91 if let Some(texture) = assign_texture(material, textures, monolib_shader, name) {
92 if *i < texture_views.len() {
93 texture_views[*i] = Some(texture.create_view(&Default::default()));
94 }
95 } else {
96 error!("Unable to assign texture {name:?} for {:?}", &material.name);
97 }
98 }
99
100 let fur_params = material
102 .fur_params
103 .as_ref()
104 .map(|p| crate::shader::model::FurShellParams {
105 xyz_offset: vec3(0.0, p.y_offset * p.shell_width, 0.0),
106 instance_count: p.instance_count as f32,
107 shell_width: 1.0 / (p.instance_count as f32) * p.shell_width,
108 alpha: (1.0 - p.alpha) / p.instance_count as f32,
109 })
110 .unwrap_or(crate::shader::model::FurShellParams {
111 xyz_offset: Vec3::ZERO,
112 instance_count: 0.0,
113 shell_width: 0.0,
114 alpha: 0.0,
115 });
116
117 let outline_width =
119 value_channel_assignment(material_assignments.outline_width.as_ref()).unwrap_or(0.005);
120
121 let per_material = device.create_uniform_buffer(
124 &format!(
126 "PerMaterial {:?} shd{:04}",
127 &material.name, material.technique_index
128 ),
129 &[crate::shader::model::PerMaterial {
130 assignments,
131 outline_width,
132 fur_params,
133 alpha_test_ref: material.alpha_test_ref,
134 }],
135 );
136
137 let bind_group2 = crate::shader::model::bind_groups::BindGroup2::from_bindings(
141 device,
142 crate::shader::model::bind_groups::BindGroupLayout2 {
143 s0: texture_views[0].as_ref().unwrap_or(&default_black),
144 s1: texture_views[1].as_ref().unwrap_or(&default_black),
145 s2: texture_views[2].as_ref().unwrap_or(&default_black),
146 s3: texture_views[3].as_ref().unwrap_or(&default_black),
147 s4: texture_views[4].as_ref().unwrap_or(&default_black),
148 s5: texture_views[5].as_ref().unwrap_or(&default_black),
149 s6: texture_views[6].as_ref().unwrap_or(&default_black),
150 s7: texture_views[7].as_ref().unwrap_or(&default_black),
151 s8: texture_views[8].as_ref().unwrap_or(&default_black),
152 s9: texture_views[9].as_ref().unwrap_or(&default_black),
153 s10: texture_views[10].as_ref().unwrap_or(&default_black),
154 s11: texture_views[11].as_ref().unwrap_or(&default_black),
155 s12: texture_views[12].as_ref().unwrap_or(&default_black),
156 s13: texture_views[13].as_ref().unwrap_or(&default_black),
157 s14: texture_views[14].as_ref().unwrap_or(&default_black),
158 s15: texture_views[15].as_ref().unwrap_or(&default_black),
159 s0_sampler: material_sampler(material, samplers, 0).unwrap_or(&default_sampler),
160 s1_sampler: material_sampler(material, samplers, 1).unwrap_or(&default_sampler),
161 s2_sampler: material_sampler(material, samplers, 2).unwrap_or(&default_sampler),
162 s3_sampler: material_sampler(material, samplers, 3).unwrap_or(&default_sampler),
163 s4_sampler: material_sampler(material, samplers, 4).unwrap_or(&default_sampler),
164 s5_sampler: material_sampler(material, samplers, 5).unwrap_or(&default_sampler),
165 s6_sampler: material_sampler(material, samplers, 6).unwrap_or(&default_sampler),
166 s7_sampler: material_sampler(material, samplers, 7).unwrap_or(&default_sampler),
167 s8_sampler: material_sampler(material, samplers, 8).unwrap_or(&default_sampler),
168 s9_sampler: material_sampler(material, samplers, 9).unwrap_or(&default_sampler),
169 s10_sampler: material_sampler(material, samplers, 10).unwrap_or(&default_sampler),
170 s11_sampler: material_sampler(material, samplers, 11).unwrap_or(&default_sampler),
171 s12_sampler: material_sampler(material, samplers, 12).unwrap_or(&default_sampler),
172 s13_sampler: material_sampler(material, samplers, 13).unwrap_or(&default_sampler),
173 s14_sampler: material_sampler(material, samplers, 14).unwrap_or(&default_sampler),
174 alpha_test_sampler: material
177 .alpha_test
178 .as_ref()
179 .map(|a| a.sampler_index)
180 .and_then(|i| samplers.get(i))
181 .unwrap_or(&default_sampler),
182 per_material: per_material.as_entire_buffer_binding(),
183 },
184 );
185
186 let output5_type = if material_assignments.mat_id().is_some() {
192 if material.render_flags.specular() {
193 Output5Type::Specular
194 } else {
195 Output5Type::Emission
197 }
198 } else {
199 Output5Type::Specular
201 };
202
203 let pipeline_key = PipelineKey {
208 pass_type: material.pass_type,
209 flags: material.state_flags,
210 is_outline: material.name.ends_with("_outline"),
211 output5_type,
212 is_instanced_static,
213 assignments_wgsl,
214 output_layers_wgsl,
215 alpha_test_wgsl,
216 normal_intensity_wgsl,
217 };
218 pipelines.insert(pipeline_key.clone());
219
220 Material {
221 name: material.name.clone(),
222 bind_group2,
223 pipeline_key,
224 fur_shell_instance_count: material.fur_params.as_ref().map(|p| p.instance_count),
225 }
226}
227
228fn output_assignments(
229 assignments: &OutputAssignments,
230) -> [crate::shader::model::OutputAssignment; 6] {
231 [0, 1, 2, 3, 4, 5].map(|i| {
232 let assignment = &assignments.output_assignments[i];
233
234 crate::shader::model::OutputAssignment {
235 has_channels: uvec4(
236 has_value(&assignments.assignments, assignment.x) as u32,
237 has_value(&assignments.assignments, assignment.y) as u32,
238 has_value(&assignments.assignments, assignment.z) as u32,
239 has_value(&assignments.assignments, assignment.w) as u32,
240 ),
241 default_value: output_default(i),
242 }
243 })
244}
245
246fn has_value(assignments: &[Assignment], i: Option<usize>) -> bool {
247 if let Some(i) = i {
248 match &assignments[i] {
249 Assignment::Value(c) => c.is_some(),
250 Assignment::Func { args, .. } => args.iter().any(|a| has_value(assignments, Some(*a))),
251 }
252 } else {
253 false
254 }
255}
256
257fn value_channel_assignment(assignment: Option<&AssignmentValue>) -> Option<f32> {
258 if let Some(AssignmentValue::Float(f)) = assignment {
259 Some(f.0)
260 } else {
261 None
262 }
263}
264
265fn create_bit_info(
266 mat_id: u32,
267 mat_flag: bool,
268 hatching_flag: bool,
269 specular_col: bool,
270 ssr: bool,
271) -> f32 {
272 let n_val = mat_id
274 | ((ssr as u32) << 3)
275 | ((specular_col as u32) << 4)
276 | ((mat_flag as u32) << 5)
277 | ((hatching_flag as u32) << 6);
278 (n_val as f32 + 0.1) / 255.0
279}
280
281fn output_default(i: usize) -> Vec4 {
282 let etc_flags = create_bit_info(2, false, false, true, false);
284
285 let output_defaults: [Vec4; 6] = [
289 Vec4::ONE,
290 Vec4::new(0.0, 0.0, 0.0, etc_flags),
291 Vec4::new(0.5, 0.5, 1.0, 0.0),
292 Vec4::ZERO,
293 Vec4::new(1.0, 1.0, 1.0, 0.0),
294 Vec4::ZERO,
295 ];
296
297 vec4(
298 output_defaults[i][0],
299 output_defaults[i][1],
300 output_defaults[i][2],
301 output_defaults[i][3],
302 )
303}
304
305fn assign_texture<'a>(
306 material: &xc3_model::material::Material,
307 textures: &'a [wgpu::Texture],
308 monolib_shader: &'a MonolibShaderTextures,
309 name: &str,
310) -> Option<&'a wgpu::Texture> {
311 let texture = match material_texture_index(name) {
312 Some(texture_index) => {
313 let image_texture_index = material.textures.get(texture_index)?.image_texture_index;
316 textures.get(image_texture_index)
317 }
318 None => {
319 monolib_shader.global_texture(name)
321 }
322 }?;
323
324 if texture.dimension() == wgpu::TextureDimension::D2 && texture.depth_or_array_layers() == 1 {
326 Some(texture)
327 } else {
328 error!(
329 "Expected 2D texture but found dimension {:?} and {} layers.",
330 texture.dimension(),
331 texture.depth_or_array_layers()
332 );
333 None
334 }
335}
336
337fn material_texture_index(sampler_name: &str) -> Option<usize> {
338 sampler_name.strip_prefix('s')?.parse().ok()
342}
343
344fn material_sampler<'a>(
345 material: &xc3_model::material::Material,
346 samplers: &'a [wgpu::Sampler],
347 index: usize,
348) -> Option<&'a wgpu::Sampler> {
349 material
351 .textures
352 .get(index)
353 .and_then(|texture| samplers.get(texture.sampler_index))
354}