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 glium::Surface;
170    let draw2d = &render.resource.draw2d;
171    // primitives
172    const POINTS : glium::index::IndicesSource =
173      glium::index::IndicesSource::NoIndices {
174        primitives: glium::index::PrimitiveType::Points
175      };
176    const LINE_LOOP : glium::index::IndicesSource =
177      glium::index::IndicesSource::NoIndices {
178        primitives: glium::index::PrimitiveType::LineLoop
179      };
180    // draw each viewport 2d
181    for (i, viewport) in render.viewports.iter() {
182      if let Some (camera2d) = viewport.camera2d() {
183        // draw parameters
184        let draw_parameters_viewport     = viewport.draw_parameters();
185        let draw_parameters_blend_normal = glium::DrawParameters {
186          blend: render::params::BLEND_FUNC_NORMAL,
187          .. draw_parameters_viewport.clone()
188        };
189        let draw_parameters_blend_invert = glium::DrawParameters {
190          // NOTE: inverse blending does not really have an effect when
191          // rendering with 'Nearest' magnify filter below
192          blend: render::params::BLEND_FUNC_INVERT_COLOR,
193          .. draw_parameters_viewport.clone()
194        };
195        // samplers
196        let tileset_sampler_128 = render.resource.tileset_128x128_texture
197          .sampled()
198          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
199        let tileset_sampler_256 = render.resource.tileset_256x256_texture
200          .sampled()
201          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
202        let tileset_sampler = |tileset_id| match tileset_id {
203          DefaultTilesetId::EasciiAcorn128 => tileset_sampler_128,
204          DefaultTilesetId::EasciiAcorn256 => tileset_sampler_256,
205          _ => unimplemented!()
206        };
207        let default_textures_16x16_sampler =
208          render.resource.default_textures_16x16.sampled()
209            .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
210        let textures_16x16_sampler = render.resource.textures_16x16.sampled()
211          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
212        let textures_64x64_sampler = render.resource.textures_64x64.sampled()
213          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
214        // matrices
215        let (transform_mat_world_to_view, projection_mat_ortho) =
216          camera2d.view_ortho_mats();
217        // uniforms
218        let uniforms = uniform!{
219          uni_transform_mat_view:   transform_mat_world_to_view,
220          uni_projection_mat_ortho: projection_mat_ortho,
221          uni_color: color::rgba_u8_to_rgba_f32 (color::DEBUG_GREY)
222        };
223        // draw per-viewport resources
224        if let Some (resources) = draw2d.viewport_resources.get (i) {
225          for draw_indices in resources.draw_indices.iter() {
226            if let Some (r) = draw_indices.rectangle.map (|r| r as usize) {
227              //////////////////////////////////////////////////////////////////
228              //  draw rectangle
229              //////////////////////////////////////////////////////////////////
230              glium_frame.draw (
231                draw2d.rectangle_2d_vertices.slice (r..r+1).unwrap(),
232                POINTS.clone(),
233                &render.resource.shader_programs [
234                  shader::ProgramId::WorldSpace2dRect as usize],
235                &uniforms,
236                &draw_parameters_blend_normal
237              ).unwrap();
238              // end draw rectangle
239            }
240            if let Some (draw_tiles) = draw_indices.draw_tiles.as_ref() {
241              //////////////////////////////////////////////////////////////////
242              //  draw tiles (inverse)
243              //////////////////////////////////////////////////////////////////
244              let tile_origin = draw_tiles.origin.to_world (camera2d).0
245                .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
272                .into_array();
273              let uniforms = uniform!{
274                uni_transform_mat_view:   transform_mat_world_to_view,
275                uni_projection_mat_ortho: projection_mat_ortho,
276                // upper left corner
277                uni_tile_space_origin:    tile_origin,
278                uni_sampler2d_tileset:    tileset_sampler (draw_tiles.tileset_id)
279              };
280              glium_frame.draw (
281                draw2d.tile_color_2d_vertices.slice (
282                  draw_tiles.vertex_range.start as usize..
283                  draw_tiles.vertex_range.end   as usize
284                ).unwrap(),
285                POINTS.clone(),
286                &render.resource.shader_programs [
287                  shader::ProgramId::TileSpace2dTileColor as usize],
288                &uniforms,
289                &draw_parameters_blend_normal
290              ).unwrap();
291              // end draw color tiles
292            }
293          }
294          if resources.draw_crosshair {
295            ////////////////////////////////////////////////////////////////////
296            //  draw crosshair
297            ////////////////////////////////////////////////////////////////////
298            let transform_mat_world_to_view =
299              camera2d::transform_mat_world_to_view (
300                [0.0, 0.0].into(),
301                math::Rotation2::identity()
302              ).into_col_arrays();
303            let uniforms = uniform!{
304              uni_transform_mat_view:     transform_mat_world_to_view,
305              uni_projection_mat_ortho:   projection_mat_ortho,
306              uni_sampler2darray:         default_textures_16x16_sampler
307            };
308            glium_frame.draw (
309              draw2d.default_sprite_16x16_vertices.slice (
310                (DefaultSprite16Id::Crosshair as usize)..
311                (DefaultSprite16Id::Crosshair as usize + 1)
312              ).unwrap(),
313              POINTS.clone(),
314              &render.resource.shader_programs [
315                shader::ProgramId::WorldSpace2dSpriteLayer as usize],
316              &uniforms,
317              &draw_parameters_blend_invert
318            ).unwrap();
319            // end draw crosshair
320          }
321          if resources.draw_lineloop {
322            ////////////////////////////////////////////////////////////////////
323            //  draw viewport line loop
324            ////////////////////////////////////////////////////////////////////
325            let base_index = i * 4;
326            glium_frame.draw (
327              draw2d.line_loop_vertices.slice (base_index..(base_index + 4))
328                .unwrap(),
329              LINE_LOOP.clone(),
330              &render.resource.shader_programs [
331                shader::ProgramId::WorldSpace2dUniColor as usize],
332              &uniforms,
333              &draw_parameters_blend_normal
334            ).unwrap();
335            // end draw viewport line loop
336          }
337        } // end draw per-viewport resources
338        ////////////////////////////////////////////////////////////////////////
339        //  draw sprites 16x16
340        ////////////////////////////////////////////////////////////////////////
341        let uniforms = uniform!{
342          uni_transform_mat_view:   transform_mat_world_to_view,
343          uni_projection_mat_ortho: projection_mat_ortho,
344          uni_sampler2darray:       textures_16x16_sampler
345        };
346        glium_frame.draw (
347          draw2d.sprite_16x16_vertices.slice (..).unwrap(),
348          POINTS.clone(),
349          &render.resource.shader_programs [
350            shader::ProgramId::WorldSpace2dSpriteLayer as usize],
351          &uniforms,
352          &draw_parameters_blend_normal
353        ).unwrap();
354        ////////////////////////////////////////////////////////////////////////
355        //  draw rectangles 16x16
356        ////////////////////////////////////////////////////////////////////////
357        glium_frame.draw (
358          draw2d.rectangle_16x16_vertices.slice (..).unwrap(),
359          POINTS.clone(),
360          &render.resource.shader_programs [
361            shader::ProgramId::WorldSpace2dRectUvLayer as usize],
362          &uniforms,
363          &draw_parameters_blend_normal
364        ).unwrap();
365        ////////////////////////////////////////////////////////////////////////
366        //  draw sprites 64x64
367        ////////////////////////////////////////////////////////////////////////
368        let uniforms = uniform!{
369          uni_transform_mat_view:   transform_mat_world_to_view,
370          uni_projection_mat_ortho: projection_mat_ortho,
371          uni_sampler2darray:       textures_64x64_sampler
372        };
373        glium_frame.draw (
374          draw2d.sprite_64x64_vertices.slice (..).unwrap(),
375          POINTS.clone(),
376          &render.resource.shader_programs [
377            shader::ProgramId::WorldSpace2dSpriteLayer as usize],
378          &uniforms,
379          &draw_parameters_blend_normal
380        ).unwrap();
381        ////////////////////////////////////////////////////////////////////////
382        //  draw rectangles 64x64
383        ////////////////////////////////////////////////////////////////////////
384        glium_frame.draw (
385          draw2d.rectangle_64x64_vertices.slice (..).unwrap(),
386          POINTS.clone(),
387          &render.resource.shader_programs [
388            shader::ProgramId::WorldSpace2dRectUvLayer as usize],
389          &uniforms,
390          &draw_parameters_blend_normal
391        ).unwrap();
392        ////////////////////////////////////////////////////////////////////////
393        //  draw rectangles anysize
394        ////////////////////////////////////////////////////////////////////////
395        for (i, buffer) in draw2d.rectangle_anysize_vertices.iter() {
396          let uniforms = uniform!{
397            uni_transform_mat_view:   transform_mat_world_to_view,
398            uni_projection_mat_ortho: projection_mat_ortho,
399            uni_sampler2d:            render.resource
400              .textures_anysize.get (i).unwrap().sampled()
401          };
402          glium_frame.draw (
403            buffer,
404            POINTS.clone(),
405            &render.resource.shader_programs [
406              shader::ProgramId::WorldSpace2dRectUv as usize],
407            &uniforms,
408            &draw_parameters_blend_normal
409          ).unwrap();
410        }
411      } // end loop over viewports
412    }
413    if let Some (buffer) = draw2d.pointer_vertex.as_ref() &&
414      let Some (texture_index) = draw2d.draw_pointer
415    {
416      ////////////////////////////////////////////////////////////////////////
417      //  draw pointer
418      ////////////////////////////////////////////////////////////////////////
419      let pointer_texture_sampler =
420        render.resource.textures_pointer.get (texture_index as usize).unwrap()
421          .0.sampled()
422          .magnify_filter (glium::uniforms::MagnifySamplerFilter::Nearest);
423      let (width, height) = render.glium_display.get_framebuffer_dimensions();
424      let transform_mat_world_to_view = camera2d::transform_mat_world_to_view (
425        [width as f32 / 2.0, height as f32 / 2.0].into(),
426        math::Rotation2::identity()
427      ).into_col_arrays();
428      let projection_mat_ortho = graphics::projection_mat_orthographic (
429        &Camera2d::ortho_from_viewport_zoom (width as u16, height as u16, 1.0)
430      ).into_col_arrays();
431      let draw_parameters = glium::DrawParameters {
432        blend:    render::params::BLEND_FUNC_NORMAL,
433        viewport: Some (glium::Rect {
434          left:   0,
435          bottom: 0,
436          width, height
437        }),
438        .. Default::default()
439      };
440      let uniforms = uniform!{
441        uni_transform_mat_view:   transform_mat_world_to_view,
442        uni_projection_mat_ortho: projection_mat_ortho,
443        uni_sampler2d:            pointer_texture_sampler
444      };
445      glium_frame.draw (
446        buffer,
447        POINTS.clone(),
448        &render.resource.shader_programs [
449          shader::ProgramId::WorldSpace2dSprite as usize],
450        &uniforms,
451        &draw_parameters
452      ).unwrap();
453    }
454  }
455
456  #[inline]
457  pub fn viewport_resources_get (&self, key : usize)
458    -> Option <&ViewportResources>
459  {
460    self.viewport_resources.get (key)
461  }
462  /// Set the viewport tile data for the specified viewport.
463  ///
464  /// Panics if the tile data range is outside of the current tile vertices
465  /// length.
466  #[inline]
467  pub fn viewport_resources_set (&mut self,
468    key       : usize,
469    resources : ViewportResources
470  ) {
471    // verify that vertex ranges are valid
472    for draw_indices in resources.draw_indices.iter() {
473      draw_indices.rectangle
474        .map (|i| assert!(i as usize <= self.rectangle_2d_vertices.get_size()));
475      if let Some (draw_tiles) = draw_indices.draw_tiles.as_ref() {
476        assert!(draw_tiles.vertex_range.end as usize
477          <= self.tile_2d_vertices.get_size());
478      }
479      if let Some (draw_tiles) = draw_indices.draw_color_tiles.as_ref() {
480        assert!(draw_tiles.vertex_range.end as usize
481          <= self.tile_color_2d_vertices.get_size());
482      }
483    }
484    let _ = self.viewport_resources.insert (key, resources);
485  }
486  #[inline]
487  pub fn line_loop_vertices_set (&mut self,
488    glium_display : &glium::Display <glutin::surface::WindowSurface>,
489    vertices      : &[vertex::Vert2d]
490  ) {
491    self.line_loop_vertices =
492      glium::VertexBuffer::persistent (glium_display, vertices).unwrap();
493  }
494
495  pub (crate) fn set_pointer_vertex (&mut self,
496    display  : &glium::Display <glutin::surface::WindowSurface>,
497    position : math::Point2 <f32>
498  ) {
499    let vertex = vertex::Vert2d { position: position.0.into_array() };
500    if let Some (buffer) = self.pointer_vertex.as_mut() {
501      buffer.write (&[vertex]);
502    } else {
503      let buffer = glium::VertexBuffer::persistent (display, &[vertex]).unwrap();
504      self.pointer_vertex = Some (buffer);
505    }
506  }
507}
508
509impl Default for ViewportResources {
510  fn default() -> Self {
511    ViewportResources {
512      draw_indices:   vec![],
513      draw_crosshair: true,
514      draw_lineloop:  true
515    }
516  }
517}
518
519impl TilesOrigin {
520  pub fn to_world (&self, camera : &Camera2d) -> math::Point2 <f32> {
521    match self {
522      TilesOrigin::World => [0.0, 0.0],
523      TilesOrigin::Viewport { offset_px_left, offset_px_top } => {
524        let pixel_size_2d = 1.0 / camera.zoom();
525        [ camera.ortho().left + camera.position().0.x +
526            pixel_size_2d * *offset_px_left as f32,
527          camera.ortho().top  + camera.position().0.y -
528            pixel_size_2d * *offset_px_top  as f32
529        ]
530      }
531    }.into()
532  }
533}