From cf3f70dbb99bf305362723329c03ea88f8f6b0cd Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 8 Mar 2024 11:04:38 -0500 Subject: [PATCH] resource, render: load in texture sampler from gltf and use them in the renderer --- examples/testbed/src/main.rs | 20 +++--- lyra-game/src/render/material.rs | 20 ++++-- lyra-game/src/render/texture.rs | 108 ++++++++++++++++++++++++++++- lyra-resource/src/gltf/material.rs | 79 +++++++++++++++++++-- lyra-resource/src/loader/image.rs | 16 ++--- lyra-resource/src/texture.rs | 67 +++++++++++++++++- 6 files changed, 276 insertions(+), 34 deletions(-) diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 19179f0..b33a539 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -93,21 +93,21 @@ async fn main() { //let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); //let antique_camera_model = resman.request::("assets/AntiqueCamera.glb").unwrap(); //let cube_model = resman.request::("assets/cube-texture-bin.glb").unwrap(); - let cube_gltf = resman.request::("assets/texture-sep/texture-sep.gltf").unwrap(); + /* let cube_gltf = resman.request::("assets/texture-sep/texture-sep.gltf").unwrap(); let crate_gltf = resman.request::("assets/crate/crate.gltf").unwrap(); - let separate_gltf = resman.request::("assets/pos-testing/separate-nodes-two-cubes.glb").unwrap(); - drop(resman); + let separate_gltf = resman.request::("assets/pos-testing/child-node-cubes.glb").unwrap(); */ + //drop(resman); - let cube_mesh = &cube_gltf.data_ref() + /* let cube_mesh = &cube_gltf.data_ref() .unwrap().meshes[0]; let crate_mesh = &crate_gltf.data_ref() .unwrap().meshes[0]; let separate_scene = &separate_gltf.data_ref() - .unwrap().scenes[0]; + .unwrap().scenes[0]; */ - /* let sponza_model = resman.request::("assets/sponza/Sponza.gltf").unwrap(); + let sponza_model = resman.request::("assets/sponza/Sponza.gltf").unwrap(); drop(resman); let sponza_scene = &sponza_model.data_ref() @@ -116,12 +116,12 @@ async fn main() { world.spawn(( sponza_scene.clone(), Transform::from_xyz(0.0, 0.0, 0.0), - )); */ + )); - world.spawn(( + /* world.spawn(( separate_scene.clone(), Transform::from_xyz(0.0, -5.0, -10.0), - )); + )); */ /* { let cube_tran = Transform::from_xyz(-5.9026427, -1.8953488, -10.0); @@ -146,7 +146,7 @@ async fn main() { specular: 1.3, }, light_tran, - cube_mesh.clone(), + //cube_mesh.clone(), )); } diff --git a/lyra-game/src/render/material.rs b/lyra-game/src/render/material.rs index b53149b..aa452d2 100755 --- a/lyra-game/src/render/material.rs +++ b/lyra-game/src/render/material.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use lyra_resource::{ResHandle, Texture}; + use super::texture::RenderTexture; pub struct MaterialSpecular { @@ -9,13 +11,18 @@ pub struct MaterialSpecular { pub color_texture: Option, } +fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Arc, i: &Option>) -> Option { + if let Some(tex) = i { + RenderTexture::from_resource(device, queue, bg_layout.clone(), tex, None).ok() + } else { + None + } +} + impl MaterialSpecular { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, value: &lyra_resource::gltf::Specular) -> Self { - let tex = value.texture.as_ref().and_then(|t| t.data_ref()) - .map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap()); - - let color_tex = value.color_texture.as_ref().and_then(|t| t.data_ref()) - .map(|i| RenderTexture::from_image(device, queue, bg_layout, &i.image, None).unwrap()); + let tex = texture_to_render(device, queue, &bg_layout, &value.texture); + let color_tex = texture_to_render(device, queue, &bg_layout, &value.color_texture); Self { factor: value.factor, @@ -39,8 +46,7 @@ pub struct Material { impl Material { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, value: &lyra_resource::gltf::Material) -> Self { - let diffuse_texture = value.base_color_texture.as_ref().and_then(|t| t.data_ref()) - .map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap()); + let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture); let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s)); diff --git a/lyra-game/src/render/texture.rs b/lyra-game/src/render/texture.rs index 0237a23..0022f07 100755 --- a/lyra-game/src/render/texture.rs +++ b/lyra-game/src/render/texture.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use image::GenericImageView; -use lyra_resource::{ResHandle, Texture}; +use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode}; use super::render_buffer::BindGroupPair; @@ -134,12 +134,101 @@ impl RenderTexture { }) } + pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, texture_res: &ResHandle, label: Option<&str>) -> anyhow::Result { + let texture_ref = texture_res.data_ref().unwrap(); + let img = texture_ref.image.data_ref().unwrap(); + + let rgba = img.to_rgba8(); + let dimensions = img.dimensions(); + + let size = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth_or_array_layers: 1, + }; + let texture = device.create_texture( + &wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + } + ); + + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &rgba, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0), + rows_per_image: std::num::NonZeroU32::new(dimensions.1), + }, + size, + ); + + // convert resource sampler into wgpu sampler + let sampler_desc = match &texture_ref.sampler { + Some(sampler) => { + let magf = res_filter_to_wgpu(sampler.mag_filter.unwrap_or(FilterMode::Linear)); + let minf = res_filter_to_wgpu(sampler.min_filter.unwrap_or(FilterMode::Nearest)); + let mipf = res_filter_to_wgpu(sampler.mipmap_filter.unwrap_or(FilterMode::Nearest)); + + let wrap_u = res_wrap_to_wgpu(sampler.wrap_u); + let wrap_v = res_wrap_to_wgpu(sampler.wrap_v); + let wrap_w = res_wrap_to_wgpu(sampler.wrap_w); + + wgpu::SamplerDescriptor { + address_mode_u: wrap_u, + address_mode_v: wrap_v, + address_mode_w: wrap_w, + mag_filter: magf, + min_filter: minf, + mipmap_filter: mipf, + ..Default::default() + } + } + None => wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }, + }; + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler( + &sampler_desc + ); + + let bgp = Self::create_bind_group_pair(device, bg_layout, &view, &sampler); + + Ok(Self { + inner_texture: texture, + view, + sampler, + bindgroup_pair: Some(bgp), + }) + } + /// Updates the texture on the gpu with the provided texture. /// /// # Panics /// Panics if `texture` is not loaded pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &ResHandle) { let texture = &texture.data_ref().unwrap().image; + let texture = &texture.data_ref().unwrap(); let rgba = texture.to_rgba8(); let dimensions = texture.dimensions(); let size = wgpu::Extent3d { @@ -215,4 +304,21 @@ impl RenderTexture { pub fn bind_group(&self) -> &wgpu::BindGroup { &self.bindgroup_pair.as_ref().unwrap().bindgroup } +} + +/// Convert [`lyra_resource::WrappingMode`] to [`wgpu::AddressMode`] +fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode { + match wmode { + WrappingMode::ClampToEdge => wgpu::AddressMode::ClampToEdge, + WrappingMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat, + WrappingMode::Repeat => wgpu::AddressMode::Repeat, + } +} + +/// Convert [`lyra_resource::FilterMode`] to [`wgpu::FilterMode`] +fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode { + match fmode { + FilterMode::Nearest => wgpu::FilterMode::Nearest, + FilterMode::Linear => wgpu::FilterMode::Linear, + } } \ No newline at end of file diff --git a/lyra-resource/src/gltf/material.rs b/lyra-resource/src/gltf/material.rs index 649086d..696ac00 100644 --- a/lyra-resource/src/gltf/material.rs +++ b/lyra-resource/src/gltf/material.rs @@ -1,6 +1,8 @@ use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; -use crate::{Texture, ResHandle, util}; +use gltf::texture::{MagFilter, MinFilter}; + +use crate::{util, FilterMode, Image, ResHandle, Texture, TextureSampler, WrappingMode}; use super::loader::GltfLoadContext; /// PBR metallic roughness @@ -248,15 +250,33 @@ impl Material { fn load_texture(context: &mut GltfLoadContext, texture_info: gltf::texture::Info<'_>) -> ResHandle { // TODO: texture_info.tex_coord() let tex = texture_info.texture(); + // TODO: tex.sampler() let img = tex.source(); let src = img.source(); let buf = Material::read_source(context, src).unwrap(); let buflen = buf.len(); - let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type(); + let mime_type = infer::get(&buf).expect("Failed to get image type").mime_type(); - context.resource_manager.load_bytes::(&uuid::Uuid::new_v4().to_string(), mime_type, - buf, 0, buflen).unwrap() + let tex_img = context.resource_manager.load_bytes::(&uuid::Uuid::new_v4().to_string(), mime_type, + buf, 0, buflen).unwrap(); + + let samp = tex.sampler(); + let samp = TextureSampler { + mag_filter: samp.mag_filter().map(FilterMode::from_mag_filter), + min_filter: samp.min_filter().map(FilterMode::from_min_filter), + mipmap_filter: samp.min_filter().and_then(FilterMode::mipmap_filter), + wrap_u: samp.wrap_s().into(), + wrap_v: samp.wrap_t().into(), + wrap_w: WrappingMode::ClampToEdge, + }; + + let handler = ResHandle::with_data("", Texture { + image: tex_img, + sampler: Some(samp), + }); + context.resource_manager.store_uuid(handler.clone()); + handler } /// Load the Material from a gltf::Material. @@ -296,4 +316,55 @@ impl Material { specular, } } +} + +impl From for FilterMode { + fn from(value: gltf::texture::MagFilter) -> Self { + match value { + gltf::texture::MagFilter::Nearest => Self::Nearest, + gltf::texture::MagFilter::Linear => Self::Linear, + } + } +} + +impl FilterMode { + /// Get the MinFilter mode and the mipmap filter mode from gltf MinFilter + pub fn from_min_filter(minf: MinFilter) -> FilterMode { + match minf { + MinFilter::Nearest => FilterMode::Nearest, + MinFilter::Linear => FilterMode::Linear, + MinFilter::NearestMipmapNearest => FilterMode::Nearest, + MinFilter::LinearMipmapNearest => FilterMode::Linear, + MinFilter::NearestMipmapLinear => FilterMode::Nearest, + MinFilter::LinearMipmapLinear => FilterMode::Linear, + } + } + + pub fn from_mag_filter(magf: MagFilter) -> FilterMode { + match magf { + MagFilter::Nearest => FilterMode::Nearest, + MagFilter::Linear => FilterMode::Linear, + } + } + + pub fn mipmap_filter(minf: MinFilter) -> Option { + match minf { + MinFilter::Nearest => None, + MinFilter::Linear => None, + MinFilter::NearestMipmapNearest => Some(FilterMode::Nearest), + MinFilter::LinearMipmapNearest => Some(FilterMode::Nearest), + MinFilter::NearestMipmapLinear => Some(FilterMode::Linear), + MinFilter::LinearMipmapLinear => Some(FilterMode::Linear), + } + } +} + +impl From for WrappingMode { + fn from(value: gltf::texture::WrappingMode) -> Self { + match value { + gltf::texture::WrappingMode::ClampToEdge => Self::ClampToEdge, + gltf::texture::WrappingMode::MirroredRepeat => Self::MirroredRepeat, + gltf::texture::WrappingMode::Repeat => Self::Repeat, + } + } } \ No newline at end of file diff --git a/lyra-resource/src/loader/image.rs b/lyra-resource/src/loader/image.rs index 4a020ac..d9d0314 100644 --- a/lyra-resource/src/loader/image.rs +++ b/lyra-resource/src/loader/image.rs @@ -3,7 +3,7 @@ use std::{fs::File, sync::Arc, io::Read}; use image::ImageError; use tracing::{debug, trace}; -use crate::{resource_manager::ResourceStorage, texture::Texture, resource::ResHandle, ResourceManager}; +use crate::{resource::ResHandle, resource_manager::ResourceStorage, Image, ResourceManager}; use super::{LoaderError, ResourceLoader}; @@ -13,7 +13,7 @@ impl From for LoaderError { } } -/// A struct that implements the `ResourceLoader` trait used for loading textures. +/// A struct that implements the `ResourceLoader` trait used for loading images. #[derive(Default)] pub struct ImageLoader; @@ -60,10 +60,8 @@ impl ResourceLoader for ImageLoader { ImageError::IoError(e) => LoaderError::IoError(e), _ => LoaderError::DecodingError(e.into()), })?; - let texture = Texture { - image, - }; - let res = ResHandle::with_data(path, texture); + let image = Image::from(image); + let res = ResHandle::with_data(path, image); Ok(Arc::new(res)) } @@ -76,10 +74,8 @@ impl ResourceLoader for ImageLoader { ImageError::IoError(e) => LoaderError::IoError(e), _ => LoaderError::DecodingError(e.into()), })?; - let texture = Texture { - image, - }; - let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), texture); + let image = Image::from(image); + let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), image); debug!("Finished loading image of {} bytes", length); diff --git a/lyra-resource/src/texture.rs b/lyra-resource/src/texture.rs index 5874d18..179b190 100644 --- a/lyra-resource/src/texture.rs +++ b/lyra-resource/src/texture.rs @@ -1,10 +1,73 @@ +use std::ops::{Deref, DerefMut}; + +//pub use gltf::texture::{MagFilter, MinFilter, WrappingMode}; use image::DynamicImage; +use crate::ResHandle; + +/// The filter mode of the sampler. +/// +/// This is used for minification, magnification, and mipmap filters +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum FilterMode { + Nearest, + Linear, +} + +/// The wrapping mode of the Texture coordinates +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum WrappingMode { + ClampToEdge, + MirroredRepeat, + Repeat, +} + +/// The descriptor of the sampler for a Texture. +#[derive(Clone)] +pub struct TextureSampler { + pub mag_filter: Option, + pub min_filter: Option, + pub mipmap_filter: Option, + pub wrap_u: WrappingMode, + pub wrap_v: WrappingMode, + pub wrap_w: WrappingMode, +} + +#[derive(Clone)] +pub struct Image(DynamicImage); + +impl Deref for Image { + type Target = DynamicImage; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Image { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for Image { + fn from(value: DynamicImage) -> Self { + Self(value) + } +} + #[derive(Clone)] pub struct Texture { - pub image: DynamicImage, + pub image: ResHandle, + pub sampler: Option, } impl Texture { - + /// Create a texture from an image. + pub fn from_image(image: ResHandle) -> Self { + Self { + image, + sampler: None, + } + } } \ No newline at end of file