1use std::{
2 borrow::Cow,
3 collections::hash_map::{Entry, HashMap},
4 fmt, hash, mem,
5 ops::Range,
6 ptr, str,
7 sync::{Arc, Mutex},
8};
9
10const PRELOAD_TEXTURES: bool = false;
11
12const META_BASE_COLOR: crate::texture::Meta = crate::texture::Meta {
13 format: blade_graphics::TextureFormat::Bc1UnormSrgb,
14 generate_mips: true,
15 y_flip: false,
16};
17const META_NORMAL: crate::texture::Meta = crate::texture::Meta {
18 format: blade_graphics::TextureFormat::Bc5Unorm,
20 generate_mips: false,
21 y_flip: false,
22};
23
24fn pack4x8snorm(v: [f32; 4]) -> u32 {
25 v.iter().rev().fold(0u32, |u, f| {
26 (u << 8) | (f.clamp(-1.0, 1.0) * 127.0 + 0.5) as i8 as u8 as u32
27 })
28}
29
30fn encode_normal(v: [f32; 3]) -> u32 {
31 pack4x8snorm([v[0], v[1], v[2], 0.0])
32}
33
34pub struct Geometry {
35 pub name: String,
36 pub vertex_range: Range<u32>,
37 pub index_offset: u64,
38 pub index_type: Option<blade_graphics::IndexType>,
39 pub triangle_count: u32,
40 pub transform: blade_graphics::Transform,
41 pub material_index: usize,
42}
43
44pub struct Material {
46 pub base_color_texture: Option<blade_asset::Handle<crate::Texture>>,
47 pub base_color_factor: [f32; 4],
48 pub normal_texture: Option<blade_asset::Handle<crate::Texture>>,
49 pub normal_scale: f32,
50 pub transparent: bool,
51}
52
53pub struct Model {
54 pub name: String,
55 pub winding: f32,
56 pub geometries: Vec<Geometry>,
57 pub materials: Vec<Material>,
58 pub vertex_buffer: blade_graphics::Buffer,
59 pub index_buffer: blade_graphics::Buffer,
60 pub transform_buffer: blade_graphics::Buffer,
61 pub acceleration_structure: blade_graphics::AccelerationStructure,
62}
63
64#[derive(blade_macros::Flat, Default)]
65struct TextureReference<'a> {
66 path: Cow<'a, [u8]>,
67 embedded_data: Cow<'a, [u8]>,
68 source_index: usize,
70}
71
72#[derive(blade_macros::Flat)]
73struct CookedMaterial<'a> {
74 base_color: TextureReference<'a>,
75 base_color_factor: [f32; 4],
76 normal: TextureReference<'a>,
77 normal_scale: f32,
78 transparent: bool,
79}
80
81#[derive(blade_macros::Flat)]
82struct CookedGeometry<'a> {
83 name: Cow<'a, [u8]>,
84 vertices: Cow<'a, [crate::Vertex]>,
85 indices: Cow<'a, [u32]>,
86 transform: [f32; 12],
87 material_index: u32,
88}
89
90#[derive(Clone, PartialEq)]
91struct GltfVertex {
92 position: [f32; 3],
93 normal: [f32; 3],
94 tangent: [f32; 4],
95 tex_coords: [f32; 2],
96}
97impl Default for GltfVertex {
98 fn default() -> Self {
99 Self {
100 position: [0.0; 3],
101 normal: [0.0, 1.0, 0.0],
102 tangent: [1.0, 0.0, 0.0, 0.0],
103 tex_coords: [0.0; 2],
104 }
105 }
106}
107impl Eq for GltfVertex {}
108impl hash::Hash for GltfVertex {
109 fn hash<H: hash::Hasher>(&self, state: &mut H) {
110 for f in self.position.iter() {
111 f.to_bits().hash(state);
112 }
113 for f in self.normal.iter() {
114 f.to_bits().hash(state);
115 }
116 for f in self.tangent.iter() {
117 f.to_bits().hash(state);
118 }
119 for f in self.tex_coords.iter() {
120 f.to_bits().hash(state);
121 }
122 }
123}
124
125#[cfg(feature = "asset")]
126struct FlattenedGeometry(Box<[GltfVertex]>);
127#[cfg(feature = "asset")]
128impl mikktspace::Geometry for FlattenedGeometry {
129 fn num_faces(&self) -> usize {
130 self.0.len() / 3
131 }
132 fn num_vertices_of_face(&self, _face: usize) -> usize {
133 3
134 }
135 fn position(&self, face: usize, vert: usize) -> [f32; 3] {
136 self.0[face * 3 + vert].position
137 }
138 fn normal(&self, face: usize, vert: usize) -> [f32; 3] {
139 self.0[face * 3 + vert].normal
140 }
141 fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] {
142 self.0[face * 3 + vert].tex_coords
143 }
144 fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) {
145 self.0[face * 3 + vert].tangent = tangent;
146 }
147}
148#[cfg(feature = "asset")]
149impl FlattenedGeometry {
150 #[profiling::function]
151 fn reconstruct_indices(self) -> (Vec<u32>, Vec<crate::Vertex>) {
152 let mut indices = Vec::with_capacity(self.0.len());
153 let mut vertices = Vec::new();
154 let mut cache = HashMap::new();
155 for v in self.0.iter() {
156 let i = match cache.entry(v.clone()) {
157 Entry::Occupied(e) => *e.get(),
158 Entry::Vacant(e) => {
159 let i = vertices.len() as u32;
160 let t = &v.tangent;
161 vertices.push(crate::Vertex {
162 position: v.position,
163 bitangent_sign: t[3],
164 tex_coords: v.tex_coords,
165 normal: encode_normal(v.normal),
166 tangent: encode_normal([t[0], t[1], t[2]]),
167 });
168 *e.insert(i)
169 }
170 };
171 indices.push(i);
172 }
173 log::debug!("Compacted {}->{}", self.0.len(), vertices.len());
174 (indices, vertices)
175 }
176}
177
178#[derive(blade_macros::Flat)]
179pub struct CookedModel<'a> {
180 name: &'a [u8],
181 winding: f32,
182 materials: Vec<CookedMaterial<'a>>,
183 geometries: Vec<CookedGeometry<'a>>,
184}
185
186#[cfg(feature = "asset")]
187impl CookedModel<'_> {
188 fn populate_gltf(
189 &mut self,
190 g_node: gltf::Node,
191 parent_transform: glam::Mat4,
192 data_buffers: &[Vec<u8>],
193 flattened_geos: &mut Vec<FlattenedGeometry>,
194 ) {
195 let local_transform = glam::Mat4::from_cols_array_2d(&g_node.transform().matrix());
196 let global_transform = parent_transform * local_transform;
197
198 if let Some(g_mesh) = g_node.mesh() {
199 let name = g_node.name().unwrap_or("");
200 let col_matrix = mint::ColumnMatrix3x4 {
201 x: global_transform.x_axis.truncate().into(),
202 y: global_transform.y_axis.truncate().into(),
203 z: global_transform.z_axis.truncate().into(),
204 w: global_transform.w_axis.truncate().into(),
205 };
206 let transform = mint::RowMatrix3x4::from(col_matrix).into();
207
208 for (prim_index, g_primitive) in g_mesh.primitives().enumerate() {
209 if g_primitive.mode() != gltf::mesh::Mode::Triangles {
210 log::warn!(
211 "Skipping primitive '{}'[{}] for having mesh mode {:?}",
212 name,
213 prim_index,
214 g_primitive.mode()
215 );
216 continue;
217 }
218 let material_index = match g_primitive.material().index() {
219 Some(index) => index as u32,
220 None => {
221 log::warn!(
222 "Skipping primitive '{}'[{}] for having default material",
223 name,
224 prim_index
225 );
226 continue;
227 }
228 };
229
230 let reader = g_primitive.reader(|buffer| Some(&data_buffers[buffer.index()]));
231 let vertex_count = g_primitive.get(&gltf::Semantic::Positions).unwrap().count();
232
233 flattened_geos.push({
235 profiling::scope!("Read data");
236 let mut pre_vertices = vec![GltfVertex::default(); vertex_count];
237
238 for (v, pos) in pre_vertices
239 .iter_mut()
240 .zip(reader.read_positions().unwrap())
241 {
242 for component in pos {
243 assert!(component.is_finite());
244 }
245 v.position = pos;
246 }
247 if let Some(iter) = reader.read_tex_coords(0) {
248 for (v, tc) in pre_vertices.iter_mut().zip(iter.into_f32()) {
249 v.tex_coords = tc;
250 }
251 } else {
252 log::warn!("No tex coords in {name}");
253 }
254 if let Some(iter) = reader.read_normals() {
255 assert_eq!(
256 pre_vertices.len(),
257 iter.len(),
258 "geometry {name} doesn't have enough normals"
259 );
260 for (v, normal) in pre_vertices.iter_mut().zip(iter) {
261 v.normal = normal;
262 assert_ne!(encode_normal(normal), 0);
263 }
264 } else {
265 log::warn!("No normals in {name}");
266 }
267
268 match reader.read_indices() {
270 Some(read) => FlattenedGeometry(
271 read.into_u32()
272 .map(|i| pre_vertices[i as usize].clone())
273 .collect(),
274 ),
275 None => FlattenedGeometry(pre_vertices.into_boxed_slice()),
276 }
277 });
278
279 self.geometries.push(CookedGeometry {
280 name: Cow::Owned(name.as_bytes().to_owned()),
281 vertices: Cow::Borrowed(&[]),
282 indices: Cow::Borrowed(&[]),
283 transform,
284 material_index,
285 });
286 }
287 }
288
289 for child in g_node.children() {
290 self.populate_gltf(child, global_transform, data_buffers, flattened_geos);
291 }
292 }
293}
294
295#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
296pub enum FrontFace {
297 Clockwise,
298 #[default]
299 CounterClockwise,
300}
301
302#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
303pub struct Meta {
304 pub generate_tangents: bool,
305 pub front_face: FrontFace,
306}
307
308impl fmt::Display for Meta {
309 fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
310 Ok(()) }
312}
313
314#[derive(Debug)]
315struct Transfer {
316 stage: blade_graphics::Buffer,
317 dst: blade_graphics::Buffer,
318 size: u64,
319}
320
321#[derive(Debug)]
322struct BlasConstruct {
323 meshes: Vec<blade_graphics::AccelerationStructureMesh>,
324 scratch: blade_graphics::Buffer,
325 dst: blade_graphics::AccelerationStructure,
326}
327
328#[derive(Default)]
329struct PendingOperations {
330 transfers: Vec<Transfer>,
331 blas_constructs: Vec<BlasConstruct>,
332}
333
334enum TextureSource {
335 Path(String),
336 Embedded(
337 Option<choir::IdleTask>,
338 Arc<blade_asset::Cooker<super::texture::Baker>>,
339 ),
340}
341
342#[cfg(feature = "asset")]
343impl TextureReference<'_> {
344 fn complete(&mut self, sources: &slab::Slab<TextureSource>) {
345 match sources.get(self.source_index) {
346 Some(&TextureSource::Embedded(ref _task, ref sub_cooker)) => {
347 self.embedded_data = Cow::Owned(sub_cooker.extract_embedded());
348 }
349 Some(&TextureSource::Path(ref full)) => {
350 self.path = Cow::Owned(full.as_bytes().to_owned());
351 }
352 None => {}
353 }
354 }
355}
356
357pub struct Baker {
358 gpu_context: Arc<blade_graphics::Context>,
359 pending_operations: Mutex<PendingOperations>,
360 asset_textures: Arc<blade_asset::AssetManager<crate::texture::Baker>>,
362}
363
364impl Baker {
365 pub fn new(
366 gpu_context: &Arc<blade_graphics::Context>,
367 asset_textures: &Arc<blade_asset::AssetManager<crate::texture::Baker>>,
368 ) -> Self {
369 Self {
370 gpu_context: Arc::clone(gpu_context),
371 pending_operations: Mutex::new(PendingOperations::default()),
372 asset_textures: Arc::clone(asset_textures),
373 }
374 }
375
376 pub fn flush(
377 &self,
378 encoder: &mut blade_graphics::CommandEncoder,
379 temp_buffers: &mut Vec<blade_graphics::Buffer>,
380 ) {
381 let mut pending_ops = self.pending_operations.lock().unwrap();
382 if !pending_ops.transfers.is_empty() {
383 let mut pass = encoder.transfer("init models");
384 for transfer in pending_ops.transfers.drain(..) {
385 pass.copy_buffer_to_buffer(
386 transfer.stage.into(),
387 transfer.dst.into(),
388 transfer.size,
389 );
390 temp_buffers.push(transfer.stage);
391 }
392 }
393 if !pending_ops.blas_constructs.is_empty() {
394 let mut pass = encoder.acceleration_structure("BLAS");
395 for construct in pending_ops.blas_constructs.drain(..) {
396 pass.build_bottom_level(construct.dst, &construct.meshes, construct.scratch.into());
397 temp_buffers.push(construct.scratch);
398 }
399 }
400 }
401
402 #[cfg(feature = "asset")]
403 fn cook_texture(
404 &self,
405 texture: gltf::texture::Texture,
406 meta: super::texture::Meta,
407 parent_cooker: &blade_asset::Cooker<Baker>,
408 data_buffers: &[Vec<u8>],
409 ) -> TextureSource {
410 match texture.source().source() {
411 gltf::image::Source::View { view, mime_type } => {
412 let sub_cooker = Arc::new(blade_asset::Cooker::new_embedded());
413 let cooker = Arc::clone(&sub_cooker);
414 let baker = Arc::clone(&self.asset_textures.baker);
415 let buffer = &data_buffers[view.buffer().index()];
416 let data = buffer[view.offset()..view.offset() + view.length()].to_vec();
417 let extension = mime_type.split_once('/').unwrap().1.to_string();
418 let task =
419 self.asset_textures
420 .choir
421 .spawn("embedded cook")
422 .init(move |exe_ontext| {
423 blade_asset::Baker::cook(
424 baker.as_ref(),
425 &data,
426 &extension,
427 meta,
428 cooker,
429 &exe_ontext,
430 );
431 });
432 TextureSource::Embedded(Some(task), sub_cooker)
433 }
434 gltf::image::Source::Uri { uri, mime_type: _ } => {
435 let relative = if let Some(_rest) = uri.strip_prefix("data:") {
436 panic!("Data URL isn't supported for textures yet");
437 } else if let Some(rest) = uri.strip_prefix("file://") {
438 rest
439 } else if let Some(rest) = uri.strip_prefix("file:") {
440 rest
441 } else {
442 uri
443 };
444 let full = parent_cooker.base_path().join(relative);
445 if PRELOAD_TEXTURES {
446 self.asset_textures.load(&full, meta);
447 }
448 TextureSource::Path(full.to_str().unwrap().to_string())
449 }
450 }
451 }
452
453 fn serve_texture(
454 &self,
455 texture_ref: &TextureReference,
456 meta: super::texture::Meta,
457 exe_context: &choir::ExecutionContext,
458 ) -> Option<blade_asset::Handle<super::texture::Texture>> {
459 if !texture_ref.path.is_empty() {
460 let path_str = str::from_utf8(&texture_ref.path).unwrap();
461 let (handle, task) = self.asset_textures.load(path_str, meta);
462 exe_context.add_fork(&task);
463 Some(handle)
464 } else if !texture_ref.embedded_data.is_empty() {
465 let cooked = unsafe {
466 <super::texture::CookedImage<'_> as blade_asset::Flat>::read(
467 texture_ref.embedded_data.as_ptr(),
468 )
469 };
470 Some(
471 self.asset_textures
472 .load_cooked_inside_task(cooked, exe_context),
473 )
474 } else {
475 None
476 }
477 }
478}
479
480pub struct ProceduralGeometry {
482 pub name: String,
483 pub vertices: Vec<crate::Vertex>,
484 pub indices: Vec<u32>,
485 pub base_color_factor: [f32; 4],
486}
487
488impl Baker {
489 pub fn create_model(&self, name: &str, geometries: Vec<ProceduralGeometry>) -> Model {
491 assert!(!geometries.is_empty(), "Need at least one geometry");
492
493 let total_vertices: usize = geometries.iter().map(|g| g.vertices.len()).sum();
494 let total_vertex_size = (total_vertices * mem::size_of::<crate::Vertex>()) as u64;
495 let vertex_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
496 name: "proc vertex",
497 size: total_vertex_size,
498 memory: blade_graphics::Memory::Shared,
499 });
500
501 let total_indices: usize = geometries.iter().map(|g| g.indices.len()).sum();
502 let total_index_size = total_indices as u64 * 4
503 + geometries.len() as u64 * blade_graphics::limits::STORAGE_BUFFER_ALIGNMENT;
504 let index_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
505 name: "proc index",
506 size: total_index_size,
507 memory: blade_graphics::Memory::Shared,
508 });
509
510 let total_transform_size =
511 (geometries.len() * mem::size_of::<blade_graphics::Transform>()) as u64;
512 let transform_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
513 name: "proc transform",
514 size: total_transform_size,
515 memory: blade_graphics::Memory::Shared,
516 });
517
518 let mut start_vertex = 0u32;
519 let mut index_offset = 0u64;
520 let mut transform_offset = 0u64;
521 let mut model_geometries = Vec::with_capacity(geometries.len());
522 let mut materials = Vec::with_capacity(geometries.len());
523
524 for geo in geometries.iter() {
525 index_offset = crate::util::align_to(
526 index_offset,
527 blade_graphics::limits::STORAGE_BUFFER_ALIGNMENT,
528 );
529
530 unsafe {
531 ptr::copy_nonoverlapping(
532 geo.vertices.as_ptr(),
533 (vertex_buffer.data() as *mut crate::Vertex).add(start_vertex as usize),
534 geo.vertices.len(),
535 );
536 ptr::copy_nonoverlapping(
537 geo.indices.as_ptr(),
538 index_buffer.data().add(index_offset as usize) as *mut u32,
539 geo.indices.len(),
540 );
541 }
542 let transform = blade_graphics::IDENTITY_TRANSFORM;
543 unsafe {
544 ptr::copy_nonoverlapping(
545 ptr::from_ref(&transform).cast::<u8>(),
546 transform_buffer.data().add(transform_offset as usize),
547 mem::size_of::<blade_graphics::Transform>(),
548 );
549 }
550
551 let index_type = if geo.indices.is_empty() {
552 None
553 } else {
554 Some(blade_graphics::IndexType::U32)
555 };
556 let triangle_count = if geo.indices.is_empty() {
557 geo.vertices.len() as u32 / 3
558 } else {
559 geo.indices.len() as u32 / 3
560 };
561
562 let material_index = materials.len();
563 materials.push(Material {
564 base_color_texture: None,
565 base_color_factor: geo.base_color_factor,
566 normal_texture: None,
567 normal_scale: 0.0,
568 transparent: false,
569 });
570
571 model_geometries.push(Geometry {
572 name: geo.name.clone(),
573 vertex_range: start_vertex..start_vertex + geo.vertices.len() as u32,
574 index_offset,
575 index_type,
576 triangle_count,
577 transform,
578 material_index,
579 });
580
581 start_vertex += geo.vertices.len() as u32;
582 index_offset += geo.indices.len() as u64 * 4;
583 transform_offset += mem::size_of::<blade_graphics::Transform>() as u64;
584 }
585
586 Model {
587 name: name.to_string(),
588 winding: 1.0,
589 geometries: model_geometries,
590 materials,
591 vertex_buffer,
592 index_buffer,
593 transform_buffer,
594 acceleration_structure: blade_graphics::AccelerationStructure::default(),
595 }
596 }
597}
598
599impl blade_asset::Baker for Baker {
600 type Meta = Meta;
601 type Data<'a> = CookedModel<'a>;
602 type Output = Model;
603
604 fn cook(
605 &self,
606 source: &[u8],
607 extension: &str,
608 meta: Meta,
609 cooker: Arc<blade_asset::Cooker<Self>>,
610 exe_context: &choir::ExecutionContext,
611 ) {
612 match extension {
613 #[cfg(feature = "asset")]
614 "gltf" | "glb" => {
615 use base64::engine::{Engine as _, general_purpose::URL_SAFE as ENCODING_ENGINE};
616
617 let gltf::Gltf { document, mut blob } = gltf::Gltf::from_slice(source).unwrap();
618 let mut buffers = Vec::new();
620 for buffer in document.buffers() {
621 let mut data = match buffer.source() {
622 gltf::buffer::Source::Uri(uri) => {
623 if let Some(rest) = uri.strip_prefix("data:") {
624 let (_before, after) = rest.split_once(";base64,").unwrap();
625 ENCODING_ENGINE.decode(after).unwrap()
626 } else if let Some(rest) = uri.strip_prefix("file://") {
627 cooker.add_dependency(rest.as_ref())
628 } else if let Some(rest) = uri.strip_prefix("file:") {
629 cooker.add_dependency(rest.as_ref())
630 } else {
631 cooker.add_dependency(uri.as_ref())
632 }
633 }
634 gltf::buffer::Source::Bin => blob.take().unwrap(),
635 };
636 assert!(data.len() >= buffer.length());
637 while data.len() % 4 != 0 {
638 data.push(0);
639 }
640 buffers.push(data);
641 }
642
643 let mut sources = slab::Slab::new();
644 let mut model = CookedModel {
645 name: &[],
646 winding: match meta.front_face {
647 FrontFace::Clockwise => -1.0,
648 FrontFace::CounterClockwise => 1.0,
649 },
650 materials: Vec::new(),
651 geometries: Vec::new(),
652 };
653 for g_material in document.materials() {
654 let pbr = g_material.pbr_metallic_roughness();
655 model.materials.push(CookedMaterial {
656 base_color: TextureReference {
657 source_index: match pbr.base_color_texture() {
658 Some(info) => sources.insert(self.cook_texture(
659 info.texture(),
660 META_BASE_COLOR,
661 &cooker,
662 &buffers,
663 )),
664 None => !0,
665 },
666 ..Default::default()
667 },
668 base_color_factor: pbr.base_color_factor(),
669 normal: TextureReference {
670 source_index: match g_material.normal_texture() {
671 Some(info) => sources.insert(self.cook_texture(
672 info.texture(),
673 META_NORMAL,
674 &cooker,
675 &buffers,
676 )),
677 None => !0,
678 },
679 ..Default::default()
680 },
681 normal_scale: g_material.normal_texture().map_or(0.0, |info| info.scale()),
682 transparent: g_material.alpha_mode() != gltf::material::AlphaMode::Opaque,
683 });
684 }
685
686 let mut flattened_geos = Vec::new();
687 for g_scene in document.scenes() {
688 for g_node in g_scene.nodes() {
689 model.populate_gltf(
690 g_node,
691 glam::Mat4::IDENTITY,
692 &buffers,
693 &mut flattened_geos,
694 );
695 }
696 }
697
698 assert!(
699 !model.geometries.is_empty(),
700 "Empty models are not supported yet"
701 );
702 let model_shared = Arc::new(Mutex::new(model));
703 let model_clone = Arc::clone(&model_shared);
704 let gen_tangents = exe_context.choir().spawn("generate tangents").init_iter(
705 flattened_geos.into_iter().enumerate(),
706 move |_, (index, mut fg)| {
707 if meta.generate_tangents {
708 let ok = mikktspace::generate_tangents(&mut fg);
709 assert!(ok, "MikkTSpace failed");
710 }
711 let (indices, vertices) = fg.reconstruct_indices();
712 let mut model = model_clone.lock().unwrap();
713 let geo = &mut model.geometries[index];
714 geo.vertices = Cow::Owned(vertices);
715 geo.indices = Cow::Owned(indices);
716 },
717 );
718
719 let mut dependencies = vec![gen_tangents];
720 for (_, source) in sources.iter_mut() {
721 if let TextureSource::Embedded(ref mut task, _) = *source {
722 dependencies.push(task.take().unwrap())
723 }
724 }
725
726 let mut finish = exe_context.fork("finish").init(move |_| {
727 let mut model = Arc::into_inner(model_shared).unwrap().into_inner().unwrap();
728 for material in model.materials.iter_mut() {
729 material.base_color.complete(&sources);
730 material.normal.complete(&sources);
731 }
732 cooker.finish(model);
733 });
734 for dependency in dependencies {
735 finish.depend_on(&dependency);
736 }
737 }
738 other => panic!("Unknown model extension: {}", other),
739 }
740 }
741
742 fn serve(&self, model: CookedModel<'_>, exe_context: &choir::ExecutionContext) -> Self::Output {
743 let mut materials = Vec::with_capacity(model.materials.len());
744 for material in model.materials.iter() {
745 materials.push(Material {
746 base_color_texture: self.serve_texture(
747 &material.base_color,
748 META_BASE_COLOR,
749 exe_context,
750 ),
751 base_color_factor: material.base_color_factor,
752 normal_texture: self.serve_texture(&material.normal, META_NORMAL, exe_context),
753 normal_scale: material.normal_scale,
754 transparent: material.transparent,
755 });
756 }
757
758 let total_vertices = model
759 .geometries
760 .iter()
761 .map(|geo| geo.vertices.len())
762 .sum::<usize>();
763 let total_vertex_size = (total_vertices * mem::size_of::<crate::Vertex>()) as u64;
764 let vertex_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
765 name: "vertex",
766 size: total_vertex_size,
767 memory: blade_graphics::Memory::Device,
768 });
769 let vertex_stage = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
770 name: "vertex stage",
771 size: total_vertex_size,
772 memory: blade_graphics::Memory::Upload,
773 });
774
775 let total_indices = model
776 .geometries
777 .iter()
778 .map(|geo| geo.indices.len())
779 .sum::<usize>();
780 let total_index_size = total_indices as u64 * 4
781 + model.geometries.len() as u64 * blade_graphics::limits::STORAGE_BUFFER_ALIGNMENT;
782 let index_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
783 name: "index",
784 size: total_index_size,
785 memory: blade_graphics::Memory::Device,
786 });
787 let index_stage = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
788 name: "index stage",
789 size: total_index_size,
790 memory: blade_graphics::Memory::Upload,
791 });
792
793 let total_transform_size =
794 (model.geometries.len() * mem::size_of::<blade_graphics::Transform>()) as u64;
795 let transform_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
796 name: "transform",
797 size: total_transform_size,
798 memory: blade_graphics::Memory::Device,
799 });
800 let transform_stage = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
801 name: "transform stage",
802 size: total_transform_size,
803 memory: blade_graphics::Memory::Upload,
804 });
805
806 let mut meshes = Vec::with_capacity(model.geometries.len());
807 let vertex_stride = mem::size_of::<super::Vertex>() as u32;
808 let mut start_vertex = 0;
809 let mut index_offset = 0;
810 let mut transform_offset = 0;
811 let mut geometries = Vec::with_capacity(model.geometries.len());
812 for geometry in model.geometries.iter() {
813 index_offset = crate::util::align_to(
814 index_offset,
815 blade_graphics::limits::STORAGE_BUFFER_ALIGNMENT,
816 );
817 let material = &model.materials[geometry.material_index as usize];
818 unsafe {
819 ptr::copy_nonoverlapping(
820 geometry.vertices.as_ptr(),
821 (vertex_stage.data() as *mut crate::Vertex).add(start_vertex as usize),
822 geometry.vertices.len(),
823 );
824 ptr::copy_nonoverlapping(
825 geometry.indices.as_ptr(),
826 index_stage.data().add(index_offset as usize) as *mut u32,
827 geometry.indices.len(),
828 );
829 ptr::copy_nonoverlapping(
830 geometry.transform.as_ptr() as *const u8,
831 transform_stage.data().add(transform_offset as usize),
832 mem::size_of::<blade_graphics::Transform>(),
833 );
834 }
835 let index_type = if geometry.indices.is_empty() {
836 None
837 } else {
838 Some(blade_graphics::IndexType::U32)
839 };
840 let triangle_count = if geometry.indices.is_empty() {
841 geometry.vertices.len() as u32 / 3
842 } else {
843 geometry.indices.len() as u32 / 3
844 };
845 meshes.push(blade_graphics::AccelerationStructureMesh {
846 vertex_data: vertex_buffer.at(start_vertex as u64 * vertex_stride as u64),
847 vertex_format: blade_graphics::VertexFormat::F32Vec3,
848 vertex_stride,
849 vertex_count: geometry.vertices.len() as u32,
850 index_data: index_buffer.at(index_offset),
851 index_type,
852 triangle_count,
853 transform_data: transform_buffer.at(transform_offset), is_opaque: !material.transparent,
855 });
856 geometries.push(Geometry {
857 name: String::from_utf8_lossy(geometry.name.as_ref()).into_owned(),
858 vertex_range: start_vertex..start_vertex + geometry.vertices.len() as u32,
859 index_offset,
860 index_type,
861 triangle_count,
862 transform: geometry.transform.into(),
863 material_index: geometry.material_index as usize,
864 });
865 start_vertex += geometry.vertices.len() as u32;
866 index_offset += geometry.indices.len() as u64 * 4;
867 transform_offset += mem::size_of::<blade_graphics::Transform>() as u64;
868 }
869 assert_eq!(start_vertex as usize, total_vertices);
870 assert!(index_offset <= total_index_size);
871 assert_eq!(transform_offset, total_transform_size);
872
873 let ray_tracing_enabled = !self.gpu_context.capabilities().ray_query.is_empty();
874 let (acceleration_structure, scratch) = if ray_tracing_enabled {
875 let sizes = self
876 .gpu_context
877 .get_bottom_level_acceleration_structure_sizes(&meshes);
878 let acceleration_structure = self.gpu_context.create_acceleration_structure(
879 blade_graphics::AccelerationStructureDesc {
880 name: str::from_utf8(model.name).unwrap(),
881 ty: blade_graphics::AccelerationStructureType::BottomLevel,
882 size: sizes.data,
883 },
884 );
885 let scratch = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
886 name: "BLAS scratch",
887 size: sizes.scratch,
888 memory: blade_graphics::Memory::Device,
889 });
890 (acceleration_structure, Some(scratch))
891 } else {
892 (blade_graphics::AccelerationStructure::default(), None)
893 };
894
895 let mut pending_ops = self.pending_operations.lock().unwrap();
896 pending_ops.transfers.push(Transfer {
897 stage: vertex_stage,
898 dst: vertex_buffer,
899 size: total_vertex_size,
900 });
901 pending_ops.transfers.push(Transfer {
902 stage: index_stage,
903 dst: index_buffer,
904 size: total_index_size,
905 });
906 pending_ops.transfers.push(Transfer {
907 stage: transform_stage,
908 dst: transform_buffer,
909 size: total_transform_size,
910 });
911 if let Some(scratch) = scratch {
912 pending_ops.blas_constructs.push(BlasConstruct {
913 meshes,
914 scratch,
915 dst: acceleration_structure,
916 });
917 }
918
919 Model {
920 name: String::from_utf8_lossy(model.name).into_owned(),
921 winding: model.winding,
922 geometries,
923 materials,
924 vertex_buffer,
925 index_buffer,
926 transform_buffer,
927 acceleration_structure,
928 }
929 }
930
931 fn delete(&self, model: Self::Output) {
932 if model.acceleration_structure != blade_graphics::AccelerationStructure::default() {
933 self.gpu_context
934 .destroy_acceleration_structure(model.acceleration_structure);
935 }
936 self.gpu_context.destroy_buffer(model.vertex_buffer);
937 self.gpu_context.destroy_buffer(model.index_buffer);
938 self.gpu_context.destroy_buffer(model.transform_buffer);
939 }
940}