diff --git a/DEVLOG.md b/DEVLOG.md index a7f9013a..01ca915e 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,20 @@ # devlog +## Tue Dec 19, 2023 + +### Thoughts on GLTF +GLTF on-the-slab has been a boon to this project and I'm tempted to make it the main way we do +rendering. I just want to write this down somewhere so I don't forget. Currently when loading +a GLTF file we traverse the GLTF document and store the whole thing on the GPU's slab. Then +the user has to specify which nodes (or a scene) to draw, which traverses one more time, linking +the `RenderUnit`s to the primitives within the GLTF. I _think_ it might be cognitively easier +to have GLTF nodes somehow be the base unit of rendering ... but I also have plans for supporting +SDFs and I'm not sure how that all fits together. + +* [At least one other person is thinking about putting SDFs in GLTF using an extension](https://community.khronos.org/t/signed-distance-field-representation-of-geometry-extension/109575) + +Anyway - I'll keep going with the momentum I have and think about refactoring towards this in the future. + ## Mon Dec 18, 2023 ### Simple Texture GLTF Example diff --git a/crates/renderling-shader/src/gltf.rs b/crates/renderling-shader/src/gltf.rs index f2f15096..fa36286a 100644 --- a/crates/renderling-shader/src/gltf.rs +++ b/crates/renderling-shader/src/gltf.rs @@ -14,6 +14,21 @@ use crate::{ #[derive(Default, Clone, Copy, Slabbed)] pub struct GltfBuffer(pub Array); +#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfBufferView { + // Pointer to the parent buffer. + pub buffer: Id, + // The offset relative to the start of the parent buffer in bytes. + pub offset: u32, + // The length of the buffer view in bytes. + pub length: u32, + // The stride in bytes between vertex attributes or other interleavable data. + // + // When 0, data is assumed to be tightly packed. + pub stride: u32, +} + #[repr(u32)] #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[derive(Default, Clone, Copy, PartialEq)] @@ -113,17 +128,14 @@ pub struct GltfAccessor { /// For example, if the accessor describes a `Vec3` of F32s, then // the size is 3 * 4 = 12. pub size: u32, - pub buffer: Id, - // Returns the offset relative to the start of the parent buffer view in bytes. + // A point to the parent view this accessor reads from. // - // This will be 0 if the corresponding accessor is sparse. - pub view_offset: u32, - // Returns the offset relative to the start of the parent buffer view in bytes. + /// This may be Id::NONE if the corresponding accessor is sparse. + pub view: Id, + // The offset relative to the start of the parent buffer view in bytes. // - // This will be 0 if the corresponding accessor is sparse. + // This will be 0 if the corresponding accessor is sparse. pub offset: u32, - // The stride in bytes between vertex attributes or other interleavable data. - pub view_stride: u32, // The number of elements within the buffer view - not to be confused with the // number of bytes in the buffer view. pub count: u32, @@ -219,22 +231,24 @@ impl IncI16 { impl GltfAccessor { fn slab_index_and_byte_offset(&self, element_index: usize, slab: &[u32]) -> (usize, usize) { crate::println!("index: {element_index:?}"); - let buffer = slab.read(self.buffer); + let view = slab.read(self.view); + crate::println!("view: {view:?}"); + let buffer = slab.read(view.buffer); crate::println!("buffer: {:?}", buffer); let buffer_start = buffer.0.starting_index(); crate::println!("buffer_start: {buffer_start:?}"); let buffer_start_bytes = buffer_start * 4; crate::println!("buffer_start_bytes: {buffer_start_bytes:?}"); - let stride = if self.size > self.view_stride { + let stride = if self.size > view.stride { self.size } else { - self.view_stride + view.stride } as usize; let byte_offset = buffer_start_bytes - + self.view_offset as usize + + view.offset as usize + self.offset as usize + element_index as usize * stride; - crate::println!("byte_offset: buffer_start_bytes({buffer_start_bytes}) + view_offset({view_offset}) + accessor.offset({offset}) + element_index({element_index}) * stride({stride}) = {byte_offset:?}", view_offset = self.view_offset, offset = self.offset); + crate::println!("byte_offset: buffer_start_bytes({buffer_start_bytes}) + view_offset({view_offset}) + accessor.offset({offset}) + element_index({element_index}) * stride({stride}) = {byte_offset:?}", view_offset = view.offset, offset = self.offset); let slab_index = byte_offset / 4; crate::println!("slab_index: {slab_index:?}"); let relative_byte_offset = byte_offset % 4; @@ -1054,15 +1068,6 @@ pub struct GltfScene { pub nodes: Array>, } -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Default, Clone, Copy, Slabbed)] -pub struct GltfBufferView { - pub buffer: Id, - pub offset: u32, - pub length: u32, - pub stride: u32, -} - /// A document of Gltf data. /// /// This tells where certain parts of the Gltf document are stored in the [`Stage`]'s slab. @@ -1083,36 +1088,3 @@ pub struct GltfDocument { pub textures: Array, pub views: Array, } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn indices_accessor_sanity() { - // Taken from the indices accessor in the "simple meshes" gltf sample, - // but with the buffer changed to match where we write it here. - let buffer_id = Id::new(20); - let accessor = GltfAccessor { - size: 2, - buffer: buffer_id, - view_offset: 0, - offset: 0, - view_stride: 0, - count: 3, - data_type: DataType::U16, - dimensions: Dimensions::Scalar, - normalized: false, - }; - let buffer = GltfBuffer(Array::new(0, 11)); - let mut slab: [u32; 22] = [ - 65536, 2, 0, 0, 0, 1065353216, 0, 0, 0, 1065353216, 0, 0, 0, 1065353216, 0, 0, - 1065353216, 0, 0, 1065353216, 0, 0, - ]; - slab.write(&buffer, buffer_id.index()); - let i0 = accessor.get_u32(0, &slab); - let i1 = accessor.get_u32(1, &slab); - let i2 = accessor.get_u32(2, &slab); - assert_eq!([0, 1, 2], [i0, i1, i2]); - } -} diff --git a/crates/renderling-shader/src/stage/light.rs b/crates/renderling-shader/src/stage/light.rs index bf4fb05d..e4961040 100644 --- a/crates/renderling-shader/src/stage/light.rs +++ b/crates/renderling-shader/src/stage/light.rs @@ -135,7 +135,7 @@ impl Slabbed for LightStyle { } } -/// A type-erased light Id that is used as a slab pointer to any light type. +/// A type-erased linked-list-of-lights that is used as a slab pointer to any light type. #[repr(C)] #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[derive(Copy, Clone, PartialEq, Slabbed)] @@ -144,6 +144,8 @@ pub struct Light { pub light_type: LightStyle, // The index of the light in the slab pub index: u32, + //// The id of the next light + //pub next: Id, } impl Default for Light { @@ -151,6 +153,7 @@ impl Default for Light { Self { light_type: LightStyle::Directional, index: Id::<()>::NONE.inner(), + //next: Id::NONE, } } } @@ -160,6 +163,7 @@ impl From> for Light { Self { light_type: LightStyle::Directional, index: id.inner(), + //next: Id::NONE, } } } @@ -169,6 +173,7 @@ impl From> for Light { Self { light_type: LightStyle::Spot, index: id.inner(), + //next: Id::NONE, } } } @@ -178,6 +183,7 @@ impl From> for Light { Self { light_type: LightStyle::Point, index: id.inner(), + //next: Id::NONE, } } } diff --git a/crates/renderling/src/linkage/stage-new_stage_vertex.spv b/crates/renderling/src/linkage/stage-new_stage_vertex.spv index ba11616f..61592a8d 100644 Binary files a/crates/renderling/src/linkage/stage-new_stage_vertex.spv and b/crates/renderling/src/linkage/stage-new_stage_vertex.spv differ diff --git a/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv b/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv index e9238444..eb8430f4 100644 Binary files a/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv and b/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv differ diff --git a/crates/renderling/src/scene/gltf_support.rs b/crates/renderling/src/scene/gltf_support.rs index 125a1cd3..32239742 100644 --- a/crates/renderling/src/scene/gltf_support.rs +++ b/crates/renderling/src/scene/gltf_support.rs @@ -1206,27 +1206,27 @@ mod test { use crate::{camera, shader::pbr::PbrMaterial, LightingModel, RenderGraphConfig, Renderling}; - #[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 mut builder = r.new_scene(); - let loader = builder.gltf_load("../../gltf/red_brick_03_1k.glb").unwrap(); - let (projection, view) = loader.cameras.get(0).copied().unwrap(); - builder.set_camera(projection, view); - - 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(); - println!("saving frame"); - img_diff::assert_img_eq("gltf_normal_mapping_brick_sphere.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 mut builder = r.new_scene(); + // let loader = builder.gltf_load("../../gltf/red_brick_03_1k.glb").unwrap(); + // let (projection, view) = loader.cameras.get(0).copied().unwrap(); + // builder.set_camera(projection, view); + + // 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(); + // println!("saving frame"); + // img_diff::assert_img_eq("gltf_normal_mapping_brick_sphere.png", img); + //} #[test] // Tests that we can reuse the same builder for multiple loaders, building diff --git a/crates/renderling/src/stage/gltf_support.rs b/crates/renderling/src/stage/gltf_support.rs index b76fb6a9..e927a93b 100644 --- a/crates/renderling/src/stage/gltf_support.rs +++ b/crates/renderling/src/stage/gltf_support.rs @@ -81,15 +81,12 @@ pub fn get_vertex_count(primitive: &gltf::Primitive<'_>) -> u32 { } } -pub fn make_accessor(accessor: gltf::Accessor<'_>, buffers: &Array) -> GltfAccessor { +pub fn make_accessor(accessor: gltf::Accessor<'_>, views: &Array) -> GltfAccessor { let size = accessor.size() as u32; let buffer_view = accessor.view().unwrap(); - let buffer_index = buffer_view.buffer().index(); - let buffer = buffers.at(buffer_index); let offset = accessor.offset() as u32; let count = accessor.count() as u32; - let view_offset = buffer_view.offset() as u32; - let view_stride = buffer_view.stride().unwrap_or(0) as u32; + let view = views.at(buffer_view.index()); let component_type = match accessor.data_type() { gltf::accessor::DataType::I8 => DataType::I8, gltf::accessor::DataType::U8 => DataType::U8, @@ -110,11 +107,9 @@ pub fn make_accessor(accessor: gltf::Accessor<'_>, buffers: &Array) let normalized = accessor.normalized(); GltfAccessor { size, + view, count, - buffer, - view_offset, offset, - view_stride, data_type: component_type, dimensions, normalized, @@ -167,7 +162,7 @@ impl Stage { .accessors() .enumerate() .map(|(i, accessor)| { - let a = make_accessor(accessor, &buffers); + let a = make_accessor(accessor, &views); log::trace!(" accessor {i}: {a:#?}",); a }) @@ -601,13 +596,17 @@ impl Stage { .collect::>(); let normals_array = self.append_array(&normals); let buffer = GltfBuffer(normals_array.into_u32_array()); - let buffer_id = self.append(&buffer); + let buffer = self.append(&buffer); + let view = self.append(&GltfBufferView { + buffer, + offset: 0, + length: normals.len() as u32 * 3 * 4, // 3 components * 4 bytes each + stride: 12, + }); let accessor = GltfAccessor { size: 12, - buffer: buffer_id, - view_offset: 0, + view, offset: 0, - view_stride: 12, count: normals.len() as u32, data_type: DataType::F32, dimensions: Dimensions::Vec3, @@ -653,13 +652,17 @@ impl Stage { .collect::>(); let tangents_array = self.append_array(&tangents); let buffer = GltfBuffer(tangents_array.into_u32_array()); - let buffer_id = self.append(&buffer); + let buffer = self.append(&buffer); + let view = self.append(&GltfBufferView { + buffer, + offset: 0, + length: tangents.len() as u32 * 4 * 4, // 4 components * 4 bytes each + stride: 16, + }); let accessor = GltfAccessor { size: 16, - buffer: buffer_id, - view_offset: 0, + view, offset: 0, - view_stride: 16, count: tangents.len() as u32, data_type: DataType::F32, dimensions: Dimensions::Vec4, @@ -1008,6 +1011,36 @@ impl Stage { node: gltf::Node<'a>, parents: Vec>, ) -> Vec> { + if let Some(_light) = node.light() { + // TODO: Support transforming lights based on node transforms + ////let light = gpu_doc.lights.at(light.index()); + //let t = Mat4::from_cols_array_2d(&node.transform().matrix()); + //let position = t.transform_point3(Vec3::ZERO); + + //let light_index = light.index(); + //let color = Vec3::from(light.color()); + //let range = light.range().unwrap_or(f32::MAX); + //let intensity = light.intensity(); + //match light.kind() { + // gltf::khr_lights_punctual::Kind::Directional => GltfLightKind::Directional, + // gltf::khr_lights_punctual::Kind::Point => GltfLightKind::Point, + // gltf::khr_lights_punctual::Kind::Spot { + // inner_cone_angle, + // outer_cone_angle, + // } => GltfLightKind::Spot { + // inner_cone_angle, + // outer_cone_angle, + // }, + //}; + + //let transform = self.append(&transform); + //let render_unit = RenderUnit { + // light, + // transform, + // ..Default::default() + //}; + //return vec![self.draw_unit(&render_unit)]; + } let mut units = if let Some(mesh) = node.mesh() { let primitives = mesh.primitives(); let mesh = gpu_doc.meshes.at(mesh.index()); @@ -1131,14 +1164,21 @@ mod test { let buffer_index = data.write_slice(&u32buffer, 0); assert_eq!(2, buffer_index); let buffer = GltfBuffer(Array::new(0, buffer_index as u32)); - let _ = data.write(&buffer, buffer_index); + let view_index = data.write(&buffer, buffer_index); + let _ = data.write( + &GltfBufferView { + buffer: Id::from(buffer_index), + offset: 0, + length: 4 * 2, // 4 elements * 2 bytes each + stride: 2, + }, + view_index, + ); let accessor = GltfAccessor { size: 2, count: 3, - buffer: Id::from(buffer_index), - view_offset: 0, + view: Id::from(view_index), offset: 0, - view_stride: 0, data_type: DataType::U16, dimensions: Dimensions::Scalar, normalized: false, @@ -1193,15 +1233,6 @@ mod test { let draws = stage.get_draws(); let slab = &data; - for i in 0..gpu_doc.accessors.len() { - let accessor = slab.read(gpu_doc.accessors.at(i)); - println!("accessor {i}: {accessor:#?}", i = i, accessor = accessor); - let buffer = slab.read(accessor.buffer); - println!("buffer: {buffer:#?}"); - let buffer_data = slab.read_vec(buffer.0); - println!("buffer_data: {buffer_data:#?}"); - } - let indices = draws .iter() .map(|draw| { @@ -1398,23 +1429,132 @@ mod test { 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().with_lighting(true).with_bloom(true); - // 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); - //} + #[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().with_lighting(true).with_bloom(true); + 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(); + img_diff::assert_img_eq("gltf_normal_mapping_brick_sphere.png", img); + } + + #[test] + // Demonstrates how to generate a mesh primitive on the CPU. + fn generate_gltf_cmy_tri() { + 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); + + // buffers + let positions = + stage.append_array(&[[0.0, 0.0, 0.5], [0.0, 100.0, 0.5], [100.0, 0.0, 0.5]]); + let positions_buffer = GltfBuffer(positions.into_u32_array()); + let cyan = [0.0, 1.0, 1.0, 1.0]; + let magenta = [1.0, 0.0, 1.0, 1.0]; + let yellow = [1.0, 1.0, 0.0, 1.0]; + let colors = stage.append_array(&[cyan, magenta, yellow]); + let colors_buffer = GltfBuffer(colors.into_u32_array()); + let buffers = stage.append_array(&[positions_buffer, colors_buffer]); + + // views + let positions_view = GltfBufferView { + buffer: buffers.at(0), + offset: 0, + length: 3 * 3 * 4, // 3 vertices * 3 components * 4 bytes each + stride: 3 * 4, + }; + let colors_view = GltfBufferView { + buffer: buffers.at(1), + offset: 0, + length: 3 * 4 * 4, // 3 vertices * 4 components * 4 bytes each + stride: 4 * 4, + }; + let views = stage.append_array(&[positions_view, colors_view]); + + // accessors + let positions_accessor = GltfAccessor { + size: 3 * 4, // 3 components * 4 bytes each + view: views.at(0), + offset: 0, + count: 3, + data_type: DataType::F32, + dimensions: Dimensions::Vec3, + normalized: false, + }; + let colors_accessor = GltfAccessor { + size: 4 * 4, + view: views.at(1), + offset: 0, + count: 3, + data_type: DataType::F32, + dimensions: Dimensions::Vec4, + normalized: false, + }; + let accessors = stage.append_array(&[positions_accessor, colors_accessor]); + + // meshes + let primitive = GltfPrimitive { + vertex_count: 3, + positions: accessors.at(0), + colors: accessors.at(1), + ..Default::default() + }; + let primitives = stage.append_array(&[primitive]); + let mesh = GltfMesh { + primitives, + ..Default::default() + }; + let meshes = stage.append_array(&[mesh]); + + // nodes + let node = GltfNode { + mesh: meshes.at(0), + ..Default::default() + }; + let nodes = stage.append_array(&[node]); + + // doc + let _doc = stage.append(&GltfDocument { + accessors, + buffers, + meshes, + nodes, + ..Default::default() + }); + + // render unit + let gltf_vertex_data = stage.append(&GltfVertexData { + mesh: meshes.at(0), + primitive_index: 0, + ..Default::default() + }); + let vertex_data = VertexData::new_gltf(gltf_vertex_data); + let (projection, view) = crate::camera::default_ortho2d(100.0, 100.0); + let camera = stage.append(&Camera::new(projection, view)); + let _unit_id = stage.draw_unit(&RenderUnit { + vertex_data, + camera, + vertex_count: 3, + ..Default::default() + }); + + let img = r.render_image().unwrap(); + img_diff::assert_img_eq("gltf/cmy_tri.png", img); + } } diff --git a/test_img/gltf/cmy_tri.png b/test_img/gltf/cmy_tri.png new file mode 100644 index 00000000..52fa9cd9 Binary files /dev/null and b/test_img/gltf/cmy_tri.png differ