1use std::collections::HashSet;
2
3use glam::{Vec3, Vec4, uvec4, vec3, vec4};
4use indexmap::IndexMap;
5use log::{error, warn};
6use xc3_model::{
7 ImageTexture, IndexMapExt,
8 material::assignments::{Assignment, AssignmentValue, OutputAssignments},
9};
10
11use crate::{
12 DeviceBufferExt, MonolibShaderTextures,
13 pipeline::{Output5Type, PipelineKey},
14 shader::model::TEXTURE_SAMPLER_COUNT,
15 shadergen::ShaderWgsl,
16 texture::{default_black_3d_texture, default_black_cube_texture, default_black_texture},
17};
18
19#[derive(Debug)]
20pub(crate) struct Material {
21 pub name: String,
22 pub bind_group2: crate::shader::model::bind_groups::BindGroup2,
23
24 pub pipeline_key: PipelineKey,
27
28 pub fur_shell_instance_count: Option<u32>,
29}
30
31#[allow(clippy::too_many_arguments)]
32#[tracing::instrument(skip_all)]
33pub fn create_material(
34 device: &wgpu::Device,
35 queue: &wgpu::Queue,
36 pipelines: &mut HashSet<PipelineKey>,
37 material: &xc3_model::material::Material,
38 textures: &[wgpu::Texture],
39 samplers: &[wgpu::Sampler],
40 image_textures: &[ImageTexture],
41 monolib_shader: &MonolibShaderTextures,
42 is_instanced_static: bool,
43) -> Material {
44 let default_2d =
46 default_black_texture(device, queue).create_view(&wgpu::TextureViewDescriptor::default());
47 let default_3d =
48 default_black_3d_texture(device, queue).create_view(&wgpu::TextureViewDescriptor {
49 dimension: Some(wgpu::TextureViewDimension::D3),
50 ..Default::default()
51 });
52 let default_cube =
53 default_black_cube_texture(device, queue).create_view(&wgpu::TextureViewDescriptor {
54 dimension: Some(wgpu::TextureViewDimension::Cube),
55 ..Default::default()
56 });
57
58 let default_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
59 address_mode_u: wgpu::AddressMode::Repeat,
60 address_mode_v: wgpu::AddressMode::Repeat,
61 min_filter: wgpu::FilterMode::Linear,
62 mag_filter: wgpu::FilterMode::Linear,
63 ..Default::default()
64 });
65
66 let mut name_to_index: IndexMap<_, _> = (0..material.textures.len())
69 .map(|i| (format!("s{i}").into(), i))
70 .collect();
71
72 let material_assignments = material.output_assignments(image_textures);
73 let assignments = output_assignments(&material_assignments);
74
75 if let Some(a) = &material.alpha_test {
77 name_to_index.entry_index(format!("s{}", a.texture_index).into());
78 }
79
80 let wgsl = ShaderWgsl::new(
81 &material_assignments,
82 material.alpha_test.as_ref(),
83 &mut name_to_index,
84 );
85
86 let mut material_textures: [Option<_>; TEXTURE_SAMPLER_COUNT as usize] =
87 std::array::from_fn(|_| None);
88
89 for (name, i) in &name_to_index {
90 if let Some(texture) = assign_texture(material, textures, monolib_shader, name) {
91 if let Some(material_texture) = material_textures.get_mut(*i) {
92 *material_texture = Some(texture);
93 }
94 } else {
95 error!("Unable to assign {name} for {:?}", &material.name);
96 }
97 }
98
99 let fur_params = material
101 .fur_params
102 .as_ref()
103 .map(|p| crate::shader::model::FurShellParams {
104 xyz_offset: vec3(0.0, p.y_offset * p.shell_width, 0.0),
105 instance_count: p.instance_count as f32,
106 shell_width: 1.0 / (p.instance_count as f32) * p.shell_width,
107 alpha: (1.0 - p.alpha) / p.instance_count as f32,
108 })
109 .unwrap_or(crate::shader::model::FurShellParams {
110 xyz_offset: Vec3::ZERO,
111 instance_count: 0.0,
112 shell_width: 0.0,
113 alpha: 0.0,
114 });
115
116 let outline_width =
118 value_channel_assignment(material_assignments.outline_width.as_ref()).unwrap_or(0.005);
119
120 let per_material = device.create_storage_buffer(
122 &format!(
124 "PerMaterial {:?} shd{:04}",
125 &material.name, material.technique_index
126 ),
127 &[crate::shader::model::PerMaterial {
128 assignments,
129 outline_width,
130 fur_params,
131 alpha_test_ref: material.alpha_test_ref,
132 }],
133 );
134
135 let texture_views = material_textures.map(|t| {
136 t.map(|t| {
137 t.create_view(&wgpu::TextureViewDescriptor {
138 dimension: if t.dimension() == wgpu::TextureDimension::D3 {
139 Some(wgpu::TextureViewDimension::D3)
140 } else if t.dimension() == wgpu::TextureDimension::D2
141 && t.depth_or_array_layers() == 6
142 {
143 Some(wgpu::TextureViewDimension::Cube)
144 } else {
145 Some(wgpu::TextureViewDimension::D2)
146 },
147 ..Default::default()
148 })
149 })
150 });
151
152 let texture_array = texture_view_array(
154 &material_textures,
155 &texture_views,
156 |t| t.dimension() == wgpu::TextureDimension::D2 && t.depth_or_array_layers() == 1,
157 &default_2d,
158 );
159 let texture_array_3d = texture_view_array(
160 &material_textures,
161 &texture_views,
162 |t| t.dimension() == wgpu::TextureDimension::D3,
163 &default_3d,
164 );
165 let texture_array_cube = texture_view_array(
166 &material_textures,
167 &texture_views,
168 |t| t.dimension() == wgpu::TextureDimension::D2 && t.depth_or_array_layers() == 6,
169 &default_cube,
170 );
171
172 let sampler_array = std::array::from_fn(|i| {
173 material_sampler(material, samplers, i).unwrap_or(&default_sampler)
174 });
175
176 let bind_group2 = crate::shader::model::bind_groups::BindGroup2::from_bindings(
180 device,
181 crate::shader::model::bind_groups::BindGroupLayout2 {
182 textures: &texture_array,
183 textures_d3: &texture_array_3d,
184 textures_cube: &texture_array_cube,
185 samplers: &sampler_array,
186 alpha_test_sampler: material
188 .alpha_test
189 .as_ref()
190 .map(|a| a.sampler_index)
191 .and_then(|i| samplers.get(i))
192 .unwrap_or(&default_sampler),
193 per_material: per_material.as_entire_buffer_binding(),
194 },
195 );
196
197 let output5_type = if material_assignments.mat_id().is_some() {
203 if material.render_flags.specular() {
204 Output5Type::Specular
205 } else {
206 Output5Type::Emission
208 }
209 } else {
210 Output5Type::Specular
212 };
213
214 let pipeline_key = PipelineKey {
219 pass_type: material.pass_type,
220 flags: material.state_flags,
221 is_outline: material.name.ends_with("_outline"),
222 output5_type,
223 is_instanced_static,
224 wgsl,
225 };
226 pipelines.insert(pipeline_key.clone());
227
228 Material {
229 name: material.name.clone(),
230 bind_group2,
231 pipeline_key,
232 fur_shell_instance_count: material.fur_params.as_ref().map(|p| p.instance_count),
233 }
234}
235
236fn texture_view_array<'a, const N: usize, F: Fn(&wgpu::Texture) -> bool>(
237 textures: &[Option<&wgpu::Texture>],
238 texture_views: &'a [Option<wgpu::TextureView>],
239 check: F,
240 default: &'a wgpu::TextureView,
241) -> [&'a wgpu::TextureView; N] {
242 std::array::from_fn(|i| {
243 textures[i]
244 .as_ref()
245 .and_then(|t| {
246 if check(t) {
247 texture_views[i].as_ref()
248 } else {
249 None
250 }
251 })
252 .unwrap_or(default)
253 })
254}
255
256fn output_assignments(
257 assignments: &OutputAssignments,
258) -> [crate::shader::model::OutputAssignment; 6] {
259 [0, 1, 2, 3, 4, 5].map(|i| {
260 let assignment = &assignments.output_assignments[i];
261
262 crate::shader::model::OutputAssignment {
263 has_channels: uvec4(
264 has_value(&assignments.assignments, assignment.x) as u32,
265 has_value(&assignments.assignments, assignment.y) as u32,
266 has_value(&assignments.assignments, assignment.z) as u32,
267 has_value(&assignments.assignments, assignment.w) as u32,
268 ),
269 default_value: output_default(i),
270 }
271 })
272}
273
274fn has_value(assignments: &[Assignment], i: Option<usize>) -> bool {
275 if let Some(i) = i {
276 match &assignments[i] {
277 Assignment::Value(c) => c.is_some(),
278 Assignment::Func { args, .. } => args.iter().any(|a| has_value(assignments, Some(*a))),
279 }
280 } else {
281 false
282 }
283}
284
285fn value_channel_assignment(assignment: Option<&AssignmentValue>) -> Option<f32> {
286 if let Some(AssignmentValue::Float(f)) = assignment {
287 Some(f.0)
288 } else {
289 None
290 }
291}
292
293fn create_bit_info(
294 mat_id: u32,
295 mat_flag: bool,
296 hatching_flag: bool,
297 specular_col: bool,
298 ssr: bool,
299) -> f32 {
300 let n_val = mat_id
302 | ((ssr as u32) << 3)
303 | ((specular_col as u32) << 4)
304 | ((mat_flag as u32) << 5)
305 | ((hatching_flag as u32) << 6);
306 (n_val as f32 + 0.1) / 255.0
307}
308
309fn output_default(i: usize) -> Vec4 {
310 let etc_flags = create_bit_info(2, false, false, true, false);
312
313 let output_defaults: [Vec4; 6] = [
317 Vec4::ONE,
318 Vec4::new(0.0, 0.0, 0.0, etc_flags),
319 Vec4::new(0.5, 0.5, 1.0, 0.0),
320 Vec4::ZERO,
321 Vec4::new(1.0, 1.0, 1.0, 0.0),
322 Vec4::ZERO,
323 ];
324
325 vec4(
326 output_defaults[i][0],
327 output_defaults[i][1],
328 output_defaults[i][2],
329 output_defaults[i][3],
330 )
331}
332
333fn assign_texture<'a>(
334 material: &xc3_model::material::Material,
335 textures: &'a [wgpu::Texture],
336 monolib_shader: &'a MonolibShaderTextures,
337 name: &str,
338) -> Option<&'a wgpu::Texture> {
339 match material_texture_index(name) {
340 Some(texture_index) => {
341 let image_texture_index = material.textures.get(texture_index)?.image_texture_index;
344 textures.get(image_texture_index)
345 }
346 None => {
347 monolib_shader.global_texture(name)
349 }
350 }
351}
352
353fn material_texture_index(sampler_name: &str) -> Option<usize> {
354 sampler_name.strip_prefix('s')?.parse().ok()
358}
359
360fn material_sampler<'a>(
361 material: &xc3_model::material::Material,
362 samplers: &'a [wgpu::Sampler],
363 index: usize,
364) -> Option<&'a wgpu::Sampler> {
365 material
367 .textures
368 .get(index)
369 .and_then(|texture| samplers.get(texture.sampler_index))
370}