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