1#[cfg(feature = "with-gui")]
4use crate::gui::Gui;
5#[cfg(feature = "with-gui")]
6use crate::plugin_manager::GuiSystem;
7#[cfg(feature = "selector")]
8use crate::selector::SelectorPlugin;
9use crate::{
10 builders,
11 camera::Camera,
12 components::Projection,
13 config::Config,
14 forward_renderer::{render_passes::blit_pass::BlitPass, renderer::Renderer},
15 logger::gloss_setup_logger_from_config,
16 plugin_manager::{
17 plugins::{Plugin, Plugins},
18 systems::{LogicSystem, SystemMetadata},
19 InternalPlugins,
20 },
21 scene::Scene,
22 set_panic_hook,
23};
24
25#[cfg(target_arch = "wasm32")]
26use std::future::Future;
27#[cfg(target_arch = "wasm32")]
28use std::pin::Pin;
29#[cfg(target_arch = "wasm32")]
30use std::sync::mpsc;
31
32use easy_wgpu::gpu::Gpu;
33use easy_wgpu::texture::Texture;
34#[cfg(feature = "with-gui")]
35use egui_winit::EventResponse;
36use gloss_utils::abi_stable_aliases::std_types::{RString, Tuple2};
37use log::{debug, warn};
38#[cfg(target_arch = "wasm32")]
39use wgpu::util::is_browser_webgpu_supported;
40use wgpu::BackendOptions;
41use winit::{
42 dpi::PhysicalSize,
43 event::TouchPhase,
44 event_loop::{ActiveEventLoop, EventLoopProxy},
45 keyboard::{KeyCode, PhysicalKey},
46 window::WindowId,
47};
48
49use core::time::Duration;
50use gloss_utils::io::FileType;
51use log::{error, info};
52#[cfg(not(target_arch = "wasm32"))]
53use pollster::FutureExt;
54use std::{error::Error, sync::Arc};
55
56#[cfg(target_arch = "wasm32")]
57use wasm_bindgen::prelude::*;
58use wasm_timer::Instant;
59use winit::application::ApplicationHandler;
60#[cfg(target_arch = "wasm32")]
61use winit::platform::web::EventLoopExtWebSys;
62
63use winit::{
64 event::{ElementState, Event, WindowEvent},
65 event_loop::EventLoop,
66 window::Window,
67};
68
69#[cfg(target_arch = "wasm32")]
70use futures::future::poll_fn;
71#[cfg(target_arch = "wasm32")]
72use std::cell::RefCell;
73#[cfg(target_arch = "wasm32")]
74use std::rc::Rc;
75#[cfg(target_arch = "wasm32")]
76use std::task::{Poll, Waker};
77
78#[cfg(target_arch = "wasm32")]
81type SceneInitFn = Box<dyn FnOnce(Scene) -> Pin<Box<dyn Future<Output = Scene> + 'static>> + 'static>;
82
83#[repr(C)]
86pub struct GpuResources {
87 window: Arc<Window>,
88 surface: wgpu::Surface<'static>,
89 surface_config: wgpu::SurfaceConfiguration,
90 pub renderer: Renderer,
91
92 #[cfg(feature = "with-gui")]
93 pub gui: Option<Gui>,
94 _blit_pass: BlitPass, pub redraw_requested: bool, pub gpu: Gpu,
102}
103#[allow(clippy::missing_panics_doc)]
104#[allow(clippy::too_many_lines)]
105#[allow(unused)]
106impl GpuResources {
107 pub async fn new(
108 event_loop: &ActiveEventLoop,
109 event_loop_proxy: &EventLoopProxy<CustomEvent>,
110 canvas_id_parsed: Option<&String>,
111 config: &Config,
112 ) -> Self {
113 let instance = wgpu::util::new_instance_with_webgpu_detection(&wgpu::InstanceDescriptor {
114 backends: supported_backends(),
115 flags: wgpu::InstanceFlags::default(),
116 backend_options: BackendOptions::default(),
117 })
118 .await;
119
120 let window = Viewer::create_window(event_loop, event_loop_proxy, canvas_id_parsed).expect("failed to create initial window");
121
122 let window = Arc::new(window);
123
124 let surface = unsafe { instance.create_surface(window.clone()) }.unwrap();
125
126 cfg_if::cfg_if! {
128 if #[cfg(not(target_arch = "wasm32"))]{
129 let adapters = enumerate_adapters(&instance);
130 info!("Number of possible adapters: {:?}", adapters.len());
131 for (i, adapter) in adapters.iter().enumerate() {
132 info!("Adapter option {:?}: {:?}", i, adapter.get_info());
133 }
134 }
135 }
136 let adapter = get_adapter(&instance, Some(&surface)).await;
137 info!("Selected adapter: {:?}", adapter.get_info());
138
139 let mut can_use_cubecl = adapter.get_info().backend != wgpu::Backend::Gl;
141 #[cfg(target_arch = "wasm32")]
142 {
143 let webgpu_capable = is_browser_webgpu_supported().await;
144 info!("Is browser webgpu supported: {webgpu_capable}");
145 can_use_cubecl &= webgpu_capable;
146 }
147
148 let mut desired_features = wgpu::Features::empty();
151 cfg_if::cfg_if! {
152 if #[cfg(not(target_arch = "wasm32"))]{
153 desired_features = desired_features.union(wgpu::Features::TIMESTAMP_QUERY.union(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES));
154 desired_features = desired_features.union(wgpu::Features::POLYGON_MODE_POINT);
155 desired_features = desired_features.union(wgpu::Features::POLYGON_MODE_LINE);
156 }
157 }
158 let mut required_features = adapter.features().intersection(desired_features); required_features = required_features.union(wgpu::Features::DEPTH32FLOAT_STENCIL8);
170 if can_use_cubecl {
171 required_features = required_features.union(wgpu::Features::TIMESTAMP_QUERY);
172 }
173 if cfg!(not(target_arch = "wasm32")) {
175 required_features = required_features.union(wgpu::Features::SUBGROUP);
176 required_features = required_features.union(wgpu::Features::SHADER_INT64);
177 }
178
179 let max_limits = adapter.limits();
182 #[allow(unused_mut)]
183 let mut limits_to_request = wgpu::Limits::default();
184 if cfg!(target_arch = "wasm32") {
185 limits_to_request = wgpu::Limits::downlevel_webgl2_defaults();
186 }
187 limits_to_request.max_texture_dimension_1d = max_limits.max_texture_dimension_1d;
188 limits_to_request.max_texture_dimension_2d = max_limits.max_texture_dimension_2d;
189 limits_to_request.max_buffer_size = max_limits.max_buffer_size;
190
191 let mut memory_hints = wgpu::MemoryHints::Performance;
192 if cfg!(target_arch = "wasm32") {
193 memory_hints = wgpu::MemoryHints::MemoryUsage;
196 }
197
198 let (device, queue) = adapter
200 .request_device(&wgpu::DeviceDescriptor {
201 label: None,
202 required_features,
203 required_limits: limits_to_request,
204 memory_hints,
205 trace: wgpu::Trace::Off,
206 })
207 .await
208 .expect("A device and queue could not be created. Maybe there's a driver issue on your machine?");
209 let gpu = Gpu::new(adapter, instance, device, queue);
210
211 let surface_caps = surface.get_capabilities(gpu.adapter());
213 let surface_format = surface_caps
216 .formats
217 .iter()
218 .copied()
219 .find(|f| !f.is_srgb())
220 .unwrap_or(surface_caps.formats[0]);
221
222 let mut size = PhysicalSize::new(1200, 1200);
225 #[cfg(target_arch = "wasm32")]
226 if let Some(canvas_size) = Viewer::get_html_elem_size(&canvas_id_parsed.as_ref().unwrap()) {
227 size = canvas_size.to_physical(window.scale_factor());
228 }
229
230 println!("scale factor: {:?}", window.scale_factor());
231 let surface_config = wgpu::SurfaceConfiguration {
232 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
233 format: surface_format,
234 width: size.width,
235 height: size.height,
236 present_mode: wgpu::PresentMode::AutoNoVsync, alpha_mode: surface_caps.alpha_modes[0],
239 view_formats: vec![],
240 desired_maximum_frame_latency: 2,
241 };
242 surface.configure(gpu.device(), &surface_config);
243
244 #[cfg(feature = "with-gui")]
245 let gui = if config.core.enable_gui {
246 let mut gui = Gui::new(&window, &gpu, surface_format);
247 gui.hidden = config.core.gui_start_hidden;
248 Some(gui)
249 } else {
250 None
251 };
252
253 let renderer = Renderer::new(&gpu, &config.render, Some(surface_format));
254 let blit_pass = BlitPass::new(&gpu, &surface_format);
255
256 if can_use_cubecl {
258 wgpu_burn_global_device::init_global_device(gpu.instance(), gpu.adapter(), gpu.device(), gpu.queue());
259 }
260
261 let mut gpu_res = Self {
262 window,
263 surface,
264 surface_config,
265 gpu,
266 renderer,
267 #[cfg(feature = "with-gui")]
268 gui,
269 _blit_pass: blit_pass,
270 redraw_requested: false,
271 };
272
273 Self::request_redraw(&mut gpu_res);
274
275 gpu_res
276 }
277 pub fn request_redraw(&mut self) {
278 if self.redraw_requested {
279 debug!("Redraw was already requested, ignoring.");
280 } else {
281 self.window.request_redraw();
282 self.redraw_requested = true;
283 }
284 }
285}
286
287#[derive(Debug)]
289#[repr(C)]
290pub struct Runner {
291 event_loop: Option<EventLoop<CustomEvent>>, event_loop_proxy: EventLoopProxy<CustomEvent>,
293 pub autostart: bool, pub is_running: bool,
295 pub do_render: bool,
296 pub first_time: bool,
297 pub did_warmup: bool,
298 frame_is_started: bool, time_init: Instant, time_last_frame: Instant, dt: Duration, }
308#[allow(unused)]
309impl Runner {
310 #[allow(clippy::missing_panics_doc)]
311 #[allow(clippy::new_without_default)]
312 pub fn new(canvas_id: &Option<String>) -> Self {
313 let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
314 let event_loop_proxy: EventLoopProxy<CustomEvent> = event_loop.create_proxy();
315
316 #[cfg(target_arch = "wasm32")]
317 if let Some(ref canvas_id) = canvas_id {
318 Viewer::add_listener_to_canvas_resize(&event_loop, &canvas_id);
319 Viewer::add_listener_to_context(&event_loop, &canvas_id);
320 }
321
322 let time_init = Instant::now();
323 let time_last_frame = Instant::now();
324 Self {
325 event_loop: Some(event_loop),
326 event_loop_proxy,
327 autostart: true,
328 is_running: false,
329 do_render: true,
330 first_time: true,
331 did_warmup: false,
332 frame_is_started: false,
333 time_init,
334 time_last_frame,
335 dt: Duration::ZERO,
336 }
337 }
338 pub fn time_since_init(&self) -> Duration {
339 if self.first_time {
340 Duration::ZERO
341 } else {
342 self.time_init.elapsed()
343 }
344 }
345 pub fn update_dt(&mut self) {
346 if self.first_time {
347 self.dt = Duration::ZERO;
348 } else {
349 self.dt = self.time_last_frame.elapsed();
350 }
351 }
352 pub fn override_dt(&mut self, new_dt: f32) {
353 self.dt = Duration::from_secs_f32(new_dt);
354 }
355 pub fn dt(&self) -> Duration {
356 self.dt
357 }
358}
359
360#[repr(C)]
367pub struct Viewer {
368 pub gpu_res: Option<GpuResources>,
370
371 scene: Option<Scene>,
373
374 window_size: winit::dpi::PhysicalSize<u32>, canvas_id_parsed: Option<String>,
378
379 pub config: Config,
380 pub runner: Runner,
381 pub plugins: Plugins,
382
383 #[allow(private_interfaces)]
384 pub internal_plugins: InternalPlugins,
385
386 #[cfg(target_arch = "wasm32")]
388 gpu_res_receiver: Option<std::sync::mpsc::Receiver<GpuResources>>,
389 #[cfg(target_arch = "wasm32")]
390 scene_receiver: Option<std::sync::mpsc::Receiver<Scene>>,
391 #[cfg(target_arch = "wasm32")]
392 pub scene_init_func: Option<SceneInitFn>,
394
395 #[cfg(target_arch = "wasm32")]
396 scene_waker: Rc<RefCell<Option<Waker>>>,
397}
398
399impl Viewer {
400 pub fn new(config_path: Option<&str>) -> Self {
401 let config = Config::new(config_path);
402 Self::new_with_config(&config)
403 }
404
405 #[allow(clippy::too_many_lines)]
406 #[allow(clippy::missing_panics_doc)]
407 pub fn new_with_config(config: &Config) -> Self {
408 set_panic_hook();
409
410 if config.core.auto_create_logger {
411 gloss_setup_logger_from_config(config);
412 }
413
414 re_memory::accounting_allocator::set_tracking_callstacks(config.core.enable_memory_profiling_callstacks);
416
417 let canvas_id_parsed = config.core.canvas_id.as_ref().map(|canvas_id| String::from("#") + canvas_id);
418
419 let runner = Runner::new(&canvas_id_parsed);
421
422 let window_size = winit::dpi::PhysicalSize::new(100, 100);
423
424 #[allow(unused_mut)]
425 let mut internal_plugins = InternalPlugins::new();
426
427 #[cfg(feature = "selector")]
428 internal_plugins.insert_plugin(&SelectorPlugin::new(true));
429
430 #[allow(unused_mut)]
431 let mut viewer = Self {
432 gpu_res: None,
433 runner,
434 scene: None,
435 plugins: Plugins::new(),
436 internal_plugins,
437 canvas_id_parsed: canvas_id_parsed.clone(),
438 config: config.clone(),
439 window_size,
440 #[cfg(target_arch = "wasm32")]
441 gpu_res_receiver: None,
442 #[cfg(target_arch = "wasm32")]
443 scene_receiver: None,
444 #[cfg(target_arch = "wasm32")]
445 scene_init_func: None,
446 #[cfg(target_arch = "wasm32")]
447 scene_waker: Rc::new(RefCell::new(None)),
448 };
449
450 #[cfg(not(target_arch = "wasm32"))]
452 {
453 viewer.start_frame();
454 viewer.update();
455 viewer.runner.do_render = true;
456 }
457
458 viewer
472 }
473
474 pub fn scene_mut(&mut self) -> &mut Scene {
475 assert!(self.scene.is_some(), "The scene has not been created yet. This typically happens on wasm environments because you are accessing the scene right after creating the viewer. You need to provide a deferred function for initializing the scene to the viewer.run() function rather than trying to fill the scene right after the creation of the viewer. Check the web examples of Gloss");
476 self.scene.as_mut().unwrap()
477 }
478 pub fn scene(&self) -> &Scene {
479 assert!(self.scene.is_some(), "The scene has not been created yet. This typically happens on wasm environments because you are accessing the scene right after creating the viewer. You need to provide a deferred function for initializing the scene to the viewer.run() function rather than trying to fill the scene right after the creation of the viewer. Check the web examples of Gloss");
480 self.scene.as_ref().unwrap()
481 }
482
483 pub fn camera(&self) -> Camera {
484 assert!(self.scene().get_current_cam().is_some(), "The camera has not been created yet. This typically happens on wasm environments because you are accessing the camera right after creating the viewer. You need to provide a deferred function for initializing the camera to the viewer.run() function rather than trying to access the camera right after the creation of the viewer. Check the web examples of Gloss");
485 let scene = self.scene();
486 scene.get_current_cam().unwrap()
487 }
488
489 #[cfg(target_arch = "wasm32")]
490 pub async fn is_scene_initialized(&self) {
491 poll_fn(|cx| {
492 if self.scene.is_some() {
493 info!("scene is initialized");
494 Poll::Ready(())
495 } else {
496 *self.scene_waker.borrow_mut() = Some(cx.waker().clone());
497 info!("scene is pending");
498 Poll::Pending
499 }
500 })
501 .await;
502 }
503
504 #[cfg(target_arch = "wasm32")]
507 fn add_listener_to_canvas_resize(event_loop: &EventLoop<CustomEvent>, canvas_id: &str) {
508 let event_loop_proxy = event_loop.create_proxy();
514 let canvas_id = String::from(canvas_id); let resize = move || {
518 if let Some(size) = Viewer::get_html_elem_size(&canvas_id) {
519 let event_resize = CustomEvent::Resize(size.width, size.height);
520 event_loop_proxy.send_event(event_resize).ok();
521 }
522 };
523
524 let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
528 resize();
529 }) as Box<dyn FnMut(_)>);
530 let window = web_sys::window().unwrap();
531
532 window
533 .add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref())
534 .unwrap();
535 closure.forget();
536 }
537
538 #[cfg(target_arch = "wasm32")]
539 fn add_listener_to_context(event_loop: &EventLoop<CustomEvent>, canvas_id: &str) {
540 let canvas_id = String::from(canvas_id); let win = web_sys::window().unwrap();
543 let doc = win.document().unwrap();
544 let element = doc.query_selector(&canvas_id).ok().unwrap().unwrap();
545 let canvas = element.dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
546
547 let event_loop_proxy = event_loop.create_proxy();
549 let context_lost = move || {
550 let event = CustomEvent::ContextLost;
551 info!("SENDING USER EVENT: context loss");
552 event_loop_proxy.send_event(event).ok();
553 };
554
555 let event_loop_proxy = event_loop.create_proxy();
556 let context_restored = move || {
557 let event = CustomEvent::ContextRestored;
558 info!("SENDING USER EVENT: context restored");
559 event_loop_proxy.send_event(event).ok();
560 };
561
562 let closure_lost = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
563 context_lost();
564 }) as Box<dyn FnMut(_)>);
565 let closure_restored = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
566 context_restored();
567 }) as Box<dyn FnMut(_)>);
568
569 canvas
572 .add_event_listener_with_callback("webglcontextlost", closure_lost.as_ref().unchecked_ref())
573 .unwrap();
574 canvas
575 .add_event_listener_with_callback("webglcontextrestored", closure_restored.as_ref().unchecked_ref())
576 .unwrap();
577 closure_lost.forget();
578 closure_restored.forget();
579 }
580
581 #[cfg(target_arch = "wasm32")]
584 fn get_html_elem_size(selector: &str) -> Option<winit::dpi::LogicalSize<f32>> {
585 let win = web_sys::window().unwrap();
587 let doc = win.document().unwrap();
588 let element = doc.query_selector(selector).ok()??;
589 let parent_element = element.parent_element()?;
590 let rect = parent_element.get_bounding_client_rect();
591 return Some(winit::dpi::LogicalSize::new(rect.width() as f32, rect.height() as f32));
592 }
593
594 #[cfg(target_arch = "wasm32")]
595 pub fn resize_to_canvas(&self) {
596 if let Some(size) = Self::get_html_elem_size(&self.canvas_id_parsed.as_ref().unwrap()) {
597 warn!("size is {:?}", size);
599 let event_resize = CustomEvent::Resize(size.width, size.height);
600 self.runner.event_loop_proxy.send_event(event_resize).ok();
601 }
602 }
603
604 #[allow(clippy::cast_precision_loss)]
605 fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
606 debug!("resizing new_size is {new_size:?}");
607 let max_2d_size = self.gpu_res.as_ref().unwrap().gpu.limits().max_texture_dimension_2d;
608 if new_size.width > 16 && new_size.height > 16 && new_size.width < max_2d_size && new_size.height < max_2d_size {
609 let gpu_res = self.gpu_res.as_mut().unwrap();
610 self.window_size = new_size;
612 gpu_res.surface_config.width = new_size.width;
613 gpu_res.surface_config.height = new_size.height;
614 gpu_res.surface.configure(gpu_res.gpu.device(), &gpu_res.surface_config);
615 gpu_res.request_redraw();
616
617 if self.scene.is_none() {
620 return;
621 }
622 let scene = self.scene.as_mut().unwrap();
623 let mut camera = scene.get_current_cam().unwrap();
624 if scene.world.has::<Projection>(camera.entity).unwrap() {
625 camera.set_aspect_ratio(new_size.width as f32 / new_size.height as f32, scene);
626 }
627
628 #[cfg(feature = "with-gui")]
630 if let Some(ref mut gui) = gpu_res.gui {
631 gui.resize(new_size.width, new_size.height);
632 }
633 } else {
634 error!("trying to resize to unsuported size of {new_size:?}");
635 }
636 }
638
639 pub fn request_redraw(&mut self) {
642 if let Some(gpu_res) = self.gpu_res.as_mut() {
643 gpu_res.request_redraw();
644 } else {
645 error!("No gpu_res created yet");
646 }
647 }
648
649 #[cfg(not(target_arch = "wasm32"))]
654 pub fn update(&mut self) {
655 self.event_loop_one_iter();
656 let _ = self.render();
657 }
658
659 #[cfg(not(target_arch = "wasm32"))]
660 pub fn update_offscreen_texture(&mut self) {
661 self.event_loop_one_iter();
662 let _ = self.render_to_texture();
663 }
664
665 fn process_custom_resize_events(&mut self, event: &Event<CustomEvent>) -> bool {
668 match event {
669 Event::UserEvent(CustomEvent::Resize(new_width, new_height)) => {
670 debug!("rs: handling resize canvas: {event:?}");
671
672 if self.gpu_res.is_none() {
674 debug!("GPU resources not ready yet, ignoring resize event");
675 return true; }
677
678 let logical_size = winit::dpi::LogicalSize {
679 width: *new_width,
680 height: *new_height,
681 };
682 let _ = self.gpu_res.as_ref().unwrap().window.request_inner_size(logical_size);
687
688 self.resize(self.gpu_res.as_ref().unwrap().window.inner_size()); true }
697 _ => false, }
699 }
700
701 fn process_custom_context_event(&mut self, event: &Event<CustomEvent>, event_loop: &ActiveEventLoop) -> bool {
702 match event {
703 Event::UserEvent(event) => {
704 match event {
705 CustomEvent::ContextLost => {
706 info!("rs: handling context lost");
707 self.suspend();
708 true }
711 CustomEvent::ContextRestored => {
712 info!("rs: handling context restored");
713 self.resume(event_loop);
714 true }
717 _ => false, }
719 }
720 _ => false, }
722 }
723
724 #[allow(clippy::collapsible_match)]
725 fn process_custom_other_event(&mut self, event: &Event<CustomEvent>, event_loop: &ActiveEventLoop) -> bool {
726 match event {
727 Event::UserEvent(event) => {
728 match event {
729 #[cfg(target_arch = "wasm32")]
730 CustomEvent::GpuResourcesReady => {
731 info!("rs: handling GPU resources ready");
732
733 if let Some(receiver) = self.gpu_res_receiver.take() {
735 if let Ok(gpu_res) = receiver.try_recv() {
736 self.gpu_res = Some(gpu_res);
737 }
738 }
739
740 if self.scene.is_none() {
742 let event_loop_proxy = self.runner.event_loop_proxy.clone();
744 let scene_init_func = self.scene_init_func.take();
745
746 let (sender, receiver) = mpsc::channel::<Scene>();
748 wasm_bindgen_futures::spawn_local(async move {
749 let scene = Self::create_scene();
750
751 let scene = if let Some(init_func) = scene_init_func {
753 init_func(scene).await
754 } else {
755 scene
756 };
757
758 let _ = sender.send(scene);
760
761 let _ = event_loop_proxy.send_event(CustomEvent::SceneReady);
763 });
764
765 self.scene_receiver = Some(receiver);
767 }
768
769 true
770 }
771 #[cfg(target_arch = "wasm32")]
772 CustomEvent::SceneReady => {
773 info!("rs: handling scene ready");
774
775 if let Some(receiver) = self.scene_receiver.take() {
777 if let Ok(scene) = receiver.try_recv() {
778 self.scene = Some(scene);
779 self.finalize_scene();
780
781 if let Some(waker) = self.scene_waker.borrow_mut().take() {
783 waker.wake();
784 }
785 }
786 }
787 self.resize_to_canvas(); self.gpu_res.as_mut().unwrap().request_redraw();
789
790 true
791 }
792 CustomEvent::ResumeLoop => {
793 info!("rs: handling custom resume loop");
794 self.resume(event_loop);
795 true
796 }
797 CustomEvent::StopLoop => {
798 info!("rs: handling custom stop loop");
799 self.runner.is_running = false;
800 event_loop.exit();
801 true
802 }
803 _ => false, }
805 }
806 _ => false, }
808 }
809
810 #[allow(clippy::too_many_lines)]
813 fn process_window_events(&mut self, event: &WindowEvent, event_loop: &ActiveEventLoop) -> bool {
814 match event {
815 WindowEvent::RedrawRequested => {
816 {
817 let gpu_res = self.gpu_res.as_mut().unwrap();
818 gpu_res.redraw_requested = false;
819 }
820
821 if !self.runner.do_render {
822 return false;
823 }
824
825 debug!("gloss: render");
826
827 if self.runner.frame_is_started {
830 debug!("the frame was already started, we are ignoring this re-render");
831 return true;
832 }
833 self.start_frame();
834
835 match self.render() {
836 Ok(()) => {}
837 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
839 self.resize(self.gpu_res.as_ref().unwrap().window.inner_size());
840 }
841 Err(wgpu::SurfaceError::OutOfMemory) => {
843 error!("SurfaceError: out of memory");
844 }
846 Err(wgpu::SurfaceError::Timeout) => error!("SurfaceError: timeout"),
848 _ => error!("Render error: other"),
849 }
850 debug!("finsihed handing RedrawRequested");
851 true
852 }
853 WindowEvent::Resized(physical_size) => {
854 self.resize(*physical_size);
855 true
856 }
857 WindowEvent::DroppedFile(path_buf) => {
864 info!("Dropped file {path_buf:?}");
865 let path = path_buf.to_str().unwrap();
866
867 if self.scene.is_none() {
868 return true;
869 }
870
871 self.render().ok();
874
875 let scene = self.scene.as_mut().unwrap();
876
877 #[allow(unused_mut)]
879 #[cfg(feature = "with-gui")]
880 {
881 let gpu_res = self.gpu_res.as_mut().unwrap();
882 let gui = gpu_res.gui.as_mut().unwrap();
883 if gui.is_hovering() {
884 gui.on_drop(path_buf, scene);
885 return true;
886 }
887 }
888
889 let filetype = match path_buf.extension() {
891 Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
892 None => FileType::Unknown,
893 };
894 match filetype {
895 FileType::Obj | FileType::Ply | FileType::Gltf => {
896 let builder = builders::build_from_file(path);
897 let name = scene.get_unused_name();
898 scene.get_or_create_entity(&name).insert_builder(builder);
899 return true;
900 }
901 FileType::Unknown => {
902 info!("Gloss doesn't know how to handle dropped file {path:?}. trying to let plugins handle it");
903 }
904 }
905
906 let event = crate::plugin_manager::Event::DroppedFile(RString::from(path));
908 let handled = self.plugins.try_handle_event(scene, &mut self.runner, &event);
909
910 if !handled {
911 info!("Neither Gloss nor any of the plugin could load the dropped file {path:?}");
912 return false;
913 }
914
915 true
916 }
917 WindowEvent::KeyboardInput { event, .. } => {
918 if let PhysicalKey::Code(code) = event.physical_key {
919 if event.state == ElementState::Pressed && !event.repeat {
920 #[allow(clippy::single_match)] match code {
922 KeyCode::KeyH => {
923 #[cfg(feature = "with-gui")]
925 {
926 if let Some(gpu_res) = self.gpu_res.as_mut() {
927 if let Some(gui) = gpu_res.gui.as_mut() {
928 gui.hidden = !gui.hidden;
929 gpu_res.request_redraw();
930 }
931 }
932 }
933 }
934 _ => {}
935 }
936 }
937 }
938 true
939 }
940
941 WindowEvent::CloseRequested {} | WindowEvent::Destroyed {} => {
942 event_loop.exit();
943 true
944 }
945
946 WindowEvent::Occluded(_) => {
947 if self.scene.is_none() {
948 return true;
949 }
950 let scene = self.scene.as_mut().unwrap();
951 let mut camera = scene.get_current_cam().unwrap();
952 camera.reset_all_touch_presses(scene);
953 true
954 }
955 _ => false, }
957 }
958
959 #[allow(clippy::cast_possible_truncation)]
962 fn process_input_events(&mut self, event: &WindowEvent) -> bool {
963 if self.scene.is_none() {
964 return true;
965 }
966 let scene = self.scene.as_mut().unwrap();
967 let mut camera = scene.get_current_cam().unwrap();
968
969 if !camera.is_initialized(scene) {
971 return false;
972 }
973
974 let consumed = match event {
975 WindowEvent::MouseInput { button, state, .. } => {
976 if *state == ElementState::Pressed {
977 camera.mouse_pressed(button, scene);
978 } else {
979 camera.mouse_released(scene);
980 }
981 true
982 }
983 WindowEvent::MouseWheel { delta, .. } => {
984 camera.process_mouse_scroll(delta, scene);
985 true
986 }
987 WindowEvent::CursorMoved { position, .. } => {
988 camera.process_mouse_move(position.x, position.y, self.window_size.width, self.window_size.height, scene);
989 true
990 }
991 WindowEvent::Touch(touch) => {
992 #[allow(clippy::cast_sign_loss)]
993 if touch.phase == TouchPhase::Started {
994 camera.touch_pressed(touch, scene);
995 }
996 if touch.phase == TouchPhase::Ended || touch.phase == TouchPhase::Cancelled {
997 camera.touch_released(touch, scene);
998 }
999 if touch.phase == TouchPhase::Moved {
1000 camera.process_touch_move(touch, self.window_size.width, self.window_size.height, scene);
1001 }
1002 true
1003 }
1004 _ => false, };
1006 let gpu_res = self.gpu_res.as_mut().unwrap();
1007
1008 if consumed {
1009 gpu_res.request_redraw();
1010 }
1011
1012 consumed
1013 }
1014
1015 #[cfg(feature = "with-gui")]
1016 fn process_gui_events(&mut self, event: &WindowEvent) -> EventResponse {
1017 let gpu_res = self.gpu_res.as_mut().unwrap();
1018 if let Some(mut gui) = gpu_res.gui.take() {
1021 let response = gui.on_event(&gpu_res.window, event);
1022 gpu_res.gui = Some(gui); response
1024 } else {
1025 EventResponse {
1026 repaint: false,
1027 consumed: false,
1028 }
1029 }
1030 }
1031
1032 fn process_all_events(&mut self, event: &Event<CustomEvent>, event_loop: &ActiveEventLoop) {
1034 if self.gpu_res.is_none() {
1036 debug!("GPU resources not ready yet, ignoring event {event:?}");
1037 return;
1038 }
1039
1040 if !self.runner.is_running {
1041 if let Event::WindowEvent { ref event, window_id: _ } = event {
1044 if event == &WindowEvent::RedrawRequested {
1045 let gpu_res = self.gpu_res.as_mut().unwrap();
1047 gpu_res.redraw_requested = false;
1048 }
1049 }
1050 if let Event::WindowEvent {
1052 event: WindowEvent::Resized(physical_size),
1053 window_id: _,
1054 } = event
1055 {
1056 self.resize(*physical_size);
1057 }
1058
1059 return; }
1061
1062 match event {
1068 Event::WindowEvent { ref event, window_id } if *window_id == self.gpu_res.as_ref().unwrap().window.id() => {
1069 if self.window_size.height < 16 || self.window_size.width < 16 {
1074 warn!("Skipping rendering and trying again to resize. Window size is {:?}", self.window_size);
1075 #[cfg(target_arch = "wasm32")]
1078 self.resize_to_canvas();
1079 let gpu_res = self.gpu_res.as_mut().unwrap();
1080 gpu_res.request_redraw();
1081 return;
1082 }
1083
1084 self.process_window_events(event, event_loop); cfg_if::cfg_if! {
1086 if #[cfg(feature = "with-gui")]{
1087 let res = self.process_gui_events(event);
1088 if res.consumed {
1090 self.gpu_res.as_mut().unwrap().request_redraw();
1091 } else {
1092 self.process_input_events(event);
1093 }
1094 if self.scene.is_none() {
1096 return;
1097 }
1098 let scene = self.scene.as_mut().unwrap();
1099 let mut camera = scene.get_current_cam().unwrap();
1100 if let Some(ref gui) = self.gpu_res.as_mut().unwrap().gui {
1102 if gui.wants_pointer_input() {
1103 camera.mouse_released(scene);
1104 }
1105 }
1106 }else{ self.process_input_events(event);
1108 }
1109 }
1110 }
1111 _ => {}
1112 }
1113 }
1114
1115 #[allow(unused)]
1118 #[cfg(not(target_arch = "wasm32"))] fn event_loop_one_iter(&mut self) {
1121 use winit::platform::pump_events::EventLoopExtPumpEvents;
1127 let mut event_loop = self.runner.event_loop.take().unwrap();
1128 self.runner.is_running = true;
1129 self.runner.do_render = false; let timeout = Some(Duration::ZERO);
1132 event_loop.pump_app_events(timeout, self);
1133
1134 self.runner.is_running = false;
1135
1136 self.runner.event_loop = Some(event_loop);
1138 }
1139
1140 pub fn start_frame(&mut self) -> Duration {
1143 #[cfg(not(target_arch = "wasm32"))]
1145 {
1146 if self.gpu_res.is_none() {
1147 self.event_loop_one_iter(); }
1149 assert!(self.gpu_res.is_some(), "GPU Res has not been created!");
1150 }
1151 if !self.runner.did_warmup {
1153 self.runner.did_warmup = true; self.warmup();
1156 self.warmup(); }
1158
1159 self.runner.update_dt();
1160 debug!("after update dt it is {:?}", self.runner.dt());
1161 self.runner.time_last_frame = Instant::now();
1162
1163 self.runner.frame_is_started = true;
1164
1165 self.runner.dt
1166 }
1167
1168 pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
1173 if !self.runner.frame_is_started {
1174 error!("The frame was not started so this might contain stale dt. Please use viewer.start_frame() before doing a v.render()");
1175 }
1176 if self.scene.is_none() {
1177 self.runner.frame_is_started = false;
1178 return Ok(());
1179 }
1180
1181 let scene = self.scene.as_mut().unwrap();
1182 let mut camera = scene.get_current_cam().unwrap();
1183
1184 if self.runner.first_time {
1188 self.runner.time_init = Instant::now();
1189 }
1190
1191 let gpu_res = self.gpu_res.as_mut().unwrap();
1192 self.plugins.run_logic_systems(gpu_res, scene, &mut self.runner, true);
1193
1194 let output = gpu_res.surface.get_current_texture()?;
1196 let out_view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
1197 let out_width = output.texture.width();
1198 let out_height = output.texture.height();
1199
1200 let dt = self.runner.dt();
1202
1203 camera.on_window_resize(out_width, out_height, scene);
1204
1205 gpu_res
1208 .renderer
1209 .render_to_view(&out_view, &gpu_res.gpu, &mut camera, scene, &mut self.config, dt);
1210
1211 #[cfg(feature = "with-gui")]
1214 if let Some(ref mut gui) = gpu_res.gui {
1215 gui.render(
1216 &gpu_res.window,
1217 &gpu_res.gpu,
1218 &gpu_res.renderer,
1219 &self.runner,
1220 scene,
1221 &self.plugins,
1222 &mut self.config,
1223 &out_view,
1224 );
1225 }
1226
1227 self.internal_plugins.run_gpu_systems(gpu_res, scene, &mut self.runner, true);
1228 camera.clear_click(scene);
1230
1231 output.present();
1233
1234 self.runner.first_time = false;
1235 self.runner.frame_is_started = false;
1236
1237 Ok(())
1238 }
1239
1240 pub fn render_to_texture(&mut self) -> Result<(), wgpu::SurfaceError> {
1245 if !self.runner.frame_is_started {
1246 error!("The frame was not started so this might contain stale dt. Please use viewer.start_frame() before doing a v.render_to_texture()");
1247 }
1248
1249 if self.scene.is_none() {
1250 return Ok(());
1251 }
1252 let scene = self.scene.as_mut().unwrap();
1253 let mut camera = scene.get_current_cam().unwrap();
1254
1255 if self.runner.first_time {
1259 self.runner.time_init = Instant::now();
1260 }
1261
1262 let gpu_res = self.gpu_res.as_mut().unwrap();
1263 self.plugins.run_logic_systems(gpu_res, scene, &mut self.runner, true);
1264
1265 let out_tex = gpu_res.renderer.rendered_tex();
1268 let out_width = out_tex.width();
1269 let out_height = out_tex.height();
1270
1271 let dt = self.runner.dt();
1273
1274 camera.on_window_resize(out_width, out_height, scene);
1275
1276 gpu_res.renderer.render_to_texture(&gpu_res.gpu, &mut camera, scene, &mut self.config, dt);
1279
1280 #[cfg(feature = "with-gui")]
1283 if let Some(ref mut gui) = gpu_res.gui {
1284 let out_view = &gpu_res.renderer.rendered_tex().view;
1285 gui.render(
1286 &gpu_res.window,
1287 &gpu_res.gpu,
1288 &gpu_res.renderer,
1289 &self.runner,
1290 scene,
1291 &self.plugins,
1292 &mut self.config,
1293 out_view,
1294 );
1295 }
1296
1297 self.internal_plugins.run_gpu_systems(gpu_res, scene, &mut self.runner, true);
1298 camera.clear_click(scene);
1300
1301 self.runner.first_time = false;
1302 self.runner.frame_is_started = false;
1303
1304 Ok(())
1305 }
1306
1307 #[cfg(not(target_arch = "wasm32"))]
1336 pub fn run(&mut self) {
1337 let event_loop = self.runner.event_loop.take().unwrap();
1338 self.runner.is_running = self.runner.autostart;
1339 let _ = event_loop.run_app(self);
1340 }
1341
1342 #[cfg(target_arch = "wasm32")]
1350 pub fn run<F, Fut>(mut self, f: F)
1351 where
1352 F: FnOnce(Scene) -> Fut + 'static,
1353 Fut: Future<Output = Scene> + 'static,
1354 {
1355 let wrapper: SceneInitFn = Box::new(move |scene: Scene| Box::pin(f(scene)));
1357
1358 self.scene_init_func = Some(wrapper);
1359
1360 let event_loop = self.runner.event_loop.take().unwrap();
1361 self.runner.is_running = self.runner.autostart;
1362 let _ = event_loop.spawn_app(self);
1363 }
1364
1365 #[allow(clippy::missing_panics_doc)]
1368 #[allow(unreachable_code)] #[cfg(not(target_arch = "wasm32"))]
1370 pub fn run_static_ref(&'static mut self) {
1371 let event_loop = self.runner.event_loop.take().unwrap();
1372 self.runner.is_running = self.runner.autostart;
1373
1374 cfg_if::cfg_if! {
1376 if #[cfg(not(target_arch = "wasm32"))]{
1377 let _ = event_loop.run_app(self);
1378 }else{
1379 let _ = event_loop.spawn_app(self);
1380 }
1381 }
1382 }
1383
1384 #[cfg(target_arch = "wasm32")]
1385 pub fn run_static_ref<F, Fut>(&'static mut self, f: F)
1386 where
1387 F: FnOnce(Scene) -> Fut + 'static,
1388 Fut: Future<Output = Scene> + 'static,
1389 {
1390 let wrapper: SceneInitFn = Box::new(move |scene: Scene| Box::pin(f(scene)));
1392
1393 self.scene_init_func = Some(wrapper);
1394
1395 let event_loop = self.runner.event_loop.take().unwrap();
1396 self.runner.is_running = self.runner.autostart;
1397 let _ = event_loop.spawn_app(self);
1398 }
1399
1400 #[allow(clippy::missing_panics_doc)]
1404 pub fn recreate_event_loop(&mut self) {
1405 self.stop_event_loop();
1406 self.suspend(); let runner = Runner::new(&self.canvas_id_parsed);
1408 self.runner = runner;
1409 let event_stop = CustomEvent::ResumeLoop;
1411 self.runner.event_loop_proxy.send_event(event_stop).ok();
1412 }
1413
1414 pub fn stop_event_loop(&self) {
1415 let event_stop = CustomEvent::StopLoop;
1416 self.runner.event_loop_proxy.send_event(event_stop).ok();
1417 }
1418
1419 fn create_scene() -> Scene {
1420 let mut scene = Scene::new();
1421 scene.create_camera();
1422
1423 scene.world.set_trackers_changed();
1427 scene
1432 }
1433
1434 fn finalize_scene(&mut self) {
1435 self.scene.as_mut().unwrap().add_resource(self.gpu_res.as_ref().unwrap().gpu.clone());
1436 }
1437
1438 fn resume(&mut self, event_loop: &ActiveEventLoop) {
1439 info!("RS: resume");
1440 self.runner.is_running = self.runner.autostart;
1441 if self.gpu_res.is_none() {
1442 #[cfg(not(target_arch = "wasm32"))]
1444 {
1445 self.gpu_res =
1446 Some(GpuResources::new(event_loop, &self.runner.event_loop_proxy, self.canvas_id_parsed.as_ref(), &self.config).block_on());
1447
1448 self.scene = Some(Self::create_scene());
1459 self.finalize_scene();
1460 }
1461
1462 #[cfg(target_arch = "wasm32")]
1464 {
1465 let event_loop_proxy = self.runner.event_loop_proxy.clone();
1466 let canvas_id = self.canvas_id_parsed.clone();
1467 let config = self.config.clone();
1468 let event_loop_ptr = event_loop as *const ActiveEventLoop;
1469
1470 let (sender, receiver) = mpsc::channel::<GpuResources>();
1472
1473 wasm_bindgen_futures::spawn_local(async move {
1474 let event_loop_ref = unsafe { &*event_loop_ptr };
1475 let gpu_res = GpuResources::new(event_loop_ref, &event_loop_proxy, canvas_id.as_ref(), &config).await;
1476
1477 let _ = sender.send(gpu_res);
1479
1480 let _ = event_loop_proxy.send_event(CustomEvent::GpuResourcesReady);
1482 });
1483
1484 self.gpu_res_receiver = Some(receiver);
1486 }
1487 }
1488 #[cfg(target_arch = "wasm32")]
1489 self.resize_to_canvas()
1490 }
1491
1492 pub fn suspend(&mut self) {
1493 info!("RS: suspend");
1496 self.runner.is_running = false;
1497 if let Some(scene) = self.scene.as_mut() {
1498 scene.remove_all_gpu_components();
1499 }
1500 self.gpu_res.take();
1501 self.scene
1502 .as_mut()
1503 .map(|scene| scene.get_current_cam().map(|mut cam| cam.reset_all_touch_presses(scene)));
1504 }
1506
1507 pub fn warmup(&mut self) {
1512 debug!("Starting warmup");
1513 self.start_frame();
1515 self.run_manual_plugins(); let _ = self.render();
1521 self.reset_for_first_time();
1522 debug!("finished warmup");
1523 }
1524
1525 pub fn reset_for_first_time(&mut self) {
1526 self.runner.first_time = true;
1527 }
1528
1529 pub fn add_logic_system(&mut self, sys: LogicSystem) {
1530 self.plugins.logic_systems.push(Tuple2(sys, SystemMetadata::default()));
1531 }
1532
1533 #[cfg(feature = "with-gui")]
1534 pub fn add_gui_system(&mut self, sys: GuiSystem) {
1535 self.plugins.gui_systems.push(Tuple2(sys, SystemMetadata::default()));
1536 }
1537
1538 #[allow(clippy::missing_panics_doc)]
1539 pub fn run_manual_plugins(&mut self) {
1540 {
1541 if self.scene.is_none() {
1542 return;
1543 }
1544 let scene = self.scene.as_mut().unwrap();
1545 let gpu_res = self.gpu_res.as_mut().unwrap();
1547 self.plugins.run_logic_systems(gpu_res, scene, &mut self.runner, false);
1548 }
1550 }
1551
1552 pub fn insert_plugin<T: Plugin + 'static>(&mut self, plugin: &T) {
1553 self.plugins.insert_plugin(plugin);
1554 }
1555
1556 #[allow(clippy::missing_errors_doc)]
1565 pub fn wait_gpu_finish(&self) -> Result<wgpu::PollStatus, wgpu::PollError> {
1566 self.gpu_res.as_ref().unwrap().gpu.device().poll(wgpu::PollType::Wait)
1567 }
1568
1569 fn create_window(
1572 event_loop: &ActiveEventLoop,
1573 _event_loop_proxy: &EventLoopProxy<CustomEvent>,
1574 _canvas_id: Option<&String>,
1575 ) -> Result<Window, Box<dyn Error>> {
1576 #[allow(unused_mut)]
1579 let mut window_attributes = Window::default_attributes()
1580 .with_title("Gloss")
1581 .with_inner_size(PhysicalSize::new(1600, 1200))
1582 .with_maximized(true);
1583
1584 #[cfg(target_arch = "wasm32")]
1586 {
1587 use winit::platform::web::WindowAttributesExtWebSys;
1588 let window = web_sys::window().unwrap();
1589 let document = window.document().unwrap();
1590 let canvas = document
1591 .query_selector(&_canvas_id.as_ref().unwrap())
1592 .expect("Cannot query for canvas element.");
1593 if let Some(canvas) = canvas {
1594 let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok();
1595
1596 window_attributes = window_attributes.with_canvas(canvas).with_prevent_default(false).with_append(false)
1598 } else {
1599 panic!("Cannot find element: {:?}.", _canvas_id.as_ref().unwrap());
1600 }
1601 }
1602
1603 let window = event_loop.create_window(window_attributes)?;
1604
1605 Ok(window)
1606 }
1607
1608 pub fn override_dt(&mut self, new_dt: f32) {
1609 self.runner.override_dt(new_dt);
1610 }
1611
1612 pub fn get_final_tex(&self) -> &Texture {
1613 let tex = self.gpu_res.as_ref().unwrap().renderer.rendered_tex();
1614 tex
1615 }
1616
1617 pub fn get_final_depth(&self) -> &Texture {
1618 let depth = self.gpu_res.as_ref().unwrap().renderer.depth_buffer();
1619 depth
1620 }
1621}
1622
1623impl ApplicationHandler<CustomEvent> for Viewer {
1624 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
1625 self.resume(event_loop);
1626 }
1627
1628 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvent) {
1629 self.process_custom_context_event(&Event::UserEvent(event), event_loop);
1630 self.process_custom_resize_events(&Event::UserEvent(event));
1631 self.process_custom_other_event(&Event::UserEvent(event), event_loop);
1632 }
1633 fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
1634 self.process_all_events(&Event::WindowEvent { window_id, event }, event_loop);
1635 }
1636 #[allow(unused_variables)]
1637 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
1638 #[cfg(not(target_arch = "wasm32"))]
1639 {
1640 if let Some(gpu_res) = self.gpu_res.as_mut() {
1641 gpu_res.request_redraw();
1642 }
1643 }
1644 }
1645
1646 fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
1647 debug!("Handling Suspended event");
1648 self.suspend();
1649 }
1650
1651 fn exiting(&mut self, event_loop: &ActiveEventLoop) {
1652 self.runner.is_running = false;
1653 event_loop.exit();
1654 }
1655}
1656
1657pub fn supported_backends() -> wgpu::Backends {
1660 if cfg!(target_arch = "wasm32") {
1661 wgpu::Backends::GL
1668 } else {
1669 wgpu::Backends::from_env().unwrap_or(wgpu::Backends::VULKAN | wgpu::Backends::METAL)
1671 }
1672}
1673
1674pub fn is_safari_browser() -> bool {
1676 #[cfg(target_arch = "wasm32")]
1677 fn is_safari_browser_inner() -> Option<bool> {
1678 use web_sys::wasm_bindgen::JsValue;
1679 let window = web_sys::window()?;
1680 Some(window.has_own_property(&JsValue::from("safari")))
1681 }
1682
1683 #[cfg(not(target_arch = "wasm32"))]
1684 fn is_safari_browser_inner() -> Option<bool> {
1685 None
1686 }
1687
1688 is_safari_browser_inner().unwrap_or(false)
1689}
1690
1691pub fn is_firefox_browser() -> bool {
1693 #[cfg(target_arch = "wasm32")]
1694 {
1695 web_sys::window()
1696 .and_then(|w| w.navigator().user_agent().ok())
1697 .is_some_and(|ua| ua.to_lowercase().contains("firefox"))
1698 }
1699
1700 #[cfg(not(target_arch = "wasm32"))]
1701 {
1702 false
1703 }
1704}
1705
1706pub async fn get_adapter(instance: &wgpu::Instance, surface: Option<&wgpu::Surface<'_>>) -> wgpu::Adapter {
1709 #[cfg(not(target_arch = "wasm32"))]
1710 fn remove_from_vec(vec: &mut Vec<wgpu::Adapter>, idx_str: &str) -> wgpu::Adapter {
1711 let idx = idx_str.split(',').next().unwrap().parse::<usize>().unwrap(); assert!(
1714 (0..vec.len()).contains(&idx),
1715 "Tried to index device with idx {} but we only have detected {} devices",
1716 idx,
1717 vec.len()
1718 );
1719
1720 info!("Selecting adapter with idx {idx}");
1721 vec.remove(idx)
1722 }
1723
1724 cfg_if::cfg_if! {
1725 if #[cfg(target_arch = "wasm32")]{
1726 instance
1727 .request_adapter(&wgpu::RequestAdapterOptions {
1728 power_preference: wgpu::PowerPreference::HighPerformance,
1729 compatible_surface: surface,
1730 force_fallback_adapter: false,
1731 })
1732 .await
1733 .expect("An adapter could not be found. Maybe there's a driver issue on your machine?")
1734 }else {
1735 let mut adapters = enumerate_adapters(instance);
1736
1737 let wgpu_dev_id = std::env::var("WGPU_VISIBLE_DEVICES");
1738 let cuda_dev_id = std::env::var("CUDA_VISIBLE_DEVICES");
1739 match wgpu_dev_id {
1740 Ok(idx) => remove_from_vec(&mut adapters, &idx),
1741 Err(_) => match cuda_dev_id {
1742 Ok(idx) => remove_from_vec(&mut adapters, &idx),
1743 Err(_) => instance
1744 .request_adapter(&wgpu::RequestAdapterOptions {
1745 power_preference: wgpu::PowerPreference::HighPerformance,
1746 compatible_surface: surface,
1747 force_fallback_adapter: false,
1748 })
1749 .await
1750 .expect("An adapter could not be found. Maybe there's a driver issue on your machine?"),
1751 },
1752 }
1753 }
1754 }
1755}
1756
1757#[cfg(not(target_arch = "wasm32"))]
1761pub fn enumerate_adapters(instance: &wgpu::Instance) -> Vec<wgpu::Adapter> {
1762 let mut adapters = instance.enumerate_adapters(wgpu::Backends::all());
1763
1764 adapters.sort_by_key(|x| (x.get_info().device_type as i32 - wgpu::DeviceType::DiscreteGpu as i32).abs());
1766
1767 adapters
1768}
1769
1770#[derive(Debug, Clone, Copy)]
1772pub enum CustomEvent {
1773 Resize(f32, f32),
1774 ContextLost,
1775 ContextRestored,
1776 ResumeLoop,
1777 StopLoop,
1778 #[cfg(target_arch = "wasm32")]
1779 GpuResourcesReady,
1780 #[cfg(target_arch = "wasm32")]
1781 SceneReady,
1782}