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 (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 (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 (x, y) = {
141      let pointer = interface::POINTER.read().unwrap();
142      (pointer.position_horizontal, pointer.position_vertical)
143    };
144    render.resource.set_pointer_position (&render.glium_display, [x, y].into())
145  }
146  render.resource.draw2d.draw_pointer = draw_pointer.map (|pointer| pointer.0);
147  // upload the vertex buffer data
148  render.resource.draw2d.rectangle_2d_vertices  =
149    glium::VertexBuffer::dynamic (&render.glium_display, &rectangles).unwrap();
150  render.resource.draw2d.tile_color_2d_vertices =
151    glium::VertexBuffer::dynamic (&render.glium_display, &tiles).unwrap();
152  render.resource.draw2d.rectangle_64x64_vertices  =
153    glium::VertexBuffer::dynamic (&render.glium_display, &rectangles_64x64)
154      .unwrap();
155  // set the viewport resources
156  // TODO: this will need to be revisited to support multiple viewports
157  render.resource.draw2d.viewport_resources_set (MAIN_VIEWPORT,
158    draw2d::ViewportResources {
159      draw_crosshair, draw_indices, .. draw2d::ViewportResources::default()
160    }
161  );
162  log::trace!("...update");
163}
164
165/// Updates the back-end viewport size, clear color, and border. View parameter
166/// must be a Canvas component.
167pub (crate) fn set_screen_view (render : &mut gl::Render, view : &View) {
168  use view::component::{canvas, Canvas, Kind};
169  let canvas = Canvas::try_ref (&view.component).unwrap();
170  // viewport size
171  // NOTE: we restrict the viewport to be minimum size of 1x1
172  match canvas.coordinates.dimensions() {
173    view::coordinates::Dimensions::Pixel (pixel) => {
174      // TODO: only call render update methods if screen view has changed
175      let width  = pixel.width();
176      let height = pixel.height();
177      render.window_resized ([
178        u32::max (width,  1),
179        u32::max (height, 1)
180      ].into());
181      render.camera2d_move_origin_to_bottom_left();
182    }
183    view::coordinates::Dimensions::Tile (_) => unreachable!()
184  }
185  // canvas clear color
186  let mut set_clear_color = |color|
187    render.clear_color = color::rgba_u8_to_rgba_f32 (color);
188  match canvas.clear_color {
189    canvas::ClearColor::Appearance => {
190      if let Some (style) = view.appearance.style.as_ref() {
191        set_clear_color (style.bg.raw());
192      } else {
193        log::debug!("TODO: no appearance clear color");
194      }
195    }
196    canvas::ClearColor::Fixed (Some (color)) => set_clear_color (color.raw()),
197    canvas::ClearColor::Fixed (None) => {
198      log::warn!("TODO: no fixed clear color");
199    }
200  }
201  // canvas border
202  if let Some (_border) = canvas.border.as_ref() {
203    log::warn!("TODO: screen border");
204  }
205}
206
207pub (crate) fn load_pointer (
208  render : &mut gl::Render,
209  key    : gl::render::resource::PointerTextureIndexRepr,
210  bytes  : &[u8],
211  offset : math::Vector2 <i16>
212) {
213  let texture = gl::texture::texture2d_with_mipmaps_from_bytes (
214    &render.glium_display,
215    bytes,
216    image::ImageFormat::Png,
217    glium::texture::MipmapsOption::NoMipmap
218  ).unwrap();
219  render.resource.textures_pointer.insert (key as usize, (texture, offset));
220}
221
222/// NOTE: loads anysize textures starting from index 1 (index 0 is reserved for
223/// the pointer texture)
224pub (crate) fn load_textures (
225  render           : &mut gl::Render,
226  textures_16x16   : &[&'static str],
227  textures_64x64   : &[&'static str],
228  textures_anysize : &[&'static str]
229) {
230  // TODO: clear current textures if no paths are given ?
231  if !textures_16x16.is_empty() {
232    let textures = gl::texture::texture2darray_with_mipmaps_from_files (
233      &render.glium_display,
234      textures_16x16,
235      image::ImageFormat::Png,
236      glium::texture::MipmapsOption::NoMipmap
237    ).unwrap();
238    render.resource.set_textures_16x16 (textures);
239  }
240  if !textures_64x64.is_empty() {
241    let textures = gl::texture::texture2darray_with_mipmaps_from_files (
242      &render.glium_display,
243      textures_64x64,
244      image::ImageFormat::Png,
245      glium::texture::MipmapsOption::NoMipmap
246    ).unwrap();
247    render.resource.set_textures_64x64 (textures);
248  }
249  render.resource.textures_anysize = VecMap::from_iter (textures_anysize.iter()
250    .map (|path| {
251      gl::texture::texture2d_with_mipmaps_from_file (
252        &render.glium_display,
253        path,
254        image::ImageFormat::Png,
255        glium::texture::MipmapsOption::NoMipmap
256      ).unwrap()
257    }).enumerate().map (|(i, texture)| (i+1, texture))
258  );
259}
260
261/// Convert an interface canvas+appearance into a rectangle vertex for the
262/// renderer.
263///
264/// If the canvas clear color is set to `ClearColor::Fixed(None)`, then no
265/// vertex will be returned.
266fn canvas_background (
267  canvas : &view::component::Canvas,
268  style  : &view::Style
269) -> Option <gl::vertex::Vert2dRectColor> {
270  use view::coordinates;
271  use view::component::canvas;
272  let color = {
273    let color = match canvas.clear_color {
274      canvas::ClearColor::Appearance => style.bg.raw(),
275      canvas::ClearColor::Fixed (Some (color)) => color.raw(),
276      canvas::ClearColor::Fixed (None) => return None
277    };
278    color::rgba_u8_to_rgba_f32 (color)
279  };
280  let (bottom_left, dimensions) = {
281    let body_coordinates = canvas.body_coordinates();
282    if body_coordinates.dimensions().horizontal() == 0 ||
283      body_coordinates.dimensions().vertical() == 0
284    {
285      return None
286    }
287    let aabb       = geometry::integer::Aabb2::from (body_coordinates);
288    let aabb_pixel = match canvas.coordinates.kind() {
289      coordinates::Kind::Tile  => coordinates::tile_to_pixel_aabb (aabb),
290      coordinates::Kind::Pixel => aabb
291    };
292    ( aabb_pixel.min().numcast().unwrap().0.into_array(),
293      [aabb_pixel.width() as f32, aabb_pixel.height() as f32]
294    )
295  };
296  Some (gl::vertex::Vert2dRectColor { bottom_left, dimensions, color })
297}
298
299/// Generate canvas border tiles
300fn canvas_border (canvas : &view::component::Canvas)
301  -> Vec <gl::vertex::Vert2dTile>
302{
303  use gl::vertex::Vert2dTile;
304  use view::coordinates;
305  let mut tiles = vec![];
306  if let Some (border) = canvas.border.as_ref() {
307    assert!(canvas.coordinates.kind() == coordinates::Kind::Tile,
308      "TODO: support pixel borders");
309    debug_assert!(border.top          <= u8::MAX as u32);
310    debug_assert!(border.left         <= u8::MAX as u32);
311    debug_assert!(border.right        <= u8::MAX as u32);
312    debug_assert!(border.bottom       <= u8::MAX as u32);
313    debug_assert!(border.top_left     <= u8::MAX as u32);
314    debug_assert!(border.top_right    <= u8::MAX as u32);
315    debug_assert!(border.bottom_left  <= u8::MAX as u32);
316    debug_assert!(border.bottom_right <= u8::MAX as u32);
317    let [row, column]   = [
318      canvas.coordinates.position_vertical(),
319      canvas.coordinates.position_horizontal()
320    ];
321    let [rows, columns] = [
322      canvas.coordinates.dimensions_vertical()   as i32,
323      canvas.coordinates.dimensions_horizontal() as i32
324    ];
325    if rows > 0 && columns > 0 {
326      let (thickness_top, thickness_bottom, thickness_left, thickness_right) = (
327        border.thickness_top    as i32,
328        border.thickness_bottom as i32,
329        border.thickness_left   as i32,
330        border.thickness_right  as i32
331      );
332      let mut push_tile = |row, column, tile|
333        tiles.push (Vert2dTile { row, column, tile: tile as u8 });
334      // top
335      for r in row..row+thickness_top {
336        for c in column..column+thickness_left {
337          push_tile (r, c, border.top_left);
338        }
339        for c in column+thickness_left..column+columns-thickness_right {
340          push_tile (r, c, border.top);
341        }
342        for c in column+columns-thickness_right..column+columns {
343          push_tile (r, c, border.top_right);
344        }
345      }
346      // sides
347      for r in row+thickness_top..row+rows-thickness_bottom {
348        for c in column..column+thickness_left {
349          push_tile (r, c, border.left);
350        }
351        for c in column+columns-thickness_right..column+columns {
352          push_tile (r, c, border.right);
353        }
354      }
355      // bottom
356      for r in row+rows-thickness_bottom..row+rows {
357        for c in column..column+thickness_left {
358          push_tile (r, c, border.bottom_left);
359        }
360        for c in column+thickness_left..column+columns-thickness_right {
361          push_tile (r, c, border.bottom);
362        }
363        for c in column+columns-thickness_right..column+columns {
364          push_tile (r, c, border.bottom_right);
365        }
366      }
367    }
368  }
369  tiles
370}
371
372/// Generate canvas body tiles
373fn canvas_body (
374  canvas : &view::component::Canvas,
375  body   : &view::component::Body,
376  style  : &view::Style
377) -> Vec <gl::vertex::Vert2dTileColor> {
378  use gl::vertex::Vert2dTileColor;
379  let (fg, bg) = {
380    let fg = color::rgba_u8_to_rgba_f32 (style.fg.raw());
381    let bg = color::rgba_u8_to_rgba_f32 (style.bg.raw());
382    (fg, bg)
383  };
384  let mut tiles = vec![];
385  let (rc_min, _rc_max) = {
386    let [row, col]   = [
387      canvas.coordinates.position_vertical(),
388      canvas.coordinates.position_horizontal()
389    ];
390    let [rows, cols] = [
391      canvas.coordinates.dimensions_vertical()   as i32,
392      canvas.coordinates.dimensions_horizontal() as i32
393    ];
394    ( (row, col),
395      (row + rows-1, col + cols-1) )
396  };
397  let (border_thickness_top, border_thickness_left) =
398    canvas.border.as_ref().map (|border|
399      ( border.thickness_top  as i32,
400        border.thickness_left as i32 )
401    ).unwrap_or_default();
402  for (i, line) in body.0.lines().enumerate() {
403    let row = rc_min.0 + border_thickness_top + i as i32;
404    for (j, ch) in line.chars().enumerate().skip_while (|(_, c)| *c == '\0') {
405      let column = rc_min.1 + border_thickness_left + j as i32;
406      tiles.push (Vert2dTileColor {
407        row, column, tile: ch as u8, fg, bg
408      });
409    }
410  }
411  tiles
412}
413
414/// Generate canvas image rectangle vertex
415fn canvas_rectangle_uv (
416  canvas : &view::component::Canvas,
417  image  : &view::component::Image
418) -> gl::vertex::Vert2dRectUvLayer {
419  use view::coordinates;
420  let (_resource, layer) = match image {
421    view::component::Image::Texture (view::Texture { resource, index }) =>
422      (*resource, *index),
423    _ => unimplemented!()
424  };
425  let (bottom_left, dimensions) = {
426    let body_coordinates = canvas.body_coordinates();
427    let horizontal = body_coordinates.position_horizontal();
428    let vertical   = body_coordinates.position_vertical();
429    let width      = body_coordinates.dimensions_horizontal();
430    let height     = body_coordinates.dimensions_vertical();
431    let ([x, y], [w, h]) = if
432      body_coordinates.kind() == coordinates::Kind::Tile
433    {
434      let [tile_w, tile_h] = *coordinates::TILE_WH;
435      ( coordinates::tile_to_pixel (vertical + height as i32, horizontal),
436        [width * tile_w, height * tile_h]
437      )
438    } else {
439      ([horizontal, vertical], [width, height])
440    };
441    ([x as f32, y as f32], [w as f32, h as f32])
442  };
443  // TODO: uv offset
444  let uv = [0.0, 0.0];
445  gl::vertex::Vert2dRectUvLayer { bottom_left, dimensions, uv, layer }
446}