yum/vfft
git clone https://git.yummers.dev/yum/vfft
2f018df
master
1use naga:: GlobalVariable ; 2use num:: Complex ; 3use std:: borrow:: Cow ; 4use std:: num:: NonZeroU64 ; 5use std:: sync:: mpsc::{ Receiver , Sender }; 6use wgpu:: naga; 7 8# [ cfg ( feature = "gpu" )] 9# [ macro_export ] 10macro_rules ! include_wgsl{ 11( $filename: literal) =>{ 12include_str! ( concat! ( env! ( "OUT_DIR" ), "/shaders/" , $filename)) 13}; 14} 15 16// Magic to allow converting a primitive type, a Vec, or a slice into a slice of bytes. 17pub trait Buffer < T > { 18fn as_bytes ( & self ) ->& [ T ]; 19} 20impl < T : bytemuck:: Pod > Buffer < T > for T { 21fn as_bytes ( & self ) ->& [ T ] { 22 std:: slice:: from_ref ( self ) 23} 24} 25impl < T > Buffer < T > for Vec < T > { 26fn as_bytes ( & self ) ->& [ T ] { 27self . as_slice () 28} 29} 30impl < T > Buffer < T > for & [ T ] { 31fn as_bytes ( & self ) ->& [ T ] { 32self 33} 34} 35 36pub struct BufferBuilder < ' a > { 37label : Option < String >, 38size : u64 , 39usage : wgpu:: BufferUsages , 40mapped_at_creation : bool , 41gpu : &' a Gpu , 42} 43 44impl < ' a > BufferBuilder < ' a > { 45fn new < T : bytemuck:: Pod >( data : & impl Buffer < T >, gpu : &' a Gpu ) ->Self { 46Self :: new_by_sz ( std:: mem:: size_of_val ( data. as_bytes ()), gpu) 47} 48 49fn new_by_sz ( sz : usize , gpu : &' a Gpu ) ->Self { 50Self { 51label : None , 52size : wgpu:: util:: align_to ( sz, 16 ) as u64 , 53usage : wgpu:: BufferUsages :: empty (), 54mapped_at_creation : false , 55gpu : gpu, 56} 57} 58 59pub fn copy_dst ( & mut self ) ->& mut Self { 60self . usage |= wgpu:: BufferUsages :: COPY_DST ; 61self 62} 63 64pub fn copy_src ( & mut self ) ->& mut Self { 65self . usage |= wgpu:: BufferUsages :: COPY_SRC ; 66self 67} 68 69pub fn map_read ( & mut self ) ->& mut Self { 70self . usage |= wgpu:: BufferUsages :: MAP_READ ; 71self 72} 73 74pub fn query_resolve ( & mut self ) ->& mut Self { 75self . usage |= wgpu:: BufferUsages :: QUERY_RESOLVE ; 76self 77} 78 79pub fn storage ( & mut self ) ->& mut Self { 80self . usage |= wgpu:: BufferUsages :: STORAGE ; 81self 82} 83 84pub fn uniform ( & mut self ) ->& mut Self { 85self . usage |= wgpu:: BufferUsages :: UNIFORM ; 86self 87} 88 89pub fn build ( & self ) -> wgpu:: Buffer { 90self . gpu . device . create_buffer ( & wgpu:: BufferDescriptor { 91label : self . label . as_deref (), 92size : self . size , 93usage : self . usage , 94mapped_at_creation : self . mapped_at_creation , 95}) 96} 97} 98 99pub struct GpuBuilder { 100required_features : wgpu:: Features , 101} 102 103impl GpuBuilder { 104pub fn new () ->Self { 105Self { 106required_features : wgpu:: Features :: empty (), 107} 108} 109 110pub fn enable_profiling ( & mut self ) ->& mut Self { 111self . required_features |= wgpu:: Features :: TIMESTAMP_QUERY ; 112self 113} 114 115pub fn build ( & self ) -> anyhow:: Result < Gpu > { 116let instance = wgpu:: Instance :: new ( wgpu:: InstanceDescriptor :: new_without_display_handle ()); 117let adapter = 118 pollster:: block_on ( instance. request_adapter ( & wgpu:: RequestAdapterOptions :: default ())) ?; 119println! ( "Got adapter: {:#?}" , adapter. get_info ()); 120 121let supported = adapter. limits (); 122let mut limits = wgpu:: Limits :: downlevel_defaults (); 123 limits. max_compute_invocations_per_workgroup = 124 supported. max_compute_invocations_per_workgroup ; 125 limits. max_compute_workgroup_storage_size = supported. max_compute_workgroup_storage_size ; 126println! ( 127"Max compute invocations per workgroup: {}" , 128 limits. max_compute_invocations_per_workgroup 129); 130 131let ( device, queue) = 132 pollster:: block_on ( adapter. request_device ( & wgpu:: DeviceDescriptor { 133label : None , 134required_features : self . required_features , 135required_limits : limits, 136# [ cfg ( target_arch = "wasm32" )] 137default_queue : wgpu:: QueueDescriptor { label : None }, 138experimental_features : wgpu:: ExperimentalFeatures :: disabled (), 139memory_hints : wgpu:: MemoryHints :: MemoryUsage , 140trace : wgpu:: Trace :: Off , 141})) ?; 142 143let mut timestamps =None ; 144if self 145. required_features 146. contains ( wgpu:: Features :: TIMESTAMP_QUERY ) 147{ 148 timestamps =Some ( device. create_query_set ( & wgpu:: QuerySetDescriptor { 149label : None , 150ty : wgpu:: QueryType :: Timestamp , 151count : 2 , 152})); 153} 154 155Ok ( Gpu { 156 adapter, 157 device, 158 queue, 159 timestamps, 160}) 161} 162} 163 164pub struct Gpu { 165adapter : wgpu:: Adapter , 166device : wgpu:: Device , 167queue : wgpu:: Queue , 168timestamps : Option < wgpu:: QuerySet >, 169} 170 171impl Gpu { 172pub fn buffer < T : bytemuck:: Pod >( & self , data : & impl Buffer < T >) ->BufferBuilder < ' _ > { 173BufferBuilder :: new ( data, self ) 174} 175 176pub fn buffer_by_sz ( & self , sz : usize ) ->BufferBuilder < ' _ > { 177BufferBuilder :: new_by_sz ( sz, self ) 178} 179} 180 181fn get_entry_point_name ( module : & naga:: Module ) -> anyhow:: Result < String > { 182if module. entry_points . len () !=1 { 183 anyhow:: bail!( 184"Expected exactly one entry point, found {}" , 185 module. entry_points . len () 186); 187} 188for entryin module. entry_points . iter () { 189return Ok ( entry. name . clone ()); 190} 191 anyhow:: bail!( "Could not find entry point" ); 192} 193 194fn get_entry_point_workgroup_size ( module : & naga:: Module , name : & str ) -> anyhow:: Result <[ u32 ; 3 ]> { 195for entry_pointin module. entry_points . iter () { 196if entry_point. name != name{ 197continue ; 198} 199return Ok ( entry_point. workgroup_size ); 200} 201 anyhow:: bail!( "Could not find entry point named {}" , name); 202} 203 204fn for_each_global_variable < T >( 205module : & naga:: Module , 206module_info : & naga:: valid:: ModuleInfo , 207mut f : impl FnMut ( 208 naga:: Handle < GlobalVariable >, 209& naga:: GlobalVariable , 210& naga:: ResourceBinding , 211& wgpu:: ShaderStages , 212bool , 213) -> anyhow:: Result < Option < T >>, 214) -> anyhow:: Result < Vec < T >> { 215let mut result =Vec :: new (); 216for ( global_handle, global) in module. global_variables . iter () { 217let Some ( binding) =& global. binding else { 218continue ; 219}; 220 221let can_write =match global. space { 222 naga:: AddressSpace :: Storage { access} =>{ 223 access. contains ( naga:: StorageAccess :: STORE ) 224 || access. contains ( naga:: StorageAccess :: ATOMIC ) 225} 226 naga:: AddressSpace :: Uniform =>false , 227 _ =>{ 228eprintln! ( 229"Skipping global {:?} with address space {:?}" , 230 global. name , global. space 231); 232continue ; 233} 234}; 235let mut stages = wgpu:: ShaderStages :: empty (); 236for ( entry_idx, entry) in module. entry_points . iter (). enumerate () { 237let usage = module_info. get_entry_point ( entry_idx)[ global_handle]; 238if usage. is_empty () { 239continue ; 240} 241 242 stages |=match entry. stage { 243 naga:: ShaderStage :: Vertex => wgpu:: ShaderStages :: VERTEX , 244 naga:: ShaderStage :: Fragment => wgpu:: ShaderStages :: FRAGMENT , 245 naga:: ShaderStage :: Compute => wgpu:: ShaderStages :: COMPUTE , 246 _ =>continue , 247} 248} 249if stages. is_empty () { 250eprintln! ( 251"Skipping bound global {:?} which is not referenced by any stage" , 252 global. name 253); 254continue ; 255} 256if let Some ( value) =f ( global_handle, global, binding, & stages, can_write) ?{ 257 result. push ( value); 258} 259} 260Ok ( result) 261} 262 263fn get_bind_group_layout ( 264gpu : & Gpu , 265module : & naga:: Module , 266module_info : & naga:: valid:: ModuleInfo , 267module_layout : & naga:: proc:: Layouter , 268) -> anyhow:: Result < wgpu:: BindGroupLayout > { 269let mut entries =Vec ::< wgpu:: BindGroupLayoutEntry >:: new (); 270for ( global_handle, global) in module. global_variables . iter () { 271let Some ( binding) =& global. binding else { 272continue ; 273}; 274 275let can_write =match global. space { 276 naga:: AddressSpace :: Storage { access} =>{ 277 access. contains ( naga:: StorageAccess :: STORE ) 278 || access. contains ( naga:: StorageAccess :: ATOMIC ) 279} 280 naga:: AddressSpace :: Uniform =>false , 281 _ =>{ 282eprintln! ( 283"Skipping global {:?} with address space {:?}" , 284 global. name , global. space 285); 286continue ; 287} 288}; 289 290let mut stages = wgpu:: ShaderStages :: empty (); 291for ( entry_idx, entry) in module. entry_points . iter (). enumerate () { 292let usage = module_info. get_entry_point ( entry_idx)[ global_handle]; 293if usage. is_empty () { 294continue ; 295} 296 297 stages |=match entry. stage { 298 naga:: ShaderStage :: Vertex => wgpu:: ShaderStages :: VERTEX , 299 naga:: ShaderStage :: Fragment => wgpu:: ShaderStages :: FRAGMENT , 300 naga:: ShaderStage :: Compute => wgpu:: ShaderStages :: COMPUTE , 301 _ =>continue , 302} 303} 304if stages. is_empty () { 305eprintln! ( 306"Skipping bound global {:?} which is not referenced by any stage" , 307 global. name 308); 309continue ; 310} 311 312let global_ty =& module. types [ global. ty ]; 313let entry =match & module. types [ global. ty ]. inner { 314 naga:: ir:: TypeInner :: Array { ..} =>{ 315let min_size = module_layout[ global. ty ]. size ; 316 wgpu:: BindGroupLayoutEntry { 317binding : binding. binding , 318visibility : stages, 319ty : wgpu:: BindingType :: Buffer { 320ty : wgpu:: BufferBindingType :: Storage { 321read_only : !can_write, 322}, 323min_binding_size : NonZeroU64 :: new ( min_sizeas u64 ), 324has_dynamic_offset : false , 325}, 326count : None , 327} 328} 329 naga:: ir:: TypeInner :: Struct { ..} =>{ 330let min_size = module_layout[ global. ty ]. size ; 331 wgpu:: BindGroupLayoutEntry { 332binding : binding. binding , 333visibility : stages, 334ty : wgpu:: BindingType :: Buffer { 335ty : wgpu:: BufferBindingType :: Uniform , 336min_binding_size : NonZeroU64 :: new ( min_sizeas u64 ), 337has_dynamic_offset : false , 338}, 339count : None , 340} 341} 342 _ =>{ 343 anyhow:: bail!( format! ( 344"Unsupported global type: {:?} (name={:?})" , 345 global_ty. name , global_ty. inner 346)) 347} 348}; 349 entries. push ( entry); 350} 351let layout = wgpu:: BindGroupLayoutDescriptor { 352label : None , 353entries : & entries, 354}; 355Ok ( gpu. device . create_bind_group_layout ( & layout)) 356} 357 358pub struct Shader { 359bind_group_layout : wgpu:: BindGroupLayout , 360shader_module : wgpu:: ShaderModule , 361entry_point_name : String , 362entry_point_workgroup_size : [ u32 ; 3 ], 363} 364 365impl Shader { 366pub fn new ( gpu : & Gpu , shader : & str ) -> anyhow:: Result < Self > { 367let module = naga:: front:: wgsl:: parse_str ( shader) ?; 368let module_info = naga:: valid:: Validator :: new ( 369 naga:: valid:: ValidationFlags :: all (), 370 naga:: valid:: Capabilities :: all (), 371) 372. validate ( & module) ?; 373let mut module_layout = naga:: proc:: Layouter :: default (); 374 module_layout. update ( module. to_ctx ()) ?; 375 376Self :: ensure_compute_compatibility ( gpu, & module) ?; 377let bind_group_layout =get_bind_group_layout ( gpu, & module, & module_info, & module_layout) ?; 378let entry_point_name =get_entry_point_name ( & module) ?; 379let entry_point_workgroup_size = 380get_entry_point_workgroup_size ( & module, & entry_point_name) ?; 381 382let module_descriptor = wgpu:: ShaderModuleDescriptor { 383label : None , 384source : wgpu:: ShaderSource :: Naga ( Cow :: Owned ( module. clone ())), 385}; 386let shader_module = gpu. device . create_shader_module ( module_descriptor. clone ()); 387 388Ok ( Self { 389 bind_group_layout, 390// naga_module: module, 391// naga_module_info: module_info, 392 shader_module, 393 entry_point_name, 394 entry_point_workgroup_size, 395// module_layout, 396}) 397} 398 399fn ensure_compute_compatibility ( gpu : & Gpu , module : & naga:: Module ) -> anyhow:: Result <()> { 400let mut has_compute_stage =false ; 401for entry_pointin module. entry_points . iter () { 402if entry_point. stage == naga:: ShaderStage :: Compute { 403 has_compute_stage =true ; 404} 405} 406if !has_compute_stage{ 407return Ok (()); 408} 409let downlevel_caps = gpu. adapter . get_downlevel_capabilities (); 410if !downlevel_caps 411. flags 412. contains ( wgpu:: DownlevelFlags :: COMPUTE_SHADERS ) 413{ 414 anyhow:: bail!( 415"GPU {} does not support compute shaders." , 416 gpu. adapter . get_info (). name 417) 418} 419Ok (()) 420} 421} 422 423pub struct MaterialPass { 424buffers : Vec < wgpu:: Buffer >, 425bind_group : wgpu:: BindGroup , 426} 427 428pub struct MaterialPassBuilder { 429buffers : Vec < wgpu:: Buffer >, 430bind_group : Option < wgpu:: BindGroup >, 431} 432 433impl MaterialPassBuilder { 434pub fn new () ->Self { 435Self { 436buffers : Vec :: new (), 437bind_group : None , 438} 439} 440 441pub fn buffers ( mut self , buffers : Vec < wgpu:: Buffer >) ->Self { 442self . buffers = buffers; 443self 444} 445 446// Must be run after buffers(), etc. 447pub fn bind_group ( mut self , gpu : & Gpu , shader : & Shader ) ->Self { 448let mut descriptor_entries =Vec ::< wgpu:: BindGroupEntry >:: new (); 449let entries = 450self . buffers 451. iter () 452. enumerate () 453. fold ( Vec :: new (), |mut entries, ( index, buf) |{ 454 entries. push ( wgpu:: BindGroupEntry { 455binding : indexas u32 , 456resource : buf. as_entire_binding (), 457}); 458 entries 459}); 460 descriptor_entries. extend ( entries); 461let descriptor = wgpu:: BindGroupDescriptor { 462label : None , 463layout : & shader. bind_group_layout , 464entries : & descriptor_entries, 465}; 466let bind_group = gpu. device . create_bind_group ( & descriptor); 467self . bind_group =Some ( bind_group); 468self 469} 470 471pub fn build ( self ) -> anyhow:: Result < MaterialPass > { 472if self . bind_group . is_none () { 473 anyhow:: bail!( "bind_group not set" ); 474} 475Ok ( MaterialPass { 476buffers : self . buffers , 477bind_group : self . bind_group . unwrap (), 478}) 479} 480} 481 482pub struct MaterialInstance { 483passes : Vec < MaterialPass >, 484readback : Option < wgpu:: Buffer >, 485} 486 487impl MaterialInstance { 488pub fn new ( passes : Vec < MaterialPass >, readback : Option < wgpu:: Buffer >) ->Self { 489Self { 490passes : passes, 491readback : readback, 492} 493} 494} 495 496// A batch of Material work to be submitted together. This allows us to send a 497// batch of work while another is still processing on the GPU. 498pub struct MaterialBatch { 499pub instances : Vec < MaterialInstance >, 500ready_tx : Sender <( usize , Result <(), wgpu:: BufferAsyncError >)>, 501ready_rx : Receiver <( usize , Result <(), wgpu:: BufferAsyncError >)>, 502// Optional timestamp (ts) buffers for profiling. 503timing : Option < MaterialTiming >, 504// Whether the batch is currently processing. This is set true as soon as a batch is reserved with get_batch(). 505// The caller is responsible for setting this false 506pub busy : bool , 507// How many instances in the batch are currently processing. Set to 508// batch_size (n_instancces) when the batch is reserved The caller may use 509// this to track completion as each instance in the batch completes. 510pub n_busy : usize , 511} 512 513impl MaterialBatch { 514// Use MaterialBuilder instead. It handles creating batches. 515fn new ( 516gpu : & Gpu , 517mut passes_generator : impl FnMut () ->( Vec < MaterialPass >, Option < wgpu:: Buffer >), 518n_instances : usize , 519enable_timing : bool , 520) ->Self { 521let mut instances =Vec ::< MaterialInstance >:: new (); 522 instances. reserve ( n_instances); 523for _in 0 ..n_instances{ 524let ( passes, readback) =passes_generator (); 525 instances. push ( MaterialInstance :: new ( passes, readback)); 526} 527let ( ready_tx, ready_rx) = std:: sync:: mpsc:: channel (); 528let mut timing =None ; 529if enable_timing{ 530 std:: hint:: cold_path (); 531 timing =Some ( MaterialTiming :: new ( gpu)); 532} 533Self { 534instances : instances, 535ready_tx : ready_tx, 536ready_rx : ready_rx, 537timing : timing, 538busy : false , 539n_busy : 0 , 540} 541} 542 543pub fn enable_profiling ( mut self , gpu : & Gpu ) ->Self { 544self . timing =Some ( MaterialTiming :: new ( gpu)); 545self 546} 547} 548 549pub struct MaterialTiming { 550ts_buf : wgpu:: Buffer , 551ts_readback : wgpu:: Buffer , 552ready_tx : Sender < Result <(), wgpu:: BufferAsyncError >>, 553ready_rx : Receiver < Result <(), wgpu:: BufferAsyncError >>, 554busy : bool , 555} 556 557impl MaterialTiming { 558pub fn new ( gpu : & Gpu ) ->Self { 559let ts_buf = gpu. buffer_by_sz ( 16 ). query_resolve (). copy_src (). build (); 560let ts_readback = gpu. buffer_by_sz ( 16 ). copy_dst (). map_read (). build (); 561let ( ready_tx, ready_rx) = std:: sync:: mpsc:: channel (); 562Self { 563ts_buf : ts_buf, 564ts_readback : ts_readback, 565ready_tx : ready_tx, 566ready_rx : ready_rx, 567busy : false , 568} 569} 570} 571 572pub struct Material { 573pub batches : Vec < MaterialBatch >, 574pub batch_size : usize , 575} 576 577impl Material { 578pub fn get_batch ( & mut self ) ->Option < & mut MaterialBatch > { 579let batch =self . batches . iter_mut (). find ( |batch|{ 580if batch. busy { 581return false ; 582} 583if let Some ( timing) =& batch. timing { 584 std:: hint:: cold_path (); 585if timing. busy { 586return false ; 587} 588} 589true 590}) ?; 591// Found a batch. 592 batch. busy =true ; 593if let Some ( timing) =& mut batch. timing { 594 std:: hint:: cold_path (); 595 timing. busy =true ; 596} 597 batch. n_busy =self . batch_size ; 598Some ( batch) 599} 600} 601 602pub struct MaterialBuilder { 603batch_size : usize , // instances per batch 604n_batches : usize , 605enable_timing : bool , 606} 607 608impl MaterialBuilder { 609pub fn new () ->Self { 610Self { 611batch_size : 1 , 612n_batches : 1 , 613enable_timing : false , 614} 615} 616 617pub fn batch_size ( mut self , batch_size : usize ) ->Self { 618self . batch_size = batch_size; 619self 620} 621 622pub fn n_batches ( mut self , n_batches : usize ) ->Self { 623self . n_batches = n_batches; 624self 625} 626 627pub fn enable_timing ( mut self ) ->Self { 628self . enable_timing =true ; 629self 630} 631 632pub fn build ( 633self , 634gpu : & Gpu , 635mut passes_generator : impl FnMut () ->( Vec < MaterialPass >, Option < wgpu:: Buffer >), 636) ->Material { 637let mut batches =Vec ::< MaterialBatch >:: new (); 638 batches. reserve ( self . n_batches ); 639for _in 0 ..self . n_batches { 640 batches. push ( MaterialBatch :: new ( 641 gpu, 642& mut passes_generator, 643self . batch_size , 644self . enable_timing , 645)); 646} 647Material { 648batches : batches, 649batch_size : self . batch_size , 650} 651} 652} 653 654pub fn get_pipeline ( gpu : & Gpu , shader : & Shader ) -> wgpu:: ComputePipeline { 655let pipeline_layout = gpu 656. device 657. create_pipeline_layout ( & wgpu:: PipelineLayoutDescriptor { 658label : None , 659bind_group_layouts : & [ Some ( & shader. bind_group_layout )], 660immediate_size : 0 , 661}); 662// TODO we should use a builder. Don't want to skip 663// zero-initializing memory in general case. 664let compilation_options = wgpu:: PipelineCompilationOptions { 665zero_initialize_workgroup_memory : false , 666 ..Default :: default () 667}; 668 gpu. device 669. create_compute_pipeline ( & wgpu:: ComputePipelineDescriptor { 670label : None , 671layout : Some ( & pipeline_layout), 672module : & shader. shader_module , 673entry_point : Some ( & shader. entry_point_name ), 674compilation_options : compilation_options, 675cache : None , 676}) 677} 678 679pub fn submit_dft_batch ( 680gpu : & Gpu , 681shader : & Shader , 682material : & mut Material , 683pipeline : & wgpu:: ComputePipeline , 684data_batch : & [ Vec < Complex < f32 >>], 685) -> anyhow:: Result < usize > { 686assert_eq! ( data_batch. len (), material. batch_size ); 687 688let Some ( batch) = material. get_batch () else { 689return Ok ( 0 ); 690}; 691 692// Submit data to the GPU for each input in the batch. 693for ( data, instance) in data_batch. iter (). zip ( batch. instances . iter ()) { 694 gpu. queue . write_buffer ( 695& instance. passes [ 0 ]. buffers [ 0 ], 6960 , 697 bytemuck:: cast_slice ( data), 698); 699} 700 701let mut encoder = gpu 702. device 703. create_command_encoder ( & wgpu:: CommandEncoderDescriptor { label : None }); 704let mut compute_pass = encoder. begin_compute_pass ( & wgpu:: ComputePassDescriptor { 705label : None , 706timestamp_writes : None , 707}); 708 compute_pass. set_pipeline ( pipeline); 709let shader_local_size = shader 710. entry_point_workgroup_size 711. iter () 712. fold ( 1 , |acc, & x| acc* x) as usize ; 713for idxin 0 ..data_batch. len () { 714 compute_pass. set_bind_group ( 0 , & batch. instances [ idx]. passes [ 0 ]. bind_group , & []); 715 compute_pass. dispatch_workgroups ( 716 data_batch[ idx]. len (). div_ceil ( shader_local_size) as u32 , 7171 , 7181 , 719); 720} 721drop ( compute_pass); 722 723for idxin 0 ..data_batch. len () { 724let slot =& batch. instances [ idx]; 725 encoder. copy_buffer_to_buffer ( 726& slot. passes [ 0 ]. buffers [ 1 ], 7270 , 728& slot. readback . as_ref (). unwrap (), 7290 , 730 slot. passes [ 0 ]. buffers [ 1 ]. size (), 731); 732let ready_tx = batch. ready_tx . clone (); 733 encoder. map_buffer_on_submit ( 734& slot. readback . as_ref (). unwrap (), 735 wgpu:: MapMode :: Read , 736 .., 737move |result|{ 738 ready_tx. send (( idx, result)). ok (); 739}, 740); 741} 742 743 gpu. queue . submit ([ encoder. finish ()]); 744Ok ( data_batch. len ()) 745} 746 747# [ allow ( dead_code )] 748fn drain_dft ( 749gpu : & Gpu , 750material : & mut Material , 751) -> anyhow:: Result < Vec <( usize , Vec < Complex < f32 >>)>> { 752 gpu. device . poll ( wgpu:: PollType :: Poll ) ?; 753let mut results =Vec :: new (); 754for batchin & mut material. batches { 755while let Ok (( instance_id, map_result)) = batch. ready_rx . try_recv () { 756let slot =& mut batch. instances [ instance_id]; 757let data = slot. readback . as_ref (). unwrap (). get_mapped_range ( ..) ?; 758let result = bytemuck:: cast_slice ( & data). to_vec (); 759drop ( data); 760 761 slot. readback . as_ref (). unwrap (). unmap (); 762 batch. n_busy -=1 ; 763if batch. n_busy ==0 { 764 batch. busy =false ; 765} 766 767 map_result?; 768 results. push (( instance_id, result)); 769} 770} 771Ok ( results) 772} 773 774# [ allow ( dead_code )] 775fn submit_fft_v0_batch ( 776gpu : & Gpu , 777shader : & Shader , 778material : & mut Material , 779pipeline : & wgpu:: ComputePipeline , 780data_batch : & [ Vec < Complex < f32 >>], 781) -> anyhow:: Result < usize > { 782assert_eq! ( data_batch. len (), material. batch_size ); 783 784let Some ( batch) = material. get_batch () else { 785return Ok ( 0 ); 786}; 787 788// Submit data to the GPU for each input in the batch. 789for ( data, instance) in data_batch. iter (). zip ( batch. instances . iter ()) { 790 gpu. queue . write_buffer ( 791& instance. passes [ 0 ]. buffers [ 0 ], 7920 , 793 bytemuck:: cast_slice ( data), 794); 795} 796 797let mut encoder = gpu 798. device 799. create_command_encoder ( & wgpu:: CommandEncoderDescriptor { label : None }); 800let timestamp_writes =if let Some ( timestamps) =& gpu. timestamps { 801 std:: hint:: cold_path (); 802Some ( wgpu:: ComputePassTimestampWrites { 803query_set : & timestamps, 804beginning_of_pass_write_index : Some ( 0 ), 805end_of_pass_write_index : Some ( 1 ), 806}) 807} else { 808None 809}; 810 811let mut compute_pass = encoder. begin_compute_pass ( & wgpu:: ComputePassDescriptor { 812label : None , 813timestamp_writes : timestamp_writes, 814}); 815 compute_pass. set_pipeline ( pipeline); 816let shader_local_size = shader 817. entry_point_workgroup_size 818. iter () 819. fold ( 1 , |acc, & x| acc* x) as usize ; 820for ( data, instance) in data_batch. iter (). zip ( batch. instances . iter ()) { 821for passin & instance. passes { 822 compute_pass. set_bind_group ( 0 , & pass. bind_group , & []); 823// Each instance computes two outputs, so we only need to dispatch len/2 instances. 824 compute_pass. dispatch_workgroups ( 825( data. len () /2 ). div_ceil ( shader_local_size) as u32 , 8261 , 8271 , 828); 829} 830} 831drop ( compute_pass); 832 833for ( instance_id, instance) in batch. instances . iter (). enumerate () { 834 encoder. copy_buffer_to_buffer ( 835& instance. passes . last (). unwrap (). buffers [ 1 ], 8360 , 837& instance. readback . as_ref (). unwrap (), 8380 , 839 instance. passes . last (). unwrap (). buffers [ 1 ]. size (), 840); 841let ready_tx = batch. ready_tx . clone (); 842 encoder. map_buffer_on_submit ( 843& instance. readback . as_ref (). unwrap (), 844 wgpu:: MapMode :: Read , 845 .., 846move |result|{ 847 ready_tx. send (( instance_id, result)). ok (); 848}, 849); 850} 851 852if let Some ( timing) =& batch. timing { 853 std:: hint:: cold_path (); 854 encoder. resolve_query_set ( gpu. timestamps . as_ref (). unwrap (), 0 ..2 , & timing. ts_buf , 0 ); 855 encoder. copy_buffer_to_buffer ( & timing. ts_buf , 0 , & timing. ts_readback , 0 , 16 ); 856let ready_tx = timing. ready_tx . clone (); 857 encoder. map_buffer_on_submit ( 858& timing. ts_readback , 859 wgpu:: MapMode :: Read , 860 .., 861move |result|{ 862 ready_tx. send ( result). ok (); 863}, 864); 865} 866 867 gpu. queue . submit ([ encoder. finish ()]); 868Ok ( data_batch. len ()) 869} 870 871# [ allow ( dead_code )] 872fn drain_fft_v0 ( 873gpu : & Gpu , 874material : & mut Material , 875mut cb : impl FnMut ( usize , & [ Complex < f32 >]) -> anyhow:: Result <()>, 876) -> anyhow:: Result <()> { 877 gpu. device . poll ( wgpu:: PollType :: Poll ) ?; 878for batchin & mut material. batches { 879if !batch. busy { 880continue ; 881} 882while let Ok (( instance_id, map_result)) = batch. ready_rx . try_recv () { 883let instance =& batch. instances [ instance_id]; 884 map_result?; 885let data = instance. readback . as_ref (). unwrap (). get_mapped_range ( ..) ?; 886let cb_result =cb ( instance_id, bytemuck:: cast_slice ( & data)); 887drop ( data); 888 889 instance. readback . as_ref (). unwrap (). unmap (); 890 batch. n_busy -=1 ; 891if batch. n_busy ==0 { 892 batch. busy =false ; 893} 894 895 cb_result?; 896} 897} 898Ok (()) 899} 900 901# [ allow ( dead_code )] 902fn submit_fft_v1_batch ( 903gpu : & Gpu , 904shader : & Shader , 905material : & mut Material , 906pipeline : & wgpu:: ComputePipeline , 907data_len : usize , // length of 1 input 908data_batch : & Vec < Complex < f32 >>, // batch of N inputs 909) -> anyhow:: Result < usize > { 910assert! ( data_len >0 ); 911assert_eq! ( data_batch. len () % data_len, 0 ); 912 913let Some ( batch) = material. get_batch () else { 914return Ok ( 0 ); 915}; 916// We do not use instances since the input is pre-batched. 917assert_eq! ( batch. instances . len (), 1 ); 918let instance =& batch. instances [ 0 ]; 919 920// Submit data 921 gpu. queue . write_buffer ( 922& instance. passes [ 0 ]. buffers [ 0 ], 9230 , 924 bytemuck:: cast_slice ( & data_batch), 925); 926 927let mut encoder = gpu 928. device 929. create_command_encoder ( & wgpu:: CommandEncoderDescriptor { label : None }); 930let timestamp_writes =if let Some ( timestamps) =& gpu. timestamps { 931 std:: hint:: cold_path (); 932Some ( wgpu:: ComputePassTimestampWrites { 933query_set : & timestamps, 934beginning_of_pass_write_index : Some ( 0 ), 935end_of_pass_write_index : Some ( 1 ), 936}) 937} else { 938None 939}; 940 941let mut compute_pass = encoder. begin_compute_pass ( & wgpu:: ComputePassDescriptor { 942label : None , 943timestamp_writes : timestamp_writes, 944}); 945 compute_pass. set_pipeline ( pipeline); 946let local_x = shader. entry_point_workgroup_size [ 0 ]; 947for passin & instance. passes { 948 compute_pass. set_bind_group ( 0 , & pass. bind_group , & []); 949// Each instance computes two outputs, so we only need to dispatch len/2 instances. 950 compute_pass. dispatch_workgroups ( 951( data_len /2 ). div_ceil ( local_xas usize ) as u32 , 952( data_batch. len () / data_len) as u32 , 9531 , 954); 955} 956drop ( compute_pass); 957 958 encoder. copy_buffer_to_buffer ( 959& instance. passes . last (). unwrap (). buffers [ 1 ], 9600 , 961& instance. readback . as_ref (). unwrap (), 9620 , 963 instance. passes . last (). unwrap (). buffers [ 1 ]. size (), 964); 965let ready_tx = batch. ready_tx . clone (); 966 encoder. map_buffer_on_submit ( 967& instance. readback . as_ref (). unwrap (), 968 wgpu:: MapMode :: Read , 969 .., 970move |result|{ 971 ready_tx. send (( 0 , result)). ok (); 972}, 973); 974 975if let Some ( timing) =& batch. timing { 976 std:: hint:: cold_path (); 977 encoder. resolve_query_set ( gpu. timestamps . as_ref (). unwrap (), 0 ..2 , & timing. ts_buf , 0 ); 978 encoder. copy_buffer_to_buffer ( & timing. ts_buf , 0 , & timing. ts_readback , 0 , 16 ); 979let ready_tx = timing. ready_tx . clone (); 980 encoder. map_buffer_on_submit ( 981& timing. ts_readback , 982 wgpu:: MapMode :: Read , 983 .., 984move |result|{ 985 ready_tx. send ( result). ok (); 986}, 987); 988} 989 990 gpu. queue . submit ([ encoder. finish ()]); 991Ok ( data_batch. len ()) 992} 993 994# [ allow ( dead_code )] 995fn submit_fft_v2_batch ( 996gpu : & Gpu , 997shader : & Shader , 998material : & mut Material , 999pipeline : & wgpu:: ComputePipeline , 1000data_len : usize , // length of 1 input 1001data_batch : & Vec < Complex < f32 >>, // batch of N inputs 1002) -> anyhow:: Result < usize > { 1003assert! ( data_len >0 ); 1004assert_eq! ( data_batch. len () % data_len, 0 ); 1005 1006let Some ( batch) = material. get_batch () else { 1007return Ok ( 0 ); 1008}; 1009// We do not use instances since the input is pre-batched. 1010assert_eq! ( batch. instances . len (), 1 ); 1011let instance =& batch. instances [ 0 ]; 1012 1013// Submit data 1014 gpu. queue . write_buffer ( 1015& instance. passes [ 0 ]. buffers [ 0 ], 10160 , 1017 bytemuck:: cast_slice ( & data_batch), 1018); 1019 1020let mut encoder = gpu 1021. device 1022. create_command_encoder ( & wgpu:: CommandEncoderDescriptor { label : None }); 1023let timestamp_writes =if let Some ( timestamps) =& gpu. timestamps { 1024 std:: hint:: cold_path (); 1025Some ( wgpu:: ComputePassTimestampWrites { 1026query_set : & timestamps, 1027beginning_of_pass_write_index : Some ( 0 ), 1028end_of_pass_write_index : Some ( 1 ), 1029}) 1030} else { 1031None 1032}; 1033 1034let mut compute_pass = encoder. begin_compute_pass ( & wgpu:: ComputePassDescriptor { 1035label : None , 1036timestamp_writes : timestamp_writes, 1037}); 1038 compute_pass. set_pipeline ( pipeline); 1039let local_x = shader. entry_point_workgroup_size [ 0 ]; 1040for passin & instance. passes { 1041 compute_pass. set_bind_group ( 0 , & pass. bind_group , & []); 1042// Each instance computes two outputs, so we only need to dispatch len/2 instances. 1043 compute_pass. dispatch_workgroups ( 1 , 1 , ( data_batch. len () / data_len) as u32 ); 1044} 1045drop ( compute_pass); 1046 1047 encoder. copy_buffer_to_buffer ( 1048& instance. passes . last (). unwrap (). buffers [ 1 ], 10490 , 1050& instance. readback . as_ref (). unwrap (), 10510 , 1052 instance. passes . last (). unwrap (). buffers [ 1 ]. size (), 1053); 1054let ready_tx = batch. ready_tx . clone (); 1055 encoder. map_buffer_on_submit ( 1056& instance. readback . as_ref (). unwrap (), 1057 wgpu:: MapMode :: Read , 1058 .., 1059move |result|{ 1060 ready_tx. send (( 0 , result)). ok (); 1061}, 1062); 1063 1064if let Some ( timing) =& batch. timing { 1065 std:: hint:: cold_path (); 1066 encoder. resolve_query_set ( gpu. timestamps . as_ref (). unwrap (), 0 ..2 , & timing. ts_buf , 0 ); 1067 encoder. copy_buffer_to_buffer ( & timing. ts_buf , 0 , & timing. ts_readback , 0 , 16 ); 1068let ready_tx = timing. ready_tx . clone (); 1069 encoder. map_buffer_on_submit ( 1070& timing. ts_readback , 1071 wgpu:: MapMode :: Read , 1072 .., 1073move |result|{ 1074 ready_tx. send ( result). ok (); 1075}, 1076); 1077} 1078 1079 gpu. queue . submit ([ encoder. finish ()]); 1080Ok ( data_batch. len ()) 1081} 1082 1083# [ allow ( dead_code )] 1084fn drain_fft_v0_timing ( 1085gpu : & Gpu , 1086material : & mut Material , 1087mut cb : impl FnMut ( f64 ), 1088) -> anyhow:: Result <()> { 1089for batchin & mut material. batches { 1090if let Some ( timing) =& mut batch. timing { 1091 std:: hint:: cold_path (); 1092 1093if !timing. busy { 1094continue ; 1095} 1096 1097if let Ok ( map_result) = timing. ready_rx . try_recv () { 1098 map_result?; 1099 1100let data = timing. ts_readback . get_mapped_range ( ..) ?; 1101let ticks: & [ u64 ] = bytemuck:: cast_slice ( & data); 1102let elapsed_ns =( ticks[ 1 ]. wrapping_sub ( ticks[ 0 ]) as f64 ) 1103* ( gpu. queue . get_timestamp_period () as f64 ); 1104 1105drop ( data); 1106 timing. ts_readback . unmap (); 1107cb ( elapsed_ns); 1108 timing. busy =false ; 1109} 1110} 1111} 1112Ok (()) 1113} 1114 1115# [ cfg ( test )] 1116mod tests{ 1117use crate :: util:: * ; 1118use std:: time:: Duration ; 1119use std:: time:: Instant ; 1120 1121use num:: Complex ; 1122use num:: traits:: Float ; 1123use rand:: RngExt ; 1124use rand:: SeedableRng ; 1125use rand:: rngs:: StdRng ; 1126 1127// Cast one float type to another, truncating if needed. 1128fn t0_to_t1 < T0 : Float , T1 : Float >( val : T0 ) ->T1 { 1129return num:: cast ( val). unwrap (); 1130} 1131 1132// Casts one Complex<T> to another. 1133fn complex_to_t < T0 : Float , T1 : Float >( data : & [ Complex < T0 >]) ->Vec < Complex < T1 >> { 1134 data. iter () 1135. map ( |c|Complex :: new ( t0_to_t1 ( c. re ), t0_to_t1 ( c. im ))) 1136. collect () 1137} 1138 1139fn evaluate_results ( 1140result_ref_64 : & Vec < Complex < f64 >>, 1141result_cur_64 : & Vec < Complex < f64 >>, 1142duration_dft : & std:: time:: Duration , 1143duration_cur : & std:: time:: Duration , 1144algo_name : & str , 1145) { 1146assert_eq! ( result_ref_64. len (), result_cur_64. len ()); 1147 1148println! ( " Algorithm: {}" , algo_name); 1149println! ( " Duration: {:?}" , duration_cur); 1150println! ( 1151" Speedup: {:?}" , 1152 duration_dft. as_nanos () as f32 / duration_cur. as_nanos () as f32 1153); 1154 1155let mut max_err: f64 =0.0 ; 1156let mut sum_err: f64 =0.0 ; 1157// TODO: median, 90p, 99p 1158 1159let result_64: Vec < Complex < f64 >> =complex_to_t ( & result_cur_64); 1160 1161for iin 0 ..result_ref_64. len () { 1162 max_err = max_err. max (( result_ref_64[ i] - result_64[ i]). norm ()); 1163 sum_err +=( result_ref_64[ i] - result_64[ i]). norm (); 1164} 1165println! ( " Max err: {}" , max_err); 1166println! ( " Avg err: {}" , sum_err /( result_ref_64. len () as f64 )); 1167} 1168 1169fn evaluate_naive_dft ( data : & [ Complex < f32 >], gpu : & super :: Gpu ) -> std:: time:: Duration { 1170let shader =super :: Shader :: new ( & gpu, include_wgsl! ( "naive_dft.wgsl" )). unwrap (); 1171let mut result_dft =Vec :: from ( data); 1172 1173const BATCH_SIZE : usize =128 ; 1174let inputs =vec! [ Vec :: from ( data); BATCH_SIZE ]; 1175 1176let passes_gen = || ->( Vec < super :: MaterialPass >, Option < wgpu:: Buffer >) { 1177let buffer_a = gpu. buffer ( & data). storage (). copy_dst (). build (); 1178let buffer_b = gpu. buffer ( & data). storage (). copy_src (). build (); 1179let buffers =vec! [ buffer_a, buffer_b]; 1180 1181let passes =vec! [ 1182super :: MaterialPassBuilder :: new () 1183. buffers ( buffers) 1184. bind_group ( & gpu, & shader) 1185. build () 1186. unwrap (), 1187]; 1188let readback = gpu. buffer ( & data). copy_dst (). map_read (). build (); 1189( passes, Some ( readback)) 1190}; 1191 1192let mut material =super :: MaterialBuilder :: new () 1193. batch_size ( BATCH_SIZE ) 1194. n_batches ( 2 ) 1195. build ( gpu, passes_gen); 1196let pipeline =super :: get_pipeline ( & gpu, & shader); 1197 1198let warmup =Instant :: now () +Duration :: from_secs ( 3 ); 1199let end = warmup +Duration :: from_secs ( 5 ); 1200let mut n_iter: u32 =0 ; 1201let mut now =Instant :: now (); 1202while now < end{ 1203// Fill the pipeline. 1204if super :: submit_dft_batch ( & gpu, & shader, & mut material, & pipeline, & inputs). unwrap () 1205 >0 1206{ 1207continue ; 1208} 1209// Drain if pipeline is full. 1210let results =super :: drain_dft ( & gpu, & mut material). unwrap (); 1211for ( _, result) in & results{ 1212 result_dft = result. clone (); 1213} 1214if now > warmup{ 1215 n_iter += results. len () as u32 ; 1216} 1217 now =Instant :: now (); 1218} 1219let duration_dft =( now - warmup) / n_iter; 1220println! ( "Duration: {:?}" , duration_dft); 1221 1222let mut result_ref: Vec < rustfft:: num_complex:: Complex < f64 >> =complex_to_t ( & data); 1223let mut ref_planner = rustfft:: FftPlanner ::< f64 >:: new (); 1224let ref_fft = ref_planner. plan_fft_forward ( result_ref. len ()); 1225 ref_fft. process ( & mut result_ref); 1226let result_ref_64: Vec < Complex < f64 >> =complex_to_t ( & result_ref); 1227 1228let result_dft_64 =complex_to_t ::< f32 , f64 >( & result_dft); 1229evaluate_results ( 1230& result_ref_64, 1231& result_dft_64, 1232& duration_dft, 1233& duration_dft, 1234"Naive DFT" , 1235); 1236 1237 duration_dft 1238} 1239 1240fn evaluate_fft_v0 ( 1241data : & [ Complex < f32 >], 1242gpu : & super :: Gpu , 1243duration_dft : & std:: time:: Duration , 1244) { 1245let shader =super :: Shader :: new ( & gpu, include_wgsl! ( "fft_v0_basic.wgsl" )). unwrap (); 1246let mut result =Vec :: from ( data); 1247const BATCH_SIZE : usize =8 ; 1248let inputs =vec! [ Vec :: from ( data); BATCH_SIZE ]; 1249 1250let twiddles =precompute_twiddles ::< f32 >( data. len ()); 1251let twiddles_buffer = gpu. buffer ( & twiddles). storage (). copy_dst (). build (); 1252 gpu. queue . write_buffer ( 1253& twiddles_buffer, 12540 , 1255 bytemuck:: cast_slice ( twiddles. as_slice ()), 1256); 1257 1258let passes_gen = || ->( Vec < super :: MaterialPass >, Option < wgpu:: Buffer >) { 1259let buffer_a = gpu. buffer ( & data). storage (). copy_src (). copy_dst (). build (); 1260let buffer_b = gpu. buffer ( & data). storage (). copy_src (). copy_dst (). build (); 1261 1262let mut stride = data. len () /2 ; 1263let mut big_n =2 ; 1264let num_passes =log_k_of ::< 2 >( data. len ()); 1265let mut passes =Vec ::< super :: MaterialPass >:: new (); 1266 passes. reserve ( num_passes); 1267for iin 0 ..num_passes{ 1268// TODO each pass needs its own uniforms, but each *instance* should share them 1269# [ repr ( C )] 1270# [ derive ( Clone , Copy , bytemuck :: Pod , bytemuck :: Zeroable )] 1271struct Uniforms { 1272stride : u32 , 1273big : u32 , 1274} 1275let uniforms =Uniforms { 1276stride : strideas u32 , 1277big : big_nas u32 , 1278}; 1279let uniforms_buffer = gpu. buffer ( & uniforms). uniform (). copy_dst (). build (); 1280 gpu. queue 1281. write_buffer ( & uniforms_buffer, 0 , bytemuck:: bytes_of ( & uniforms)); 1282 stride /=2 ; 1283 big_n *=2 ; 1284 1285let buffers =if i %2 ==0 { 1286vec! [ 1287 buffer_a. clone (), 1288 buffer_b. clone (), 1289 twiddles_buffer. clone (), 1290 uniforms_buffer. clone (), 1291] 1292} else { 1293vec! [ 1294 buffer_b. clone (), 1295 buffer_a. clone (), 1296 twiddles_buffer. clone (), 1297 uniforms_buffer. clone (), 1298] 1299}; 1300 passes. push ( 1301super :: MaterialPassBuilder :: new () 1302. buffers ( buffers) 1303. bind_group ( & gpu, & shader) 1304. build () 1305. unwrap (), 1306); 1307} 1308let readback = gpu. buffer ( & data). copy_dst (). map_read (). build (); 1309( passes, Some ( readback)) 1310}; 1311 1312// Allocate twice as many buffers as each batch uses. 1313// This lets us send data over while the GPU is still working. 1314let mut material =super :: MaterialBuilder :: new () 1315. batch_size ( BATCH_SIZE ) 1316. n_batches ( 2 ) 1317. enable_timing () 1318. build ( gpu, passes_gen); 1319let pipeline =super :: get_pipeline ( & gpu, & shader); 1320 1321let warmup =Instant :: now () +Duration :: from_secs ( 3 ); 1322let end = warmup +Duration :: from_secs ( 5 ); 1323let mut n_iter: u32 =0 ; 1324let mut sum_gpu_ns: f64 =0.0 ; 1325let mut now =Instant :: now (); 1326while now < end{ 1327// Fill the pipeline. 1328if super :: submit_fft_v0_batch ( & gpu, & shader, & mut material, & pipeline, & inputs). unwrap () 1329 >0 1330{ 1331continue ; 1332} 1333// Drain if pipeline is full. 1334let mut n_results =0 ; 1335super :: drain_fft_v0 ( & gpu, & mut material, |_, data|{ 1336 result. copy_from_slice ( data); 1337 n_results +=1 ; 1338Ok (()) 1339}) 1340. unwrap (); 1341let mut cur_sum_gpu_ns: f64 =0.0 ; 1342super :: drain_fft_v0_timing ( gpu, & mut material, |elapsed_ns|{ 1343 cur_sum_gpu_ns += elapsed_ns; 1344}) 1345. unwrap (); 1346if now > warmup{ 1347 n_iter += n_results; 1348 sum_gpu_ns += cur_sum_gpu_ns; 1349} 1350 now =Instant :: now (); 1351} 1352let duration =( now - warmup) / n_iter; 1353println! ( "Duration (wall): {:?}" , duration); 1354 1355let gpu_duration =Duration :: from_secs_f64 ( sum_gpu_ns /( n_iteras f64 * 1e9 )); 1356println! ( "Duration (GPU): {:?}" , gpu_duration); 1357 1358let mut result_ref: Vec < rustfft:: num_complex:: Complex < f64 >> =complex_to_t ( & data); 1359let mut ref_planner = rustfft:: FftPlanner ::< f64 >:: new (); 1360let ref_fft = ref_planner. plan_fft_forward ( result_ref. len ()); 1361 ref_fft. process ( & mut result_ref); 1362let result_ref_64: Vec < Complex < f64 >> =complex_to_t ( & result_ref); 1363 1364let result_64 =complex_to_t ::< f32 , f64 >( & result); 1365evaluate_results ( 1366& result_ref_64, 1367& result_64, 1368& duration_dft, 1369& duration, 1370"FFT v0" , 1371); 1372} 1373 1374fn evaluate_fft_v1 ( 1375data : & [ Complex < f32 >], 1376gpu : & super :: Gpu , 1377duration_dft : & std:: time:: Duration , 1378) { 1379let shader =super :: Shader :: new ( & gpu, include_wgsl! ( "fft_v1_batched.wgsl" )). unwrap (); 1380let mut result =Vec :: from ( data); 1381const BATCH_SIZE : usize =64 ; 1382 1383let mut input_batch =Vec ::< Complex < f32 >>:: new (); 1384 input_batch. reserve ( data. len () * BATCH_SIZE ); 1385for _in 0 ..BATCH_SIZE { 1386 input_batch. extend_from_slice ( data); 1387} 1388 1389let twiddles =precompute_twiddles ::< f32 >( data. len ()); 1390let twiddles_buffer = gpu. buffer ( & twiddles). storage (). copy_dst (). build (); 1391 gpu. queue . write_buffer ( 1392& twiddles_buffer, 13930 , 1394 bytemuck:: cast_slice ( twiddles. as_slice ()), 1395); 1396 1397let passes_gen = || ->( Vec < super :: MaterialPass >, Option < wgpu:: Buffer >) { 1398let buffer_a = gpu 1399. buffer ( & input_batch) 1400. storage () 1401. copy_src () 1402. copy_dst () 1403. build (); 1404let buffer_b = gpu 1405. buffer ( & input_batch) 1406. storage () 1407. copy_src () 1408. copy_dst () 1409. build (); 1410 1411let mut stride = data. len () /2 ; 1412let mut big_n =2 ; 1413let num_passes =log_k_of ::< 2 >( data. len ()); 1414let mut passes =Vec ::< super :: MaterialPass >:: new (); 1415 passes. reserve ( num_passes); 1416for iin 0 ..num_passes{ 1417// TODO each pass needs its own uniforms, but each *instance* should share them 1418# [ repr ( C )] 1419# [ derive ( Clone , Copy , bytemuck :: Pod , bytemuck :: Zeroable )] 1420struct Uniforms { 1421stride : u32 , 1422big_n : u32 , 1423input_len : u32 , 1424} 1425let uniforms =Uniforms { 1426stride : strideas u32 , 1427big_n : big_nas u32 , 1428input_len : data. len () as u32 , 1429}; 1430let uniforms_buffer = gpu. buffer ( & uniforms). uniform (). copy_dst (). build (); 1431 gpu. queue 1432. write_buffer ( & uniforms_buffer, 0 , bytemuck:: bytes_of ( & uniforms)); 1433 stride /=2 ; 1434 big_n *=2 ; 1435 1436let buffers =if i %2 ==0 { 1437vec! [ 1438 buffer_a. clone (), 1439 buffer_b. clone (), 1440 twiddles_buffer. clone (), 1441 uniforms_buffer. clone (), 1442] 1443} else { 1444vec! [ 1445 buffer_b. clone (), 1446 buffer_a. clone (), 1447 twiddles_buffer. clone (), 1448 uniforms_buffer. clone (), 1449] 1450}; 1451 passes. push ( 1452super :: MaterialPassBuilder :: new () 1453. buffers ( buffers) 1454. bind_group ( & gpu, & shader) 1455. build () 1456. unwrap (), 1457); 1458} 1459let readback = gpu. buffer ( & input_batch). copy_dst (). map_read (). build (); 1460( passes, Some ( readback)) 1461}; 1462 1463// Allocate twice as many buffers as each batch uses. 1464// This lets us send data over while the GPU is still working. 1465let mut material =super :: MaterialBuilder :: new () 1466. batch_size ( 1 ) // use 1 instance per batch since we're pre-batching our inputs 1467. n_batches ( 2 ) 1468. enable_timing () 1469. build ( gpu, passes_gen); 1470let pipeline =super :: get_pipeline ( & gpu, & shader); 1471 1472let warmup =Instant :: now () +Duration :: from_secs ( 3 ); 1473let end = warmup +Duration :: from_secs ( 5 ); 1474let mut n_iter: u32 =0 ; 1475let mut sum_gpu_ns: f64 =0.0 ; 1476let mut now =Instant :: now (); 1477while now < end{ 1478// Fill the pipeline. 1479if super :: submit_fft_v1_batch ( 1480& gpu, 1481& shader, 1482& mut material, 1483& pipeline, 1484 data. len (), 1485& input_batch, 1486) 1487. unwrap () 1488 >0 1489{ 1490// Keep submitting as long as we're able to. 1491continue ; 1492} 1493 1494// Drain if pipeline is full. 1495let mut n_results =0 ; 1496let data_len = data. len (); 1497super :: drain_fft_v0 ( & gpu, & mut material, |_, data_batch|{ 1498for iin 0 ..BATCH_SIZE { 1499 result. copy_from_slice ( & data_batch[ i* data_len..( i +1 ) * data_len]); 1500} 1501 n_results +=BATCH_SIZE as u32 ; 1502Ok (()) 1503}) 1504. unwrap (); 1505let mut cur_sum_gpu_ns: f64 =0.0 ; 1506super :: drain_fft_v0_timing ( gpu, & mut material, |elapsed_ns|{ 1507 cur_sum_gpu_ns += elapsed_ns; 1508}) 1509. unwrap (); 1510if now > warmup{ 1511 n_iter += n_results; 1512 sum_gpu_ns += cur_sum_gpu_ns; 1513} 1514 now =Instant :: now (); 1515} 1516let duration =( now - warmup) / n_iter; 1517println! ( "Duration (wall): {:?}" , duration); 1518 1519let gpu_duration =Duration :: from_secs_f64 ( sum_gpu_ns /( n_iteras f64 * 1e9 )); 1520println! ( "Duration (GPU): {:?}" , gpu_duration); 1521 1522let mut result_ref: Vec < rustfft:: num_complex:: Complex < f64 >> =complex_to_t ( & data); 1523let mut ref_planner = rustfft:: FftPlanner ::< f64 >:: new (); 1524let ref_fft = ref_planner. plan_fft_forward ( result_ref. len ()); 1525 ref_fft. process ( & mut result_ref); 1526let result_ref_64: Vec < Complex < f64 >> =complex_to_t ( & result_ref); 1527 1528let result_64 =complex_to_t ::< f32 , f64 >( & result); 1529evaluate_results ( 1530& result_ref_64, 1531& result_64, 1532& duration_dft, 1533& duration, 1534"FFT v1" , 1535); 1536} 1537 1538fn evaluate_fft_v2 ( 1539data : & [ Complex < f32 >], 1540gpu : & super :: Gpu , 1541duration_dft : & std:: time:: Duration , 1542shader : super :: Shader , 1543) { 1544let mut result =Vec :: from ( data); 1545const BATCH_SIZE : usize =1024 ; 1546 1547let mut input_batch =Vec ::< Complex < f32 >>:: new (); 1548 input_batch. reserve ( data. len () * BATCH_SIZE ); 1549for _in 0 ..BATCH_SIZE { 1550 input_batch. extend_from_slice ( data); 1551} 1552 1553let twiddles =precompute_twiddles ::< f32 >( data. len ()); 1554let twiddles_buffer = gpu. buffer ( & twiddles). storage (). copy_dst (). build (); 1555 gpu. queue . write_buffer ( 1556& twiddles_buffer, 15570 , 1558 bytemuck:: cast_slice ( twiddles. as_slice ()), 1559); 1560 1561let passes_gen = || ->( Vec < super :: MaterialPass >, Option < wgpu:: Buffer >) { 1562let buffer_a = gpu 1563. buffer ( & input_batch) 1564. storage () 1565. copy_src () 1566. copy_dst () 1567. build (); 1568let buffer_b = gpu 1569. buffer ( & input_batch) 1570. storage () 1571. copy_src () 1572. copy_dst () 1573. build (); 1574 1575let mut passes =Vec ::< super :: MaterialPass >:: new (); 1576 1577// TODO each pass needs its own uniforms, but each *instance* should share them 1578# [ repr ( C )] 1579# [ derive ( Clone , Copy , bytemuck :: Pod , bytemuck :: Zeroable )] 1580struct Uniforms { 1581input_len : u32 , 1582n_iter : u32 , 1583} 1584let uniforms =Uniforms { 1585input_len : data. len () as u32 , 1586n_iter : log_k_of ::< 4 >( data. len ()) as u32 , 1587}; 1588let uniforms_buffer = gpu. buffer ( & uniforms). uniform (). copy_dst (). build (); 1589 gpu. queue 1590. write_buffer ( & uniforms_buffer, 0 , bytemuck:: bytes_of ( & uniforms)); 1591 1592 passes. push ( 1593super :: MaterialPassBuilder :: new () 1594. buffers ( vec! [ 1595 buffer_a, 1596 buffer_b, 1597 twiddles_buffer. clone (), 1598 uniforms_buffer, 1599]) 1600. bind_group ( & gpu, & shader) 1601. build () 1602. unwrap (), 1603); 1604let readback = gpu. buffer ( & input_batch). copy_dst (). map_read (). build (); 1605( passes, Some ( readback)) 1606}; 1607 1608// Allocate twice as many buffers as each batch uses. 1609// This lets us send data over while the GPU is still working. 1610let mut material =super :: MaterialBuilder :: new () 1611. batch_size ( 1 ) // use 1 instance per batch since we're pre-batching our inputs 1612. n_batches ( 2 ) 1613. enable_timing () 1614. build ( gpu, passes_gen); 1615let pipeline =super :: get_pipeline ( & gpu, & shader); 1616 1617let warmup =Instant :: now () +Duration :: from_secs ( 3 ); 1618let end = warmup +Duration :: from_secs ( 5 ); 1619let mut n_iter: u32 =0 ; 1620let mut sum_gpu_ns: f64 =0.0 ; 1621let mut now =Instant :: now (); 1622while now < end{ 1623// Fill the pipeline. 1624if super :: submit_fft_v2_batch ( 1625& gpu, 1626& shader, 1627& mut material, 1628& pipeline, 1629 data. len (), 1630& input_batch, 1631) 1632. unwrap () 1633 >0 1634{ 1635// Keep submitting as long as we're able to. 1636continue ; 1637} 1638 1639// Drain if pipeline is full. 1640let mut n_results =0 ; 1641let data_len = data. len (); 1642super :: drain_fft_v0 ( & gpu, & mut material, |_, data_batch|{ 1643for iin 0 ..BATCH_SIZE { 1644 result. copy_from_slice ( & data_batch[ i* data_len..( i +1 ) * data_len]); 1645} 1646 n_results +=BATCH_SIZE as u32 ; 1647Ok (()) 1648}) 1649. unwrap (); 1650let mut cur_sum_gpu_ns: f64 =0.0 ; 1651super :: drain_fft_v0_timing ( gpu, & mut material, |elapsed_ns|{ 1652 cur_sum_gpu_ns += elapsed_ns; 1653}) 1654. unwrap (); 1655if now > warmup{ 1656 n_iter += n_results; 1657 sum_gpu_ns += cur_sum_gpu_ns; 1658} 1659 now =Instant :: now (); 1660} 1661let duration =( now - warmup) / n_iter; 1662println! ( "Duration (wall): {:?}" , duration); 1663 1664let gpu_duration =Duration :: from_secs_f64 ( sum_gpu_ns /( n_iteras f64 * 1e9 )); 1665println! ( "Duration (GPU): {:?}" , gpu_duration); 1666 1667let mut result_ref: Vec < rustfft:: num_complex:: Complex < f64 >> =complex_to_t ( & data); 1668let mut ref_planner = rustfft:: FftPlanner ::< f64 >:: new (); 1669let ref_fft = ref_planner. plan_fft_forward ( result_ref. len ()); 1670 ref_fft. process ( & mut result_ref); 1671let result_ref_64: Vec < Complex < f64 >> =complex_to_t ( & result_ref); 1672 1673let result_64 =complex_to_t ::< f32 , f64 >( & result); 1674evaluate_results ( 1675& result_ref_64, 1676& result_64, 1677& duration_dft, 1678& duration, 1679"FFT v2" , 1680); 1681} 1682 1683fn evaluate_fft_v4 ( 1684data : & [ Complex < f32 >], 1685gpu : & super :: Gpu , 1686duration_dft : & std:: time:: Duration , 1687shader : super :: Shader , 1688) { 1689let mut result =Vec :: from ( data); 1690const BATCH_SIZE : usize =2048 ; 1691 1692let mut input_batch =Vec ::< Complex < f32 >>:: new (); 1693 input_batch. reserve ( data. len () * BATCH_SIZE ); 1694for _in 0 ..BATCH_SIZE { 1695 input_batch. extend_from_slice ( data); 1696} 1697 1698let twiddles =precompute_twiddles ::< f32 >( data. len ()); 1699let twiddles_buffer = gpu. buffer ( & twiddles). storage (). copy_dst (). build (); 1700 gpu. queue . write_buffer ( 1701& twiddles_buffer, 17020 , 1703 bytemuck:: cast_slice ( twiddles. as_slice ()), 1704); 1705 1706let passes_gen = || ->( Vec < super :: MaterialPass >, Option < wgpu:: Buffer >) { 1707let buffer_a = gpu 1708. buffer ( & input_batch) 1709. storage () 1710. copy_src () 1711. copy_dst () 1712. build (); 1713let buffer_b = gpu 1714. buffer ( & input_batch) 1715. storage () 1716. copy_src () 1717. copy_dst () 1718. build (); 1719 1720let mut passes =Vec ::< super :: MaterialPass >:: new (); 1721 1722// TODO each pass needs its own uniforms, but each *instance* should share them 1723# [ repr ( C )] 1724# [ derive ( Clone , Copy , bytemuck :: Pod , bytemuck :: Zeroable )] 1725struct Uniforms { 1726input_len : u32 , 1727n_iter : u32 , 1728} 1729let uniforms =Uniforms { 1730input_len : data. len () as u32 , 1731n_iter : log_k_of ::< 8 >( data. len ()) as u32 , 1732}; 1733let uniforms_buffer = gpu. buffer ( & uniforms). uniform (). copy_dst (). build (); 1734 gpu. queue 1735. write_buffer ( & uniforms_buffer, 0 , bytemuck:: bytes_of ( & uniforms)); 1736 1737 passes. push ( 1738super :: MaterialPassBuilder :: new () 1739. buffers ( vec! [ 1740 buffer_a, 1741 buffer_b, 1742 twiddles_buffer. clone (), 1743 uniforms_buffer, 1744]) 1745. bind_group ( & gpu, & shader) 1746. build () 1747. unwrap (), 1748); 1749let readback = gpu. buffer ( & input_batch). copy_dst (). map_read (). build (); 1750( passes, Some ( readback)) 1751}; 1752 1753// Allocate twice as many buffers as each batch uses. 1754// This lets us send data over while the GPU is still working. 1755let mut material =super :: MaterialBuilder :: new () 1756. batch_size ( 1 ) // use 1 instance per batch since we're pre-batching our inputs 1757. n_batches ( 4 ) 1758. enable_timing () 1759. build ( gpu, passes_gen); 1760let pipeline =super :: get_pipeline ( & gpu, & shader); 1761 1762let warmup =Instant :: now () +Duration :: from_secs ( 3 ); 1763let end = warmup +Duration :: from_secs ( 5 ); 1764let mut n_iter: u32 =0 ; 1765let mut sum_gpu_ns: f64 =0.0 ; 1766let mut now =Instant :: now (); 1767while now < end{ 1768// Fill the pipeline. 1769if super :: submit_fft_v2_batch ( 1770& gpu, 1771& shader, 1772& mut material, 1773& pipeline, 1774 data. len (), 1775& input_batch, 1776) 1777. unwrap () 1778 >0 1779{ 1780// Keep submitting as long as we're able to. 1781continue ; 1782} 1783 1784// Drain if pipeline is full. 1785let mut n_results =0 ; 1786let data_len = data. len (); 1787super :: drain_fft_v0 ( & gpu, & mut material, |_, data_batch|{ 1788for iin 0 ..BATCH_SIZE { 1789 result. copy_from_slice ( & data_batch[ i* data_len..( i +1 ) * data_len]); 1790} 1791 n_results +=BATCH_SIZE as u32 ; 1792Ok (()) 1793}) 1794. unwrap (); 1795let mut cur_sum_gpu_ns: f64 =0.0 ; 1796super :: drain_fft_v0_timing ( gpu, & mut material, |elapsed_ns|{ 1797 cur_sum_gpu_ns += elapsed_ns; 1798}) 1799. unwrap (); 1800if now > warmup{ 1801 n_iter += n_results; 1802 sum_gpu_ns += cur_sum_gpu_ns; 1803} 1804 now =Instant :: now (); 1805} 1806let duration =( now - warmup) / n_iter; 1807println! ( "Duration (wall): {:?}" , duration); 1808 1809let gpu_duration =Duration :: from_secs_f64 ( sum_gpu_ns /( n_iteras f64 * 1e9 )); 1810println! ( "Duration (GPU): {:?}" , gpu_duration); 1811 1812let mut result_ref: Vec < rustfft:: num_complex:: Complex < f64 >> =complex_to_t ( & data); 1813let mut ref_planner = rustfft:: FftPlanner ::< f64 >:: new (); 1814let ref_fft = ref_planner. plan_fft_forward ( result_ref. len ()); 1815 ref_fft. process ( & mut result_ref); 1816let result_ref_64: Vec < Complex < f64 >> =complex_to_t ( & result_ref); 1817 1818let result_64 =complex_to_t ::< f32 , f64 >( & result); 1819evaluate_results ( 1820& result_ref_64, 1821& result_64, 1822& duration_dft, 1823& duration, 1824"FFT v4" , 1825); 1826} 1827 1828# [ test ] 1829fn run_it () { 1830let mut data_f64: Vec < Complex < f64 >> =Vec :: new (); 1831 data_f64. resize (( 2 as usize ). pow ( 12 ), Complex :: ZERO ); 1832// Randomize data. 1833let mut rng =StdRng :: seed_from_u64 ( 42 ); 1834for iin 0 ..data_f64. len () { 1835 data_f64[ i] =Complex :: new ( rng. random (), rng. random ()); 1836} 1837let data_f32: Vec < Complex < f32 >> =complex_to_t ::< f64 , f32 >( & data_f64); 1838 1839let gpu =super :: GpuBuilder :: new (). enable_profiling (). build (). unwrap (); 1840 1841//let duration_dft = evaluate_naive_dft(&data_f32, &gpu); 1842let duration_dft =Duration :: from_secs ( 0 ); 1843//evaluate_fft_v0(&data_f32, &gpu, &duration_dft); 1844//evaluate_fft_v1(&data_f32, &gpu, &duration_dft); 1845//evaluate_fft_v2(&data_f32, &gpu, &duration_dft, super::Shader::new(&gpu, include_wgsl!("fft_v2_barrier.wgsl")).unwrap()); 1846/* 1847evaluate_fft_v2( 1848&data_f32, 1849&gpu, 1850&duration_dft, 1851super::Shader::new(&gpu, include_wgsl!("fft_v3_groupshared.wgsl")).unwrap(), 1852); 1853*/ 1854evaluate_fft_v4 ( 1855& data_f32, 1856& gpu, 1857& duration_dft, 1858super :: Shader :: new ( & gpu, include_wgsl! ( "fft_v4_radix_8.wgsl" )). unwrap (), 1859); 1860} 1861}