nsys-gl-utils 0.11.9

OpenGL and graphics utilities
Documentation
//! Support types for Glium rendering.
//!
//! The `Render` struct holds all the context information and resources
//! required for making calls to OpenGL render commands (via `glium`).

use std;
use std::sync::atomic;
use glium;
use glutin;
use image;
use log;
use rs_utils;
use vec_map::VecMap;

pub mod params;
pub mod resource;
pub mod viewport;

pub use self::resource::Resource;
pub use self::viewport::Viewport;

/// Initialize viewport `VecMap` with this capacity
const INITIAL_VIEWPORT_VECMAP_CAPACITY : usize = 4;
/// Sets a limit to how many simultaneous screenshot worker threads may be
/// running simultaneously-- should be more than enough.
const MAX_SCREENSHOT_WORKER_COUNT : u8 = 8;
/// Prevents renderers from dropping while screenshot workers are not yet
/// finished.
///
/// Note that this is a global variable so it is shared among all renderers
/// and it will not prevent other threads from terminating if not synchronized
/// with the render thread.
static SCREENSHOT_WORKER_COUNT : atomic::AtomicU8 = atomic::AtomicU8::new (0);

/// State for Glium-based rendering.
///
/// Holds all the context information and resources required for rendering. A
/// frame function may be specified by the `frame_fun` field which is a
/// function pointer that takes a mutable self reference for the purpose of
/// making Glium render commands.
///
/// OpenGL has four types of *render commands*:
///
/// 1. Framebuffer clearing commands
/// 2. Framebuffer blitting commands
/// 3. Drawing commands (*vertex rendering*)
/// 4. Compute dispatch commands (OpenGL 4.3+ only)
///
/// All resources (vertex and index buffers, textures and other uniforms)
/// should be in the user defined generic resource type `R : Resource`.
pub struct Render <R : Resource = resource::Default> {
  /// The `glium` context represented by a `glium::backend::Context` and a
  /// `glutin::surface::WindowSurface`
  pub glium_display : glium::Display <glutin::surface::WindowSurface>,
  pub window        : winit::window::Window,
  /// A function for rendering a single frame
  pub resource      : R,
  pub frame_fun     : fn (&mut Render <R>, Option <&mut glium::Frame>),
  pub clear_color   : [f32; 4],
  pub clear_depth   : f32,
  pub clear_stencil : i32,
  viewports         : VecMap <Viewport>
}

/// Default frame function clears all and calls resource `draw_3d` method
/// followed by `draw_2d` method.
pub fn frame_fun_default <R : Resource> (
  render : &mut Render <R>, frame : Option <&mut glium::Frame>
) {
  use glium::Surface;
  log::trace!("frame fun default...");
  let mut maybe_frame : Option <glium::Frame> = None;
  let glium_frame = match frame {
    Some (frame) => frame,
    None         => {
      maybe_frame = Some (render.glium_display.draw());
      maybe_frame.as_mut().unwrap()
    }
  };
  glium_frame.clear_all (
    render.clear_color_tuple(), render.clear_depth, render.clear_stencil);
  Resource::draw_3d (render, glium_frame);
  Resource::draw_2d (render, glium_frame);
  if let Some (glium_frame) = maybe_frame.take() {
    glium_frame.finish().unwrap();
  }
  log::trace!("...frame fun default");
}

/// Default 2D frame function clears all and calls resource `draw_2d` method.
pub fn frame_fun_default_2d <R : Resource> (
  render : &mut Render <R>, frame : Option <&mut glium::Frame>
) {
  use glium::Surface;
  log::trace!("frame fun default 2d...");
  let mut maybe_frame = None;
  let glium_frame = match frame {
    Some (frame) => frame,
    None         => {
      maybe_frame = Some (render.glium_display.draw());
      maybe_frame.as_mut().unwrap()
    }
  };
  glium_frame.clear_all (
    render.clear_color_tuple(), render.clear_depth, render.clear_stencil);
  Resource::draw_2d (render, glium_frame);
  if let Some (glium_frame) = maybe_frame.take() {
    glium_frame.finish().unwrap();
  }
  log::trace!("...frame fun default 2d");
}

/// Default 3D frame function clears all and calls resource `draw_3d` method.
pub fn frame_fun_default_3d <R : Resource> (
  render : &mut Render <R>, frame : Option <&mut glium::Frame>
) {
  use glium::Surface;
  log::trace!("frame fun default 3d...");
  let mut maybe_frame = None;
  let glium_frame = match frame {
    Some (frame) => frame,
    None         => {
      maybe_frame = Some (render.glium_display.draw());
      maybe_frame.as_mut().unwrap()
    }
  };
  glium_frame.clear_all (
    render.clear_color_tuple(), render.clear_depth, render.clear_stencil);
  Resource::draw_3d (render, glium_frame);
  if let Some (glium_frame) = maybe_frame.take() {
    glium_frame.finish().unwrap();
  }
  log::trace!("...frame fun default 3d");
}

impl <R : Resource> Render <R> {
  /// Creates a new renderer with default viewport and resources.
  pub fn new (
    glium_display : glium::Display <glutin::surface::WindowSurface>,
    window        : winit::window::Window
  ) -> Self {
    let resource = R::new (&glium_display);
    let frame_fun     = frame_fun_default::<R>;
    let clear_color   = Default::default();
    let clear_depth   = Default::default();
    let clear_stencil = Default::default();
    let viewports     = Default::default();
    let mut render = Render {
      glium_display, window, frame_fun, clear_color, clear_depth, clear_stencil,
      viewports, resource
    };
    render.initialize();
    render
  }

