gl_utils/render/resource/
demo.rs

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