diff --git a/Cargo.lock b/Cargo.lock index c665035..634dc12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1701,6 +1701,7 @@ dependencies = [ name = "strafesnet_roblox_bot_player_wasm_module" version = "0.1.0" dependencies = [ + "pollster", "strafesnet_common", "strafesnet_graphics", "strafesnet_roblox_bot_file", diff --git a/wasm-module/Cargo.toml b/wasm-module/Cargo.toml index aa1307f..c951524 100644 --- a/wasm-module/Cargo.toml +++ b/wasm-module/Cargo.toml @@ -12,6 +12,7 @@ strafesnet_common.workspace = true strafesnet_graphics.workspace = true strafesnet_roblox_bot_file.workspace = true strafesnet_snf.workspace = true +pollster = "0.4.0" wasm-bindgen = "0.2.108" wgpu = "28.0.0" web-sys = { version = "0.3.85", features = ["HtmlCanvasElement"] } diff --git a/wasm-module/src/lib.rs b/wasm-module/src/lib.rs index 15b58e9..c1d8a68 100644 --- a/wasm-module/src/lib.rs +++ b/wasm-module/src/lib.rs @@ -3,6 +3,8 @@ use wasm_bindgen::JsValue; use strafesnet_roblox_bot_player::{bot,graphics,head}; use strafesnet_common::session::Time as SessionTime; +mod setup; + #[wasm_bindgen] pub struct Graphics<'a>{ graphics:graphics::Graphics<'a>, diff --git a/wasm-module/src/setup.rs b/wasm-module/src/setup.rs new file mode 100644 index 0000000..d8267dd --- /dev/null +++ b/wasm-module/src/setup.rs @@ -0,0 +1,199 @@ +fn optional_features()->wgpu::Features{ + wgpu::Features::TEXTURE_COMPRESSION_ASTC + |wgpu::Features::TEXTURE_COMPRESSION_ETC2 +} +fn required_features()->wgpu::Features{ + wgpu::Features::TEXTURE_COMPRESSION_BC +} +fn required_downlevel_capabilities()->wgpu::DownlevelCapabilities{ + wgpu::DownlevelCapabilities{ + flags:wgpu::DownlevelFlags::empty(), + shader_model:wgpu::ShaderModel::Sm5, + ..wgpu::DownlevelCapabilities::default() + } +} + +struct SetupContextPartial1{ + backends:wgpu::Backends, + instance:wgpu::Instance, +} +fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Result{ + let mut attr=winit::window::WindowAttributes::default(); + attr=attr.with_title(title); + event_loop.create_window(attr) +} +fn create_instance()->SetupContextPartial1{ + let backends=wgpu::Backends::from_env().unwrap_or_default(); + SetupContextPartial1{ + backends, + instance:Default::default(), + } +} +impl SetupContextPartial1{ + fn create_surface<'a>(self,window:&'a winit::window::Window)->Result,wgpu::CreateSurfaceError>{ + Ok(SetupContextPartial2{ + backends:self.backends, + surface:self.instance.create_surface(window)?, + instance:self.instance, + }) + } +} +struct SetupContextPartial2<'a>{ + backends:wgpu::Backends, + instance:wgpu::Instance, + surface:wgpu::Surface<'a>, +} +impl<'a> SetupContextPartial2<'a>{ + fn pick_adapter(self)->SetupContextPartial3<'a>{ + //TODO: prefer adapter that implements optional features + //let optional_features=optional_features(); + let required_features=required_features(); + + //no helper function smh gotta write it myself + let adapters=pollster::block_on(self.instance.enumerate_adapters(self.backends)); + + let chosen_adapter=adapters.into_iter() + // reverse because we want to select adapters that appear first in ties, + // and max_by_key selects the last equal element in the iterator. + .rev() + .filter(|adapter| + adapter.is_surface_supported(&self.surface) + &&adapter.features().contains(required_features) + ) + .max_by_key(|adapter|match adapter.get_info().device_type{ + wgpu::DeviceType::IntegratedGpu=>3, + wgpu::DeviceType::DiscreteGpu=>4, + wgpu::DeviceType::VirtualGpu=>2, + wgpu::DeviceType::Other|wgpu::DeviceType::Cpu=>1, + }); + + let adapter=chosen_adapter.expect("No suitable GPU adapters found on the system!"); + + let adapter_info=adapter.get_info(); + println!("Using {} ({:?})", adapter_info.name, adapter_info.backend); + + let required_downlevel_capabilities=required_downlevel_capabilities(); + let downlevel_capabilities=adapter.get_downlevel_capabilities(); + assert!( + downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model, + "Adapter does not support the minimum shader model required to run this example: {:?}", + required_downlevel_capabilities.shader_model + ); + assert!( + downlevel_capabilities + .flags + .contains(required_downlevel_capabilities.flags), + "Adapter does not support the downlevel capabilities required to run this example: {:?}", + required_downlevel_capabilities.flags - downlevel_capabilities.flags + ); + SetupContextPartial3{ + surface:self.surface, + adapter, + } + } +} +struct SetupContextPartial3<'a>{ + surface:wgpu::Surface<'a>, + adapter:wgpu::Adapter, +} +impl<'a> SetupContextPartial3<'a>{ + fn request_device(self)->SetupContextPartial4<'a>{ + let optional_features=optional_features(); + let required_features=required_features(); + + // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface. + let needed_limits=strafesnet_graphics::graphics::required_limits().using_resolution(self.adapter.limits()); + + let (device, queue)=pollster::block_on(self.adapter + .request_device( + &wgpu::DeviceDescriptor{ + label:None, + required_features:(optional_features&self.adapter.features())|required_features, + required_limits:needed_limits, + memory_hints:wgpu::MemoryHints::Performance, + trace:wgpu::Trace::Off, + experimental_features:wgpu::ExperimentalFeatures::disabled(), + }, + )) + .expect("Unable to find a suitable GPU adapter!"); + + SetupContextPartial4{ + surface:self.surface, + adapter:self.adapter, + device, + queue, + } + } +} +struct SetupContextPartial4<'a>{ + surface:wgpu::Surface<'a>, + adapter:wgpu::Adapter, + device:wgpu::Device, + queue:wgpu::Queue, +} +impl<'a> SetupContextPartial4<'a>{ + fn configure_surface(self,size:&'a winit::dpi::PhysicalSize)->SetupContext<'a>{ + let mut config=self.surface + .get_default_config(&self.adapter, size.width, size.height) + .expect("Surface isn't supported by the adapter."); + let surface_view_format=config.format.add_srgb_suffix(); + config.view_formats.push(surface_view_format); + config.present_mode=wgpu::PresentMode::AutoNoVsync; + self.surface.configure(&self.device, &config); + + SetupContext{ + surface:self.surface, + device:self.device, + queue:self.queue, + config, + } + } +} +pub struct SetupContext<'a>{ + pub surface:wgpu::Surface<'a>, + pub device:wgpu::Device, + pub queue:wgpu::Queue, + pub config:wgpu::SurfaceConfiguration, +} + +pub fn setup_and_start(title:&str){ + let event_loop=winit::event_loop::EventLoop::new().unwrap(); + + println!("Initializing the surface..."); + + let partial_1=create_instance(); + + let window=create_window(title,&event_loop).unwrap(); + + let partial_2=partial_1.create_surface(&window).unwrap(); + + let partial_3=partial_2.pick_adapter(); + + let partial_4=partial_3.request_device(); + + let size=window.inner_size(); + + let setup_context=partial_4.configure_surface(&size); + + //dedicated thread to ping request redraw back and resize the window doesn't seem logical + + //the thread that spawns the physics thread + let mut window_thread=crate::window::WindowContext::new( + &window, + setup_context, + ); + + for arg in std::env::args().skip(1){ + window_thread.send(strafesnet_common::instruction::TimedInstruction{ + time:strafesnet_common::integer::Time::ZERO, + instruction:crate::window::Instruction::WindowEvent(winit::event::WindowEvent::DroppedFile(arg.into())), + }); + }; + + println!("Entering event loop..."); + let mut app=crate::app::App::new( + std::time::Instant::now(), + window_thread + ); + event_loop.run_app(&mut app).unwrap(); +}