gooey/presentation/opengl/
remote.rs

1//! Remote Opengl backend: events are collected locally and rendering is done on
2//! a remote thread.
3//!
4//! `Opengl::init` should be called first. It will wait until the
5//! `Remote::make_interface` initialization process has placed the windowed
6//! context and display receiver into a shared mutex so the `Render`
7//! initialization can proceed. The `Remote::make_interface` initialization
8//! process will then in turn wait for the `Opengl::init` process to finish.
9
10use std::{env, thread, time, vec};
11use std::sync::{self, atomic};
12use lazy_static::lazy_static;
13use nsys::{self, gl, math};
14use nsys::gl::glium::{self, glutin};
15use nsys::gl::winit;
16use key_vec::KeyVec;
17use unbounded_spsc;
18
19use crate::prelude::*;
20use super::{render, Graphics, InputHandler, InputState, Presentation};
21
22lazy_static!{
23  /// Used to share window with render thread
24  static ref GLUTIN_WINDOW
25    : sync::Mutex <Option <(winit::window::Window, glutin::config::Config)>>
26    = sync::Mutex::new (None);
27  /// Used to transfer view display events to the rendering backend
28  static ref DISPLAY_RECEIVER
29    : sync::Mutex <Option <
30      unbounded_spsc::Receiver <Vec <(NodeId, view::Display)>>>>
31    = sync::Mutex::new (None);
32  /// Frame counter
33  static ref FRAME : sync::Arc <atomic::AtomicU64> =
34    sync::Arc::new (atomic::AtomicU64::new (0));
35}
36
37/// Remote rendering handle and glutin event loop
38pub struct Remote {
39  pub event_loop : winit::event_loop::EventLoop <()>,
40  display        : unbounded_spsc::Sender <Vec <(NodeId, view::Display)>>,
41  input_state    : InputState,
42  screen_tiles   : Option <NodeId>
43}
44
45/// Rendering backend that can receive and handle display updates
46pub struct Opengl {
47  pub draw_crosshair : bool,
48  pub inner       : gl::Render,
49  tileset_id      : Option <gl::render::resource::DefaultTilesetId>,
50  updates         : unbounded_spsc::Receiver <Vec <(NodeId, view::Display)>>,
51  id_map          : KeyVec <NodeId, NodeId>,
52  view            : Tree <interface::View>,
53  glium_frame     : Option <glium::Frame>,
54  /// Counts number of batches of display events have been processed
55  display_counter : u64
56}
57
58/// Create a tile child frame of the root screen frame.
59///
60/// Must be called after `Opengl::init()`.
61pub fn create_screen_tiles <A, P> (interface : &mut Interface <A, P>) where
62  A : Application,
63  P : Presentation + presentation::HasGraphics <Remote>
64{
65  let screen_id     = interface.root_id().clone();
66  let screen_tiles  = frame::free::Builder::<A>::new (
67    interface.elements(), &screen_id
68  ) .layout (layout::Free {
69      size: Size::fill(), .. layout::Free::default_tile()
70    }.into())
71    .clear_color (canvas::ClearColor::Fixed (None))
72    .coord_kind_override (coordinates::Kind::Tile)
73    .build_element();
74  let tiles_id =
75    match interface.action (&screen_id,
76      Action::create_singleton (screen_tiles, CreateOrder::Append)
77    ).next().unwrap() {
78      (_, Event::Create (_, id, _)) => id,
79      _ => unreachable!()
80    };
81  interface.presentation.graphics().screen_tiles = Some (tiles_id);
82}
83
84impl Default for Remote {
85  fn default() -> Self {
86    let event_loop = {
87      let (event_loop, window, gl_config) =
88        gl::init::glutin_window ("Gooey Opengl Remote Window");
89      {
90        let mut glutin_window_lock = GLUTIN_WINDOW.lock().unwrap();
91        debug_assert!(glutin_window_lock.is_none());
92        // hide & grab cursor
93        // NOTE: as of winit 0.30.5 setting cursor visibility on other threads
94        // seems to block indefinitely on windows, so we set it here before
95        // sending to remote thread
96        window.set_cursor_visible (false);
97        // NOTE: grabbing cursor here on x11 results in error:
98        // Misc("Cursor could not be confined: confine location not viewable")
99        //window.set_cursor_grab (winit::window::CursorGrabMode::Confined)
100        //  .unwrap();
101        *glutin_window_lock = Some ((window, gl_config));
102      }
103      event_loop
104    };
105    let display = {
106      let (sender, receiver) =
107        unbounded_spsc::channel::<Vec <(NodeId, view::Display)>>();
108      {
109        let mut display_receiver_lock = DISPLAY_RECEIVER.lock().unwrap();
110        debug_assert!(display_receiver_lock.is_none());
111        *display_receiver_lock = Some (receiver);
112      }
113      sender
114    };
115    Remote {
116      event_loop,
117      display,
118      input_state:  InputState::new(),
119      screen_tiles: None
120    }
121  }
122}
123
124/// Gets the current rendered frame count
125pub fn frame() -> u64 {
126  FRAME.load (atomic::Ordering::SeqCst)
127}
128
129impl Remote {
130  #[inline]
131  pub fn screen_tiles_id (&self) -> &NodeId {
132    self.screen_tiles.as_ref().unwrap()
133  }
134  #[inline]
135  pub fn dimensions (&self) -> dimensions::Pixel {
136    self.input_state.dimensions.clone()
137  }
138  pub fn pointer_sensitivity (&self) -> f32 {
139    self.input_state.pointer_sensitivity
140  }
141  pub fn set_pointer_sensitivity (&mut self, pointer_sensitivity : f32) {
142    self.input_state.pointer_sensitivity = pointer_sensitivity
143  }
144}
145
146impl Graphics     for Remote { }
147impl Presentation for Remote {
148  /// &#9888; Must be called after Opengl::init.
149  ///
150  /// Expects a root Canvas component
151  fn with_root (root : View, id : NodeId) -> Self {
152    let remote = Self::default();
153    log::debug!("opengl remote with root: {:?}", root);
154    remote.display.send (vec![
155      (id.clone(), Display::Create (root, id, CreateOrder::Append))
156    ]).unwrap();
157    // wait for Opengl::init to finish: to do so we check if the
158    // DISPLAY_RECEIVER has been taken
159    loop {
160      if (*DISPLAY_RECEIVER).lock().unwrap().is_none() {
161        break
162      }
163      thread::sleep (time::Duration::from_millis (100));
164    }
165    remote
166  }
167
168  /// &#9888; Must be called after Opengl::init
169  ///
170  /// Creates a root screen canvas with default dimensions (1x1).
171  fn make_interface <A : Application> () -> Interface <A, Self> {
172    let screen = {
173      // create a root screen frame and rely on the resize control function to
174      // update the dimensions
175      let mut screen = frame::screen::PixelBuilder::<A>::new()
176        .anchor (Alignment::pixel())
177        .build_element();
178      // NOTE: viewport width/height must be non-zero
179      let canvas = Canvas::try_ref_mut (&mut screen.view.component).unwrap();
180      canvas.coordinates.modify_dimensions_horizontal (1);
181      canvas.coordinates.modify_dimensions_vertical (1);
182      screen
183    };
184    Interface::<A, Remote>::with_root (screen)
185  }
186
187  fn get_input (&mut self, input_buffer : &mut Vec <view::Input>) {
188    use winit::platform::pump_events::EventLoopExtPumpEvents;
189    log::trace!("get input...");
190    // TODO: we may want to ignore some extra glutin events, e.g. window axis
191    // motion events or keyboard device events on linux
192    self.event_loop.pump_app_events (
193      Some (time::Duration::ZERO),
194      &mut InputHandler { input_buffer, input_state: &mut self.input_state });
195    log::trace!("...get input");
196  }
197
198  fn display_view <V : AsRef <View>> (&mut self,
199    _view_tree      : &Tree <V>,
200    display_values : vec::Drain <(NodeId, view::Display)>
201  ) {
202    self.display.send (display_values.collect()).unwrap();
203  }
204}
205
206impl Opengl {
207  /// Get the windowed context and display receiver and create the Opengl
208  /// backend. This function will not return until the Remote struct has been
209  /// created.
210  pub fn init (tileset_id : Option <gl::render::resource::DefaultTilesetId>)
211    -> Self
212  {
213    let inner = {
214      use gl::render::resource::MAIN_VIEWPORT;
215      // wait for glutin window to be set
216      let (window, gl_config) = loop {
217        if let Some (glutin_window) = (*GLUTIN_WINDOW).lock().unwrap().take() {
218          break glutin_window
219        }
220        thread::sleep (time::Duration::from_millis (100));
221      };
222      // NOTE: as of winit 0.30.5 setting cursor visibility/grab on other
223      // threads seems to block indefinitely on windows, so we set it on main
224      // before receiving here
225      //window.set_cursor_visible (false);
226      //window.set_cursor_grab (winit::window::CursorGrabMode::Confined).unwrap();
227      let display = gl::init::glium_display_gl33core (&window, &gl_config);
228      let mut render =
229        gl::Render::<gl::render::resource::Default>::new (display, window);
230      // set the tile dimensions
231      let [tile_width, tile_height] =
232        render.resource.tile_dimensions (tileset_id.unwrap_or_default());
233      unsafe {
234        env::set_var ("GOOEY_TILE_WIDTH", tile_width.to_string());
235        env::set_var ("GOOEY_TILE_HEIGHT", tile_height.to_string());
236      }
237      // set the main viewport
238      render.resource.draw2d
239        .viewport_resources_set (MAIN_VIEWPORT, Default::default());
240      render
241    };
242    let updates = loop {
243      if let Some (receiver) = (*DISPLAY_RECEIVER).lock().unwrap().take() {
244        break receiver
245      }
246      thread::sleep (time::Duration::from_millis (100));
247    };
248    Opengl {
249      inner,
250      tileset_id,
251      updates,
252      id_map:          KeyVec::new(),
253      view:            Tree::new(),
254      glium_frame:     None,
255      display_counter: 0,
256      draw_crosshair:  false
257    }
258  }
259
260  #[inline]
261  pub fn inner (&self) -> &gl::Render {
262    &self.inner
263  }
264  #[inline]
265  pub fn inner_mut (&mut self) -> &mut gl::Render {
266    &mut self.inner
267  }
268  #[inline]
269  #[deprecated = "use remote::frame() function instead"]
270  pub fn frame (&self) -> u64 {
271    frame()
272  }
273  #[inline]
274  pub fn display_counter (&self) -> u64 {
275    self.display_counter
276  }
277  #[inline]
278  pub fn reset_display_counter (&mut self) {
279    self.display_counter = 0;
280  }
281  pub fn load_pointer (&mut self,
282    key    : gl::render::resource::PointerTextureIndexRepr,
283    bytes  : &[u8],
284    offset : math::Vector2 <i16>
285  ) {
286    let render = &mut self.inner;
287    render::load_pointer (render, key, bytes, offset);
288  }
289  pub fn load_textures (&mut self,
290    textures_16x16   : &[&'static str],
291    textures_64x64   : &[&'static str],
292    textures_anysize : &[&'static str]
293  ) {
294    render::load_textures (
295      &mut self.inner, textures_16x16, textures_64x64, textures_anysize)
296  }
297  pub fn process_display_events (&mut self) {
298    while let Ok (updates) = self.updates.try_recv() {
299      for (node_id, display) in updates {
300        match display {
301          view::Display::Create (view, child_id, order) => {
302            let new_id = if self.id_map.is_empty() {
303              // special case for initial root node: parent_id == child_id
304              self.view.insert (tree::Node::new (view),
305                tree::InsertBehavior::AsRoot).unwrap()
306            } else {
307              let parent_id = self.id_map.get (&node_id).unwrap();
308              let new_id    = self.view.insert (tree::Node::new (view),
309                tree::InsertBehavior::UnderNode (&parent_id)).unwrap();
310              match order {
311                interface::CreateOrder::Prepend => {
312                  let _ = self.view.make_first_sibling (&new_id).unwrap();
313                }
314                interface::CreateOrder::NthSibling (n) => {
315                  let _ = self.view.make_nth_sibling (&new_id, n as usize)
316                    .unwrap();
317                }
318                interface::CreateOrder::Append => {}
319              }
320              new_id
321            };
322            assert!(self.id_map.insert (child_id, new_id).is_none());
323          }
324          view::Display::Update (update) => {
325            let id = self.id_map.get (&node_id).unwrap();
326            match update {
327              view::Update::View (view) =>
328                *self.view.get_mut (id).unwrap().data_mut() = view,
329              view::Update::FocusTop => {
330                self.view.make_last_sibling (&id).unwrap();
331              }
332            }
333          }
334          view::Display::Destroy => {
335            let id = self.id_map.remove (&node_id).unwrap();
336            let _ = self.view
337              .remove_node (id, tree::RemoveBehavior::DropChildren).unwrap();
338          }
339        }
340      }
341      self.display_counter += 1;
342    }
343  }
344  pub fn update_and_do_frame (&mut self) {
345    // update the renderer with the current view
346    render::update (
347      &mut self.inner, self.tileset_id, &self.view, self.draw_crosshair);
348    // render the frame
349    self.inner.do_frame (self.glium_frame.as_mut());
350    FRAME.fetch_add (1, atomic::Ordering::SeqCst);
351  }
352  /// Pump display events and render a frame
353  pub fn display (&mut self) {
354    self.process_display_events();
355    self.update_and_do_frame();
356  }
357}
358
359/// Hack to allow passing a tree of views directly to the render::update
360/// function
361impl AsRef <View> for View {
362  fn as_ref (&self) -> &View {
363    self
364  }
365}