From e8aabb770ac1a38321a8e358635ede02b16a605c Mon Sep 17 00:00:00 2001 From: Facundo Villa Date: Mon, 30 Oct 2023 00:25:17 -0300 Subject: [PATCH] Mesh rendering WIP. --- Cargo.lock | 135 +++++++- Cargo.toml | 1 + assets/Sphere.bin | Bin 0 -> 130912 bytes assets/Sphere.gltf | 212 ++++++++++++ src/render_domain.rs | 114 +++++-- src/rendering/render_system.rs | 57 ++-- src/rendering/vulkan_render_system.rs | 48 +-- src/resource_manager/mesh_resource_handler.rs | 312 ++++++++++++------ tests/gi.rs | 4 +- 9 files changed, 697 insertions(+), 186 deletions(-) create mode 100644 assets/Sphere.bin create mode 100644 assets/Sphere.gltf diff --git a/Cargo.lock b/Cargo.lock index b1b6dad4..09aa4a8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -49,6 +58,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.1" @@ -127,6 +151,7 @@ dependencies = [ "libdeflater", "log", "maths-rs", + "meshopt", "notify-debouncer-full", "png", "polodb_core", @@ -197,7 +222,7 @@ name = "component_derive" version = "0.1.0" dependencies = [ "quote", - "syn", + "syn 2.0.38", ] [[package]] @@ -277,6 +302,28 @@ dependencies = [ "libc", ] +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + [[package]] name = "fdeflate" version = "0.3.0" @@ -317,6 +364,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" +dependencies = [ + "num-traits", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -406,7 +462,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.38", ] [[package]] @@ -452,6 +508,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "gltf" version = "1.3.0" @@ -475,7 +537,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn", + "syn 2.0.38", ] [[package]] @@ -764,6 +826,17 @@ dependencies = [ "libc", ] +[[package]] +name = "meshopt" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bc418147122ebbcbe3d26f486ebb4f186c24a6ee9ce8f4d53780e32b264ced" +dependencies = [ + "cc", + "failure", + "float-cmp 0.5.3", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -876,7 +949,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.38", ] [[package]] @@ -888,6 +961,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -1116,7 +1198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "272da9ec1e28b0ef17df4dcefad820b13f098ebe9c82697111fc57ccff621e12" dependencies = [ "bitflags 1.3.2", - "float-cmp", + "float-cmp 0.9.0", "libloading", "once_cell", "renderdoc-sys", @@ -1154,6 +1236,12 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustix" version = "0.38.20" @@ -1257,7 +1345,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.38", ] [[package]] @@ -1338,6 +1426,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.38" @@ -1349,6 +1448,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "tap" version = "1.0.1" @@ -1372,7 +1483,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.38", ] [[package]] @@ -1468,6 +1579,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "untrusted" version = "0.7.1" @@ -1571,7 +1688,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -1605,7 +1722,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 4b59851f..7f3907eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ maths-rs = "0.2.4" serde = "1.0.187" log = "0.4.20" simple_logger = "4.2.0" +meshopt = "0.1.9" [profile.dev] incremental = true diff --git a/assets/Sphere.bin b/assets/Sphere.bin new file mode 100644 index 0000000000000000000000000000000000000000..b44b03dee44141edf6d05cdb51732f32bb1e6e52 GIT binary patch literal 130912 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 literal 0 HcmV?d00001 diff --git a/assets/Sphere.gltf b/assets/Sphere.gltf new file mode 100644 index 00000000..d1aece50 --- /dev/null +++ b/assets/Sphere.gltf @@ -0,0 +1,212 @@ +{ + "accessors": [ + { + "bufferView": 2, + "componentType": 5126, + "count": 2399, + "max": [ + 1.0, + 2.0, + 1.0 + ], + "min": [ + -1.0, + 0.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 28788, + "componentType": 5126, + "count": 2399, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 2399, + "max": [ + 1.0, + 1.0 + ], + "min": [ + 0.0, + 0.0 + ], + "type": "VEC2" + }, + { + "bufferView": 0, + "componentType": 5125, + "count": 13536, + "type": "SCALAR" + } + ], + "asset": { + "extras": { + "author": "rawwerks (https://sketchfab.com/rawwerks)", + "license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)", + "source": "https://sketchfab.com/3d-models/sphere-gltf-example-cd602a89287b4cf1a704c13e15cf48f8", + "title": "sphere-gltf-example" + }, + "generator": "Sketchfab-12.65.0", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 54144, + "name": "floatBufferViews", + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 19192, + "byteOffset": 54144, + "byteStride": 8, + "name": "floatBufferViews", + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 57576, + "byteOffset": 73336, + "byteStride": 12, + "name": "floatBufferViews", + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 130912, + "uri": "Sphere.bin" + } + ], + "extensionsUsed": [ + "KHR_materials_transmission" + ], + "materials": [ + { + "extensions": { + "KHR_materials_transmission": { + "transmissionFactor": 0.0 + } + }, + "name": "GLASS", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.99, + 0.99, + 0.99, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.0 + } + } + ], + "meshes": [ + { + "name": "Object_0", + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 0, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.220446049250313e-16, + -1.0, + 0.0, + 0.0, + 1.0, + 2.220446049250313e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "name": "Sketchfab_model" + }, + { + "children": [ + 2 + ], + "name": "root" + }, + { + "children": [ + 3 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.220446049250313e-16, + 1.0, + 0.0, + 0.0, + -1.0, + 2.220446049250313e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ], + "name": "GLTF_SceneRootNode" + }, + { + "children": [ + 4 + ], + "name": "Sphere_1" + }, + { + "mesh": 0, + "name": "Object_4" + } + ], + "scene": 0, + "scenes": [ + { + "name": "Sketchfab_Scene", + "nodes": [ + 0 + ] + } + ] +} diff --git a/src/render_domain.rs b/src/render_domain.rs index 04b62b29..37a61b87 100644 --- a/src/render_domain.rs +++ b/src/render_domain.rs @@ -29,10 +29,10 @@ struct MeshData { pub struct VisibilityWorldRenderDomain { pipeline_layout_handle: render_system::PipelineLayoutHandle, - vertex_positions_buffer: render_system::BufferHandle, - vertex_normals_buffer: render_system::BufferHandle, + vertex_positions_buffer: render_system::BaseBufferHandle, + vertex_normals_buffer: render_system::BaseBufferHandle, - indices_buffer: render_system::BufferHandle, + indices_buffer: render_system::BaseBufferHandle, albedo: render_system::ImageHandle, depth_target: render_system::ImageHandle, @@ -45,8 +45,8 @@ pub struct VisibilityWorldRenderDomain { render_command_buffer: render_system::CommandBufferHandle, current_frame: usize, - camera_data_buffer_handle: render_system::BufferHandle, - materials_data_buffer_handle: render_system::BufferHandle, + camera_data_buffer_handle: render_system::BaseBufferHandle, + materials_data_buffer_handle: render_system::BaseBufferHandle, descriptor_set_layout: render_system::DescriptorSetLayoutHandle, descriptor_set: render_system::DescriptorSetHandle, @@ -54,8 +54,8 @@ pub struct VisibilityWorldRenderDomain { transfer_synchronizer: render_system::SynchronizerHandle, transfer_command_buffer: render_system::CommandBufferHandle, - meshes_data_buffer: render_system::BufferHandle, - meshlets_data_buffer: render_system::BufferHandle, + meshes_data_buffer: render_system::BaseBufferHandle, + meshlets_data_buffer: render_system::BaseBufferHandle, camera: Option>, @@ -82,11 +82,11 @@ pub struct VisibilityWorldRenderDomain { instance_id: render_system::ImageHandle, primitive_index: render_system::ImageHandle, - material_count: render_system::BufferHandle, - material_offset: render_system::BufferHandle, - material_offset_scratch: render_system::BufferHandle, - material_evaluation_dispatches: render_system::BufferHandle, - material_xy: render_system::BufferHandle, + material_count: render_system::BaseBufferHandle, + material_offset: render_system::BaseBufferHandle, + material_offset_scratch: render_system::BaseBufferHandle, + material_evaluation_dispatches: render_system::BaseBufferHandle, + material_xy: render_system::BaseBufferHandle, material_evaluation_descriptor_set_layout: render_system::DescriptorSetLayoutHandle, material_evaluation_descriptor_set: render_system::DescriptorSetHandle, @@ -97,7 +97,7 @@ pub struct VisibilityWorldRenderDomain { tone_map_pass: ToneMapPass, debug_position: render_system::ImageHandle, debug_normal: render_system::ImageHandle, - light_data_buffer: render_system::BufferHandle, + light_data_buffer: render_system::BaseBufferHandle, pending_texture_loads: Vec, @@ -200,8 +200,8 @@ impl VisibilityWorldRenderDomain { let camera_data_buffer_handle = render_system.create_buffer(Some("Visibility Camera Data"), 16 * 4 * 4, render_system::Uses::Storage, render_system::DeviceAccesses::CpuWrite | render_system::DeviceAccesses::GpuRead, render_system::UseCases::DYNAMIC); - let meshes_data_buffer = render_system.create_buffer(Some("Visibility Meshes Data"), 16 * 4 * 4 * 16, render_system::Uses::Storage, render_system::DeviceAccesses::CpuWrite | render_system::DeviceAccesses::GpuRead, render_system::UseCases::DYNAMIC); - let meshlets_data_buffer = render_system.create_buffer(Some("Visibility Meshlets Data"), 16 * 4 * 4 * 16, render_system::Uses::Storage, render_system::DeviceAccesses::CpuWrite | render_system::DeviceAccesses::GpuRead, render_system::UseCases::DYNAMIC); + let meshes_data_buffer = render_system.create_buffer(Some("Visibility Meshes Data"), std::mem::size_of::() * 1024, render_system::Uses::Storage, render_system::DeviceAccesses::CpuWrite | render_system::DeviceAccesses::GpuRead, render_system::UseCases::STATIC); + let meshlets_data_buffer = render_system.create_buffer(Some("Visibility Meshlets Data"), std::mem::size_of::() * 1024, render_system::Uses::Storage, render_system::DeviceAccesses::CpuWrite | render_system::DeviceAccesses::GpuRead, render_system::UseCases::STATIC); render_system.write(&[ render_system::DescriptorWrite { @@ -252,6 +252,7 @@ impl VisibilityWorldRenderDomain { #extension GL_EXT_shader_16bit_storage: require #extension GL_EXT_shader_explicit_arithmetic_types: enable #extension GL_EXT_mesh_shader: require +#extension GL_EXT_debug_printf : enable layout(row_major) uniform; layout(row_major) buffer; @@ -275,6 +276,7 @@ struct Meshlet {{ uint16_t triangle_offset; uint8_t vertex_count; uint8_t triangle_count; + uint8_t padding[6]; }}; layout(set=0,binding=0,scalar) buffer readonly CameraBuffer {{ @@ -307,14 +309,15 @@ void main() {{ uint instance_index = meshlet.instance_index; SetMeshOutputsEXT(meshlet.vertex_count, meshlet.triangle_count); - - if (gl_LocalInvocationID.x < meshlet.vertex_count) {{ - uint vertex_index = meshlet.vertex_offset + gl_LocalInvocationID.x; + + if (gl_LocalInvocationID.x < uint(meshlet.vertex_count) && gl_LocalInvocationID.x < {VERTEX_COUNT}) {{ + uint vertex_index = uint(meshlet.vertex_offset) + gl_LocalInvocationID.x; gl_MeshVerticesEXT[gl_LocalInvocationID.x].gl_Position = camera.view_projection * meshes[instance_index].model * vec4(vertex_positions[vertex_index], 1.0); + // gl_MeshVerticesEXT[gl_LocalInvocationID.x].gl_Position = vec4(vertex_positions[vertex_index], 1.0); }} - if (gl_LocalInvocationID.x < meshlet.triangle_count) {{ - uint triangle_index = meshlet.triangle_offset + gl_LocalInvocationID.x; + if (gl_LocalInvocationID.x < uint(meshlet.triangle_count) && gl_LocalInvocationID.x < {TRIANGLE_COUNT}) {{ + uint triangle_index = uint(meshlet.triangle_offset) + gl_LocalInvocationID.x; uint triangle_indices[3] = uint[](indices[triangle_index * 3 + 0], indices[triangle_index * 3 + 1], indices[triangle_index * 3 + 2]); gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationID.x] = uvec3(triangle_indices[0], triangle_indices[1], triangle_indices[2]); out_instance_index[gl_LocalInvocationID.x] = instance_index; @@ -1324,7 +1327,7 @@ void main() { render_system.wait(self.render_finished_synchronizer); - //render_system.start_frame_capture(); + render_system.start_frame_capture(); let camera_data_buffer = render_system.get_mut_buffer_slice(self.camera_data_buffer_handle); @@ -1404,6 +1407,45 @@ void main() { command_buffer_recording.start_region("Visibility Pass"); + command_buffer_recording.consume_resources(&[ + render_system::Consumption { + handle: render_system::Handle::Buffer(self.camera_data_buffer_handle), + stages: render_system::Stages::MESH | render_system::Stages::FRAGMENT, + access: render_system::AccessPolicies::READ, + layout: render_system::Layouts::General, + }, + render_system::Consumption { + handle: render_system::Handle::Buffer(self.vertex_positions_buffer), + stages: render_system::Stages::MESH, + access: render_system::AccessPolicies::READ, + layout: render_system::Layouts::General, + }, + render_system::Consumption { + handle: render_system::Handle::Buffer(self.vertex_normals_buffer), + stages: render_system::Stages::MESH, + access: render_system::AccessPolicies::READ, + layout: render_system::Layouts::General, + }, + render_system::Consumption { + handle: render_system::Handle::Buffer(self.indices_buffer), + stages: render_system::Stages::MESH, + access: render_system::AccessPolicies::READ, + layout: render_system::Layouts::General, + }, + render_system::Consumption { + handle: render_system::Handle::Buffer(self.meshes_data_buffer), + stages: render_system::Stages::MESH | render_system::Stages::FRAGMENT, + access: render_system::AccessPolicies::READ, + layout: render_system::Layouts::General, + }, + render_system::Consumption { + handle: render_system::Handle::Buffer(self.meshlets_data_buffer), + stages: render_system::Stages::MESH | render_system::Stages::FRAGMENT, + access: render_system::AccessPolicies::READ, + layout: render_system::Layouts::General, + }, + ]); + command_buffer_recording.start_render_pass(Extent::new(1920, 1080, 1), &attachments); // Visibility pass @@ -1601,7 +1643,7 @@ void main() { command_buffer_recording.execute(&[self.transfer_synchronizer, self.image_ready], &[self.render_finished_synchronizer], self.render_finished_synchronizer); - //render_system.end_frame_capture(); + render_system.end_frame_capture(); render_system.present(image_index, &[swapchain_handle], self.render_finished_synchronizer); @@ -1617,14 +1659,15 @@ impl orchestrator::EntitySubscriber for VisibilityWorldRenderDom } } -#[repr(C)] #[derive(Copy, Clone)] +#[repr(C)] struct ShaderMeshletData { instance_index: u32, vertex_offset: u16, - primitive_offset: u16, + triangle_offset: u16, vertex_count: u8, triangle_count: u8, + pad: [u8; 6], } #[repr(C)] @@ -1686,9 +1729,9 @@ impl orchestrator::EntitySubscriber for VisibilityWorldRenderDomain { options.resources.push(resource_manager::OptionResource { url: resource.url.clone(), buffers: vec![ - resource_manager::Buffer{ buffer: vertex_positions_buffer, tag: "Vertex.Position".to_string() }, - resource_manager::Buffer{ buffer: vertex_normals_buffer, tag: "Vertex.Normal".to_string() }, - resource_manager::Buffer{ buffer: index_buffer, tag: "Index".to_string() } + resource_manager::Buffer{ buffer: &mut vertex_positions_buffer[(self.visiblity_info.vertex_count as usize * std::mem::size_of::())..], tag: "Vertex.Position".to_string() }, + resource_manager::Buffer{ buffer: &mut vertex_normals_buffer[(self.visiblity_info.vertex_count as usize * std::mem::size_of::())..], tag: "Vertex.Normal".to_string() }, + resource_manager::Buffer{ buffer: &mut index_buffer[(self.visiblity_info.triangle_count as usize * 3 * std::mem::size_of::())..], tag: "MeshletIndices".to_string() } ], }); } @@ -1709,25 +1752,30 @@ impl orchestrator::EntitySubscriber for VisibilityWorldRenderDomain { { let vertex_offset = self.visiblity_info.vertex_count; - let triangle_offset = self.visiblity_info.triangle_count / 3; + let triangle_offset = self.visiblity_info.triangle_count; - let meshlet_count = (mesh.index_count / 3).div_ceil(TRIANGLE_COUNT); + let meshlet_count = (mesh.vertex_count).div_ceil(63); let mut mesh_vertex_count = 0; let mut mesh_triangle_count = 0; let mut meshlets = Vec::with_capacity(meshlet_count as usize); + let meshlet_index_stream = mesh.index_streams.iter().find(|is| is.stream_type == mesh_resource_handler::IndexStreamTypes::Meshlets).unwrap(); + + assert_eq!(meshlet_index_stream.data_type, mesh_resource_handler::IntegralTypes::U16, "Meshlet index stream is not u16"); + for _ in 0..meshlet_count { - let meshlet_vertex_count = (mesh.vertex_count - mesh_vertex_count).min(TRIANGLE_COUNT) as u8; - let meshlet_triangle_count = (mesh.index_count / 3 - mesh_triangle_count).min(TRIANGLE_COUNT) as u8; + let meshlet_vertex_count = (mesh.vertex_count - mesh_vertex_count).min(63) as u8; + let meshlet_triangle_count = (meshlet_index_stream.count / 3 - mesh_triangle_count).min(21) as u8; let meshlet_data = ShaderMeshletData { instance_index: self.visiblity_info.instance_count, vertex_offset: vertex_offset as u16 + mesh_vertex_count as u16, - primitive_offset:triangle_offset as u16 + mesh_triangle_count as u16, + triangle_offset:triangle_offset as u16 + mesh_triangle_count as u16, vertex_count: meshlet_vertex_count, triangle_count: meshlet_triangle_count, + pad: [0u8; 6], }; meshlets.push(meshlet_data); @@ -1757,7 +1805,7 @@ impl orchestrator::EntitySubscriber for VisibilityWorldRenderDomain { let meshlets_data_slice = render_system.get_mut_buffer_slice(self.meshlets_data_buffer); - let meshlets_data_slice = unsafe { std::slice::from_raw_parts_mut(meshlets_data_slice.as_mut_ptr() as *mut ShaderMeshletData, 128) }; + let meshlets_data_slice = unsafe { std::slice::from_raw_parts_mut(meshlets_data_slice.as_mut_ptr() as *mut ShaderMeshletData, 256) }; let mesh = self.meshes.get(mesh.resource_id).expect("Mesh not loaded"); diff --git a/src/rendering/render_system.rs b/src/rendering/render_system.rs index 14bbd35a..e8cbdd3f 100644 --- a/src/rendering/render_system.rs +++ b/src/rendering/render_system.rs @@ -55,7 +55,10 @@ bitflags::bitflags! { // HANDLES #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] -pub struct BufferHandle(pub(super) u64); +pub struct BaseBufferHandle(pub(super) u64); + +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +pub struct BufferHandle(pub(super) u64, std::marker::PhantomData); #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] pub struct AccelerationStructureHandle(pub(super) u64); @@ -104,7 +107,7 @@ pub struct TextureCopyHandle(pub(crate) u64); #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum Handle { - Buffer(BufferHandle), + Buffer(BaseBufferHandle), AccelerationStructure(AccelerationStructureHandle), CommandBuffer(CommandBufferHandle), Shader(ShaderHandle), @@ -132,9 +135,9 @@ pub struct Consumption { pub enum AccelerationStructureBuildAA { Mesh { - vertex_buffer: BufferHandle, - index_buffer: BufferHandle, - transform_buffer: BufferHandle, + vertex_buffer: BaseBufferHandle, + index_buffer: BaseBufferHandle, + transform_buffer: BaseBufferHandle, vertex_format: Formats, vertex_stride: u32, index_format: DataTypes, @@ -143,25 +146,25 @@ pub enum AccelerationStructureBuildAA { vertex_count: u32, }, AABB { - aabb_buffer: BufferHandle, - transform_buffer: BufferHandle, + aabb_buffer: BaseBufferHandle, + transform_buffer: BaseBufferHandle, transform_count: u32, }, Instance { acceleration_structure: AccelerationStructureHandle, - transform_buffer: BufferHandle, + transform_buffer: BaseBufferHandle, transform_count: u32, }, } pub struct AccelerationStructureBuild { pub acceleration_structure: AccelerationStructureHandle, - pub scratch_buffer: BufferHandle, + pub scratch_buffer: BaseBufferHandle, pub acceleration_structure_build_type: AccelerationStructureBuildAA, } pub struct BufferStridedRange { - pub buffer: BufferHandle, + pub buffer: BaseBufferHandle, pub offset: u64, pub stride: u64, pub size: u64, @@ -222,7 +225,7 @@ pub trait CommandBufferRecording { fn trace_rays(&mut self, binding_tables: BindingTables, x: u32, y: u32, z: u32); fn clear_textures(&mut self, textures: &[(ImageHandle, ClearValue)]); - fn clear_buffers(&mut self, buffer_handles: &[BufferHandle]); + fn clear_buffers(&mut self, buffer_handles: &[BaseBufferHandle]); fn transfer_textures(&mut self, texture_handles: &[ImageHandle]); @@ -254,7 +257,7 @@ pub enum Ranges { pub enum Descriptor { Buffer { - handle: BufferHandle, + handle: BaseBufferHandle, size: Ranges, }, Image { @@ -321,14 +324,14 @@ pub trait RenderSystem: orchestrator::System { /// # Returns /// /// The handle of the buffer. - fn create_buffer(&mut self, name: Option<&str>, size: usize, resource_uses: Uses, device_accesses: DeviceAccesses, use_case: UseCases) -> BufferHandle; + fn create_buffer(&mut self, name: Option<&str>, size: usize, resource_uses: Uses, device_accesses: DeviceAccesses, use_case: UseCases) -> BaseBufferHandle; - fn get_buffer_address(&self, buffer_handle: BufferHandle) -> u64; + fn get_buffer_address(&self, buffer_handle: BaseBufferHandle) -> u64; - fn get_buffer_slice(&mut self, buffer_handle: BufferHandle) -> &[u8]; + fn get_buffer_slice(&mut self, buffer_handle: BaseBufferHandle) -> &[u8]; // Return a mutable slice to the buffer data. - fn get_mut_buffer_slice(&self, buffer_handle: BufferHandle) -> &mut [u8]; + fn get_mut_buffer_slice(&self, buffer_handle: BaseBufferHandle) -> &mut [u8]; fn get_texture_slice_mut(&self, texture_handle: ImageHandle) -> &mut [u8]; @@ -337,7 +340,7 @@ pub trait RenderSystem: orchestrator::System { fn create_sampler(&mut self) -> SamplerHandle; - fn create_acceleration_structure_instance_buffer(&mut self, name: Option<&str>, max_instance_count: u32) -> BufferHandle; + fn create_acceleration_structure_instance_buffer(&mut self, name: Option<&str>, max_instance_count: u32) -> BaseBufferHandle; fn create_acceleration_structure(&mut self, name: Option<&str>, r#type: AccelerationStructureTypes, buffer_descriptor: BufferDescriptor,) -> AccelerationStructureHandle; fn bind_to_window(&mut self, window_os_handles: &window_system::WindowOsHandles) -> SwapchainHandle; @@ -1397,9 +1400,9 @@ pub struct ImageCopy { /// Stores the information of a buffer copy. pub struct BufferCopy { /// The source buffer. - pub(super) source: BufferHandle, + pub(super) source: BaseBufferHandle, /// The destination buffer. - pub(super) destination: BufferHandle, + pub(super) destination: BaseBufferHandle, /// The size of the copy. pub(super) size: usize, } @@ -1429,7 +1432,7 @@ pub enum Barrier { /// An image barrier. Image(ImageHandle), /// A buffer barrier. - Buffer(BufferHandle), + Buffer(BaseBufferHandle), /// A memory barrier. Memory, } @@ -1568,7 +1571,7 @@ pub enum DescriptorInfo { /// A buffer descriptor. Buffer { /// The buffer of the descriptor. - buffer: BufferHandle, + buffer: BaseBufferHandle, /// The offset to start reading from inside the buffer. offset: usize, /// How much to read from the buffer after `offset`. @@ -1634,7 +1637,7 @@ pub enum SwapchainStates { } pub struct BufferDescriptor { - pub buffer: BufferHandle, + pub buffer: BaseBufferHandle, pub offset: u64, pub range: u64, pub slot: u32, @@ -1736,7 +1739,7 @@ impl RenderSystem for RenderSystemImplementation { self.pointer.create_shader(shader_source_type, stage, shader) } - fn get_buffer_address(&self, buffer_handle: BufferHandle) -> u64 { + fn get_buffer_address(&self, buffer_handle: BaseBufferHandle) -> u64 { self.pointer.get_buffer_address(buffer_handle) } @@ -1744,11 +1747,11 @@ impl RenderSystem for RenderSystemImplementation { self.pointer.write(descriptor_set_writes) } - fn get_buffer_slice(&mut self, buffer_handle: BufferHandle) -> &[u8] { + fn get_buffer_slice(&mut self, buffer_handle: BaseBufferHandle) -> &[u8] { self.pointer.get_buffer_slice(buffer_handle) } - fn get_mut_buffer_slice(&self, buffer_handle: BufferHandle) -> &mut [u8] { + fn get_mut_buffer_slice(&self, buffer_handle: BaseBufferHandle) -> &mut [u8] { self.pointer.get_mut_buffer_slice(buffer_handle) } @@ -1784,7 +1787,7 @@ impl RenderSystem for RenderSystemImplementation { self.pointer.acquire_swapchain_image(swapchain_handle, synchronizer_handle) } - fn create_buffer(&mut self, name: Option<&str>, size: usize, uses: Uses, accesses: DeviceAccesses, use_case: UseCases) -> BufferHandle { + fn create_buffer(&mut self, name: Option<&str>, size: usize, uses: Uses, accesses: DeviceAccesses, use_case: UseCases) -> BaseBufferHandle { self.pointer.create_buffer(name, size, uses, accesses, use_case) } @@ -1828,7 +1831,7 @@ impl RenderSystem for RenderSystemImplementation { self.pointer.create_sampler() } - fn create_acceleration_structure_instance_buffer(&mut self, name: Option<&str>, max_instance_count: u32) -> BufferHandle { + fn create_acceleration_structure_instance_buffer(&mut self, name: Option<&str>, max_instance_count: u32) -> BaseBufferHandle { self.pointer.create_acceleration_structure_instance_buffer(name, max_instance_count) } diff --git a/src/rendering/vulkan_render_system.rs b/src/rendering/vulkan_render_system.rs index de59820a..a211f7cd 100644 --- a/src/rendering/vulkan_render_system.rs +++ b/src/rendering/vulkan_render_system.rs @@ -4,8 +4,11 @@ use ash::vk; use crate::{orchestrator, window_system, render_debugger::RenderDebugger, rendering::render_system}; +#[cfg(not(test))] +use log::{warn, error, debug}; + #[cfg(test)] -use std::{println as error, println as warn}; +use std::{println as error, println as warn, println as debug}; pub struct VulkanRenderSystem { entry: ash::Entry, @@ -112,9 +115,9 @@ impl render_system::RenderSystem for VulkanRenderSystem { let mut options = shaderc::CompileOptions::new().unwrap(); options.set_optimization_level(shaderc::OptimizationLevel::Performance); - options.set_target_env(shaderc::TargetEnv::Vulkan, shaderc::EnvVersion::Vulkan1_2 as u32); + options.set_target_env(shaderc::TargetEnv::Vulkan, (1 << 22) | (3 << 12)); options.set_generate_debug_info(); - options.set_target_spirv(shaderc::SpirvVersion::V1_5); + options.set_target_spirv(shaderc::SpirvVersion::V1_6); options.set_invert_y(true); let shader_text = std::str::from_utf8(shader).unwrap(); @@ -611,7 +614,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { /// # Returns /// /// The handle of the buffer. - fn create_buffer(&mut self, name: Option<&str>, size: usize, resource_uses: render_system::Uses, device_accesses: render_system::DeviceAccesses, use_case: render_system::UseCases) -> render_system::BufferHandle { + fn create_buffer(&mut self, name: Option<&str>, size: usize, resource_uses: render_system::Uses, device_accesses: render_system::DeviceAccesses, use_case: render_system::UseCases) -> render_system::BaseBufferHandle { if device_accesses.contains(render_system::DeviceAccesses::CpuWrite | render_system::DeviceAccesses::GpuRead) { match use_case { render_system::UseCases::STATIC => { @@ -621,7 +624,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { let (device_address, pointer) = self.bind_vulkan_buffer_memory(&buffer_creation_result, allocation_handle, 0); - let buffer_handle = render_system::BufferHandle(self.buffers.len() as u64); + let buffer_handle = render_system::BaseBufferHandle(self.buffers.len() as u64); self.buffers.push(Buffer { buffer: buffer_creation_result.resource, @@ -639,7 +642,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { let (device_address, pointer) = self.bind_vulkan_buffer_memory(&buffer_creation_result, allocation_handle, 0); - let buffer_handle = render_system::BufferHandle(self.buffers.len() as u64); + let buffer_handle = render_system::BaseBufferHandle(self.buffers.len() as u64); self.buffers.push(Buffer { buffer: buffer_creation_result.resource, @@ -671,7 +674,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { let (device_address, pointer) = self.bind_vulkan_buffer_memory(&buffer_creation_result, allocation_handle, 0); - let buffer_handle = render_system::BufferHandle(self.buffers.len() as u64); + let buffer_handle = render_system::BaseBufferHandle(self.buffers.len() as u64); self.buffers.push(Buffer { buffer: buffer_creation_result.resource, @@ -686,11 +689,11 @@ impl render_system::RenderSystem for VulkanRenderSystem { } } - fn get_buffer_address(&self, buffer_handle: render_system::BufferHandle) -> u64 { + fn get_buffer_address(&self, buffer_handle: render_system::BaseBufferHandle) -> u64 { self.buffers[buffer_handle.0 as usize].device_address } - fn get_buffer_slice(&mut self, buffer_handle: render_system::BufferHandle) -> &[u8] { + fn get_buffer_slice(&mut self, buffer_handle: render_system::BaseBufferHandle) -> &[u8] { let buffer = self.buffers[buffer_handle.0 as usize]; unsafe { std::slice::from_raw_parts(buffer.pointer, buffer.size as usize) @@ -698,7 +701,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { } // Return a mutable slice to the buffer data. - fn get_mut_buffer_slice(&self, buffer_handle: render_system::BufferHandle) -> &mut [u8] { + fn get_mut_buffer_slice(&self, buffer_handle: render_system::BaseBufferHandle) -> &mut [u8] { let buffer = self.buffers[buffer_handle.0 as usize]; unsafe { std::slice::from_raw_parts_mut(buffer.pointer, buffer.size) @@ -746,7 +749,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { let (address, pointer) = self.bind_vulkan_buffer_memory(&staging_buffer_creation_result, allocation_handle, 0); - let staging_buffer_handle = render_system::BufferHandle(self.buffers.len() as u64); + let staging_buffer_handle = render_system::BaseBufferHandle(self.buffers.len() as u64); self.buffers.push(Buffer { buffer: staging_buffer_creation_result.resource, @@ -763,7 +766,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { let (address, pointer) = self.bind_vulkan_buffer_memory(&staging_buffer_creation_result, allocation_handle, 0); - let staging_buffer_handle = render_system::BufferHandle(self.buffers.len() as u64); + let staging_buffer_handle = render_system::BaseBufferHandle(self.buffers.len() as u64); self.buffers.push(Buffer { buffer: staging_buffer_creation_result.resource, @@ -805,7 +808,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { render_system::SamplerHandle(self.create_vulkan_sampler().as_raw()) } - fn create_acceleration_structure_instance_buffer(&mut self, name: Option<&str>, max_instance_count: u32) -> render_system::BufferHandle { + fn create_acceleration_structure_instance_buffer(&mut self, name: Option<&str>, max_instance_count: u32) -> render_system::BaseBufferHandle { let size = max_instance_count as usize * std::mem::size_of::(); let buffer_creation_result = self.create_vulkan_buffer(name, size, vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR | vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS); @@ -814,7 +817,7 @@ impl render_system::RenderSystem for VulkanRenderSystem { let (address, pointer) = self.bind_vulkan_buffer_memory(&buffer_creation_result, allocation_handle, 0); - let buffer_handle = render_system::BufferHandle(self.buffers.len() as u64); + let buffer_handle = render_system::BaseBufferHandle(self.buffers.len() as u64); self.buffers.push(Buffer { buffer: buffer_creation_result.resource, @@ -1048,9 +1051,6 @@ impl render_system::RenderSystem for VulkanRenderSystem { use ash::{vk::{ValidationFeatureEnableEXT, Handle}, Entry}; -#[cfg(not(test))] -use log::{warn, error, debug}; - use super::render_system::{CommandBufferRecording, Formats, DispatchExtent}; #[derive(Clone)] @@ -1122,7 +1122,7 @@ pub(crate) struct Synchronizer { #[derive(Clone, Copy)] pub(crate) struct Texture { next: Option, - staging_buffer: Option, + staging_buffer: Option, allocation_handle: render_system::AllocationHandle, image: vk::Image, image_view: vk::ImageView, @@ -1145,6 +1145,9 @@ unsafe extern "system" fn vulkan_debug_utils_callback(message_severity: vk::Debu let message = std::ffi::CStr::from_ptr((*p_callback_data).p_message); match message_severity { + vk::DebugUtilsMessageSeverityFlagsEXT::INFO => { + debug!("{}", message.to_str().unwrap()); + } vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => { warn!("{}", message.to_str().unwrap()); } @@ -1521,6 +1524,9 @@ impl VulkanRenderSystem { let enabled_validation_features = [ ValidationFeatureEnableEXT::SYNCHRONIZATION_VALIDATION, ValidationFeatureEnableEXT::BEST_PRACTICES, + // ValidationFeatureEnableEXT::GPU_ASSISTED, + ValidationFeatureEnableEXT::GPU_ASSISTED_RESERVE_BINDING_SLOT, + ValidationFeatureEnableEXT::DEBUG_PRINTF, ]; let mut validation_features = vk::ValidationFeaturesEXT::default() @@ -2040,7 +2046,7 @@ impl VulkanRenderSystem { memory } - fn get_vulkan_buffer_address(&self, buffer: &render_system::BufferHandle, _allocation: &render_system::AllocationHandle) -> u64 { + fn get_vulkan_buffer_address(&self, buffer: &render_system::BaseBufferHandle, _allocation: &render_system::AllocationHandle) -> u64 { let buffer = self.buffers.get(buffer.0 as usize).expect("No buffer with that handle.").buffer.clone(); unsafe { self.device.get_buffer_device_address(&vk::BufferDeviceAddressInfo::default().buffer(buffer)) } } @@ -2448,7 +2454,7 @@ impl VulkanCommandBufferRecording<'_> { // } // } - fn get_buffer(&self, buffer_handle: render_system::BufferHandle) -> (render_system::BufferHandle, &Buffer) { + fn get_buffer(&self, buffer_handle: render_system::BaseBufferHandle) -> (render_system::BaseBufferHandle, &Buffer) { (buffer_handle, &self.render_system.buffers[buffer_handle.0 as usize]) } @@ -2922,7 +2928,7 @@ impl render_system::CommandBufferRecording for VulkanCommandBufferRecording<'_> unsafe { self.render_system.device.cmd_pipeline_barrier2(command_buffer.command_buffer, &dependency_info) }; } - fn clear_buffers(&mut self, buffer_handles: &[render_system::BufferHandle]) { + fn clear_buffers(&mut self, buffer_handles: &[render_system::BaseBufferHandle]) { self.consume_resources(&buffer_handles.iter().map(|buffer_handle| render_system::Consumption{ handle: render_system::Handle::Buffer(*buffer_handle), diff --git a/src/resource_manager/mesh_resource_handler.rs b/src/resource_manager/mesh_resource_handler.rs index f79a637c..f391b3b7 100644 --- a/src/resource_manager/mesh_resource_handler.rs +++ b/src/resource_manager/mesh_resource_handler.rs @@ -14,6 +14,15 @@ impl MeshResourceHandler { pub fn new() -> Self { Self {} } + + fn make_bounding_box(mesh: &gltf::Primitive) -> [[f32; 3]; 2] { + let bounds = mesh.bounding_box(); + + [ + [bounds.min[0], bounds.min[1], bounds.min[2],], + [bounds.max[0], bounds.max[1], bounds.max[2],], + ] + } } impl ResourceHandler for MeshResourceHandler { @@ -28,83 +37,103 @@ impl ResourceHandler for MeshResourceHandler { fn process(&self, resource_manager: &ResourceManager, asset_url: &str) -> Result, String> { let (gltf, buffers, _) = gltf::import(resource_manager.realize_asset_path(asset_url).unwrap()).unwrap(); - let mut buf: Vec = Vec::with_capacity(4096 * 1024 * 3); + const MESHLETIZE: bool = true; - // 'mesh_loop: for mesh in gltf.meshes() { - // for primitive in mesh.primitives() { - - let primitive = gltf.meshes().next().unwrap().primitives().next().unwrap(); + let mut resources = Vec::with_capacity(2); - let mut vertex_components = Vec::new(); - let index_type; - let bounding_box: [[f32; 3]; 2]; - let vertex_count = primitive.attributes().next().unwrap().1.count() as u32; - let index_count = primitive.indices().unwrap().count() as u32; + for mesh in gltf.meshes() { + for primitive in mesh.primitives() { + let mut vertex_components = Vec::new(); - let bounds = primitive.bounding_box(); + let bounding_box = Self::make_bounding_box(&primitive); - bounding_box = [ - [bounds.min[0], bounds.min[1], bounds.min[2],], - [bounds.max[0], bounds.max[1], bounds.max[2],], - ]; + let mut buffer = Vec::with_capacity(4096 * 1024 * 3); - let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); + let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); - if let Some(positions) = reader.read_positions() { - positions.for_each(|position| position.iter().for_each(|m| m.to_le_bytes().iter().for_each(|byte| buf.push(*byte)))); - vertex_components.push(VertexComponent { semantic: VertexSemantics::Position, format: "vec3f".to_string(), channel: 0 }); - } else { - return Err("Mesh does not have positions".to_string()); - } + let vertex_count = if let Some(positions) = reader.read_positions() { + let vertex_count = positions.clone().count(); + positions.for_each(|position| position.iter().for_each(|m| m.to_le_bytes().iter().for_each(|byte| buffer.push(*byte)))); + vertex_components.push(VertexComponent { semantic: VertexSemantics::Position, format: "vec3f".to_string(), channel: 0 }); + vertex_count + } else { + return Err("Mesh does not have positions".to_string()); + }; - if let Some(normals) = reader.read_normals() { - normals.for_each(|normal| normal.iter().for_each(|m| m.to_le_bytes().iter().for_each(|byte| buf.push(*byte)))); - vertex_components.push(VertexComponent { semantic: VertexSemantics::Normal, format: "vec3f".to_string(), channel: 1 }); - } + let indices = reader.read_indices().expect("Cannot create mesh which does not have indices").into_u32().collect::>(); - if let Some(tangents) = reader.read_tangents() { - tangents.for_each(|tangent| tangent.iter().for_each(|m| m.to_le_bytes().iter().for_each(|byte| buf.push(*byte)))); - vertex_components.push(VertexComponent { semantic: VertexSemantics::Tangent, format: "vec3f".to_string(), channel: 2 }); - } + let optimized_indices = meshopt::optimize::optimize_vertex_cache(&indices, vertex_count); + + if let Some(normals) = reader.read_normals() { + normals.for_each(|normal| normal.iter().for_each(|m| m.to_le_bytes().iter().for_each(|byte| buffer.push(*byte)))); + vertex_components.push(VertexComponent { semantic: VertexSemantics::Normal, format: "vec3f".to_string(), channel: 1 }); + } + + if let Some(tangents) = reader.read_tangents() { + tangents.for_each(|tangent| tangent.iter().for_each(|m| m.to_le_bytes().iter().for_each(|byte| buffer.push(*byte)))); + vertex_components.push(VertexComponent { semantic: VertexSemantics::Tangent, format: "vec3f".to_string(), channel: 2 }); + } + + if let Some(uv) = reader.read_tex_coords(0) { + uv.into_f32().for_each(|uv| uv.iter().for_each(|m| m.to_le_bytes().iter().for_each(|byte| buffer.push(*byte)))); + vertex_components.push(VertexComponent { semantic: VertexSemantics::Uv, format: "vec3f".to_string(), channel: 3 }); + } + + // align buffer to 16 bytes for indices + while buffer.len() % 16 != 0 { buffer.push(0); } - if let Some(uv) = reader.read_tex_coords(0) { - uv.into_f32().for_each(|uv| uv.iter().for_each(|m| m.to_le_bytes().iter().for_each(|byte| buf.push(*byte)))); - vertex_components.push(VertexComponent { semantic: VertexSemantics::Uv, format: "vec3f".to_string(), channel: 3 }); - } + let mut index_streams = Vec::with_capacity(2); + + let offset = buffer.len(); + + { + let index_type = IntegralTypes::U16; + + match index_type { + IntegralTypes::U16 => { + optimized_indices.iter().map(|i| *i as u16).for_each(|index| index.to_le_bytes().iter().for_each(|byte| buffer.push(*byte))); + index_streams.push(IndexStream{ data_type: IntegralTypes::U16, stream_type: IndexStreamTypes::Raw, offset, count: optimized_indices.len() as u32 }); + } + _ => panic!("Unsupported index type") + } + } + + let offset = buffer.len(); + + if MESHLETIZE { + let meshlets = meshopt::clusterize::build_meshlets(&optimized_indices, vertex_count, 64, 126); + + let mut index_count: usize = 0; + + for meshlet in meshlets { + index_count += meshlet.triangle_count as usize * 3; + for i in 0..meshlet.triangle_count as usize { + for x in meshlet.indices[i] { + (x as u16).to_le_bytes().iter().for_each(|byte| buffer.push(*byte)); + } + } + } + + assert_eq!(index_count, optimized_indices.len()); - // align buffer to 16 bytes for indices - while buf.len() % 16 != 0 { buf.push(0); } - - if let Some(indices) = reader.read_indices() { - match indices { - gltf::mesh::util::ReadIndices::U8(indices) => { - indices.for_each(|index| index.to_le_bytes().iter().for_each(|byte| buf.push(*byte))); - index_type = IntegralTypes::U8; - }, - gltf::mesh::util::ReadIndices::U16(indices) => { - indices.for_each(|index| index.to_le_bytes().iter().for_each(|byte| buf.push(*byte))); - index_type = IntegralTypes::U16; - }, - gltf::mesh::util::ReadIndices::U32(indices) => { - indices.for_each(|index| index.to_le_bytes().iter().for_each(|byte| buf.push(*byte))); - index_type = IntegralTypes::U32; - }, + index_streams.push(IndexStream{ data_type: IntegralTypes::U16, stream_type: IndexStreamTypes::Meshlets, offset, count: optimized_indices.len() as u32 }); + } + + let mesh = Mesh { + compression: CompressionSchemes::None, + bounding_box, + vertex_components, + vertex_count: vertex_count as u32, + index_streams, + }; + + let resource_document = GenericResourceSerialization::new(asset_url.to_string(), mesh); + + resources.push(ProcessedResources::Generated((resource_document, buffer))); } - } else { - return Err("Mesh does not have indices".to_string()); } - - let mesh = Mesh { - bounding_box, - vertex_components, - index_count, - vertex_count, - index_type - }; - let resource_document = GenericResourceSerialization::new(asset_url.to_string(), mesh); - - Ok(vec![ProcessedResources::Generated((resource_document, buf))]) + Ok(resources) } fn get_deserializers(&self) -> Vec<(&'static str, Box Box + Send>)> { @@ -128,14 +157,29 @@ impl ResourceHandler for MeshResourceHandler { file.read(&mut buffer.buffer[0..(mesh.vertex_count as usize * 12)]).unwrap(); } "Vertex.Normal" => { + #[cfg(debug_assertions)] + if !mesh.vertex_components.iter().any(|v| v.semantic == VertexSemantics::Normal) { error!("Requested Vertex.Normal stream but mesh does not have normals."); continue; } + file.seek(std::io::SeekFrom::Start(mesh.vertex_count as u64 * 12)).unwrap(); // 12 bytes per vertex file.read(&mut buffer.buffer[0..(mesh.vertex_count as usize * 12)]).unwrap(); } - "Index" => { - let base_offset = mesh.vertex_count as u64 * mesh.vertex_components.size() as u64; - let rounded_offset = base_offset.next_multiple_of(16); - file.seek(std::io::SeekFrom::Start(rounded_offset)).expect("Failed to seek to index buffer"); - file.read(&mut buffer.buffer[0..(mesh.index_count as usize * mesh.index_type.size())]).unwrap(); + "Indices" => { + #[cfg(debug_assertions)] + if !mesh.index_streams.iter().any(|stream| stream.stream_type == IndexStreamTypes::Raw) { error!("Requested Index stream but mesh does not have RAW indices."); continue; } + + let raw_index_stram = mesh.index_streams.iter().find(|stream| stream.stream_type == IndexStreamTypes::Raw).unwrap(); + + file.seek(std::io::SeekFrom::Start(raw_index_stram.offset as u64)).expect("Failed to seek to index buffer"); + file.read(&mut buffer.buffer[0..(raw_index_stram.count as usize * raw_index_stram.data_type.size())]).unwrap(); + } + "MeshletIndices" => { + #[cfg(debug_assertions)] + if !mesh.index_streams.iter().any(|stream| stream.stream_type == IndexStreamTypes::Meshlets) { error!("Requested MeshletIndices stream but mesh does not have meshlet indices indices."); continue; } + + let meshlet_indices_streams = mesh.index_streams.iter().find(|stream| stream.stream_type == IndexStreamTypes::Meshlets).unwrap(); + + file.seek(std::io::SeekFrom::Start(meshlet_indices_streams.offset as u64)).expect("Failed to seek to index buffer"); + file.read(&mut buffer.buffer[0..(meshlet_indices_streams.count as usize * meshlet_indices_streams.data_type.size())]).unwrap(); } _ => { error!("Unknown buffer tag: {}", buffer.tag); @@ -175,13 +219,35 @@ pub struct VertexComponent { pub channel: u32, } +#[derive(Debug, Serialize, Deserialize)] +pub enum CompressionSchemes { + None, + Quantization, + Octahedral, + OctahedralQuantization, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub enum IndexStreamTypes { + Raw, + Meshlets, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct IndexStream { + pub stream_type: IndexStreamTypes, + pub offset: usize, + pub count: u32, + pub data_type: IntegralTypes, +} + #[derive(Debug, Serialize, Deserialize)] pub struct Mesh { + pub compression: CompressionSchemes, pub bounding_box: [[f32; 3]; 2], pub vertex_components: Vec, - pub index_type: IntegralTypes, pub vertex_count: u32, - pub index_count: u32, + pub index_streams: Vec, } impl Resource for Mesh { @@ -197,7 +263,7 @@ impl Size for VertexSemantics { match self { VertexSemantics::Position => 3 * 4, VertexSemantics::Normal => 3 * 4, - VertexSemantics::Tangent => 3 * 4, + VertexSemantics::Tangent => 4 * 4, VertexSemantics::BiTangent => 3 * 4, VertexSemantics::Uv => 2 * 4, VertexSemantics::Color => 4 * 4, @@ -303,14 +369,12 @@ mod tests { assert_eq!(resource.type_id(), std::any::TypeId::of::()); - assert_eq!(buffer.len(), (24 /* vertices */ * (3 /* components per position */ * 4 /* float size */ + 3/*normals */ * 4) as usize).next_multiple_of(16) + 6/* cube faces */ * 2 /* triangles per face */ * 3 /* indices per triangle */ * 2 /* bytes per index */); - let mesh = resource.downcast_ref::().unwrap(); + let _offset = 0usize; + assert_eq!(mesh.bounding_box, [[-0.5f32, -0.5f32, -0.5f32], [0.5f32, 0.5f32, 0.5f32]]); assert_eq!(mesh.vertex_count, 24); - assert_eq!(mesh.index_count, 36); - assert_eq!(mesh.index_type, IntegralTypes::U16); assert_eq!(mesh.vertex_components.len(), 2); assert_eq!(mesh.vertex_components[0].semantic, VertexSemantics::Position); assert_eq!(mesh.vertex_components[0].format, "vec3f"); @@ -319,6 +383,22 @@ mod tests { assert_eq!(mesh.vertex_components[1].format, "vec3f"); assert_eq!(mesh.vertex_components[1].channel, 1); + assert_eq!(mesh.index_streams.len(), 2); + + let offset = ((mesh.vertex_count * mesh.vertex_components.size() as u32) as usize).next_multiple_of(16); + + assert_eq!(mesh.index_streams[0].stream_type, IndexStreamTypes::Raw); + assert_eq!(mesh.index_streams[0].offset, offset); + assert_eq!(mesh.index_streams[0].count, 36); + assert_eq!(mesh.index_streams[0].data_type, IntegralTypes::U16); + + let offset = offset + mesh.index_streams[0].count as usize * mesh.index_streams[0].data_type.size(); + + assert_eq!(mesh.index_streams[1].stream_type, IndexStreamTypes::Meshlets); + assert_eq!(mesh.index_streams[1].offset, offset); + assert_eq!(mesh.index_streams[1].count, 36); + assert_eq!(mesh.index_streams[1].data_type, IntegralTypes::U16); + let resource_request = resource_manager.request_resource("Box"); let resource_request = if let Some(resource_info) = resource_request { resource_info } else { return; }; @@ -334,7 +414,7 @@ mod tests { "Mesh" => { options.resources.push(OptionResource { url: resource.url.clone(), - buffers: vec![Buffer{ buffer: vertex_buffer.as_mut_slice(), tag: "Vertex".to_string() }, Buffer{ buffer: index_buffer.as_mut_slice(), tag: "Index".to_string() }], + buffers: vec![Buffer{ buffer: vertex_buffer.as_mut_slice(), tag: "Vertex".to_string() }, Buffer{ buffer: index_buffer.as_mut_slice(), tag: "Indices".to_string() }], }); } _ => {} @@ -351,7 +431,7 @@ mod tests { assert_eq!(buffer[0..(mesh.vertex_count * mesh.vertex_components.size() as u32) as usize], vertex_buffer[0..(mesh.vertex_count * mesh.vertex_components.size() as u32) as usize]); - assert_eq!(buffer[576..(576 + mesh.index_count * 2) as usize], index_buffer[0..(mesh.index_count * 2) as usize]); + assert_eq!(buffer[576..(576 + mesh.index_streams[0].count * 2) as usize], index_buffer[0..(mesh.index_streams[0].count * 2) as usize]); } _ => {} } @@ -362,7 +442,7 @@ mod tests { fn load_local_gltf_mesh_with_external_binaries() { let mut resource_manager = ResourceManager::new(); - let (response, _) = resource_manager.get("Suzanne").expect("Failed to get resource"); + let (response, buffer) = resource_manager.get("Suzanne").expect("Failed to get resource"); assert_eq!(response.resources.len(), 1); @@ -371,14 +451,12 @@ mod tests { assert_eq!(resource.type_id(), std::any::TypeId::of::()); - // assert_eq!(buffer.len(), (11808 /* vertices */ * (3 /* components per position */ * 4 /* float size */ + 3/*normals */ * 4) as usize).next_multiple_of(16) + 6/* cube faces */ * 2 /* triangles per face */ * 3 /* indices per triangle */ * 2 /* bytes per index */); - let mesh = resource.downcast_ref::().unwrap(); - // assert_eq!(mesh.bounding_box, [[-0.5f32, -0.5f32, -0.5f32], [0.5f32, 0.5f32, 0.5f32]]); + let _offset = 0usize; + + // assert_eq!(mesh.bounding_box, [[-2.674f32, -1.925f32, -1.626f32], [2.674f32, 1.925f32, 1.626f32]]); assert_eq!(mesh.vertex_count, 11808); - assert_eq!(mesh.index_count, 3936 * 3); - assert_eq!(mesh.index_type, IntegralTypes::U16); assert_eq!(mesh.vertex_components.len(), 4); assert_eq!(mesh.vertex_components[0].semantic, VertexSemantics::Position); assert_eq!(mesh.vertex_components[0].format, "vec3f"); @@ -386,6 +464,38 @@ mod tests { assert_eq!(mesh.vertex_components[1].semantic, VertexSemantics::Normal); assert_eq!(mesh.vertex_components[1].format, "vec3f"); assert_eq!(mesh.vertex_components[1].channel, 1); + + assert_eq!(mesh.index_streams.len(), 2); + + let offset = ((mesh.vertex_count * mesh.vertex_components.size() as u32) as usize).next_multiple_of(16); + + assert_eq!(mesh.index_streams[0].stream_type, IndexStreamTypes::Raw); + assert_eq!(mesh.index_streams[0].offset, offset); + assert_eq!(mesh.index_streams[0].count, 3936 * 3); + assert_eq!(mesh.index_streams[0].data_type, IntegralTypes::U16); + + let offset = offset + mesh.index_streams[0].count as usize * mesh.index_streams[0].data_type.size(); + + assert_eq!(mesh.index_streams[1].stream_type, IndexStreamTypes::Meshlets); + assert_eq!(mesh.index_streams[1].offset, offset); + assert_eq!(mesh.index_streams[1].count, 3936 * 3); + assert_eq!(mesh.index_streams[1].data_type, IntegralTypes::U16); + + let vertex_positions = unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const Vector3, mesh.vertex_count as usize) }; + + assert_eq!(vertex_positions.len(), 11808); + + assert_eq!(vertex_positions[0], Vector3::new(0.492188f32, 0.185547f32, 0.720703f32)); + assert_eq!(vertex_positions[1], Vector3::new(0.472656f32, 0.243042f32, 0.751221f32)); + assert_eq!(vertex_positions[2], Vector3::new(0.463867f32, 0.198242f32, 0.753418f32)); + + let vertex_normals = unsafe { std::slice::from_raw_parts((buffer.as_ptr() as *const Vector3).add(11808), mesh.vertex_count as usize) }; + + assert_eq!(vertex_normals.len(), 11808); + + assert_eq!(vertex_normals[0], Vector3::new(0.703351f32, -0.228379f32, 0.673156f32)); + assert_eq!(vertex_normals[1], Vector3::new(0.818977f32, -0.001884f32, 0.573824f32)); + assert_eq!(vertex_normals[2], Vector3::new(0.776439f32, -0.262265f32, 0.573027f32)); } #[test] @@ -401,21 +511,35 @@ mod tests { assert_eq!(resource.type_id(), std::any::TypeId::of::()); - assert_eq!(buffer.len(), (24 /* vertices */ * (3 /* components per position */ * 4 /* float size */ + 3/*normals */ * 4) as usize).next_multiple_of(16) + 6/* cube faces */ * 2 /* triangles per face */ * 3 /* indices per triangle */ * 2 /* bytes per index */); - let mesh = resource.downcast_ref::().unwrap(); + let offset = 0usize; + assert_eq!(mesh.bounding_box, [[-0.5f32, -0.5f32, -0.5f32], [0.5f32, 0.5f32, 0.5f32]]); assert_eq!(mesh.vertex_count, 24); - assert_eq!(mesh.index_count, 36); - assert_eq!(mesh.index_type, IntegralTypes::U16); assert_eq!(mesh.vertex_components.len(), 2); + assert_eq!(offset, 0); assert_eq!(mesh.vertex_components[0].semantic, VertexSemantics::Position); assert_eq!(mesh.vertex_components[0].format, "vec3f"); assert_eq!(mesh.vertex_components[0].channel, 0); assert_eq!(mesh.vertex_components[1].semantic, VertexSemantics::Normal); assert_eq!(mesh.vertex_components[1].format, "vec3f"); assert_eq!(mesh.vertex_components[1].channel, 1); + assert_eq!(mesh.index_streams.len(), 2); + + let offset = ((mesh.vertex_count * mesh.vertex_components.size() as u32) as usize).next_multiple_of(16); + + assert_eq!(mesh.index_streams[0].stream_type, IndexStreamTypes::Raw); + assert_eq!(mesh.index_streams[0].offset, offset); + assert_eq!(mesh.index_streams[0].count, 36); + assert_eq!(mesh.index_streams[0].data_type, IntegralTypes::U16); + + let offset = offset + mesh.index_streams[0].count as usize * mesh.index_streams[0].data_type.size(); + + assert_eq!(mesh.index_streams[1].stream_type, IndexStreamTypes::Meshlets); + assert_eq!(mesh.index_streams[1].offset, offset); + assert_eq!(mesh.index_streams[1].count, 36); + assert_eq!(mesh.index_streams[1].data_type, IntegralTypes::U16); // Cast buffer to Vector3 let vertex_positions = unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const Vector3, mesh.vertex_count as usize) }; @@ -426,7 +550,7 @@ mod tests { assert_eq!(vertex_positions[2], Vector3::new(-0.5f32, 0.5f32, 0.5f32)); // Cast buffer + 12 * 24 to Vector3 - let vertex_normals = unsafe { std::slice::from_raw_parts((buffer.as_ptr() as *const Vector3).add(24) as *const Vector3, mesh.vertex_count as usize) }; + let vertex_normals = unsafe { std::slice::from_raw_parts((buffer.as_ptr() as *const Vector3).add(24), mesh.vertex_count as usize) }; assert_eq!(vertex_normals.len(), 24); assert_eq!(vertex_normals[0], Vector3::new(0f32, 0f32, 1f32)); @@ -434,7 +558,7 @@ mod tests { assert_eq!(vertex_normals[2], Vector3::new(0f32, 0f32, 1f32)); // Cast buffer + 12 * 24 + 12 * 24 to u16 - let indeces = unsafe { std::slice::from_raw_parts((buffer.as_ptr().add(12 * 24 + 12 * 24)) as *const u16, mesh.index_count as usize) }; + let indeces = unsafe { std::slice::from_raw_parts((buffer.as_ptr().add(12 * 24 + 12 * 24)) as *const u16, mesh.index_streams[0].count as usize) }; assert_eq!(indeces.len(), 36); assert_eq!(indeces[0], 0); @@ -459,7 +583,7 @@ mod tests { "Mesh" => { options.resources.push(OptionResource { url: resource.url.clone(), - buffers: vec![Buffer{ buffer: vertex_buffer.as_mut_slice(), tag: "Vertex".to_string() }, Buffer{ buffer: index_buffer.as_mut_slice(), tag: "Index".to_string() }], + buffers: vec![Buffer{ buffer: vertex_buffer.as_mut_slice(), tag: "Vertex".to_string() }, Buffer{ buffer: index_buffer.as_mut_slice(), tag: "Indices".to_string() }], }); } _ => {} @@ -492,7 +616,7 @@ mod tests { // Cast index_buffer to u16 - let index_buffer = unsafe { std::slice::from_raw_parts(index_buffer.as_ptr() as *const u16, mesh.index_count as usize) }; + let index_buffer = unsafe { std::slice::from_raw_parts(index_buffer.as_ptr() as *const u16, mesh.index_streams[0].count as usize) }; assert_eq!(index_buffer.len(), 36); assert_eq!(index_buffer[0], 0); @@ -525,7 +649,7 @@ mod tests { buffers: vec![ Buffer{ buffer: vertex_positions_buffer.as_mut_slice(), tag: "Vertex.Position".to_string() }, Buffer{ buffer: vertex_normals_buffer.as_mut_slice(), tag: "Vertex.Normal".to_string() }, - Buffer{ buffer: index_buffer.as_mut_slice(), tag: "Index".to_string() } + Buffer{ buffer: index_buffer.as_mut_slice(), tag: "Indices".to_string() } ], }); } @@ -559,7 +683,7 @@ mod tests { // Cast index_buffer to u16 - let index_buffer = unsafe { std::slice::from_raw_parts(index_buffer.as_ptr() as *const u16, mesh.index_count as usize) }; + let index_buffer = unsafe { std::slice::from_raw_parts(index_buffer.as_ptr() as *const u16, mesh.index_streams[0].count as usize) }; assert_eq!(index_buffer.len(), 36); assert_eq!(index_buffer[0], 0); diff --git a/tests/gi.rs b/tests/gi.rs index 59c3b9ec..20f28c98 100644 --- a/tests/gi.rs +++ b/tests/gi.rs @@ -12,7 +12,7 @@ fn gi() { let orchestrator = app.get_mut_orchestrator(); orchestrator.spawn(byte_engine::camera::Camera { - position: Vec3f::new(0.0, 0.5, -2.0), + position: Vec3f::new(1.0, 0.5, -2.0), direction: Vec3f::new(0.0, 0.0, 1.0), fov: 90.0, aspect_ratio: 1.0, @@ -21,7 +21,7 @@ fn gi() { }); let _floor: EntityHandle = orchestrator.spawn(Mesh{ resource_id: "Box", material_id: "white_solid", transform: maths_rs::Mat4f::from_translation(Vec3f::new(0.0, -0.5, 0.0)) * maths_rs::Mat4f::from_scale(Vec3f::new(5.0, 1.0, 2.5)), }); - let _a: EntityHandle = orchestrator.spawn(Mesh{ resource_id: "Box", material_id: "white_solid", transform: maths_rs::Mat4f::from_translation(Vec3f::new(0.0, 0.25, 0.0)) * maths_rs::Mat4f::from_scale(Vec3f::new(0.5, 0.5, 0.5)), }); + let _a: EntityHandle = orchestrator.spawn(Mesh{ resource_id: "Suzanne", material_id: "white_solid", transform: maths_rs::Mat4f::from_translation(Vec3f::new(0.0, 0.25, 0.0)) * maths_rs::Mat4f::from_scale(Vec3f::new(0.5, 0.5, -0.5)), }); let _b: EntityHandle = orchestrator.spawn(Mesh{ resource_id: "Box", material_id: "red_solid", transform: maths_rs::Mat4f::from_translation(Vec3f::new(-0.6, 0.17, -0.1)) * maths_rs::Mat4f::from_scale(Vec3f::new(0.34, 0.34, 0.34)), }); let _c: EntityHandle = orchestrator.spawn(Mesh{ resource_id: "Box", material_id: "green_solid", transform: maths_rs::Mat4f::from_translation(Vec3f::new(0.5, 0.13, -0.3)) * maths_rs::Mat4f::from_scale(Vec3f::new(0.26, 0.26, 0.26)), });