├── .gitignore ├── Cargo.toml ├── LICENCE ├── examples ├── sort.rs └── sort_indirect.rs ├── benches └── sort.rs ├── README.md ├── src ├── utils.rs ├── radix_sort.wgsl └── lib.rs ├── tests └── sort.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wgpu_sort" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Simon Niedermayr", "Josef Stumpfegger"] 6 | license = "BSD-2-Clause" 7 | description = " WebGPU/wgpu Radix Key-Value Sort " 8 | repository = "https://github.com/KeKsBoTer/wgpu_sort" 9 | homepage = "https://github.com/KeKsBoTer/wgpu_sort" 10 | documentation = "https://docs.rs/wgpu_sort" 11 | keywords = ["wgpu", "gpu", "sort","radxi","wgpu"] 12 | categories = ["rendering","algorithms"] 13 | readme = "README.md" 14 | 15 | 16 | [package.metadata.docs.rs] 17 | all-features = true 18 | 19 | [dependencies] 20 | wgpu = { version = "0.20" } 21 | bytemuck = { version = "1.13.0", features = ["derive"] } 22 | futures-intrusive = "0.5.0" 23 | 24 | log = "0.4" 25 | env_logger = "0.11" 26 | 27 | 28 | [dev-dependencies] 29 | rand = "0.8.5" 30 | pollster = { version = "0.3.0", features = ["macro"] } 31 | float-ord = "0.3.2" 32 | criterion = { version = "0.4", features = ["html_reports"] } 33 | 34 | 35 | [[bench]] 36 | name = "sort" 37 | harness = false 38 | 39 | 40 | [[example]] 41 | name = "sort" 42 | 43 | [[example]] 44 | name = "sort_indirect" 45 | 46 | [[test]] 47 | name = "sort" 48 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2024, Simon Niedermayr, Josef Stumpfegger 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | -------------------------------------------------------------------------------- /examples/sort.rs: -------------------------------------------------------------------------------- 1 | // this example creates an array with 10 key-value (u32,f32) pairs and sorts them on the gpu 2 | use std::num::NonZeroU32; 3 | 4 | use wgpu_sort::{utils::{download_buffer, guess_workgroup_size, upload_to_buffer}, GPUSorter}; 5 | 6 | 7 | #[pollster::main] 8 | async fn main(){ 9 | let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); 10 | 11 | let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None) 12 | .await 13 | .unwrap(); 14 | 15 | let (device, queue) = adapter 16 | .request_device( 17 | &wgpu::DeviceDescriptor { 18 | required_features: wgpu::Features::empty(), 19 | required_limits: wgpu::Limits::default(), 20 | label: None, 21 | }, 22 | None, 23 | ) 24 | .await 25 | .unwrap(); 26 | let subgroup_size = guess_workgroup_size(&device, &queue).await.expect("could not find a valid subgroup size"); 27 | println!("using subgroup size {subgroup_size}"); 28 | let sorter = GPUSorter::new(&device, subgroup_size); 29 | 30 | let n = 10; 31 | let sort_buffers = sorter.create_sort_buffers(&device, NonZeroU32::new(n).unwrap()); 32 | 33 | 34 | let keys_scrambled: Vec = (0..n).rev().collect(); 35 | 36 | let values_scrambled:Vec = keys_scrambled.iter().map(|v|1./(*v as f32)).collect(); 37 | 38 | 39 | let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { 40 | label: None, 41 | }); 42 | 43 | upload_to_buffer( 44 | &mut encoder, 45 | &sort_buffers.keys(), 46 | &device, 47 | keys_scrambled.as_slice(), 48 | ); 49 | upload_to_buffer( 50 | &mut encoder, 51 | &sort_buffers.values(), 52 | &device, 53 | values_scrambled.as_slice(), 54 | ); 55 | 56 | println!("before: {:?}",keys_scrambled.iter().zip(values_scrambled.iter()).collect::>()); 57 | 58 | // sorter.sort(&mut encoder, &sort_buffers); 59 | sorter.sort(&mut encoder,&queue,&sort_buffers,None); 60 | 61 | // wait for sorter to finish 62 | let idx = queue.submit([encoder.finish()]); 63 | device.poll(wgpu::Maintain::WaitForSubmissionIndex(idx)); 64 | 65 | // keys buffer has padding at the end 66 | // so we only download the "valid" data 67 | let keys_sorted:Vec = download_buffer::( 68 | &sort_buffers.keys(), 69 | &device, 70 | &queue, 71 | 0..sort_buffers.keys_valid_size(), 72 | ) 73 | .await; 74 | let value_sorted = download_buffer::( 75 | &sort_buffers.values(), 76 | &device, 77 | &queue, 78 | .., 79 | ) 80 | .await; 81 | 82 | println!("after: {:?}",keys_sorted.iter().zip(value_sorted.iter()).collect::>()); 83 | } 84 | 85 | -------------------------------------------------------------------------------- /benches/sort.rs: -------------------------------------------------------------------------------- 1 | use std::{num::NonZeroU32, time::Duration}; 2 | 3 | use wgpu_sort::{utils::{download_buffer, guess_workgroup_size}, GPUSorter, SortBuffers}; 4 | 5 | struct SortStuff{ 6 | device:wgpu::Device, 7 | queue:wgpu::Queue, 8 | query_set:wgpu::QuerySet, 9 | query_buffer:wgpu::Buffer, 10 | } 11 | 12 | async fn setup()-> SortStuff{ 13 | let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); 14 | 15 | let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None) 16 | .await 17 | .unwrap(); 18 | 19 | let (device, queue) = adapter 20 | .request_device( 21 | &wgpu::DeviceDescriptor { 22 | required_features: wgpu::Features::TIMESTAMP_QUERY, 23 | required_limits: wgpu::Limits{ 24 | max_buffer_size:1<<30, 25 | max_storage_buffer_binding_size:1<<30, 26 | ..Default::default() 27 | }, 28 | label: None, 29 | }, 30 | None, 31 | ) 32 | .await 33 | .unwrap(); 34 | 35 | let capacity = 2; 36 | let query_set = device.create_query_set(&wgpu::QuerySetDescriptor { 37 | label: Some("time stamp query set"), 38 | ty: wgpu::QueryType::Timestamp, 39 | count: capacity, 40 | }); 41 | 42 | 43 | let query_buffer = device.create_buffer(&wgpu::BufferDescriptor { 44 | label: Some("query set buffer"), 45 | size: capacity as u64 * std::mem::size_of::() as u64, 46 | usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC, 47 | mapped_at_creation: false, 48 | }); 49 | 50 | return SortStuff{device,queue,query_set,query_buffer} 51 | 52 | } 53 | 54 | async fn sort(context:&SortStuff,sorter:&GPUSorter,buffers:&SortBuffers,n:u32,iters:u32) -> Duration { 55 | 56 | let mut encoder = context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { 57 | label: None, 58 | }); 59 | 60 | encoder.write_timestamp(&context.query_set, 0); 61 | 62 | for _ in 0..iters{ 63 | sorter.sort(&mut encoder,&context.queue,buffers,Some(n)); 64 | } 65 | 66 | encoder.write_timestamp(&context.query_set, 1); 67 | encoder.resolve_query_set( 68 | &context.query_set, 69 | 0..2, 70 | &context.query_buffer, 71 | 0, 72 | ); 73 | let idx = context.queue.submit([encoder.finish()]); 74 | context.device.poll(wgpu::Maintain::WaitForSubmissionIndex(idx)); 75 | 76 | let timestamps : Vec = pollster::block_on(download_buffer(&context.query_buffer, &context.device, &context.queue, ..)); 77 | let diff_ticks = timestamps[1] - timestamps[0]; 78 | let period = context.queue.get_timestamp_period(); 79 | let diff_time = Duration::from_nanos((diff_ticks as f32 * period / iters as f32) as u64); 80 | 81 | return diff_time; 82 | } 83 | 84 | 85 | 86 | #[pollster::main] 87 | async fn main() { 88 | 89 | let context = setup().await; 90 | 91 | let subgroup_size = guess_workgroup_size(&context.device, &context.queue).await.expect("could not find a valid subgroup size"); 92 | 93 | let sorter = GPUSorter::new(&context.device, subgroup_size); 94 | 95 | 96 | for n in [10_000,100_000,1_000_000,8_000_000,20_000_000]{ 97 | let buffers = sorter.create_sort_buffers(&context.device, NonZeroU32::new(n).unwrap()); 98 | let d = sort(&context,&sorter, &buffers,n,10000).await; 99 | println!("{n}: {d:?}"); 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /examples/sort_indirect.rs: -------------------------------------------------------------------------------- 1 | // this example creates an array with 10 key-value (f32,u32) pairs and sorts them on the gpu 2 | // Important: sorting by f32 keys only works for non negative key values. Also NaN and inf values give unexpected results 3 | use std::num::NonZeroU32; 4 | 5 | use bytemuck::bytes_of; 6 | use wgpu::util::DeviceExt; 7 | use wgpu_sort::{utils::{download_buffer, guess_workgroup_size, upload_to_buffer}, GPUSorter, HISTO_BLOCK_KVS}; 8 | 9 | 10 | #[pollster::main] 11 | async fn main(){ 12 | let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); 13 | 14 | let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None) 15 | .await 16 | .unwrap(); 17 | 18 | let (device, queue) = adapter 19 | .request_device( 20 | &wgpu::DeviceDescriptor { 21 | required_features: wgpu::Features::empty(), 22 | required_limits: wgpu::Limits::default(), 23 | label: None, 24 | }, 25 | None, 26 | ) 27 | .await 28 | .unwrap(); 29 | let subgroup_size = guess_workgroup_size(&device, &queue).await.expect("could not find a valid subgroup size"); 30 | println!("using subgroup size {subgroup_size}"); 31 | let sorter = GPUSorter::new(&device, subgroup_size); 32 | 33 | let n = 10; 34 | let sort_buffers = sorter.create_sort_buffers(&device, NonZeroU32::new(n).unwrap()); 35 | 36 | 37 | let keys_scrambled: Vec = (1..=n).map(|v| 1./v as f32).collect(); 38 | let values_scrambled:Vec = (1..=n).collect(); 39 | 40 | 41 | let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { 42 | label: Some("GPURSSorter test_sort"), 43 | }); 44 | 45 | upload_to_buffer( 46 | &mut encoder, 47 | &sort_buffers.keys(), 48 | &device, 49 | keys_scrambled.as_slice(), 50 | ); 51 | upload_to_buffer( 52 | &mut encoder, 53 | &sort_buffers.values(), 54 | &device, 55 | values_scrambled.as_slice(), 56 | ); 57 | 58 | println!("before: {:?}",keys_scrambled.iter().zip(values_scrambled.iter()).collect::>()); 59 | 60 | // round to next larger multiple of HISTO_BLOCK_KVS 61 | let num_wg = (n + HISTO_BLOCK_KVS- 1)/HISTO_BLOCK_KVS; 62 | 63 | let dispatch_indirect = wgpu::util::DispatchIndirectArgs{ 64 | x: num_wg, 65 | y: 1, 66 | z: 1 67 | }; 68 | 69 | queue.write_buffer(sort_buffers.state_buffer(), 0, bytes_of(&n)); 70 | 71 | let dispatch_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ 72 | label: Some("dispatch indirect buffer"), 73 | contents: dispatch_indirect.as_bytes(), 74 | usage: wgpu::BufferUsages::INDIRECT, 75 | }); 76 | 77 | sorter.sort_indirect(&mut encoder, &sort_buffers,&dispatch_buffer); 78 | 79 | // wait for sorter to fininsh 80 | let idx = queue.submit([encoder.finish()]); 81 | device.poll(wgpu::Maintain::WaitForSubmissionIndex(idx)); 82 | 83 | // keys buffer has padding at the end 84 | // so we only download the "valid" data 85 | let keys_sorted = download_buffer::( 86 | &sort_buffers.keys(), 87 | &device, 88 | &queue, 89 | 0..sort_buffers.keys_valid_size() 90 | ) 91 | .await; 92 | let value_sorted = download_buffer::( 93 | &sort_buffers.values(), 94 | &device, 95 | &queue, 96 | .. 97 | ) 98 | .await; 99 | 100 | println!("after: {:?}",keys_sorted.iter().zip(value_sorted.iter()).collect::>()); 101 | } 102 | 103 | 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGPU Radix Key-Value Sort 2 | 3 | [![crates.io version](https://img.shields.io/crates/v/wgpu_sort.svg)](https://crates.io/crates/wgpu_sort) [![Documentation](https://docs.rs/wgpu_sort/badge.svg)](https://docs.rs/wgpu_sort/) 4 | 5 | 6 | This package implements a GPU version of radix sort. A good introduction to general purpose radix sort can be found here: 7 | 8 | The GPU radix sort implemented here is a re-implementation of the Vulkan radix sort found in the fuchsia repos: . 9 | 10 | Currently only the sorting for 32-bit key-value pairs is implemented. 11 | It can be used to sort unsigned integers and non negative float numbers. See [Limitations](#limitations) for more details. 12 | The keys are sorted in ascending order. 13 | 14 | It was originally implemented for [our 3D Gaussian Splatting Renderer](https://github.com/KeKsBoTer/web-splat) to sort splats according to their depth in real time. It can be seen in action in this [web demo](https://keksboter.github.io/web-splat/demo.html). 15 | 16 | ## Example 17 | 18 | ```rust,ignore 19 | // find best subgroup size 20 | let subgroup_size = guess_workgroup_size(&device, &queue).await.unwrap(); 21 | let sorter = GPUSorter::new(&device, subgroup_size); 22 | 23 | // setup buffers to sort 100 key-value pairs 24 | let n = 100; 25 | let sort_buffers = sorter.create_sort_buffers(&device, NonZeroU32::new(n).unwrap()); 26 | 27 | let keys_scrambled: Vec = (0..n).rev().collect(); 28 | let values_scrambled:Vec = keys_scrambled.clone(); 29 | 30 | let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {label: None}); 31 | 32 | upload_to_buffer( 33 | &mut encoder, 34 | &sort_buffers.keys(), 35 | &device, 36 | keys_scrambled.as_slice(), 37 | ); 38 | upload_to_buffer( 39 | &mut encoder, 40 | &sort_buffers.values(), 41 | &device, 42 | values_scrambled.as_slice(), 43 | ); 44 | 45 | // sorter.sort(&mut encoder, &sort_buffers); 46 | sorter.sort(&mut encoder,&queue,&sort_buffers,None); 47 | queue.submit([encoder.finish()]); 48 | 49 | // key and value buffer is now sorted. 50 | ``` 51 | Indirect dispatching is also supported. See [examples/sort_indirect.rs](examples/sort_indirect.rs); 52 | 53 | ## Benchmarks 54 | 55 | To measure the performance we sort the key-value pairs 1000 times and report the average duration per run. 56 | Measurements were performed for different number of pairs. 57 | Take a look at [benches/sort.rs](benches/sort.rs) for more details. 58 | 59 | | Device | 10k | 100k | 1 Million | 8 Million | 20 Million | 60 | |------------------------|-----------|-----------|-----------|------------|------------| 61 | | NVIDIA RTX A5000 | 108.277µs | 110.179µs | 317.191µs | 1.641699ms | 3.980834ms | 62 | | AMD Radeon R9 380 | 803.527µs | 829.003µs | 2.76469ms | 18.81558ms | 46.12854ms | 63 | | Intel HD Graphics 4600 | 790.382µs | 4.12287ms | 38.7421ms | 295.2937ms | 732.3900ms | 64 | 65 | ## Limitations 66 | 67 | This sorter comes with a number of limitations that are explained in the following. 68 | 69 | **Subgroups** 70 | 71 | This renderer makes use of [subgroups](https://docs.vulkan.org/guide/latest/subgroups.html) to reduce synchronization and increase performance. 72 | Unfortunately subgroup operations are not supported bei WebGPU/wgpu right now. 73 | 74 | To overcome this issue we "guess" the subgroup size by trying out different subgroups and pick the largest one that works (see [utils::guess_workgroup_size](src/utils.rs)). 75 | This works in almost all cases but can fail because the subgroup size can change over time. 76 | Once subgroups are support this will be fixed. 77 | Status can be found [here](https://github.com/gpuweb/gpuweb/issues/4306). 78 | 79 | **Floating Point Numbers** 80 | 81 | The sorting algorithm interprets the values as integers and sorts the keys in ascending order. 82 | Non-negative float values can be interpreted as unsigned integers without affecting the ordering. 83 | Therefore this sorter can be used to sort 32-bit float keys. 84 | Note that NaN and Inf values lead to unexpected results as theses are interpreted as integers as well. 85 | An example for sorting float values can be found [here](examples/sort_indirect.rs). 86 | 87 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | num::NonZeroU32, 3 | ops::{Deref, RangeBounds}, 4 | }; 5 | 6 | use wgpu::util::DeviceExt; 7 | 8 | use crate::GPUSorter; 9 | 10 | #[doc(hidden)] 11 | /// only used for testing 12 | /// temporally used for guessing subgroup size 13 | pub fn upload_to_buffer( 14 | encoder: &mut wgpu::CommandEncoder, 15 | buffer: &wgpu::Buffer, 16 | device: &wgpu::Device, 17 | values: &[T], 18 | ) { 19 | let staging_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 20 | label: Some("Staging buffer"), 21 | contents: bytemuck::cast_slice(values), 22 | usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, 23 | }); 24 | encoder.copy_buffer_to_buffer(&staging_buffer, 0, buffer, 0, staging_buffer.size()); 25 | } 26 | 27 | #[doc(hidden)] 28 | /// only used for testing 29 | /// temporally used for guessing subgroup size 30 | pub async fn download_buffer( 31 | buffer: &wgpu::Buffer, 32 | device: &wgpu::Device, 33 | queue: &wgpu::Queue, 34 | range: impl RangeBounds, 35 | ) -> Vec { 36 | // copy buffer data 37 | let download_buffer = device.create_buffer(&wgpu::BufferDescriptor { 38 | label: Some("Download buffer"), 39 | size: buffer.size(), 40 | usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, 41 | mapped_at_creation: false, 42 | }); 43 | let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { 44 | label: Some("Copy encoder"), 45 | }); 46 | encoder.copy_buffer_to_buffer(buffer, 0, &download_buffer, 0, buffer.size()); 47 | queue.submit([encoder.finish()]); 48 | 49 | // download buffer 50 | let buffer_slice = download_buffer.slice(range); 51 | let (tx, rx) = futures_intrusive::channel::shared::oneshot_channel(); 52 | buffer_slice.map_async(wgpu::MapMode::Read, move |result| tx.send(result).unwrap()); 53 | device.poll(wgpu::Maintain::Wait); 54 | rx.receive().await.unwrap().unwrap(); 55 | 56 | let data = buffer_slice.get_mapped_range(); 57 | return bytemuck::cast_slice(data.deref()).to_vec(); 58 | } 59 | 60 | async fn test_sort(sorter: &GPUSorter, device: &wgpu::Device, queue: &wgpu::Queue) -> bool { 61 | // simply runs a small sort and check if the sorting result is correct 62 | let n = 8192; // means that 2 workgroups are needed for sorting 63 | let scrambled_data: Vec = (0..n).rev().map(|x| x as f32).collect(); 64 | let sorted_data: Vec = (0..n).map(|x| x as f32).collect(); 65 | 66 | let sort_buffers = sorter.create_sort_buffers(device, NonZeroU32::new(n).unwrap()); 67 | 68 | let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { 69 | label: Some("GPURSSorter test_sort"), 70 | }); 71 | upload_to_buffer( 72 | &mut encoder, 73 | &sort_buffers.keys(), 74 | device, 75 | scrambled_data.as_slice(), 76 | ); 77 | 78 | sorter.sort(&mut encoder, queue, &sort_buffers,None); 79 | let idx = queue.submit([encoder.finish()]); 80 | device.poll(wgpu::Maintain::WaitForSubmissionIndex(idx)); 81 | 82 | let sorted = download_buffer::( 83 | &sort_buffers.keys(), 84 | device, 85 | queue, 86 | 0..sort_buffers.keys_valid_size(), 87 | ) 88 | .await; 89 | return sorted.into_iter().zip(sorted_data.into_iter()).all(|(a,b)|a==b); 90 | } 91 | 92 | /// Function guesses the best subgroup size by testing the sorter with 93 | /// subgroup sizes 1,8,16,32,64,128 and returning the largest subgroup size that worked. 94 | pub async fn guess_workgroup_size(device: &wgpu::Device, queue: &wgpu::Queue) -> Option { 95 | let mut cur_sorter: GPUSorter; 96 | 97 | log::debug!("Searching for the maximum subgroup size (wgpu currently does not allow to query subgroup sizes)"); 98 | 99 | let mut best = None; 100 | for subgroup_size in [1, 8, 16, 32, 64, 128] { 101 | log::debug!("Checking sorting with subgroupsize {}", subgroup_size); 102 | 103 | cur_sorter = GPUSorter::new(device, subgroup_size); 104 | let sort_success = test_sort(&cur_sorter, device, queue).await; 105 | 106 | log::debug!("{} worked: {}", subgroup_size, sort_success); 107 | 108 | if !sort_success { 109 | break; 110 | } else { 111 | best = Some(subgroup_size) 112 | } 113 | } 114 | return best; 115 | } 116 | -------------------------------------------------------------------------------- /tests/sort.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Debug, num::NonZeroU32}; 2 | 3 | use bytemuck::bytes_of; 4 | use float_ord::FloatOrd; 5 | use rand::{ 6 | distributions::{Distribution, Standard}, 7 | rngs::StdRng, 8 | Rng, SeedableRng, 9 | }; 10 | use wgpu::util::DeviceExt; 11 | use wgpu_sort::{ 12 | utils::{download_buffer, guess_workgroup_size, upload_to_buffer}, 13 | GPUSorter, SortBuffers, HISTO_BLOCK_KVS, 14 | }; 15 | 16 | 17 | /// tests sorting of two u32 keys 18 | #[pollster::test] 19 | async fn sort_u32_small() { 20 | test_sort::(2,&apply_sort,None).await; 21 | } 22 | 23 | /// tests sorting of one million pairs with u32 keys 24 | #[pollster::test] 25 | async fn sort_u32_large() { 26 | test_sort::(1_000_00,&apply_sort,None).await; 27 | } 28 | 29 | /// tests sorting of one million pairs with f32 keys 30 | #[pollster::test] 31 | async fn sort_f32_large() { 32 | test_sort::(1_000_00,&apply_sort,None).await; 33 | } 34 | 35 | /// tests sorting only first half of one million pairs 36 | #[pollster::test] 37 | async fn sort_half() { 38 | test_sort::(1_000_000,&apply_sort,Some(500_00)).await; 39 | } 40 | 41 | // INDIRECT SORTING 42 | 43 | 44 | /// tests sorting of two u32 keys 45 | /// indirect dispatch 46 | #[pollster::test] 47 | async fn sort_indirect_small() { 48 | test_sort::(2,&apply_sort_indirect,None).await; 49 | } 50 | 51 | /// tests sorting of one million pairs with u32 keys 52 | /// indirect dispatch 53 | #[pollster::test] 54 | async fn sort_indirect_large() { 55 | test_sort::(1_000_00,&apply_sort,None).await; 56 | } 57 | 58 | 59 | /// tests sorting only first half of one million pairs 60 | /// indirect dispatch 61 | #[pollster::test] 62 | async fn sort_indirect_half() { 63 | test_sort::(1_000_000,&apply_sort_indirect,Some(500_00)).await; 64 | } 65 | 66 | 67 | 68 | async fn setup() -> (wgpu::Device, wgpu::Queue) { 69 | let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); 70 | 71 | let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None) 72 | .await 73 | .unwrap(); 74 | 75 | let (device, queue) = adapter 76 | .request_device( 77 | &wgpu::DeviceDescriptor { 78 | required_features: wgpu::Features::empty(), 79 | required_limits: wgpu::Limits::default(), 80 | label: None, 81 | }, 82 | None, 83 | ) 84 | .await 85 | .unwrap(); 86 | 87 | return (device, queue); 88 | } 89 | 90 | type SortFn = dyn Fn(&mut wgpu::CommandEncoder,&wgpu::Device,&wgpu::Queue,&GPUSorter,&SortBuffers,Option)->(); 91 | 92 | 93 | /// applies gpu sort with direct dispatch 94 | fn apply_sort(encoder:&mut wgpu::CommandEncoder,_device:&wgpu::Device,queue:&wgpu::Queue,sorter:&GPUSorter,sort_buffers:&SortBuffers,n:Option){ 95 | sorter.sort(encoder, queue,&sort_buffers,n); 96 | } 97 | 98 | 99 | /// applies gpu sort with indirect dispatch 100 | fn apply_sort_indirect(encoder:&mut wgpu::CommandEncoder,device:&wgpu::Device,queue:&wgpu::Queue,sorter:&GPUSorter,sort_buffers:&SortBuffers,n:Option){ 101 | 102 | // round to next larger multiple of HISTO_BLOCK_KVS 103 | let nelm = n.unwrap_or(sort_buffers.len()); 104 | let num_wg = (nelm + HISTO_BLOCK_KVS- 1)/HISTO_BLOCK_KVS; 105 | 106 | let dispatch_indirect = wgpu::util::DispatchIndirectArgs{ 107 | x: num_wg, 108 | y: 1, 109 | z: 1 110 | }; 111 | 112 | queue.write_buffer(sort_buffers.state_buffer(), 0, bytes_of(&nelm)); 113 | 114 | let dispatch_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ 115 | label: Some("dispatch indirect buffer"), 116 | contents: dispatch_indirect.as_bytes(), 117 | usage: wgpu::BufferUsages::INDIRECT, 118 | }); 119 | 120 | sorter.sort_indirect(encoder, &sort_buffers,&dispatch_buffer); 121 | } 122 | 123 | async fn test_sort(n: u32,sort_fn:&SortFn,sort_first_n:Option) 124 | where 125 | Standard: Distribution, 126 | T: PartialEq + Clone + Copy + Debug + bytemuck::Pod + Ord 127 | { 128 | let (device, queue) = setup().await; 129 | let subgroup_size = guess_workgroup_size(&device, &queue).await; 130 | assert_ne!(subgroup_size, None); 131 | let sorter = GPUSorter::new(&device, subgroup_size.unwrap()); 132 | 133 | let sort_buffers = sorter.create_sort_buffers(&device, NonZeroU32::new(n).unwrap()); 134 | let n_sorted = sort_first_n.unwrap_or(sort_buffers.len()); 135 | 136 | 137 | let mut rng = StdRng::seed_from_u64(0); 138 | let keys_scrambled: Vec = (0..n).map(|_| rng.gen()).collect(); 139 | let mut keys_sorted = keys_scrambled.clone(); 140 | keys_sorted[0..n_sorted as usize].sort(); 141 | 142 | 143 | let values_scrambled = keys_scrambled.clone(); 144 | let values_sorted = keys_sorted.clone(); 145 | 146 | let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { 147 | label: Some("GPURSSorter test_sort"), 148 | }); 149 | 150 | upload_to_buffer( 151 | &mut encoder, 152 | &sort_buffers.keys(), 153 | &device, 154 | keys_scrambled.as_slice(), 155 | ); 156 | upload_to_buffer( 157 | &mut encoder, 158 | &sort_buffers.values(), 159 | &device, 160 | values_scrambled.as_slice(), 161 | ); 162 | 163 | // sorter.sort(&mut encoder, &sort_buffers); 164 | sort_fn(&mut encoder,&device,&queue,&sorter,&sort_buffers,sort_first_n); 165 | 166 | let idx = queue.submit([encoder.finish()]); 167 | device.poll(wgpu::Maintain::WaitForSubmissionIndex(idx)); 168 | 169 | let keys_sorted_gpu = download_buffer::( 170 | &sort_buffers.keys(), 171 | &device, 172 | &queue, 173 | 0..sort_buffers.keys_valid_size(), 174 | ) 175 | .await; 176 | assert_eq!( 177 | keys_sorted_gpu[0..n_sorted as usize], keys_sorted[0..n_sorted as usize], 178 | "GPU keys equal to keys sorted on CPU" 179 | ); 180 | 181 | let values_sorted_gpu = download_buffer::(&sort_buffers.values(), &device, &queue, ..).await; 182 | assert_eq!( 183 | values_sorted_gpu[0..n_sorted as usize], values_sorted[0..n_sorted as usize], 184 | "GPU values equal to values sorted on CPU" 185 | ); 186 | } 187 | 188 | 189 | // ordered float 190 | #[repr(C)] 191 | #[derive(PartialEq,Debug,Clone, Copy,bytemuck::Pod,bytemuck::Zeroable)] 192 | struct Float(f32); 193 | 194 | impl Eq for Float{} 195 | 196 | impl Ord for Float{ 197 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 198 | FloatOrd(self.0).cmp(&FloatOrd(other.0)) 199 | } 200 | } 201 | 202 | impl PartialOrd for Float{ 203 | fn partial_cmp(&self, other: &Self) -> Option { 204 | self.0.partial_cmp(&other.0) 205 | } 206 | } 207 | impl Distribution for Standard { 208 | fn sample(&self, rng: &mut R) -> Float { 209 | Float(rng.gen()) 210 | } 211 | } -------------------------------------------------------------------------------- /src/radix_sort.wgsl: -------------------------------------------------------------------------------- 1 | // shader implementing gpu radix sort. More information in the beginning of gpu_rs.rs 2 | // info: 3 | 4 | // also the workgroup sizes are added in these prepasses 5 | // before the pipeline is started the following constant definitionis are prepended to this shadercode 6 | 7 | // const histogram_sg_size 8 | // const histogram_wg_size 9 | // const rs_radix_log2 10 | // const rs_radix_size 11 | // const rs_keyval_size 12 | // const rs_histogram_block_rows 13 | // const rs_scatter_block_rows 14 | 15 | struct GeneralInfo { 16 | num_keys: u32, 17 | padded_size: u32, 18 | even_pass: u32, 19 | odd_pass: u32, 20 | }; 21 | 22 | @group(0) @binding(0) 23 | var infos: GeneralInfo; 24 | @group(0) @binding(1) 25 | var histograms : array>; 26 | @group(0) @binding(2) 27 | var keys : array; 28 | @group(0) @binding(3) 29 | var keys_b : array; 30 | @group(0) @binding(4) 31 | var payload_a : array; 32 | @group(0) @binding(5) 33 | var payload_b : array; 34 | 35 | // layout of the histograms buffer 36 | // +---------------------------------+ <-- 0 37 | // | histograms[keyval_size] | 38 | // +---------------------------------+ <-- keyval_size * histo_size 39 | // | partitions[scatter_blocks_ru-1] | 40 | // +---------------------------------+ <-- (keyval_size + scatter_blocks_ru - 1) * histo_size 41 | // | workgroup_ids[keyval_size] | 42 | // +---------------------------------+ <-- (keyval_size + scatter_blocks_ru - 1) * histo_size + workgroup_ids_size 43 | 44 | // -------------------------------------------------------------------------------------------------------------- 45 | // Filling histograms and keys with default values (also resets the pass infos for odd and even scattering) 46 | // -------------------------------------------------------------------------------------------------------------- 47 | @compute @workgroup_size({histogram_wg_size}) 48 | fn zero_histograms(@builtin(global_invocation_id) gid: vec3, @builtin(num_workgroups) nwg: vec3) { 49 | if gid.x == 0u { 50 | infos.even_pass = 0u; 51 | infos.odd_pass = 1u; // has to be one, as on the first call to even pass + 1 % 2 is calculated 52 | } 53 | // here the histograms are set to zero and the partitions are set to 0xfffffffff to avoid sorting problems 54 | let scatter_wg_size = histogram_wg_size; 55 | let scatter_block_kvs = scatter_wg_size * rs_scatter_block_rows; 56 | let scatter_blocks_ru = (infos.num_keys + scatter_block_kvs - 1u) / scatter_block_kvs; 57 | 58 | let histo_size = rs_radix_size; 59 | var n = (rs_keyval_size + scatter_blocks_ru - 1u) * histo_size; 60 | let b = n; 61 | if infos.num_keys < infos.padded_size { 62 | n += infos.padded_size - infos.num_keys; 63 | } 64 | 65 | let line_size = nwg.x * {histogram_wg_size}u; 66 | for (var cur_index = gid.x; cur_index < n; cur_index += line_size){ 67 | if cur_index >= n { 68 | return; 69 | } 70 | 71 | if cur_index < rs_keyval_size * histo_size { 72 | atomicStore(&histograms[cur_index], 0u); 73 | } 74 | else if cur_index < b { 75 | atomicStore(&histograms[cur_index], 0u); 76 | } 77 | else { 78 | keys[infos.num_keys + cur_index - b] = 0xFFFFFFFFu; 79 | } 80 | } 81 | } 82 | 83 | // -------------------------------------------------------------------------------------------------------------- 84 | // Calculating the histograms 85 | // -------------------------------------------------------------------------------------------------------------- 86 | var smem : array, rs_radix_size>; 87 | var kv : array; 88 | fn zero_smem(lid: u32) { 89 | if lid < rs_radix_size { 90 | atomicStore(&smem[lid], 0u); 91 | } 92 | } 93 | fn histogram_pass(pass_: u32, lid: u32) { 94 | zero_smem(lid); 95 | workgroupBarrier(); 96 | 97 | for (var j = 0u; j < rs_histogram_block_rows; j++) { 98 | let u_val = bitcast(kv[j]); 99 | let digit = extractBits(u_val, pass_ * rs_radix_log2, rs_radix_log2); 100 | atomicAdd(&smem[digit], 1u); 101 | } 102 | 103 | workgroupBarrier(); 104 | let histogram_offset = rs_radix_size * pass_ + lid; 105 | if lid < rs_radix_size && atomicLoad(&smem[lid]) >= 0u { 106 | atomicAdd(&histograms[histogram_offset], atomicLoad(&smem[lid])); 107 | } 108 | } 109 | 110 | // the workgrpu_size can be gotten on the cpu by by calling pipeline.get_bind_group_layout(0).unwrap().get_local_workgroup_size(); 111 | fn fill_kv(wid: u32, lid: u32) { 112 | let rs_block_keyvals: u32 = rs_histogram_block_rows * histogram_wg_size; 113 | let kv_in_offset = wid * rs_block_keyvals + lid; 114 | for (var i = 0u; i < rs_histogram_block_rows; i++) { 115 | let pos = kv_in_offset + i * histogram_wg_size; 116 | kv[i] = keys[pos]; 117 | } 118 | } 119 | fn fill_kv_keys_b(wid: u32, lid: u32) { 120 | let rs_block_keyvals: u32 = rs_histogram_block_rows * histogram_wg_size; 121 | let kv_in_offset = wid * rs_block_keyvals + lid; 122 | for (var i = 0u; i < rs_histogram_block_rows; i++) { 123 | let pos = kv_in_offset + i * histogram_wg_size; 124 | kv[i] = keys_b[pos]; 125 | } 126 | } 127 | @compute @workgroup_size({histogram_wg_size}) 128 | fn calculate_histogram(@builtin(workgroup_id) wid: vec3, @builtin(local_invocation_id) lid: vec3) { 129 | // efficient loading of multiple values 130 | fill_kv(wid.x, lid.x); 131 | 132 | // Accumulate and store histograms for passes 133 | histogram_pass(3u, lid.x); 134 | histogram_pass(2u, lid.x); 135 | histogram_pass(1u, lid.x); 136 | histogram_pass(0u, lid.x); 137 | } 138 | 139 | // -------------------------------------------------------------------------------------------------------------- 140 | // Prefix sum over histogram 141 | // -------------------------------------------------------------------------------------------------------------- 142 | fn prefix_reduce_smem(lid: u32) { 143 | var offset = 1u; 144 | for (var d = rs_radix_size >> 1u; d > 0u; d = d >> 1u) { // sum in place tree 145 | workgroupBarrier(); 146 | if lid < d { 147 | let ai = offset * (2u * lid + 1u) - 1u; 148 | let bi = offset * (2u * lid + 2u) - 1u; 149 | atomicAdd(&smem[bi], atomicLoad(&smem[ai])); 150 | } 151 | offset = offset << 1u; 152 | } 153 | 154 | if lid == 0u { 155 | atomicStore(&smem[rs_radix_size - 1u], 0u); 156 | } // clear the last element 157 | 158 | for (var d = 1u; d < rs_radix_size; d = d << 1u) { 159 | offset = offset >> 1u; 160 | workgroupBarrier(); 161 | if lid < d { 162 | let ai = offset * (2u * lid + 1u) - 1u; 163 | let bi = offset * (2u * lid + 2u) - 1u; 164 | 165 | let t = atomicLoad(&smem[ai]); 166 | atomicStore(&smem[ai], atomicLoad(&smem[bi])); 167 | atomicAdd(&smem[bi], t); 168 | } 169 | } 170 | } 171 | @compute @workgroup_size({prefix_wg_size}) 172 | fn prefix_histogram(@builtin(workgroup_id) wid: vec3, @builtin(local_invocation_id) lid: vec3) { 173 | // the work group id is the pass, and is inverted in the next line, such that pass 3 is at the first position in the histogram buffer 174 | let histogram_base = (rs_keyval_size - 1u - wid.x) * rs_radix_size; 175 | let histogram_offset = histogram_base + lid.x; 176 | 177 | // the following coode now corresponds to the prefix calc code in fuchsia/../shaders/prefix.h 178 | // however the implementation is taken from https://www.eecs.umich.edu/courses/eecs570/hw/parprefix.pdf listing 2 (better overview, nw subgroup arithmetic) 179 | // this also means that only half the amount of workgroups is spawned (one workgroup calculates for 2 positioons) 180 | // the smemory is used from the previous section 181 | atomicStore(&smem[lid.x], atomicLoad(&histograms[histogram_offset])); 182 | atomicStore(&smem[lid.x + {prefix_wg_size}u], atomicLoad(&histograms[histogram_offset + {prefix_wg_size}u])); 183 | 184 | prefix_reduce_smem(lid.x); 185 | workgroupBarrier(); 186 | 187 | atomicStore(&histograms[histogram_offset], atomicLoad(&smem[lid.x])); 188 | atomicStore(&histograms[histogram_offset + {prefix_wg_size}u], atomicLoad(&smem[lid.x + {prefix_wg_size}u])); 189 | } 190 | 191 | // -------------------------------------------------------------------------------------------------------------- 192 | // Scattering the keys 193 | // -------------------------------------------------------------------------------------------------------------- 194 | // General note: Only 2 sweeps needed here 195 | var scatter_smem: array; // note: rs_mem_dwords is caclulated in the beginngin of gpu_rs.rs 196 | // | Dwords | Bytes 197 | // ----------+-------------------------------------------+-------- 198 | // Lookback | 256 | 1 KB 199 | // Histogram | 256 | 1 KB 200 | // Prefix | 4-84 | 16-336 201 | // Reorder | RS_WORKGROUP_SIZE * RS_SCATTER_BLOCK_ROWS | 2-8 KB 202 | fn partitions_base_offset() -> u32 { return rs_keyval_size * rs_radix_size;} 203 | fn smem_prefix_offset() -> u32 { return rs_radix_size + rs_radix_size;} 204 | fn rs_prefix_sweep_0(idx: u32) -> u32 { return scatter_smem[smem_prefix_offset() + rs_mem_sweep_0_offset + idx];} 205 | fn rs_prefix_sweep_1(idx: u32) -> u32 { return scatter_smem[smem_prefix_offset() + rs_mem_sweep_1_offset + idx];} 206 | fn rs_prefix_sweep_2(idx: u32) -> u32 { return scatter_smem[smem_prefix_offset() + rs_mem_sweep_2_offset + idx];} 207 | fn rs_prefix_load(lid: u32, idx: u32) -> u32 { return scatter_smem[rs_radix_size + lid + idx];} 208 | fn rs_prefix_store(lid: u32, idx: u32, val: u32) { scatter_smem[rs_radix_size + lid + idx] = val;} 209 | fn is_first_local_invocation(lid: u32) -> bool { return lid == 0u;} 210 | 211 | fn histogram_load(digit: u32) -> u32 { 212 | return atomicLoad(&smem[digit]); 213 | } 214 | 215 | fn histogram_store(digit: u32, count: u32) { 216 | atomicStore(&smem[digit], count); 217 | } 218 | 219 | 220 | const rs_partition_mask_status : u32 = 0xC0000000u; 221 | const rs_partition_mask_count : u32 = 0x3FFFFFFFu; 222 | var kr : array; 223 | var pv : array; 224 | 225 | fn fill_kv_even(wid: u32, lid: u32) { 226 | let subgroup_id = lid / histogram_sg_size; 227 | let subgroup_invoc_id = lid - subgroup_id * histogram_sg_size; 228 | let subgroup_keyvals = rs_scatter_block_rows * histogram_sg_size; 229 | let rs_block_keyvals: u32 = rs_histogram_block_rows * histogram_wg_size; 230 | let kv_in_offset = wid * rs_block_keyvals + subgroup_id * subgroup_keyvals + subgroup_invoc_id; 231 | for (var i = 0u; i < rs_histogram_block_rows; i++) { 232 | let pos = kv_in_offset + i * histogram_sg_size; 233 | kv[i] = keys[pos]; 234 | } 235 | for (var i = 0u; i < rs_histogram_block_rows; i++) { 236 | let pos = kv_in_offset + i * histogram_sg_size; 237 | pv[i] = payload_a[pos]; 238 | } 239 | } 240 | 241 | fn fill_kv_odd(wid: u32, lid: u32) { 242 | let subgroup_id = lid / histogram_sg_size; 243 | let subgroup_invoc_id = lid - subgroup_id * histogram_sg_size; 244 | let subgroup_keyvals = rs_scatter_block_rows * histogram_sg_size; 245 | let rs_block_keyvals: u32 = rs_histogram_block_rows * histogram_wg_size; 246 | let kv_in_offset = wid * rs_block_keyvals + subgroup_id * subgroup_keyvals + subgroup_invoc_id; 247 | for (var i = 0u; i < rs_histogram_block_rows; i++) { 248 | let pos = kv_in_offset + i * histogram_sg_size; 249 | kv[i] = keys_b[pos]; 250 | } 251 | for (var i = 0u; i < rs_histogram_block_rows; i++) { 252 | let pos = kv_in_offset + i * histogram_sg_size; 253 | pv[i] = payload_b[pos]; 254 | } 255 | } 256 | fn scatter(pass_: u32, lid: vec3, gid: vec3, wid: vec3, nwg: vec3, partition_status_invalid: u32, partition_status_reduction: u32, partition_status_prefix: u32) { 257 | let partition_mask_invalid = partition_status_invalid << 30u; 258 | let partition_mask_reduction = partition_status_reduction << 30u; 259 | let partition_mask_prefix = partition_status_prefix << 30u; 260 | // kv_filling is done in the scatter_even and scatter_odd functions to account for front and backbuffer switch 261 | // in the reference there is a nulling of the smmem here, was moved to line 251 as smem is used in the code until then 262 | 263 | // The following implements conceptually the same as the 264 | // Emulate a "match" operation with broadcasts for small subgroup sizes (line 665 ff in scatter.glsl) 265 | // The difference however is, that instead of using subrgoupBroadcast each thread stores 266 | // its current number in the smem at lid.x, and then looks up their neighbouring values of the subgroup 267 | let subgroup_id = lid.x / histogram_sg_size; 268 | let subgroup_offset = subgroup_id * histogram_sg_size; 269 | let subgroup_tid = lid.x - subgroup_offset; 270 | let subgroup_count = {scatter_wg_size}u / histogram_sg_size; 271 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 272 | let u_val = bitcast(kv[i]); 273 | let digit = extractBits(u_val, pass_ * rs_radix_log2, rs_radix_log2); 274 | atomicStore(&smem[lid.x], digit); 275 | var count = 0u; 276 | var rank = 0u; 277 | 278 | for (var j = 0u; j < histogram_sg_size; j++) { 279 | if atomicLoad(&smem[subgroup_offset + j]) == digit { 280 | count += 1u; 281 | if j <= subgroup_tid { 282 | rank += 1u; 283 | } 284 | } 285 | } 286 | 287 | kr[i] = (count << 16u) | rank; 288 | } 289 | 290 | zero_smem(lid.x); // now zeroing the smmem as we are now accumulating the histogram there 291 | workgroupBarrier(); 292 | 293 | // The final histogram is stored in the smem buffer 294 | for (var i = 0u; i < subgroup_count; i++) { 295 | if subgroup_id == i { 296 | for (var j = 0u; j < rs_scatter_block_rows; j++) { 297 | let v = bitcast(kv[j]); 298 | let digit = extractBits(v, pass_ * rs_radix_log2, rs_radix_log2); 299 | let prev = histogram_load(digit); 300 | let rank = kr[j] & 0xFFFFu; 301 | let count = kr[j] >> 16u; 302 | kr[j] = prev + rank; 303 | 304 | if rank == count { 305 | histogram_store(digit, (prev + count)); 306 | } 307 | 308 | // TODO: check if the barrier here is needed 309 | } 310 | } 311 | workgroupBarrier(); 312 | } 313 | // kr filling is now done and contains the total offset for each value to be able to 314 | // move the values into order without having any collisions 315 | 316 | // we do not check for single work groups (is currently not assumed to occur very often) 317 | let partition_offset = lid.x + partitions_base_offset(); // is correct, the partitions pointer does not change 318 | let partition_base = wid.x * rs_radix_size; 319 | if wid.x == 0u { 320 | // special treating for the first workgroup as the data might be read back by later workgroups 321 | // corresponds to rs_first_prefix_store 322 | let hist_offset = pass_ * rs_radix_size + lid.x; 323 | if lid.x < rs_radix_size { 324 | // let exc = histograms[hist_offset]; 325 | let exc = atomicLoad(&histograms[hist_offset]); 326 | let red = histogram_load(lid.x);// scatter_smem[rs_keyval_size + lid.x]; 327 | 328 | scatter_smem[lid.x] = exc; 329 | 330 | let inc = exc + red; 331 | 332 | atomicStore(&histograms[partition_offset], inc | partition_mask_prefix); 333 | } 334 | } 335 | else { 336 | // standard case for the "inbetween" workgroups 337 | 338 | // rs_reduction_store, only for inbetween workgroups 339 | if lid.x < rs_radix_size && wid.x < nwg.x - 1u { 340 | let red = histogram_load(lid.x); 341 | atomicStore(&histograms[partition_offset + partition_base], red | partition_mask_reduction); 342 | } 343 | 344 | // rs_loopback_store 345 | if lid.x < rs_radix_size { 346 | var partition_base_prev = partition_base - rs_radix_size; 347 | var exc = 0u; 348 | 349 | // Note: Each workgroup invocation can proceed independently. 350 | // Subgroups and workgroups do NOT have to coordinate. 351 | while true { 352 | //let prev = atomicLoad(&histograms[partition_offset]);// histograms[partition_offset + partition_base_prev]; 353 | let prev = atomicLoad(&histograms[partition_base_prev + partition_offset]);// histograms[partition_offset + partition_base_prev]; 354 | if (prev & rs_partition_mask_status) == partition_mask_invalid { 355 | continue; 356 | } 357 | exc += prev & rs_partition_mask_count; 358 | if (prev & rs_partition_mask_status) != partition_mask_prefix { 359 | // continue accumulating reduction 360 | partition_base_prev -= rs_radix_size; 361 | continue; 362 | } 363 | 364 | // otherwise save the exclusive scan and atomically transform the 365 | // reduction into an inclusive prefix status math: reduction + 1 = prefix 366 | scatter_smem[lid.x] = exc; 367 | 368 | if wid.x < nwg.x - 1u { // only store when inbetween, skip for last workgrup 369 | atomicAdd(&histograms[partition_offset + partition_base], exc | (1u << 30u)); 370 | } 371 | break; 372 | } 373 | } 374 | } 375 | // special case for last workgroup is also done in the "inbetween" case 376 | 377 | // compute exclusive prefix scan of histogram 378 | // corresponds to rs_prefix 379 | // TODO make sure that the data is put into smem 380 | prefix_reduce_smem(lid.x); 381 | workgroupBarrier(); 382 | 383 | // convert keyval rank to local index, corresponds to rs_rank_to_local 384 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 385 | let v = bitcast(kv[i]); 386 | let digit = extractBits(v, pass_ * rs_radix_log2, rs_radix_log2); 387 | let exc = histogram_load(digit); 388 | let idx = exc + kr[i]; 389 | 390 | kr[i] |= (idx << 16u); 391 | } 392 | workgroupBarrier(); 393 | 394 | // reorder kv[] and kr[], corresponds to rs_reorder 395 | let smem_reorder_offset = rs_radix_size; 396 | let smem_base = smem_reorder_offset + lid.x; // as we are in smem, the radix_size offset is not needed 397 | 398 | // keyvalues ---------------------------------------------- 399 | // store keyval to sorted location 400 | for (var j = 0u; j < rs_scatter_block_rows; j++) { 401 | let smem_idx = smem_reorder_offset + (kr[j] >> 16u) - 1u; 402 | 403 | scatter_smem[smem_idx] = bitcast(kv[j]); 404 | } 405 | workgroupBarrier(); 406 | 407 | // Load keyval dword from sorted location 408 | for (var j = 0u; j < rs_scatter_block_rows; j++) { 409 | kv[j] = scatter_smem[smem_base + j * {scatter_wg_size}u]; 410 | } 411 | workgroupBarrier(); 412 | // payload ---------------------------------------------- 413 | // store payload to sorted location 414 | for (var j = 0u; j < rs_scatter_block_rows; j++) { 415 | let smem_idx = smem_reorder_offset + (kr[j] >> 16u) - 1u; 416 | 417 | scatter_smem[smem_idx] = pv[j]; 418 | } 419 | workgroupBarrier(); 420 | 421 | // Load payload dword from sorted location 422 | for (var j = 0u; j < rs_scatter_block_rows; j++) { 423 | pv[j] = scatter_smem[smem_base + j * {scatter_wg_size}u]; 424 | } 425 | workgroupBarrier(); 426 | 427 | // store the digit-index to sorted location 428 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 429 | let smem_idx = smem_reorder_offset + (kr[i] >> 16u) - 1u; 430 | scatter_smem[smem_idx] = kr[i]; 431 | } 432 | workgroupBarrier(); 433 | 434 | // Load kr[] from sorted location -- we only need the rank 435 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 436 | kr[i] = scatter_smem[smem_base + i * {scatter_wg_size}u] & 0xFFFFu; 437 | } 438 | 439 | // convert local index to a global index, corresponds to rs_local_to_global 440 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 441 | let v = bitcast(kv[i]); 442 | let digit = extractBits(v, pass_ * rs_radix_log2, rs_radix_log2); 443 | let exc = scatter_smem[digit]; 444 | 445 | kr[i] += exc - 1u; 446 | } 447 | 448 | // the storing is done in the scatter_even and scatter_odd functions as the front and back buffer changes 449 | } 450 | 451 | @compute @workgroup_size({scatter_wg_size}) 452 | fn scatter_even(@builtin(workgroup_id) wid: vec3, @builtin(local_invocation_id) lid: vec3, @builtin(global_invocation_id) gid: vec3, @builtin(num_workgroups) nwg: vec3) { 453 | if gid.x == 0u { 454 | infos.odd_pass = (infos.odd_pass + 1u) % 2u; // for this to work correctly the odd_pass has to start 1 455 | } 456 | let cur_pass = infos.even_pass * 2u; 457 | 458 | // load from keys, store to keys_b 459 | fill_kv_even(wid.x, lid.x); 460 | 461 | let partition_status_invalid = 0u; 462 | let partition_status_reduction = 1u; 463 | let partition_status_prefix = 2u; 464 | scatter(cur_pass, lid, gid, wid, nwg, partition_status_invalid, partition_status_reduction, partition_status_prefix); 465 | 466 | // store keyvals to their new locations, corresponds to rs_store 467 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 468 | keys_b[kr[i]] = kv[i]; 469 | } 470 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 471 | payload_b[kr[i]] = pv[i]; 472 | } 473 | } 474 | @compute @workgroup_size({scatter_wg_size}) 475 | fn scatter_odd(@builtin(workgroup_id) wid: vec3, @builtin(local_invocation_id) lid: vec3, @builtin(global_invocation_id) gid: vec3, @builtin(num_workgroups) nwg: vec3) { 476 | if gid.x == 0u { 477 | infos.even_pass = (infos.even_pass + 1u) % 2u; // for this to work correctly the even_pass has to start at 0 478 | } 479 | let cur_pass = infos.odd_pass * 2u + 1u; 480 | 481 | // load from keys_b, store to keys 482 | fill_kv_odd(wid.x, lid.x); 483 | 484 | let partition_status_invalid = 2u; 485 | let partition_status_reduction = 3u; 486 | let partition_status_prefix = 0u; 487 | scatter(cur_pass, lid, gid, wid, nwg, partition_status_invalid, partition_status_reduction, partition_status_prefix); 488 | 489 | // store keyvals to their new locations, corresponds to rs_store 490 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 491 | keys[kr[i]] = kv[i]; 492 | } 493 | for (var i = 0u; i < rs_scatter_block_rows; i++) { 494 | payload_a[kr[i]] = pv[i]; 495 | } 496 | 497 | // the indirect buffer is reset after scattering via write buffer, see record_scatter_indirect for details 498 | } 499 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | /* 3 | This file implements a gpu version of radix sort. A good introduction to general purpose radix sort can 4 | be found here: http://www.codercorner.com/RadixSortRevisited.htm 5 | 6 | The gpu radix sort implemented here is a re-implementation of the Vulkan radix sort found in the fuchsia repos: https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/graphics/lib/compute/radix_sort/ 7 | Currently only the sorting for 32-bit key-value pairs is implemented 8 | 9 | All shaders can be found in radix_sort.wgsl 10 | */ 11 | 12 | use std::{ 13 | mem, 14 | num::{NonZeroU32, NonZeroU64}, 15 | }; 16 | pub mod utils; 17 | 18 | use bytemuck::bytes_of; 19 | use wgpu::{util::DeviceExt, ComputePassDescriptor}; 20 | 21 | // IMPORTANT: the following constants have to be synced with the numbers in radix_sort.wgsl 22 | 23 | /// workgroup size of histogram shader 24 | const HISTOGRAM_WG_SIZE: u32 = 256; 25 | 26 | /// one thread operates on 2 prefixes at the same time 27 | const PREFIX_WG_SIZE: u32 = 1 << 7; 28 | 29 | /// scatter compute shader work group size 30 | const SCATTER_WG_SIZE: u32 = 1 << 8; 31 | 32 | /// we sort 8 bits per pass 33 | const RS_RADIX_LOG2: u32 = 8; 34 | 35 | /// 256 entries into the radix table 36 | const RS_RADIX_SIZE: u32 = 1 << RS_RADIX_LOG2; 37 | 38 | /// number of bytes our keys and values have 39 | const RS_KEYVAL_SIZE: u32 = 32 / RS_RADIX_LOG2; 40 | 41 | /// TODO describe me 42 | const RS_HISTOGRAM_BLOCK_ROWS: u32 = 15; 43 | 44 | /// DO NOT CHANGE, shader assume this!!! 45 | const RS_SCATTER_BLOCK_ROWS: u32 = RS_HISTOGRAM_BLOCK_ROWS; 46 | 47 | /// number of elements scattered by one work group 48 | const SCATTER_BLOCK_KVS: u32 = HISTOGRAM_WG_SIZE * RS_SCATTER_BLOCK_ROWS; 49 | 50 | /// number of elements scattered by one work group 51 | pub const HISTO_BLOCK_KVS: u32 = HISTOGRAM_WG_SIZE * RS_HISTOGRAM_BLOCK_ROWS; 52 | 53 | /// bytes per value 54 | /// currently only 4 byte values are allowed 55 | const BYTES_PER_PAYLOAD_ELEM: u32 = 4; 56 | 57 | /// number of passed used for sorting 58 | /// we sort 8 bits per pass so 4 passes are required for a 32 bit value 59 | const NUM_PASSES: u32 = BYTES_PER_PAYLOAD_ELEM; 60 | 61 | 62 | /// Sorting pipeline. It can be used to sort key-value pairs stored in [SortBuffers] 63 | pub struct GPUSorter { 64 | zero_p: wgpu::ComputePipeline, 65 | histogram_p: wgpu::ComputePipeline, 66 | prefix_p: wgpu::ComputePipeline, 67 | scatter_even_p: wgpu::ComputePipeline, 68 | scatter_odd_p: wgpu::ComputePipeline, 69 | } 70 | 71 | impl GPUSorter { 72 | pub fn new(device: &wgpu::Device, subgroup_size: u32) -> Self { 73 | // special variables for scatter shade 74 | let histogram_sg_size = subgroup_size; 75 | let rs_sweep_0_size = RS_RADIX_SIZE / histogram_sg_size; 76 | let rs_sweep_1_size = rs_sweep_0_size / histogram_sg_size; 77 | let rs_sweep_2_size = rs_sweep_1_size / histogram_sg_size; 78 | let rs_sweep_size = rs_sweep_0_size + rs_sweep_1_size + rs_sweep_2_size; 79 | let _rs_smem_phase_1 = RS_RADIX_SIZE + RS_RADIX_SIZE + rs_sweep_size; 80 | let rs_smem_phase_2 = RS_RADIX_SIZE + RS_SCATTER_BLOCK_ROWS * SCATTER_WG_SIZE; 81 | // rs_smem_phase_2 will always be larger, so always use phase2 82 | let rs_mem_dwords = rs_smem_phase_2; 83 | let rs_mem_sweep_0_offset = 0; 84 | let rs_mem_sweep_1_offset = rs_mem_sweep_0_offset + rs_sweep_0_size; 85 | let rs_mem_sweep_2_offset = rs_mem_sweep_1_offset + rs_sweep_1_size; 86 | 87 | let bind_group_layout = Self::bind_group_layout(device); 88 | 89 | let pipeline_layout: wgpu::PipelineLayout = 90 | device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 91 | label: Some("radix sort pipeline layout"), 92 | bind_group_layouts: &[&bind_group_layout], 93 | push_constant_ranges: &[], 94 | }); 95 | 96 | let raw_shader: &str = include_str!("radix_sort.wgsl"); 97 | 98 | // TODO replace with this with pipeline-overridable constants once they are available 99 | let shader_w_const = format!( 100 | "const histogram_sg_size: u32 = {:}u;\n\ 101 | const histogram_wg_size: u32 = {:}u;\n\ 102 | const rs_radix_log2: u32 = {:}u;\n\ 103 | const rs_radix_size: u32 = {:}u;\n\ 104 | const rs_keyval_size: u32 = {:}u;\n\ 105 | const rs_histogram_block_rows: u32 = {:}u;\n\ 106 | const rs_scatter_block_rows: u32 = {:}u;\n\ 107 | const rs_mem_dwords: u32 = {:}u;\n\ 108 | const rs_mem_sweep_0_offset: u32 = {:}u;\n\ 109 | const rs_mem_sweep_1_offset: u32 = {:}u;\n\ 110 | const rs_mem_sweep_2_offset: u32 = {:}u;\n{:}", 111 | histogram_sg_size, 112 | HISTOGRAM_WG_SIZE, 113 | RS_RADIX_LOG2, 114 | RS_RADIX_SIZE, 115 | RS_KEYVAL_SIZE, 116 | RS_HISTOGRAM_BLOCK_ROWS, 117 | RS_SCATTER_BLOCK_ROWS, 118 | rs_mem_dwords, 119 | rs_mem_sweep_0_offset, 120 | rs_mem_sweep_1_offset, 121 | rs_mem_sweep_2_offset, 122 | raw_shader 123 | ); 124 | let shader_code = shader_w_const 125 | .replace( 126 | "{histogram_wg_size}", 127 | HISTOGRAM_WG_SIZE.to_string().as_str(), 128 | ) 129 | .replace("{prefix_wg_size}", PREFIX_WG_SIZE.to_string().as_str()) 130 | .replace("{scatter_wg_size}", SCATTER_WG_SIZE.to_string().as_str()); 131 | 132 | let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { 133 | label: Some("Radix sort shader"), 134 | source: wgpu::ShaderSource::Wgsl(shader_code.into()), 135 | }); 136 | let zero_p = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { 137 | label: Some("Zero the histograms"), 138 | layout: Some(&pipeline_layout), 139 | module: &shader, 140 | entry_point: "zero_histograms", 141 | compilation_options: Default::default(), 142 | }); 143 | let histogram_p = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { 144 | label: Some("calculate_histogram"), 145 | layout: Some(&pipeline_layout), 146 | module: &shader, 147 | entry_point: "calculate_histogram", 148 | compilation_options: Default::default(), 149 | }); 150 | let prefix_p = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { 151 | label: Some("prefix_histogram"), 152 | layout: Some(&pipeline_layout), 153 | module: &shader, 154 | entry_point: "prefix_histogram", 155 | compilation_options: Default::default(), 156 | }); 157 | let scatter_even_p = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { 158 | label: Some("scatter_even"), 159 | layout: Some(&pipeline_layout), 160 | module: &shader, 161 | entry_point: "scatter_even", 162 | compilation_options: Default::default(), 163 | }); 164 | let scatter_odd_p = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { 165 | label: Some("scatter_odd"), 166 | layout: Some(&pipeline_layout), 167 | module: &shader, 168 | entry_point: "scatter_odd", 169 | compilation_options: Default::default(), 170 | }); 171 | 172 | return Self { 173 | zero_p, 174 | histogram_p, 175 | prefix_p, 176 | scatter_even_p, 177 | scatter_odd_p, 178 | }; 179 | } 180 | 181 | fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { 182 | return device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 183 | label: Some("radix sort bind group layout"), 184 | entries: &[ 185 | wgpu::BindGroupLayoutEntry { 186 | binding: 0, 187 | visibility: wgpu::ShaderStages::COMPUTE, 188 | ty: wgpu::BindingType::Buffer { 189 | ty: wgpu::BufferBindingType::Storage { read_only: false }, 190 | has_dynamic_offset: false, 191 | min_binding_size: Some( 192 | NonZeroU64::new(mem::size_of::() as u64).unwrap(), 193 | ), 194 | }, 195 | count: None, 196 | }, 197 | wgpu::BindGroupLayoutEntry { 198 | binding: 1, 199 | visibility: wgpu::ShaderStages::COMPUTE, 200 | ty: wgpu::BindingType::Buffer { 201 | ty: wgpu::BufferBindingType::Storage { read_only: false }, 202 | has_dynamic_offset: false, 203 | min_binding_size: None, 204 | }, 205 | count: None, 206 | }, 207 | wgpu::BindGroupLayoutEntry { 208 | binding: 2, 209 | visibility: wgpu::ShaderStages::COMPUTE, 210 | ty: wgpu::BindingType::Buffer { 211 | ty: wgpu::BufferBindingType::Storage { read_only: false }, 212 | has_dynamic_offset: false, 213 | min_binding_size: None, 214 | }, 215 | count: None, 216 | }, 217 | wgpu::BindGroupLayoutEntry { 218 | binding: 3, 219 | visibility: wgpu::ShaderStages::COMPUTE, 220 | ty: wgpu::BindingType::Buffer { 221 | ty: wgpu::BufferBindingType::Storage { read_only: false }, 222 | has_dynamic_offset: false, 223 | min_binding_size: None, 224 | }, 225 | count: None, 226 | }, 227 | wgpu::BindGroupLayoutEntry { 228 | binding: 4, 229 | visibility: wgpu::ShaderStages::COMPUTE, 230 | ty: wgpu::BindingType::Buffer { 231 | ty: wgpu::BufferBindingType::Storage { read_only: false }, 232 | has_dynamic_offset: false, 233 | min_binding_size: None, 234 | }, 235 | count: None, 236 | }, 237 | wgpu::BindGroupLayoutEntry { 238 | binding: 5, 239 | visibility: wgpu::ShaderStages::COMPUTE, 240 | ty: wgpu::BindingType::Buffer { 241 | ty: wgpu::BufferBindingType::Storage { read_only: false }, 242 | has_dynamic_offset: false, 243 | min_binding_size: None, 244 | }, 245 | count: None, 246 | }, 247 | ], 248 | }); 249 | } 250 | 251 | fn create_keyval_buffers( 252 | device: &wgpu::Device, 253 | length: u32, 254 | ) -> (wgpu::Buffer, wgpu::Buffer, wgpu::Buffer, wgpu::Buffer) { 255 | // add padding so that our buffer size is a multiple of keys_per_workgroup 256 | let count_ru_histo = keys_buffer_size(length) * RS_KEYVAL_SIZE; 257 | 258 | // creating the two needed buffers for sorting 259 | let keys = device.create_buffer(&wgpu::BufferDescriptor { 260 | label: Some("radix sort keys buffer"), 261 | size: (count_ru_histo * BYTES_PER_PAYLOAD_ELEM) as u64, 262 | usage: wgpu::BufferUsages::STORAGE 263 | | wgpu::BufferUsages::COPY_DST 264 | | wgpu::BufferUsages::COPY_SRC, 265 | mapped_at_creation: false, 266 | }); 267 | 268 | // auxiliary buffer for keys 269 | let keys_aux = device.create_buffer(&wgpu::BufferDescriptor { 270 | label: Some("radix sort keys auxiliary buffer"), 271 | size: (count_ru_histo * BYTES_PER_PAYLOAD_ELEM) as u64, 272 | usage: wgpu::BufferUsages::STORAGE, 273 | mapped_at_creation: false, 274 | }); 275 | 276 | let payload_size = length * BYTES_PER_PAYLOAD_ELEM; // make sure that we have at least 1 byte of data; 277 | let payload = device.create_buffer(&wgpu::BufferDescriptor { 278 | label: Some("radix sort payload buffer"), 279 | size: payload_size as u64, 280 | usage: wgpu::BufferUsages::STORAGE 281 | | wgpu::BufferUsages::COPY_DST 282 | | wgpu::BufferUsages::COPY_SRC, 283 | mapped_at_creation: false, 284 | }); 285 | // auxiliary buffer for payload/values 286 | let payload_aux = device.create_buffer(&wgpu::BufferDescriptor { 287 | label: Some("radix sort payload auxiliary buffer"), 288 | size: payload_size as u64, 289 | usage: wgpu::BufferUsages::STORAGE, 290 | mapped_at_creation: false, 291 | }); 292 | return (keys, keys_aux, payload, payload_aux); 293 | } 294 | 295 | // calculates and allocates a buffer that is sufficient for holding all needed information for 296 | // sorting. This includes the histograms and the temporary scatter buffer 297 | // @return: tuple containing [internal memory buffer (should be bound at shader binding 1, count_ru_histo (padded size needed for the keyval buffer)] 298 | fn create_internal_mem_buffer(&self, device: &wgpu::Device, length: u32) -> wgpu::Buffer { 299 | // currently only a few different key bits are supported, maybe has to be extended 300 | 301 | // The "internal" memory map looks like this: 302 | // +---------------------------------+ <-- 0 303 | // | histograms[keyval_size] | 304 | // +---------------------------------+ <-- keyval_size * histo_size 305 | // | partitions[scatter_blocks_ru-1] | 306 | // +---------------------------------+ <-- (keyval_size + scatter_blocks_ru - 1) * histo_size 307 | // | workgroup_ids[keyval_size] | 308 | // +---------------------------------+ <-- (keyval_size + scatter_blocks_ru - 1) * histo_size + workgroup_ids_size 309 | 310 | let scatter_blocks_ru = scatter_blocks_ru(length); 311 | 312 | let histo_size = RS_RADIX_SIZE * std::mem::size_of::() as u32; 313 | 314 | let internal_size = (RS_KEYVAL_SIZE + scatter_blocks_ru) * histo_size; // +1 safety 315 | 316 | let buffer = device.create_buffer(&wgpu::BufferDescriptor { 317 | label: Some("Internal radix sort buffer"), 318 | size: internal_size as u64, 319 | usage: wgpu::BufferUsages::STORAGE, 320 | mapped_at_creation: false, 321 | }); 322 | return buffer; 323 | } 324 | 325 | fn general_info_data(length: u32) -> SorterState { 326 | SorterState { 327 | num_keys: length, 328 | padded_size: keys_buffer_size(length), 329 | even_pass: 0, 330 | odd_pass: 0, 331 | } 332 | } 333 | 334 | fn record_calculate_histogram( 335 | &self, 336 | bind_group: &wgpu::BindGroup, 337 | length: u32, 338 | encoder: &mut wgpu::CommandEncoder, 339 | ) { 340 | // as we only deal with 32 bit float values always 4 passes are conducted 341 | let hist_blocks_ru = histo_blocks_ru(length); 342 | 343 | { 344 | let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor { 345 | label: Some("zeroing the histogram"), 346 | timestamp_writes: None, 347 | }); 348 | 349 | pass.set_pipeline(&self.zero_p); 350 | pass.set_bind_group(0, bind_group, &[]); 351 | pass.dispatch_workgroups(hist_blocks_ru as u32, 1, 1); 352 | } 353 | 354 | { 355 | let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor { 356 | label: Some("calculate histogram"), 357 | timestamp_writes: None, 358 | }); 359 | 360 | pass.set_pipeline(&self.histogram_p); 361 | pass.set_bind_group(0, bind_group, &[]); 362 | pass.dispatch_workgroups(hist_blocks_ru as u32, 1, 1); 363 | } 364 | } 365 | 366 | fn record_calculate_histogram_indirect( 367 | &self, 368 | bind_group: &wgpu::BindGroup, 369 | dispatch_buffer: &wgpu::Buffer, 370 | encoder: &mut wgpu::CommandEncoder, 371 | ) { 372 | { 373 | let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor { 374 | label: Some("zeroing the histogram"), 375 | timestamp_writes: None, 376 | }); 377 | 378 | pass.set_pipeline(&self.zero_p); 379 | pass.set_bind_group(0, bind_group, &[]); 380 | pass.dispatch_workgroups_indirect(dispatch_buffer, 0); 381 | } 382 | 383 | { 384 | let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor { 385 | label: Some("calculate histogram"), 386 | timestamp_writes: None, 387 | }); 388 | 389 | pass.set_pipeline(&self.histogram_p); 390 | pass.set_bind_group(0, bind_group, &[]); 391 | pass.dispatch_workgroups_indirect(dispatch_buffer, 0); 392 | } 393 | } 394 | 395 | // There does not exist an indirect histogram dispatch as the number of prefixes is determined by the amount of passes 396 | fn record_prefix_histogram( 397 | &self, 398 | bind_group: &wgpu::BindGroup, 399 | encoder: &mut wgpu::CommandEncoder, 400 | ) { 401 | let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { 402 | label: Some("prefix histogram"), 403 | timestamp_writes: None, 404 | }); 405 | 406 | pass.set_pipeline(&self.prefix_p); 407 | pass.set_bind_group(0, &bind_group, &[]); 408 | pass.dispatch_workgroups(NUM_PASSES as u32, 1, 1); 409 | } 410 | 411 | fn record_scatter_keys( 412 | &self, 413 | bind_group: &wgpu::BindGroup, 414 | length: u32, 415 | encoder: &mut wgpu::CommandEncoder, 416 | ) { 417 | let scatter_blocks_ru = scatter_blocks_ru(length); 418 | 419 | let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { 420 | label: Some("Scatter keyvals"), 421 | timestamp_writes: None, 422 | }); 423 | 424 | pass.set_bind_group(0, bind_group, &[]); 425 | pass.set_pipeline(&self.scatter_even_p); 426 | pass.dispatch_workgroups(scatter_blocks_ru as u32, 1, 1); 427 | 428 | pass.set_pipeline(&self.scatter_odd_p); 429 | pass.dispatch_workgroups(scatter_blocks_ru as u32, 1, 1); 430 | 431 | pass.set_pipeline(&self.scatter_even_p); 432 | pass.dispatch_workgroups(scatter_blocks_ru as u32, 1, 1); 433 | 434 | pass.set_pipeline(&self.scatter_odd_p); 435 | pass.dispatch_workgroups(scatter_blocks_ru as u32, 1, 1); 436 | } 437 | 438 | fn record_scatter_keys_indirect( 439 | &self, 440 | bind_group: &wgpu::BindGroup, 441 | dispatch_buffer: &wgpu::Buffer, 442 | encoder: &mut wgpu::CommandEncoder, 443 | ) { 444 | let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { 445 | label: Some("radix sort scatter keyvals"), 446 | timestamp_writes: None, 447 | }); 448 | 449 | pass.set_bind_group(0, bind_group, &[]); 450 | pass.set_pipeline(&self.scatter_even_p); 451 | pass.dispatch_workgroups_indirect(dispatch_buffer, 0); 452 | 453 | pass.set_pipeline(&self.scatter_odd_p); 454 | pass.dispatch_workgroups_indirect(dispatch_buffer, 0); 455 | 456 | pass.set_pipeline(&self.scatter_even_p); 457 | pass.dispatch_workgroups_indirect(dispatch_buffer, 0); 458 | 459 | pass.set_pipeline(&self.scatter_odd_p); 460 | pass.dispatch_workgroups_indirect(dispatch_buffer, 0); 461 | } 462 | 463 | 464 | /// Writes sort commands to command encoder. 465 | /// If sort_first_n is not none one the first n elements are sorted 466 | /// otherwise everything is sorted. 467 | /// 468 | /// **IMPORTANT**: if less than the whole buffer is sorted the rest of the keys buffer will be be corrupted 469 | pub fn sort(&self, encoder: &mut wgpu::CommandEncoder,queue:&wgpu::Queue, sort_buffers: &SortBuffers, sort_first_n:Option) { 470 | let bind_group = &sort_buffers.bind_group; 471 | let num_elements = sort_first_n.unwrap_or(sort_buffers.len()); 472 | 473 | // write number of elements to buffer 474 | queue.write_buffer(&sort_buffers.state_buffer, 0, bytes_of(&num_elements)); 475 | 476 | 477 | self.record_calculate_histogram(bind_group, num_elements, encoder); 478 | self.record_prefix_histogram(bind_group, encoder); 479 | self.record_scatter_keys(bind_group, num_elements, encoder); 480 | } 481 | 482 | /// Initiates sorting with an indirect call. 483 | /// The dispatch buffer must contain the struct [wgpu::util::DispatchIndirectArgs]. 484 | /// 485 | /// number of y and z workgroups must be 1 486 | /// 487 | /// x = (N + [HISTO_BLOCK_KVS]- 1 )/[HISTO_BLOCK_KVS], 488 | /// where N are the first N elements to be sorted 489 | /// 490 | /// [SortBuffers::state_buffer] contains the number of keys that will be sorted. 491 | /// This is set to sort the whole buffer by default. 492 | /// 493 | /// **IMPORTANT**: if less than the whole buffer is sorted the rest of the keys buffer will most likely be corrupted. 494 | pub fn sort_indirect( 495 | &self, 496 | encoder: &mut wgpu::CommandEncoder, 497 | sort_buffers: &SortBuffers, 498 | dispatch_buffer: &wgpu::Buffer, 499 | ) { 500 | let bind_group = &sort_buffers.bind_group; 501 | 502 | self.record_calculate_histogram_indirect(bind_group, dispatch_buffer, encoder); 503 | self.record_prefix_histogram(bind_group, encoder); 504 | self.record_scatter_keys_indirect(bind_group, dispatch_buffer, encoder); 505 | } 506 | 507 | /// creates all buffers necessary for sorting 508 | pub fn create_sort_buffers(&self, device: &wgpu::Device, length: NonZeroU32) -> SortBuffers { 509 | let length = length.get(); 510 | 511 | let (keys_a, keys_b, payload_a, payload_b) = 512 | GPUSorter::create_keyval_buffers(&device, length); 513 | let internal_mem_buffer = self.create_internal_mem_buffer(&device, length); 514 | 515 | let uniform_infos = Self::general_info_data(length); 516 | let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 517 | label: Some("radix sort uniform buffer"), 518 | contents: bytemuck::bytes_of(&uniform_infos), 519 | usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, 520 | }); 521 | let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 522 | label: Some("radix sort bind group"), 523 | layout: &Self::bind_group_layout(device), 524 | entries: &[ 525 | wgpu::BindGroupEntry { 526 | binding: 0, 527 | resource: uniform_buffer.as_entire_binding(), 528 | }, 529 | wgpu::BindGroupEntry { 530 | binding: 1, 531 | resource: internal_mem_buffer.as_entire_binding(), 532 | }, 533 | wgpu::BindGroupEntry { 534 | binding: 2, 535 | resource: keys_a.as_entire_binding(), 536 | }, 537 | wgpu::BindGroupEntry { 538 | binding: 3, 539 | resource: keys_b.as_entire_binding(), 540 | }, 541 | wgpu::BindGroupEntry { 542 | binding: 4, 543 | resource: payload_a.as_entire_binding(), 544 | }, 545 | wgpu::BindGroupEntry { 546 | binding: 5, 547 | resource: payload_b.as_entire_binding(), 548 | }, 549 | ], 550 | }); 551 | // return (uniform_buffer, bind_group); 552 | SortBuffers { 553 | keys_a, 554 | keys_b, 555 | payload_a, 556 | payload_b, 557 | internal_mem_buffer, 558 | state_buffer: uniform_buffer, 559 | bind_group, 560 | length, 561 | } 562 | } 563 | } 564 | 565 | 566 | /// Struct containing information about the state of the sorter. 567 | #[repr(C)] 568 | #[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] 569 | pub struct SorterState { 570 | /// number of first n keys that will be sorted 571 | pub num_keys: u32, 572 | padded_size: u32, 573 | even_pass: u32, 574 | odd_pass: u32, 575 | } 576 | 577 | /// Struct containing all buffers necessary for sorting. 578 | /// The key and value buffers can be read and written. 579 | pub struct SortBuffers { 580 | /// keys that are sorted 581 | keys_a: wgpu::Buffer, 582 | /// intermediate key buffer for sorting 583 | #[allow(dead_code)] 584 | keys_b: wgpu::Buffer, 585 | /// value/payload buffer that is sorted 586 | payload_a: wgpu::Buffer, 587 | /// intermediate value buffer for sorting 588 | #[allow(dead_code)] 589 | payload_b: wgpu::Buffer, 590 | 591 | /// buffer used to store intermediate results like histograms and scatter partitions 592 | #[allow(dead_code)] 593 | internal_mem_buffer: wgpu::Buffer, 594 | 595 | /// state buffer used for sorting 596 | state_buffer: wgpu::Buffer, 597 | 598 | /// bind group used for sorting 599 | bind_group: wgpu::BindGroup, 600 | 601 | // number of key-value pairs 602 | length: u32, 603 | } 604 | 605 | impl SortBuffers { 606 | /// number of key-value pairs that can be stored in this buffer 607 | pub fn len(&self) -> u32 { 608 | self.length 609 | } 610 | 611 | /// Buffer storing the keys values. 612 | /// 613 | /// **WARNING**: this buffer has padding bytes at the end 614 | /// use [SortBuffers::keys_valid_size] to get the valid size. 615 | pub fn keys(&self) -> &wgpu::Buffer { 616 | &self.keys_a 617 | } 618 | 619 | /// The keys buffer has padding bytes. 620 | /// This function returns the number of bytes without padding 621 | pub fn keys_valid_size(&self) -> u64 { 622 | (self.len() * RS_KEYVAL_SIZE) as u64 623 | } 624 | 625 | /// Buffer containing the values 626 | pub fn values(&self) -> &wgpu::Buffer { 627 | &self.payload_a 628 | } 629 | 630 | /// Buffer containing a [SorterState] 631 | pub fn state_buffer(&self)->&wgpu::Buffer{ 632 | &self.state_buffer 633 | } 634 | } 635 | 636 | fn scatter_blocks_ru(n: u32) -> u32 { 637 | (n + SCATTER_BLOCK_KVS - 1) / SCATTER_BLOCK_KVS 638 | } 639 | 640 | /// number of histogram blocks required 641 | fn histo_blocks_ru(n: u32) -> u32 { 642 | (scatter_blocks_ru(n) * SCATTER_BLOCK_KVS + HISTO_BLOCK_KVS - 1) / HISTO_BLOCK_KVS 643 | } 644 | 645 | /// keys buffer must be multiple of HISTO_BLOCK_KVS 646 | fn keys_buffer_size(n: u32) -> u32 { 647 | histo_blocks_ru(n) * HISTO_BLOCK_KVS 648 | } 649 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "allocator-api2" 28 | version = "0.2.18" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 31 | 32 | [[package]] 33 | name = "android_system_properties" 34 | version = "0.1.5" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 37 | dependencies = [ 38 | "libc", 39 | ] 40 | 41 | [[package]] 42 | name = "anes" 43 | version = "0.1.6" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 46 | 47 | [[package]] 48 | name = "anstream" 49 | version = "0.6.14" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 52 | dependencies = [ 53 | "anstyle", 54 | "anstyle-parse", 55 | "anstyle-query", 56 | "anstyle-wincon", 57 | "colorchoice", 58 | "is_terminal_polyfill", 59 | "utf8parse", 60 | ] 61 | 62 | [[package]] 63 | name = "anstyle" 64 | version = "1.0.7" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 67 | 68 | [[package]] 69 | name = "anstyle-parse" 70 | version = "0.2.4" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 73 | dependencies = [ 74 | "utf8parse", 75 | ] 76 | 77 | [[package]] 78 | name = "anstyle-query" 79 | version = "1.1.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 82 | dependencies = [ 83 | "windows-sys", 84 | ] 85 | 86 | [[package]] 87 | name = "anstyle-wincon" 88 | version = "3.0.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 91 | dependencies = [ 92 | "anstyle", 93 | "windows-sys", 94 | ] 95 | 96 | [[package]] 97 | name = "arrayvec" 98 | version = "0.7.4" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 101 | 102 | [[package]] 103 | name = "ash" 104 | version = "0.37.3+1.3.251" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" 107 | dependencies = [ 108 | "libloading 0.7.4", 109 | ] 110 | 111 | [[package]] 112 | name = "atty" 113 | version = "0.2.14" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 116 | dependencies = [ 117 | "hermit-abi", 118 | "libc", 119 | "winapi", 120 | ] 121 | 122 | [[package]] 123 | name = "autocfg" 124 | version = "1.3.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 127 | 128 | [[package]] 129 | name = "bit-set" 130 | version = "0.5.3" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 133 | dependencies = [ 134 | "bit-vec", 135 | ] 136 | 137 | [[package]] 138 | name = "bit-vec" 139 | version = "0.6.3" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 142 | 143 | [[package]] 144 | name = "bitflags" 145 | version = "1.3.2" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 148 | 149 | [[package]] 150 | name = "bitflags" 151 | version = "2.6.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 154 | 155 | [[package]] 156 | name = "block" 157 | version = "0.1.6" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 160 | 161 | [[package]] 162 | name = "bumpalo" 163 | version = "3.16.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 166 | 167 | [[package]] 168 | name = "bytemuck" 169 | version = "1.16.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" 172 | dependencies = [ 173 | "bytemuck_derive", 174 | ] 175 | 176 | [[package]] 177 | name = "bytemuck_derive" 178 | version = "1.7.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" 181 | dependencies = [ 182 | "proc-macro2", 183 | "quote", 184 | "syn 2.0.71", 185 | ] 186 | 187 | [[package]] 188 | name = "cast" 189 | version = "0.3.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 192 | 193 | [[package]] 194 | name = "cfg-if" 195 | version = "1.0.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 198 | 199 | [[package]] 200 | name = "cfg_aliases" 201 | version = "0.1.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 204 | 205 | [[package]] 206 | name = "ciborium" 207 | version = "0.2.2" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 210 | dependencies = [ 211 | "ciborium-io", 212 | "ciborium-ll", 213 | "serde", 214 | ] 215 | 216 | [[package]] 217 | name = "ciborium-io" 218 | version = "0.2.2" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 221 | 222 | [[package]] 223 | name = "ciborium-ll" 224 | version = "0.2.2" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 227 | dependencies = [ 228 | "ciborium-io", 229 | "half", 230 | ] 231 | 232 | [[package]] 233 | name = "clap" 234 | version = "3.2.25" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 237 | dependencies = [ 238 | "bitflags 1.3.2", 239 | "clap_lex", 240 | "indexmap 1.9.3", 241 | "textwrap", 242 | ] 243 | 244 | [[package]] 245 | name = "clap_lex" 246 | version = "0.2.4" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 249 | dependencies = [ 250 | "os_str_bytes", 251 | ] 252 | 253 | [[package]] 254 | name = "codespan-reporting" 255 | version = "0.11.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 258 | dependencies = [ 259 | "termcolor", 260 | "unicode-width", 261 | ] 262 | 263 | [[package]] 264 | name = "colorchoice" 265 | version = "1.0.1" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 268 | 269 | [[package]] 270 | name = "com" 271 | version = "0.6.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" 274 | dependencies = [ 275 | "com_macros", 276 | ] 277 | 278 | [[package]] 279 | name = "com_macros" 280 | version = "0.6.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" 283 | dependencies = [ 284 | "com_macros_support", 285 | "proc-macro2", 286 | "syn 1.0.109", 287 | ] 288 | 289 | [[package]] 290 | name = "com_macros_support" 291 | version = "0.6.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" 294 | dependencies = [ 295 | "proc-macro2", 296 | "quote", 297 | "syn 1.0.109", 298 | ] 299 | 300 | [[package]] 301 | name = "core-foundation" 302 | version = "0.9.4" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 305 | dependencies = [ 306 | "core-foundation-sys", 307 | "libc", 308 | ] 309 | 310 | [[package]] 311 | name = "core-foundation-sys" 312 | version = "0.8.6" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 315 | 316 | [[package]] 317 | name = "core-graphics-types" 318 | version = "0.1.3" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 321 | dependencies = [ 322 | "bitflags 1.3.2", 323 | "core-foundation", 324 | "libc", 325 | ] 326 | 327 | [[package]] 328 | name = "criterion" 329 | version = "0.4.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" 332 | dependencies = [ 333 | "anes", 334 | "atty", 335 | "cast", 336 | "ciborium", 337 | "clap", 338 | "criterion-plot", 339 | "itertools", 340 | "lazy_static", 341 | "num-traits", 342 | "oorandom", 343 | "plotters", 344 | "rayon", 345 | "regex", 346 | "serde", 347 | "serde_derive", 348 | "serde_json", 349 | "tinytemplate", 350 | "walkdir", 351 | ] 352 | 353 | [[package]] 354 | name = "criterion-plot" 355 | version = "0.5.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 358 | dependencies = [ 359 | "cast", 360 | "itertools", 361 | ] 362 | 363 | [[package]] 364 | name = "crossbeam-deque" 365 | version = "0.8.5" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 368 | dependencies = [ 369 | "crossbeam-epoch", 370 | "crossbeam-utils", 371 | ] 372 | 373 | [[package]] 374 | name = "crossbeam-epoch" 375 | version = "0.9.18" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 378 | dependencies = [ 379 | "crossbeam-utils", 380 | ] 381 | 382 | [[package]] 383 | name = "crossbeam-utils" 384 | version = "0.8.20" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 387 | 388 | [[package]] 389 | name = "crunchy" 390 | version = "0.2.2" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 393 | 394 | [[package]] 395 | name = "d3d12" 396 | version = "0.20.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813" 399 | dependencies = [ 400 | "bitflags 2.6.0", 401 | "libloading 0.8.4", 402 | "winapi", 403 | ] 404 | 405 | [[package]] 406 | name = "document-features" 407 | version = "0.2.10" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" 410 | dependencies = [ 411 | "litrs", 412 | ] 413 | 414 | [[package]] 415 | name = "either" 416 | version = "1.13.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 419 | 420 | [[package]] 421 | name = "env_filter" 422 | version = "0.1.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" 425 | dependencies = [ 426 | "log", 427 | "regex", 428 | ] 429 | 430 | [[package]] 431 | name = "env_logger" 432 | version = "0.11.3" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" 435 | dependencies = [ 436 | "anstream", 437 | "anstyle", 438 | "env_filter", 439 | "humantime", 440 | "log", 441 | ] 442 | 443 | [[package]] 444 | name = "equivalent" 445 | version = "1.0.1" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 448 | 449 | [[package]] 450 | name = "float-ord" 451 | version = "0.3.2" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" 454 | 455 | [[package]] 456 | name = "foreign-types" 457 | version = "0.5.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 460 | dependencies = [ 461 | "foreign-types-macros", 462 | "foreign-types-shared", 463 | ] 464 | 465 | [[package]] 466 | name = "foreign-types-macros" 467 | version = "0.2.3" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 470 | dependencies = [ 471 | "proc-macro2", 472 | "quote", 473 | "syn 2.0.71", 474 | ] 475 | 476 | [[package]] 477 | name = "foreign-types-shared" 478 | version = "0.3.1" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 481 | 482 | [[package]] 483 | name = "futures-core" 484 | version = "0.3.30" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 487 | 488 | [[package]] 489 | name = "futures-intrusive" 490 | version = "0.5.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" 493 | dependencies = [ 494 | "futures-core", 495 | "lock_api", 496 | "parking_lot", 497 | ] 498 | 499 | [[package]] 500 | name = "getrandom" 501 | version = "0.2.15" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 504 | dependencies = [ 505 | "cfg-if", 506 | "libc", 507 | "wasi", 508 | ] 509 | 510 | [[package]] 511 | name = "gl_generator" 512 | version = "0.14.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" 515 | dependencies = [ 516 | "khronos_api", 517 | "log", 518 | "xml-rs", 519 | ] 520 | 521 | [[package]] 522 | name = "glow" 523 | version = "0.13.1" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" 526 | dependencies = [ 527 | "js-sys", 528 | "slotmap", 529 | "wasm-bindgen", 530 | "web-sys", 531 | ] 532 | 533 | [[package]] 534 | name = "glutin_wgl_sys" 535 | version = "0.5.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" 538 | dependencies = [ 539 | "gl_generator", 540 | ] 541 | 542 | [[package]] 543 | name = "gpu-alloc" 544 | version = "0.6.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" 547 | dependencies = [ 548 | "bitflags 2.6.0", 549 | "gpu-alloc-types", 550 | ] 551 | 552 | [[package]] 553 | name = "gpu-alloc-types" 554 | version = "0.3.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" 557 | dependencies = [ 558 | "bitflags 2.6.0", 559 | ] 560 | 561 | [[package]] 562 | name = "gpu-allocator" 563 | version = "0.25.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" 566 | dependencies = [ 567 | "log", 568 | "presser", 569 | "thiserror", 570 | "winapi", 571 | "windows", 572 | ] 573 | 574 | [[package]] 575 | name = "gpu-descriptor" 576 | version = "0.3.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" 579 | dependencies = [ 580 | "bitflags 2.6.0", 581 | "gpu-descriptor-types", 582 | "hashbrown 0.14.5", 583 | ] 584 | 585 | [[package]] 586 | name = "gpu-descriptor-types" 587 | version = "0.2.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" 590 | dependencies = [ 591 | "bitflags 2.6.0", 592 | ] 593 | 594 | [[package]] 595 | name = "half" 596 | version = "2.4.1" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" 599 | dependencies = [ 600 | "cfg-if", 601 | "crunchy", 602 | ] 603 | 604 | [[package]] 605 | name = "hashbrown" 606 | version = "0.12.3" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 609 | 610 | [[package]] 611 | name = "hashbrown" 612 | version = "0.14.5" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 615 | dependencies = [ 616 | "ahash", 617 | "allocator-api2", 618 | ] 619 | 620 | [[package]] 621 | name = "hassle-rs" 622 | version = "0.11.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" 625 | dependencies = [ 626 | "bitflags 2.6.0", 627 | "com", 628 | "libc", 629 | "libloading 0.8.4", 630 | "thiserror", 631 | "widestring", 632 | "winapi", 633 | ] 634 | 635 | [[package]] 636 | name = "hermit-abi" 637 | version = "0.1.19" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 640 | dependencies = [ 641 | "libc", 642 | ] 643 | 644 | [[package]] 645 | name = "hexf-parse" 646 | version = "0.2.1" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" 649 | 650 | [[package]] 651 | name = "humantime" 652 | version = "2.1.0" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 655 | 656 | [[package]] 657 | name = "indexmap" 658 | version = "1.9.3" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 661 | dependencies = [ 662 | "autocfg", 663 | "hashbrown 0.12.3", 664 | ] 665 | 666 | [[package]] 667 | name = "indexmap" 668 | version = "2.2.6" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 671 | dependencies = [ 672 | "equivalent", 673 | "hashbrown 0.14.5", 674 | ] 675 | 676 | [[package]] 677 | name = "is_terminal_polyfill" 678 | version = "1.70.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 681 | 682 | [[package]] 683 | name = "itertools" 684 | version = "0.10.5" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 687 | dependencies = [ 688 | "either", 689 | ] 690 | 691 | [[package]] 692 | name = "itoa" 693 | version = "1.0.11" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 696 | 697 | [[package]] 698 | name = "jni-sys" 699 | version = "0.3.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 702 | 703 | [[package]] 704 | name = "js-sys" 705 | version = "0.3.69" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 708 | dependencies = [ 709 | "wasm-bindgen", 710 | ] 711 | 712 | [[package]] 713 | name = "khronos-egl" 714 | version = "6.0.0" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" 717 | dependencies = [ 718 | "libc", 719 | "libloading 0.8.4", 720 | "pkg-config", 721 | ] 722 | 723 | [[package]] 724 | name = "khronos_api" 725 | version = "3.1.0" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 728 | 729 | [[package]] 730 | name = "lazy_static" 731 | version = "1.5.0" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 734 | 735 | [[package]] 736 | name = "libc" 737 | version = "0.2.155" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 740 | 741 | [[package]] 742 | name = "libloading" 743 | version = "0.7.4" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 746 | dependencies = [ 747 | "cfg-if", 748 | "winapi", 749 | ] 750 | 751 | [[package]] 752 | name = "libloading" 753 | version = "0.8.4" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" 756 | dependencies = [ 757 | "cfg-if", 758 | "windows-targets", 759 | ] 760 | 761 | [[package]] 762 | name = "litrs" 763 | version = "0.4.1" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" 766 | 767 | [[package]] 768 | name = "lock_api" 769 | version = "0.4.12" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 772 | dependencies = [ 773 | "autocfg", 774 | "scopeguard", 775 | ] 776 | 777 | [[package]] 778 | name = "log" 779 | version = "0.4.22" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 782 | 783 | [[package]] 784 | name = "malloc_buf" 785 | version = "0.0.6" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 788 | dependencies = [ 789 | "libc", 790 | ] 791 | 792 | [[package]] 793 | name = "memchr" 794 | version = "2.7.4" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 797 | 798 | [[package]] 799 | name = "metal" 800 | version = "0.28.0" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" 803 | dependencies = [ 804 | "bitflags 2.6.0", 805 | "block", 806 | "core-graphics-types", 807 | "foreign-types", 808 | "log", 809 | "objc", 810 | "paste", 811 | ] 812 | 813 | [[package]] 814 | name = "naga" 815 | version = "0.20.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" 818 | dependencies = [ 819 | "arrayvec", 820 | "bit-set", 821 | "bitflags 2.6.0", 822 | "codespan-reporting", 823 | "hexf-parse", 824 | "indexmap 2.2.6", 825 | "log", 826 | "num-traits", 827 | "rustc-hash", 828 | "spirv", 829 | "termcolor", 830 | "thiserror", 831 | "unicode-xid", 832 | ] 833 | 834 | [[package]] 835 | name = "ndk-sys" 836 | version = "0.5.0+25.2.9519653" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" 839 | dependencies = [ 840 | "jni-sys", 841 | ] 842 | 843 | [[package]] 844 | name = "num-traits" 845 | version = "0.2.19" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 848 | dependencies = [ 849 | "autocfg", 850 | ] 851 | 852 | [[package]] 853 | name = "objc" 854 | version = "0.2.7" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 857 | dependencies = [ 858 | "malloc_buf", 859 | ] 860 | 861 | [[package]] 862 | name = "once_cell" 863 | version = "1.19.0" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 866 | 867 | [[package]] 868 | name = "oorandom" 869 | version = "11.1.4" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" 872 | 873 | [[package]] 874 | name = "os_str_bytes" 875 | version = "6.6.1" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 878 | 879 | [[package]] 880 | name = "parking_lot" 881 | version = "0.12.3" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 884 | dependencies = [ 885 | "lock_api", 886 | "parking_lot_core", 887 | ] 888 | 889 | [[package]] 890 | name = "parking_lot_core" 891 | version = "0.9.10" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 894 | dependencies = [ 895 | "cfg-if", 896 | "libc", 897 | "redox_syscall", 898 | "smallvec", 899 | "windows-targets", 900 | ] 901 | 902 | [[package]] 903 | name = "paste" 904 | version = "1.0.15" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 907 | 908 | [[package]] 909 | name = "pkg-config" 910 | version = "0.3.30" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 913 | 914 | [[package]] 915 | name = "plotters" 916 | version = "0.3.6" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" 919 | dependencies = [ 920 | "num-traits", 921 | "plotters-backend", 922 | "plotters-svg", 923 | "wasm-bindgen", 924 | "web-sys", 925 | ] 926 | 927 | [[package]] 928 | name = "plotters-backend" 929 | version = "0.3.6" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" 932 | 933 | [[package]] 934 | name = "plotters-svg" 935 | version = "0.3.6" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" 938 | dependencies = [ 939 | "plotters-backend", 940 | ] 941 | 942 | [[package]] 943 | name = "pollster" 944 | version = "0.3.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" 947 | dependencies = [ 948 | "pollster-macro", 949 | ] 950 | 951 | [[package]] 952 | name = "pollster-macro" 953 | version = "0.1.0" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "ea78f0ef4193055a4b09814ce6bcb572ad1174d6023e2f00a9ea1a798d18d301" 956 | dependencies = [ 957 | "proc-macro2", 958 | "quote", 959 | "syn 1.0.109", 960 | ] 961 | 962 | [[package]] 963 | name = "ppv-lite86" 964 | version = "0.2.17" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 967 | 968 | [[package]] 969 | name = "presser" 970 | version = "0.3.1" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" 973 | 974 | [[package]] 975 | name = "proc-macro2" 976 | version = "1.0.86" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 979 | dependencies = [ 980 | "unicode-ident", 981 | ] 982 | 983 | [[package]] 984 | name = "profiling" 985 | version = "1.0.15" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" 988 | 989 | [[package]] 990 | name = "quote" 991 | version = "1.0.36" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 994 | dependencies = [ 995 | "proc-macro2", 996 | ] 997 | 998 | [[package]] 999 | name = "rand" 1000 | version = "0.8.5" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1003 | dependencies = [ 1004 | "libc", 1005 | "rand_chacha", 1006 | "rand_core", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "rand_chacha" 1011 | version = "0.3.1" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1014 | dependencies = [ 1015 | "ppv-lite86", 1016 | "rand_core", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "rand_core" 1021 | version = "0.6.4" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1024 | dependencies = [ 1025 | "getrandom", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "range-alloc" 1030 | version = "0.1.3" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" 1033 | 1034 | [[package]] 1035 | name = "raw-window-handle" 1036 | version = "0.6.2" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" 1039 | 1040 | [[package]] 1041 | name = "rayon" 1042 | version = "1.10.0" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 1045 | dependencies = [ 1046 | "either", 1047 | "rayon-core", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "rayon-core" 1052 | version = "1.12.1" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 1055 | dependencies = [ 1056 | "crossbeam-deque", 1057 | "crossbeam-utils", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "redox_syscall" 1062 | version = "0.5.2" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" 1065 | dependencies = [ 1066 | "bitflags 2.6.0", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "regex" 1071 | version = "1.10.5" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 1074 | dependencies = [ 1075 | "aho-corasick", 1076 | "memchr", 1077 | "regex-automata", 1078 | "regex-syntax", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "regex-automata" 1083 | version = "0.4.7" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 1086 | dependencies = [ 1087 | "aho-corasick", 1088 | "memchr", 1089 | "regex-syntax", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "regex-syntax" 1094 | version = "0.8.4" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1097 | 1098 | [[package]] 1099 | name = "renderdoc-sys" 1100 | version = "1.1.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" 1103 | 1104 | [[package]] 1105 | name = "rustc-hash" 1106 | version = "1.1.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1109 | 1110 | [[package]] 1111 | name = "ryu" 1112 | version = "1.0.18" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1115 | 1116 | [[package]] 1117 | name = "same-file" 1118 | version = "1.0.6" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1121 | dependencies = [ 1122 | "winapi-util", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "scopeguard" 1127 | version = "1.2.0" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1130 | 1131 | [[package]] 1132 | name = "serde" 1133 | version = "1.0.204" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" 1136 | dependencies = [ 1137 | "serde_derive", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "serde_derive" 1142 | version = "1.0.204" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" 1145 | dependencies = [ 1146 | "proc-macro2", 1147 | "quote", 1148 | "syn 2.0.71", 1149 | ] 1150 | 1151 | [[package]] 1152 | name = "serde_json" 1153 | version = "1.0.120" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" 1156 | dependencies = [ 1157 | "itoa", 1158 | "ryu", 1159 | "serde", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "slotmap" 1164 | version = "1.0.7" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" 1167 | dependencies = [ 1168 | "version_check", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "smallvec" 1173 | version = "1.13.2" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1176 | 1177 | [[package]] 1178 | name = "spirv" 1179 | version = "0.3.0+sdk-1.3.268.0" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" 1182 | dependencies = [ 1183 | "bitflags 2.6.0", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "static_assertions" 1188 | version = "1.1.0" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1191 | 1192 | [[package]] 1193 | name = "syn" 1194 | version = "1.0.109" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1197 | dependencies = [ 1198 | "proc-macro2", 1199 | "quote", 1200 | "unicode-ident", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "syn" 1205 | version = "2.0.71" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" 1208 | dependencies = [ 1209 | "proc-macro2", 1210 | "quote", 1211 | "unicode-ident", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "termcolor" 1216 | version = "1.4.1" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1219 | dependencies = [ 1220 | "winapi-util", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "textwrap" 1225 | version = "0.16.1" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 1228 | 1229 | [[package]] 1230 | name = "thiserror" 1231 | version = "1.0.62" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" 1234 | dependencies = [ 1235 | "thiserror-impl", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "thiserror-impl" 1240 | version = "1.0.62" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" 1243 | dependencies = [ 1244 | "proc-macro2", 1245 | "quote", 1246 | "syn 2.0.71", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "tinytemplate" 1251 | version = "1.2.1" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 1254 | dependencies = [ 1255 | "serde", 1256 | "serde_json", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "unicode-ident" 1261 | version = "1.0.12" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1264 | 1265 | [[package]] 1266 | name = "unicode-width" 1267 | version = "0.1.13" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" 1270 | 1271 | [[package]] 1272 | name = "unicode-xid" 1273 | version = "0.2.4" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 1276 | 1277 | [[package]] 1278 | name = "utf8parse" 1279 | version = "0.2.2" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1282 | 1283 | [[package]] 1284 | name = "version_check" 1285 | version = "0.9.4" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1288 | 1289 | [[package]] 1290 | name = "walkdir" 1291 | version = "2.5.0" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1294 | dependencies = [ 1295 | "same-file", 1296 | "winapi-util", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "wasi" 1301 | version = "0.11.0+wasi-snapshot-preview1" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1304 | 1305 | [[package]] 1306 | name = "wasm-bindgen" 1307 | version = "0.2.92" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1310 | dependencies = [ 1311 | "cfg-if", 1312 | "wasm-bindgen-macro", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "wasm-bindgen-backend" 1317 | version = "0.2.92" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1320 | dependencies = [ 1321 | "bumpalo", 1322 | "log", 1323 | "once_cell", 1324 | "proc-macro2", 1325 | "quote", 1326 | "syn 2.0.71", 1327 | "wasm-bindgen-shared", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "wasm-bindgen-futures" 1332 | version = "0.4.42" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 1335 | dependencies = [ 1336 | "cfg-if", 1337 | "js-sys", 1338 | "wasm-bindgen", 1339 | "web-sys", 1340 | ] 1341 | 1342 | [[package]] 1343 | name = "wasm-bindgen-macro" 1344 | version = "0.2.92" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1347 | dependencies = [ 1348 | "quote", 1349 | "wasm-bindgen-macro-support", 1350 | ] 1351 | 1352 | [[package]] 1353 | name = "wasm-bindgen-macro-support" 1354 | version = "0.2.92" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1357 | dependencies = [ 1358 | "proc-macro2", 1359 | "quote", 1360 | "syn 2.0.71", 1361 | "wasm-bindgen-backend", 1362 | "wasm-bindgen-shared", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "wasm-bindgen-shared" 1367 | version = "0.2.92" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1370 | 1371 | [[package]] 1372 | name = "web-sys" 1373 | version = "0.3.69" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 1376 | dependencies = [ 1377 | "js-sys", 1378 | "wasm-bindgen", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "wgpu" 1383 | version = "0.20.1" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c" 1386 | dependencies = [ 1387 | "arrayvec", 1388 | "cfg-if", 1389 | "cfg_aliases", 1390 | "document-features", 1391 | "js-sys", 1392 | "log", 1393 | "naga", 1394 | "parking_lot", 1395 | "profiling", 1396 | "raw-window-handle", 1397 | "smallvec", 1398 | "static_assertions", 1399 | "wasm-bindgen", 1400 | "wasm-bindgen-futures", 1401 | "web-sys", 1402 | "wgpu-core", 1403 | "wgpu-hal", 1404 | "wgpu-types", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "wgpu-core" 1409 | version = "0.21.1" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39" 1412 | dependencies = [ 1413 | "arrayvec", 1414 | "bit-vec", 1415 | "bitflags 2.6.0", 1416 | "cfg_aliases", 1417 | "codespan-reporting", 1418 | "document-features", 1419 | "indexmap 2.2.6", 1420 | "log", 1421 | "naga", 1422 | "once_cell", 1423 | "parking_lot", 1424 | "profiling", 1425 | "raw-window-handle", 1426 | "rustc-hash", 1427 | "smallvec", 1428 | "thiserror", 1429 | "web-sys", 1430 | "wgpu-hal", 1431 | "wgpu-types", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "wgpu-hal" 1436 | version = "0.21.1" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222" 1439 | dependencies = [ 1440 | "android_system_properties", 1441 | "arrayvec", 1442 | "ash", 1443 | "bit-set", 1444 | "bitflags 2.6.0", 1445 | "block", 1446 | "cfg_aliases", 1447 | "core-graphics-types", 1448 | "d3d12", 1449 | "glow", 1450 | "glutin_wgl_sys", 1451 | "gpu-alloc", 1452 | "gpu-allocator", 1453 | "gpu-descriptor", 1454 | "hassle-rs", 1455 | "js-sys", 1456 | "khronos-egl", 1457 | "libc", 1458 | "libloading 0.8.4", 1459 | "log", 1460 | "metal", 1461 | "naga", 1462 | "ndk-sys", 1463 | "objc", 1464 | "once_cell", 1465 | "parking_lot", 1466 | "profiling", 1467 | "range-alloc", 1468 | "raw-window-handle", 1469 | "renderdoc-sys", 1470 | "rustc-hash", 1471 | "smallvec", 1472 | "thiserror", 1473 | "wasm-bindgen", 1474 | "web-sys", 1475 | "wgpu-types", 1476 | "winapi", 1477 | ] 1478 | 1479 | [[package]] 1480 | name = "wgpu-types" 1481 | version = "0.20.0" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" 1484 | dependencies = [ 1485 | "bitflags 2.6.0", 1486 | "js-sys", 1487 | "web-sys", 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "wgpu_sort" 1492 | version = "0.1.0" 1493 | dependencies = [ 1494 | "bytemuck", 1495 | "criterion", 1496 | "env_logger", 1497 | "float-ord", 1498 | "futures-intrusive", 1499 | "log", 1500 | "pollster", 1501 | "rand", 1502 | "wgpu", 1503 | ] 1504 | 1505 | [[package]] 1506 | name = "widestring" 1507 | version = "1.1.0" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" 1510 | 1511 | [[package]] 1512 | name = "winapi" 1513 | version = "0.3.9" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1516 | dependencies = [ 1517 | "winapi-i686-pc-windows-gnu", 1518 | "winapi-x86_64-pc-windows-gnu", 1519 | ] 1520 | 1521 | [[package]] 1522 | name = "winapi-i686-pc-windows-gnu" 1523 | version = "0.4.0" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1526 | 1527 | [[package]] 1528 | name = "winapi-util" 1529 | version = "0.1.8" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 1532 | dependencies = [ 1533 | "windows-sys", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "winapi-x86_64-pc-windows-gnu" 1538 | version = "0.4.0" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1541 | 1542 | [[package]] 1543 | name = "windows" 1544 | version = "0.52.0" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" 1547 | dependencies = [ 1548 | "windows-core", 1549 | "windows-targets", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "windows-core" 1554 | version = "0.52.0" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1557 | dependencies = [ 1558 | "windows-targets", 1559 | ] 1560 | 1561 | [[package]] 1562 | name = "windows-sys" 1563 | version = "0.52.0" 1564 | source = "registry+https://github.com/rust-lang/crates.io-index" 1565 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1566 | dependencies = [ 1567 | "windows-targets", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "windows-targets" 1572 | version = "0.52.6" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1575 | dependencies = [ 1576 | "windows_aarch64_gnullvm", 1577 | "windows_aarch64_msvc", 1578 | "windows_i686_gnu", 1579 | "windows_i686_gnullvm", 1580 | "windows_i686_msvc", 1581 | "windows_x86_64_gnu", 1582 | "windows_x86_64_gnullvm", 1583 | "windows_x86_64_msvc", 1584 | ] 1585 | 1586 | [[package]] 1587 | name = "windows_aarch64_gnullvm" 1588 | version = "0.52.6" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1591 | 1592 | [[package]] 1593 | name = "windows_aarch64_msvc" 1594 | version = "0.52.6" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1597 | 1598 | [[package]] 1599 | name = "windows_i686_gnu" 1600 | version = "0.52.6" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1603 | 1604 | [[package]] 1605 | name = "windows_i686_gnullvm" 1606 | version = "0.52.6" 1607 | source = "registry+https://github.com/rust-lang/crates.io-index" 1608 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1609 | 1610 | [[package]] 1611 | name = "windows_i686_msvc" 1612 | version = "0.52.6" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1615 | 1616 | [[package]] 1617 | name = "windows_x86_64_gnu" 1618 | version = "0.52.6" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1621 | 1622 | [[package]] 1623 | name = "windows_x86_64_gnullvm" 1624 | version = "0.52.6" 1625 | source = "registry+https://github.com/rust-lang/crates.io-index" 1626 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1627 | 1628 | [[package]] 1629 | name = "windows_x86_64_msvc" 1630 | version = "0.52.6" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1633 | 1634 | [[package]] 1635 | name = "xml-rs" 1636 | version = "0.8.20" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" 1639 | 1640 | [[package]] 1641 | name = "zerocopy" 1642 | version = "0.7.35" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1645 | dependencies = [ 1646 | "zerocopy-derive", 1647 | ] 1648 | 1649 | [[package]] 1650 | name = "zerocopy-derive" 1651 | version = "0.7.35" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1654 | dependencies = [ 1655 | "proc-macro2", 1656 | "quote", 1657 | "syn 2.0.71", 1658 | ] 1659 | --------------------------------------------------------------------------------