yum/demomo
A graphics engine built on wgpu.
git clone https://git.yummers.dev/yum/demomo
ff99423
master
1use anyhow:: Context ; 2use bytemuck:: NoUninit ; 3use derive_builder:: Builder ; 4use std:: borrow:: Cow ; 5use std:: num:: NonZeroU64 ; 6# [ cfg ( feature = "window" )] 7use std:: sync:: Arc ; 8use std:: sync:: mpsc::{ Receiver , Sender }; 9use wgpu:: naga; 10use wgpu:: util:: DeviceExt ; 11 12# [ macro_export ] 13macro_rules ! include_wgsl{ 14( $filename: literal) =>{ 15include_str! ( concat! ( env! ( "OUT_DIR" ), "/shaders/" , $filename)) 16}; 17} 18 19// Magic to allow converting a primitive type, a Vec, or a slice into a slice of bytes. 20pub trait Buffer < T > { 21fn as_bytes ( & self ) ->& [ T ]; 22} 23impl < T : bytemuck:: Pod > Buffer < T > for T { 24fn as_bytes ( & self ) ->& [ T ] { 25 std:: slice:: from_ref ( self ) 26} 27} 28impl < T > Buffer < T > for Vec < T > { 29fn as_bytes ( & self ) ->& [ T ] { 30self . as_slice () 31} 32} 33impl < T > Buffer < T > for & [ T ] { 34fn as_bytes ( & self ) ->& [ T ] { 35self 36} 37} 38 39pub struct BufferBuilder < ' a > { 40label : Option < String >, 41size : u64 , 42usage : wgpu:: BufferUsages , 43mapped_at_creation : bool , 44gpu : &' a Gpu , 45} 46 47impl < ' a > BufferBuilder < ' a > { 48fn new < T : bytemuck:: Pod >( data : & impl Buffer < T >, gpu : &' a Gpu ) ->Self { 49Self :: new_by_sz ( std:: mem:: size_of_val ( data. as_bytes ()), gpu) 50} 51 52fn new_by_sz ( sz : usize , gpu : &' a Gpu ) ->Self { 53Self { 54label : None , 55size : wgpu:: util:: align_to ( sz, 16 ) as u64 , 56usage : wgpu:: BufferUsages :: empty (), 57mapped_at_creation : false , 58gpu : gpu, 59} 60} 61 62pub fn copy_dst ( & mut self ) ->& mut Self { 63self . usage |= wgpu:: BufferUsages :: COPY_DST ; 64self 65} 66 67pub fn copy_src ( & mut self ) ->& mut Self { 68self . usage |= wgpu:: BufferUsages :: COPY_SRC ; 69self 70} 71 72pub fn map_read ( & mut self ) ->& mut Self { 73self . usage |= wgpu:: BufferUsages :: MAP_READ ; 74self 75} 76 77pub fn query_resolve ( & mut self ) ->& mut Self { 78self . usage |= wgpu:: BufferUsages :: QUERY_RESOLVE ; 79self 80} 81 82pub fn storage ( & mut self ) ->& mut Self { 83self . usage |= wgpu:: BufferUsages :: STORAGE ; 84self 85} 86 87pub fn uniform ( & mut self ) ->& mut Self { 88self . usage |= wgpu:: BufferUsages :: UNIFORM ; 89self 90} 91 92pub fn build ( & self ) -> wgpu:: Buffer { 93self . gpu . device . create_buffer ( & wgpu:: BufferDescriptor { 94label : self . label . as_deref (), 95size : self . size , 96usage : self . usage , 97mapped_at_creation : self . mapped_at_creation , 98}) 99} 100} 101 102pub struct GpuBuilder { 103required_features : wgpu:: Features , 104# [ cfg ( feature = "window" )] 105window : Option < Arc < winit:: window:: Window >>, 106} 107 108impl GpuBuilder { 109pub fn new () ->Self { 110Self { 111required_features : wgpu:: Features :: empty (), 112# [ cfg ( feature = "window" )] 113window : None , 114} 115} 116 117pub fn enable_profiling ( mut self ) ->Self { 118self . required_features |= wgpu:: Features :: TIMESTAMP_QUERY ; 119self 120} 121 122# [ cfg ( feature = "window" )] 123pub fn window ( mut self , window : winit:: window:: Window ) ->Self { 124self . window =Some ( Arc :: new ( window)); 125self 126} 127 128pub async fn build ( self ) -> anyhow:: Result < Gpu > { 129# [ cfg ( feature = "window" )] 130let instance = wgpu:: Instance :: new ( wgpu:: InstanceDescriptor { 131backends : wgpu:: Backends :: PRIMARY , 132flags : Default :: default (), 133memory_budget_thresholds : Default :: default (), 134backend_options : Default :: default (), 135display : None , 136}); 137# [ cfg ( not ( feature = "window" ))] 138let instance = wgpu:: Instance :: new ( wgpu:: InstanceDescriptor :: new_without_display_handle ()); 139 140# [ cfg ( feature = "window" )] 141let surface =self 142. window 143. as_ref () 144. map ( |window| instance. create_surface ( window. clone ())) 145. transpose () ?; 146# [ cfg ( feature = "window" )] 147let adapter_req_opt = wgpu:: RequestAdapterOptions { 148power_preference : wgpu:: PowerPreference :: default (), 149compatible_surface : surface. as_ref (), 150force_fallback_adapter : false , 151apply_limit_buckets : true , 152}; 153# [ cfg ( not ( feature = "window" ))] 154let adapter_req_opt = wgpu:: RequestAdapterOptions :: default (); 155let adapter = instance. request_adapter ( & adapter_req_opt). await ?; 156println! ( "Got adapter: {:#?}" , adapter. get_info ()); 157 158let supported = adapter. limits (); 159let mut limits = wgpu:: Limits :: downlevel_defaults (); 160 limits. max_compute_invocations_per_workgroup = 161 supported. max_compute_invocations_per_workgroup ; 162 limits. max_compute_workgroup_storage_size = supported. max_compute_workgroup_storage_size ; 163println! ( 164"Max compute invocations per workgroup: {}" , 165 limits. max_compute_invocations_per_workgroup 166); 167 168let ( device, queue) = adapter 169. request_device ( & wgpu:: DeviceDescriptor { 170label : None , 171required_features : self . required_features , 172required_limits : limits, 173experimental_features : wgpu:: ExperimentalFeatures :: disabled (), 174memory_hints : wgpu:: MemoryHints :: MemoryUsage , 175trace : wgpu:: Trace :: Off , 176}) 177. await ?; 178 179let mut timestamps =None ; 180if self 181. required_features 182. contains ( wgpu:: Features :: TIMESTAMP_QUERY ) 183{ 184 timestamps =Some ( device. create_query_set ( & wgpu:: QuerySetDescriptor { 185label : None , 186ty : wgpu:: QueryType :: Timestamp , 187count : 2 , 188})); 189} 190 191Ok ( Gpu { 192 instance, 193 adapter, 194 device, 195 queue, 196# [ cfg ( feature = "window" )] 197window : self . window , 198# [ cfg ( feature = "window" )] 199surface : surface, 200 timestamps, 201}) 202} 203} 204 205pub struct Gpu { 206pub instance : wgpu:: Instance , 207pub adapter : wgpu:: Adapter , 208pub device : wgpu:: Device , 209pub queue : wgpu:: Queue , 210# [ cfg ( feature = "window" )] 211pub window : Option < Arc < winit:: window:: Window >>, 212# [ cfg ( feature = "window" )] 213pub surface : Option < wgpu:: Surface < ' static >>, 214pub timestamps : Option < wgpu:: QuerySet >, 215} 216 217impl Gpu { 218pub fn buffer < T : bytemuck:: Pod >( & self , data : & impl Buffer < T >) ->BufferBuilder < ' _ > { 219BufferBuilder :: new ( data, self ) 220} 221 222pub fn buffer_by_sz ( & self , sz : usize ) ->BufferBuilder < ' _ > { 223BufferBuilder :: new_by_sz ( sz, self ) 224} 225} 226 227fn get_entry_point_names ( module : & naga:: Module ) -> anyhow:: Result < Vec < String >> { 228if module. entry_points . is_empty () { 229 anyhow:: bail!( "Expected at least one entry point, found none." ); 230} 231Ok ( module. entry_points . iter (). map ( |e| e. name . clone ()). collect ()) 232} 233 234fn get_entry_point_workgroup_size ( module : & naga:: Module , name : & str ) -> anyhow:: Result <[ u32 ; 3 ]> { 235for entry_pointin module. entry_points . iter () { 236if entry_point. name != name{ 237continue ; 238} 239return Ok ( entry_point. workgroup_size ); 240} 241 anyhow:: bail!( "Could not find entry point named {}" , name); 242} 243 244/* 245fn for_each_global_variable<T>( 246module: &naga::Module, 247module_info: &naga::valid::ModuleInfo, 248mut f: impl FnMut( 249naga::Handle<GlobalVariable>, 250&naga::GlobalVariable, 251&naga::ResourceBinding, 252&wgpu::ShaderStages, 253bool, 254) -> anyhow::Result<Option<T>>, 255) -> anyhow::Result<Vec<T>> { 256let mut result = Vec::new(); 257for (global_handle, global) in module.global_variables.iter() { 258let Some(binding) = &global.binding else { 259continue; 260}; 261 262let can_write = match global.space { 263naga::AddressSpace::Storage { access } => { 264access.contains(naga::StorageAccess::STORE) 265|| access.contains(naga::StorageAccess::ATOMIC) 266} 267naga::AddressSpace::Uniform => false, 268_ => { 269eprintln!( 270"Skipping global {:?} with address space {:?}", 271global.name, global.space 272); 273continue; 274} 275}; 276let mut stages = wgpu::ShaderStages::empty(); 277for (entry_idx, entry) in module.entry_points.iter().enumerate() { 278let usage = module_info.get_entry_point(entry_idx)[global_handle]; 279if usage.is_empty() { 280continue; 281} 282 283stages |= match entry.stage { 284naga::ShaderStage::Vertex => wgpu::ShaderStages::VERTEX, 285naga::ShaderStage::Fragment => wgpu::ShaderStages::FRAGMENT, 286naga::ShaderStage::Compute => wgpu::ShaderStages::COMPUTE, 287_ => continue, 288} 289} 290if stages.is_empty() { 291eprintln!( 292"Skipping bound global {:?} which is not referenced by any stage", 293global.name 294); 295continue; 296} 297if let Some(value) = f(global_handle, global, binding, &stages, can_write)? { 298result.push(value); 299} 300} 301Ok(result) 302} 303*/ 304 305fn get_bind_group_layout ( 306gpu : & Gpu , 307module : & naga:: Module , 308module_info : & naga:: valid:: ModuleInfo , 309module_layout : & naga:: proc:: Layouter , 310) -> anyhow:: Result < wgpu:: BindGroupLayout > { 311let mut entries =Vec ::< wgpu:: BindGroupLayoutEntry >:: new (); 312for ( global_handle, global) in module. global_variables . iter () { 313let Some ( binding) =& global. binding else { 314continue ; 315}; 316 317let can_write =match global. space { 318 naga:: AddressSpace :: Storage { access} =>{ 319 access. contains ( naga:: StorageAccess :: STORE ) 320 || access. contains ( naga:: StorageAccess :: ATOMIC ) 321} 322 naga:: AddressSpace :: Uniform =>false , 323 _ =>{ 324eprintln! ( 325"Skipping global {:?} with address space {:?}" , 326 global. name , global. space 327); 328continue ; 329} 330}; 331 332let mut stages = wgpu:: ShaderStages :: empty (); 333for ( entry_idx, entry) in module. entry_points . iter (). enumerate () { 334let usage = module_info. get_entry_point ( entry_idx)[ global_handle]; 335if usage. is_empty () { 336continue ; 337} 338 339 stages |=match entry. stage { 340 naga:: ShaderStage :: Vertex => wgpu:: ShaderStages :: VERTEX , 341 naga:: ShaderStage :: Fragment => wgpu:: ShaderStages :: FRAGMENT , 342 naga:: ShaderStage :: Compute => wgpu:: ShaderStages :: COMPUTE , 343 _ =>continue , 344} 345} 346if stages. is_empty () { 347eprintln! ( 348"Skipping bound global {:?} which is not referenced by any stage" , 349 global. name 350); 351continue ; 352} 353 354let global_ty =& module. types [ global. ty ]; 355let entry =match & module. types [ global. ty ]. inner { 356 naga:: ir:: TypeInner :: Array { ..} =>{ 357let min_size = module_layout[ global. ty ]. size ; 358 wgpu:: BindGroupLayoutEntry { 359binding : binding. binding , 360visibility : stages, 361ty : wgpu:: BindingType :: Buffer { 362ty : wgpu:: BufferBindingType :: Storage { 363read_only : !can_write, 364}, 365min_binding_size : NonZeroU64 :: new ( min_sizeas u64 ), 366has_dynamic_offset : false , 367}, 368count : None , 369} 370} 371 naga:: ir:: TypeInner :: Struct { ..} =>{ 372let min_size = module_layout[ global. ty ]. size ; 373 wgpu:: BindGroupLayoutEntry { 374binding : binding. binding , 375visibility : stages, 376ty : wgpu:: BindingType :: Buffer { 377ty : wgpu:: BufferBindingType :: Uniform , 378min_binding_size : NonZeroU64 :: new ( min_sizeas u64 ), 379has_dynamic_offset : false , 380}, 381count : None , 382} 383} 384 _ =>{ 385 anyhow:: bail!( format! ( 386"Unsupported global type: {:?} (name={:?})" , 387 global_ty. name , global_ty. inner 388)) 389} 390}; 391 entries. push ( entry); 392} 393let layout = wgpu:: BindGroupLayoutDescriptor { 394label : None , 395entries : & entries, 396}; 397Ok ( gpu. device . create_bind_group_layout ( & layout)) 398} 399 400pub struct ShaderEntryPoint { 401pub name : String , 402pub workgroup_size : [ u32 ; 3 ], 403} 404 405# [ derive ( PartialEq , Eq )] 406pub enum VertexSemantic { 407Position , 408Normal , 409Uv0 , 410} 411 412pub struct VertexLayout { 413pub semantics : Vec < VertexSemantic >, 414pub attributes : Vec < wgpu:: VertexAttribute >, 415pub stride : u64 , 416} 417 418// Vertex data is conceived of as zero or more streams of data. We need a way 419// to translate various higher-level types (f32, i32, etc) into these untyped 420// streams, and finally at the end, get an array of them. This builder handles 421// doing exactly that. 422// Example usage: 423// let positions = [[-0.5_f32, -0.5, 0.5], [0.5, -0.5, 0.5], [0.0, 0.5, 0.5]]; 424// let normals = [[0.0_f32, 0.0, 1.0]; 3]; 425// let uvs = [[0.0_f32, 0.0], [1.0, 0.0], [0.5, 1.0]]; 426// let data_streams = VertexDataStreamBuilder::new() 427// .stream(VertexSemantic::Position, &positions) 428// .stream(VertexSemantic::Normal, &normals) 429// .stream(VertexSemantic::Uv0, &uvs) 430// .build(); 431 432pub struct VertexDataStreamBuilder < ' a > { 433streams : Vec <( VertexSemantic , &' a [ u8 ])>, 434} 435 436impl < ' a > VertexDataStreamBuilder < ' a > { 437pub fn new () ->Self { 438Self { 439streams : Vec :: new (), 440} 441} 442 443pub fn stream < T : NoUninit >( mut self , semantic : VertexSemantic , data : &' a [ T ]) ->Self { 444let raw_data: & [ u8 ] = bytemuck:: cast_slice ( & data); 445self . streams . push (( semantic, raw_data)); 446self 447} 448 449pub fn build ( self ) ->Vec <( VertexSemantic , &' a [ u8 ])> { 450self . streams 451} 452} 453 454impl VertexLayout { 455pub fn vertex_buffer_layout ( & self ) -> wgpu:: VertexBufferLayout < ' _ > { 456 wgpu:: VertexBufferLayout { 457array_stride : self . stride , 458step_mode : wgpu:: VertexStepMode :: Vertex , 459attributes : & self . attributes , 460} 461} 462 463// Assembles streams of vertex data into an interleaved stream. 464fn interleave_data ( 465& self , 466n_vertices : usize , 467data_streams : Vec <( VertexSemantic , & [ u8 ])>, 468) -> anyhow:: Result < Vec < u8 >> { 469if data_streams. len () !=self . attributes . len () { 470 anyhow:: bail!( 471"Expected {} vertex data streams, got {}" , 472self . attributes . len (), 473 data_streams. len () 474); 475} 476let mut streams: Vec < _ > =self 477. semantics 478. iter () 479. zip ( & self . attributes ) 480. map ( |( semantic, attr) |{ 481let ( _, data) = data_streams 482. iter () 483. find ( |( s, _) | s == semantic) 484. expect ( "Vertex shader given unrecognized input semantic" ); 485let size = attr. format . size () as usize ; 486let start = attr. offset as usize ; 487( start..start + size, data. chunks_exact ( size)) 488}) 489. collect (); 490 491let stride =self . stride as usize ; 492let mut data =vec! [ 0 ; n_vertices* stride]; 493 494for vertexin data. chunks_exact_mut ( stride) { 495for ( range, source) in & mut streams{ 496let value = source. next (). expect ( "Not enough vertex data" ); 497 vertex[ range. clone ()]. copy_from_slice ( value); 498} 499} 500 501Ok ( data) 502} 503 504pub fn vertex_buffer ( 505& self , 506gpu : & Gpu , 507data_streams : Vec <( VertexSemantic , & [ u8 ])>, 508n_vertices : usize , 509) -> anyhow:: Result < wgpu:: Buffer > { 510let vertex_data_bytes =self . interleave_data ( n_vertices, data_streams) ?; 511Ok ( gpu 512. device 513. create_buffer_init ( & wgpu:: util:: BufferInitDescriptor { 514label : None , 515contents : & vertex_data_bytes, 516usage : wgpu:: BufferUsages :: VERTEX , 517})) 518} 519} 520 521pub struct Shader { 522pub bind_group_layout : wgpu:: BindGroupLayout , 523pub naga_module : naga:: Module , 524pub shader_module : wgpu:: ShaderModule , 525pub entry_points : Vec < ShaderEntryPoint >, 526uses_camera : bool , 527} 528 529impl Shader { 530pub fn new ( gpu : & Gpu , shader : & str ) -> anyhow:: Result < Self > { 531let naga_module = naga:: front:: wgsl:: parse_str ( shader) ?; 532let module_info = naga:: valid:: Validator :: new ( 533 naga:: valid:: ValidationFlags :: all (), 534 naga:: valid:: Capabilities :: all (), 535) 536. validate ( & naga_module) ?; 537let mut module_layout = naga:: proc:: Layouter :: default (); 538 module_layout. update ( naga_module. to_ctx ()) ?; 539 540Self :: ensure_compute_compatibility ( gpu, & naga_module) ?; 541let bind_group_layout = 542get_bind_group_layout ( gpu, & naga_module, & module_info, & module_layout) ?; 543let uses_camera = naga_module. global_variables . iter (). any ( |( _, global) |{ 544 global. space == naga:: AddressSpace :: Uniform 545 && global. binding . as_ref (). is_some_and ( |binding|{ 546 binding. group ==0 && binding. binding ==0 547}) 548}); 549let entry_point_names =get_entry_point_names ( & naga_module) ?; 550let entry_points = entry_point_names 551. iter () 552. map ( |name|{ 553let workgroup_size =get_entry_point_workgroup_size ( & naga_module, name) ?; 554Ok ( ShaderEntryPoint { 555name : name. clone (), 556 workgroup_size, 557}) 558}) 559. collect ::< anyhow:: Result < Vec < _ >>>() ?; 560 561let module_descriptor = wgpu:: ShaderModuleDescriptor { 562label : None , 563source : wgpu:: ShaderSource :: Naga ( Cow :: Owned ( naga_module. clone ())), 564}; 565let shader_module = gpu. device . create_shader_module ( module_descriptor. clone ()); 566 567Ok ( Self { 568 bind_group_layout, 569naga_module : naga_module, 570 shader_module, 571 entry_points, 572 uses_camera, 573}) 574} 575 576pub fn uses_camera ( & self ) ->bool { 577self . uses_camera 578} 579 580fn ensure_compute_compatibility ( gpu : & Gpu , module : & naga:: Module ) -> anyhow:: Result <()> { 581let mut has_compute_stage =false ; 582for entry_pointin module. entry_points . iter () { 583if entry_point. stage == naga:: ShaderStage :: Compute { 584 has_compute_stage =true ; 585} 586} 587if !has_compute_stage{ 588return Ok (()); 589} 590let downlevel_caps = gpu. adapter . get_downlevel_capabilities (); 591if !downlevel_caps 592. flags 593. contains ( wgpu:: DownlevelFlags :: COMPUTE_SHADERS ) 594{ 595 anyhow:: bail!( 596"GPU {} does not support compute shaders." , 597 gpu. adapter . get_info (). name 598) 599} 600Ok (()) 601} 602 603// Returns the first shader entry point for the given stage. 604pub fn get_shader_by_stage ( 605& self , 606stage : naga:: ir:: ShaderStage , 607) -> anyhow:: Result < & naga:: EntryPoint > { 608let Some ( shader) =self 609. naga_module 610. entry_points 611. iter () 612. find ( |ep| ep. stage == stage) 613else { 614 anyhow:: bail!( "Shader not found for stage {:?}" , stage); 615}; 616Ok ( shader) 617} 618 619fn vertex_member_to_semantic ( & self , name : & str ) -> anyhow:: Result < VertexSemantic > { 620// Vertex members might be suffixed with _0 by slangc. Since none of our reserved names have underscores, we 621// can safely split by underscore. 622let name = name. split ( '_' ). collect ::< Vec < _ >>()[ 0 ]; 623match name{ 624"objPos" =>Ok ( VertexSemantic :: Position ), 625"objNormal" =>Ok ( VertexSemantic :: Normal ), 626"uv0" =>Ok ( VertexSemantic :: Uv0 ), 627 _ => anyhow:: bail!( "Unknown vertex member: {}" , name), 628} 629} 630 631fn struct_member_to_vertex_format ( 632& self , 633member : & naga:: ir:: StructMember , 634) -> anyhow:: Result < wgpu:: VertexFormat > { 635let inner_type =& self . naga_module . types [ member. ty ]. inner ; 636match inner_type{ 637 naga:: TypeInner :: Vector { size, scalar} =>match ( scalar. kind , scalar. width ) { 638( naga:: ScalarKind :: Float , 4 ) =>match size{ 639 naga:: VectorSize :: Bi =>Ok ( wgpu:: VertexFormat :: Float32x2 ), 640 naga:: VectorSize :: Tri =>Ok ( wgpu:: VertexFormat :: Float32x3 ), 641 naga:: VectorSize :: Quad =>Ok ( wgpu:: VertexFormat :: Float32x4 ), 642}, 643 _ => anyhow:: bail!( "Unsupported vertex member vector type: {:?}" , inner_type), 644}, 645 _ => anyhow:: bail!( "Unsupported vertex member type: {:?}" , inner_type), 646} 647} 648 649fn struct_member_shader_location ( 650& self , 651member : & naga:: ir:: StructMember , 652) -> anyhow:: Result < wgpu:: ShaderLocation > { 653match & member. binding { 654Some ( naga:: Binding :: Location { location, ..}) =>Ok ( * location), 655// TODO should handle built-in bindings 656 _ => anyhow:: bail!( "Unsupported vertex member binding: {:?}" , member. binding ), 657} 658} 659 660pub fn get_vertex_layout ( & self ) -> anyhow:: Result < VertexLayout > { 661let vertex_shader =self . get_shader_by_stage ( naga:: ir:: ShaderStage :: Vertex ) ?; 662if vertex_shader. function . arguments . len () !=1 { 663 anyhow:: bail!( 664"Vertex shader must have exactly one argument; got {}" , 665 vertex_shader. function . arguments . len () 666); 667} 668let arg =& vertex_shader. function . arguments [ 0 ]; 669let arg_ty =& self . naga_module . types [ arg. ty ]; 670match & arg_ty. inner { 671 naga:: ir:: TypeInner :: Struct { members, ..} =>{ 672let semantics = members 673. iter () 674. map ( |m|{ 675let Some ( name) =& m. name else { 676// This should never happen 677 anyhow:: bail!( "Vertex shader input member missing name" ); 678}; 679self . vertex_member_to_semantic ( name. as_str ()) 680}) 681. collect ::< anyhow:: Result < Vec < _ >>>() ?; 682let mut acc_offset =0 ; 683let attributes = members 684. iter () 685. map ( |m| -> anyhow:: Result < wgpu:: VertexAttribute > { 686let vertex_fmt =self . struct_member_to_vertex_format ( m) ?; 687let location =self . struct_member_shader_location ( m) ?; 688let offset = acc_offset; 689 acc_offset += vertex_fmt. size (); 690Ok ( wgpu:: VertexAttribute { 691format : vertex_fmt, 692 offset, 693shader_location : location, 694}) 695}) 696. collect ::< anyhow:: Result < Vec < _ >>>() ?; 697let stride = attributes. iter (). fold ( 0 , |acc, a| acc + a. format . size ()); 698Ok ( VertexLayout { 699 semantics, 700 attributes, 701 stride, 702}) 703} 704 _ =>{ 705 anyhow:: bail!( "Argument type must be a struct; got {:?}" , arg_ty. inner ); 706} 707} 708} 709} 710 711pub struct MaterialPass { 712pub buffers : Vec < wgpu:: Buffer >, 713pub bind_group : wgpu:: BindGroup , 714} 715 716pub struct MaterialPassBuilder { 717buffers : Vec < wgpu:: Buffer >, 718bind_group : Option < wgpu:: BindGroup >, 719} 720 721impl MaterialPassBuilder { 722pub fn new () ->Self { 723Self { 724buffers : Vec :: new (), 725bind_group : None , 726} 727} 728 729pub fn buffers ( mut self , buffers : Vec < wgpu:: Buffer >) ->Self { 730self . buffers = buffers; 731self 732} 733 734// Must be run after buffers(), etc. 735pub fn bind_group ( mut self , gpu : & Gpu , shader : & Shader ) ->Self { 736let mut descriptor_entries =Vec ::< wgpu:: BindGroupEntry >:: new (); 737let entries = 738self . buffers 739. iter () 740. enumerate () 741. fold ( Vec :: new (), |mut entries, ( index, buf) |{ 742 entries. push ( wgpu:: BindGroupEntry { 743binding : indexas u32 , 744resource : buf. as_entire_binding (), 745}); 746 entries 747}); 748 descriptor_entries. extend ( entries); 749let descriptor = wgpu:: BindGroupDescriptor { 750label : None , 751layout : & shader. bind_group_layout , 752entries : & descriptor_entries, 753}; 754let bind_group = gpu. device . create_bind_group ( & descriptor); 755self . bind_group =Some ( bind_group); 756self 757} 758 759pub fn build ( self ) -> anyhow:: Result < MaterialPass > { 760if self . bind_group . is_none () { 761 anyhow:: bail!( "bind_group not set" ); 762} 763Ok ( MaterialPass { 764buffers : self . buffers , 765bind_group : self . bind_group . unwrap (), 766}) 767} 768} 769 770pub struct MaterialInstance { 771pub passes : Vec < MaterialPass >, 772pub readback : Option < wgpu:: Buffer >, 773} 774 775impl MaterialInstance { 776pub fn new ( passes : Vec < MaterialPass >, readback : Option < wgpu:: Buffer >) ->Self { 777Self { 778passes : passes, 779readback : readback, 780} 781} 782} 783 784// A batch of Material work to be submitted together. This allows us to send a 785// batch of work while another is still processing on the GPU. 786pub struct MaterialBatch { 787pub instances : Vec < MaterialInstance >, 788pub ready_tx : Sender <( usize , Result <(), wgpu:: BufferAsyncError >)>, 789pub ready_rx : Receiver <( usize , Result <(), wgpu:: BufferAsyncError >)>, 790// Optional timestamp (ts) buffers for profiling. 791pub timing : Option < MaterialTiming >, 792// Whether the batch is currently processing. This is set true as soon as a batch is reserved with get_batch(). 793// The caller is responsible for setting this false 794pub busy : bool , 795// How many instances in the batch are currently processing. Set to 796// batch_size (n_instancces) when the batch is reserved The caller may use 797// this to track completion as each instance in the batch completes. 798pub n_busy : usize , 799} 800 801impl MaterialBatch { 802// Use MaterialBuilder instead. It handles creating batches. 803fn new ( 804gpu : & Gpu , 805mut passes_generator : impl FnMut () ->( Vec < MaterialPass >, Option < wgpu:: Buffer >), 806n_instances : usize , 807enable_timing : bool , 808) ->Self { 809let mut instances =Vec ::< MaterialInstance >:: new (); 810 instances. reserve ( n_instances); 811for _in 0 ..n_instances{ 812let ( passes, readback) =passes_generator (); 813 instances. push ( MaterialInstance :: new ( passes, readback)); 814} 815let ( ready_tx, ready_rx) = std:: sync:: mpsc:: channel (); 816let mut timing =None ; 817if enable_timing{ 818 std:: hint:: cold_path (); 819 timing =Some ( MaterialTiming :: new ( gpu)); 820} 821Self { 822instances : instances, 823ready_tx : ready_tx, 824ready_rx : ready_rx, 825timing : timing, 826busy : false , 827n_busy : 0 , 828} 829} 830 831pub fn enable_profiling ( mut self , gpu : & Gpu ) ->Self { 832self . timing =Some ( MaterialTiming :: new ( gpu)); 833self 834} 835} 836 837pub struct MaterialTiming { 838pub ts_buf : wgpu:: Buffer , 839pub ts_readback : wgpu:: Buffer , 840pub ready_tx : Sender < Result <(), wgpu:: BufferAsyncError >>, 841pub ready_rx : Receiver < Result <(), wgpu:: BufferAsyncError >>, 842pub busy : bool , 843} 844 845impl MaterialTiming { 846pub fn new ( gpu : & Gpu ) ->Self { 847let ts_buf = gpu. buffer_by_sz ( 16 ). query_resolve (). copy_src (). build (); 848let ts_readback = gpu. buffer_by_sz ( 16 ). copy_dst (). map_read (). build (); 849let ( ready_tx, ready_rx) = std:: sync:: mpsc:: channel (); 850Self { 851ts_buf : ts_buf, 852ts_readback : ts_readback, 853ready_tx : ready_tx, 854ready_rx : ready_rx, 855busy : false , 856} 857} 858} 859 860pub struct Material { 861pub batches : Vec < MaterialBatch >, 862pub batch_size : usize , 863} 864 865impl Material { 866pub fn get_batch ( & mut self ) ->Option < & mut MaterialBatch > { 867let batch =self . batches . iter_mut (). find ( |batch|{ 868if batch. busy { 869return false ; 870} 871if let Some ( timing) =& batch. timing { 872 std:: hint:: cold_path (); 873if timing. busy { 874return false ; 875} 876} 877true 878}) ?; 879// Found a batch. 880 batch. busy =true ; 881if let Some ( timing) =& mut batch. timing { 882 std:: hint:: cold_path (); 883 timing. busy =true ; 884} 885 batch. n_busy =self . batch_size ; 886Some ( batch) 887} 888} 889 890pub struct MaterialBuilder { 891batch_size : usize , // instances per batch 892n_batches : usize , 893enable_timing : bool , 894} 895 896impl MaterialBuilder { 897pub fn new () ->Self { 898Self { 899batch_size : 1 , 900n_batches : 1 , 901enable_timing : false , 902} 903} 904 905pub fn batch_size ( mut self , batch_size : usize ) ->Self { 906self . batch_size = batch_size; 907self 908} 909 910pub fn n_batches ( mut self , n_batches : usize ) ->Self { 911self . n_batches = n_batches; 912self 913} 914 915pub fn enable_timing ( mut self ) ->Self { 916self . enable_timing =true ; 917self 918} 919 920pub fn build ( 921self , 922gpu : & Gpu , 923mut passes_generator : impl FnMut () ->( Vec < MaterialPass >, Option < wgpu:: Buffer >), 924) ->Material { 925let mut batches =Vec ::< MaterialBatch >:: new (); 926 batches. reserve ( self . n_batches ); 927for _in 0 ..self . n_batches { 928 batches. push ( MaterialBatch :: new ( 929 gpu, 930& mut passes_generator, 931self . batch_size , 932self . enable_timing , 933)); 934} 935Material { 936batches : batches, 937batch_size : self . batch_size , 938} 939} 940} 941 942pub struct PipelineBuilder < ' a > { 943shader : &' a Shader , 944zero_initialize : bool , 945color_target : Option < wgpu:: ColorTargetState >, 946vertex_buffer_layout : Option < wgpu:: VertexBufferLayout < ' a >>, 947} 948 949impl < ' a > PipelineBuilder < ' a > { 950pub fn new ( shader : &' a Shader ) ->Self { 951Self { 952 shader, 953zero_initialize : true , 954color_target : None , 955vertex_buffer_layout : None , 956} 957} 958 959pub fn zero_initialize ( mut self , zero_initialize : bool ) ->Self { 960self . zero_initialize = zero_initialize; 961self 962} 963 964pub fn color_target ( mut self , color_target : wgpu:: ColorTargetState ) ->Self { 965self . color_target =Some ( color_target); 966self 967} 968 969pub fn vertex_buffer_layout ( 970mut self , 971vertex_buffer_layout : wgpu:: VertexBufferLayout < ' a >, 972) ->Self { 973self . vertex_buffer_layout =Some ( vertex_buffer_layout); 974self 975} 976 977// Build a ComputePipeline. Assumes the shader has exactly one compute shader. 978pub fn build_compute ( 979self , 980gpu : & Gpu , 981entry_point : & str , 982) -> anyhow:: Result < wgpu:: ComputePipeline > { 983let pipeline_layout = gpu 984. device 985. create_pipeline_layout ( & wgpu:: PipelineLayoutDescriptor { 986label : None , 987bind_group_layouts : & [ Some ( & self . shader . bind_group_layout )], 988immediate_size : 0 , 989}); 990 991if !self 992. shader 993. naga_module 994. entry_points 995. iter () 996. any ( |ep| ep. name == entry_point && ep. stage == naga:: ir:: ShaderStage :: Compute ) 997{ 998 anyhow:: bail!( 999"Shader entry point '{}' with compute type not found." , 1000 entry_point 1001); 1002} 1003let compilation_options = wgpu:: PipelineCompilationOptions { 1004zero_initialize_workgroup_memory : self . zero_initialize , 1005 ..Default :: default () 1006}; 1007Ok ( gpu 1008. device 1009. create_compute_pipeline ( & wgpu:: ComputePipelineDescriptor { 1010label : None , 1011layout : Some ( & pipeline_layout), 1012module : & self . shader . shader_module , 1013entry_point : Some ( entry_point), 1014compilation_options : compilation_options, 1015cache : None , 1016})) 1017} 1018 1019// Build a RenderPipeline. Assumes the shader has exactly one vertex and one fragment shader. 1020pub fn build_render ( self , gpu : & Gpu ) -> anyhow:: Result < wgpu:: RenderPipeline > { 1021let pipeline_layout = gpu 1022. device 1023. create_pipeline_layout ( & wgpu:: PipelineLayoutDescriptor { 1024label : None , 1025bind_group_layouts : & [ Some ( & self . shader . bind_group_layout )], 1026immediate_size : 0 , 1027}); 1028let vertex_shader =self 1029. shader 1030. get_shader_by_stage ( naga:: ir:: ShaderStage :: Vertex ) ?; 1031let fragment_shader =self 1032. shader 1033. get_shader_by_stage ( naga:: ir:: ShaderStage :: Fragment ) ?; 1034let compilation_options = wgpu:: PipelineCompilationOptions { 1035zero_initialize_workgroup_memory : self . zero_initialize , 1036 ..Default :: default () 1037}; 1038let Some ( color_target) =self . color_target else { 1039 anyhow:: bail!( "color_target is not set" ); 1040}; 1041let Some ( vertex_buffer_layout) =self . vertex_buffer_layout else { 1042 anyhow:: bail!( "vertex_buffer_layout is not set" ); 1043}; 1044Ok ( gpu 1045. device 1046. create_render_pipeline ( & wgpu:: RenderPipelineDescriptor { 1047label : None , 1048layout : Some ( & pipeline_layout), 1049vertex : wgpu:: VertexState { 1050module : & self . shader . shader_module , 1051entry_point : Some ( & vertex_shader. name ), 1052compilation_options : compilation_options. clone (), 1053buffers : & vec! [ Some ( vertex_buffer_layout)], 1054}, 1055fragment : Some ( wgpu:: FragmentState { 1056module : & self . shader . shader_module , 1057entry_point : Some ( & fragment_shader. name ), 1058targets : & vec! [ Some ( color_target)], 1059compilation_options : compilation_options, 1060}), 1061primitive : wgpu:: PrimitiveState :: default (), 1062depth_stencil : None , 1063multisample : wgpu:: MultisampleState :: default (), 1064multiview_mask : None , 1065cache : None , 1066})) 1067} 1068} 1069 1070# [ derive ( Default )] 1071pub struct Transform { 1072pub position : [ f32 ; 3 ], 1073pub scale : [ f32 ; 3 ], 1074// TODO abstract to support euler angles and quaternions 1075pub orientation : [ f32 ; 3 ], 1076} 1077 1078pub struct Object { 1079pub transform : Transform , 1080pub vertex_buffer : wgpu:: Buffer , 1081// Could hoist this into Scene to have more than one mesh per pipeline, but 1082// this is good enough for prototype. 1083pub pipeline : wgpu:: RenderPipeline , 1084pub camera_bind_group : Option < wgpu:: BindGroup >, 1085} 1086 1087pub struct ObjectBuilder < ' a > { 1088transform : Transform , 1089vertex_buffer : Option < wgpu:: Buffer >, 1090pipeline : Option < wgpu:: RenderPipeline >, 1091camera : Option < &' a Camera >, 1092} 1093 1094impl < ' a > ObjectBuilder < ' a > { 1095pub fn new () ->Self { 1096Self { 1097transform : Transform :: default (), 1098vertex_buffer : None , 1099pipeline : None , 1100camera : None , 1101} 1102} 1103 1104pub fn transform ( mut self , transform : Transform ) ->Self { 1105self . transform = transform; 1106self 1107} 1108 1109pub fn vertex_buffer ( mut self , vertex_buffer : wgpu:: Buffer ) ->Self { 1110self . vertex_buffer =Some ( vertex_buffer); 1111self 1112} 1113 1114pub fn pipeline ( mut self , pipeline : wgpu:: RenderPipeline ) ->Self { 1115self . pipeline =Some ( pipeline); 1116self 1117} 1118 1119pub fn camera ( mut self , camera : &' a Camera ) ->Self { 1120self . camera =Some ( camera); 1121self 1122} 1123 1124pub fn build ( self , gpu : & Gpu , shader : & Shader ) -> anyhow:: Result < Object > { 1125let Some ( vertex_buffer) =self . vertex_buffer else { 1126 anyhow:: bail!( "Cannot build Object without vertex_buffer" ); 1127}; 1128let Some ( pipeline) =self . pipeline else { 1129 anyhow:: bail!( "Cannot build Object without pipeline" ); 1130}; 1131let camera_bind_group =if shader. uses_camera () { 1132Some ( self . camera . context ( "Shader requires a camera" ) ?. bind_group ( gpu, shader)) 1133} else { 1134None 1135}; 1136Ok ( Object { 1137transform : self . transform , 1138 vertex_buffer, 1139 pipeline, 1140 camera_bind_group, 1141}) 1142} 1143} 1144 1145# [ derive ( Builder )] 1146# [ builder ( build_fn ( skip ))] 1147pub struct Camera { 1148pub eye : glam:: Vec3 , 1149pub target : glam:: Vec3 , 1150pub up : glam:: Vec3 , 1151pub vertical_fov : f32 , 1152pub near : f32 , 1153pub far : f32 , 1154# [ builder ( setter ( custom ))] 1155pub buffer : wgpu:: Buffer , 1156} 1157 1158impl CameraBuilder { 1159pub fn build ( & self , gpu : & Gpu , aspect : f32 ) -> anyhow:: Result < Camera > { 1160let buffer = gpu. buffer_by_sz ( 64 ). uniform (). copy_dst (). build (); 1161let camera =Camera { 1162eye : self . eye . context ( "eye is not set" ) ?, 1163target : self . target . context ( "target is not set" ) ?, 1164up : self . up . context ( "up is not set" ) ?, 1165vertical_fov : self . vertical_fov . context ( "vertical_fov is not set" ) ?, 1166near : self . near . context ( "near is not set" ) ?, 1167far : self . far . context ( "far is not set" ) ?, 1168 buffer, 1169}; 1170 camera. update ( gpu, aspect); 1171Ok ( camera) 1172} 1173} 1174 1175impl Camera { 1176fn bind_group ( & self , gpu : & Gpu , shader : & Shader ) -> wgpu:: BindGroup { 1177 gpu. device . create_bind_group ( & wgpu:: BindGroupDescriptor { 1178label : Some ( "Object camera bindings" ), 1179layout : & shader. bind_group_layout , 1180entries : & [ wgpu:: BindGroupEntry { 1181binding : 0 , 1182resource : self . buffer . as_entire_binding (), 1183}], 1184}) 1185} 1186 1187pub fn update ( & self , gpu : & Gpu , aspect : f32 ) { 1188let matrix =self . view_projection ( aspect). to_cols_array_2d (); 1189 gpu. queue . write_buffer ( & self . buffer , 0 , bytemuck:: bytes_of ( & matrix)); 1190} 1191 1192fn view_projection ( & self , aspect : f32 ) -> glam:: Mat4 { 1193// right x, up y. right handed -> near z 1194use glam:: camera:: rh::{ proj, view}; 1195let view = view:: look_at_mat4 ( self . eye , self . target , self . up ); 1196// 0-1 depth. 1197let projection = proj:: directx:: perspective ( self . vertical_fov , aspect, self . near , self . far ); 1198 projection* view 1199} 1200} 1201 1202pub struct Scene { 1203pub camera : Camera , 1204pub objects : Vec < Object >, 1205} 1206 1207pub struct SceneBuilder { 1208camera : Option < Camera >, 1209objects : Vec < Object >, 1210} 1211 1212impl SceneBuilder { 1213pub fn new () ->Self { 1214Self { 1215camera : None , 1216objects : Vec :: new (), 1217} 1218} 1219 1220pub fn camera ( mut self , camera : Camera ) ->Self { 1221self . camera =Some ( camera); 1222self 1223} 1224 1225pub fn object ( mut self , object : Object ) ->Self { 1226self . objects . push ( object); 1227self 1228} 1229 1230pub fn build ( self ) -> anyhow:: Result < Scene > { 1231let Some ( camera) =self . camera else { 1232 anyhow:: bail!( "camera is not set" ); 1233}; 1234Ok ( Scene { 1235 camera, 1236objects : self . objects , 1237}) 1238} 1239}