  /// Internally the clear color is stored as an array, but the glium clear
  /// command takes a tuple
  #[inline]
  pub const fn clear_color_tuple(&self) -> (f32, f32, f32, f32) {
    let [r, g, b, a] = self.clear_color;
    (r, g, b, a)
  }

  pub const fn viewports (&self) -> &VecMap <Viewport> {
    &self.viewports
  }

  pub const fn viewports_mut (&mut self) -> &mut VecMap <Viewport> {
    &mut self.viewports
  }

  pub fn get_viewport (&self, key : usize) -> Option <&Viewport> {
    self.viewports.get (key)
  }

  pub fn get_viewport_mut (&mut self, key : usize) -> Option <&mut Viewport> {
    self.viewports.get_mut (key)
  }

  /// Restore the renderer to the newly created state.
  ///
  /// Calls `Resource::reset` on the current resource state which is allowed to
  /// be overridden to keep some resources persistent.
  pub fn reset (&mut self) {
    Resource::reset (self);
    self.initialize();
  }

  /// Convenience method to call the current frame function on self
  #[inline]
  pub fn do_frame (&mut self, frame : Option <&mut glium::Frame>) {
    (self.frame_fun) (self, frame)
  }

  /// Read the content of the front buffer and save in a PNG file with unique
  /// file name `screenshot-N.png` in the current directory.
  // NOTE: we moved the screenshot implementation into the default module so
  // that drop could be implemented on the default resource type instead of the
  // render type so that the renderer could be deconstructed during a reset()
  pub fn screenshot (&self) {
    let raw : glium::texture::RawImage2d <u8> =
      self.glium_display.read_front_buffer().unwrap();
    let worker_count = SCREENSHOT_WORKER_COUNT
      .fetch_add (1, atomic::Ordering::SeqCst);
    if worker_count < MAX_SCREENSHOT_WORKER_COUNT {
      let _ = std::thread::spawn (move || {
        let mut image_buffer = image::ImageBuffer::from_raw (
          raw.width, raw.height, raw.data.into_owned()).unwrap();
        image_buffer = image::imageops::flip_vertical (&image_buffer);
        let image    = image::DynamicImage::ImageRgba8 (image_buffer);
        // create a file incrementally named 'screenshot-N.png'
        let filepath = rs_utils::file::file_path_incremental_with_extension (
          std::path::Path::new ("screenshot.png")
        ).unwrap();
        println!("saving {}...", filepath.display());
        image.save (filepath).unwrap();
        SCREENSHOT_WORKER_COUNT.fetch_sub (1, atomic::Ordering::SeqCst);
      });
    }
  }

  pub fn report_sizes() {
    use std::mem::size_of;
    use rs_utils::show;
    use crate::{vertex, Camera2d, Camera3d};
    println!("Render report sizes...");
    show!(size_of::<Self>());
    show!(size_of::<R>());
    show!(size_of::<glium::VertexBuffer <vertex::Vert2d>>());
    show!(size_of::<glium::IndexBuffer <u8>>());
    show!(size_of::<glium::texture::Texture2d>());
    show!(size_of::<glium::texture::Texture2dArray>());
    show!(size_of::<Viewport>());
    show!(size_of::<Camera2d>());
    show!(size_of::<Camera3d>());
    vertex::report_sizes();
    println!("...Render report sizes");
  }

  //
  //  private
  //

  /// Set fields to initial state and calls `R::init`. Does not re-initialize
  /// resources.
  fn initialize (&mut self) {
    self.frame_fun     = frame_fun_default::<R>;
    self.clear_color   = [0.0, 0.0, 1.0, 1.0]; // initial clear color: blue
    self.clear_depth   = 1.0;
    self.clear_stencil = 0;
    let (resolution_width, resolution_height) = self.window.inner_size().into();
    log::debug!(resolution:?=(resolution_width, resolution_height);
      "create viewport");
    self.viewports  = {
      let mut v = VecMap::with_capacity (INITIAL_VIEWPORT_VECMAP_CAPACITY);
      assert!{
        v.insert (0, Viewport::new (glium::Rect {
          left: 0, bottom: 0, width: resolution_width, height: resolution_height
        })).is_none()
      }
      v
    };
    R::init (self)
  }
}

impl <R : Resource> Drop for Render <R> {
  fn drop (&mut self) {
    // wait for screenshot worker threads to finish
    while 0 < SCREENSHOT_WORKER_COUNT.load (atomic::Ordering::SeqCst) {
      std::thread::sleep (std::time::Duration::from_millis (100));
    }
  }
}

impl <R : Resource> std::fmt::Debug for Render <R> {
  fn fmt (&self, f : &mut std::fmt::Formatter) -> Result <(), std::fmt::Error> {
    if f.alternate() {
      write!(f, "Render {{
    glium_display: {:?},
    resource: {:p},
    frame_fun: {:p},
    clear_color: {:?},
    clear_depth: {},
    clear_stencil: {},
    viewports: {:?}
}}",
        self.glium_display, &self.resource, &self.frame_fun, self.clear_color,
        self.clear_depth, self.clear_stencil,
        self.viewports.keys().collect::<Vec<_>>()
      )
    } else {
      write!(f, "Render {{ glium_display: {:?}, resource: {:p}, frame_fun: {:p}, \
        clear_color: {:?}, clear_depth: {}, clear_stencil: {}, viewports: {:?} }}",
        self.glium_display, &self.resource, &self.frame_fun, self.clear_color,
        self.clear_depth, self.clear_stencil,
        self.viewports.keys().collect::<Vec<_>>()
      )
    }
  }
}