yum/demomo
A graphics engine built on wgpu.
git clone https://git.yummers.dev/yum/demomo
ff99423
master
1use demomo:: gpu::{ self , * }; 2use wasm_bindgen:: prelude:: wasm_bindgen; 3use wasm_bindgen::{ JsCast , UnwrapThrowExt }; 4use winit:: application:: ApplicationHandler ; 5use winit:: event:: WindowEvent ; 6use winit:: event_loop::{ ActiveEventLoop , EventLoop , EventLoopProxy }; 7use winit:: platform:: web::{ EventLoopExtWebSys , WindowAttributesExtWebSys }; 8use winit:: window::{ Window , WindowId }; 9 10// An application that draws to the HTML canas element named `name`. 11pub struct App { 12name : &' static str , 13proxy : EventLoopProxy < AppEvent >, 14gpu : Option < gpu:: Gpu >, 15scene : Option < Scene >, 16surface_cfg : Option < wgpu:: SurfaceConfiguration >, 17} 18 19enum AppEvent { 20GpuReady ( gpu:: Gpu ), 21} 22 23impl App { 24async fn new ( name : &' static str , event_loop : & EventLoop < AppEvent >) -> anyhow:: Result < Self > { 25 env_logger:: init (); 26 console_error_panic_hook:: set_once (); 27let proxy = event_loop. create_proxy (); 28Ok ( Self { 29 name, 30 proxy, 31gpu : None , 32scene : None , 33surface_cfg : None , 34}) 35} 36 37fn resize ( & mut self , width : u32 , height : u32 ) { 38if width ==0 || height ==0 { 39self . surface_cfg =None ; 40return ; 41} 42let Some ( gpu) =& mut self . gpu else { 43return ; 44}; 45let Some ( surface) =& mut gpu. surface else { 46return ; 47}; 48 49let mut cfg =self . surface_cfg . take (). unwrap_or_else ( ||{ 50 surface 51. get_default_config ( & gpu. adapter , width, height) 52. expect ( "Incompatible surface" ) 53}); 54 55 cfg. width = width; 56 cfg. height = height; 57 surface. configure ( & gpu. device , & cfg); 58self . surface_cfg =Some ( cfg); 59 60 wgpu:: web_sys:: console:: log_1 ( & format! ( "Resize: {}x{}" , width, height). into ()); 61} 62 63fn make_scene ( gpu : & Gpu , surface_cfg : & wgpu:: SurfaceConfiguration ) -> anyhow:: Result < Scene > { 64let shader_txt = demomo:: include_wgsl!( "standard.wgsl" ); 65let shader =Shader :: new ( & gpu, & shader_txt) ?; 66 67let vertex_layout = shader. get_vertex_layout () ?; 68let color_target = wgpu:: ColorTargetState { 69format : surface_cfg. format , 70blend : Some ( wgpu:: BlendState :: REPLACE ), 71write_mask : wgpu:: ColorWrites :: all (), 72}; 73let pipeline =PipelineBuilder :: new ( & shader) 74. vertex_buffer_layout ( vertex_layout. vertex_buffer_layout ()) 75. color_target ( color_target) 76. build_render ( gpu) ?; 77 78let positions =[[ -0.5_f32 , -0.5 , 0.5 ], [ 0.5 , -0.5 , 0.5 ], [ 0.0 , 0.5 , 0.5 ]]; 79let normals =[[ 0.0_f32 , 0.0 , 1.0 ]; 3 ]; 80let uvs =[[ 0.0_f32 , 0.0 ], [ 1.0 , 0.0 ], [ 0.5 , 1.0 ]]; 81let n_vertices = positions. len (); 82let data_streams =VertexDataStreamBuilder :: new () 83. stream ( VertexSemantic :: Position , & positions) 84. stream ( VertexSemantic :: Normal , & normals) 85. stream ( VertexSemantic :: Uv0 , & uvs) 86. build (); 87let vertex_buffer = vertex_layout. vertex_buffer ( gpu, data_streams, n_vertices) ?; 88 89let camera =CameraBuilder :: default () 90. eye ( glam:: Vec3 :: new ( 0.0 , 0.0 , 1.0 )) 91. target ( glam:: Vec3 :: ZERO ) 92. up ( glam:: Vec3 :: Y ) 93. vertical_fov ( 60.0_f32 . to_radians ()) 94. near ( 0.1 ) 95. far ( 100.0 ) 96. build ( gpu, surface_cfg. width as f32 / surface_cfg. height as f32 ) ?; 97 98SceneBuilder :: new () 99. object ( 100ObjectBuilder :: new () 101. vertex_buffer ( vertex_buffer) 102. pipeline ( pipeline) 103. camera ( & camera) 104. build ( gpu, & shader) ?, 105) 106. camera ( camera) 107. build () 108} 109 110fn render ( & mut self ) -> anyhow:: Result <()> { 111let Some ( gpu) =& mut self . gpu else { 112return Ok (()); 113}; 114let Some ( surface_cfg) =& mut self . surface_cfg else { 115return Ok (()); 116}; 117let Some ( surface) =& gpu. surface else { 118 anyhow:: bail!( "No surface" ); 119}; 120 121let ( frame, suboptimal) =match surface. get_current_texture () { 122 wgpu:: CurrentSurfaceTexture :: Success ( frame) =>( frame, false ), 123// Suboptimal just means we have to re-configure once done with the texture. 124// https://docs.rs/wgpu/latest/wgpu/enum.CurrentSurfaceTexture.html#variant.Suboptimal 125 wgpu:: CurrentSurfaceTexture :: Suboptimal ( frame) =>( frame, true ), 126 127 wgpu:: CurrentSurfaceTexture :: Timeout | wgpu:: CurrentSurfaceTexture :: Occluded =>{ 128return Ok (()); 129} 130 131 wgpu:: CurrentSurfaceTexture :: Outdated =>{ 132 surface. configure ( & gpu. device , surface_cfg); 133return Ok (()); 134} 135 136 wgpu:: CurrentSurfaceTexture :: Lost =>{ 137let window = gpu. window . as_ref (). unwrap (). clone (); 138let surface = gpu. instance . create_surface ( window) ?; 139 surface. configure ( & gpu. device , surface_cfg); 140 gpu. surface =Some ( surface); 141return Ok (()); 142} 143 144 wgpu:: CurrentSurfaceTexture :: Validation =>{ 145 anyhow:: bail!( "Surface acquisition failed to validate" ); 146} 147}; 148 149if self . scene . is_none () { 150self . scene =Some ( Self :: make_scene ( & gpu, surface_cfg) ?); 151} 152let scene =self . scene . as_ref (). unwrap (); 153 scene 154. camera 155. update ( gpu, surface_cfg. width as f32 / surface_cfg. height as f32 ); 156 157let view = frame 158. texture 159. create_view ( & wgpu:: TextureViewDescriptor :: default ()); 160let mut encoder = gpu 161. device 162. create_command_encoder ( & wgpu:: CommandEncoderDescriptor :: default ()); 163{ 164let mut pass = encoder. begin_render_pass ( & wgpu:: RenderPassDescriptor { 165label : None , 166color_attachments : & [ Some ( wgpu:: RenderPassColorAttachment { 167view : & view, 168depth_slice : None , 169resolve_target : None , 170ops : wgpu:: Operations { 171load : wgpu:: LoadOp :: Clear ( wgpu:: Color { 172r : 0.1 , 173g : 0.2 , 174b : 0.4 , 175a : 1.0 , 176}), 177store : wgpu:: StoreOp :: Store , 178}, 179})], 180 ..Default :: default () 181}); 182 pass. set_pipeline ( & scene. objects [ 0 ]. pipeline ); 183if let Some ( camera_bind_group) =& scene. objects [ 0 ]. camera_bind_group { 184 pass. set_bind_group ( 0 , camera_bind_group, & []); 185} 186 pass. set_vertex_buffer ( 0 , scene. objects [ 0 ]. vertex_buffer . slice ( ..)); 187 pass. draw ( 0 ..3 , 0 ..1 ); 188} 189 gpu. queue . submit ([ encoder. finish ()]); 190 gpu. queue . present ( frame); 191 192if suboptimal{ 193 surface. configure ( & gpu. device , surface_cfg); 194} 195 196Ok (()) 197} 198} 199 200impl ApplicationHandler < AppEvent > for App { 201fn resumed ( & mut self , event_loop : & ActiveEventLoop ) { 202// Get a canvas to draw on 203let get_window_attr = ||{ 204let window = wgpu:: web_sys:: window (). unwrap_throw (); 205let document = window. document (). unwrap_throw (); 206let canvas = document. get_element_by_id ( & self . name ). unwrap_throw (); 207let html_canvas_element = canvas. unchecked_into (); 208Window :: default_attributes () 209. with_canvas ( Some ( html_canvas_element)) 210. with_prevent_default ( false ) 211}; 212let window_attr =get_window_attr (); 213// Create a window. 214let window = event_loop. create_window ( window_attr). unwrap_throw (); 215 window. request_redraw (); 216self . resize ( window. inner_size (). width , window. inner_size (). height ); 217 218// Have to create Gpu handle asynchronously. On completion, this will 219// invoke user_event. 220let proxy =self . proxy . clone (); 221 wasm_bindgen_futures:: spawn_local ( async move { 222let gpu =GpuBuilder :: new () 223. window ( window) 224. build () 225. await 226. unwrap_throw (); 227 proxy 228. send_event ( AppEvent :: GpuReady ( gpu)) 229. unwrap_or_else ( |err|panic! ( "{}" , err)); 230}); 231} 232 233fn window_event ( & mut self , event_loop : & ActiveEventLoop , _id : WindowId , event : WindowEvent ) { 234match event{ 235WindowEvent :: CloseRequested =>{ 236 event_loop. exit (); 237} 238WindowEvent :: Resized ( size) =>{ 239self . resize ( size. width , size. height ); 240self . render (). expect ( "Render failed" ); 241} 242WindowEvent :: RedrawRequested =>{ 243self . render (). expect ( "Render failed" ); 244if let Some ( window) =self . gpu . as_ref (). and_then ( |gpu| gpu. window . as_ref ()) { 245 window. request_redraw (); 246} 247} 248 _ =>{} 249} 250} 251 252fn user_event ( & mut self , _: & ActiveEventLoop , event : AppEvent ) { 253match event{ 254AppEvent :: GpuReady ( gpu) =>{ 255let window = gpu. window . as_ref (). unwrap (). clone (); 256let size = window. inner_size (); 257self . gpu =Some ( gpu); 258self . resize ( size. width , size. height ); 259 window. request_redraw (); 260} 261} 262} 263} 264 265# [ wasm_bindgen ( start )] 266pub fn start () { 267let event_loop =EventLoop ::< AppEvent >:: with_user_event () 268. build () 269. unwrap_throw (); 270 wasm_bindgen_futures:: spawn_local ( async move { 271let app =App :: new ( "meow" , & event_loop). await . unwrap_throw (); 272 event_loop. spawn_app ( app); 273}); 274}