gooey/presentation/opengl/
render.rs

1use std::iter::FromIterator;
2use vec_map::VecMap;
3
4use nsys::{self, color, geometry, math};
5use nsys::gl::{self, glium, image};
6
7use crate::Tree;
8use crate::interface::{self, view, View};
9
10pub (crate) fn update <V : AsRef <View>> (
11  render         : &mut gl::Render,
12  tileset_id     : Option <gl::render::resource::DefaultTilesetId>,
13  view_tree      : &Tree <V>,
14  draw_crosshair : bool
15) {
16  use gl::render::resource::{draw2d, MAIN_VIEWPORT};
17  use view::component::{Canvas, Kind};
18  log::trace!("update...");
19  let tileset_id           = tileset_id.unwrap_or_default();
20  let mut rectangles       = vec![];
21  let mut tiles            = vec![];
22  let mut rectangles_64x64 = vec![];
23  let mut draw_indices     = vec![];
24  // screen is the root canvas element
25  let screen_id        = view_tree.root_node_id().unwrap();
26  let mut view_ids     = view_tree.traverse_pre_order_ids (&screen_id).unwrap();
27  let mut style        = view::Style::default();
28  let mut draw_pointer = None;
29  view_ids.next().map (|screen_id|{
30    let screen_view = view_tree.get (&screen_id).unwrap().data().as_ref();
31    set_screen_view (render, &screen_view);
32    // TODO: currently this uses the default style colors if no style is
33    // present; possibly use inverse color tiles
34    screen_view.appearance.style.as_ref().map (|s| style = s.clone());
35    draw_pointer = screen_view.appearance.pointer.clone();
36    let (fg, bg) = {
37      let fg = color::rgba_u8_to_rgba_f32 (style.fg.raw());
38      let bg = color::rgba_u8_to_rgba_f32 (style.bg.raw());
39      (fg, bg)
40    };
41    let canvas   = Canvas::try_ref (&screen_view.component).unwrap();
42    // TODO: should screen allow a background rectangle ?
43    let border_tiles = canvas_border (&canvas);
44    if !border_tiles.is_empty() {
45      // add color
46      let border_tiles = border_tiles.into_iter().map (
47        |gl::vertex::Vert2dTile { tile, row, column }|
48        gl::vertex::Vert2dTileColor { tile, row, column, fg, bg }
49      ).collect::<Vec<_>>();
50      let tiles_len = tiles.len() as u32;
51      draw_indices.push (draw2d::DrawIndices {
52        rectangle:        None,
53        draw_tiles:       None,
54        draw_color_tiles: Some (
55          draw2d::Tiles {
56            vertex_range: tiles_len..tiles_len + border_tiles.len() as u32,
57            origin: (0, 0).into(),
58            tileset_id
59          }
60        )
61      });
62      tiles.extend (border_tiles);
63    }
64  }).unwrap();
65  for id in view_ids {
66    let view = view_tree.get (&id).unwrap().data().as_ref();
67    if view.appearance.pointer.is_some() {
68      draw_pointer = view.appearance.pointer.clone();
69    }
70    match &view.component {
71      view::Component::Canvas (canvas) => {
72        let style = view.appearance.style.as_ref().unwrap_or (&style);
73        let rectangle =
74          if let Some (vertex) = canvas_background (canvas, style) {
75            rectangles.push (vertex);
76            Some (rectangles.len() as u32 - 1)
77          } else {
78            None
79          };
80        let border_tiles = canvas_border (&canvas);
81        let (body_tiles, body_rectangles_64x64) = {
82          let mut tiles            = vec![];
83          let mut rectangles_64x64 = vec![];
84          for child in view_tree.children (&id).unwrap()
85            .map (|node| node.data().as_ref())
86          {
87            match child.component {
88              view::Component::Body (ref body) => {
89                let style = child.appearance.style.as_ref().unwrap_or (style);
90                tiles.extend (canvas_body (canvas, body, &style))
91              }
92              view::Component::Image (ref image) =>
93                rectangles_64x64.push (canvas_rectangle_uv (canvas, image)),
94              _ => {}
95            }
96          }
97          (tiles, rectangles_64x64)
98        };
99        let draw_color_tiles = if !border_tiles.is_empty() ||
100          !body_tiles.is_empty()
101        {
102          // add color
103          // TODO: currently this uses the default style colors if no style is
104          // present; instead use inherited parent style
105          // TODO: body tiles use child view style
106          let (fg, bg) = {
107            let style = view.appearance.style.clone().unwrap_or_default();
108            ( color::rgba_u8_to_rgba_f32 (style.fg.raw()),
109              color::rgba_u8_to_rgba_f32 (style.bg.raw()) )
110          };
111          let border_tiles = border_tiles.into_iter().map (
112            |gl::vertex::Vert2dTile { tile, row, column }|
113            gl::vertex::Vert2dTileColor { tile, row, column, fg, bg }
114          ).collect::<Vec<_>>();
115          let tiles_len    = tiles.len() as u32;
116          let vertex_range = tiles_len..tiles_len +
117            border_tiles.len() as u32 + body_tiles.len() as u32;
118          tiles.extend (border_tiles);
119          tiles.extend (body_tiles);
120          Some (draw2d::Tiles {
121            vertex_range, tileset_id, origin: (0, 0).into()
122          })
123        } else {
124          None
125        };
126        if rectangle.is_some() || draw_color_tiles.is_some() {
127          draw_indices.push (draw2d::DrawIndices {
128            rectangle,
129            draw_color_tiles,
130            draw_tiles: None
131          });
132        }
133        rectangles_64x64.extend (body_rectangles_64x64);
134      }
135      // TODO: more components
136      _ => {}
137    }
138  }
139  if draw_pointer.is_some() {
140    let pointer = interface::POINTER.read().unwrap();
141    let x = pointer.position_horizontal;
142    let y = pointer.position_vertical;
143    render.resource.set_pointer_position (&render.glium_display, [x, y].into())
144  }
145  render.resource.draw2d.draw_pointer = draw_pointer.map (|pointer| pointer.0);
146  // upload the vertex buffer data
147  render.resource.draw2d.rectangle_2d_vertices  =
148    glium::VertexBuffer::dynamic (&render.glium_display, &rectangles).unwrap();
149  render.resource.draw2d.tile_color_2d_vertices =
150    glium::VertexBuffer::dynamic (&render.glium_display, &tiles).unwrap();
151  render.resource.draw2d.rectangle_64x64_vertices  =
152    glium::VertexBuffer::dynamic (&render.glium_display, &rectangles_64x64)
153      .unwrap();
154  // set the viewport resources
155  // TODO: this will need to be revisited to support multiple viewports
156  render.resource.draw2d.viewport_resources_set (MAIN_VIEWPORT,
157    draw2d::ViewportResources {
158      draw_crosshair, draw_indices, .. draw2d::ViewportResources::default()
159    }
160  );
161  log::trace!("...update");
162}
163
164/// Updates the back-end viewport size, clear color, and border. View parameter
165/// must be a Canvas component.
166pub (crate) fn set_screen_view (render : &mut gl::Render, view : &View) {
167  use view::component::{canvas, Canvas, Kind};
168  let canvas = Canvas::try_ref (&view.component).unwrap();
169  // viewport size
170  // NOTE: we restrict the viewport to be minimum size of 1x1
171  match canvas.coordinates.dimensions() {
172    view::coordinates::Dimensions::Pixel (pixel) => {
173      // TODO: only call render update methods if screen view has changed
174      let width  = pixel.width();
175      let height = pixel.height();
176      render.window_resized ([
177        u32::max (width,  1),
178        u32::max (height, 1)
179      ].into());
180      render.camera2d_move_origin_to_bottom_left();
181    }
182    view::coordinates::Dimensions::Tile (_) => unreachable!()
183  }
184  // canvas clear color
185  let mut set_clear_color = |color|
186    render.clear_color = color::rgba_u8_to_rgba_f32 (color);
187  match canvas.clear_color {
188    canvas::ClearColor::Appearance => {
189      if let Some (style) = view.appearance.style.as_ref() {
190        set_clear_color (style.bg.raw());
191      } else {
192        log::debug!("TODO: no appearance clear color");
193      }
194    }
195    canvas::ClearColor::Fixed (Some (color)) => set_clear_color (color.raw()),
196    canvas::ClearColor::Fixed (None) => {
197      log::warn!("TODO: no fixed clear color");
198    }
199  }
200  // canvas border
201  if let Some (_border) = canvas.border.as_ref() {
202    log::warn!("TODO: screen border");
203  }
204}
205
206pub (crate) fn load_pointer (
207  render : &mut gl::Render,
208  key    : gl::render::resource::PointerTextureIndexRepr,
209  bytes  : &[u8],
210  offset : math::Vector2 <i16>
211) {
212  let texture = gl::texture::texture2d_with_mipmaps_from_bytes (
213    &render.glium_display,
214    bytes,
215    image::ImageFormat::Png,
216    glium::texture::MipmapsOption::NoMipmap
217  ).unwrap();
218  render.resource.textures_pointer.insert (key as usize, (texture, offset));
219}
220
221/// NOTE: loads anysize textures starting from index 1 (index 0 is reserved for
222/// the pointer texture)
223pub (crate) fn load_textures (
224  render           : &mut gl::Render,
225  textures_16x16   : &[&'static str],
226  textures_64x64   : &[&'static str],
227  textures_anysize : &[&'static str]
228) {
229  // TODO: clear current textures if no paths are given ?
230  if !textures_16x16.is_empty() {
231    let textures = gl::texture::texture2darray_with_mipmaps_from_files (
232      &render.glium_display,
233      textures_16x16,
234      image::ImageFormat::Png,
235      glium::texture::MipmapsOption::NoMipmap
236    ).unwrap();
237    render.resource.set_textures_16x16 (textures);
238  }
239  if !textures_64x64.is_empty() {
240    let textures = gl::texture::texture2darray_with_mipmaps_from_files (
241      &render.glium_display,
242      textures_64x64,
243      image::ImageFormat::Png,
244      glium::texture::MipmapsOption::NoMipmap
245    ).unwrap();
246    render.resource.set_textures_64x64 (textures);
247  }
248  render.resource.textures_anysize = VecMap::from_iter (textures_anysize.iter()
249    .map (|path| {
250      gl::texture::texture2d_with_mipmaps_from_file (
251        &render.glium_display,
252        path,
253        image::ImageFormat::Png,
254        glium::texture::MipmapsOption::NoMipmap
255      ).unwrap()
256    }).enumerate().map (|(i, texture)| (i+1, texture))
257  );
258}
259
260/// Convert an interface canvas+appearance into a rectangle vertex for the
261/// renderer.
262///
263/// If the canvas clear color is set to `ClearColor::Fixed(None)`, then no
264/// vertex will be returned.
265fn canvas_background (
266  canvas : &view::component::Canvas,
267  style  : &view::Style
268) -> Option <gl::vertex::Vert2dRectColor> {
269  use view::coordinates;
270  use view::component::canvas;
271  let color = {
272    let color = match canvas.clear_color {
273      canvas::ClearColor::Appearance => style.bg.raw(),
274      canvas::ClearColor::Fixed (Some (color)) => color.raw(),
275      canvas::ClearColor::Fixed (None) => return None
276    };
277    color::rgba_u8_to_rgba_f32 (color)
278  };
279  let (bottom_left, dimensions) = {
280    let body_coordinates = canvas.body_coordinates();
281    if body_coordinates.dimensions().horizontal() == 0 ||
282      body_coordinates.dimensions().vertical() == 0
283    {
284      return None
285    }
286    let aabb       = geometry::integer::Aabb2::from (body_coordinates);
287    let aabb_pixel = match canvas.coordinates.kind() {
288      coordinates::Kind::Tile  => coordinates::tile_to_pixel_aabb (aabb),
289      coordinates::Kind::Pixel => aabb
290    };
291    ( aabb_pixel.min().numcast().unwrap().0.into_array(),
292      [aabb_pixel.width() as f32, aabb_pixel.height() as f32]
293    )
294  };
295  Some (gl::vertex::Vert2dRectColor { bottom_left, dimensions, color })
296}
297
298/// Generate canvas border tiles
299fn canvas_border (canvas : &view::component::Canvas)
300  -> Vec <gl::vertex::Vert2dTile>
301{
302  use gl::vertex::Vert2dTile;
303  use view::coordinates;
304  let mut tiles = vec![];
305  if let Some (border) = canvas.border.as_ref() {
306    assert!(canvas.coordinates.kind() == coordinates::Kind::Tile,
307      "TODO: support pixel borders");
308    debug_assert!(border.top          <= std::u8::MAX as u32);
309    debug_assert!(border.left         <= std::u8::MAX as u32);
310    debug_assert!(border.right        <= std::u8::MAX as u32);
311    debug_assert!(border.bottom       <= std::u8::MAX as u32);
312    debug_assert!(border.top_left     <= std::u8::MAX as u32);
313    debug_assert!(border.top_right    <= std::u8::MAX as u32);
314    debug_assert!(border.bottom_left  <= std::u8::MAX as u32);
315    debug_assert!(border.bottom_right <= std::u8::MAX as u32);
316    let [row, column]   = [
317      canvas.coordinates.position_vertical(),
318      canvas.coordinates.position_horizontal()
319    ];
320    let [rows, columns] = [
321      canvas.coordinates.dimensions_vertical()   as i32,
322      canvas.coordinates.dimensions_horizontal() as i32
323    ];
324    if rows > 0 && columns > 0 {
325      let (thickness_top, thickness_bottom, thickness_left, thickness_right) = (
326        border.thickness_top    as i32,
327        border.thickness_bottom as i32,
328        border.thickness_left   as i32,
329        border.thickness_right  as i32
330      );
331      let mut push_tile = |row, column, tile|
332        tiles.push (Vert2dTile { row, column, tile: tile as u8 });
333      // top
334      for r in row..row+thickness_top {
335        for c in column..column+thickness_left {
336          push_tile (r, c, border.top_left);
337        }
338        for c in column+thickness_left..column+columns-thickness_right {
339          push_tile (r, c, border.top);
340        }
341        for c in column+columns-thickness_right..column+columns {
342          push_tile (r, c, border.top_right);
343        }
344      }
345      // sides
346      for r in row+thickness_top..row+rows-thickness_bottom {
347        for c in column..column+thickness_left {
348          push_tile (r, c, border.left);
349        }
350        for c in column+columns-thickness_right..column+columns {
351          push_tile (r, c, border.right);
352        }
353      }
354      // bottom
355      for r in row+rows-thickness_bottom..row+rows {
356        for c in column..column+thickness_left {
357          push_tile (r, c, border.bottom_left);
358        }
359        for c in column+thickness_left..column+columns-thickness_right {
360          push_tile (r, c, border.bottom);
361        }
362        for c in column+columns-thickness_right..column+columns {
363          push_tile (r, c, border.bottom_right);
364        }
365      }
366    }
367  }
368  tiles
369}
370
371/// Generate canvas body tiles
372fn canvas_body (
373  canvas : &view::component::Canvas,
374  body   : &view::component::Body,
375  style  : &view::Style
376) -> Vec <gl::vertex::Vert2dTileColor> {
377  use gl::vertex::Vert2dTileColor;
378  let (fg, bg) = {
379    let fg = color::rgba_u8_to_rgba_f32 (style.fg.raw());
380    let bg = color::rgba_u8_to_rgba_f32 (style.bg.raw());
381    (fg, bg)
382  };
383  let mut tiles = vec![];
384  let (rc_min, _rc_max) = {
385    let [row, col]   = [
386      canvas.coordinates.position_vertical(),
387      canvas.coordinates.position_horizontal()
388    ];
389    let [rows, cols] = [
390      canvas.coordinates.dimensions_vertical()   as i32,
391      canvas.coordinates.dimensions_horizontal() as i32
392    ];
393    ( (row, col),
394      (row + rows-1, col + cols-1) )
395  };
396  let (border_thickness_top, border_thickness_left) =
397    canvas.border.as_ref().map (|border|
398      ( border.thickness_top  as i32,
399        border.thickness_left as i32 )
400    ).unwrap_or_default();
401  for (i, line) in body.0.lines().enumerate() {
402    let row = rc_min.0 + border_thickness_top + i as i32;
403    for (j, ch) in line.chars().enumerate().skip_while (|(_, c)| *c == '\0') {
404      let column = rc_min.1 + border_thickness_left + j as i32;
405      tiles.push (Vert2dTileColor {
406        row, column, tile: ch as u8, fg, bg
407      });
408    }
409  }
410  tiles
411}
412
413/// Generate canvas image rectangle vertex
414fn canvas_rectangle_uv (
415  canvas : &view::component::Canvas,
416  image  : &view::component::Image
417) -> gl::vertex::Vert2dRectUvLayer {
418  use view::coordinates;
419  let (_resource, layer) = match image {
420    view::component::Image::Texture (view::Texture { resource, index }) =>
421      (*resource, *index),
422    _ => unimplemented!()
423  };
424  let (bottom_left, dimensions) = {
425    let body_coordinates = canvas.body_coordinates();
426    let horizontal = body_coordinates.position_horizontal();
427    let vertical   = body_coordinates.position_vertical();
428    let width      = body_coordinates.dimensions_horizontal();
429    let height     = body_coordinates.dimensions_vertical();
430    let ([x, y], [w, h]) = if
431      body_coordinates.kind() == coordinates::Kind::Tile
432    {
433      let [tile_w, tile_h] = *coordinates::TILE_WH;
434      ( coordinates::tile_to_pixel (vertical + height as i32, horizontal),
435        [width * tile_w, height * tile_h]
436      )
437    } else {
438      ([horizontal, vertical], [width, height])
439    };
440    ([x as f32, y as f32], [w as f32, h as f32])
441  };
442  // TODO: uv offset
443  let uv = [0.0, 0.0];
444  gl::vertex::Vert2dRectUvLayer { bottom_left, dimensions, uv, layer }
445}