gloss_renderer/
viewer_headless.rs

1cfg_if::cfg_if! {
2    if #[cfg(not(target_arch = "wasm32"))] {
3        use crate::components::{TargetResolution, TargetResolutionUpdate};
4        use crate::config::Config;
5        use crate::forward_renderer::renderer::Renderer;
6        use crate::logger::gloss_setup_logger_from_config;
7        use crate::plugin_manager::plugins::{Plugin, Plugins};
8        use crate::scene::Scene;
9        use crate::set_panic_hook;
10        use crate::viewer::supported_backends;
11        use crate::{camera::Camera, scene::GLOSS_CAM_NAME};
12
13        use easy_wgpu::gpu::Gpu;
14        use easy_wgpu::texture::Texture;
15
16        use log::debug;
17        use pollster::FutureExt;
18    }
19}
20// #[cfg(target_arch = "wasm32")]
21// use winit::platform::web::EventLoopExtWebSys;
22
23use core::time::Duration;
24#[allow(unused_imports)]
25use log::{error, info, Level};
26
27#[cfg(target_arch = "wasm32")]
28#[allow(unused_imports)]
29use wasm_bindgen::prelude::*;
30use wasm_timer::Instant;
31
32#[cfg(not(target_arch = "wasm32"))]
33use crate::viewer::enumerate_adapters;
34#[cfg(not(target_arch = "wasm32"))]
35use crate::viewer::get_adapter;
36
37#[derive(Debug)]
38#[repr(C)]
39pub struct RunnerHeadless {
40    pub is_running: bool,
41    pub do_render: bool,
42    pub first_time: bool,
43    pub did_warmup: bool,
44    frame_is_started: bool, /* after calling viewer.start_frame() this gets set to true. any subsequent cals to start frame will be ignored if
45                             * this frame has already been started. This can happen when using python and doing viewer.update() this will run some
46                             * system that does a window.request_redraw which will cause another rerender but this frame has already started so we
47                             * don't need to do anything since we are already in the middle of redrawing */
48    time_init: Instant,       //time when the init of the viewer has finished
49    time_last_frame: Instant, //time when we started the previous frame. So the instant when we called viewer.start_frame()
50    dt: Duration,             /* delta time since we finished last frame, we store it directly here instead of constantly querying it because
51                               * different systems might then get different dt depending on how long they take to run. This dt is set once when
52                               * doing viewer.start_frame() */
53}
54impl Default for RunnerHeadless {
55    fn default() -> Self {
56        let time_init = Instant::now();
57        let time_last_frame = Instant::now();
58        Self {
59            is_running: false,
60            do_render: true,
61            first_time: true,
62            did_warmup: false,
63            frame_is_started: false,
64            time_init,
65            time_last_frame,
66            dt: Duration::ZERO,
67        }
68    }
69}
70#[allow(unused)]
71impl RunnerHeadless {
72    pub fn time_since_init(&self) -> Duration {
73        if self.first_time {
74            Duration::ZERO
75        } else {
76            self.time_init.elapsed()
77        }
78    }
79    pub fn update_dt(&mut self) {
80        if self.first_time {
81            self.dt = Duration::ZERO;
82        } else {
83            self.dt = self.time_last_frame.elapsed();
84        }
85    }
86    pub fn dt(&self) -> Duration {
87        self.dt
88    }
89}
90
91/// `ViewerHeadless` performs similarly as [Viewer] with the difference that it
92/// can be used when rendering on headless machines. A typical usage is
93/// rendering an image using [`ViewerHeadless::update`], recovering the texture
94/// using [`ViewerHeadless::get_final_tex`], getting it to CPU using
95/// [`Texture::download_to_cpu`] and finally write it to disk using
96/// [`ImageBuffer::save`]
97#[cfg(not(target_arch = "wasm32"))] //wasm cannot compile the run_return() call so we just disable the whole
98                                    // headless viewer
99pub struct ViewerHeadless {
100    // have the renderer separate
101    pub renderer: Renderer,
102    pub camera: Camera,
103    pub scene: Scene,
104    pub plugins: Plugins,
105    pub config: Config,
106    pub runner: RunnerHeadless,
107    // The order of properties in a struct is the order in which items are dropped.
108    // wgpu seems to require that the device be dropped last, otherwise there is a resouce
109    // leak.
110    pub gpu: Gpu,
111}
112
113#[cfg(not(target_arch = "wasm32"))]
114impl ViewerHeadless {
115    pub fn new(width: u32, height: u32, config_path: Option<&str>) -> Self {
116        let config = Config::new(config_path);
117        Self::new_with_config(width, height, &config)
118    }
119
120    #[allow(clippy::too_many_lines)]
121    #[allow(clippy::missing_panics_doc)]
122    pub fn new_with_config(width: u32, height: u32, config: &Config) -> Self {
123        set_panic_hook();
124        if config.core.auto_create_logger {
125            gloss_setup_logger_from_config(config);
126        }
127
128        //expensive but useful
129        re_memory::accounting_allocator::set_tracking_callstacks(config.core.enable_memory_profiling_callstacks);
130
131        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
132            backends: supported_backends(),
133            dx12_shader_compiler: wgpu::Dx12Compiler::default(),
134            flags: wgpu::InstanceFlags::default(),
135            gles_minor_version: wgpu::Gles3MinorVersion::Automatic,
136        });
137
138        //if we are on wasm we cannot enumerate adapter so we skip this at compile time
139        cfg_if::cfg_if! {
140                if #[cfg(not(target_arch = "wasm32"))]{
141                let adapters = enumerate_adapters(&instance);
142                info!("Number of possible adapters: {:?}", adapters.len());
143                for (i, adapter) in adapters.iter().enumerate() {
144                    info!("Adapter option {:?}: {:?}", i + 1, adapter.get_info());
145                }
146            }
147        }
148        let adapter = get_adapter(&instance, None);
149        info!("Selected adapter: {:?}", adapter.get_info());
150
151        // info!("{:?}", adapter.get_info());
152        //TODO if the adapter is not a discrete Nvidia gpu, disable the wgpu to pytorch
153        // interop
154
155        //features
156        let mut desired_features = wgpu::Features::empty();
157        cfg_if::cfg_if! {
158            if #[cfg(not(target_arch = "wasm32"))]{
159                // println!("compiling with time query");
160                desired_features = desired_features.union(wgpu::Features::TIMESTAMP_QUERY.union(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES));
161                desired_features = desired_features.union(wgpu::Features::POLYGON_MODE_POINT);
162                desired_features = desired_features.union(wgpu::Features::POLYGON_MODE_LINE);
163            }
164        }
165        let required_features = adapter.features().intersection(desired_features); //only take the features that are actually supported
166        info!("enabled features: {required_features:?}");
167
168        //dealing with wasm putting 2048 as maximum texture size
169        //https://github.com/gfx-rs/wgpu/discussions/2952
170        let max_limits = adapter.limits();
171        #[allow(unused_mut)]
172        let mut limits_to_request = wgpu::Limits::default();
173        if cfg!(target_arch = "wasm32") {
174            limits_to_request = wgpu::Limits::downlevel_webgl2_defaults();
175        }
176        limits_to_request.max_texture_dimension_1d = max_limits.max_texture_dimension_1d;
177        limits_to_request.max_texture_dimension_2d = max_limits.max_texture_dimension_2d;
178        limits_to_request.max_buffer_size = max_limits.max_buffer_size;
179
180        let mut memory_hints = wgpu::MemoryHints::Performance;
181        if cfg!(target_arch = "wasm32") {
182            //we usually have issue with running out of memory on wasm, so I would rather
183            // optimize for low memory usage
184            memory_hints = wgpu::MemoryHints::MemoryUsage;
185        }
186
187        let (device, queue) = adapter
188            .request_device(
189                &wgpu::DeviceDescriptor {
190                    label: None,
191                    required_features,
192                    required_limits: limits_to_request,
193                    memory_hints,
194                },
195                None, // Trace path
196            )
197            .block_on()
198            .expect("A device and queue could not be created. Maybe there's a driver issue on your machine?");
199
200        let runner = RunnerHeadless::default();
201
202        let gpu = Gpu::new(adapter, instance, device, queue);
203        let mut scene = Scene::new();
204        let camera = Camera::new(GLOSS_CAM_NAME, &mut scene, false);
205        let _ = scene.world.insert_one(
206            camera.entity,
207            TargetResolution {
208                width,
209                height,
210                update_mode: TargetResolutionUpdate::Fixed,
211            },
212        );
213        let renderer = Renderer::new(&gpu, &config.render, None); //moves the device and queue into it and takes ownership of them
214
215        Self {
216            gpu,
217            renderer,
218            camera,
219            scene,
220            plugins: Plugins::new(),
221            config: config.clone(),
222            runner,
223        }
224    }
225
226    pub fn insert_plugin<T: Plugin + 'static>(&mut self, plugin: &T) {
227        self.plugins.insert_plugin(plugin);
228    }
229    #[allow(clippy::missing_panics_doc)]
230    pub fn run_manual_plugins(&mut self) {
231        self.plugins.run_logic_systems_headless(&mut self.scene, &mut self.runner, false);
232    }
233    //wasm cannot compile the run_return() call so we just disable this whole
234    // function
235    pub fn update(&mut self) {
236        self.render(None);
237    }
238
239    pub fn render_from_cam(&mut self, cam: &mut Camera) {
240        //we replace our defaul
241        self.render(Some(cam));
242    }
243
244    //called at the beggining of the render and sets the time that all systems will
245    // use
246    pub fn start_frame(&mut self) -> Duration {
247        //first time we call this we do a warmup render to initialize everything
248        if !self.runner.did_warmup {
249            self.runner.did_warmup = true; //has to be put here because warmup actually calls start_frame and we don't
250                                           // want an infinite recurrsion
251            self.warmup();
252            self.warmup();
253        }
254
255        self.runner.update_dt();
256        debug!("after update dt it is {:?}", self.runner.dt());
257        self.runner.time_last_frame = Instant::now();
258
259        self.runner.frame_is_started = true;
260
261        self.runner.dt
262    }
263
264    fn render(&mut self, other_cam: Option<&mut Camera>) {
265        if self.runner.first_time {
266            self.runner.time_init = Instant::now();
267        }
268
269        self.plugins.run_logic_systems_headless(&mut self.scene, &mut self.runner, true);
270
271        let dt = self.runner.dt();
272
273        //we render to an internal texture since we have no surface
274        // let out_view = self.renderer.rendered_tex().view;
275
276        self.renderer
277            .render_to_texture(&self.gpu, other_cam.unwrap_or(&mut self.camera), &mut self.scene, &mut self.config, dt);
278
279        self.runner.first_time = false;
280        self.runner.frame_is_started = false;
281    }
282
283    pub fn warmup(&mut self) {
284        debug!("Starting warmup");
285        self.start_frame();
286        self.run_manual_plugins(); //auto plugins will run when we do self.render(), but here we also need to run
287                                   // the manual ones
288        #[cfg(not(target_arch = "wasm32"))] //wasm cannot do update because it needs run_return
289        self.update();
290        #[cfg(target_arch = "wasm32")] //
291        self.render();
292        self.reset_for_first_time();
293        debug!("finished warmup");
294    }
295
296    pub fn reset_for_first_time(&mut self) {
297        self.runner.first_time = true;
298    }
299
300    pub fn get_final_tex(&self) -> &Texture {
301        let tex = self.renderer.rendered_tex();
302        tex
303    }
304
305    pub fn get_final_depth(&self) -> &Texture {
306        let depth = self.renderer.depth_buffer();
307        depth
308    }
309
310    pub fn set_size(&mut self, width: u32, height: u32) {
311        self.camera.set_target_res(width, height, &mut self.scene);
312    }
313}