kitgit

tirbofish/dropbear · commit

8d2b167b8aa058139bc3733c552ac95e20e709c4

sigh jarvis, run github actions

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-03-09 11:27

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index 839b008..81c5a3e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"]
 
diff --git a/crates/dropbear-engine/Cargo.toml b/crates/dropbear-engine/Cargo.toml
index 2fe5667..3f42a2c 100644
--- a/crates/dropbear-engine/Cargo.toml
+++ b/crates/dropbear-engine/Cargo.toml
@@ -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
diff --git a/crates/dropbear-engine/src/model.rs b/crates/dropbear-engine/src/model.rs
index d08d88e..0133352 100644
--- a/crates/dropbear-engine/src/model.rs
+++ b/crates/dropbear-engine/src/model.rs
@@ -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,
diff --git a/crates/dropbear-engine/src/sky.rs b/crates/dropbear-engine/src/sky.rs
index 97c8d87..333d178 100644
--- a/crates/dropbear-engine/src/sky.rs
+++ b/crates/dropbear-engine/src/sky.rs
@@ -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")]
diff --git a/crates/dropbear-engine/src/texture.rs b/crates/dropbear-engine/src/texture.rs
index b532f7b..707485e 100644
--- a/crates/dropbear-engine/src/texture.rs
+++ b/crates/dropbear-engine/src/texture.rs
@@ -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);
 
diff --git a/crates/eucalyptus-core/src/component.rs b/crates/eucalyptus-core/src/component.rs
index 904946a..c06bd98 100644
--- a/crates/eucalyptus-core/src/component.rs
+++ b/crates/eucalyptus-core/src/component.rs
@@ -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 =
diff --git a/crates/eucalyptus-core/src/ser/model.rs b/crates/eucalyptus-core/src/ser/model.rs
index a4135ff..56cf5b8 100644
--- a/crates/eucalyptus-core/src/ser/model.rs
+++ b/crates/eucalyptus-core/src/ser/model.rs
@@ -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();
 
diff --git a/crates/eucalyptus-core/src/states.rs b/crates/eucalyptus-core/src/states.rs
index 8a2f11e..8921bca 100644
--- a/crates/eucalyptus-core/src/states.rs
+++ b/crates/eucalyptus-core/src/states.rs
@@ -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>,
 }
diff --git a/crates/eucalyptus-editor/src/editor/asset_viewer.rs b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
index bd3069d..b39062c 100644
--- a/crates/eucalyptus-editor/src/editor/asset_viewer.rs
+++ b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
@@ -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());
diff --git a/crates/kino-ui/src/lib.rs b/crates/kino-ui/src/lib.rs
index c61347d..b55b4b6 100644
--- a/crates/kino-ui/src/lib.rs
+++ b/crates/kino-ui/src/lib.rs
@@ -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