tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

wrench.rs (22419B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 
      6 use crate::blob;
      7 use crossbeam::sync::chase_lev;
      8 #[cfg(windows)]
      9 use dwrote;
     10 #[cfg(all(unix, not(target_os = "android")))]
     11 use font_loader::system_fonts;
     12 use winit::event_loop::EventLoopProxy;
     13 use std::collections::HashMap;
     14 use std::path::{Path, PathBuf};
     15 use std::sync::{Arc, Mutex};
     16 use std::sync::mpsc::Receiver;
     17 use std::time::Instant;
     18 use webrender::{api::*, CompositorConfig};
     19 use webrender::render_api::*;
     20 use webrender::api::units::*;
     21 use webrender::{DebugFlags, RenderResults, ShaderPrecacheFlags, LayerCompositor};
     22 use crate::{WindowWrapper, NotifierEvent};
     23 
     24 #[derive(Clone)]
     25 pub struct DisplayList {
     26    pub pipeline: PipelineId,
     27    pub payload: BuiltDisplayList,
     28    /// Whether to request that the transaction is presented to the window.
     29    ///
     30    /// The transaction will be presented if it contains at least one display list
     31    /// with present set to true.
     32    pub present: bool,
     33    /// If set to true, send the transaction after adding this display list to it.
     34    pub send_transaction: bool,
     35    /// If set to true, the pipeline will be rendered off-screen. Only snapshotted
     36    /// stacking contexts will be kept.
     37    pub render_offscreen: bool,
     38 }
     39 
     40 // TODO(gw): This descriptor matches what we currently support for fonts
     41 //           but is quite a mess. We should at least document and
     42 //           use better types for things like the style and stretch.
     43 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
     44 pub enum FontDescriptor {
     45    Path { path: PathBuf, font_index: u32 },
     46    Family { name: String },
     47    Properties {
     48        family: String,
     49        weight: u32,
     50        style: u32,
     51        stretch: u32,
     52    },
     53 }
     54 
     55 struct NotifierData {
     56    events_loop_proxy: Option<EventLoopProxy<()>>,
     57    frames_notified: u32,
     58    timing_receiver: chase_lev::Stealer<std::time::Instant>,
     59    verbose: bool,
     60 }
     61 
     62 impl NotifierData {
     63    fn new(
     64        events_loop_proxy: Option<EventLoopProxy<()>>,
     65        timing_receiver: chase_lev::Stealer<std::time::Instant>,
     66        verbose: bool,
     67    ) -> Self {
     68        NotifierData {
     69            events_loop_proxy,
     70            frames_notified: 0,
     71            timing_receiver,
     72            verbose,
     73        }
     74    }
     75 }
     76 
     77 struct Notifier(Arc<Mutex<NotifierData>>);
     78 
     79 impl Notifier {
     80    fn update(&self, check_document: bool) {
     81        let mut data = self.0.lock();
     82        let data = data.as_mut().unwrap();
     83        if check_document {
     84            match data.timing_receiver.steal() {
     85                chase_lev::Steal::Data(last_timing) => {
     86                    data.frames_notified += 1;
     87                    if data.verbose && data.frames_notified == 600 {
     88                        let elapsed = Instant::now() - last_timing;
     89                        println!(
     90                            "frame latency (consider queue depth here): {:3.6} ms",
     91                            elapsed.as_secs_f64() * 1000.
     92                        );
     93                        data.frames_notified = 0;
     94                    }
     95                }
     96                _ => {
     97                    println!("Notified of frame, but no frame was ready?");
     98                }
     99            }
    100        }
    101 
    102        if let Some(ref _elp) = data.events_loop_proxy {
    103            #[cfg(not(target_os = "android"))]
    104            let _ = _elp.send_event(());
    105        }
    106    }
    107 }
    108 
    109 impl RenderNotifier for Notifier {
    110    fn clone(&self) -> Box<dyn RenderNotifier> {
    111        Box::new(Notifier(self.0.clone()))
    112    }
    113 
    114    fn wake_up(&self, _composite_needed: bool) {
    115        self.update(false);
    116    }
    117 
    118    fn new_frame_ready(&self, _: DocumentId, _: FramePublishId, params: &FrameReadyParams) {
    119        self.update(!params.scrolled);
    120    }
    121 }
    122 
    123 pub trait WrenchThing {
    124    fn next_frame(&mut self);
    125    fn prev_frame(&mut self);
    126    fn do_frame(&mut self, _: &mut Wrench) -> u32;
    127 }
    128 
    129 impl WrenchThing for CapturedDocument {
    130    fn next_frame(&mut self) {}
    131    fn prev_frame(&mut self) {}
    132    fn do_frame(&mut self, wrench: &mut Wrench) -> u32 {
    133        if let Some(root_pipeline_id) = self.root_pipeline_id.take() {
    134            // skip the first frame - to not overwrite the loaded one
    135            let mut txn = Transaction::new();
    136            txn.set_root_pipeline(root_pipeline_id);
    137            wrench.api.send_transaction(self.document_id, txn);
    138        } else {
    139            wrench.refresh();
    140        }
    141        0
    142    }
    143 }
    144 
    145 pub struct CapturedSequence {
    146    root: PathBuf,
    147    frame: usize,
    148    frame_set: Vec<(u32, u32)>,
    149 }
    150 
    151 impl CapturedSequence {
    152    pub fn new(root: PathBuf, scene_start: u32, frame_start: u32) -> Self {
    153        // Build set of a scene and frame IDs.
    154        let mut scene = scene_start;
    155        let mut frame = frame_start;
    156        let mut frame_set = Vec::new();
    157        while Self::scene_root(&root, scene).as_path().is_dir() {
    158            while Self::frame_root(&root, scene, frame).as_path().is_dir() {
    159                frame_set.push((scene, frame));
    160                frame += 1;
    161            }
    162            scene += 1;
    163            frame = 1;
    164        }
    165 
    166        assert!(!frame_set.is_empty());
    167 
    168        Self {
    169            root,
    170            frame: 0,
    171            frame_set,
    172        }
    173    }
    174 
    175    fn scene_root(root: &Path, scene: u32) -> PathBuf {
    176        let path = format!("scenes/{:05}", scene);
    177        root.join(path)
    178    }
    179 
    180    fn frame_root(root: &Path, scene: u32, frame: u32) -> PathBuf {
    181        let path = format!("scenes/{:05}/frames/{:05}", scene, frame);
    182        root.join(path)
    183    }
    184 }
    185 
    186 impl WrenchThing for CapturedSequence {
    187    fn next_frame(&mut self) {
    188        if self.frame + 1 < self.frame_set.len() {
    189            self.frame += 1;
    190        }
    191    }
    192 
    193    fn prev_frame(&mut self) {
    194        if self.frame > 0 {
    195            self.frame -= 1;
    196        }
    197    }
    198 
    199    fn do_frame(&mut self, wrench: &mut Wrench) -> u32 {
    200        let mut documents = wrench.api.load_capture(self.root.clone(), Some(self.frame_set[self.frame]));
    201        println!("loaded {:?} from {:?}",
    202            documents.iter().map(|cd| cd.document_id).collect::<Vec<_>>(),
    203            self.frame_set[self.frame]);
    204        let captured = documents.swap_remove(0);
    205        wrench.document_id = captured.document_id;
    206        self.frame as u32
    207    }
    208 }
    209 
    210 pub struct Wrench {
    211    window_size: DeviceIntSize,
    212 
    213    pub renderer: webrender::Renderer,
    214    pub api: RenderApi,
    215    pub document_id: DocumentId,
    216    pub root_pipeline_id: PipelineId,
    217 
    218    window_title_to_set: Option<String>,
    219 
    220    graphics_api: webrender::GraphicsApiInfo,
    221 
    222    pub rebuild_display_lists: bool,
    223 
    224    pub frame_start_sender: chase_lev::Worker<Instant>,
    225 
    226    pub callbacks: Arc<Mutex<blob::BlobCallbacks>>,
    227 }
    228 
    229 impl Wrench {
    230    #[allow(clippy::too_many_arguments)]
    231    pub fn new(
    232        window: &mut WindowWrapper,
    233        proxy: Option<EventLoopProxy<()>>,
    234        shader_override_path: Option<PathBuf>,
    235        use_optimized_shaders: bool,
    236        size: DeviceIntSize,
    237        do_rebuild: bool,
    238        no_subpixel_aa: bool,
    239        verbose: bool,
    240        no_scissor: bool,
    241        no_batch: bool,
    242        precache_shaders: bool,
    243        dump_shader_source: Option<String>,
    244        notifier: Option<Box<dyn RenderNotifier>>,
    245        layer_compositor: Option<Box<dyn LayerCompositor>>,
    246    ) -> Self {
    247        println!("Shader override path: {:?}", shader_override_path);
    248 
    249        let mut debug_flags = DebugFlags::ECHO_DRIVER_MESSAGES;
    250        debug_flags.set(DebugFlags::DISABLE_BATCHING, no_batch);
    251        debug_flags.set(DebugFlags::MISSING_SNAPSHOT_PINK, true);
    252        let callbacks = Arc::new(Mutex::new(blob::BlobCallbacks::new()));
    253 
    254        let precache_flags = if precache_shaders {
    255            ShaderPrecacheFlags::FULL_COMPILE
    256        } else {
    257            ShaderPrecacheFlags::empty()
    258        };
    259 
    260        let compositor_config = match layer_compositor {
    261            Some(compositor) => CompositorConfig::Layer { compositor },
    262            None => CompositorConfig::default(),
    263        };
    264 
    265        let opts = webrender::WebRenderOptions {
    266            resource_override_path: shader_override_path,
    267            use_optimized_shaders,
    268            enable_subpixel_aa: !no_subpixel_aa,
    269            debug_flags,
    270            enable_clear_scissor: no_scissor.then_some(false),
    271            max_recorded_profiles: 16,
    272            precache_flags,
    273            blob_image_handler: Some(Box::new(blob::CheckerboardRenderer::new(callbacks.clone()))),
    274            testing: true,
    275            max_internal_texture_size: Some(8196), // Needed for rawtest::test_resize_image.
    276            allow_advanced_blend_equation: window.is_software(),
    277            dump_shader_source,
    278            // SWGL doesn't support the GL_ALWAYS depth comparison function used by
    279            // `clear_caches_with_quads`, but scissored clears work well.
    280            clear_caches_with_quads: !window.is_software(),
    281            compositor_config,
    282            enable_debugger: true,
    283            precise_radial_gradients: true,
    284            precise_conic_gradients: true,
    285            precise_linear_gradients: window.is_software(),
    286            ..Default::default()
    287        };
    288 
    289        // put an Awakened event into the queue to kick off the first frame
    290        if let Some(ref _elp) = proxy {
    291            #[cfg(not(target_os = "android"))]
    292            let _ = _elp.send_event(());
    293        }
    294 
    295        let (timing_sender, timing_receiver) = chase_lev::deque();
    296        let notifier = notifier.unwrap_or_else(|| {
    297            let data = Arc::new(Mutex::new(NotifierData::new(proxy, timing_receiver, verbose)));
    298            Box::new(Notifier(data))
    299        });
    300 
    301        let (renderer, sender) = webrender::create_webrender_instance(
    302            window.clone_gl(),
    303            notifier,
    304            opts,
    305            None,
    306        ).unwrap();
    307 
    308        let api = sender.create_api();
    309        let document_id = api.add_document(size);
    310 
    311        let graphics_api = renderer.get_graphics_api_info();
    312 
    313        let mut wrench = Wrench {
    314            window_size: size,
    315 
    316            renderer,
    317            api,
    318            document_id,
    319            window_title_to_set: None,
    320 
    321            rebuild_display_lists: do_rebuild,
    322 
    323            root_pipeline_id: PipelineId(0, 0),
    324 
    325            graphics_api,
    326            frame_start_sender: timing_sender,
    327 
    328            callbacks,
    329        };
    330 
    331        wrench.set_title("start");
    332        let mut txn = Transaction::new();
    333        txn.set_root_pipeline(wrench.root_pipeline_id);
    334        wrench.api.send_transaction(wrench.document_id, txn);
    335 
    336        wrench
    337    }
    338 
    339    pub fn set_quality_settings(&mut self, settings: QualitySettings) {
    340        let mut txn = Transaction::new();
    341        txn.set_quality_settings(settings);
    342        self.api.send_transaction(self.document_id, txn);
    343    }
    344 
    345    pub fn layout_simple_ascii(
    346        &mut self,
    347        font_key: FontKey,
    348        instance_key: FontInstanceKey,
    349        text: &str,
    350        size: f32,
    351        origin: LayoutPoint,
    352        flags: FontInstanceFlags,
    353    ) -> (Vec<u32>, Vec<LayoutPoint>, LayoutRect) {
    354        // Map the string codepoints to glyph indices in this font.
    355        // Just drop any glyph that isn't present in this font.
    356        let indices: Vec<u32> = self.api
    357            .get_glyph_indices(font_key, text)
    358            .iter()
    359            .filter_map(|idx| *idx)
    360            .collect();
    361 
    362        // Retrieve the metrics for each glyph.
    363        let metrics = self.api.get_glyph_dimensions(instance_key, indices.clone());
    364 
    365        let mut bounding_rect = LayoutRect::zero();
    366        let mut positions = Vec::new();
    367 
    368        let mut cursor = origin;
    369        let direction = if flags.contains(FontInstanceFlags::TRANSPOSE) {
    370            LayoutVector2D::new(
    371                0.0,
    372                if flags.contains(FontInstanceFlags::FLIP_Y) { -1.0 } else { 1.0 },
    373            )
    374        } else {
    375            LayoutVector2D::new(
    376                if flags.contains(FontInstanceFlags::FLIP_X) { -1.0 } else { 1.0 },
    377                0.0,
    378            )
    379        };
    380        for metric in metrics {
    381            positions.push(cursor);
    382 
    383            if let Some(GlyphDimensions { left, top, width, height, advance }) = metric {
    384                let glyph_rect = LayoutRect::from_origin_and_size(
    385                    LayoutPoint::new(cursor.x + left as f32, cursor.y - top as f32),
    386                    LayoutSize::new(width as f32, height as f32)
    387                );
    388                bounding_rect = bounding_rect.union(&glyph_rect);
    389                cursor += direction * advance;
    390            } else {
    391                // Extract the advances from the metrics. The get_glyph_dimensions API
    392                // has a limitation that it can't currently get dimensions for non-renderable
    393                // glyphs (e.g. spaces), so just use a rough estimate in that case.
    394                let space_advance = size / 3.0;
    395                cursor += direction * space_advance;
    396            }
    397        }
    398 
    399        // The platform font implementations don't always handle
    400        // the exact dimensions used when subpixel AA is enabled
    401        // on glyphs. As a workaround, inflate the bounds by
    402        // 2 pixels on either side, to give a slightly less
    403        // tight fitting bounding rect.
    404        let bounding_rect = bounding_rect.inflate(2.0, 2.0);
    405 
    406        (indices, positions, bounding_rect)
    407    }
    408 
    409    pub fn set_title(&mut self, extra: &str) {
    410        self.window_title_to_set = Some(format!(
    411            "Wrench: {} - {} - {}",
    412            extra,
    413            self.graphics_api.renderer,
    414            self.graphics_api.version
    415        ));
    416    }
    417 
    418    pub fn take_title(&mut self) -> Option<String> {
    419        self.window_title_to_set.take()
    420    }
    421 
    422    pub fn should_rebuild_display_lists(&self) -> bool {
    423        self.rebuild_display_lists
    424    }
    425 
    426    pub fn window_size_f32(&self) -> LayoutSize {
    427        LayoutSize::new(
    428            self.window_size.width as f32,
    429            self.window_size.height as f32,
    430        )
    431    }
    432 
    433    #[cfg(target_os = "windows")]
    434    pub fn font_key_from_native_handle(&mut self, descriptor: &NativeFontHandle) -> FontKey {
    435        let key = self.api.generate_font_key();
    436        let mut txn = Transaction::new();
    437        txn.add_native_font(key, descriptor.clone());
    438        self.api.send_transaction(self.document_id, txn);
    439        key
    440    }
    441 
    442    #[cfg(target_os = "windows")]
    443    pub fn font_key_from_name(&mut self, font_name: &str) -> FontKey {
    444        self.font_key_from_properties(
    445            font_name,
    446            dwrote::FontWeight::Regular.to_u32(),
    447            dwrote::FontStyle::Normal.to_u32(),
    448            dwrote::FontStretch::Normal.to_u32(),
    449        )
    450    }
    451 
    452    #[cfg(target_os = "windows")]
    453    pub fn font_key_from_properties(
    454        &mut self,
    455        family: &str,
    456        weight: u32,
    457        style: u32,
    458        stretch: u32,
    459    ) -> FontKey {
    460        let weight = dwrote::FontWeight::from_u32(weight);
    461        let style = dwrote::FontStyle::from_u32(style);
    462        let stretch = dwrote::FontStretch::from_u32(stretch);
    463        let desc = dwrote::FontDescriptor {
    464            family_name: family.to_owned(),
    465            weight,
    466            style,
    467            stretch,
    468        };
    469        let system_fc = dwrote::FontCollection::system();
    470        #[allow(deprecated)]
    471        if let Some(font) = system_fc.get_font_from_descriptor(&desc) {
    472            let face = font.create_font_face();
    473            #[allow(deprecated)]
    474            let files = face.get_files();
    475            if files.len() == 1 {
    476                #[allow(deprecated)]
    477                if let Some(path) = files[0].get_font_file_path() {
    478                    return self.font_key_from_native_handle(&NativeFontHandle {
    479                        path,
    480                        index: face.get_index(),
    481                    });
    482                }
    483            }
    484        }
    485        panic!("failed loading font from properties {:?}", desc)
    486    }
    487 
    488    #[cfg(all(unix, not(target_os = "android")))]
    489    pub fn font_key_from_properties(
    490        &mut self,
    491        family: &str,
    492        _weight: u32,
    493        _style: u32,
    494        _stretch: u32,
    495    ) -> FontKey {
    496        let property = system_fonts::FontPropertyBuilder::new()
    497            .family(family)
    498            .build();
    499        let (font, index) = system_fonts::get(&property).unwrap();
    500        self.font_key_from_bytes(font, index as u32)
    501    }
    502 
    503    #[cfg(target_os = "android")]
    504    pub fn font_key_from_properties(
    505        &mut self,
    506        _family: &str,
    507        _weight: u32,
    508        _style: u32,
    509        _stretch: u32,
    510    ) -> FontKey {
    511        unimplemented!()
    512    }
    513 
    514    #[cfg(all(unix, not(target_os = "android")))]
    515    pub fn font_key_from_name(&mut self, font_name: &str) -> FontKey {
    516        let property = system_fonts::FontPropertyBuilder::new()
    517            .family(font_name)
    518            .build();
    519        let (font, index) = system_fonts::get(&property).unwrap();
    520        self.font_key_from_bytes(font, index as u32)
    521    }
    522 
    523    #[cfg(target_os = "android")]
    524    pub fn font_key_from_name(&mut self, _font_name: &str) -> FontKey {
    525        unimplemented!()
    526    }
    527 
    528    pub fn font_key_from_bytes(&mut self, bytes: Vec<u8>, index: u32) -> FontKey {
    529        let key = self.api.generate_font_key();
    530        let mut txn = Transaction::new();
    531        txn.add_raw_font(key, bytes, index);
    532        self.api.send_transaction(self.document_id, txn);
    533        key
    534    }
    535 
    536    pub fn add_font_instance(&mut self,
    537        font_key: FontKey,
    538        size: f32,
    539        flags: FontInstanceFlags,
    540        render_mode: Option<FontRenderMode>,
    541        synthetic_italics: SyntheticItalics,
    542    ) -> FontInstanceKey {
    543        let key = self.api.generate_font_instance_key();
    544        let mut txn = Transaction::new();
    545        let mut options: FontInstanceOptions = Default::default();
    546        options.flags |= flags;
    547        if let Some(render_mode) = render_mode {
    548            options.render_mode = render_mode;
    549        }
    550        options.synthetic_italics = synthetic_italics;
    551        txn.add_font_instance(key, font_key, size, Some(options), None, Vec::new());
    552        self.api.send_transaction(self.document_id, txn);
    553        key
    554    }
    555 
    556    #[allow(dead_code)]
    557    pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
    558        let mut txn = Transaction::new();
    559        txn.delete_font_instance(key);
    560        self.api.send_transaction(self.document_id, txn);
    561    }
    562 
    563    pub fn update(&mut self, dim: DeviceIntSize) {
    564        if dim != self.window_size {
    565            self.window_size = dim;
    566        }
    567    }
    568 
    569    pub fn begin_frame(&mut self) {
    570        self.frame_start_sender.push(Instant::now());
    571    }
    572 
    573    pub fn send_lists(
    574        &mut self,
    575        frame_number: &mut u32,
    576        display_lists: Vec<DisplayList>,
    577        scroll_offsets: &HashMap<ExternalScrollId, Vec<SampledScrollOffset>>,
    578    ) {
    579        let mut txn = Transaction::new();
    580        let mut present = false;
    581        for display_list in display_lists {
    582            present |= display_list.present;
    583 
    584            txn.set_display_list(
    585                Epoch(*frame_number),
    586                (display_list.pipeline, display_list.payload),
    587            );
    588 
    589            if display_list.render_offscreen {
    590                txn.render_offscreen(display_list.pipeline);
    591            }
    592 
    593            if display_list.send_transaction {
    594                for (id, offsets) in scroll_offsets {
    595                    txn.set_scroll_offsets(*id, offsets.clone());
    596                }
    597 
    598                let tracked = false;
    599                txn.generate_frame(0, present, tracked, RenderReasons::TESTING);
    600                self.api.send_transaction(self.document_id, txn);
    601                txn = Transaction::new();
    602 
    603                present = false;
    604                *frame_number += 1;
    605            }
    606        }
    607 
    608        // The last display lists should be have send_transaction set to true.
    609        assert!(txn.is_empty());
    610    }
    611 
    612    pub fn get_frame_profiles(
    613        &mut self,
    614    ) -> (Vec<webrender::CpuProfile>, Vec<webrender::GpuProfile>) {
    615        self.renderer.get_frame_profiles()
    616    }
    617 
    618    pub fn render(&mut self) -> RenderResults {
    619        self.renderer.update();
    620        let _ = self.renderer.flush_pipeline_info();
    621        self.renderer
    622            .render(self.window_size, 0)
    623            .expect("errors encountered during render!")
    624    }
    625 
    626    pub fn refresh(&mut self) {
    627        self.begin_frame();
    628        let mut txn = Transaction::new();
    629        let present = true;
    630        let tracked = false;
    631        txn.generate_frame(0, present, tracked, RenderReasons::TESTING);
    632        self.api.send_transaction(self.document_id, txn);
    633    }
    634 
    635    pub fn show_onscreen_help(&mut self) {
    636        let help_lines = [
    637            "Esc - Quit",
    638            "H - Toggle help",
    639            "R - Toggle recreating display items each frame",
    640            "P - Toggle profiler",
    641            "O - Toggle showing intermediate targets",
    642            "I - Toggle showing texture caches",
    643            "B - Toggle showing alpha primitive rects",
    644            "V - Toggle showing overdraw",
    645            "G - Toggle showing gpu cache updates",
    646            "S - Toggle compact profiler",
    647            "Q - Toggle GPU queries for time and samples",
    648            "M - Trigger memory pressure event",
    649            "T - Save CPU profile to a file",
    650            "C - Save a capture to captures/wrench/",
    651            "X - Do a hit test at the current cursor position",
    652            "Y - Clear all caches",
    653        ];
    654 
    655        let color_and_offset = [(ColorF::BLACK, 2.0), (ColorF::WHITE, 0.0)];
    656        self.renderer.device.begin_frame(); // next line might compile shaders:
    657        let dr = self.renderer.debug_renderer().unwrap();
    658 
    659        for co in &color_and_offset {
    660            let x = 15.0 + co.1;
    661            let mut y = 15.0 + co.1 + dr.line_height();
    662            for line in &help_lines {
    663                dr.add_text(x, y, line, co.0.into(), None);
    664                y += dr.line_height();
    665            }
    666        }
    667        self.renderer.device.end_frame();
    668    }
    669 
    670    pub fn shut_down(self, rx: Receiver<NotifierEvent>) {
    671        self.api.shut_down(true);
    672 
    673        loop {
    674            match rx.recv() {
    675                Ok(NotifierEvent::ShutDown) => { break; }
    676                Ok(_) => {}
    677                Err(e) => { panic!("Did not shut down properly: {:?}.", e); }
    678            }
    679        }
    680 
    681        self.renderer.deinit();
    682    }
    683 }