Skip to main content

gl_utils/render/
mod.rs

1//! Support types for Glium rendering.
2//!
3//! The `Render` struct holds all the context information and resources
4//! required for making calls to OpenGL render commands (via `glium`).
5
6use std;
7use std::sync::atomic;
8use glium;
9use glutin;
10use image;
11use log;
12use rs_utils;
13use vec_map::VecMap;
14
15pub mod params;
16pub mod resource;
17pub mod viewport;
18
19pub use self::resource::Resource;
20pub use self::viewport::Viewport;
21
22/// Initialize viewport `VecMap` with this capacity
23const INITIAL_VIEWPORT_VECMAP_CAPACITY : usize = 4;
24/// Sets a limit to how many simultaneous screenshot worker threads may be
25/// running simultaneously-- should be more than enough.
26const MAX_SCREENSHOT_WORKER_COUNT : u8 = 8;
27/// Prevents renderers from dropping while screenshot workers are not yet
28/// finished.
29///
30/// Note that this is a global variable so it is shared among all renderers
31/// and it will not prevent other threads from terminating if not synchronized
32/// with the render thread.
33static SCREENSHOT_WORKER_COUNT : atomic::AtomicU8 = atomic::AtomicU8::new (0);
34
35/// State for Glium-based rendering.
36///
37/// Holds all the context information and resources required for rendering. A
38/// frame function may be specified by the `frame_fun` field which is a
39/// function pointer that takes a mutable self reference for the purpose of
40/// making Glium render commands.
41///
42/// OpenGL has four types of *render commands*:
43///
44/// 1. Framebuffer clearing commands
45/// 2. Framebuffer blitting commands
46/// 3. Drawing commands (*vertex rendering*)
47/// 4. Compute dispatch commands (OpenGL 4.3+ only)
48///
49/// All resources (vertex and index buffers, textures and other uniforms)
50/// should be in the user defined generic resource type `R : Resource`.
51pub struct Render <R : Resource = resource::Default> {
52  /// The `glium` context represented by a `glium::backend::Context` and a
53  /// `glutin::surface::WindowSurface`
54  pub glium_display : glium::Display <glutin::surface::WindowSurface>,
55  pub window        : winit::window::Window,
56  /// A function for rendering a single frame
57  pub resource      : R,
58  pub frame_fun     : fn (&mut Render <R>, Option <&mut glium::Frame>),
59  pub clear_color   : [f32; 4],
60  pub clear_depth   : f32,
61  pub clear_stencil : i32,
62  viewports         : VecMap <Viewport>
63}
64
65/// Default frame function clears all and calls resource `draw_3d` method
66/// followed by `draw_2d` method.
67pub fn frame_fun_default <R : Resource> (
68  render : &mut Render <R>, frame : Option <&mut glium::Frame>
69) {
70  use glium::Surface;
71  log::trace!("frame fun default...");
72  let mut maybe_frame : Option <glium::Frame> = None;
73  let glium_frame = match frame {
74    Some (frame) => frame,
75    None         => {
76      maybe_frame = Some (render.glium_display.draw());
77      maybe_frame.as_mut().unwrap()
78    }
79  };
80  glium_frame.clear_all (
81    render.clear_color_tuple(), render.clear_depth, render.clear_stencil);
82  Resource::draw_3d (render, glium_frame);
83  Resource::draw_2d (render, glium_frame);
84  if let Some (glium_frame) = maybe_frame.take() {
85    glium_frame.finish().unwrap();
86  }
87  log::trace!("...frame fun default");
88}
89
90/// Default 2D frame function clears all and calls resource `draw_2d` method.
91pub fn frame_fun_default_2d <R : Resource> (
92  render : &mut Render <R>, frame : Option <&mut glium::Frame>
93) {
94  use glium::Surface;
95  log::trace!("frame fun default 2d...");
96  let mut maybe_frame = None;
97  let glium_frame = match frame {
98    Some (frame) => frame,
99    None         => {
100      maybe_frame = Some (render.glium_display.draw());
101      maybe_frame.as_mut().unwrap()
102    }
103  };
104  glium_frame.clear_all (
105    render.clear_color_tuple(), render.clear_depth, render.clear_stencil);
106  Resource::draw_2d (render, glium_frame);
107  if let Some (glium_frame) = maybe_frame.take() {
108    glium_frame.finish().unwrap();
109  }
110  log::trace!("...frame fun default 2d");
111}
112
113/// Default 3D frame function clears all and calls resource `draw_3d` method.
114pub fn frame_fun_default_3d <R : Resource> (
115  render : &mut Render <R>, frame : Option <&mut glium::Frame>
116) {
117  use glium::Surface;
118  log::trace!("frame fun default 3d...");
119  let mut maybe_frame = None;
120  let glium_frame = match frame {
121    Some (frame) => frame,
122    None         => {
123      maybe_frame = Some (render.glium_display.draw());
124      maybe_frame.as_mut().unwrap()
125    }
126  };
127  glium_frame.clear_all (
128    render.clear_color_tuple(), render.clear_depth, render.clear_stencil);
129  Resource::draw_3d (render, glium_frame);
130  if let Some (glium_frame) = maybe_frame.take() {
131    glium_frame.finish().unwrap();
132  }
133  log::trace!("...frame fun default 3d");
134}
135
136impl <R : Resource> Render <R> {
137  /// Creates a new renderer with default viewport and resources.
138  pub fn new (
139    glium_display : glium::Display <glutin::surface::WindowSurface>,
140    window        : winit::window::Window
141  ) -> Self {
142    let resource = R::new (&glium_display);
143    let frame_fun     = frame_fun_default::<R>;
144    let clear_color   = Default::default();
145    let clear_depth   = Default::default();
146    let clear_stencil = Default::default();
147    let viewports     = Default::default();
148    let mut render = Render {
149      glium_display, window, frame_fun, clear_color, clear_depth, clear_stencil,
150      viewports, resource
151    };
152    render.initialize();
153    render
154  }
155
156  /// Internally the clear color is stored as an array, but the glium clear
157  /// command takes a tuple
158  #[inline]
159  pub const fn clear_color_tuple(&self) -> (f32, f32, f32, f32) {
160    let [r, g, b, a] = self.clear_color;
161    (r, g, b, a)
162  }
163
164  pub const fn viewports (&self) -> &VecMap <Viewport> {
165    &self.viewports
166  }
167
168  pub const fn viewports_mut (&mut self) -> &mut VecMap <Viewport> {
169    &mut self.viewports
170  }
171
172  pub fn get_viewport (&self, key : usize) -> Option <&Viewport> {
173    self.viewports.get (key)
174  }
175
176  pub fn get_viewport_mut (&mut self, key : usize) -> Option <&mut Viewport> {
177    self.viewports.get_mut (key)
178  }
179
180  /// Restore the renderer to the newly created state.
181  ///
182  /// Calls `Resource::reset` on the current resource state which is allowed to
183  /// be overridden to keep some resources persistent.
184  pub fn reset (&mut self) {
185    Resource::reset (self);
186    self.initialize();
187  }
188
189  /// Convenience method to call the current frame function on self
190  #[inline]
191  pub fn do_frame (&mut self, frame : Option <&mut glium::Frame>) {
192    (self.frame_fun) (self, frame)
193  }
194
195  /// Read the content of the front buffer and save in a PNG file with unique
196  /// file name `screenshot-N.png` in the current directory.
197  // NOTE: we moved the screenshot implementation into the default module so
198  // that drop could be implemented on the default resource type instead of the
199  // render type so that the renderer could be deconstructed during a reset()
200  pub fn screenshot (&self) {
201    let raw : glium::texture::RawImage2d <u8> =
202      self.glium_display.read_front_buffer().unwrap();
203    let worker_count = SCREENSHOT_WORKER_COUNT
204      .fetch_add (1, atomic::Ordering::SeqCst);
205    if worker_count < MAX_SCREENSHOT_WORKER_COUNT {
206      let _ = std::thread::spawn (move || {
207        let mut image_buffer = image::ImageBuffer::from_raw (
208          raw.width, raw.height, raw.data.into_owned()).unwrap();
209        image_buffer = image::imageops::flip_vertical (&image_buffer);
210        let image    = image::DynamicImage::ImageRgba8 (image_buffer);
211        // create a file incrementally named 'screenshot-N.png'
212        let filepath = rs_utils::file::file_path_incremental_with_extension (
213          std::path::Path::new ("screenshot.png")
214        ).unwrap();
215        println!("saving {}...", filepath.display());
216        image.save (filepath).unwrap();
217        SCREENSHOT_WORKER_COUNT.fetch_sub (1, atomic::Ordering::SeqCst);
218      });
219    }
220  }
221
222  pub fn report_sizes() {
223    use std::mem::size_of;
224    use rs_utils::show;
225    use crate::{vertex, Camera2d, Camera3d};
226    println!("Render report sizes...");
227    show!(size_of::<Self>());
228    show!(size_of::<R>());
229    show!(size_of::<glium::VertexBuffer <vertex::Vert2d>>());
230    show!(size_of::<glium::IndexBuffer <u8>>());
231    show!(size_of::<glium::texture::Texture2d>());
232    show!(size_of::<glium::texture::Texture2dArray>());
233    show!(size_of::<Viewport>());
234    show!(size_of::<Camera2d>());
235    show!(size_of::<Camera3d>());
236    vertex::report_sizes();
237    println!("...Render report sizes");
238  }
239
240  //
241  //  private
242  //
243
244  /// Set fields to initial state and calls `R::init`. Does not re-initialize
245  /// resources.
246  fn initialize (&mut self) {
247    self.frame_fun     = frame_fun_default::<R>;
248    self.clear_color   = [0.0, 0.0, 1.0, 1.0]; // initial clear color: blue
249    self.clear_depth   = 1.0;
250    self.clear_stencil = 0;
251    let (resolution_width, resolution_height) = self.window.inner_size().into();
252    log::debug!(resolution:?=(resolution_width, resolution_height);
253      "create viewport");
254    self.viewports  = {
255      let mut v = VecMap::with_capacity (INITIAL_VIEWPORT_VECMAP_CAPACITY);
256      assert!{
257        v.insert (0, Viewport::new (glium::Rect {
258          left: 0, bottom: 0, width: resolution_width, height: resolution_height
259        })).is_none()
260      }
261      v
262    };
263    R::init (self)
264  }
265}
266
267impl <R : Resource> Drop for Render <R> {
268  fn drop (&mut self) {
269    // wait for screenshot worker threads to finish
270    while 0 < SCREENSHOT_WORKER_COUNT.load (atomic::Ordering::SeqCst) {
271      std::thread::sleep (std::time::Duration::from_millis (100));
272    }
273  }
274}
275
276impl <R : Resource> std::fmt::Debug for Render <R> {
277  fn fmt (&self, f : &mut std::fmt::Formatter) -> Result <(), std::fmt::Error> {
278    if f.alternate() {
279      write!(f, "Render {{
280    glium_display: {:?},
281    resource: {:p},
282    frame_fun: {:p},
283    clear_color: {:?},
284    clear_depth: {},
285    clear_stencil: {},
286    viewports: {:?}
287}}",
288        self.glium_display, &self.resource, &self.frame_fun, self.clear_color,
289        self.clear_depth, self.clear_stencil,
290        self.viewports.keys().collect::<Vec<_>>()
291      )
292    } else {
293      write!(f, "Render {{ glium_display: {:?}, resource: {:p}, frame_fun: {:p}, \
294        clear_color: {:?}, clear_depth: {}, clear_stencil: {}, viewports: {:?} }}",
295        self.glium_display, &self.resource, &self.frame_fun, self.clear_color,
296        self.clear_depth, self.clear_stencil,
297        self.viewports.keys().collect::<Vec<_>>()
298      )
299    }
300  }
301}