tirbofish/dropbear · commit
8d2b167b8aa058139bc3733c552ac95e20e709c4
sigh
jarvis, run github actions
Signature present but could not be verified.
Unverified
@@ -92,7 +92,7 @@ float-derive = "0.1" rkyv = "0.8" [workspace.dependencies.image] -version = "0.24" +version = "0.25" default-features = false features = ["png", "jpeg", "hdr"] @@ -44,7 +44,7 @@ dashmap.workspace = true typetag.workspace = true postcard.workspace = true pollster.workspace = true -image.workspace = true +image = {workspace = true, features = ["serde"]} puffin.workspace = true bitflags.workspace = true puffin_http.workspace = true @@ -1024,7 +1024,21 @@ impl Model { puffin::profile_function!(label.unwrap_or("unlabelled model")); let mut registry = registry.write(); - let model_label = label.unwrap_or("No named model"); + let model_label = label + .map(ToString::to_string) + .or_else(|| { + optional_resref + .as_ref() + .and_then(ResourceReference::as_uri) + .and_then(|uri| uri.rsplit('/').next()) + .map(|name| { + name.rsplit_once('.') + .map(|(stem, _)| stem) + .unwrap_or(name) + .to_string() + }) + }) + .unwrap_or_else(|| "No named model".to_string()); let hash = { puffin::profile_scope!("hashing model"); let mut hasher = DefaultHasher::default(); @@ -1130,7 +1144,7 @@ impl Model { _ => TextureWrapMode::Clamp, }; let mut builder = TextureBuilder::new(&graphics.device) - .from_raw_pixels(graphics.clone(), tex.pixels.as_slice()) + .with_raw_pixels(graphics.clone(), tex.pixels.as_slice()) .size(tex.dimensions.0, tex.dimensions.1) .format(format) .wrap_mode(wrap) @@ -1283,7 +1297,7 @@ impl Model { }; let model = Model { - label: model_label.to_string(), + label: model_label, hash, path: model_path, meshes: gpu_meshes, @@ -156,14 +156,12 @@ impl HdrLoader { #[cfg(not(target_arch = "wasm32"))] let pixels = { - let mut pixels = vec![[0.0, 0.0, 0.0, 0.0]; meta.width as usize * meta.height as usize]; - hdr_decoder.read_image_transform( - |pix| { - let rgb = pix.to_hdr(); - [rgb.0[0], rgb.0[1], rgb.0[2], 1.0f32] - }, - &mut pixels[..], - )?; + let dec = image::DynamicImage::from_decoder(hdr_decoder)?; + let pixels: Vec<[f32; 4]> = dec + .into_rgba32f() + .pixels() + .map(|p| p.0) + .collect(); pixels }; #[cfg(target_arch = "wasm32")] @@ -6,6 +6,7 @@ use crate::utils::{ResourceReference, ToPotentialString}; use image::{DynamicImage, GenericImageView, RgbaImage}; use rkyv::Archive; use serde::{Deserialize, Serialize}; +use wgpu::{TextureAspect, TextureFormat, TextureUsages, TextureViewDescriptor, TextureViewDimension}; use crate::multisampling::{AntiAliasingMode}; /// Describes a texture, like an image of some sort. Can be a normal texture on a model or a viewport or depth texture. @@ -19,8 +20,11 @@ pub struct Texture { pub reference: Option<ResourceReference>, } +#[derive(serde::Serialize, serde::Deserialize)] pub struct TextureBuilder<'a> { - device: &'a wgpu::Device, + #[serde(skip)] + device: Option<&'a wgpu::Device>, + #[serde(skip)] graphics: Option<Arc<SharedGraphicsContext>>, width: u32, @@ -42,7 +46,7 @@ pub struct TextureBuilder<'a> { lod_min_clamp: f32, lod_max_clamp: f32, - view_descriptor: Option<wgpu::TextureViewDescriptor<'a>>, // doesnt support serde + view_descriptor: Option<SerTextureViewDescriptor>, label: Option<&'a str>, mime_type: Option<String>, @@ -50,19 +54,103 @@ pub struct TextureBuilder<'a> { source: TextureSource, } +#[derive(serde::Serialize, serde::Deserialize, Clone, Default)] +struct SerTextureViewDescriptor { + pub label: Option<String>, + pub format: Option<TextureFormat>, + pub dimension: Option<TextureViewDimension>, + pub usage: Option<TextureUsages>, + pub aspect: TextureAspect, + pub base_mip_level: u32, + pub mip_level_count: Option<u32>, + pub base_array_layer: u32, + pub array_layer_count: Option<u32>, +} + +impl<'a> From<wgpu::TextureViewDescriptor<'a>> for SerTextureViewDescriptor { + fn from(value: TextureViewDescriptor<'a>) -> Self { + Self { + label: value.label.map(|s| s.to_string()), + format: value.format, + dimension: value.dimension, + usage: value.usage, + aspect: value.aspect, + base_mip_level: value.base_mip_level, + mip_level_count: value.mip_level_count, + base_array_layer: value.base_array_layer, + array_layer_count: value.array_layer_count, + } + } +} + +impl<'a> From<SerTextureViewDescriptor> for TextureViewDescriptor<'a> { + fn from(value: SerTextureViewDescriptor) -> Self { + Self { + label: value.label.map(|v| Box::leak(v.into_boxed_str()) as &str), + format: value.format, + dimension: value.dimension, + usage: value.usage, + aspect: value.aspect, + base_mip_level: value.base_mip_level, + mip_level_count: value.mip_level_count, + base_array_layer: value.base_array_layer, + array_layer_count: value.array_layer_count, + } + } +} + +#[derive(Default, serde::Serialize, serde::Deserialize)] enum TextureSource { + #[default] Empty, Image { - image: DynamicImage, + image: Image, hash: u64, reference: ResourceReference, }, } +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Image { + width: u32, + height: u32, + pixel_data: Arc<[u8]>, +} + +impl Image { + fn from_dynamic(image: &DynamicImage) -> Self { + let rgba = image.to_rgba8(); + let (width, height) = rgba.dimensions(); + Self { + width, + height, + pixel_data: Arc::<[u8]>::from(rgba.into_raw()), + } + } + + fn to_dynamic(&self) -> DynamicImage { + if let Some(rgba) = RgbaImage::from_raw(self.width, self.height, self.pixel_data.to_vec()) { + DynamicImage::ImageRgba8(rgba) + } else { + DynamicImage::ImageRgba8(RgbaImage::from_pixel( + 1, + 1, + image::Rgba([255, 0, 255, 255]), + )) + } + } +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum TextureReference { + Resource(ResourceReference), + RGBAColour([f32; 4]), +} + impl<'a> TextureBuilder<'a> { pub fn new(device: &'a wgpu::Device) -> Self { Self { - device, + device: Some(device), graphics: None, width: 1, height: 1, @@ -220,7 +308,7 @@ impl<'a> TextureBuilder<'a> { }; self.source = TextureSource::Image { - image, + image: Image::from_dynamic(&image), hash, reference: ResourceReference::from_bytes(bytes), }; @@ -233,11 +321,7 @@ impl<'a> TextureBuilder<'a> { self } - pub fn from_bytes(self, graphics: Arc<SharedGraphicsContext>, bytes: &'a [u8]) -> Self { - self.with_bytes(graphics, bytes) - } - - pub fn from_raw_pixels(mut self, graphics: Arc<SharedGraphicsContext>, pixels: &'a [u8]) -> Self { + pub fn with_raw_pixels(mut self, graphics: Arc<SharedGraphicsContext>, pixels: &'a [u8]) -> Self { self.graphics = Some(graphics); let hash = AssetRegistry::hash_bytes(pixels); @@ -279,7 +363,7 @@ impl<'a> TextureBuilder<'a> { }; self.source = TextureSource::Image { - image, + image: Image::from_dynamic(&image), hash, reference: ResourceReference::from_bytes(pixels), }; @@ -308,13 +392,18 @@ impl<'a> TextureBuilder<'a> { } pub fn view_descriptor(mut self, desc: wgpu::TextureViewDescriptor<'a>) -> Self { - self.view_descriptor = Some(desc); + self.view_descriptor = Some(desc.into()); self } pub fn build(self) -> Texture { puffin::profile_function!(self.label.unwrap_or("TextureBuilder::build")); + let view_desc: Option<wgpu::TextureViewDescriptor<'_>> = self.view_descriptor.clone().and_then(|v| Some(v.into())); + let Some(device) = self.device else { + panic!("TextureBuilder::build() requires a device, and it should be provided to have this to exist. weird...") + }; + match &self.source { TextureSource::Image { image, hash, reference } => { let graphics = self @@ -323,7 +412,7 @@ impl<'a> TextureBuilder<'a> { .expect("with_data() requires graphics context"); let requested_dimensions = Some((self.width, self.height)).filter(|&d| d != (1, 1)); - let mut image = image.clone(); + let mut image = image.to_dynamic(); if let Some((width, height)) = requested_dimensions { if image.width() != width || image.height() != height { image = image.resize_exact(width, height, image::imageops::FilterType::Triangle); @@ -357,7 +446,7 @@ impl<'a> TextureBuilder<'a> { depth_or_array_layers: self.depth_or_array_layers, }; - let texture = self.device.create_texture(&wgpu::TextureDescriptor { + let texture = device.create_texture(&wgpu::TextureDescriptor { label: self.label, size, mip_level_count: self.mip_level_count, @@ -369,9 +458,9 @@ impl<'a> TextureBuilder<'a> { }); let view = texture.create_view( - &self.view_descriptor.clone().unwrap_or_default() + &view_desc.unwrap_or_default() ); - let sampler = self.device.create_sampler(&self.build_sampler_desc()); + let sampler = device.create_sampler(&self.build_sampler_desc()); Texture { label: self.label.map(|s| s.to_string()), @@ -479,7 +568,7 @@ impl<'a> TextureBuilder<'a> { reference: ResourceReference, ) -> Texture { let sampler_desc = self.build_sampler_desc(); - let view_descriptor = self.view_descriptor.clone().unwrap_or_default(); + let view_descriptor: wgpu::TextureViewDescriptor<'_> = self.view_descriptor.clone().and_then(|v| Some(v.into())).unwrap_or_default(); let view = texture.create_view(&view_descriptor); let sampler = graphics.device.create_sampler(&sampler_desc); @@ -9,7 +9,7 @@ use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::model::Model; use dropbear_engine::procedural::{ProcObjType, ProcedurallyGeneratedObject}; -use dropbear_engine::texture::{Texture, TextureBuilder}; +use dropbear_engine::texture::{Texture, TextureBuilder, TextureReference}; use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, UiBuilder}; use hecs::{Entity, World}; @@ -558,7 +558,7 @@ impl Component for MeshRenderer { graphics, buffer, Some(model_ref.clone()), - None, + Some(source_label.as_str()), ASSET_REGISTRY.clone(), ) .await @@ -588,7 +588,7 @@ impl Component for MeshRenderer { graphics.clone(), bytes, Some(ser.handle.clone()), - None, + Some(ser.label.as_str()), ASSET_REGISTRY.clone(), ) .await? @@ -618,18 +618,15 @@ impl Component for MeshRenderer { mat.texture_tag = m.texture_tag.clone(); mat.wrap_mode = m.wrap_mode; - let get_tex_handle = - async |resource: &Option<ResourceReference>| -> Option<anyhow::Result<Handle<Texture>>> { - if let Some(dif) = resource { - match &dif.ref_type { - ResourceReferenceType::None => { - None - } + let get_tex_handle = async |resource: &Option<TextureReference>| -> Option<anyhow::Result<Handle<Texture>>> { + match resource { + Some(TextureReference::Resource(dif)) => match &dif.ref_type { + ResourceReferenceType::None => None, ResourceReferenceType::File(_) => { let path = dif.resolve().ok()?; let bytes = std::fs::read(&path).ok()?; let mut texture = TextureBuilder::new(&graphics.device) - .from_bytes(graphics.clone(), bytes.as_slice()) + .with_bytes(graphics.clone(), bytes.as_slice()) .label(label.as_str()) .build(); texture.reference = Some(dif.clone()); @@ -638,7 +635,7 @@ impl Component for MeshRenderer { } ResourceReferenceType::Bytes(bytes) => { let texture = TextureBuilder::new(&graphics.device) - .from_bytes(graphics.clone(), bytes) + .with_bytes(graphics.clone(), bytes) .label(label.as_str()) .build(); let mut registry = ASSET_REGISTRY.write(); @@ -647,9 +644,18 @@ impl Component for MeshRenderer { ResourceReferenceType::ProcObj(_) => { Some(Err(anyhow::anyhow!("Using a ProcObj as a texture is not valid, for texture with label {}", label))) } + }, + Some(TextureReference::RGBAColour(rgba)) => { + let to_u8 = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8; + let mut registry = ASSET_REGISTRY.write(); + let handle = registry.solid_texture_rgba8( + graphics.clone(), + [to_u8(rgba[0]), to_u8(rgba[1]), to_u8(rgba[2]), to_u8(rgba[3])], + Some(Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix()), + ); + Some(Ok(handle)) } - } else { - None + None => None, } }; @@ -764,8 +770,8 @@ impl Component for MeshRenderer { } }; - let save_optional_reference = |reference: Option<ResourceReference>, context: &str| { - reference.map(|resource| save_reference(resource, context)) + let save_optional_texture_reference = |reference: Option<ResourceReference>, context: &str| { + reference.map(|resource| TextureReference::Resource(save_reference(resource, context))) }; let asset = ASSET_REGISTRY.read(); @@ -801,7 +807,7 @@ impl Component for MeshRenderer { .get_texture(mat.diffuse_texture) .and_then(|t| t.reference.clone()) }; - let diffuse_texture = save_optional_reference( + let diffuse_texture = save_optional_texture_reference( diffuse_texture, "mesh renderer diffuse texture", ); @@ -814,7 +820,7 @@ impl Component for MeshRenderer { mat.normal_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; - let normal_texture = save_optional_reference( + let normal_texture = save_optional_texture_reference( normal_texture, "mesh renderer normal texture", ); @@ -827,7 +833,7 @@ impl Component for MeshRenderer { mat.emissive_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; - let emissive_texture = save_optional_reference( + let emissive_texture = save_optional_texture_reference( emissive_texture, "mesh renderer emissive texture", ); @@ -840,7 +846,7 @@ impl Component for MeshRenderer { mat.occlusion_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; - let occlusion_texture = save_optional_reference( + let occlusion_texture = save_optional_texture_reference( occlusion_texture, "mesh renderer occlusion texture", ); @@ -854,7 +860,7 @@ impl Component for MeshRenderer { mat.metallic_roughness_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; - let metallic_roughness_texture = save_optional_reference( + let metallic_roughness_texture = save_optional_texture_reference( metallic_roughness_texture, "mesh renderer metallic-roughness texture", ); @@ -1287,7 +1293,14 @@ impl InspectableComponent for MeshRenderer { registry .list_textures() .into_iter() - .map(|(handle, label, reference)| { + .filter_map(|(handle, label, reference)| { + let is_file_reference = reference + .as_ref() + .is_some_and(|r| matches!(r.ref_type, ResourceReferenceType::File(_))); + if !is_file_reference { + return None; + } + let display = label .or_else(|| { reference.and_then(|r| { @@ -1301,7 +1314,7 @@ impl InspectableComponent for MeshRenderer { }) }) .unwrap_or_else(|| format!("Texture {:016x}", handle.id)); - (handle, display) + Some((handle, display)) }) .collect::<Vec<_>>() }; @@ -1587,27 +1600,6 @@ impl InspectableComponent for MeshRenderer { ui.end_row(); }); - ui.add_space(6.0); - ui.label(RichText::new("Tag").strong()); - Grid::new(format!("material_tag_{}", material_name)) - .num_columns(2) - .spacing([12.0, 6.0]) - .striped(true) - .show(ui, |ui| { - ui.label("Texture Tag"); - let mut texture_tag = - material.texture_tag.clone().unwrap_or_default(); - if ui.text_edit_singleline(&mut texture_tag).changed() - { - material.texture_tag = if texture_tag.trim().is_empty() { - None - } else { - Some(texture_tag) - }; - } - ui.end_row(); - }); - ui.add_space(8.0); ui.label(RichText::new("Textures").strong()); let texture_matches = @@ -26,24 +26,6 @@ pub struct EucalyptusModel { } impl EucalyptusModel { - fn runtime_hash(&self, source: &ResourceReference) -> u64 { - let mut hasher = DefaultHasher::default(); - source.hash(&mut hasher); - self.label.hash(&mut hasher); - self.meshes.len().hash(&mut hasher); - self.materials.len().hash(&mut hasher); - self.nodes.len().hash(&mut hasher); - - for mesh in &self.meshes { - mesh.name.hash(&mut hasher); - mesh.num_elements.hash(&mut hasher); - mesh.vertices.len().hash(&mut hasher); - mesh.material.hash(&mut hasher); - } - - hasher.finish() - } - /// Loads the [`EucalyptusModel`] as a [`Model`] by loading the buffers. pub fn load(&self, source: ResourceReference, graphics: Arc<SharedGraphicsContext>) -> Model { let materials = self @@ -80,6 +62,24 @@ impl EucalyptusModel { morph_deltas_buffer, } } + + fn runtime_hash(&self, source: &ResourceReference) -> u64 { + let mut hasher = DefaultHasher::default(); + source.hash(&mut hasher); + self.label.hash(&mut hasher); + self.meshes.len().hash(&mut hasher); + self.materials.len().hash(&mut hasher); + self.nodes.len().hash(&mut hasher); + + for mesh in &self.meshes { + mesh.name.hash(&mut hasher); + mesh.num_elements.hash(&mut hasher); + mesh.vertices.len().hash(&mut hasher); + mesh.material.hash(&mut hasher); + } + + hasher.finish() + } } impl From<Model> for EucalyptusModel { @@ -231,7 +231,7 @@ impl EucalyptusMaterial { let bytes = std::fs::read(path).ok()?; let label = format!("{}_{}", self.name, suffix); let mut texture = dropbear_engine::texture::TextureBuilder::new(&graphics.device) - .from_bytes(graphics.clone(), bytes.as_slice()) + .with_bytes(graphics.clone(), bytes.as_slice()) .label(label.as_str()) .build(); texture.reference = Some(reference.clone()); @@ -242,7 +242,7 @@ impl EucalyptusMaterial { ResourceReferenceType::Bytes(bytes) => { let label = format!("{}_{}", self.name, suffix); let texture = dropbear_engine::texture::TextureBuilder::new(&graphics.device) - .from_bytes(graphics.clone(), bytes) + .with_bytes(graphics.clone(), bytes) .label(label.as_str()) .build(); @@ -14,7 +14,7 @@ use dropbear_engine::entity::Transform; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::lighting::LightComponent; use dropbear_engine::model::AlphaMode; -use dropbear_engine::texture::TextureWrapMode; +use dropbear_engine::texture::{TextureReference, TextureWrapMode}; use dropbear_engine::utils::ResourceReference; use egui::{CollapsingHeader, TextEdit, Ui}; use hecs::{Entity, World}; @@ -464,7 +464,11 @@ pub struct SerializedMeshRenderer { #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct SerializedMaterialCustomisation { pub label: String, - pub diffuse_texture: Option<ResourceReference>, + pub diffuse_texture: Option<TextureReference>, + pub emissive_texture: Option<TextureReference>, + pub normal_texture: Option<TextureReference>, + pub occlusion_texture: Option<TextureReference>, + pub metallic_roughness_texture: Option<TextureReference>, pub tint: [f32; 4], pub emissive_factor: [f32; 3], pub metallic_factor: f32, @@ -477,8 +481,4 @@ pub struct SerializedMaterialCustomisation { pub uv_tiling: [f32; 2], pub texture_tag: Option<String>, pub wrap_mode: TextureWrapMode, - pub emissive_texture: Option<ResourceReference>, - pub normal_texture: Option<ResourceReference>, - pub occlusion_texture: Option<ResourceReference>, - pub metallic_roughness_texture: Option<ResourceReference>, } @@ -1309,7 +1309,7 @@ impl<'a> EditorTabViewer<'a> { } let mut texture = TextureBuilder::new(&graphics.device) - .from_bytes(graphics.clone(), bytes.as_slice()) + .with_bytes(graphics.clone(), bytes.as_slice()) .label(label.as_str()) .build(); texture.reference = Some(reference.clone()); @@ -256,6 +256,7 @@ impl KinoState { queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, ) { + #[cfg(feature = "trace")] puffin::profile_function!(); self.render_target_cache.tick(); @@ -270,6 +271,7 @@ impl KinoState { .collect::<Vec<_>>(); for target in targets { + #[cfg(feature = "trace")] puffin::profile_scope!("rendering target", format!("{:?}", target)); let mut geometry = self .batches