diff --git a/DEVLOG.md b/DEVLOG.md index 21bb4cca..9e9fb7e5 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -7,6 +7,9 @@ previous tests. Mainly because of little things I had forgotten. Little bits of state that need to be updated to run the shaders. The most recent was that the size of the atlas needs to be updated on the GPU when the atlas changes. +I'm moving over tests from `renderling/scene/gltf_support.rs` to +`renderling/stage/gltf_support.rs` one at a time. + ## Thu Dec 7, 2023 Ongoing work to get GLTF files on-the-slab working. When this work is done GLTF diff --git a/crates/renderling-shader/src/array.rs b/crates/renderling-shader/src/array.rs index 35591901..5ba08080 100644 --- a/crates/renderling-shader/src/array.rs +++ b/crates/renderling-shader/src/array.rs @@ -27,15 +27,16 @@ impl Copy for Array {} impl core::fmt::Debug for Array { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct(if self.is_null() { - "Array (null)" + if self.is_null() { + f.write_fmt(format_args!("Array<{}>(null)", core::any::type_name::())) } else { - "Array" - }) - .field("index", &self.index) - .field("len", &self.len) - .field("_phantom", &self._phantom) - .finish() + f.write_fmt(format_args!( + "Array<{}>({}, {})", + core::any::type_name::(), + self.index, + self.len + )) + } } } diff --git a/crates/renderling-shader/src/id.rs b/crates/renderling-shader/src/id.rs index f7a2d5f4..6cc0537d 100644 --- a/crates/renderling-shader/src/id.rs +++ b/crates/renderling-shader/src/id.rs @@ -71,10 +71,11 @@ impl Default for Id { impl core::fmt::Debug for Id { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Id") - .field("type", &core::any::type_name::()) - .field("index", &self.0) - .finish() + f.write_fmt(format_args!( + "Id<{}>({})", + &core::any::type_name::(), + &self.0 + )) } } diff --git a/crates/renderling/src/renderer.rs b/crates/renderling/src/renderer.rs index 7f973d58..7ba5b142 100644 --- a/crates/renderling/src/renderer.rs +++ b/crates/renderling/src/renderer.rs @@ -5,8 +5,8 @@ use snafu::prelude::*; use std::{ops::Deref, sync::Arc}; use crate::{ - hdr::HdrSurface, CreateSurfaceFn, Graph, RenderTarget, Scene, SceneBuilder, TextureError, - UiDrawObject, UiScene, UiSceneBuilder, View, ViewMut, WgpuStateError, + hdr::HdrSurface, CreateSurfaceFn, Graph, RenderTarget, Scene, SceneBuilder, Stage, + TextureError, UiDrawObject, UiScene, UiSceneBuilder, View, ViewMut, WgpuStateError, }; #[derive(Debug, Snafu)] @@ -400,6 +400,11 @@ impl Renderling { self.graph.get_resource().unwrap().unwrap() } + pub fn new_stage(&self) -> Stage { + let (device, queue) = self.get_device_and_queue_owned(); + Stage::new(device, queue) + } + pub fn new_scene(&self) -> SceneBuilder { let (device, queue) = self.get_device_and_queue_owned(); SceneBuilder::new(device.0, queue.0) diff --git a/crates/renderling/src/scene/gltf_support.rs b/crates/renderling/src/scene/gltf_support.rs index f7165847..269517be 100644 --- a/crates/renderling/src/scene/gltf_support.rs +++ b/crates/renderling/src/scene/gltf_support.rs @@ -1206,37 +1206,6 @@ mod test { use crate::{camera, shader::pbr::PbrMaterial, LightingModel, RenderGraphConfig, Renderling}; - #[test] - fn simple_texture() { - let size = 100; - let mut r = - Renderling::headless(size, size).with_background_color(Vec3::splat(0.0).extend(1.0)); - let mut builder = r.new_scene(); - let _loader = builder - .gltf_load("../../gltf/gltfTutorial_013_SimpleTexture.gltf") - .unwrap(); - - let projection = camera::perspective(size as f32, size as f32); - let view = camera::look_at(Vec3::new(0.5, 0.5, 1.25), Vec3::new(0.5, 0.5, 0.0), Vec3::Y); - builder.set_camera(projection, view); - - // there are no lights in the scene and the material isn't marked as "unlit", so - // let's force it to be unlit. - let mut material = builder.materials.get(0).copied().unwrap(); - material.lighting_model = LightingModel::NO_LIGHTING; - builder.materials[0] = material; - - let scene = builder.build().unwrap(); - r.setup_render_graph(RenderGraphConfig { - scene: Some(scene), - with_screen_capture: true, - ..Default::default() - }); - - let img = r.render_image().unwrap(); - img_diff::assert_img_eq("gltf_simple_texture.png", img); - } - #[test] fn normal_mapping_brick_sphere() { let size = 600; diff --git a/crates/renderling/src/stage/gltf_support.rs b/crates/renderling/src/stage/gltf_support.rs index e4af6e78..4c912024 100644 --- a/crates/renderling/src/stage/gltf_support.rs +++ b/crates/renderling/src/stage/gltf_support.rs @@ -15,6 +15,9 @@ use snafu::{OptionExt, ResultExt, Snafu}; #[derive(Debug, Snafu)] pub enum StageGltfError { + #[snafu(display("{source}"))] + Gltf { source: gltf::Error }, + #[snafu(display("{source}"))] Atlas { source: crate::atlas::AtlasError }, @@ -42,6 +45,9 @@ pub enum StageGltfError { #[snafu(display("Missing sampler"))] MissingSampler, + #[snafu(display("Missing gltf camera at index {index}"))] + MissingCamera { index: usize }, + #[snafu(display("{source}"))] Slab { source: crate::slab::SlabError }, } @@ -52,6 +58,12 @@ impl From for StageGltfError { } } +impl From for StageGltfError { + fn from(source: gltf::Error) -> Self { + Self::Gltf { source } + } +} + pub fn get_vertex_count(primitive: &gltf::Primitive<'_>) -> u32 { if let Some(indices) = primitive.indices() { let count = indices.count() as u32; @@ -109,6 +121,15 @@ pub fn make_accessor(accessor: gltf::Accessor<'_>, buffers: &Array) } impl Stage { + pub fn load_gltf_document_from_path( + &self, + path: impl AsRef, + ) -> Result<(gltf::Document, GltfDocument), StageGltfError> { + let (document, buffers, images) = gltf::import(path)?; + let gpu_doc = self.load_gltf_document(&document, buffers, images)?; + Ok((document, gpu_doc)) + } + pub fn load_gltf_document( &self, document: &gltf::Document, @@ -527,10 +548,17 @@ impl Stage { Some(data.0.as_slice()) }); let indices = get_indices(buffer_data, primitive, indices); - let uvs: Vec = reader + let mut uvs: Vec = reader .read_tex_coords(0) .map(|coords| coords.into_f32().map(Vec2::from).collect::>()) .unwrap_or_else(|| vec![Vec2::ZERO; indices.len()]); + if uvs.len() != indices.len() { + let mut new_uvs = Vec::with_capacity(indices.len()); + for index in indices { + new_uvs.push(uvs[*index as usize]); + } + uvs = new_uvs; + } *uv_vec = Some(uvs); } // UNWRAP: safe because we just set it to `Some` @@ -913,6 +941,56 @@ impl Stage { }) } + /// Create a native camera for the gltf camera with the given index. + pub fn create_camera_from_gltf( + &self, + cpu_doc: &gltf::Document, + index: usize, + ) -> Result { + let gltf_camera = cpu_doc + .cameras() + .nth(index) + .context(MissingCameraSnafu { index })?; + let projection = match gltf_camera.projection() { + gltf::camera::Projection::Orthographic(o) => glam::Mat4::orthographic_rh( + -o.xmag(), + o.xmag(), + -o.ymag(), + o.ymag(), + o.znear(), + o.zfar(), + ), + gltf::camera::Projection::Perspective(p) => { + let fovy = p.yfov(); + let aspect = p.aspect_ratio().unwrap_or(1.0); + if let Some(zfar) = p.zfar() { + glam::Mat4::perspective_rh(fovy, aspect, p.znear(), zfar) + } else { + glam::Mat4::perspective_infinite_rh( + p.yfov(), + p.aspect_ratio().unwrap_or(1.0), + p.znear(), + ) + } + } + }; + let view = cpu_doc + .nodes() + .find_map(|node| { + if node.camera().map(|c| c.index()) == Some(index) { + Some(glam::Mat4::from_cols_array_2d(&node.transform().matrix()).inverse()) + } else { + None + } + }) + .unwrap_or_default(); + Ok(Camera { + projection, + view, + ..Default::default() + }) + } + // For now we have to keep the original document around to figure out // what to draw. fn draw_gltf_node_with<'a>( @@ -1305,4 +1383,53 @@ mod test { let img = r.render_image().unwrap(); img_diff::assert_img_eq("gltf_images.png", img); } + + #[test] + fn simple_texture() { + let size = 100; + let mut r = + Renderling::headless(size, size).with_background_color(Vec3::splat(0.0).extend(1.0)); + let (device, queue) = r.get_device_and_queue_owned(); + let stage = Stage::new(device.clone(), queue.clone()) + // There are no lights in the scene and the material isn't marked as "unlit", so + // let's force it to be unlit. + .with_lighting(false); + stage.configure_graph(&mut r, true); + let (cpu_doc, gpu_doc) = stage + .load_gltf_document_from_path("../../gltf/gltfTutorial_013_SimpleTexture.gltf") + .unwrap(); + + let position = Vec3::new(0.5, 0.5, 1.25); + let projection = crate::camera::perspective(size as f32, size as f32); + let view = crate::camera::look_at(position, Vec3::new(0.5, 0.5, 0.0), Vec3::Y); + let camera = stage.append(&Camera { + projection, + view, + position, + }); + let _unit_ids = stage.draw_gltf_scene(&gpu_doc, camera, cpu_doc.default_scene().unwrap()); + + let img = r.render_image().unwrap(); + img_diff::assert_img_eq("gltf_simple_texture.png", img); + } + + #[test] + fn normal_mapping_brick_sphere() { + let size = 600; + let mut r = + Renderling::headless(size, size).with_background_color(Vec3::splat(1.0).extend(1.0)); + let stage = r.new_stage(); + stage.configure_graph(&mut r, true); + let (cpu_doc, gpu_doc) = stage + .load_gltf_document_from_path("../../gltf/red_brick_03_1k.glb") + .unwrap(); + let camera = stage.create_camera_from_gltf(&cpu_doc, 0).unwrap(); + let camera_id = stage.append(&camera); + let _unit_ids = + stage.draw_gltf_scene(&gpu_doc, camera_id, cpu_doc.default_scene().unwrap()); + + let img = r.render_image().unwrap(); + println!("saving frame"); + img_diff::assert_img_eq("gltf_normal_mapping_brick_sphere.png", img); + } }