Skip to main content

gl_utils/render/resource/
draw2d.rs

1use derive_more::{From, TryInto};
2use glium::{self, uniform};
3use strum::{EnumCount, EnumIter, FromRepr};
4use vec_map::VecMap;
5
6use crate::{camera2d, color, graphics, math, render, shader, vertex, Camera2d,
7  Render};
8use super::{DefaultTexture16Id, DefaultTilesetId, PointerTextureIndexRepr};
9
10/// 2D drawing resources
11pub struct Draw2d {
12  // rectangles
13  /// 2D rectangle vertices with sub-ranges mapped to viewports by
14  /// `viewport_resources[i].draw_indices[j].rectangle`.
15  pub rectangle_2d_vertices     : glium::VertexBuffer <vertex::Vert2dRectColor>,
16  // line loops
17  /// Vertices to be rendered as line loops
18  pub line_loop_vertices        : glium::VertexBuffer <vertex::Vert2d>,
19  // sprites
20  /// Position and texture index of 2d sprites indexing the 16x16 texture array
21  pub sprite_16x16_vertices     : glium::VertexBuffer <vertex::Vert2dLayer>,
22  /// Position and texture index of 2d sprites indexing the 64x64 texture array
23  pub sprite_64x64_vertices     : glium::VertexBuffer <vertex::Vert2dLayer>,
24  /// Position and texture index of 2d textured rectangles indexing the 16x16
25  /// texture array
26  pub rectangle_16x16_vertices  :
27    glium::VertexBuffer <vertex::Vert2dRectUvLayer>,
28  /// Position and texture index of 2d textured rectangles indexing the 64x64
29  /// texture array
30  pub rectangle_64x64_vertices  :
31    glium::VertexBuffer <vertex::Vert2dRectUvLayer>,
32  pub rectangle_anysize_vertices :
33    VecMap <glium::VertexBuffer <vertex::Vert2dRectUv>>,
34  /// Position of optional pointer sprite
35  pointer_vertex                : Option <glium::VertexBuffer <vertex::Vert2d>>,
36  /// Draw the pointer vertex *if* the vertex has been set
37  pub draw_pointer              : Option <PointerTextureIndexRepr>,
38  /// Position and texture index of 2d sprites indexing the 16x16 texture array.
39  /// This buffer contains built-in default vertices such as the crosshair
40  /// vertex.
41  default_sprite_16x16_vertices : glium::VertexBuffer <vertex::Vert2dLayer>,
42  // tiles
43  /// 2D tile vertices with sub-ranges mapped to viewports by
44  /// `viewport_resources[i].draw_tiles[j].vertex_range`.
45  pub tile_2d_vertices          : glium::VertexBuffer <vertex::Vert2dTile>,
46  /// 2D tile vertices with sub-ranges mapped to viewports by
47  /// `viewport_resources[i].draw_color_tiles[j].vertex_range`.
48  pub tile_color_2d_vertices    : glium::VertexBuffer <vertex::Vert2dTileColor>,
49  // viewport data
50  /// Data used when drawing each viewport's 2D rectangles and tiles
51  viewport_resources            : VecMap <ViewportResources>,
52}
53
54#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, EnumCount, EnumIter,
55  FromRepr)]
56#[repr(u16)]
57pub enum DefaultSprite16Id {
58  // Index into `default_sprites_16x16_vertices` for the crosshair.
59  //
60  // In `draw_2d` this vertex is drawn as a separate draw call.
61  Crosshair
62}
63
64/// Defines indices for drawing rectangles, tiles, and whether or not to draw
65/// crosshair and line loop
66#[derive(Clone, Debug)]
67pub struct ViewportResources {
68  pub draw_indices   : Vec <DrawIndices>,
69  pub draw_crosshair : bool,
70  pub draw_lineloop  : bool
71}
72
73/// Defines a set of rectangle and tile calls
74#[derive(Clone, Debug, Default)]
75pub struct DrawIndices {
76  /// Index into the `rectangle_2d_vertices` buffer
77  pub rectangle        : Option <u32>,
78  /// `Tile` draw call
79  pub draw_tiles       : Option <Tiles>,
80  /// `TileColor` draw call
81  pub draw_color_tiles : Option <Tiles>
82}
83
84/// Defines a draw call with the `tile_2d_vertices` buffer as a vertex source
85#[derive(Clone, Debug)]
86pub struct Tiles {
87  /// Each range defines the tiles in the `tile_2d_vertices` or
88  /// `tile_color_2d_vertices` to be drawn with the defined offset.
89  pub vertex_range : std::ops::Range <u32>,
90  /// Origin is either in world space or relative to upper-left corner of
91  /// viewport
92  pub origin       : TilesOrigin,
93  pub tileset_id   : DefaultTilesetId
94}
95
96/// Selects the origin for a tile-based draw call
97#[derive(Clone, Debug, From, TryInto)]
98pub enum TilesOrigin {
99  /// Origin is at the upper-left corner of the viewport.
100  ///
101  /// Offset can be used to shift the origin relative to the upper-left corner
102  /// of the viewport.
103  Viewport {
104    /// A positive offset will shift the tile grid relative to the left of the
105    /// viewport by this many pixels
106    offset_px_left : i16,
107    /// A positive offset will shift the tile grid downward from the top of the
108    /// viewport by this many pixels
109    offset_px_top  : i16
110  },
111  /// Origin is at the world origin
112  World
113}
114
115impl Draw2d {
116  pub fn new (glium_display : &glium::Display <glutin::surface::WindowSurface>)
117    -> Self
118  {
119    let rectangle_2d_vertices =
120      glium::VertexBuffer::dynamic (glium_display, &[ ]).unwrap();
121    let pointer_vertex = None;
122    let draw_pointer   = None;
123    let default_sprite_16x16_vertices = glium::VertexBuffer::immutable (
124      glium_display,
125      // crosshair
126      &[vertex::Vert2dLayer {
127        position: [0.0, 0.0],
128        layer:    DefaultTexture16Id::CrosshairInverse as u16
129      }]
130    ).unwrap();
131    let sprite_16x16_vertices =
132      glium::VertexBuffer::empty_immutable (glium_display, 0).unwrap();
133    let sprite_64x64_vertices =
134      glium::VertexBuffer::empty_immutable (glium_display, 0).unwrap();
135    let rectangle_16x16_vertices =
136      glium::VertexBuffer::empty_immutable (glium_display, 0).unwrap();
137    let rectangle_64x64_vertices =
138      glium::VertexBuffer::empty_immutable (glium_display, 0).unwrap();
139    let rectangle_anysize_vertices = VecMap::new();
140    let tile_2d_vertices = glium::VertexBuffer::dynamic (glium_display, &[ ])
141      .unwrap();
142    let tile_color_2d_vertices = glium::VertexBuffer::dynamic (glium_display, &[ ])
143      .unwrap();
144    let viewport_resources =
145      VecMap::with_capacity (render::INITIAL_VIEWPORT_VECMAP_CAPACITY);
146    let line_loop_vertices =
147      glium::VertexBuffer::dynamic (glium_display, &[ ]).unwrap();
148    Draw2d {
149      rectangle_2d_vertices,
150      pointer_vertex,
151      draw_pointer,
152      default_sprite_16x16_vertices,
153      sprite_16x16_vertices,
154      sprite_64x64_vertices,
155      rectangle_16x16_vertices,
156      rectangle_64x64_vertices,
157      rectangle_anysize_vertices,
158      tile_2d_vertices,
159      tile_color_2d_vertices,
160      viewport_resources,
161      line_loop_vertices
162    }
163  }
164
165  pub fn draw (
166    render      : &Render <render::resource::Default>,
167    glium_frame : &mut glium::Frame
168  ) {
169    use color::{IntoArray, Normalize, WithAlphaOpaque};
170    use glium::Surface;
171    let draw2d = &render.resource.draw2d;
172    // primitives
173    const POINTS : glium::index::IndicesSource =
174      glium::index::IndicesSource::NoIndices {
175        primitives: glium::index::PrimitiveType::Points
176      };
177    const LINE_LOOP : glium::index::IndicesSource =
178      glium::index::IndicesSource::NoIndices {
179        primitives: glium::index::PrimitiveType::LineLoop
180      };
181    // draw each viewport 2d
182    for (i, viewport) in render.viewports.iter() {
183      if let Some (camera2d) = viewport.camera2d() {
184        // draw parameters
185        let draw_parameters_viewport     = viewport.draw_parameters();
186        let draw_parameters_blend_normal = glium::DrawParameters {
187          blend: render::params::BLEND_FUNC_NORMAL,
188          .. draw_parameters_viewport.clone()
189        };
190        let draw_parameters_blend_invert = glium::DrawParameters {
191          // NOTE: inverse blending does not really have an effect when
192          // rendering with 'Nearest' magnify filter below
193          blend: render::params::BLEND_FUNC_INVERT_COLOR,
194          .. draw_parameters_viewport.clone()
195        };
196        // samplers
197        let tileset_sampler_128 = render.resource.tileset_128x128_texture
198          .sampled()
199          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
200        let tileset_sampler_256 = render.resource.tileset_256x256_texture
201          .sampled()
202          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
203        let tileset_sampler = |tileset_id| match tileset_id {
204          DefaultTilesetId::EasciiAcorn128 => tileset_sampler_128,
205          DefaultTilesetId::EasciiAcorn256 => tileset_sampler_256,
206          _ => unimplemented!()
207        };
208        let default_textures_16x16_sampler =
209          render.resource.default_textures_16x16.sampled()
210            .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
211        let textures_16x16_sampler = render.resource.textures_16x16.sampled()
212          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
213        let textures_64x64_sampler = render.resource.textures_64x64.sampled()
214          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
215        // matrices
216        let (transform_mat_world_to_view, projection_mat_ortho) =
217          camera2d.view_ortho_mats();
218        // uniforms
219        let uniforms = uniform!{
220          uni_transform_mat_view:   transform_mat_world_to_view,
221          uni_projection_mat_ortho: projection_mat_ortho,
222          uni_color:                color::DEBUG_GREY.normalize().rgba().into_array()
223        };
224        // draw per-viewport resources
225        if let Some (resources) = draw2d.viewport_resources.get (i) {
226          for draw_indices in resources.draw_indices.iter() {
227            if let Some (r) = draw_indices.rectangle.map (|r| r as usize) {
228              //////////////////////////////////////////////////////////////////
229              //  draw rectangle
230              //////////////////////////////////////////////////////////////////
231              glium_frame.draw (
232                draw2d.rectangle_2d_vertices.slice (r..r+1).unwrap(),
233                POINTS.clone(),
234                &render.resource.shader_programs [
235                  shader::ProgramId::WorldSpace2dRect as usize],
236                &uniforms,
237                &draw_parameters_blend_normal
238              ).unwrap();
239              // end draw rectangle
240            }
241            if let Some (draw_tiles) = draw_indices.draw_tiles.as_ref() {
242              //////////////////////////////////////////////////////////////////
243              //  draw tiles (inverse)
244              //////////////////////////////////////////////////////////////////
245              let tile_origin = draw_tiles.origin.to_world (camera2d).0.into_array();
246              let uniforms = uniform!{
247                uni_transform_mat_view:   transform_mat_world_to_view,
248                uni_projection_mat_ortho: projection_mat_ortho,
249                // upper left corner
250                uni_tile_space_origin:    tile_origin,
251                uni_sampler2d_tileset:    tileset_sampler (draw_tiles.tileset_id)
252              };
253              glium_frame.draw (
254                draw2d.tile_2d_vertices.slice (
255                  draw_tiles.vertex_range.start as usize..
256                  draw_tiles.vertex_range.end   as usize
257                ).unwrap(),
258                POINTS.clone(),
259                &render.resource
260                  .shader_programs [shader::ProgramId::TileSpace2dTile as usize],
261                &uniforms,
262                &draw_parameters_blend_invert
263              ).unwrap();
264              // end draw tiles (inverse)
265            }
266            // draw color tiles
267            if let Some (draw_tiles) = draw_indices.draw_color_tiles.as_ref() {
268              //////////////////////////////////////////////////////////////////
269              //  draw color tiles
270              //////////////////////////////////////////////////////////////////
271              let tile_origin = draw_tiles.origin.to_world (camera2d).0.into_array();
272              let uniforms = uniform!{
273                uni_transform_mat_view:   transform_mat_world_to_view,
274                uni_projection_mat_ortho: projection_mat_ortho,
275                // upper left corner
276                uni_tile_space_origin:    tile_origin,
277                uni_sampler2d_tileset:    tileset_sampler (draw_tiles.tileset_id)
278              };
279              glium_frame.draw (
280                draw2d.tile_color_2d_vertices.slice (
281                  draw_tiles.vertex_range.start as usize..
282                  draw_tiles.vertex_range.end   as usize
283                ).unwrap(),
284                POINTS.clone(),
285                &render.resource.shader_programs [
286                  shader::ProgramId::TileSpace2dTileColor as usize],
287                &uniforms,
288                &draw_parameters_blend_normal
289              ).unwrap();
290              // end draw color tiles
291            }
292          }
293          if resources.draw_crosshair {
294            ////////////////////////////////////////////////////////////////////
295            //  draw crosshair
296            ////////////////////////////////////////////////////////////////////
297            let transform_mat_world_to_view =
298              camera2d::transform_mat_world_to_view (
299                [0.0, 0.0].into(),
300                math::Rotation2::identity()
301              ).into_col_arrays();
302            let uniforms = uniform!{
303              uni_transform_mat_view:   transform_mat_world_to_view,
304              uni_projection_mat_ortho: projection_mat_ortho,
305              uni_sampler2darray:       default_textures_16x16_sampler
306            };
307            glium_frame.draw (
308              draw2d.default_sprite_16x16_vertices.slice (
309                (DefaultSprite16Id::Crosshair as usize)..
310                (DefaultSprite16Id::Crosshair as usize + 1)
311              ).unwrap(),
312              POINTS.clone(),
313              &render.resource.shader_programs [
314                shader::ProgramId::WorldSpace2dSpriteLayer as usize],
315              &uniforms,
316              &draw_parameters_blend_invert
317            ).unwrap();
318            // end draw crosshair
319          }
320          if resources.draw_lineloop {
321            ////////////////////////////////////////////////////////////////////
322            //  draw viewport line loop
323            ////////////////////////////////////////////////////////////////////
324            let base_index = i * 4;
325            glium_frame.draw (
326              draw2d.line_loop_vertices.slice (base_index..(base_index + 4))
327                .unwrap(),
328              LINE_LOOP.clone(),
329              &render.resource.shader_programs [
330                shader::ProgramId::WorldSpace2dUniColor as usize],
331              &uniforms,
332              &draw_parameters_blend_normal
333            ).unwrap();
334            // end draw viewport line loop
335          }
336        } // end draw per-viewport resources
337        ////////////////////////////////////////////////////////////////////////
338        //  draw sprites 16x16
339        ////////////////////////////////////////////////////////////////////////
340        let uniforms = uniform!{
341          uni_transform_mat_view:   transform_mat_world_to_view,
342          uni_projection_mat_ortho: projection_mat_ortho,
343          uni_sampler2darray:       textures_16x16_sampler
344        };
345        glium_frame.draw (
346          draw2d.sprite_16x16_vertices.slice (..).unwrap(),
347          POINTS.clone(),
348          &render.resource.shader_programs [
349            shader::ProgramId::WorldSpace2dSpriteLayer as usize],
350          &uniforms,
351          &draw_parameters_blend_normal
352        ).unwrap();
353        ////////////////////////////////////////////////////////////////////////
354        //  draw rectangles 16x16
355        ////////////////////////////////////////////////////////////////////////
356        glium_frame.draw (
357          draw2d.rectangle_16x16_vertices.slice (..).unwrap(),
358          POINTS.clone(),
359          &render.resource.shader_programs [
360            shader::ProgramId::WorldSpace2dRectUvLayer as usize],
361          &uniforms,
362          &draw_parameters_blend_normal
363        ).unwrap();
364        ////////////////////////////////////////////////////////////////////////
365        //  draw sprites 64x64
366        ////////////////////////////////////////////////////////////////////////
367        let uniforms = uniform!{
368          uni_transform_mat_view:   transform_mat_world_to_view,
369          uni_projection_mat_ortho: projection_mat_ortho,
370          uni_sampler2darray:       textures_64x64_sampler
371        };
372        glium_frame.draw (
373          draw2d.sprite_64x64_vertices.slice (..).unwrap(),
374          POINTS.clone(),
375          &render.resource.shader_programs [
376            shader::ProgramId::WorldSpace2dSpriteLayer as usize],
377          &uniforms,
378          &draw_parameters_blend_normal
379        ).unwrap();
380        ////////////////////////////////////////////////////////////////////////
381        //  draw rectangles 64x64
382        ////////////////////////////////////////////////////////////////////////
383        glium_frame.draw (
384          draw2d.rectangle_64x64_vertices.slice (..).unwrap(),
385          POINTS.clone(),
386          &render.resource.shader_programs [
387            shader::ProgramId::WorldSpace2dRectUvLayer as usize],
388          &uniforms,
389          &draw_parameters_blend_normal
390        ).unwrap();
391        ////////////////////////////////////////////////////////////////////////
392        //  draw rectangles anysize
393        ////////////////////////////////////////////////////////////////////////
394        for (i, buffer) in draw2d.rectangle_anysize_vertices.iter() {
395          let uniforms = uniform!{
396            uni_transform_mat_view:   transform_mat_world_to_view,
397            uni_projection_mat_ortho: projection_mat_ortho,
398            uni_sampler2d:            render.resource
399              .textures_anysize.get (i).unwrap().sampled()
400          };
401          glium_frame.draw (
402            buffer,
403            POINTS.clone(),
404            &render.resource.shader_programs [
405              shader::ProgramId::WorldSpace2dRectUv as usize],
406            &uniforms,
407            &draw_parameters_blend_normal
408          ).unwrap();
409        }
410      } // end loop over viewports
411    }
412    if let Some (buffer) = draw2d.pointer_vertex.as_ref() &&
413      let Some (texture_index) = draw2d.draw_pointer
414    {
415      ////////////////////////////////////////////////////////////////////////
416      //  draw pointer
417      ////////////////////////////////////////////////////////////////////////
418      let pointer_texture_sampler =
419        render.resource.textures_pointer.get (texture_index as usize).unwrap()
420          .0.sampled()
421          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
422      let (width, height) = render.glium_display.get_framebuffer_dimensions();
423      let transform_mat_world_to_view = camera2d::transform_mat_world_to_view (
424        [width as f32 / 2.0, height as f32 / 2.0].into(),
425        math::Rotation2::identity()
426      ).into_col_arrays();
427      let projection_mat_ortho = graphics::projection_mat_orthographic (
428        &Camera2d::ortho_from_viewport_zoom (width as u16, height as u16, 1.0)
429      ).into_col_arrays();
430      let draw_parameters = glium::DrawParameters {
431        blend:    render::params::BLEND_FUNC_NORMAL,
432        viewport: Some (glium::Rect {
433          left:   0,
434          bottom: 0,
435          width, height
436        }),
437        .. Default::default()
438      };
439      let uniforms = uniform!{
440        uni_transform_mat_view:   transform_mat_world_to_view,
441        uni_projection_mat_ortho: projection_mat_ortho,
442        uni_sampler2d:            pointer_texture_sampler
443      };
444      glium_frame.draw (
445        buffer,
446        POINTS.clone(),
447        &render.resource.shader_programs [
448          shader::ProgramId::WorldSpace2dSprite as usize],
449        &uniforms,
450        &draw_parameters
451      ).unwrap();
452    }
453  }
454
455  #[inline]
456  pub fn viewport_resources_get (&self, key : usize)
457    -> Option <&ViewportResources>
458  {
459    self.viewport_resources.get (key)
460  }
461  /// Set the viewport tile data for the specified viewport.
462  ///
463  /// Panics if the tile data range is outside of the current tile vertices
464  /// length.
465  #[inline]
466  pub fn viewport_resources_set (&mut self,
467    key       : usize,
468    resources : ViewportResources
469  ) {
470    // verify that vertex ranges are valid
471    for draw_indices in resources.draw_indices.iter() {
472      draw_indices.rectangle
473        .map (|i| assert!(i as usize <= self.rectangle_2d_vertices.get_size()));
474      if let Some (draw_tiles) = draw_indices.draw_tiles.as_ref() {
475        assert!(draw_tiles.vertex_range.end as usize
476          <= self.tile_2d_vertices.get_size());
477      }
478      if let Some (draw_tiles) = draw_indices.draw_color_tiles.as_ref() {
479        assert!(draw_tiles.vertex_range.end as usize
480          <= self.tile_color_2d_vertices.get_size());
481      }
482    }
483    let _ = self.viewport_resources.insert (key, resources);
484  }
485  #[inline]
486  pub fn line_loop_vertices_set (&mut self,
487    glium_display : &glium::Display <glutin::surface::WindowSurface>,
488    vertices      : &[vertex::Vert2d]
489  ) {
490    self.line_loop_vertices =
491      glium::VertexBuffer::persistent (glium_display, vertices).unwrap();
492  }
493
494  pub (crate) fn set_pointer_vertex (&mut self,
495    display  : &glium::Display <glutin::surface::WindowSurface>,
496    position : math::Point2 <f32>
497  ) {
498    let vertex = vertex::Vert2d { position: position.0.into_array() };
499    if let Some (buffer) = self.pointer_vertex.as_mut() {
500      buffer.write (&[vertex]);
501    } else {
502      let buffer = glium::VertexBuffer::persistent (display, &[vertex]).unwrap();
503      self.pointer_vertex = Some (buffer);
504    }
505  }
506}
507
508impl Default for ViewportResources {
509  fn default() -> Self {
510    ViewportResources {
511      draw_indices:   vec![],
512      draw_crosshair: true,
513      draw_lineloop:  true
514    }
515  }
516}
517
518impl TilesOrigin {
519  pub fn to_world (&self, camera : &Camera2d) -> math::Point2 <f32> {
520    match self {
521      TilesOrigin::World => [0.0, 0.0],
522      TilesOrigin::Viewport { offset_px_left, offset_px_top } => {
523        let pixel_size_2d = 1.0 / camera.zoom();
524        [ camera.ortho().left + camera.position().0.x +
525            pixel_size_2d * *offset_px_left as f32,
526          camera.ortho().top  + camera.position().0.y -
527            pixel_size_2d * *offset_px_top  as f32
528        ]
529      }
530    }.into()
531  }
532}