diff --git a/Cargo.toml b/Cargo.toml index bfc4811c..5b23d1f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ log = "0.4" mint = "0.5" naga = { version = "0.14", features = ["wgsl-in", "span", "validate"] } profiling = "1" +slab = "0.4" strum = { version = "0.25", features = ["derive"] } web-sys = "0.3.60" diff --git a/blade-asset/src/flat.rs b/blade-asset/src/flat.rs index 4119347b..775f2cd4 100644 --- a/blade-asset/src/flat.rs +++ b/blade-asset/src/flat.rs @@ -42,6 +42,7 @@ macro_rules! impl_basic { impl_basic!(bool); impl_basic!(u32); impl_basic!(u64); +impl_basic!(usize); impl_basic!(f32); /* diff --git a/blade-asset/src/lib.rs b/blade-asset/src/lib.rs index 63a0e131..98cd88c6 100644 --- a/blade-asset/src/lib.rs +++ b/blade-asset/src/lib.rs @@ -94,6 +94,7 @@ impl Default for Slot { } } +#[derive(Default)] struct Inner { result: Vec, dependencies: Vec, @@ -143,6 +144,21 @@ impl Cooker { } } + /// Create a new container with no data, no path, and no hasher. + pub fn new_embedded() -> Self { + Self { + inner: Mutex::new(Inner::default()), + base_path: Default::default(), + _phantom: PhantomData, + } + } + + pub fn extract_embedded(&self) -> Vec { + let mut inner = self.inner.lock().unwrap(); + assert!(inner.dependencies.is_empty()); + mem::take(&mut inner.result) + } + /// Return the base path of the asset. pub fn base_path(&self) -> &Path { &self.base_path diff --git a/blade-graphics/Cargo.toml b/blade-graphics/Cargo.toml index 45a1859a..cb9739b6 100644 --- a/blade-graphics/Cargo.toml +++ b/blade-graphics/Cargo.toml @@ -32,7 +32,7 @@ ash-window = "0.12" gpu-alloc = "0.6" gpu-alloc-ash = "0.6" naga = { workspace = true, features = ["spv-out"] } -slab = "0.4" +slab = { workspace = true } [target.'cfg(any(gles, target_arch = "wasm32"))'.dependencies] # Version contains `glGetProgramResource` diff --git a/blade-render/Cargo.toml b/blade-render/Cargo.toml index 44cf9909..8442c16e 100644 --- a/blade-render/Cargo.toml +++ b/blade-render/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/kvark/blade" [features] default = ["asset"] -asset = ["gltf" , "base64", "exr", "mikktspace", "texpresso", "zune-core", "zune-jpeg", "zune-png", "zune-imageprocs"] +asset = ["gltf" , "base64", "exr", "mikktspace", "slab", "texpresso", "zune-core", "zune-jpeg", "zune-png", "zune-imageprocs"] [dependencies] base64 = { workspace = true, optional = true } @@ -28,6 +28,7 @@ log = { workspace = true } mikktspace = { package = "bevy_mikktspace", version = "0.10", optional = true } mint = { workspace = true } profiling = { workspace = true } +slab = { workspace = true, optional = true } strum = { workspace = true } texpresso = { version = "2.0", optional = true } #zune-core = { version = "0.2", optional = true } diff --git a/blade-render/src/model/mod.rs b/blade-render/src/model/mod.rs index 92cd7db5..b9d2a0c4 100644 --- a/blade-render/src/model/mod.rs +++ b/blade-render/src/model/mod.rs @@ -3,8 +3,10 @@ use std::{ collections::hash_map::{Entry, HashMap}, fmt, hash, mem, ops::Range, + path::PathBuf, ptr, str, sync::{Arc, Mutex}, + unimplemented, }; const PRELOAD_TEXTURES: bool = false; @@ -58,11 +60,19 @@ pub struct Model { pub acceleration_structure: blade_graphics::AccelerationStructure, } +#[derive(blade_macros::Flat, Default)] +struct TextureReference<'a> { + path: Cow<'a, [u8]>, + embedded_data: Cow<'a, [u8]>, + //Note: this isn't used for anything during deserialization + source_index: usize, +} + #[derive(blade_macros::Flat)] struct CookedMaterial<'a> { - base_color_path: Cow<'a, [u8]>, + base_color: TextureReference<'a>, base_color_factor: [f32; 4], - normal_path: Cow<'a, [u8]>, + normal: TextureReference<'a>, transparent: bool, } @@ -295,6 +305,28 @@ struct PendingOperations { blas_constructs: Vec, } +enum TextureSource { + Path(String, super::texture::Meta), + Embedded( + Option, + Arc>, + ), +} + +impl TextureReference<'_> { + fn complete(&mut self, sources: &slab::Slab) { + match sources.get(self.source_index) { + Some(&TextureSource::Embedded(_task, sub_cooker)) => { + self.embedded_data = Cow::Owned(sub_cooker.extract_embedded()); + } + Some(&TextureSource::Path(ref relative, meta)) => { + self.path = Cow::Owned(relative.as_bytes().to_owned()); + } + None => {} + } + } +} + pub struct Baker { gpu_context: Arc, pending_operations: Mutex, @@ -339,6 +371,54 @@ impl Baker { } } } + + fn add_texture( + &self, + texture: gltf::texture::Texture, + meta: super::texture::Meta, + data_buffers: &[Vec], + ) -> TextureSource { + match texture.source().source() { + gltf::image::Source::View { view, mime_type } => { + let sub_cooker = Arc::new(blade_asset::Cooker::new_embedded()); + let cooker = Arc::clone(&sub_cooker); + let baker = Arc::clone(&self.asset_textures.baker); + let buffer = &data_buffers[view.buffer().index()]; + let data = buffer[view.offset()..view.offset() + view.length()].to_vec(); + let (_, extension) = mime_type.split_once('/').unwrap(); + let task = self + .asset_textures + .choir + .spawn("embedded cook") + .init(move |ec| { + blade_asset::Baker::cook( + baker.as_ref(), + &data, + extension, + meta, + cooker, + ec, + ); + }); + TextureSource::Embedded(Some(task), sub_cooker) + } + gltf::image::Source::Uri { uri, mime_type: _ } => { + let relative = if let Some(_rest) = uri.strip_prefix("data:") { + panic!("Data URL isn't supported for textures yet"); + } else if let Some(rest) = uri.strip_prefix("file://") { + rest + } else if let Some(rest) = uri.strip_prefix("file:") { + rest + } else { + uri + }; + if PRELOAD_TEXTURES { + self.asset_textures.load(relative, meta); + } + TextureSource::Path(relative.to_string(), meta) + } + } + } } impl blade_asset::Baker for Baker { @@ -384,30 +464,8 @@ impl blade_asset::Baker for Baker { } buffers.push(data); } - let mut texture_paths = Vec::new(); - for texture in document.textures() { - let relative = match texture.source().source() { - gltf::image::Source::Uri { uri, .. } => { - if let Some(rest) = uri.strip_prefix("data:") { - let (_before, after) = rest.split_once(";base64,").unwrap(); - let _data = ENCODING_ENGINE.decode(after).unwrap(); - panic!("Data URL isn't supported here yet"); - } else if let Some(rest) = uri.strip_prefix("file://") { - rest - } else if let Some(rest) = uri.strip_prefix("file:") { - rest - } else { - uri - } - } - gltf::image::Source::View { .. } => { - panic!("Embedded images are not supported yet") - } - }; - let full = cooker.base_path().join(relative); - texture_paths.push(full.to_str().unwrap().to_string()); - } + let mut sources = slab::Slab::new(); let mut model = CookedModel { name: &[], materials: Vec::new(), @@ -416,30 +474,33 @@ impl blade_asset::Baker for Baker { for g_material in document.materials() { let pbr = g_material.pbr_metallic_roughness(); model.materials.push(CookedMaterial { - base_color_path: Cow::Owned(match pbr.base_color_texture() { - Some(info) => { - let path = &texture_paths[info.texture().index()]; - if PRELOAD_TEXTURES { - self.asset_textures.load(path, META_BASE_COLOR); - } - path.as_bytes().to_vec() - } - None => Vec::new(), - }), + base_color: TextureReference { + source_index: match pbr.base_color_texture() { + Some(info) => sources.insert(self.add_texture( + info.texture(), + META_BASE_COLOR, + &buffers, + )), + None => !0, + }, + ..Default::default() + }, base_color_factor: pbr.base_color_factor(), - normal_path: Cow::Owned(match g_material.normal_texture() { - Some(info) => { - let path = &texture_paths[info.texture().index()]; - if PRELOAD_TEXTURES { - self.asset_textures.load(path, META_BASE_COLOR); - } - path.as_bytes().to_vec() - } - None => Vec::new(), - }), + normal: TextureReference { + source_index: match pbr.base_color_texture() { + Some(info) => sources.insert(self.add_texture( + info.texture(), + META_NORMAL, + &buffers, + )), + None => !0, + }, + ..Default::default() + }, transparent: g_material.alpha_mode() != gltf::material::AlphaMode::Opaque, }); } + let mut flattened_geos = Vec::new(); for g_scene in document.scenes() { for g_node in g_scene.nodes() { @@ -476,11 +537,25 @@ impl blade_asset::Baker for Baker { geo.indices = Cow::Owned(indices); }, ); + + let mut dependencies = vec![gen_tangents]; + for (_, source) in sources.iter_mut() { + if let TextureSource::Embedded(ref mut task, _) = *source { + dependencies.push(task.take().unwrap()) + } + } + let mut finish = exe_context.fork("finish").init(move |_| { let model = Arc::into_inner(model_shared).unwrap().into_inner().unwrap(); + for material in model.materials.iter_mut() { + material.base_color.complete(&sources); + material.normal.complete(&sources); + } cooker.finish(model); }); - finish.depend_on(&gen_tangents); + for dependency in dependencies { + finish.depend_on(&dependency); + } } other => panic!("Unknown model extension: {}", other), }