diff --git a/Cargo.toml b/Cargo.toml index 1c4d0628..ca3a88ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_gaussian_splatting" description = "bevy gaussian splatting render pipeline plugin" -version = "2.6.1" +version = "2.7.0" edition = "2021" authors = ["mosure "] license = "MIT" @@ -134,20 +134,21 @@ web = [ "webgpu", ] +# note: webgl2/buffer_texture are deprecated webgl2 = ["bevy/webgl2"] webgpu = ["bevy/webgpu"] [dependencies] bevy_args = "1.6" -bevy-inspector-egui = { version = "0.26", optional = true } +bevy-inspector-egui = { version = "0.26", optional = true } # update to latest once they upgrade to bevy_egui 0.30 (currently at 0.29) bevy_mod_picking = { version = "0.20", optional = true } bevy_panorbit_camera = { version = "0.19", optional = true, features = ["bevy_egui"] } bevy_transform_gizmo = { version = "0.12", optional = true } bincode2 = { version = "2.0", optional = true } byte-unit = { version = "5.0", optional = true } -bytemuck = "1.14" -clap = { version = "4.4", features = ["derive"] } +bytemuck = "1.19" +clap = { version = "4.5", features = ["derive"] } flate2 = { version = "1.0", optional = true } flexbuffers = { version = "2.0", optional = true } half = { version = "2.3", optional = true, features = ["serde"] } @@ -257,6 +258,10 @@ path = "examples/minimal.rs" name = "headless" path = "examples/headless.rs" +[[example]] +name = "multi_camera" +path = "examples/multi_camera.rs" + [[bench]] name = "io" diff --git a/examples/multi_camera.rs b/examples/multi_camera.rs new file mode 100644 index 00000000..6bebf98b --- /dev/null +++ b/examples/multi_camera.rs @@ -0,0 +1,259 @@ +use bevy::{ + prelude::*, + app::AppExit, + core_pipeline::tonemapping::Tonemapping, + render::camera::Viewport, + window::WindowResized, +}; +use bevy_args::{ + BevyArgsPlugin, + parse_args, +}; +use bevy_inspector_egui::quick::WorldInspectorPlugin; +use bevy_panorbit_camera::{ + PanOrbitCamera, + PanOrbitCameraPlugin, +}; + +use bevy_gaussian_splatting::{ + Gaussian, + GaussianCamera, + GaussianCloud, + GaussianMode, + GaussianCloudSettings, + GaussianSplattingBundle, + GaussianSplattingPlugin, + gaussian::f32::Rotation, + utils::{ + setup_hooks, + GaussianSplattingViewer, + }, + SphericalHarmonicCoefficients, +}; + + +fn compare_surfel_app() { + let config = parse_args::(); + let mut app = App::new(); + + // setup for gaussian viewer app + app.insert_resource(ClearColor(Color::srgb_u8(0, 0, 0))); + app.add_plugins( + DefaultPlugins + .set(ImagePlugin::default_nearest()) + .set(WindowPlugin { + primary_window: Some(Window { + mode: bevy::window::WindowMode::Windowed, + present_mode: bevy::window::PresentMode::AutoVsync, + prevent_default_event_handling: false, + resolution: (config.width, config.height).into(), + title: config.name.clone(), + ..default() + }), + ..default() + }), + ); + app.add_plugins(BevyArgsPlugin::::default()); + app.add_plugins(PanOrbitCameraPlugin); + + if config.editor { + app.add_plugins(WorldInspectorPlugin::new()); + } + + if config.press_esc_close { + app.add_systems(Update, esc_close); + } + + app.add_plugins(GaussianSplattingPlugin); + app.add_systems(Startup, setup_surfel_compare); + app.add_systems( + Update, + ( + // press_s_swap_cameras, + set_camera_viewports, + ) + ); + + app.run(); +} + + +pub fn setup_surfel_compare( + mut commands: Commands, + mut gaussian_assets: ResMut>, +) { + let grid_size_x = 10; + let grid_size_y = 10; + let spacing = 12.0; + let visualize_bounding_box = false; + + let mut blue_gaussians = Vec::new(); + let mut blue_sh = SphericalHarmonicCoefficients::default(); + blue_sh.set(2, 5.0); + + for i in 0..grid_size_x { + for j in 0..grid_size_y { + let x = i as f32 * spacing - (grid_size_x as f32 * spacing) / 2.0; + let y = j as f32 * spacing - (grid_size_y as f32 * spacing) / 2.0; + let position = [x, y, 0.0, 1.0]; + let scale = [2.0, 1.0, 0.01, 0.5]; + + let angle = std::f32::consts::PI / 2.0 * i as f32 / grid_size_x as f32; + let rotation = Quat::from_rotation_z(angle).to_array(); + let rotation = [3usize, 0usize, 1usize, 2usize] + .iter() + .map(|i| rotation[*i]) + .collect::>() + .try_into() + .unwrap(); + + let gaussian = Gaussian { + position_visibility: position.into(), + rotation: Rotation { + rotation, + }, + scale_opacity: scale.into(), + spherical_harmonic: blue_sh, + }; + blue_gaussians.push(gaussian); + } + } + + commands.spawn(( + GaussianSplattingBundle { + cloud: gaussian_assets.add(GaussianCloud::from_gaussians(blue_gaussians)), + settings: GaussianCloudSettings { + visualize_bounding_box, + ..default() + }, + ..default() + }, + Name::new("gaussian_cloud_3dgs"), + )); + + let mut red_gaussians = Vec::new(); + let mut red_sh = SphericalHarmonicCoefficients::default(); + red_sh.set(0, 5.0); + + for i in 0..grid_size_x { + for j in 0..grid_size_y { + let x = i as f32 * spacing - (grid_size_x as f32 * spacing) / 2.0; + let y = j as f32 * spacing - (grid_size_y as f32 * spacing) / 2.0; + let position = [x, y, 0.0, 1.0]; + let scale = [2.0, 1.0, 0.01, 0.5]; + + let angle = std::f32::consts::PI / 2.0 * (i + 1) as f32 / grid_size_x as f32; + let rotation = Quat::from_rotation_z(angle).to_array(); + let rotation = [3usize, 0usize, 1usize, 2usize] + .iter() + .map(|i| rotation[*i]) + .collect::>() + .try_into() + .unwrap(); + + let gaussian = Gaussian { + position_visibility: position.into(), + rotation: Rotation { + rotation, + }, + scale_opacity: scale.into(), + spherical_harmonic: red_sh, + }; + red_gaussians.push(gaussian); + } + } + + commands.spawn(( + GaussianSplattingBundle { + cloud: gaussian_assets.add(GaussianCloud::from_gaussians(red_gaussians)), + settings: GaussianCloudSettings { + visualize_bounding_box, + aabb: true, + transform: Transform::from_translation(Vec3::new(spacing, spacing, 0.0)), + gaussian_mode: GaussianMode::GaussianSurfel, + ..default() + }, + ..default() + }, + Name::new("gaussian_cloud_2dgs"), + )); + + commands.spawn(( + GaussianCamera, + Camera3dBundle { + camera: Camera{ + order: 0, + ..default() + }, + transform: Transform::from_translation(Vec3::new(0.0, 1.5, 20.0)), + tonemapping: Tonemapping::None, + ..default() + }, + CameraPosition { + pos: UVec2::new(0, 0), + }, + PanOrbitCamera { + allow_upside_down: true, + ..default() + }, + )); + + commands.spawn(( + GaussianCamera, + Camera3dBundle { + camera: Camera{ + order: 1, + ..default() + }, + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 40.0)), + tonemapping: Tonemapping::None, + ..default() + }, + CameraPosition { + pos: UVec2::new(1, 0), + }, + PanOrbitCamera { + allow_upside_down: true, + ..default() + }, + )); +} + + +#[derive(Component)] +struct CameraPosition { + pos: UVec2, +} + +fn set_camera_viewports( + windows: Query<&Window>, + mut resize_events: EventReader, + mut query: Query<(&CameraPosition, &mut Camera), With>, +) { + for resize_event in resize_events.read() { + let window = windows.get(resize_event.window).unwrap(); + let size = window.physical_size() / UVec2::new(2, 1); + + for (position, mut camera) in &mut query { + camera.viewport = Some(Viewport { + physical_position: position.pos * size, + physical_size: size, + ..default() + }); + } + } +} + +fn esc_close( + keys: Res>, + mut exit: EventWriter +) { + if keys.just_pressed(KeyCode::Escape) { + exit.send(AppExit::Success); + } +} + +pub fn main() { + setup_hooks(); + compare_surfel_app(); +} diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 00000000..446dcdb0 --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,15 @@ +use bevy::{ + prelude::*, + render::extract_component::ExtractComponent +}; + + +#[derive( + Clone, + Component, + Debug, + Default, + ExtractComponent, + Reflect, +)] +pub struct GaussianCamera; diff --git a/src/gaussian/group.rs b/src/gaussian/group.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/lib.rs b/src/lib.rs index a0c071a0..beba2182 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ use bevy::prelude::*; +pub use camera::GaussianCamera; + pub use gaussian::{ packed::Gaussian, cloud::GaussianCloud, @@ -15,9 +17,9 @@ pub use material::spherical_harmonics::SphericalHarmonicCoefficients; use io::loader::GaussianCloudLoader; -pub use render::GaussianCamera; use render::RenderPipelinePlugin; +pub mod camera; pub mod gaussian; pub mod io; pub mod material; diff --git a/src/morph/particle.rs b/src/morph/particle.rs index 9f36e249..69a0a389 100644 --- a/src/morph/particle.rs +++ b/src/morph/particle.rs @@ -78,14 +78,16 @@ use serde::{ Serialize, }; -use crate::render::{ - GaussianCamera, - GaussianCloudBindGroup, - GaussianCloudPipeline, - GaussianCloudPipelineKey, - GaussianUniformBindGroups, - GaussianViewBindGroup, - shader_defs, +use crate::{ + camera::GaussianCamera, + render::{ + GaussianCloudBindGroup, + GaussianCloudPipeline, + GaussianCloudPipelineKey, + GaussianUniformBindGroups, + GaussianViewBindGroup, + shader_defs, + }, }; diff --git a/src/render/mod.rs b/src/render/mod.rs index 4715bc59..4866d22d 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -19,7 +19,6 @@ use bevy::{ extract_component::{ ComponentUniforms, DynamicUniformIndex, - ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, }, @@ -60,6 +59,7 @@ use bevy::{ }; use crate::{ + camera::GaussianCamera, gaussian::{ cloud::GaussianCloud, settings::{ @@ -78,7 +78,9 @@ use crate::{ sort::{ GpuSortedEntry, SortPlugin, + SortEntry, SortedEntries, + SortTrigger, }, }; @@ -296,7 +298,7 @@ fn queue_gaussians( mut views: Query< ( Entity, - &ExtractedView + &ExtractedView, ), With, >, @@ -438,8 +440,8 @@ impl FromWorld for GaussianCloudPipeline { visibility: ShaderStages::VERTEX_FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: BufferSize::new(std::mem::size_of::<(u32, u32)>() as u64), + has_dynamic_offset: true, + min_binding_size: BufferSize::new(std::mem::size_of::() as u64), }, count: None, }, @@ -789,7 +791,6 @@ fn queue_gaussian_bind_group( gaussian_cloud_res: Res>, sorted_entries_res: Res>, gaussian_clouds: Query, - #[cfg(feature = "buffer_texture")] gpu_images: Res>, ) { @@ -860,7 +861,7 @@ fn queue_gaussian_bind_group( resource: BindingResource::Buffer(BufferBinding { buffer: &sorted_entries.sorted_entry_buffer, offset: 0, - size: BufferSize::new((cloud.count * std::mem::size_of::<(u32, u32)>()) as u64), + size: BufferSize::new((cloud.count * std::mem::size_of::()) as u64), }), }, ], @@ -872,7 +873,7 @@ fn queue_gaussian_bind_group( &[ BindGroupEntry { binding: 0, - resource: BindingResource::TextureView( + resource: BindingResource::TextureView( // TODO: convert to texture view array &gpu_images.get(&sorted_entries.texture).unwrap().texture_view ), }, @@ -886,16 +887,6 @@ fn queue_gaussian_bind_group( } } -#[derive( - Clone, - Component, - Debug, - Default, - ExtractComponent, - Reflect, -)] -pub struct GaussianCamera; - #[derive(Component)] pub struct GaussianViewBindGroup { pub value: BindGroup, @@ -1016,7 +1007,7 @@ impl RenderCommand

