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