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