renderling/material/cpu.rs
1//! CPU side of materials.
2use std::sync::{Arc, Mutex};
3
4use craballoc::{
5 // Craballoc is used for memory allocation and management.
6 runtime::WgpuRuntime,
7 slab::{SlabAllocator, SlabBuffer},
8 value::Hybrid,
9};
10use crabslab::Id;
11use glam::{Vec3, Vec4};
12
13use crate::{
14 atlas::{Atlas, AtlasTexture},
15 material::shader::MaterialDescriptor,
16};
17
18/// Wrapper around the materials slab, which holds material textures in an atlas.
19#[derive(Clone)]
20pub struct Materials {
21 slab: SlabAllocator<WgpuRuntime>,
22 atlas: Atlas,
23 default_material: Material,
24}
25
26impl AsRef<WgpuRuntime> for Materials {
27 fn as_ref(&self) -> &WgpuRuntime {
28 self.slab.runtime()
29 }
30}
31
32impl Materials {
33 /// Creates a new `Materials` instance with the specified runtime and atlas size.
34 ///
35 /// # Arguments
36 ///
37 /// * `runtime` - A reference to the WgpuRuntime.
38 /// * `atlas_size` - The size of the atlas texture.
39 pub fn new(runtime: impl AsRef<WgpuRuntime>, atlas_size: wgpu::Extent3d) -> Self {
40 let slab = SlabAllocator::new(runtime, "materials", wgpu::BufferUsages::empty());
41 let atlas = Atlas::new(&slab, atlas_size, None, Some("materials-atlas"), None);
42 let default_material = Material {
43 descriptor: slab.new_value(Default::default()),
44 albedo_texture: Default::default(),
45 metallic_roughness_texture: Default::default(),
46 normal_mapping_texture: Default::default(),
47 ao_texture: Default::default(),
48 emissive_texture: Default::default(),
49 };
50 Self {
51 slab,
52 atlas,
53 default_material,
54 }
55 }
56
57 /// Returns a reference to the WgpuRuntime.
58 pub fn runtime(&self) -> &WgpuRuntime {
59 self.as_ref()
60 }
61
62 /// Returns a reference to the slab allocator.
63 pub fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
64 &self.slab
65 }
66
67 /// Returns a reference to the atlas.
68 pub fn atlas(&self) -> &Atlas {
69 &self.atlas
70 }
71
72 /// Returns the default material.
73 pub fn default_material(&self) -> &Material {
74 &self.default_material
75 }
76
77 /// Runs atlas upkeep and commits all changes to the GPU.
78 ///
79 /// Returns `true` if the atlas texture was recreated.
80 #[must_use]
81 pub fn commit(&self) -> (bool, SlabBuffer<wgpu::Buffer>) {
82 // Atlas upkeep must be called first because it generates updates into the slab
83 (self.atlas.upkeep(self.runtime()), self.slab.commit())
84 }
85
86 /// Stage a new [`Material`] on the materials slab.
87 pub fn new_material(&self) -> Material {
88 let descriptor = self.slab.new_value(MaterialDescriptor::default());
89 Material {
90 descriptor,
91 albedo_texture: Default::default(),
92 metallic_roughness_texture: Default::default(),
93 normal_mapping_texture: Default::default(),
94 ao_texture: Default::default(),
95 emissive_texture: Default::default(),
96 }
97 }
98}
99
100/// A material staged on the GPU.
101///
102/// Internally a `Material` holds references to:
103/// * its descriptor, [`MaterialDescriptor`], which lives on the GPU
104/// * [`AtlasTexture`]s that determine how the material presents:
105/// * albedo color
106/// * metallic roughness
107/// * normal mapping
108/// * ambient occlusion
109/// * emissive
110///
111/// ## Note
112///
113/// Clones of `Material` all point to the same underlying data.
114/// Changing a value on one `Material` will change that value for all clones as well.
115#[derive(Clone)]
116pub struct Material {
117 descriptor: Hybrid<MaterialDescriptor>,
118
119 albedo_texture: Arc<Mutex<Option<AtlasTexture>>>,
120 metallic_roughness_texture: Arc<Mutex<Option<AtlasTexture>>>,
121 normal_mapping_texture: Arc<Mutex<Option<AtlasTexture>>>,
122 ao_texture: Arc<Mutex<Option<AtlasTexture>>>,
123 emissive_texture: Arc<Mutex<Option<AtlasTexture>>>,
124}
125
126impl From<&Material> for Material {
127 fn from(value: &Material) -> Self {
128 value.clone()
129 }
130}
131
132impl Material {
133 /// Returns the unique identifier of the material descriptor.
134 pub fn id(&self) -> Id<MaterialDescriptor> {
135 self.descriptor.id()
136 }
137
138 /// Returns a copy of the underlying descriptor.
139 pub fn descriptor(&self) -> MaterialDescriptor {
140 self.descriptor.get()
141 }
142
143 /// Sets the emissive factor of the material.
144 ///
145 /// # Arguments
146 ///
147 /// * `param` - The emissive factor as a `Vec3`.
148 pub fn set_emissive_factor(&self, param: Vec3) -> &Self {
149 self.descriptor.modify(|d| d.emissive_factor = param);
150 self
151 }
152 /// Sets the emissive factor.
153 ///
154 /// # Arguments
155 ///
156 /// * `param` - The emissive factor as a `Vec3`.
157 pub fn with_emissive_factor(self, param: Vec3) -> Self {
158 self.set_emissive_factor(param);
159 self
160 }
161 /// Sets the emissive strength multiplier of the material.
162 ///
163 /// # Arguments
164 ///
165 /// * `param` - The emissive strength multiplier as a `f32`.
166 pub fn set_emissive_strength_multiplier(&self, param: f32) -> &Self {
167 self.descriptor
168 .modify(|d| d.emissive_strength_multiplier = param);
169 self
170 }
171 /// Sets the emissive strength multiplier.
172 ///
173 /// # Arguments
174 ///
175 /// * `param` - The emissive strength multiplier as a `f32`.
176 pub fn with_emissive_strength_multiplier(self, param: f32) -> Self {
177 self.set_emissive_strength_multiplier(param);
178 self
179 }
180 /// Sets the albedo factor of the material.
181 ///
182 /// # Arguments
183 ///
184 /// * `param` - The albedo factor as a `Vec4`.
185 pub fn set_albedo_factor(&self, param: Vec4) -> &Self {
186 self.descriptor.modify(|d| d.albedo_factor = param);
187 self
188 }
189 /// Sets the albedo factor.
190 ///
191 /// # Arguments
192 ///
193 /// * `param` - The albedo factor as a `Vec4`.
194 pub fn with_albedo_factor(self, param: Vec4) -> Self {
195 self.set_albedo_factor(param);
196 self
197 }
198 /// Sets the metallic factor of the material.
199 ///
200 /// # Arguments
201 ///
202 /// * `param` - The metallic factor as a `f32`.
203 pub fn set_metallic_factor(&self, param: f32) -> &Self {
204 self.descriptor.modify(|d| d.metallic_factor = param);
205 self
206 }
207 /// Sets the metallic factor.
208 ///
209 /// # Arguments
210 ///
211 /// * `param` - The metallic factor as a `f32`.
212 pub fn with_metallic_factor(self, param: f32) -> Self {
213 self.set_metallic_factor(param);
214 self
215 }
216 /// Sets the roughness factor of the material.
217 ///
218 /// # Arguments
219 ///
220 /// * `param` - The roughness factor as a `f32`.
221 pub fn set_roughness_factor(&self, param: f32) -> &Self {
222 self.descriptor.modify(|d| d.roughness_factor = param);
223 self
224 }
225 /// Sets the roughness factor.
226 ///
227 /// # Arguments
228 ///
229 /// * `param` - The roughness factor as a `f32`.
230 pub fn with_roughness_factor(self, param: f32) -> Self {
231 self.set_roughness_factor(param);
232 self
233 }
234 /// Sets the albedo texture coordinate of the material.
235 ///
236 /// # Arguments
237 ///
238 /// * `param` - The texture coordinate as a `u32`.
239 pub fn set_albedo_tex_coord(&self, param: u32) -> &Self {
240 self.descriptor.modify(|d| d.albedo_tex_coord = param);
241 self
242 }
243 /// Sets the albedo texture coordinate.
244 ///
245 /// # Arguments
246 ///
247 /// * `param` - The texture coordinate as a `u32`.
248 pub fn with_albedo_tex_coord(self, param: u32) -> Self {
249 self.set_albedo_tex_coord(param);
250 self
251 }
252 /// Sets the metallic roughness texture coordinate of the material.
253 ///
254 /// # Arguments
255 ///
256 /// * `param` - The texture coordinate as a `u32`.
257 pub fn set_metallic_roughness_tex_coord(&self, param: u32) -> &Self {
258 self.descriptor
259 .modify(|d| d.metallic_roughness_tex_coord = param);
260 self
261 }
262 /// Sets the metallic roughness texture coordinate.
263 ///
264 /// # Arguments
265 ///
266 /// * `param` - The texture coordinate as a `u32`.
267 pub fn with_metallic_roughness_tex_coord(self, param: u32) -> Self {
268 self.set_metallic_roughness_tex_coord(param);
269 self
270 }
271 /// Sets the normal texture coordinate of the material.
272 ///
273 /// # Arguments
274 ///
275 /// * `param` - The texture coordinate as a `u32`.
276 pub fn set_normal_tex_coord(&self, param: u32) -> &Self {
277 self.descriptor.modify(|d| d.normal_tex_coord = param);
278 self
279 }
280 /// Sets the normal texture coordinate.
281 ///
282 /// # Arguments
283 ///
284 /// * `param` - The texture coordinate as a `u32`.
285 pub fn with_normal_tex_coord(self, param: u32) -> Self {
286 self.set_normal_tex_coord(param);
287 self
288 }
289 /// Sets the ambient occlusion texture coordinate of the material.
290 ///
291 /// # Arguments
292 ///
293 /// * `param` - The texture coordinate as a `u32`.
294 pub fn set_ambient_occlusion_tex_coord(&self, param: u32) -> &Self {
295 self.descriptor.modify(|d| d.ao_tex_coord = param);
296 self
297 }
298 /// Sets the ambient occlusion texture coordinate.
299 ///
300 /// # Arguments
301 ///
302 /// * `param` - The texture coordinate as a `u32`.
303 pub fn with_ambient_occlusion_tex_coord(self, param: u32) -> Self {
304 self.set_ambient_occlusion_tex_coord(param);
305 self
306 }
307 /// Sets the emissive texture coordinate of the material.
308 ///
309 /// # Arguments
310 ///
311 /// * `param` - The texture coordinate as a `u32`.
312 pub fn set_emissive_tex_coord(&self, param: u32) -> &Self {
313 self.descriptor.modify(|d| d.emissive_tex_coord = param);
314 self
315 }
316 /// Sets the emissive texture coordinate.
317 ///
318 /// # Arguments
319 ///
320 /// * `param` - The texture coordinate as a `u32`.
321 pub fn with_emissive_tex_coord(self, param: u32) -> Self {
322 self.set_emissive_tex_coord(param);
323 self
324 }
325 /// Sets whether the material has lighting.
326 ///
327 /// # Arguments
328 ///
329 /// * `param` - A boolean indicating if the material has lighting.
330 pub fn set_has_lighting(&self, param: bool) -> &Self {
331 self.descriptor.modify(|d| d.has_lighting = param);
332 self
333 }
334 /// Sets whether the material has lighting.
335 ///
336 /// # Arguments
337 ///
338 /// * `param` - A boolean indicating if the material has lighting.
339 pub fn with_has_lighting(self, param: bool) -> Self {
340 self.set_has_lighting(param);
341 self
342 }
343 /// Sets the ambient occlusion strength of the material.
344 ///
345 /// # Arguments
346 ///
347 /// * `param` - The ambient occlusion strength as a `f32`.
348 pub fn set_ambient_occlusion_strength(&self, param: f32) -> &Self {
349 self.descriptor.modify(|d| d.ao_strength = param);
350 self
351 }
352 /// Sets the ambient occlusion strength.
353 ///
354 /// # Arguments
355 ///
356 /// * `param` - The ambient occlusion strength as a `f32`.
357 pub fn with_ambient_occlusion_strength(self, param: f32) -> Self {
358 self.set_ambient_occlusion_strength(param);
359 self
360 }
361
362 /// Remove the albedo texture.
363 ///
364 /// This causes any `[Primitive]` that references this material to fall back to
365 /// using the albedo factor for color.
366 pub fn remove_albedo_texture(&self) {
367 self.descriptor.modify(|d| d.albedo_texture_id = Id::NONE);
368 self.albedo_texture.lock().unwrap().take();
369 }
370
371 /// Sets the albedo color texture.
372 pub fn set_albedo_texture(&self, texture: &AtlasTexture) -> &Self {
373 self.descriptor
374 .modify(|d| d.albedo_texture_id = texture.id());
375 *self.albedo_texture.lock().unwrap() = Some(texture.clone());
376 self
377 }
378
379 /// Replace the albedo texture.
380 pub fn with_albedo_texture(self, texture: &AtlasTexture) -> Self {
381 self.descriptor
382 .modify(|d| d.albedo_texture_id = texture.id());
383 *self.albedo_texture.lock().unwrap() = Some(texture.clone());
384 self
385 }
386
387 /// Remove the metallic roughness texture.
388 ///
389 /// This causes any `[Renderlet]` that references this material to fall back to
390 /// using the metallic and roughness factors for appearance.
391 pub fn remove_metallic_roughness_texture(&self) {
392 self.descriptor
393 .modify(|d| d.metallic_roughness_texture_id = Id::NONE);
394 self.metallic_roughness_texture.lock().unwrap().take();
395 }
396
397 /// Sets the metallic roughness texture of the material.
398 ///
399 /// # Arguments
400 ///
401 /// * `texture` - A reference to the metallic roughness `AtlasTexture`.
402 pub fn set_metallic_roughness_texture(&self, texture: &AtlasTexture) -> &Self {
403 self.descriptor
404 .modify(|d| d.metallic_roughness_texture_id = texture.id());
405 *self.metallic_roughness_texture.lock().unwrap() = Some(texture.clone());
406 self
407 }
408
409 /// Sets the metallic roughness texture and returns the material.
410 ///
411 /// # Arguments
412 ///
413 /// * `texture` - A reference to the metallic roughness `AtlasTexture`.
414 pub fn with_metallic_roughness_texture(self, texture: &AtlasTexture) -> Self {
415 self.set_metallic_roughness_texture(texture);
416 self
417 }
418
419 /// Remove the normal texture.
420 ///
421 /// This causes any `[Renderlet]` that references this material to fall back to
422 /// using the default normal mapping.
423 pub fn remove_normal_texture(&self) {
424 self.descriptor.modify(|d| d.normal_texture_id = Id::NONE);
425 self.normal_mapping_texture.lock().unwrap().take();
426 }
427
428 /// Sets the normal texture of the material.
429 ///
430 /// # Arguments
431 ///
432 /// * `texture` - A reference to the normal `AtlasTexture`.
433 pub fn set_normal_texture(&self, texture: &AtlasTexture) -> &Self {
434 self.descriptor
435 .modify(|d| d.normal_texture_id = texture.id());
436 *self.normal_mapping_texture.lock().unwrap() = Some(texture.clone());
437 self
438 }
439
440 /// Sets the normal texture and returns the material.
441 ///
442 /// # Arguments
443 ///
444 /// * `texture` - A reference to the normal `AtlasTexture`.
445 pub fn with_normal_texture(self, texture: &AtlasTexture) -> Self {
446 self.set_normal_texture(texture);
447 self
448 }
449
450 /// Remove the ambient occlusion texture.
451 ///
452 /// This causes any `[Renderlet]` that references this material to fall back to
453 /// using the default ambient occlusion.
454 pub fn remove_ambient_occlusion_texture(&self) {
455 self.descriptor.modify(|d| d.ao_texture_id = Id::NONE);
456 self.ao_texture.lock().unwrap().take();
457 }
458
459 /// Sets the ambient occlusion texture of the material.
460 ///
461 /// # Arguments
462 ///
463 /// * `texture` - A reference to the ambient occlusion `AtlasTexture`.
464 pub fn set_ambient_occlusion_texture(&self, texture: &AtlasTexture) -> &Self {
465 self.descriptor.modify(|d| d.ao_texture_id = texture.id());
466 *self.ao_texture.lock().unwrap() = Some(texture.clone());
467 self
468 }
469
470 /// Sets the ambient occlusion texture and returns the material.
471 ///
472 /// # Arguments
473 ///
474 /// * `texture` - A reference to the ambient occlusion `AtlasTexture`.
475 pub fn with_ambient_occlusion_texture(self, texture: &AtlasTexture) -> Self {
476 self.set_ambient_occlusion_texture(texture);
477 self
478 }
479
480 /// Remove the emissive texture.
481 ///
482 /// This causes any `[Renderlet]` that references this material to fall back to
483 /// using the emissive factor for appearance.
484 pub fn remove_emissive_texture(&self) {
485 self.descriptor.modify(|d| d.emissive_texture_id = Id::NONE);
486 self.emissive_texture.lock().unwrap().take();
487 }
488
489 /// Sets the emissive texture of the material.
490 ///
491 /// # Arguments
492 ///
493 /// * `texture` - A reference to the emissive `AtlasTexture`.
494 pub fn set_emissive_texture(&self, texture: &AtlasTexture) -> &Self {
495 self.descriptor
496 .modify(|d| d.emissive_texture_id = texture.id());
497 *self.emissive_texture.lock().unwrap() = Some(texture.clone());
498 self
499 }
500
501 /// Sets the emissive texture and returns the material.
502 ///
503 /// # Arguments
504 ///
505 /// * `texture` - A reference to the emissive `AtlasTexture`.
506 pub fn with_emissive_texture(self, texture: &AtlasTexture) -> Self {
507 self.set_emissive_texture(texture);
508 self
509 }
510}