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, PartialEq, Eq, Hash)]
296pub enum FrontFace {
297 Clockwise,
298 CounterClockwise,
299}
300impl Default for FrontFace {
301 fn default() -> Self {
302 Self::CounterClockwise
303 }
304}
305
306#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
307pub struct Meta {
308 pub generate_tangents: bool,
309 pub front_face: FrontFace,
310}
311
312impl fmt::Display for Meta {
313 fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
314 Ok(()) }
316}
317
318#[derive(Debug)]
319struct Transfer {
320 stage: blade_graphics::Buffer,
321 dst: blade_graphics::Buffer,
322 size: u64,
323}
324
325#[derive(Debug)]
326struct BlasConstruct {
327 meshes: Vec<blade_graphics::AccelerationStructureMesh>,
328 scratch: blade_graphics::Buffer,
329 dst: blade_graphics::AccelerationStructure,
330}
331
332#[derive(Default)]
333struct PendingOperations {
334 transfers: Vec<Transfer>,
335 blas_constructs: Vec<BlasConstruct>,
336}
337
338enum TextureSource {
339 Path(String),
340 Embedded(
341 Option<choir::IdleTask>,
342 Arc<blade_asset::Cooker<super::texture::Baker>>,
343 ),
344}
345
346#[cfg(feature = "asset")]
347impl TextureReference<'_> {
348 fn complete(&mut self, sources: &slab::Slab<TextureSource>) {
349 match sources.get(self.source_index) {
350 Some(&TextureSource::Embedded(ref _task, ref sub_cooker)) => {
351 self.embedded_data = Cow::Owned(sub_cooker.extract_embedded());
352 }
353 Some(&TextureSource::Path(ref full)) => {
354 self.path = Cow::Owned(full.as_bytes().to_owned());
355 }
356 None => {}
357 }
358 }
359}
360
361pub struct Baker {
362 gpu_context: Arc<blade_graphics::Context>,
363 pending_operations: Mutex<PendingOperations>,
364 asset_textures: Arc<blade_asset::AssetManager<crate::texture::Baker>>,
366}
367
368impl Baker {
369 pub fn new(
370 gpu_context: &Arc<blade_graphics::Context>,
371 asset_textures: &Arc<blade_asset::AssetManager<crate::texture::Baker>>,
372 ) -> Self {
373 Self {
374 gpu_context: Arc::clone(gpu_context),
375 pending_operations: Mutex::new(PendingOperations::default()),
376 asset_textures: Arc::clone(asset_textures),
377 }
378 }
379
380 pub fn flush(
381 &self,
382 encoder: &mut blade_graphics::CommandEncoder,
383 temp_buffers: &mut Vec<blade_graphics::Buffer>,
384 ) {
385 let mut pending_ops = self.pending_operations.lock().unwrap();
386 if !pending_ops.transfers.is_empty() {
387 let mut pass = encoder.transfer("init models");
388 for transfer in pending_ops.transfers.drain(..) {
389 pass.copy_buffer_to_buffer(
390 transfer.stage.into(),
391 transfer.dst.into(),
392 transfer.size,
393 );
394 temp_buffers.push(transfer.stage);
395 }
396 }
397 if !pending_ops.blas_constructs.is_empty() {
398 let mut pass = encoder.acceleration_structure("BLAS");
399 for construct in pending_ops.blas_constructs.drain(..) {
400 pass.build_bottom_level(construct.dst, &construct.meshes, construct.scratch.into());
401 temp_buffers.push(construct.scratch);
402 }
403 }
404 }
405
406 #[cfg(feature = "asset")]
407 fn cook_texture(
408 &self,
409 texture: gltf::texture::Texture,
410 meta: super::texture::Meta,
411 parent_cooker: &blade_asset::Cooker<Baker>,
412 data_buffers: &[Vec<u8>],
413 ) -> TextureSource {
414 match texture.source().source() {
415 gltf::image::Source::View { view, mime_type } => {
416 let sub_cooker = Arc::new(blade_asset::Cooker::new_embedded());
417 let cooker = Arc::clone(&sub_cooker);
418 let baker = Arc::clone(&self.asset_textures.baker);
419 let buffer = &data_buffers[view.buffer().index()];
420 let data = buffer[view.offset()..view.offset() + view.length()].to_vec();
421 let extension = mime_type.split_once('/').unwrap().1.to_string();
422 let task =
423 self.asset_textures
424 .choir
425 .spawn("embedded cook")
426 .init(move |exe_ontext| {
427 blade_asset::Baker::cook(
428 baker.as_ref(),
429 &data,
430 &extension,
431 meta,
432 cooker,
433 &exe_ontext,
434 );
435 });
436 TextureSource::Embedded(Some(task), sub_cooker)
437 }
438 gltf::image::Source::Uri { uri, mime_type: _ } => {
439 let relative = if let Some(_rest) = uri.strip_prefix("data:") {
440 panic!("Data URL isn't supported for textures yet");
441 } else if let Some(rest) = uri.strip_prefix("file://") {
442 rest
443 } else if let Some(rest) = uri.strip_prefix("file:") {
444 rest
445 } else {
446 uri
447 };
448 let full = parent_cooker.base_path().join(relative);
449 if PRELOAD_TEXTURES {
450 self.asset_textures.load(&full, meta);
451 }
452 TextureSource::Path(full.to_str().unwrap().to_string())
453 }
454 }
455 }
456
457 fn serve_texture(
458 &self,
459 texture_ref: &TextureReference,
460 meta: super::texture::Meta,
461 exe_context: &choir::ExecutionContext,
462 ) -> Option<blade_asset::Handle<super::texture::Texture>> {
463 if !texture_ref.path.is_empty() {
464 let path_str = str::from_utf8(&texture_ref.path).unwrap();
465 let (handle, task) = self.asset_textures.load(path_str, meta);
466 exe_context.add_fork(&task);
467 Some(handle)
468 } else if !texture_ref.embedded_data.is_empty() {
469 let cooked = unsafe {
470 <super::texture::CookedImage<'_> as blade_asset::Flat>::read(
471 texture_ref.embedded_data.as_ptr(),
472 )
473 };
474 Some(
475 self.asset_textures
476 .load_cooked_inside_task(cooked, exe_context),
477 )
478 } else {
479 None
480 }
481 }
482}
483
484impl blade_asset::Baker for Baker {
485 type Meta = Meta;
486 type Data<'a> = CookedModel<'a>;
487 type Output = Model;
488
489 fn cook(
490 &self,
491 source: &[u8],
492 extension: &str,
493 meta: Meta,
494 cooker: Arc<blade_asset::Cooker<Self>>,
495 exe_context: &choir::ExecutionContext,
496 ) {
497 match extension {
498 #[cfg(feature = "asset")]
499 "gltf" | "glb" => {
500 use base64::engine::{general_purpose::URL_SAFE as ENCODING_ENGINE, Engine as _};
501
502 let gltf::Gltf { document, mut blob } = gltf::Gltf::from_slice(source).unwrap();
503 let mut buffers = Vec::new();
505 for buffer in document.buffers() {
506 let mut data = match buffer.source() {
507 gltf::buffer::Source::Uri(uri) => {
508 if let Some(rest) = uri.strip_prefix("data:") {
509 let (_before, after) = rest.split_once(";base64,").unwrap();
510 ENCODING_ENGINE.decode(after).unwrap()
511 } else if let Some(rest) = uri.strip_prefix("file://") {
512 cooker.add_dependency(rest.as_ref())
513 } else if let Some(rest) = uri.strip_prefix("file:") {
514 cooker.add_dependency(rest.as_ref())
515 } else {
516 cooker.add_dependency(uri.as_ref())
517 }
518 }
519 gltf::buffer::Source::Bin => blob.take().unwrap(),
520 };
521 assert!(data.len() >= buffer.length());
522 while data.len() % 4 != 0 {
523 data.push(0);
524 }
525 buffers.push(data);
526 }
527
528 let mut sources = slab::Slab::new();
529 let mut model = CookedModel {
530 name: &[],
531 winding: match meta.front_face {
532 FrontFace::Clockwise => -1.0,
533 FrontFace::CounterClockwise => 1.0,
534 },
535 materials: Vec::new(),
536 geometries: Vec::new(),
537 };
538 for g_material in document.materials() {
539 let pbr = g_material.pbr_metallic_roughness();
540 model.materials.push(CookedMaterial {
541 base_color: TextureReference {
542 source_index: match pbr.base_color_texture() {
543 Some(info) => sources.insert(self.cook_texture(
544 info.texture(),
545 META_BASE_COLOR,
546 &cooker,
547 &buffers,
548 )),
549 None => !0,
550 },
551 ..Default::default()
552 },
553 base_color_factor: pbr.base_color_factor(),
554 normal: TextureReference {
555 source_index: match g_material.normal_texture() {
556 Some(info) => sources.insert(self.cook_texture(
557 info.texture(),
558 META_NORMAL,
559 &cooker,
560 &buffers,
561 )),
562 None => !0,
563 },
564 ..Default::default()
565 },
566 normal_scale: g_material.normal_texture().map_or(0.0, |info| info.scale()),
567 transparent: g_material.alpha_mode() != gltf::material::AlphaMode::Opaque,
568 });
569 }
570
571 let mut flattened_geos = Vec::new();
572 for g_scene in document.scenes() {
573 for g_node in g_scene.nodes() {
574 model.populate_gltf(
575 g_node,
576 glam::Mat4::IDENTITY,
577 &buffers,
578 &mut flattened_geos,
579 );
580 }
581 }
582
583 assert!(
584 !model.geometries.is_empty(),
585 "Empty models are not supported yet"
586 );
587 let model_shared = Arc::new(Mutex::new(model));
588 let model_clone = Arc::clone(&model_shared);
589 let gen_tangents = exe_context.choir().spawn("generate tangents").init_iter(
590 flattened_geos.into_iter().enumerate(),
591 move |_, (index, mut fg)| {
592 if meta.generate_tangents {
593 let ok = mikktspace::generate_tangents(&mut fg);
594 assert!(ok, "MikkTSpace failed");
595 }
596 let (indices, vertices) = fg.reconstruct_indices();
597 let mut model = model_clone.lock().unwrap();
598 let geo = &mut model.geometries[index];
599 geo.vertices = Cow::Owned(vertices);
600 geo.indices = Cow::Owned(indices);
601 },
602 );
603
604 let mut dependencies = vec![gen_tangents];
605 for (_, source) in sources.iter_mut() {
606 if let TextureSource::Embedded(ref mut task, _) = *source {
607 dependencies.push(task.take().unwrap())
608 }
609 }
610
611 let mut finish = exe_context.fork("finish").init(move |_| {
612 let mut model = Arc::into_inner(model_shared).unwrap().into_inner().unwrap();
613 for material in model.materials.iter_mut() {
614 material.base_color.complete(&sources);
615 material.normal.complete(&sources);
616 }
617 cooker.finish(model);
618 });
619 for dependency in dependencies {
620 finish.depend_on(&dependency);
621 }
622 }
623 other => panic!("Unknown model extension: {}", other),
624 }
625 }
626
627 fn serve(&self, model: CookedModel<'_>, exe_context: &choir::ExecutionContext) -> Self::Output {
628 let mut materials = Vec::with_capacity(model.materials.len());
629 for material in model.materials.iter() {
630 materials.push(Material {
631 base_color_texture: self.serve_texture(
632 &material.base_color,
633 META_BASE_COLOR,
634 exe_context,
635 ),
636 base_color_factor: material.base_color_factor,
637 normal_texture: self.serve_texture(&material.normal, META_NORMAL, exe_context),
638 normal_scale: material.normal_scale,
639 transparent: material.transparent,
640 });
641 }
642
643 let total_vertices = model
644 .geometries
645 .iter()
646 .map(|geo| geo.vertices.len())
647 .sum::<usize>();
648 let total_vertex_size = (total_vertices * mem::size_of::<crate::Vertex>()) as u64;
649 let vertex_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
650 name: "vertex",
651 size: total_vertex_size,
652 memory: blade_graphics::Memory::Device,
653 });
654 let vertex_stage = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
655 name: "vertex stage",
656 size: total_vertex_size,
657 memory: blade_graphics::Memory::Upload,
658 });
659
660 let total_indices = model
661 .geometries
662 .iter()
663 .map(|geo| geo.indices.len())
664 .sum::<usize>();
665 let total_index_size = total_indices as u64 * 4;
666 let index_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
667 name: "index",
668 size: total_index_size,
669 memory: blade_graphics::Memory::Device,
670 });
671 let index_stage = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
672 name: "index stage",
673 size: total_index_size,
674 memory: blade_graphics::Memory::Upload,
675 });
676
677 let total_transform_size =
678 (model.geometries.len() * mem::size_of::<blade_graphics::Transform>()) as u64;
679 let transform_buffer = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
680 name: "transform",
681 size: total_transform_size,
682 memory: blade_graphics::Memory::Device,
683 });
684 let transform_stage = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
685 name: "transform stage",
686 size: total_transform_size,
687 memory: blade_graphics::Memory::Upload,
688 });
689
690 let mut meshes = Vec::with_capacity(model.geometries.len());
691 let vertex_stride = mem::size_of::<super::Vertex>() as u32;
692 let mut start_vertex = 0;
693 let mut index_offset = 0;
694 let mut transform_offset = 0;
695 let mut geometries = Vec::with_capacity(model.geometries.len());
696 for geometry in model.geometries.iter() {
697 let material = &model.materials[geometry.material_index as usize];
698 unsafe {
699 ptr::copy_nonoverlapping(
700 geometry.vertices.as_ptr(),
701 (vertex_stage.data() as *mut crate::Vertex).add(start_vertex as usize),
702 geometry.vertices.len(),
703 );
704 ptr::copy_nonoverlapping(
705 geometry.indices.as_ptr(),
706 index_stage.data().add(index_offset as usize) as *mut u32,
707 geometry.indices.len(),
708 );
709 ptr::copy_nonoverlapping(
710 geometry.transform.as_ptr() as *const u8,
711 transform_stage.data().add(transform_offset as usize),
712 mem::size_of::<blade_graphics::Transform>(),
713 );
714 }
715 let index_type = if geometry.indices.is_empty() {
716 None
717 } else {
718 Some(blade_graphics::IndexType::U32)
719 };
720 let triangle_count = if geometry.indices.is_empty() {
721 geometry.vertices.len() as u32 / 3
722 } else {
723 geometry.indices.len() as u32 / 3
724 };
725 meshes.push(blade_graphics::AccelerationStructureMesh {
726 vertex_data: vertex_buffer.at(start_vertex as u64 * vertex_stride as u64),
727 vertex_format: blade_graphics::VertexFormat::F32Vec3,
728 vertex_stride,
729 vertex_count: geometry.vertices.len() as u32,
730 index_data: index_buffer.at(index_offset),
731 index_type,
732 triangle_count,
733 transform_data: transform_buffer.at(transform_offset), is_opaque: !material.transparent,
735 });
736 geometries.push(Geometry {
737 name: String::from_utf8_lossy(geometry.name.as_ref()).into_owned(),
738 vertex_range: start_vertex..start_vertex + geometry.vertices.len() as u32,
739 index_offset,
740 index_type,
741 triangle_count,
742 transform: geometry.transform.into(),
743 material_index: geometry.material_index as usize,
744 });
745 start_vertex += geometry.vertices.len() as u32;
746 index_offset += geometry.indices.len() as u64 * 4;
747 transform_offset += mem::size_of::<blade_graphics::Transform>() as u64;
748 }
749 assert_eq!(start_vertex as usize, total_vertices);
750 assert_eq!(index_offset, total_index_size);
751 assert_eq!(transform_offset, total_transform_size);
752
753 let sizes = self
754 .gpu_context
755 .get_bottom_level_acceleration_structure_sizes(&meshes);
756 let acceleration_structure = self.gpu_context.create_acceleration_structure(
757 blade_graphics::AccelerationStructureDesc {
758 name: str::from_utf8(model.name).unwrap(),
759 ty: blade_graphics::AccelerationStructureType::BottomLevel,
760 size: sizes.data,
761 },
762 );
763 let scratch = self.gpu_context.create_buffer(blade_graphics::BufferDesc {
764 name: "BLAS scratch",
765 size: sizes.scratch,
766 memory: blade_graphics::Memory::Device,
767 });
768
769 let mut pending_ops = self.pending_operations.lock().unwrap();
770 pending_ops.transfers.push(Transfer {
771 stage: vertex_stage,
772 dst: vertex_buffer,
773 size: total_vertex_size,
774 });
775 pending_ops.transfers.push(Transfer {
776 stage: index_stage,
777 dst: index_buffer,
778 size: total_index_size,
779 });
780 pending_ops.transfers.push(Transfer {
781 stage: transform_stage,
782 dst: transform_buffer,
783 size: total_transform_size,
784 });
785 pending_ops.blas_constructs.push(BlasConstruct {
786 meshes,
787 scratch,
788 dst: acceleration_structure,
789 });
790
791 Model {
792 name: String::from_utf8_lossy(model.name).into_owned(),
793 winding: model.winding,
794 geometries,
795 materials,
796 vertex_buffer,
797 index_buffer,
798 transform_buffer,
799 acceleration_structure,
800 }
801 }
802
803 fn delete(&self, model: Self::Output) {
804 self.gpu_context
805 .destroy_acceleration_structure(model.acceleration_structure);
806 self.gpu_context.destroy_buffer(model.vertex_buffer);
807 self.gpu_context.destroy_buffer(model.index_buffer);
808 self.gpu_context.destroy_buffer(model.transform_buffer);
809 }
810}