gloss_renderer/
viewer.rs

1// TODO: Investigate canvas creation for WASM; Its not created properly
2
3#[cfg(feature = "with-gui")]
4use crate::gui::Gui;
5#[cfg(feature = "with-gui")]
6use crate::plugin_manager::GuiSystem;
7use crate::{
8    camera::Camera,
9    components::Projection,
10    config::Config,
11    forward_renderer::{render_passes::blit_pass::BlitPass, renderer::Renderer},
12    geom::Geom,
13    logger::gloss_setup_logger_from_config,
14    plugin_manager::{
15        plugins::{Plugin, Plugins},
16        systems::{LogicSystem, SystemMetadata},
17    },
18    scene::{Scene, GLOSS_CAM_NAME},
19    set_panic_hook,
20};
21
22use easy_wgpu::gpu::Gpu;
23use easy_wgpu::texture::Texture;
24#[cfg(feature = "with-gui")]
25use egui_winit::EventResponse;
26use gloss_utils::abi_stable_aliases::std_types::{RString, Tuple2};
27use log::{debug, warn};
28use winit::{
29    dpi::PhysicalSize,
30    event::TouchPhase,
31    event_loop::{ActiveEventLoop, EventLoopProxy},
32    keyboard::{KeyCode, PhysicalKey},
33    window::WindowId,
34};
35
36use core::time::Duration;
37use gloss_utils::io::FileType;
38use log::{error, info};
39use pollster::FutureExt;
40use std::{error::Error, sync::Arc};
41
42#[cfg(target_arch = "wasm32")]
43use wasm_bindgen::prelude::*;
44use wasm_timer::Instant;
45use winit::application::ApplicationHandler;
46#[cfg(target_arch = "wasm32")]
47use winit::platform::web::EventLoopExtWebSys;
48
49use winit::{
50    event::{ElementState, Event, WindowEvent},
51    event_loop::EventLoop,
52    window::Window,
53};
54
55/// All the ``GpuResources`` are kept together to be able to easily recreate
56/// them
57pub struct GpuResources {
58    window: Arc<Window>,
59    surface: wgpu::Surface<'static>,
60    surface_config: wgpu::SurfaceConfiguration,
61    pub renderer: Renderer,
62
63    #[cfg(feature = "with-gui")]
64    pub gui: Option<Gui>,
65    _blit_pass: BlitPass, // Copies the final rendered texture towards screen for visualization
66    pub redraw_requested: bool, /* When we request a redraw we set this to true. If we are to request another redraw, if this is set to true we
67                           * ignore the call. We need this because the call to window.redraw_requested() is quite expensive and it can be
68                           * called multiple times a frame if there are multiple inputs */
69
70    // The order of properties in a struct is the order in which items are dropped.
71    // wgpu seems to require that the device be dropped last, otherwise there is a resource leak
72    pub gpu: Gpu,
73}
74#[allow(clippy::missing_panics_doc)]
75#[allow(clippy::too_many_lines)]
76#[allow(unused)]
77impl GpuResources {
78    pub fn new(
79        event_loop: &ActiveEventLoop,
80        event_loop_proxy: &EventLoopProxy<CustomEvent>,
81        canvas_id_parsed: &Option<String>,
82        config: &Config,
83    ) -> Self {
84        // The instance is a handle to our GPU
85        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
86            backends: supported_backends(),
87            dx12_shader_compiler: wgpu::Dx12Compiler::default(),
88            flags: wgpu::InstanceFlags::default(),
89            gles_minor_version: wgpu::Gles3MinorVersion::Automatic,
90        });
91
92        let window = Viewer::create_window(event_loop, event_loop_proxy, &canvas_id_parsed.clone()).expect("failed to create initial window");
93
94        let window = Arc::new(window);
95
96        let surface = unsafe { instance.create_surface(window.clone()) }.unwrap();
97
98        //if we are on wasm we cannot enumerate adapter so we skip this at compile time
99        cfg_if::cfg_if! {
100            if #[cfg(not(target_arch = "wasm32"))]{
101                let adapters = enumerate_adapters(&instance);
102                info!("Number of possible adapters: {:?}", adapters.len());
103                for (i, adapter) in adapters.iter().enumerate() {
104                    info!("Adapter option {:?}: {:?}", i + 1, adapter.get_info());
105                }
106            }
107        }
108        let adapter = get_adapter(&instance, Some(&surface));
109        info!("Selected adapter: {:?}", adapter.get_info());
110
111        //TODO if the adapter is not a discrete Nvidia gpu, disable the wgpu to pytorch
112        // interop
113        let mut desired_features = wgpu::Features::empty();
114        cfg_if::cfg_if! {
115            if #[cfg(not(target_arch = "wasm32"))]{
116                desired_features = desired_features.union(wgpu::Features::TIMESTAMP_QUERY.union(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES));
117                desired_features = desired_features.union(wgpu::Features::POLYGON_MODE_POINT);
118                desired_features = desired_features.union(wgpu::Features::POLYGON_MODE_LINE);
119            }
120        }
121        let required_features = adapter.features().intersection(desired_features); //only take the features that are actually supported
122                                                                                   // info!("enabled features: {required_features:?}");
123
124        //dealing with wasm putting 2048 as maximum texture size
125        //https://github.com/gfx-rs/wgpu/discussions/2952
126        let max_limits = adapter.limits();
127        #[allow(unused_mut)]
128        let mut limits_to_request = wgpu::Limits::default();
129        if cfg!(target_arch = "wasm32") {
130            limits_to_request = wgpu::Limits::downlevel_webgl2_defaults();
131        }
132        limits_to_request.max_texture_dimension_1d = max_limits.max_texture_dimension_1d;
133        limits_to_request.max_texture_dimension_2d = max_limits.max_texture_dimension_2d;
134        limits_to_request.max_buffer_size = max_limits.max_buffer_size;
135
136        let mut memory_hints = wgpu::MemoryHints::Performance;
137        if cfg!(target_arch = "wasm32") {
138            //we usually have issue with running out of memory on wasm, so I would rather
139            // optimize for low memory usage
140            memory_hints = wgpu::MemoryHints::MemoryUsage;
141        }
142
143        //device and queue
144        let (device, queue) = adapter
145            .request_device(
146                &wgpu::DeviceDescriptor {
147                    label: None,
148                    required_features,
149                    required_limits: limits_to_request,
150                    memory_hints,
151                },
152                None, // Trace path
153            )
154            .block_on()
155            .expect("A device and queue could not be created. Maybe there's a driver issue on your machine?");
156        let gpu = Gpu::new(adapter, instance, device, queue);
157
158        //configure surface
159        let surface_caps = surface.get_capabilities(gpu.adapter());
160        // Shader code assumes we are writing to non-Srgb surface texture and we do
161        // tonemapping and gamma-correction manually
162        let surface_format = surface_caps
163            .formats
164            .iter()
165            .copied()
166            .find(|f| !f.is_srgb())
167            .unwrap_or(surface_caps.formats[0]);
168
169        //get size of canvas or just some dummy value at the beggining until we get a
170        // resize event
171        let mut size = PhysicalSize::new(1200, 1200);
172        #[cfg(target_arch = "wasm32")]
173        if let Some(canvas_size) = Viewer::get_html_elem_size(&canvas_id_parsed.as_ref().unwrap()) {
174            size = canvas_size.to_physical(window.scale_factor());
175        }
176        // info!("init: size is {:?}", size);
177
178        let surface_config = wgpu::SurfaceConfiguration {
179            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
180            format: surface_format,
181            width: size.width,
182            height: size.height,
183            present_mode: wgpu::PresentMode::AutoNoVsync, // double-buffered: low-latency, may have tearing
184            // present_mode: wgpu::PresentMode::AutoVsync, // triple-buffered: high-latency, no tearing
185            alpha_mode: surface_caps.alpha_modes[0],
186            view_formats: vec![],
187            desired_maximum_frame_latency: 2,
188        };
189        surface.configure(gpu.device(), &surface_config);
190
191        // info!("enable gui is {}", config.core.enable_gui);
192        #[cfg(feature = "with-gui")]
193        let gui = if config.core.enable_gui {
194            let mut gui = Gui::new(&window, &gpu, surface_format);
195            gui.hidden = config.core.gui_start_hidden;
196            Some(gui)
197        } else {
198            None
199        };
200
201        let renderer = Renderer::new(&gpu, &config.render, Some(surface_format));
202        let blit_pass = BlitPass::new(&gpu, &surface_format);
203
204        Self {
205            window,
206            surface,
207            surface_config,
208            gpu,
209            renderer,
210            #[cfg(feature = "with-gui")]
211            gui,
212            _blit_pass: blit_pass,
213            redraw_requested: false,
214        }
215    }
216    pub fn request_redraw(&mut self) {
217        if self.redraw_requested {
218            debug!("Redraw was already requested, ignoring.");
219        } else {
220            self.window.request_redraw();
221            self.redraw_requested = true;
222        }
223    }
224}
225
226/// The ``Runner`` for managing the event loop and time based scene updates
227#[derive(Debug)]
228#[repr(C)]
229pub struct Runner {
230    event_loop: Option<EventLoop<CustomEvent>>, // Keep it as an option so as to remove it from this object and avoid lifetime issues
231    event_loop_proxy: EventLoopProxy<CustomEvent>,
232    pub autostart: bool, // if this is true, when doing viewer.run we also start actually running the event loop and set the is_running to true
233    pub is_running: bool,
234    pub do_render: bool,
235    pub first_time: bool,
236    pub did_warmup: bool,
237    frame_is_started: bool, /* after calling viewer.start_frame() this gets set to true. any subsequent cals to start frame will be ignored if
238                             * this frame has already been started. This can happen when using python and doing viewer.update() this will run some
239                             * system that does a window.request_redraw which will cause another rerender but this frame has already started so we
240                             * don't need to do anything since we are already in the middle of redrawing */
241    time_init: Instant,       //time when the init of the viewer has finished
242    time_last_frame: Instant, //time when we started the previous frame. So the instant when we called viewer.start_frame()
243    dt: Duration,             /* delta time since we finished last frame, we store it directly here instead of constantly querying it because
244                               * different systems might then get different dt depending on how long they take to run. This dt is set once when
245                               * doing viewer.start_frame() */
246}
247#[allow(unused)]
248impl Runner {
249    #[allow(clippy::missing_panics_doc)]
250    #[allow(clippy::new_without_default)]
251    pub fn new(canvas_id: &Option<String>) -> Self {
252        let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
253        let event_loop_proxy: EventLoopProxy<CustomEvent> = event_loop.create_proxy();
254
255        #[cfg(target_arch = "wasm32")]
256        if let Some(ref canvas_id) = canvas_id {
257            Viewer::add_listener_to_canvas_resize(&event_loop, &canvas_id);
258            Viewer::add_listener_to_context(&event_loop, &canvas_id);
259        }
260
261        let time_init = Instant::now();
262        let time_last_frame = Instant::now();
263        Self {
264            event_loop: Some(event_loop),
265            event_loop_proxy,
266            autostart: true,
267            is_running: false,
268            do_render: true,
269            first_time: true,
270            did_warmup: false,
271            frame_is_started: false,
272            time_init,
273            time_last_frame,
274            dt: Duration::ZERO,
275        }
276    }
277    pub fn time_since_init(&self) -> Duration {
278        if self.first_time {
279            Duration::ZERO
280        } else {
281            self.time_init.elapsed()
282        }
283    }
284    pub fn update_dt(&mut self) {
285        if self.first_time {
286            self.dt = Duration::ZERO;
287        } else {
288            self.dt = self.time_last_frame.elapsed();
289        }
290    }
291    pub fn override_dt(&mut self, new_dt: f32) {
292        self.dt = Duration::from_secs_f32(new_dt);
293    }
294    pub fn dt(&self) -> Duration {
295        self.dt
296    }
297}
298
299/// Viewer encapsulates the window and event managing of the app.
300/// The viewer contains a [Window], a [Scene] and a [Renderer].
301/// Typically the viewer is used by calling [`Viewer::run`] which automatically
302/// spins a rendering loop and processes keyboard and mouse events.
303/// The rendering loop can also be created manually by running
304/// [`Viewer::update`] in a loop {}
305#[repr(C)]
306pub struct Viewer {
307    // Gpu resources that need to be rebuild every time we lose context or suspend the application
308    pub gpu_res: Option<GpuResources>,
309
310    // Cpu resources
311    pub camera: Camera, //TODO pull the camera into the scene as just another entity
312    pub scene: Scene,
313
314    window_size: winit::dpi::PhysicalSize<u32>, /* We need this because sometimes we need to recreate the window and we need to recreate it with
315                                                 * this size */
316
317    canvas_id_parsed: Option<String>,
318
319    pub config: Config,
320    pub runner: Runner,
321    pub plugins: Plugins,
322}
323
324impl Viewer {
325    pub fn new(config_path: Option<&str>) -> Self {
326        let config = Config::new(config_path);
327        Self::new_with_config(&config)
328    }
329
330    #[allow(clippy::too_many_lines)]
331    #[allow(clippy::missing_panics_doc)]
332    pub fn new_with_config(config: &Config) -> Self {
333        set_panic_hook();
334
335        if config.core.auto_create_logger {
336            gloss_setup_logger_from_config(config);
337        }
338
339        // Expensive but useful
340        re_memory::accounting_allocator::set_tracking_callstacks(config.core.enable_memory_profiling_callstacks);
341
342        let canvas_id_parsed = config.core.canvas_id.as_ref().map(|canvas_id| String::from("#") + canvas_id);
343
344        // Runner stuff
345        let runner = Runner::new(&canvas_id_parsed);
346
347        let window_size = winit::dpi::PhysicalSize::new(100, 100);
348
349        let mut scene = Scene::new();
350        let camera = Camera::new(GLOSS_CAM_NAME, &mut scene, false); //TODO make it another entity inside the Scene
351
352        Self {
353            gpu_res: None,
354            runner,
355            scene,
356            camera,
357            plugins: Plugins::new(),
358            canvas_id_parsed: canvas_id_parsed.clone(),
359            config: config.clone(),
360            window_size,
361        }
362    }
363
364    /// Makes the loop emit resize events when the Web canvas changes size. This
365    /// allows the renderer to adjust to the size of the dynamic HTML elements
366    #[cfg(target_arch = "wasm32")]
367    fn add_listener_to_canvas_resize(event_loop: &EventLoop<CustomEvent>, canvas_id: &str) {
368        //https://github.com/rust-windowing/winit/blob/master/examples/custom_events.rs
369        // `EventLoopProxy` allows you to dispatch custom events to the main Winit event
370        // loop from any thread.
371        //make it listen to resizes of the canvas
372        // https://github.com/bevyengine/bevy/blob/main/crates/bevy_winit/src/web_resize.rs#L61
373        let event_loop_proxy = event_loop.create_proxy();
374        let canvas_id = String::from(canvas_id); //copy it internally
375
376        // Function that triggers a custom event for resizing
377        let resize = move || {
378            if let Some(size) = Viewer::get_html_elem_size(&canvas_id) {
379                let event_resize = CustomEvent::Resize(size.width, size.height);
380                event_loop_proxy.send_event(event_resize).ok();
381            }
382        };
383
384        // ensure resize happens on startup
385        // resize();
386
387        let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
388            resize();
389        }) as Box<dyn FnMut(_)>);
390        let window = web_sys::window().unwrap();
391
392        window
393            .add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref())
394            .unwrap();
395        closure.forget();
396    }
397
398    #[cfg(target_arch = "wasm32")]
399    fn add_listener_to_context(event_loop: &EventLoop<CustomEvent>, canvas_id: &str) {
400        let canvas_id = String::from(canvas_id); //copy it internally
401
402        let win = web_sys::window().unwrap();
403        let doc = win.document().unwrap();
404        let element = doc.query_selector(&canvas_id).ok().unwrap().unwrap();
405        let canvas = element.dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
406
407        //function that triggers a custom event for contextlost
408        let event_loop_proxy = event_loop.create_proxy();
409        let context_lost = move || {
410            let event = CustomEvent::ContextLost;
411            info!("SENDING USER EVENT: context loss");
412            event_loop_proxy.send_event(event).ok();
413        };
414
415        let event_loop_proxy = event_loop.create_proxy();
416        let context_restored = move || {
417            let event = CustomEvent::ContextRestored;
418            info!("SENDING USER EVENT: context restored");
419            event_loop_proxy.send_event(event).ok();
420        };
421
422        let closure_lost = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
423            context_lost();
424        }) as Box<dyn FnMut(_)>);
425        let closure_restored = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
426            context_restored();
427        }) as Box<dyn FnMut(_)>);
428
429        //https://developer.mozilla.org/en-US/docs/Web/Events
430        //https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.HtmlCanvasElement.html#method.add_event_listener_with_callback
431        canvas
432            .add_event_listener_with_callback("webglcontextlost", closure_lost.as_ref().unchecked_ref())
433            .unwrap();
434        canvas
435            .add_event_listener_with_callback("webglcontextrestored", closure_restored.as_ref().unchecked_ref())
436            .unwrap();
437        closure_lost.forget();
438        closure_restored.forget();
439    }
440
441    // Queries for the size of an html element like the canvas for which we are
442    // drawing to
443    #[cfg(target_arch = "wasm32")]
444    fn get_html_elem_size(selector: &str) -> Option<winit::dpi::LogicalSize<f32>> {
445        //https://github.com/bevyengine/bevy/blob/main/crates/bevy_winit/src/web_resize.rs#L61
446        let win = web_sys::window().unwrap();
447        let doc = win.document().unwrap();
448        let element = doc.query_selector(selector).ok()??;
449        let parent_element = element.parent_element()?;
450        let rect = parent_element.get_bounding_client_rect();
451        return Some(winit::dpi::LogicalSize::new(rect.width() as f32, rect.height() as f32));
452    }
453
454    #[cfg(target_arch = "wasm32")]
455    pub fn resize_to_canvas(&self) {
456        if let Some(size) = Self::get_html_elem_size(&self.canvas_id_parsed.as_ref().unwrap()) {
457            //TODO: remove
458            warn!("size is {:?}", size);
459            let event_resize = CustomEvent::Resize(size.width, size.height);
460            self.runner.event_loop_proxy.send_event(event_resize).ok();
461        }
462    }
463
464    #[allow(clippy::cast_precision_loss)]
465    fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
466        debug!("resizing new_size is {:?}", new_size);
467        let max_2d_size = self.gpu_res.as_ref().unwrap().gpu.limits().max_texture_dimension_2d;
468        if new_size.width > 16 && new_size.height > 16 && new_size.width < max_2d_size && new_size.height < max_2d_size {
469            let gpu_res = self.gpu_res.as_mut().unwrap();
470            //window and surface
471            self.window_size = new_size;
472            gpu_res.surface_config.width = new_size.width;
473            gpu_res.surface_config.height = new_size.height;
474            gpu_res.surface.configure(gpu_res.gpu.device(), &gpu_res.surface_config);
475            gpu_res.request_redraw();
476
477            //camera aspect ratio
478            //camera has not yet been initialized so there is nothing to do
479            if self.scene.world.has::<Projection>(self.camera.entity).unwrap() {
480                self.camera
481                    .set_aspect_ratio(new_size.width as f32 / new_size.height as f32, &mut self.scene);
482            }
483
484            //gui
485            #[cfg(feature = "with-gui")]
486            if let Some(ref mut gui) = gpu_res.gui {
487                gui.resize(new_size.width, new_size.height);
488            }
489        } else {
490            error!("trying to resize to unsuported size of {new_size:?}");
491        }
492        //camera
493    }
494
495    //Only a conveneince function so that you don't have to do
496    // viewer.gpu_res.request_redraw()
497    pub fn request_redraw(&mut self) {
498        if let Some(gpu_res) = self.gpu_res.as_mut() {
499            gpu_res.request_redraw();
500        } else {
501            error!("No gpu_res created yet");
502        }
503    }
504
505    /// Runs only one update of the rendering loop by processing events and
506    /// rendering a single frame. Useful for cases when the rendering loop needs
507    /// to be created manually. WASM cannot compile this because of internal
508    /// calls to `run_return`()
509    #[cfg(not(target_arch = "wasm32"))]
510    pub fn update(&mut self) {
511        self.event_loop_one_iter();
512        let _ = self.render();
513    }
514
515    #[cfg(not(target_arch = "wasm32"))]
516    pub fn update_offscreen_texture(&mut self) {
517        self.event_loop_one_iter();
518        let _ = self.render_to_texture();
519    }
520
521    /// Processes a custom event that we defined. If it matches one of the
522    /// events, returns true
523    fn process_custom_resize_events(&mut self, event: &Event<CustomEvent>) -> bool {
524        match event {
525            Event::UserEvent(CustomEvent::Resize(new_width, new_height)) => {
526                debug!("rs: handling resize canvas: {:?}", event);
527                let logical_size = winit::dpi::LogicalSize {
528                    width: *new_width,
529                    height: *new_height,
530                };
531                // the event should be handled by resizing the logical size so the width and
532                // height are floats https://github.com/bevyengine/bevy/blob/eb485b1acc619baaae88d5daca0a311b95886281/crates/bevy_winit/src/web_resize.rs#L34C13-L34C46
533                //TODO check if it's needed to get the return value and use it for the
534                // self.resize
535                let _ = self.gpu_res.as_ref().unwrap().window.request_inner_size(logical_size);
536
537                //no need to resize here. Resizing the canvas with window.set_inner_size will
538                // trigger a Window::Resized event sometimes it's needed to
539                // resize here, I'm not very sure why but in some cases the Window:resize event
540                // is sent AFTER the rendering one
541                self.resize(self.gpu_res.as_ref().unwrap().window.inner_size()); //surface requires physical pixels which are not the same as the logical pixels
542                                                                                 // for the window
543
544                true //return true so that this branch is taken in the match
545            }
546            _ => false, //doesn't match any of the events, some other function will need to process this event
547        }
548    }
549
550    fn process_custom_context_event(&mut self, event: &Event<CustomEvent>, event_loop: &ActiveEventLoop) -> bool {
551        match event {
552            Event::UserEvent(event) => {
553                match event {
554                    CustomEvent::ContextLost => {
555                        info!("rs: handling context lost");
556                        self.suspend();
557                        true //return true so that this branch is taken in the
558                             // match
559                    }
560                    CustomEvent::ContextRestored => {
561                        info!("rs: handling context restored");
562                        self.resume(event_loop);
563                        true //return true so that this branch is taken in the
564                             // match
565                    }
566                    _ => false, //doesn't match any of the events, some other function will need to process this event
567                }
568            }
569            _ => false, //doesn't match any of the events, some other function will need to process this event
570        }
571    }
572
573    #[allow(clippy::collapsible_match)]
574    fn process_custom_other_event(&mut self, event: &Event<CustomEvent>, event_loop: &ActiveEventLoop) -> bool {
575        match event {
576            Event::UserEvent(event) => {
577                match event {
578                    CustomEvent::ResumeLoop => {
579                        info!("rs: handling custom resume loop");
580                        self.resume(event_loop);
581                        true
582                    }
583                    CustomEvent::StopLoop => {
584                        info!("rs: handling custom stop loop");
585                        self.runner.is_running = false;
586                        event_loop.exit();
587                        true
588                    }
589                    _ => false, //doesn't match any of the events, some other function will need to process this event
590                }
591            }
592            _ => false, //doesn't match any of the events, some other function will need to process this event
593        }
594    }
595
596    /// Processes loop-related events like stopping the loop. If it matches one
597    /// of the events, returns true
598    // #[allow(unused_variables)]
599    // fn process_loop_events(&mut self, event: &Event<CustomEvent>, event_loop: &ActiveEventLoop) -> bool {
600    //     // Check if gpu_res is initialized
601    //     if let Some(gpu_res) = self.gpu_res.as_mut() {
602    //         match event {
603    //             Event::AboutToWait => {
604    //                 // For native, it essentially does pooling
605    //                 #[cfg(not(target_arch = "wasm32"))]
606    //                 gpu_res.request_redraw();
607    //                 true
608    //             }
609    //             Event::Resumed => {
610    //                 debug!("rs: Resumed");
611    //                 self.resume(event_loop);
612    //                 true
613    //             }
614    //             Event::Suspended => {
615    //                 debug!("rs: Suspended");
616    //                 self.suspend();
617    //                 true
618    //             }
619    //             Event::LoopExiting => {
620    //                 self.runner.is_running = false;
621    //                 true
622    //             }
623    //             _ => false, // Doesn't match any of the events, some other function will need to process this event
624    //         }
625    //     } else {
626    //         // Handle cases where gpu_res is not yet initialized
627    //         match event {
628    //             Event::Resumed => {
629    //                 debug!("rs: Resumed");
630    //                 self.resume(event_loop);
631    //                 true
632    //             }
633    //             Event::LoopExiting => {
634    //                 self.runner.is_running = false;
635    //                 true
636    //             }
637    //             _ => false, // If gpu_res is None and no other relevant event, do nothing
638    //         }
639    //     }
640    // }
641
642    /// Processes events like closing, resizing etc. If it matches one of the
643    /// events, returns true
644    #[allow(clippy::too_many_lines)]
645    fn process_window_events(&mut self, event: &WindowEvent, event_loop: &ActiveEventLoop) -> bool {
646        match event {
647            WindowEvent::RedrawRequested => {
648                {
649                    let gpu_res = self.gpu_res.as_mut().unwrap();
650                    gpu_res.redraw_requested = false;
651                }
652
653                if !self.runner.do_render {
654                    return false;
655                }
656
657                debug!("gloss: render");
658
659                //frame was already started before so there is no need to do another render.
660                // Check comment in runner.frame_is_started on why this may happen
661                if self.runner.frame_is_started {
662                    debug!("the frame was already started, we are ignoring this re-render");
663                    return true;
664                }
665                self.start_frame();
666
667                match self.render() {
668                    Ok(()) => {}
669                    // Reconfigure the surface if it's lost or outdated
670                    Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
671                        self.resize(self.gpu_res.as_ref().unwrap().window.inner_size());
672                    }
673                    // The system is out of memory, we should probably quit
674                    Err(wgpu::SurfaceError::OutOfMemory) => {
675                        error!("SurfaceError: out of memory");
676                        //TODO how to best handle this?
677                    }
678                    // We're ignoring timeouts
679                    Err(wgpu::SurfaceError::Timeout) => error!("SurfaceError: timeout"),
680                }
681                debug!("finsihed handing RedrawRequested");
682                true
683            }
684            WindowEvent::Resized(physical_size) => {
685                self.resize(*physical_size);
686                true
687            }
688            // WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
689            //     // new_inner_size is &mut so w have to dereference it twice
690            //     // self.resize(**new_inner_size);
691            //     warn!("New scale factor {}", scale_factor);
692            //     true
693            // }
694            WindowEvent::DroppedFile(path_buf) => {
695                info!("Dropped file {:?}", path_buf);
696                let path = path_buf.to_str().unwrap();
697
698                //do it so that we process the events we accumulated and actually get a proper
699                // mouse position for egui
700                self.render().ok();
701
702                //Gui tries to handle the drop file event
703                #[allow(unused_mut)]
704                #[cfg(feature = "with-gui")]
705                {
706                    let gpu_res = self.gpu_res.as_mut().unwrap();
707                    let gui = gpu_res.gui.as_mut().unwrap();
708                    if gui.is_hovering() {
709                        gui.on_drop(path_buf, &mut self.scene);
710                        return true;
711                    }
712                }
713
714                //Gloss tries to handle the drop file event
715                let filetype = match path_buf.extension() {
716                    Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
717                    None => FileType::Unknown,
718                };
719                match filetype {
720                    FileType::Obj | FileType::Ply => {
721                        let builder = Geom::build_from_file(path);
722                        let name = self.scene.get_unused_name();
723                        self.scene.get_or_create_entity(&name).insert_builder(builder);
724                        return true;
725                    }
726                    FileType::Unknown => {
727                        info!(
728                            "Gloss doesn't know how to handle dropped file {:?}. trying to let plugins handle it",
729                            path
730                        );
731                    }
732                }
733
734                //try the plugins to see if they have an event for dropped file
735                let event = crate::plugin_manager::Event::DroppedFile(RString::from(path));
736                let handled = self.plugins.try_handle_event(&mut self.scene, &mut self.runner, &event);
737
738                if !handled {
739                    info!("Neither Gloss nor any of the plugin could load the dropped file {:?}", path);
740                    return false;
741                }
742
743                true
744            }
745            WindowEvent::KeyboardInput { event, .. } => {
746                if let PhysicalKey::Code(code) = event.physical_key {
747                    if event.state == ElementState::Pressed && !event.repeat {
748                        #[allow(clippy::single_match)] //we might add more keys afterwards
749                        match code {
750                            KeyCode::KeyH => {
751                                //disable gui
752                                #[cfg(feature = "with-gui")]
753                                {
754                                    if let Some(gpu_res) = self.gpu_res.as_mut() {
755                                        if let Some(gui) = gpu_res.gui.as_mut() {
756                                            gui.hidden = !gui.hidden;
757                                            gpu_res.request_redraw();
758                                        }
759                                    }
760                                }
761                            }
762                            _ => {}
763                        }
764                    }
765                }
766                true
767            }
768
769            WindowEvent::CloseRequested {} | WindowEvent::Destroyed {} => {
770                event_loop.exit();
771                true
772            }
773
774            WindowEvent::Occluded(_) => {
775                self.camera.reset_all_touch_presses(&mut self.scene);
776                true
777            }
778            _ => false, //doesn't match any of the events, some other function will need to process this event
779        }
780    }
781
782    /// Processes events like mouse drag, scroll etc. If it matches one of the
783    /// events, returns true
784    #[allow(clippy::cast_possible_truncation)]
785    fn process_input_events(&mut self, event: &WindowEvent) -> bool {
786        //camera has not yet been initialized so there is nothing to do
787        if !self.camera.is_initialized(&self.scene) {
788            return false;
789        }
790
791        let gpu_res = self.gpu_res.as_mut().unwrap();
792
793        let consumed = match event {
794            WindowEvent::MouseInput { button, state, .. } => {
795                // self.camera.mouse_pressed = *state == ElementState::Pressed;
796                if *state == ElementState::Pressed {
797                    self.camera.mouse_pressed(button, &mut self.scene);
798                } else {
799                    self.camera.mouse_released(&mut self.scene);
800                }
801                true
802            }
803            WindowEvent::MouseWheel { delta, .. } => {
804                self.camera.process_mouse_scroll(delta, &mut self.scene);
805                true
806            }
807            WindowEvent::CursorMoved { position, .. } => {
808                self.camera.process_mouse_move(
809                    position.x as f32,
810                    position.y as f32,
811                    self.window_size.width,
812                    self.window_size.height,
813                    &mut self.scene,
814                );
815                true
816            }
817            WindowEvent::Touch(touch) => {
818                if touch.phase == TouchPhase::Started {
819                    self.camera.touch_pressed(touch, &mut self.scene);
820                }
821                if touch.phase == TouchPhase::Ended || touch.phase == TouchPhase::Cancelled {
822                    self.camera.touch_released(touch, &mut self.scene);
823                }
824                if touch.phase == TouchPhase::Moved {
825                    self.camera
826                        .process_touch_move(touch, self.window_size.width, self.window_size.height, &mut self.scene);
827                }
828                true
829            }
830            _ => false, //doesn't match any of the events, some other function will need to process this event
831        };
832
833        if consumed {
834            gpu_res.request_redraw();
835        }
836
837        consumed
838    }
839
840    #[cfg(feature = "with-gui")]
841    fn process_gui_events(&mut self, event: &WindowEvent) -> EventResponse {
842        let gpu_res = self.gpu_res.as_mut().unwrap();
843        //the gui need to take window as reference. To make the borrow checker happy we
844        // take ownership of the gui
845        if let Some(mut gui) = gpu_res.gui.take() {
846            let response = gui.on_event(&gpu_res.window, event);
847            gpu_res.gui = Some(gui); //put the gui back
848            response
849        } else {
850            EventResponse {
851                repaint: false,
852                consumed: false,
853            }
854        }
855    }
856
857    /// Processes all events from the event loop. These are all `WindowEvents`
858    fn process_all_events(&mut self, event: &Event<CustomEvent>, event_loop: &ActiveEventLoop) {
859        if !self.runner.is_running {
860            // If we receive a draw event and the loop isn't running we basically ignore it
861            // but we have to notify that we have no more draw event queued
862            if let Event::WindowEvent { ref event, window_id: _ } = event {
863                if event == &WindowEvent::RedrawRequested {
864                    //dropping request redraw event
865                    let gpu_res = self.gpu_res.as_mut().unwrap();
866                    gpu_res.redraw_requested = false;
867                }
868            }
869            //even if the event loop isn't running we still want to respond to resizes of the canvas because missing this event means that when we actually start the event loop we will render at the wrong size
870            if let Event::WindowEvent {
871                event: WindowEvent::Resized(physical_size),
872                window_id: _,
873            } = event
874            {
875                self.resize(*physical_size);
876            }
877
878            return; //TODO wait for like 1ms or something
879        }
880
881        // self.process_loop_events(event, event_loop);
882
883        //TODO technically we don't need to check the rest of if we already processed
884        // the event as a custom event or a loop event
885
886        match event {
887            Event::WindowEvent { ref event, window_id } if *window_id == self.gpu_res.as_ref().unwrap().window.id() => {
888                // Now we start doing rendering stuff but we skip them if we don't have yet a
889                // correct size for rendering, this can happen inf the wasm canvas wasn't
890                // initialized yet which can happen if the user moves to a different tab before
891                // the viewer is fully initialized
892                if self.window_size.height < 16 || self.window_size.width < 16 {
893                    warn!("Skipping rendering and trying again to resize. Window size is {:?}", self.window_size);
894                    // If we are on wasm we try to reget the canvas size and request another redraw
895                    // and hope that now the canvas is initialized and gives us a proper size
896                    #[cfg(target_arch = "wasm32")]
897                    self.resize_to_canvas();
898                    let gpu_res = self.gpu_res.as_mut().unwrap();
899                    gpu_res.request_redraw();
900                    return;
901                }
902
903                self.process_window_events(event, event_loop); //process this first because it does resizing
904                cfg_if::cfg_if! {
905                    if #[cfg(feature = "with-gui")]{
906                        let res = self.process_gui_events(event);
907                        //if the gui consumed the event then we don't pass it to the rest of the input pipeline
908                        if res.consumed {
909                            self.gpu_res.as_mut().unwrap().request_redraw();
910                        } else {
911                            self.process_input_events(event);
912                        }
913                        //HACK his is a hack to deal with the fact that clicking on the egui gizmos triggers a mouse press on the camera and then it gets locked there.
914                        // if self.gpu_res.as_ref().unwrap().gui.wants_pointer_input() {
915                        if let Some(ref gui) = self.gpu_res.as_mut().unwrap().gui {
916                            if gui.wants_pointer_input() {
917                                self.camera.mouse_released(&mut self.scene);
918                            }
919                        }
920                    }else{ //if we don't have a gui, we just process input events
921                        self.process_input_events(event);
922                    }
923                }
924            }
925            _ => {}
926        }
927    }
928
929    /// Processes one iteration of the event loop. Useful when running the loop
930    /// manually using [`Viewer::update`]
931    #[allow(unused)]
932    #[cfg(not(target_arch = "wasm32"))] //wasm cannot compile the run_return() call so we just disable this whole
933                                        // function
934    fn event_loop_one_iter(&mut self) {
935        // remove the eventloop from self
936        // We remove this so that we have ownership over it.
937        // https://github.com/bevyengine/bevy/blob/eb485b1acc619baaae88d5daca0a311b95886281/crates/bevy_winit/src/lib.rs#L299
938        // https://users.rust-lang.org/t/wrapping-a-winit-window-with-a-struct/40750/6
939
940        use winit::platform::pump_events::EventLoopExtPumpEvents;
941        let mut event_loop = self.runner.event_loop.take().unwrap();
942        self.runner.is_running = true;
943        self.runner.do_render = false; //if we use run_return we don't do the rendering in this event processing and
944                                       // we rather do it manually
945        let timeout = Some(Duration::ZERO);
946        event_loop.pump_app_events(timeout, self);
947        // self.process_all_events(event, event_loop);
948        // event_loop.pump_events(timeout, |event, event_loop| {
949        //     self.process_all_events(&event, event_loop);
950        // });
951
952        self.runner.is_running = false;
953
954        //put the event loop back into self
955        self.runner.event_loop = Some(event_loop);
956    }
957
958    //called at the beggining of the render and sets the time that all systems will
959    // use
960    pub fn start_frame(&mut self) -> Duration {
961        // info!("rs: start frame");
962        #[cfg(not(target_arch = "wasm32"))]
963        {
964            if self.gpu_res.is_none() {
965                self.event_loop_one_iter(); // TODO: ?
966            }
967            assert!(self.gpu_res.is_some(), "GPU Res has not been created!");
968        }
969        // First time we call this we do a warmup render to initialize everything
970        if !self.runner.did_warmup {
971            self.runner.did_warmup = true; //has to be put here because warmup actually calls start_frame and we don't
972                                           // want an infinite recurrsion
973            self.warmup();
974            self.warmup(); // TODO: for testing
975        }
976
977        self.runner.update_dt();
978        debug!("after update dt it is {:?}", self.runner.dt());
979        self.runner.time_last_frame = Instant::now();
980
981        self.runner.frame_is_started = true;
982
983        self.runner.dt
984    }
985
986    /// # Panics
987    /// Will panic if the `gpu_resources` have not been created
988    /// # Errors
989    /// Will return error if the surface texture cannot be adquired
990    pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
991        if !self.runner.frame_is_started {
992            error!("The frame was not started so this might contain stale dt. Please use viewer.start_frame() before doing a v.render()");
993        }
994
995        //we actually take the init time as being the fist time we render, otherwise
996        // the init time would be higher since some other processing might happen
997        // between creating the viewer and actually rendering with it
998        if self.runner.first_time {
999            self.runner.time_init = Instant::now();
1000        }
1001
1002        let gpu_res = self.gpu_res.as_mut().unwrap();
1003        self.plugins.run_logic_systems(gpu_res, &mut self.scene, &mut self.runner, true);
1004
1005        //get surface texture (which can fail and return an SurfaceError)
1006        let output = gpu_res.surface.get_current_texture()?;
1007        let out_view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
1008        let out_width = output.texture.width();
1009        let out_height = output.texture.height();
1010
1011        //render to_texture of the size of the surface
1012        let dt = self.runner.dt();
1013
1014        self.camera.on_window_resize(out_width, out_height, &mut self.scene);
1015
1016        //TODO return the textured final so we can just plug it into blit pass without
1017        // doing renderer.rendered_tex
1018        gpu_res.renderer.render_to_view(
1019            &out_view,
1020            // out_width,
1021            // out_height,
1022            &gpu_res.gpu,
1023            &mut self.camera,
1024            &mut self.scene,
1025            &mut self.config,
1026            dt,
1027        );
1028
1029        //render gui
1030        //TODO pass the whole renderer and the scene so we can do gui stuff on them
1031        #[cfg(feature = "with-gui")]
1032        if let Some(ref mut gui) = gpu_res.gui {
1033            gui.render(
1034                &gpu_res.window,
1035                &gpu_res.gpu,
1036                &gpu_res.renderer,
1037                &self.runner,
1038                &mut self.scene,
1039                &self.plugins,
1040                &mut self.config,
1041                &out_view,
1042            );
1043        }
1044
1045        //swap
1046        output.present();
1047
1048        self.runner.first_time = false;
1049        self.runner.frame_is_started = false;
1050
1051        Ok(())
1052    }
1053
1054    /// # Panics
1055    /// Will panic if the `gpu_resources` have not been created
1056    /// # Errors
1057    /// Will return error if the surface texture cannot be adquired
1058    pub fn render_to_texture(&mut self) -> Result<(), wgpu::SurfaceError> {
1059        if !self.runner.frame_is_started {
1060            error!("The frame was not started so this might contain stale dt. Please use viewer.start_frame() before doing a v.render_to_texture()");
1061        }
1062
1063        //we actually take the init time as being the fist time we render, otherwise
1064        // the init time would be higher since some other processing might happen
1065        // between creating the viewer and actually rendering with it
1066        if self.runner.first_time {
1067            self.runner.time_init = Instant::now();
1068        }
1069
1070        let gpu_res = self.gpu_res.as_mut().unwrap();
1071        self.plugins.run_logic_systems(gpu_res, &mut self.scene, &mut self.runner, true);
1072
1073        //get surface texture (which can fail and return an SurfaceError)
1074        // let output = gpu_res.surface.get_current_texture()?;
1075        let out_tex = gpu_res.renderer.rendered_tex();
1076        let out_width = out_tex.width();
1077        let out_height = out_tex.height();
1078
1079        //render to_texture of the size of the surface
1080        let dt = self.runner.dt();
1081
1082        self.camera.on_window_resize(out_width, out_height, &mut self.scene);
1083
1084        //TODO return the textured final so we can just plug it into blit pass without
1085        // doing renderer.rendered_tex
1086        gpu_res
1087            .renderer
1088            .render_to_texture(&gpu_res.gpu, &mut self.camera, &mut self.scene, &mut self.config, dt);
1089
1090        //render gui
1091        //TODO pass the whole renderer and the scene so we can do gui stuff on them
1092        #[cfg(feature = "with-gui")]
1093        if let Some(ref mut gui) = gpu_res.gui {
1094            let out_view = &gpu_res.renderer.rendered_tex().view;
1095            gui.render(
1096                &gpu_res.window,
1097                &gpu_res.gpu,
1098                &gpu_res.renderer,
1099                &self.runner,
1100                &mut self.scene,
1101                &self.plugins,
1102                &mut self.config,
1103                out_view,
1104            );
1105        }
1106
1107        self.runner.first_time = false;
1108        self.runner.frame_is_started = false;
1109
1110        Ok(())
1111    }
1112
1113    /// Runs the rendering loop automatically. This function does not return as
1114    /// it will take full control of the loop. If you need to still have control
1115    /// over the loop use [`Viewer::update`]
1116    // #[allow(clippy::missing_panics_doc)]
1117    // pub fn run(mut self) {
1118    //     // pub fn run(&'static mut self) {
1119    //     // has to take self by value because this
1120    //     // https://www.reddit.com/r/rust/comments/cgdhzb/newbie_question_how_do_i_avoid_breaking_apart_my/
1121    //     // https://users.rust-lang.org/t/cannot-move-out-of-self-event-loop-which-is-behind-a-mutable-reference/67363/6
1122    //     // https://stackoverflow.com/questions/76577042/why-do-i-get-a-borrowed-data-escapes-outside-of-method-error
1123    //     // remove the eventloop from self
1124    //     // We remove this so that we have ownership over it.
1125    //     // https://github.com/bevyengine/bevy/blob/eb485b1acc619baaae88d5daca0a311b95886281/crates/bevy_winit/src/lib.rs#L299
1126    //     // https://users.rust-lang.org/t/wrapping-a-winit-window-with-a-struct/40750/6
1127
1128    //     let event_loop = self.runner.event_loop.take().unwrap();
1129    //     self.runner.is_running = self.runner.autostart;
1130
1131    //     // let _ = event_loop.run_app(&mut self);
1132
1133    //     cfg_if::cfg_if! {
1134    //         if #[cfg(not(target_arch = "wasm32"))]{
1135    //             let _ = event_loop.run_app(&mut self);
1136    //         }else{
1137    //             let _ = event_loop.spawn_app(self);
1138    //         }
1139    //     }
1140    // }
1141    #[cfg(not(target_arch = "wasm32"))]
1142    pub fn run(&mut self) {
1143        let event_loop = self.runner.event_loop.take().unwrap();
1144        self.runner.is_running = self.runner.autostart;
1145        let _ = event_loop.run_app(self);
1146    }
1147
1148    #[cfg(target_arch = "wasm32")]
1149    pub fn run(mut self) {
1150        let event_loop = self.runner.event_loop.take().unwrap();
1151        self.runner.is_running = self.runner.autostart;
1152        let _ = event_loop.spawn_app(self);
1153    }
1154
1155    /// Same as the run function but uses a static reference to the ``Viewer``.
1156    /// Useful for web apps when the viewer is a static member.
1157    #[allow(clippy::missing_panics_doc)]
1158    #[allow(unreachable_code)] //the lines after the event loop are actually ran on wasm
1159    pub fn run_static_ref(&'static mut self) {
1160        let event_loop = self.runner.event_loop.take().unwrap();
1161        self.runner.is_running = self.runner.autostart;
1162
1163        // let _ = event_loop.run_app(self);
1164        cfg_if::cfg_if! {
1165            if #[cfg(not(target_arch = "wasm32"))]{
1166                let _ = event_loop.run_app(self);
1167            }else{
1168                let _ = event_loop.spawn_app(self);
1169            }
1170        }
1171    }
1172
1173    /// if the event loop is running we can call this to destroy everything and
1174    /// be able to call ``viewer.run()`` again and potentially connect to a new
1175    /// canvas
1176    #[allow(clippy::missing_panics_doc)]
1177    pub fn recreate_event_loop(&mut self) {
1178        self.stop_event_loop();
1179        self.suspend(); //need to destroy window because we potentially are connecting to new canvas
1180        let runner = Runner::new(&self.canvas_id_parsed);
1181        self.runner = runner;
1182        // self.resume(runner.event_loop.as_ref().unwrap());
1183        let event_stop = CustomEvent::ResumeLoop;
1184        self.runner.event_loop_proxy.send_event(event_stop).ok();
1185    }
1186
1187    pub fn stop_event_loop(&self) {
1188        let event_stop = CustomEvent::StopLoop;
1189        self.runner.event_loop_proxy.send_event(event_stop).ok();
1190    }
1191
1192    fn resume(&mut self, event_loop: &ActiveEventLoop) {
1193        // info!("RS: resume");
1194        self.runner.is_running = self.runner.autostart;
1195        if self.gpu_res.is_none() {
1196            self.gpu_res = Some(GpuResources::new(
1197                event_loop,
1198                &self.runner.event_loop_proxy,
1199                &self.canvas_id_parsed.clone(),
1200                &self.config,
1201            ));
1202            // We set all the components to changed because we want them to be reuploaded to
1203            // gpu. Don't set them as added because some systems rely on components being
1204            // added to run only once.
1205            self.scene.world.set_trackers_changed(); //TODO set it to changed, and push hecs to github
1206
1207            // self.gpu_res.as_mut().unwrap().window.request_redraw();
1208            self.gpu_res.as_mut().unwrap().request_redraw();
1209        }
1210        #[cfg(target_arch = "wasm32")]
1211        self.resize_to_canvas()
1212    }
1213
1214    pub fn suspend(&mut self) {
1215        // Drop surface and anything to do with the gl context since it's probable that
1216        // we lost it https://docs.rs/winit/latest/winit/event/enum.Event.html
1217        info!("RS: suspend");
1218        self.runner.is_running = false;
1219        self.scene.remove_all_gpu_components();
1220        self.gpu_res.take();
1221        self.camera.reset_all_touch_presses(&mut self.scene);
1222    }
1223
1224    // First time we render will take longer since we have to possibly load a lot of
1225    // data, textures, smpl_models etc, This can cause the animation time for the
1226    // second frame to be very long which will look as if we skipped part of the
1227    // animation. Therefore we do a warmup at the first rendered frame
1228    pub fn warmup(&mut self) {
1229        debug!("Starting warmup");
1230        // self.resume(&self.runner.event_loop);
1231        self.start_frame();
1232        self.run_manual_plugins(); //auto plugins will run when we do self.render(), but here we also need to run
1233                                   // the manual ones
1234
1235        // We cannot do update() because update requires .take() of the event loop. But
1236        // the event loop is already running so the .take() will fails
1237        let _ = self.render();
1238        self.reset_for_first_time();
1239        debug!("finished warmup");
1240    }
1241
1242    pub fn reset_for_first_time(&mut self) {
1243        self.runner.first_time = true;
1244    }
1245
1246    pub fn add_logic_system(&mut self, sys: LogicSystem) {
1247        self.plugins.logic_systems.push(Tuple2(sys, SystemMetadata::default()));
1248    }
1249
1250    #[cfg(feature = "with-gui")]
1251    pub fn add_gui_system(&mut self, sys: GuiSystem) {
1252        self.plugins.gui_systems.push(Tuple2(sys, SystemMetadata::default()));
1253    }
1254
1255    #[allow(clippy::missing_panics_doc)]
1256    pub fn run_manual_plugins(&mut self) {
1257        {
1258            // if self.gpu_res.is_some() {
1259            let gpu_res = self.gpu_res.as_mut().unwrap();
1260            self.plugins.run_logic_systems(gpu_res, &mut self.scene, &mut self.runner, false);
1261            // }
1262        }
1263    }
1264
1265    pub fn insert_plugin<T: Plugin + 'static>(&mut self, plugin: &T) {
1266        self.plugins.insert_plugin(plugin);
1267    }
1268
1269    /// # Panics
1270    /// Will panic if the `gpu_resources` have not been created
1271    pub fn wait_gpu_finish(&self) -> wgpu::MaintainResult {
1272        self.gpu_res.as_ref().unwrap().gpu.device().poll(wgpu::Maintain::Wait)
1273    }
1274
1275    ////////////////////////////////////////////////////////////////////////////
1276
1277    fn create_window(
1278        event_loop: &ActiveEventLoop,
1279        _event_loop_proxy: &EventLoopProxy<CustomEvent>,
1280        _canvas_id: &Option<String>,
1281    ) -> Result<Window, Box<dyn Error>> {
1282        // TODO read-out activation token.
1283
1284        #[allow(unused_mut)]
1285        let mut window_attributes = Window::default_attributes().with_title("Gloss").with_maximized(true);
1286
1287        // #[cfg(web_platform)]
1288        #[cfg(target_arch = "wasm32")]
1289        {
1290            use winit::platform::web::WindowAttributesExtWebSys;
1291            let window = web_sys::window().unwrap();
1292            let document = window.document().unwrap();
1293            let canvas = document
1294                .query_selector(&_canvas_id.as_ref().unwrap())
1295                .expect("Cannot query for canvas element.");
1296            if let Some(canvas) = canvas {
1297                let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok();
1298
1299                //prevent_default prevents the window for getting web keyboard events like F12 and Ctrl+r https://github.com/rust-windowing/winit/issues/1768
1300                window_attributes = window_attributes.with_canvas(canvas).with_prevent_default(false).with_append(false)
1301            } else {
1302                panic!("Cannot find element: {:?}.", _canvas_id.as_ref().unwrap());
1303            }
1304        }
1305
1306        let window = event_loop.create_window(window_attributes)?;
1307
1308        Ok(window)
1309    }
1310
1311    pub fn override_dt(&mut self, new_dt: f32) {
1312        self.runner.override_dt(new_dt);
1313    }
1314
1315    pub fn get_final_tex(&self) -> &Texture {
1316        let tex = self.gpu_res.as_ref().unwrap().renderer.rendered_tex();
1317        tex
1318    }
1319
1320    pub fn get_final_depth(&self) -> &Texture {
1321        let depth = self.gpu_res.as_ref().unwrap().renderer.depth_buffer();
1322        depth
1323    }
1324}
1325
1326impl ApplicationHandler<CustomEvent> for Viewer {
1327    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
1328        self.resume(event_loop);
1329    }
1330
1331    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvent) {
1332        self.process_custom_context_event(&Event::UserEvent(event), event_loop);
1333        self.process_custom_resize_events(&Event::UserEvent(event));
1334        self.process_custom_other_event(&Event::UserEvent(event), event_loop);
1335    }
1336    fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
1337        if window_id != self.gpu_res.as_ref().unwrap().window.id() {
1338            return;
1339        }
1340        self.process_all_events(&Event::WindowEvent { window_id, event }, event_loop);
1341    }
1342    #[allow(unused_variables)]
1343    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
1344        #[cfg(not(target_arch = "wasm32"))]
1345        {
1346            if let Some(gpu_res) = self.gpu_res.as_mut() {
1347                gpu_res.request_redraw();
1348            }
1349        }
1350    }
1351
1352    fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
1353        debug!("Handling Suspended event");
1354        self.suspend();
1355    }
1356
1357    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
1358        self.runner.is_running = false;
1359        event_loop.exit();
1360    }
1361}
1362
1363/// We target Vulkan for native and WebGL for wasm. We only use Vulkan for
1364/// native because that allows us to use the `PyTorch` and wgpu interoperability
1365pub fn supported_backends() -> wgpu::Backends {
1366    if cfg!(target_arch = "wasm32") {
1367        // Web - WebGL is used automatically when wgpu is compiled with `webgl` feature.
1368        wgpu::Backends::GL
1369    } else {
1370        // For Native
1371        wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::VULKAN | wgpu::Backends::METAL)
1372    }
1373}
1374
1375//get the adapter with the following priorities
1376//if the WGPU_VISIBLE_DEVICES is set get the adaptor with that id, if not get the adaptor from CUDA_VISIBLE_DEVICES and finally if none are set just get whatever wgpu wants
1377pub fn get_adapter(instance: &wgpu::Instance, surface: Option<&wgpu::Surface>) -> wgpu::Adapter {
1378    #[cfg(not(target_arch = "wasm32"))]
1379    fn remove_from_vec(vec: &mut Vec<wgpu::Adapter>, idx_str: &str) -> wgpu::Adapter {
1380        let idx = idx_str.split(',').next().unwrap().parse::<usize>().unwrap(); //in the case we pass multiple indexes to CUDA_VISIBLE_DEVICES=0,1,2 we use the first index
1381
1382        assert!(
1383            (0..vec.len()).contains(&idx),
1384            "Tried to index device with idx {} but we only have detected {} devices",
1385            idx,
1386            vec.len()
1387        );
1388
1389        info!("Selecting adapter with idx {}", idx);
1390        vec.remove(idx)
1391    }
1392
1393    cfg_if::cfg_if! {
1394        if #[cfg(target_arch = "wasm32")]{
1395            instance
1396                .request_adapter(&wgpu::RequestAdapterOptions {
1397                    power_preference: wgpu::PowerPreference::HighPerformance,
1398                    compatible_surface: surface,
1399                    force_fallback_adapter: false,
1400                })
1401                .block_on()
1402                .expect("An adapter could not be found. Maybe there's a driver issue on your machine?")
1403        }else {
1404            let mut adapters = enumerate_adapters(instance);
1405
1406            let wgpu_dev_id = std::env::var("WGPU_VISIBLE_DEVICES");
1407            let cuda_dev_id = std::env::var("CUDA_VISIBLE_DEVICES");
1408            match wgpu_dev_id {
1409                Ok(idx) => remove_from_vec(&mut adapters, &idx),
1410                Err(_) => match cuda_dev_id {
1411                    Ok(idx) => remove_from_vec(&mut adapters, &idx),
1412                    Err(_) => instance
1413                        .request_adapter(&wgpu::RequestAdapterOptions {
1414                            power_preference: wgpu::PowerPreference::HighPerformance,
1415                            compatible_surface: surface,
1416                            force_fallback_adapter: false,
1417                        })
1418                        .block_on()
1419                        .expect("An adapter could not be found. Maybe there's a driver issue on your machine?"),
1420                },
1421            }
1422        }
1423    }
1424}
1425
1426//sort adapter so that the GPU ones come first, we don't want the llvmp pipe one (cpu) being somewhere in the middle since it messes with the index based selection
1427//despite the sorting, we cannot guarantee that the order of adapters is the same as nvidia-smi. In order to find out which adapter is which we probably need to resort to trial-and-and error by starting Gloss on multiple GPUs and check which nvidia-smi on which one it's running
1428//for more info check: https://github.com/pygfx/wgpu-py/issues/482
1429#[cfg(not(target_arch = "wasm32"))]
1430pub fn enumerate_adapters(instance: &wgpu::Instance) -> Vec<wgpu::Adapter> {
1431    let mut adapters = instance.enumerate_adapters(wgpu::Backends::all());
1432
1433    //this sorts by the distance of device type to the discretegpu, since the sort is stable the devices that are discrete gpus (distance=0) are put first
1434    adapters.sort_by_key(|x| (x.get_info().device_type as i32 - wgpu::DeviceType::DiscreteGpu as i32).abs());
1435
1436    adapters
1437}
1438
1439/// Custom resize event emited by canvas resizes on the web.
1440#[derive(Debug, Clone, Copy)]
1441pub enum CustomEvent {
1442    Resize(f32, f32),
1443    ContextLost,
1444    ContextRestored,
1445    ResumeLoop,
1446    StopLoop,
1447}