gl_utils/render/resource/
demo.rs

1use std::sync::{Arc, RwLock};
2use lazy_static::lazy_static;
3use strum::IntoEnumIterator;
4use winit;
5
6use math_utils as math;
7use math_utils::num_traits as num;
8
9use crate::{tile, Render};
10use super::*;
11
12/// A type to implement winit ApplicationHandler trait
13pub struct App {
14  pub render             : Render <Default>,
15  pub mouse_position     : (f64, f64),
16  pub mouse_button_event : Option <winit::event::ElementState>,
17  pub running            : bool
18}
19
20impl App {
21  pub fn new (
22    glium_display : glium::Display <glutin::surface::WindowSurface>,
23    window        : winit::window::Window
24  ) -> Self {
25    let mut render = Render::<Default>::new (glium_display, window);
26    render.demo_init();
27    let mouse_position      = (0.0, 0.0);
28    let mouse_button_event  = None;
29    let running             = true;
30    Self {
31      render, mouse_position, mouse_button_event, running
32    }
33  }
34}
35
36impl winit::application::ApplicationHandler for App {
37  // required
38  fn resumed (&mut self, _event_loop : &winit::event_loop::ActiveEventLoop) { }
39
40  fn window_event (&mut self,
41    _event_loop : &winit::event_loop::ActiveEventLoop,
42    _window_id  : winit::window::WindowId,
43    event       : winit::event::WindowEvent
44  ) {
45    // winit events:
46    // TODO: review
47    // - window events includes input events received by the window
48    // - device events are received independent of window focus
49    // there may be differences in the events received depending on platform:
50    // - on Linux keyboard input is received as both a device and window event
51    // - on Windows only mouse motion device events are received, all other
52    //   input is received as window events
53    self.render.demo_handle_winit_window_event (
54      event,
55      &mut self.running,
56      &mut self.mouse_position,
57      &mut self.mouse_button_event)
58  }
59}
60
61impl Render <Default> {
62  /// Initializes "demo" state such as example mesh instances and viewport text
63  /// tiles.
64  ///
65  /// This should normally be called immediately after render context creation
66  /// as some assumptions are made about the current state (see Panics below).
67  /// The renderer can be returned to this state with the `reset` method.
68  ///
69  /// After `demo_init`, `demo_handle_winit_event` can be called on incoming
70  /// events to interact with the demo.
71  ///
72  /// A usage example is provided in `./examples/demo.rs`.
73  ///
74  /// # Panics
75  ///
76  /// A prerequisite is that the first four `viewport_resources` entries are
77  /// empty.
78  ///
79  /// The currently allocated per-instance buffers should be empty.
80  // TODO: doctests, make safe to call from any state?
81  fn demo_init (&mut self) {
82    use draw3d::*;
83
84    println!(">>> Initializing gl-utils demo...");
85    println!("  Press 'Q' or 'Esc' to quit");
86    println!("  Horizontal movement: 'W', 'A', 'S', 'D'");
87    println!("  Vertical movement: 'Space', 'LCtrl' or 'R', 'F'");
88    println!("  Rotation: 'Up', 'Down', 'Left', 'Right' or 'H', 'J', 'K', 'L'");
89    println!("  Zoom in/out 3D (perspective): 'I', 'O'");
90    println!("  Zoom in/out 3D (orthographic): 'Alt+I', 'Alt+O'");
91    println!("  Zoom in/out 2D: 'Shift+Alt+I', 'Shift+Alt+O'");
92    println!("  Toggle split into 4 viewports with orthographic views: 'X'");
93    println!("  Screenshot: 'F10'");
94
95    // set pointer texture
96    self.resource.draw2d.draw_pointer =
97      Some (DefaultTexturePointerId::Hand as PointerTextureIndexRepr);
98    // hide hardware cursor
99    self.window.set_cursor_visible (false);
100
101    //
102    // 3D per-instance data
103    //
104    // aabb lines vertex data
105    let aabb_lines_vertex = [vertex::Vert3dOrientationScaleColor {
106      position:    [-0.5, 2.5, 1.0],
107      orientation: math::Matrix3::identity().into_col_arrays(),
108      scale:       [0.5, 0.5, 1.0],
109      color:       color::rgba_u8_to_rgba_f32 (color::DEBUG_PINK)
110    }];
111    // aabb triangles vertex data
112    let aabb_triangles_vertex = [vertex::Vert3dOrientationScaleColor {
113      color: color::rgba_u8_to_rgba_f32 (color::DEBUG_GREY),
114      .. aabb_lines_vertex[0]
115    }];
116    // grid vertex data
117    let grid_vertex_data = Default::debug_grid_vertices();
118    // hemisphere vertex data
119    let hemisphere_vertex = [vertex::Vert3dOrientationScaleColor {
120      position:    [2.5, 2.5, 0.0],
121      orientation: math::Matrix3::identity().into_col_arrays(),
122      scale:       [0.5, 0.5, 0.5],
123      color:       color::rgba_u8_to_rgba_f32 (color::DEBUG_VIOLET)
124    }];
125    // sphere vertex data
126    // sphere: 1.0 diameter
127    let sphere_vertex = [vertex::Vert3dOrientationScaleColor {
128      position:    [1.5, 2.5, 0.5],
129      orientation: math::Matrix3::identity().into_col_arrays(),
130      scale:       [0.5, 0.5, 0.5],
131      color:       color::rgba_u8_to_rgba_f32 (color::DEBUG_RED)
132    }];
133    // capsule vertex data
134    let capsule_vertex = [vertex::Vert3dOrientationScaleColor {
135      position:    [0.5, 2.5, 1.0],
136      orientation: math::Matrix3::identity().into_col_arrays(),
137      scale:       [0.5, 0.5, 1.0],
138      color:       color::rgba_u8_to_rgba_f32 (color::DEBUG_AZURE)
139    }];
140    // cylinder vertex data
141    let cylinder_vertex = [vertex::Vert3dOrientationScaleColor {
142      position:    [3.5, 2.5, 1.0],
143      orientation: math::Matrix3::identity().into_col_arrays(),
144      scale:       [0.5, 0.5, 1.0],
145      color:       color::rgba_u8_to_rgba_f32 (color::DEBUG_LIGHT_BLUE)
146    }];
147    let aabb_lines               = Some (&aabb_lines_vertex[..]);
148    let aabb_triangles           = Some (&aabb_triangles_vertex[..]);
149    let aabb_lines_and_triangles = None;
150    let meshes = {
151      let mut v = VecMap::with_capacity (MeshId::COUNT);
152      assert!(v.insert (MeshId::Grid       as usize, &grid_vertex_data[..])
153        .is_none());
154      assert!(v.insert (MeshId::Hemisphere as usize, &hemisphere_vertex[..])
155        .is_none());
156      assert!(v.insert (MeshId::Sphere     as usize, &sphere_vertex[..])
157        .is_none());
158      assert!(v.insert (MeshId::Capsule    as usize, &capsule_vertex[..])
159        .is_none());
160      assert!(v.insert (MeshId::Cylinder   as usize, &cylinder_vertex[..])
161        .is_none());
162      v
163    };
164    // note: no billboard instances are created here, instead they are assigned
165    // to share instances with meshes
166    let billboards = VecMap::new();
167    self.resource.draw3d.instance_vertices_set (
168      &self.glium_display,
169      draw3d::InstancesInit {
170        aabb_lines, aabb_triangles, aabb_lines_and_triangles, meshes, billboards
171      }
172    );
173    // tile billboards
174    for mesh_id in MeshId::iter() {
175      let key = mesh_id as usize;
176      self.resource.draw3d.tile_billboard_create (
177        &self.glium_display, &self.resource.tileset_128x128_texture,
178        key, self.resource.draw3d.instanced_meshes()[key].instances_range.clone(),
179        format!("{:?}", mesh_id).as_str()
180      );
181    }
182    let aabb_billboard_key = MeshId::COUNT;
183    self.resource.draw3d.tile_billboard_create (
184      &self.glium_display, &self.resource.tileset_128x128_texture,
185      aabb_billboard_key,  // start immediately after mesh billboards
186      self.resource.draw3d.instanced_aabb_lines().clone(),
187      "Aabb"
188    );
189
190    let (width, height) = self.window.inner_size().into();
191    let [_tile_width, tile_height] =
192      self.resource.tile_dimensions (DefaultTilesetId::EasciiAcorn128);
193    //
194    // 2D per-viewport tiles
195    //
196    let mut tile_2d_vertices = tile::vertices ("Viewport0", (0, 0));
197    let viewport0_tile_range = 0..tile_2d_vertices.len() as u32;
198    tile_2d_vertices.extend (tile::vertices ("Viewport1: +Y", (0, 0)));
199    let viewport1_tile_range = viewport0_tile_range.end..tile_2d_vertices.len()
200      as u32;
201    tile_2d_vertices.extend (tile::vertices ("Viewport2: +X", (0, 0)));
202    let viewport2_tile_range = viewport1_tile_range.end..tile_2d_vertices.len()
203      as u32;
204    tile_2d_vertices.extend (tile::vertices ("Viewport3: -Z", (0, 0)));
205    let viewport3_tile_range = viewport2_tile_range.end..tile_2d_vertices.len()
206      as u32;
207    //let center_tile = ((width / tile_width) / 2) as i32;
208    let fps_row = ((height / tile_height) - 1) as i32;
209    tile_2d_vertices.extend (tile::vertices ("FPS: 0     ", (fps_row, 0)));
210    let viewport4_tile_range = viewport3_tile_range.end..tile_2d_vertices.len()
211      as u32;
212    self.resource.draw2d.tile_2d_vertices = glium::VertexBuffer::dynamic (
213      &self.glium_display, &tile_2d_vertices[..]
214    ).unwrap();
215    let tile_color_2d_vertices = {
216      let tiles  = tile::vertices ("abc123",    (2, 0));
217      let colors = vec![
218        ( [1.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0] ),
219        ( [0.0, 1.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0] ),
220        ( [0.0, 0.0, 1.0, 1.0], [0.0, 0.0, 0.0, 0.0] ),
221        ( [0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 0.0, 1.0] ),
222        ( [0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 1.0, 1.0] ),
223        ( [0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 1.0, 1.0] )
224      ];
225      tiles.into_iter().zip (colors).map (
226        |(vertex::Vert2dTile { tile, row, column }, (fg, bg))|
227        vertex::Vert2dTileColor { tile, row, column, fg, bg }
228      ).collect::<Vec <_>>()
229    };
230    let viewport0_tile_color_range = 0..tile_color_2d_vertices.len() as u32;
231    self.resource.draw2d.tile_color_2d_vertices = glium::VertexBuffer::dynamic (
232      &self.glium_display, &tile_color_2d_vertices[..]
233    ).unwrap();
234    let draw_tiles = draw2d::Tiles {
235      vertex_range: 0..0, origin: (2, 2).into(),
236      tileset_id: DefaultTilesetId::EasciiAcorn128
237    };
238    let draw_tiles_color = draw2d::Tiles {
239      vertex_range: 0..0, origin: draw2d::TilesOrigin::World,
240      tileset_id: DefaultTilesetId::EasciiAcorn256
241    };
242    self.resource.draw2d.viewport_resources_set (MAIN_VIEWPORT,
243      draw2d::ViewportResources {
244        draw_indices: vec![draw2d::DrawIndices {
245          rectangle:        None,
246          draw_tiles:       Some (draw2d::Tiles {
247            vertex_range: viewport0_tile_range,
248            .. draw_tiles.clone()
249          }),
250          draw_color_tiles: Some (draw2d::Tiles {
251            vertex_range: viewport0_tile_color_range,
252            .. draw_tiles_color
253          })
254        }],
255        .. draw2d::ViewportResources::default()
256      }
257    );
258    self.resource.draw2d.viewport_resources_set (UPPER_LEFT_VIEWPORT,
259      draw2d::ViewportResources {
260        draw_indices: vec![draw2d::DrawIndices {
261          draw_tiles: Some (draw2d::Tiles {
262            vertex_range: viewport1_tile_range,
263            .. draw_tiles.clone()
264          }),
265          .. draw2d::DrawIndices::default()
266        }],
267        .. draw2d::ViewportResources::default()
268      }
269    );
270    self.resource.draw2d.viewport_resources_set (UPPER_RIGHT_VIEWPORT,
271      draw2d::ViewportResources {
272        draw_indices: vec![draw2d::DrawIndices {
273          draw_tiles: Some (draw2d::Tiles {
274            vertex_range: viewport2_tile_range,
275            .. draw_tiles.clone()
276          }),
277          .. draw2d::DrawIndices::default()
278        }],
279        .. draw2d::ViewportResources::default()
280      }
281    );
282    self.resource.draw2d.viewport_resources_set (LOWER_LEFT_VIEWPORT,
283      draw2d::ViewportResources {
284        draw_indices: vec![draw2d::DrawIndices {
285          draw_tiles: Some (draw2d::Tiles {
286            vertex_range: viewport3_tile_range,
287            .. draw_tiles.clone()
288          }),
289          .. draw2d::DrawIndices::default()
290        }],
291        .. draw2d::ViewportResources::default()
292      }
293    );
294    self.resource.draw2d.viewport_resources_set (OVERLAY_VIEWPORT,
295      draw2d::ViewportResources {
296        draw_indices: vec![draw2d::DrawIndices {
297          draw_tiles: Some (draw2d::Tiles {
298            vertex_range: viewport4_tile_range,
299            .. draw_tiles.clone()
300          }),
301          .. draw2d::DrawIndices::default()
302        }],
303        draw_lineloop: false,
304        draw_crosshair: false,
305        .. draw2d::ViewportResources::default()
306      }
307    );
308    // create overlay viewport
309    let overlay = render::viewport::Builder::new (glium::Rect {
310      width:  width,
311      height: height,
312      left:   0,
313      bottom: 0
314    }).with_camera_3d (false)
315      .build();
316    assert!(self.viewports.insert (OVERLAY_VIEWPORT, overlay).is_none());
317  }
318
319  pub fn demo_handle_winit_window_event (&mut self,
320    event              : winit::event::WindowEvent,
321    running            : &mut bool,
322    mouse_position     : &mut (f64, f64),
323    mouse_button_event : &mut Option <winit::event::ElementState>
324  ) {
325    use std::f32::consts::PI;
326    use winit::{event, keyboard};
327    use num::Zero;
328    log::debug!("demo handle winit window event: {:?}", event);
329    // winit deprecated the modifiers field of keyboard input so we have to
330    // track modifier changes
331    lazy_static!{
332      static ref MODIFIERS : Arc <RwLock <keyboard::ModifiersState>> =
333        Arc::new (RwLock::new (keyboard::ModifiersState::empty()));
334    }
335    match event {
336      // window resized event
337      event::WindowEvent::Resized (physical_size) => {
338        log::debug!("window event: {:?}", event);
339        self.window_resized (physical_size);
340      }
341      // window closed event
342      event::WindowEvent::CloseRequested => { *running = false; }
343      // TODO: for some reason a destroyed event is received on startup even
344      // though window has not been destroyed (winit 27.5)
345      event::WindowEvent::Destroyed => { /* *running = false; */ }
346      // modifiers changed event
347      event::WindowEvent::ModifiersChanged (modifiers) =>
348        *MODIFIERS.write().unwrap() = modifiers.state(),
349      // keyboard input pressed event
350      event::WindowEvent::KeyboardInput {
351        event: event::KeyEvent {
352          state: event::ElementState::Pressed,
353          physical_key, logical_key, location, ..
354        },
355        ..
356      } => match (logical_key, location) {
357        ( keyboard::Key::Named (keyboard::NamedKey::Control),
358          keyboard::KeyLocation::Left
359        ) => self.camera3d_move_local_xy (0.0, 0.0, -1.0),
360        _ => match physical_key {
361          keyboard::PhysicalKey::Code (key_code) => match key_code {
362            // quit
363            keyboard::KeyCode::KeyQ | keyboard::KeyCode::Escape =>
364              *running = false,
365            // choose a frame function 0-9
366            keyboard::KeyCode::Digit1 => self.frame_fun =
367              render::frame_fun_default::<render::resource::Default>,
368            // TODO: more frame functions
369            // TODO: cycle between frame functions
370            //keyboard::KeyCode::Tab => { }
371            keyboard::KeyCode::KeyW =>
372              self.camera3d_move_local_xy (0.0, 1.0, 0.0),
373            keyboard::KeyCode::KeyS =>
374              self.camera3d_move_local_xy (0.0, -1.0, 0.0),
375            keyboard::KeyCode::KeyD =>
376              self.camera3d_move_local_xy (1.0, 0.0, 0.0),
377            keyboard::KeyCode::KeyA =>
378              self.camera3d_move_local_xy (-1.0, 0.0, 0.0),
379            keyboard::KeyCode::Space | keyboard::KeyCode::KeyR =>
380              self.camera3d_move_local_xy (0.0, 0.0, 1.0),
381            keyboard::KeyCode::KeyF =>
382              self.camera3d_move_local_xy (0.0, 0.0, -1.0),
383            keyboard::KeyCode::KeyJ | keyboard::KeyCode::ArrowDown => {
384              let modifiers = MODIFIERS.read().unwrap();
385              if modifiers.alt_key() && modifiers.shift_key() {
386                self.camera2d_move_local (0.0, -1.0);
387              } else {
388                self.camera3d_rotate (
389                  math::Rad::zero(),
390                  math::Rad (-PI / 12.0),
391                  math::Rad::zero());
392              }
393            }
394            keyboard::KeyCode::KeyK | keyboard::KeyCode::ArrowUp => {
395              let modifiers = MODIFIERS.read().unwrap();
396              if modifiers.alt_key() && modifiers.shift_key() {
397                self.camera2d_move_local (0.0, 1.0);
398              } else {
399                self.camera3d_rotate (
400                  math::Rad::zero(),
401                  math::Rad (PI / 12.0),
402                  math::Rad::zero());
403              }
404            }
405            keyboard::KeyCode::KeyH | keyboard::KeyCode::ArrowLeft => {
406              let modifiers = MODIFIERS.read().unwrap();
407              if modifiers.alt_key() && modifiers.shift_key() {
408                self.camera2d_move_local (-1.0, 0.0);
409              } else {
410                self.camera3d_rotate (
411                  math::Rad (PI / 12.0),
412                  math::Rad::zero(),
413                  math::Rad::zero());
414              }
415            }
416            keyboard::KeyCode::KeyL | keyboard::KeyCode::ArrowRight => {
417              let modifiers = MODIFIERS.read().unwrap();
418              if modifiers.alt_key() && modifiers.shift_key() {
419                self.camera2d_move_local (1.0, 0.0);
420              } else {
421                self.camera3d_rotate (
422                  math::Rad (-PI / 12.0),
423                  math::Rad::zero(),
424                  math::Rad::zero());
425              }
426            }
427            keyboard::KeyCode::KeyI => {
428              let modifiers = MODIFIERS.read().unwrap();
429              if modifiers.alt_key() && modifiers.shift_key() {
430                self.camera2d_zoom_shift (1.0);
431              } else if modifiers.alt_key() {
432                self.camera3d_orthographic_zoom_scale (1.1);
433              } else {
434                self.camera3d_perspective_fovy_scale (1.0 / 1.1);
435              }
436            }
437            keyboard::KeyCode::KeyO => {
438              let modifiers = MODIFIERS.read().unwrap();
439              if modifiers.alt_key() && modifiers.shift_key() {
440                self.camera2d_zoom_shift (-1.0);
441              } else if modifiers.alt_key() {
442                self.camera3d_orthographic_zoom_scale (1.0 / 1.1);
443              } else {
444                self.camera3d_perspective_fovy_scale (1.1);
445              }
446            }
447            // toggle 4-viewport mode
448            keyboard::KeyCode::KeyX   => self.demo_toggle_quad_viewports(),
449            // end camera controls
450            keyboard::KeyCode::F10 | keyboard::KeyCode::PrintScreen =>
451              self.screenshot(),
452            _ => {}
453          }
454          keyboard::PhysicalKey::Unidentified (_) => {}
455        }
456      } // end keyboard input
457      event::WindowEvent::MouseInput { button, state, .. } => match button {
458        event::MouseButton::Left => *mouse_button_event = Some (state),
459        _ => {}
460      }
461      event::WindowEvent::CursorMoved { position, ..  } => {
462        if self.resource.draw2d.draw_pointer.is_some() {
463          let (_, height) = self.glium_display.get_framebuffer_dimensions();
464          let x = position.x as f32;
465          let y = height as f32 - position.y as f32;
466          self.resource
467            .set_pointer_position (&self.glium_display, [x, y].into());
468        }
469        *mouse_position = (position.x, position.y);
470      }
471      _ => {}
472    }
473  }
474
475  /// Switch between single (perspective) and quad viewport modes (perspective
476  /// + three ortho viewports).
477  ///
478  /// Generally should be called from a render context that was initialized
479  /// with `demo_init`.
480  ///
481  /// # Panics
482  ///
483  /// If there is a single viewport (other than the `OVERLAY_VIEWPORT`) it must
484  /// be `MAIN_VIEWPORT`.
485  ///
486  /// If there are multiple viewports, they must be exactly the first four
487  /// viewports only (other than the `OVERLAY_VIEWPORT`).
488  // TODO: doctests, make safe to call from any state?
489  fn demo_toggle_quad_viewports (&mut self) {
490    if self.viewports.len() <= 2 {
491      // switch to four viewports from single viewport
492      // main viewport becomes "lower right"
493      let (
494        left_width, right_width, upper_height, lower_height, position,
495        position_2d, zoom_2d
496      ) = {
497        let lower_right   = &mut self.viewports[MAIN_VIEWPORT];
498        let window_width  = lower_right.rect().width;
499        let window_height = lower_right.rect().height;
500        let left_width    = left_width   (window_width);
501        let right_width   = right_width  (window_width);
502        let upper_height  = upper_height (window_height);
503        let lower_height  = lower_height (window_height);
504        lower_right.set_rect (glium::Rect {
505          width:  right_width,
506          height: lower_height,
507          left:   left_width,
508          bottom: 0
509        });
510        ( left_width, right_width, upper_height, lower_height,
511          lower_right.camera3d().unwrap().position(),
512          lower_right.camera2d().unwrap().position(),
513          lower_right.camera2d().unwrap().zoom()
514        )
515      };
516
517      let upper_left  = render::viewport::Builder::new (glium::Rect {
518        width:  left_width,
519        height: upper_height,
520        left:   0,
521        bottom: lower_height
522      }).with_zoom_2d (zoom_2d).with_position_2d (position_2d)
523        .orthographic_3d (1.0)
524        .with_pose_3d (
525          // looking down positive Y axis
526          math::Pose3 { position, angles: math::Angles3::default() })
527        .build();
528
529      let upper_right = render::viewport::Builder::new (glium::Rect {
530        width:  right_width,
531        height: upper_height,
532        left:   left_width,
533        bottom: lower_height,
534      }).with_zoom_2d (zoom_2d).with_position_2d (position_2d)
535        .orthographic_3d (1.0)
536        .with_pose_3d (
537          math::Pose3 {
538            position,
539            // looking down positive X axis
540            angles: math::Angles3::wrap (
541              math::Deg (-90.0).into(),
542              math::Rad (0.0),
543              math::Rad (0.0))
544          })
545        .build();
546
547      let lower_left  = render::viewport::Builder::new (glium::Rect {
548        width:  left_width,
549        height: lower_height,
550        left:   0,
551        bottom: 0,
552      }).with_zoom_2d (zoom_2d).with_position_2d (position_2d)
553        .orthographic_3d (1.0)
554        .with_pose_3d (
555          math::Pose3 {
556            position,
557            // looking down negative Z axis
558            angles: math::Angles3::wrap (
559              math::Rad (0.0),
560              math::Deg (-90.0).into(),
561              math::Rad (0.0))
562          })
563        .build();
564
565      assert!(self.viewports.insert (UPPER_LEFT_VIEWPORT, upper_left)
566        .is_none());
567      assert!(self.viewports.insert (UPPER_RIGHT_VIEWPORT, upper_right)
568        .is_none());
569      assert!(self.viewports.insert (LOWER_LEFT_VIEWPORT, lower_left)
570        .is_none());
571    } else {
572      // switch to single viewport from four viewports
573      assert!(self.viewports.len() == 4 || self.viewports.len() == 5);
574      assert!(self.viewports.remove (UPPER_LEFT_VIEWPORT) .is_some());
575      assert!(self.viewports.remove (UPPER_RIGHT_VIEWPORT).is_some());
576      assert!(self.viewports.remove (LOWER_LEFT_VIEWPORT) .is_some());
577      let main_viewport   = &mut self.viewports[MAIN_VIEWPORT];
578      let (width, height) = self.window.inner_size().into();
579      main_viewport.set_rect (glium::Rect {
580        width,
581        height,
582        left:   0,
583        bottom: 0
584      });
585    }
586    self.update_viewport_line_loops();
587  } // end fn toggle_quad_viewports
588}