for SetGaussianUniformBindGr pub struct DrawGaussianInstanced; impl RenderCommand

for DrawGaussianInstanced { type Param = SRes>; - type ViewQuery = (); + type ViewQuery = Read; type ItemQuery = ( Read>, Read, @@ -1025,7 +1016,7 @@ impl RenderCommand

for DrawGaussianInstanced { #[inline] fn render<'w>( _item: &P, - _view: (), + view: &'w SortTrigger, entity: Option<( &'w Handle, &'w GaussianCloudBindGroup, @@ -1040,8 +1031,18 @@ impl RenderCommand

for DrawGaussianInstanced { None => return RenderCommandResult::Failure, }; - pass.set_bind_group(2, &bind_groups.cloud_bind_group, &[]); - pass.set_bind_group(3, &bind_groups.sorted_bind_group, &[]); + pass.set_bind_group( + 2, + &bind_groups.cloud_bind_group, + &[], + ); + pass.set_bind_group( + 3, + &bind_groups.sorted_bind_group, + &[ + view.camera_index as u32 * std::mem::size_of::() as u32 * gpu_gaussian_cloud.count as u32, + ], + ); #[cfg(feature = "webgl2")] pass.draw(0..4, 0..gpu_gaussian_cloud.count as u32); diff --git a/src/sort/mod.rs b/src/sort/mod.rs index fba19dcc..20267b5c 100644 --- a/src/sort/mod.rs +++ b/src/sort/mod.rs @@ -5,7 +5,12 @@ use bevy::{ lifetimeless::SRes, SystemParamItem, }, + math::Vec3A, render::{ + extract_component::{ + ExtractComponent, + ExtractComponentPlugin, + }, render_resource::*, render_asset::{ RenderAsset, @@ -15,6 +20,10 @@ use bevy::{ }, renderer::RenderDevice, }, + utils::{ + Duration, + Instant, + }, }; use bytemuck::{ Pod, @@ -23,6 +32,7 @@ use bytemuck::{ use static_assertions::assert_cfg; use crate::{ + camera::GaussianCamera, GaussianCloud, GaussianCloudSettings, }; @@ -85,6 +95,27 @@ impl Default for SortMode { } +#[derive( + Resource, + Debug, + Clone, + PartialEq, + Reflect, +)] +#[reflect(Resource)] +pub struct SortConfig { + pub period_ms: usize, +} + +impl Default for SortConfig { + fn default() -> Self { + Self { + period_ms: 100, + } + } +} + + #[derive(Default)] pub struct SortPlugin; @@ -99,14 +130,25 @@ impl Plugin for SortPlugin { #[cfg(feature = "sort_std")] app.add_plugins(std::StdSortPlugin); + app.register_type::(); + app.init_resource::(); app.register_type::(); app.init_asset::(); app.register_asset_reflect::(); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(RenderAssetPlugin::::default()); - app.add_systems(Update, auto_insert_sorted_entries); + app.add_systems( + Update, + ( + auto_insert_sorted_entries, + update_sort_trigger, + update_sorted_entries_sizes, + ) + ); #[cfg(feature = "buffer_texture")] app.add_systems(PostUpdate, update_textures_on_change); @@ -114,6 +156,71 @@ impl Plugin for SortPlugin { } +#[derive( + Component, + ExtractComponent, + Debug, + Default, + Clone, + PartialEq, + Reflect, +)] +#[reflect(Component)] +pub struct SortTrigger { + pub camera_index: usize, + pub needs_sort: bool, + pub last_camera_position: Vec3A, + pub last_sort_time: Option, +} + +fn update_sort_trigger( + mut commands: Commands, + new_gaussian_cameras: Query< + Entity, + ( + With, + Without, + ), + >, + mut existing_sort_triggers: Query<( + &Transform, + &Camera, + &mut SortTrigger, + )>, + sort_config: Res, +) { + for entity in new_gaussian_cameras.iter() { + commands.entity(entity) + .insert(SortTrigger::default()); + } + + for ( + camera_transform, + camera, + mut sort_trigger, + ) in existing_sort_triggers.iter_mut() { + if sort_trigger.last_sort_time.is_none() { + assert!(camera.order >= 0, "camera order must be a non-negative index into gaussian cameras"); + + sort_trigger.camera_index = camera.order as usize; + sort_trigger.needs_sort = true; + sort_trigger.last_sort_time = Some(Instant::now()); + continue; + } else if sort_trigger.last_sort_time.unwrap().elapsed() < Duration::from_millis(sort_config.period_ms as u64) { + continue; + } + + let camera_position = camera_transform.compute_affine().translation; + let camera_movement = sort_trigger.last_camera_position != camera_position; + + if camera_movement { + sort_trigger.needs_sort = true; + sort_trigger.last_camera_position = camera_position; + } + } +} + + #[cfg(feature = "buffer_texture")] fn update_textures_on_change( mut images: ResMut>, @@ -151,7 +258,7 @@ fn auto_insert_sorted_entries( ), Without> >, - + gaussian_cameras: Query>, #[cfg(feature = "buffer_texture")] mut images: ResMut>, ) { @@ -175,36 +282,12 @@ fn auto_insert_sorted_entries( } let cloud = cloud.unwrap(); - let sorted: Vec = (0..cloud.len()) - .map(|idx| { - SortEntry { - key: 1, - index: idx as u32, - } - }) - .collect(); - - // TODO: move gaussian_cloud and sorted_entry assets into an asset bundle - #[cfg(feature = "buffer_storage")] - let sorted_entries = sorted_entries_res.add(SortedEntries { - sorted, - }); - - #[cfg(feature = "buffer_texture")] - let sorted_entries = sorted_entries_res.add(SortedEntries { - texture: images.add(Image::new( - Extent3d { - width: cloud.len_sqrt_ceil() as u32, - height: cloud.len_sqrt_ceil() as u32, - depth_or_array_layers: 1, - }, - TextureDimension::D2, - bytemuck::cast_slice(sorted.as_slice()).to_vec(), - TextureFormat::Rg32Uint, - RenderAssetUsages::default(), - )), - sorted, - }); + let sorted_entries = sorted_entries_res.add(SortedEntries::new( + gaussian_cameras.iter().len(), + cloud.len_sqrt_ceil().pow(2), + #[cfg(feature = "buffer_texture")] + images, + )); commands.entity(entity) .insert(sorted_entries); @@ -212,6 +295,32 @@ fn auto_insert_sorted_entries( } +fn update_sorted_entries_sizes( + mut sorted_entries_res: ResMut>, + sorted_entries: Query< + &Handle, + >, + gaussian_cameras: Query>, + #[cfg(feature = "buffer_texture")] + mut images: ResMut>, +) { + let camera_count: usize = gaussian_cameras.iter().len(); + + for handle in sorted_entries.iter() { + let sorted_entries = sorted_entries_res.get(handle).unwrap(); + if sorted_entries.camera_count != camera_count { + let new_entry = SortedEntries::new( + camera_count, + sorted_entries.entry_count, + #[cfg(feature = "buffer_texture")] + images, + ); + sorted_entries_res.insert(handle, new_entry); + } + } +} + + #[derive( Clone, Copy, @@ -238,12 +347,63 @@ pub struct SortEntry { Reflect, )] pub struct SortedEntries { + pub camera_count: usize, + pub entry_count: usize, pub sorted: Vec, #[cfg(feature = "buffer_texture")] pub texture: Handle, } +impl SortedEntries { + pub fn new( + camera_count: usize, + entry_count: usize, + #[cfg(feature = "buffer_texture")] + mut images: ResMut>, + ) -> Self { + let sorted = (0..camera_count) + .flat_map(|_camera_idx| { + (0..entry_count) + .map(|idx| { + SortEntry { + key: 1, + index: idx as u32, + } + }) + }) + .collect(); + + // TODO: move gaussian_cloud and sorted_entry assets into an asset bundle + #[cfg(feature = "buffer_storage")] + let sorted_entries = SortedEntries { + camera_count, + entry_count, + sorted, + }; + + #[cfg(feature = "buffer_texture")] + let sorted_entries = SortedEntries { + camera_count, + entry_count, + sorted, + texture: images.add(Image::new( + Extent3d { + width: cloud.len_sqrt_ceil() as u32, + height: cloud.len_sqrt_ceil() as u32, + depth_or_array_layers: gaussian_cameras.iter().len(), + }, + TextureDimension::D2, + bytemuck::cast_slice(sorted.as_slice()).to_vec(), + TextureFormat::Rg32Uint, + RenderAssetUsages::default(), + )), + }; + + sorted_entries + } +} + impl RenderAsset for GpuSortedEntry { type SourceAsset = SortedEntries; type Param = SRes; diff --git a/src/sort/rayon.rs b/src/sort/rayon.rs index b64201a4..97c56242 100644 --- a/src/sort/rayon.rs +++ b/src/sort/rayon.rs @@ -4,15 +4,17 @@ use bevy::{ math::Vec3A, utils::Instant, }; - use rayon::prelude::*; use crate::{ + camera::GaussianCamera, GaussianCloud, GaussianCloudSettings, sort::{ - SortedEntries, + SortConfig, SortMode, + SortTrigger, + SortedEntries, }, }; @@ -30,50 +32,29 @@ impl Plugin for RayonSortPlugin { pub fn rayon_sort( asset_server: Res, gaussian_clouds_res: Res>, - mut sorted_entries_res: ResMut>, gaussian_clouds: Query<( &Handle, &Handle, &GaussianCloudSettings, )>, - cameras: Query<( - &Transform, - &Camera3d, - )>, - mut last_camera_position: Local, - mut last_sort_time: Local>, - mut period: Local, - mut sort_done: Local, + mut sorted_entries_res: ResMut>, + mut cameras: Query< + &mut SortTrigger, + With, + >, + mut sort_config: ResMut, ) { - if last_sort_time.is_none() { - *period = std::time::Duration::from_millis(100); - } - - if let Some(last_sort_time) = last_sort_time.as_ref() { - if last_sort_time.elapsed() < *period { - return; - } - } - // TODO: move sort to render world, use extracted views and update the existing buffer instead of creating new let sort_start_time = Instant::now(); let mut performed_sort = false; - for ( - camera_transform, - _camera, - ) in cameras.iter() { - let camera_position = camera_transform.compute_affine().translation; - let camera_movement = *last_camera_position != camera_position; - - if camera_movement { - *sort_done = false; - } else if *sort_done { - return; + for mut trigger in cameras.iter_mut() { + if !trigger.needs_sort { + continue; } - - *last_camera_position = camera_position; + trigger.needs_sort = false; + performed_sort = true; for ( gaussian_cloud_handle, @@ -94,27 +75,24 @@ pub fn rayon_sort( if let Some(gaussian_cloud) = gaussian_clouds_res.get(gaussian_cloud_handle) { if let Some(sorted_entries) = sorted_entries_res.get_mut(sorted_entries_handle) { - assert_eq!(gaussian_cloud.len(), sorted_entries.sorted.len()); - - *sort_done = true; - *last_sort_time = Some(Instant::now()); - - performed_sort = true; + let gaussians = gaussian_cloud.len(); + let mut chunks = sorted_entries.sorted.chunks_mut(gaussians); + let chunk = chunks.nth(trigger.camera_index).unwrap(); gaussian_cloud.position_par_iter() - .zip(sorted_entries.sorted.par_iter_mut()) + .zip(chunk.par_iter_mut()) .enumerate() .for_each(|(idx, (position, sort_entry))| { let position = Vec3A::from_slice(position.as_ref()); let position = settings.transform.compute_affine().transform_point3a(position); - let delta = camera_position - position; + let delta = trigger.last_camera_position - position; sort_entry.key = bytemuck::cast(delta.length_squared()); sort_entry.index = idx as u32; }); - sorted_entries.sorted.par_sort_unstable_by(|a, b| { + chunk.par_sort_unstable_by(|a, b| { bytemuck::cast::(b.key).partial_cmp(&bytemuck::cast::(a.key)).unwrap_or(std::cmp::Ordering::Equal) }); @@ -128,10 +106,8 @@ pub fn rayon_sort( let delta = sort_end_time - sort_start_time; if performed_sort { - *period = std::time::Duration::from_millis( - 100 - .max(period.as_millis() as u64 * 4 / 5) - .max(4 * delta.as_millis() as u64) - ); + sort_config.period_ms = sort_config.period_ms + .max(sort_config.period_ms * 4 / 5) + .max(4 * delta.as_millis() as usize); } }