Skip to main content

blade_render/model/
mod.rs

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    //Note: "texpresso" doesn't know how to produce signed normalized
19    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
44//TODO: move out into a separate asset type
45pub 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    //Note: this isn't used for anything during deserialization
69    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                // Read the vertices into memory
234                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                    // Untangle from the index buffer
269                    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(()) //TODO
311    }
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    //TODO: change to asset materials
361    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
480/// Description of a procedural model geometry.
481pub 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    /// Create a model from procedural geometry data, bypassing the asset cooking pipeline.
490    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                // extract buffers
619                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), //TODO
854                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}