Skip to main content

gl_utils/render/resource/
mod.rs

1//! A trait for resource types with arbitrary `draw` functionality and a
2//! default implementation that supports switching to a "quad viewport" mode.
3
4use std::iter::FromIterator;
5use std::ops::Deref;
6use std::sync::LazyLock;
7
8use glium;
9use image;
10use strum::{EnumCount, EnumIter, FromRepr};
11use vec_map::VecMap;
12use winit;
13
14use math_utils as math;
15
16use crate::{color, shader, texture, vertex, Render};
17
18pub mod draw2d;
19pub mod draw3d;
20#[cfg(feature="demo")]
21#[cfg_attr(docsrs, doc(cfg(feature="demo")))]
22pub mod demo;
23
24pub use self::draw2d::Draw2d;
25pub use self::draw3d::Draw3d;
26
27////////////////////////////////////////////////////////////////////////////////
28//  constants                                                                 //
29////////////////////////////////////////////////////////////////////////////////
30
31pub const MAIN_VIEWPORT        : usize = 0;
32pub const LOWER_RIGHT_VIEWPORT : usize = MAIN_VIEWPORT;
33pub const UPPER_LEFT_VIEWPORT  : usize = 1;
34pub const UPPER_RIGHT_VIEWPORT : usize = 2;
35pub const LOWER_LEFT_VIEWPORT  : usize = 3;
36pub const OVERLAY_VIEWPORT     : usize = 4;
37
38////////////////////////////////////////////////////////////////////////////////
39//  statics                                                                   //
40////////////////////////////////////////////////////////////////////////////////
41
42static DEFAULT_TEXTURES_16X16_BYTES : LazyLock <VecMap <&'static [u8]>> =
43  LazyLock::new (|| VecMap::from_iter ([
44    ( DefaultTexture16Id::Crosshair         as usize,
45      texture::CROSSHAIR_PNG_FILE_BYTES.as_slice()
46    ), (
47      DefaultTexture16Id::CrosshairInverse  as usize,
48      texture::CROSSHAIR_INVERSE_PNG_FILE_BYTES.as_slice()
49    )
50  ]));
51
52static DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS :
53  LazyLock <VecMap <(&'static [u8], [i16; 2])>> = LazyLock::new (|| VecMap::from_iter ([
54    ( DefaultTexturePointerId::Hand as usize,
55      ( texture::POINTER_HAND_PNG_FILE_BYTES_OFFSET.0.as_slice(),
56        texture::POINTER_HAND_PNG_FILE_BYTES_OFFSET.1
57      )
58    )
59  ]));
60
61////////////////////////////////////////////////////////////////////////////////
62//  typedefs                                                                  //
63////////////////////////////////////////////////////////////////////////////////
64
65pub type PointerTextureIndexRepr = u16;
66
67////////////////////////////////////////////////////////////////////////////////
68//  traits                                                                    //
69////////////////////////////////////////////////////////////////////////////////
70
71/// Represents a generic Glium resource type for a renderer to use as a source
72/// of *drawing* (vertex rendering).
73///
74/// The resource should have all the shader programs, vertex buffers, textures,
75/// etc. necessary to make a `draw` call on a given `glium::Frame`.
76///
77/// The default frame functions will call the `draw_2d` and `draw_3d` methods,
78/// which do nothing by default.
79pub trait Resource {
80  fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
81    -> Self;
82  fn init (_render : &mut Render <Self>) where Self : Sized
83    { /* default: do nothing */ }
84  /// Default replace self with `Self::new`. Can be overridden.
85  fn reset (render : &mut Render <Self>)  where Self : Sized {
86    render.resource = Self::new (&render.glium_display)
87  }
88  fn draw_2d (_render : &Render <Self>, _glium_frame : &mut glium::Frame) where
89    Self : Sized { /* default: do nothing */ }
90  fn draw_3d (_render : &Render <Self>, _glium_frame : &mut glium::Frame) where
91    Self : Sized { /* default: do nothing */ }
92}
93
94////////////////////////////////////////////////////////////////////////////////
95//  structs                                                                   //
96////////////////////////////////////////////////////////////////////////////////
97
98/// A default implementation of `Resource` containing the builtin shader programs and
99/// some pre-defined vertex sources.
100pub struct Default {
101  pub draw2d : Draw2d,
102  pub draw3d : Draw3d,
103  /// Textures of heterogeneous dimensions
104  pub textures_anysize        : VecMap <glium::texture::Texture2d>,
105  /// Custom pointer textures with offsets for non-centered cursors
106  pub textures_pointer : VecMap <(glium::texture::Texture2d, math::Vector2 <i16>)>,
107  /// 128x128 pixel tileset texture with 8x8 tile dimensions
108  pub tileset_128x128_texture : glium::texture::Texture2d,
109  /// 256x256 pixel tileset texture with 16x16 tile dimensions
110  pub tileset_256x256_texture : glium::texture::Texture2d,
111  // TODO: more tilesets?
112  shader_programs             : VecMap <glium::Program>,
113  /// Default 16x16 textures indexed by `DefaultTexture16Id`
114  default_textures_16x16      : glium::texture::Texture2dArray,
115  /// Texture array of 16x16 pixel general purpose textures
116  textures_16x16              : glium::texture::Texture2dArray,
117  /// Texture array of 64x64 pixel general purpose textures
118  textures_64x64              : glium::texture::Texture2dArray
119}
120
121////////////////////////////////////////////////////////////////////////////////
122//  enums                                                                     //
123////////////////////////////////////////////////////////////////////////////////
124
125#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
126  FromRepr)]
127#[repr(u16)]
128pub enum DefaultTexturePointerId {
129  Hand
130}
131
132#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
133  FromRepr)]
134#[repr(u16)]
135pub enum DefaultTexture16Id {
136  // for use with `BLEND_FUNC_NORMAL`
137  Crosshair,
138  // for use with `BLEND_FUNC_INVERT_COLOR`
139  CrosshairInverse
140}
141
142#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
143  FromRepr)]
144#[repr(u16)]
145#[derive(Default)]
146pub enum DefaultTilesetId {
147  // for use with `BLEND_FUNC_NORMAL`
148  #[default]
149  EasciiAcorn128,
150  EasciiAcorn256,
151  // for use with `BLEND_FUNC_INVERT_COLOR`
152  EasciiAcornInverse128,
153  EasciiAcornInverse256
154}
155
156////////////////////////////////////////////////////////////////////////////////
157//  impls                                                                     //
158////////////////////////////////////////////////////////////////////////////////
159
160impl Default {
161  /// Default debug grid vertices (3): [X plane (red), Y plane (green), Z plane
162  /// (blue)]
163  pub fn debug_grid_vertices() -> [vertex::Vert3dOrientationScaleColor; 3] {
164    use color::Glsl;
165    use std::f32::consts::FRAC_PI_2;
166    const HALF_GRID_DIMS : f32 = 0.5 * draw3d::MESH_GRID_DIMS as f32;
167    [
168      // X plane
169      vertex::Vert3dOrientationScaleColor {
170        position:    [HALF_GRID_DIMS, 0.0, HALF_GRID_DIMS],
171        orientation: (*math::Rotation3::from_angle_y (math::Rad (FRAC_PI_2)))
172          .into_col_arrays(),
173        scale:       [1.0, 1.0, 1.0],
174        color:       color::RED.glsl_rgba()
175      },
176      // Y plane
177      vertex::Vert3dOrientationScaleColor {
178        position:    [0.0, HALF_GRID_DIMS, HALF_GRID_DIMS],
179        orientation: (*math::Rotation3::from_angle_x (math::Rad (FRAC_PI_2)))
180          .into_col_arrays(),
181        scale:       [1.0, 1.0, 1.0],
182        color:       color::GREEN.glsl_rgba()
183      },
184      // Z plane
185      vertex::Vert3dOrientationScaleColor {
186        position:    [0.0, 0.0, 0.0],
187        orientation: math::Matrix3::identity().into_col_arrays(),
188        scale:       [1.0, 1.0, 1.0],
189        color:       color::BLUE.glsl_rgba()
190      }
191    ]
192  }
193  /// Returns tile pixel [width, height] of the tileset.
194  #[inline]
195  pub fn tile_dimensions (&self, tileset_id : DefaultTilesetId) -> [u32; 2] {
196    let (width, height) = match tileset_id {
197      DefaultTilesetId::EasciiAcorn128 =>
198        self.tileset_128x128_texture.dimensions(),
199      DefaultTilesetId::EasciiAcorn256 =>
200        self.tileset_256x256_texture.dimensions(),
201      _ => unimplemented!()
202    };
203    debug_assert_eq!(width  % 16, 0);
204    debug_assert_eq!(height % 16, 0);
205    [width / 16, height / 16]
206  }
207  #[inline]
208  pub const fn shader_programs (&self) -> &VecMap <glium::Program> {
209    &self.shader_programs
210  }
211  #[inline]
212  pub fn set_pointer_position (&mut self,
213    display  : &glium::Display <glutin::surface::WindowSurface>,
214    position : math::Point2 <f32>
215  ) {
216    let offset = self.draw2d.draw_pointer.map_or (
217      math::Vector2::zero(),
218      |texture_index| self.textures_pointer.get (texture_index as usize)
219        .unwrap().1.numcast().unwrap()
220    );
221    self.draw2d.set_pointer_vertex (display, position + offset);
222  }
223  #[inline]
224  pub fn set_textures_16x16 (&mut self,
225    textures_16x16 : glium::texture::Texture2dArray
226  ) {
227    assert_eq!(textures_16x16.width(),  16);
228    assert_eq!(textures_16x16.height(), 16);
229    self.textures_16x16 = textures_16x16;
230  }
231  #[inline]
232  pub fn set_textures_64x64 (&mut self,
233    textures_64x64 : glium::texture::Texture2dArray
234  ) {
235    assert_eq!(textures_64x64.width(),  64);
236    assert_eq!(textures_64x64.height(), 64);
237    self.textures_64x64 = textures_64x64;
238  }
239}
240
241impl Resource for Default {
242  fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>) -> Self {
243    // shaders
244    let shader_programs  = shader::build_programs (glium_display).unwrap();
245    // shared resources
246    let default_textures_16x16 =
247      texture::texture2darray_with_mipmaps_from_bytes (
248        glium_display,
249        &DEFAULT_TEXTURES_16X16_BYTES.values().map (Deref::deref)
250          .collect::<Vec <&[u8]>>(),
251        image::ImageFormat::Png,
252        glium::texture::MipmapsOption::NoMipmap
253      ).unwrap();
254    let textures_16x16   =
255      glium::texture::Texture2dArray::empty (glium_display, 16, 16, 0).unwrap();
256    let textures_64x64   =
257      glium::texture::Texture2dArray::empty (glium_display, 64, 64, 0).unwrap();
258    let textures_anysize = VecMap::new();
259    let textures_pointer = DEFAULT_TEXTURES_POINTER_BYTES_OFFSETS.iter()
260      .map (|(i, (bytes, offset))|{
261        let texture = texture::texture2d_with_mipmaps_from_bytes (
262          glium_display, bytes, image::ImageFormat::Png,
263          glium::texture::MipmapsOption::NoMipmap
264        ).unwrap();
265        (i, (texture, math::Vector2::from (*offset)))
266      }).collect();
267    let tileset_128x128_texture = texture::texture2d_with_mipmaps_from_bytes (
268      glium_display,
269      // for use with normal blending
270      texture::TILESET_EASCII_ACORN_8X8_PNG_FILE_BYTES,
271      image::ImageFormat::Png,
272      glium::texture::MipmapsOption::NoMipmap
273    ).unwrap();
274    let tileset_256x256_texture = texture::texture2d_with_mipmaps_from_bytes (
275      glium_display,
276      // for use with normal blending
277      texture::TILESET_EASCII_ACORN_16X16_PNG_FILE_BYTES,
278      image::ImageFormat::Png,
279      glium::texture::MipmapsOption::NoMipmap
280    ).unwrap();
281    // TODO
282    /*
283    let tileset_128x128_inverse_texture =
284      texture::texture2d_with_mipmaps_from_bytes (
285        glium_display,
286        // for use with inverse blending
287        texture::TILESET_EASCII_ACORN_8X8_INVERSE_PNG_FILE_BYTES,
288        image::PNG,
289        glium::texture::MipmapsOption::NoMipmap
290      ).unwrap();
291    */
292
293    let draw2d = Draw2d::new (glium_display);
294    let draw3d = Draw3d::new (glium_display);
295
296    Default {
297      shader_programs,
298      default_textures_16x16,
299      textures_16x16,
300      textures_64x64,
301      textures_anysize,
302      textures_pointer,
303      tileset_128x128_texture,
304      tileset_256x256_texture,
305      draw2d,
306      draw3d
307    }
308  }
309
310  #[inline]
311  fn init (render : &mut Render <Self>) {
312    render.update_viewport_line_loops();
313  }
314
315  #[inline]
316  fn draw_2d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
317    Draw2d::draw (render, glium_frame);
318  }
319
320  #[inline]
321  fn draw_3d (render : &Render <Self>, glium_frame : &mut glium::Frame) {
322    Draw3d::draw (render, glium_frame);
323  } // end fn draw_3d
324
325  #[inline]
326  fn reset (render : &mut Render <Self>) {
327    render.resource.draw2d = Draw2d::new (&render.glium_display);
328    render.resource.draw3d = Draw3d::new (&render.glium_display);
329  }
330} // end impl Resource for DefaultResource
331
332impl Render <Default> {
333
334  #[inline]
335  pub fn camera3d_position_set (&mut self, position : math::Point3 <f32>) {
336    for (_, viewport) in self.viewports.iter_mut() {
337      if viewport.camera3d().is_some() {
338        viewport.camera3d_set_position (position);
339      }
340    }
341  }
342
343  #[inline]
344  pub fn camera3d_orientation_set (&mut self,
345    orientation : math::Rotation3 <f32>
346  ) {
347    for (_, viewport) in self.viewports.iter_mut() {
348      if viewport.camera3d().is_some() {
349        viewport.camera3d_set_orientation (orientation);
350      }
351    }
352  }
353
354  #[inline]
355  pub fn camera3d_look_at (&mut self, target : math::Point3 <f32>) {
356    for (_, viewport) in self.viewports.iter_mut() {
357      if viewport.camera3d().is_some() {
358        viewport.camera3d_look_at (target);
359      }
360    }
361  }
362
363  pub fn camera3d_move_local_xy (&mut self, dx : f32, dy : f32, dz : f32) {
364    self.viewports[MAIN_VIEWPORT].camera3d_move_local_xy (dx, dy, dz);
365    let position = self.viewports[MAIN_VIEWPORT].camera3d().unwrap().position();
366    for (_, viewport) in self.viewports.iter_mut().skip (1) {
367      if viewport.camera3d().is_some() {
368        viewport.camera3d_set_position (position);
369      }
370    }
371  }
372
373  #[inline]
374  pub fn camera3d_rotate (&mut self,
375    dyaw : math::Rad <f32>, dpitch : math::Rad <f32>, droll : math::Rad <f32>
376  ) {
377    self.viewports[MAIN_VIEWPORT].camera3d_rotate (dyaw, dpitch, droll);
378  }
379
380  /// Scales 3D zoom of viewports 1-3.
381  ///
382  /// Does nothing in single viewport mode.
383  ///
384  /// # Panics
385  ///
386  /// Panics if scale is negative.
387  pub fn camera3d_orthographic_zoom_scale (&mut self, scale : f32) {
388    assert!(0.0 < scale);
389    for (_, viewport) in self.viewports.iter_mut().skip(1) {
390      if viewport.camera3d().is_some() {
391        debug_assert!(viewport.camera3d().unwrap().projection()
392          .is_orthographic());
393        viewport.camera3d_scale_fovy_or_zoom (scale);
394      }
395    }
396  }
397
398  /// Scales 3D zoom of viewport 0 (the main viewport) only.
399  ///
400  /// # Panics
401  ///
402  /// Panics if scale is negative.
403  pub fn camera3d_perspective_fovy_scale (&mut self, scale : f32) {
404    assert!(0.0 < scale);
405    debug_assert!(self.viewports[MAIN_VIEWPORT].camera3d().unwrap().projection()
406      .is_perspective());
407    self.viewports[MAIN_VIEWPORT].camera3d_scale_fovy_or_zoom (scale);
408  }
409
410  pub fn camera2d_zoom_set (&mut self, zoom : f32) {
411    assert!(0.0 < zoom);
412    for (_, viewport) in self.viewports.iter_mut() {
413      if viewport.camera2d().is_some() {
414        viewport.camera2d_set_zoom (zoom);
415      }
416    }
417    self.update_viewport_line_loops();
418  }
419
420  /// Modifies the 2D zoom by the given amount for all viewports.
421  pub fn camera2d_zoom_shift (&mut self, shift : f32) {
422    let mut dirty = false;
423    for (_, viewport) in self.viewports.iter_mut() {
424      if viewport.camera2d().is_some() {
425        let zoom = viewport.camera2d().unwrap().zoom() + shift;
426        if 0.0 < zoom {
427          viewport.camera2d_set_zoom (zoom);
428          dirty = true;
429        }
430      }
431    }
432    if dirty {
433      self.update_viewport_line_loops();
434    }
435  }
436
437  pub fn camera2d_move_local (&mut self, dx : f32, dy : f32) {
438    self.viewports[MAIN_VIEWPORT].camera2d_move_local (dx, dy);
439    let position = self.viewports[MAIN_VIEWPORT].camera2d().unwrap().position();
440    for (_, viewport) in self.viewports.iter_mut().skip (1) {
441      if viewport.camera2d().is_some() {
442        viewport.camera2d_set_position (position);
443      }
444    }
445  }
446
447  /// Moves origin for all viewports to lower left
448  pub fn camera2d_move_origin_to_bottom_left (&mut self) {
449    for (_, viewport) in self.viewports.iter_mut() {
450      if viewport.camera2d().is_some() {
451        viewport.camera2d_move_origin_to_bottom_left()
452      }
453    }
454    self.update_viewport_line_loops();
455  }
456
457  /// Resize the viewport(s) based on the `LogicalSize` that was returned by a
458  /// `WindowEvent::Resized` event
459  // TODO: come up with a better scheme for quad viewports, overlay viewports
460  pub fn window_resized (&mut self,
461    physical_size : winit::dpi::PhysicalSize <u32>
462  ) {
463    let (width, height) = physical_size.into();
464    if self.viewports.len() < 4 {
465      self.viewports[MAIN_VIEWPORT].set_rect (
466        glium::Rect { left: 0, bottom: 0, width, height }
467      );
468      self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
469        viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
470    } else {
471      // quad viewports
472      let left_width    = left_width   (width);
473      let right_width   = right_width  (width);
474      let upper_height  = upper_height (height);
475      let lower_height  = lower_height (height);
476      self.viewports[LOWER_RIGHT_VIEWPORT].set_rect (
477        glium::Rect {
478          width:  right_width,
479          height: lower_height,
480          left:   left_width,
481          bottom: 0
482        }
483      );
484      self.viewports[UPPER_LEFT_VIEWPORT].set_rect (
485        glium::Rect {
486          width:  left_width,
487          height: upper_height,
488          left:   0,
489          bottom: lower_height
490        }
491      );
492      self.viewports[UPPER_RIGHT_VIEWPORT].set_rect (
493        glium::Rect {
494          width:  right_width,
495          height: upper_height,
496          left:   left_width,
497          bottom: lower_height
498        }
499      );
500      self.viewports[LOWER_LEFT_VIEWPORT].set_rect (
501        glium::Rect {
502          width:  left_width,
503          height: lower_height,
504          left:   0,
505          bottom: 0
506        }
507      );
508      self.viewports.get_mut (OVERLAY_VIEWPORT).map (|viewport|
509        viewport.set_rect (glium::Rect { left: 0, bottom: 0, width, height }));
510    }
511    self.update_viewport_line_loops();
512  }
513
514  /// Updates vertex and index buffers for viewport line loops.
515  ///
516  /// Should be called whenever viewport sizes or 2D camera zoom or position
517  /// changes.
518  fn update_viewport_line_loops (&mut self) {
519    const VERTS_PER_VIEWPORT : usize = 4;
520    let num_vertices = VERTS_PER_VIEWPORT * self.viewports.len();
521    let vertices = {
522      let mut vertices = Vec::<vertex::Vert2d>::with_capacity (num_vertices);
523      for (_, viewport) in self.viewports.iter() {
524        if let Some (camera2d) = viewport.camera2d() {
525          // pixels are centered on 0.5 unit centers
526          let pixel_radius = 0.5 / camera2d.zoom();
527          let position     = camera2d.position();
528          let ortho        = camera2d.ortho();
529          let left         = position.0.x + ortho.left;
530          let bottom       = position.0.y + ortho.bottom;
531          let top          = position.0.y + ortho.top;
532          let right        = position.0.x + ortho.right;
533          vertices.append (&mut vec![
534            vertex::Vert2d {
535              position: [left  + pixel_radius, bottom + pixel_radius] },
536            vertex::Vert2d {
537              position: [left  + pixel_radius, top    - pixel_radius] },
538            vertex::Vert2d {
539              position: [right - pixel_radius, top    - pixel_radius] },
540            vertex::Vert2d {
541              position: [right - pixel_radius, bottom + pixel_radius] }
542          ]);
543        }
544      }
545      vertices
546    };
547    self.resource.draw2d
548      .line_loop_vertices_set (&self.glium_display, &vertices[..]);
549  }
550}
551
552
553////////////////////////////////////////////////////////////////////////////////
554//  private functions                                                         //
555////////////////////////////////////////////////////////////////////////////////
556
557// some functions to compute viewport widths for odd window dimensions:
558// right viewports will be 1 pixel wider on odd width windows and bottom
559// viewports will be 1 pixel taller on odd height windows
560
561#[inline]
562const fn left_width (window_width : u32) -> u32 {
563  window_width / 2
564}
565#[inline]
566const fn right_width (window_width : u32) -> u32 {
567  window_width / 2 + window_width % 2
568}
569#[inline]
570const fn upper_height (window_height : u32) -> u32 {
571  window_height / 2
572}
573#[inline]
574const fn lower_height (window_height : u32) -> u32 {
575  window_height / 2 + window_height % 2
576}