GLVis  v4.2
Accurate and flexible finite element visualization
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
sdl.cpp
Go to the documentation of this file.
1 // Copyright (c) 2010-2022, Lawrence Livermore National Security, LLC. Produced
2 // at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3 // LICENSE and NOTICE for details. LLNL-CODE-443271.
4 //
5 // This file is part of the GLVis visualization tool and library. For more
6 // information and source code availability see https://glvis.org.
7 //
8 // GLVis is free software; you can redistribute it and/or modify it under the
9 // terms of the BSD-3 license. We welcome feedback and contributions, see file
10 // CONTRIBUTING.md for details.
11 
12 #include <iostream>
13 #include "aux_vis.hpp"
14 #include "gl/renderer_core.hpp"
15 #include "gl/renderer_ff.hpp"
16 #include "sdl.hpp"
17 #include "sdl_main.hpp"
18 #ifdef __EMSCRIPTEN__
19 #include <emscripten.h>
20 #include <emscripten/html5.h>
21 #endif
22 
23 #ifdef SDL_VIDEO_DRIVER_COCOA
24 #include "sdl_mac.hpp"
25 #endif
26 
27 using std::cerr;
28 using std::endl;
29 
30 #ifdef GLVIS_DEBUG
31 #define PRINT_DEBUG(s) std::cerr << s
32 #else
33 #define PRINT_DEBUG(s) {}
34 #endif
35 
36 extern int GetMultisample();
37 
38 SdlWindow::Handle::Handle(const std::string& title, int x, int y, int w, int h,
39  Uint32 wndflags)
40  : hwnd(nullptr)
41  , gl_ctx(0)
42 {
43  hwnd = SDL_CreateWindow(title.c_str(), x, y, w, h, wndflags);
44  if (!hwnd)
45  {
46  PRINT_DEBUG("SDL window creation failed with error: "
47  << SDL_GetError() << endl);
48  return;
49  }
50  gl_ctx = SDL_GL_CreateContext(hwnd);
51  if (!gl_ctx)
52  {
53  PRINT_DEBUG("OpenGL context creation failed with error: "
54  << SDL_GetError() << endl);
55  }
56 }
57 
58 SdlWindow::Handle::~Handle()
59 {
60  if (gl_ctx)
61  {
62  SDL_GL_DeleteContext(gl_ctx);
63  }
64  if (hwnd)
65  {
66  SDL_DestroyWindow(hwnd);
67  }
68 }
69 
71 {
72  static SdlMainThread inst;
73  return inst;
74 }
75 
77 {
78  return (handle.gl_ctx != 0);
79 }
80 
82 
83 void SdlWindow::StartSDL(bool server_mode)
84 {
85  GetMainThread().MainLoop(server_mode);
86 }
87 
88 const int default_dpi = 72;
89 
90 bool SdlWindow::createWindow(const char* title, int x, int y, int w, int h,
91  bool legacyGlOnly)
92 {
93 #ifdef __EMSCRIPTEN__
94  is_multithreaded = false;
95 #endif
96  // create a new SDL window
97  handle = GetMainThread().GetHandle(this, title, x, y, w, h, legacyGlOnly);
98 
99  // at this point, window should be up
100  if (!handle.isInitialized())
101  {
102  return false;
103  }
104 
105  window_id = SDL_GetWindowID(handle.hwnd);
106 
107  GLenum err = glewInit();
108 #ifdef GLEW_ERROR_NO_GLX_DISPLAY
109  // NOTE: Hacky workaround for Wayland initialization failure
110  // See https://github.com/nigels-com/glew/issues/172
111  if (err == GLEW_ERROR_NO_GLX_DISPLAY)
112  {
113  cerr << "GLEW: No GLX display found. If you are using Wayland this can "
114  << "be ignored." << endl;
115  err = GLEW_OK;
116  }
117 #endif
118  if (err != GLEW_OK)
119  {
120  cerr << "FATAL: Failed to initialize GLEW: "
121  << glewGetErrorString(err) << endl;
122  return false;
123  }
124 
125  // print versions
126  PRINT_DEBUG("Using GLEW " << glewGetString(GLEW_VERSION) << std::endl);
127  PRINT_DEBUG("Using GL " << glGetString(GL_VERSION) << std::endl);
128 
129  renderer.reset(new gl3::MeshRenderer);
130  renderer->setSamplesMSAA(GetMultisample());
131 #ifndef __EMSCRIPTEN__
132  if (!GLEW_VERSION_1_1)
133  {
134  cerr << "FATAL: Minimum of OpenGL 1.1 is required." << endl;
135  return false;
136  }
137  if (!GLEW_VERSION_1_3)
138  {
139  // Multitexturing was introduced into the core OpenGL specification in
140  // version 1.3; for versions before, we need to load the functions from
141  // the ARB_multitexture extension.
142  if (GLEW_ARB_multitexture)
143  {
144  glActiveTexture = glActiveTextureARB;
145  glClientActiveTexture = glClientActiveTextureARB;
146  glMultiTexCoord2f = glMultiTexCoord2fARB;
147  }
148  else
149  {
150  cerr << "FATAL: Missing OpenGL multitexture support." << endl;
151  return false;
152  }
153  }
154  if (!GLEW_VERSION_3_0 && GLEW_EXT_transform_feedback)
155  {
156  glBindBufferBase = glBindBufferBaseEXT;
157  // Use an explicit typecast to suppress an error from inconsistent types
158  // that are present in older versions of GLEW.
159  glTransformFeedbackVaryings =
160  (PFNGLTRANSFORMFEEDBACKVARYINGSPROC)glTransformFeedbackVaryingsEXT;
161  glBeginTransformFeedback = glBeginTransformFeedbackEXT;
162  glEndTransformFeedback = glEndTransformFeedbackEXT;
163  }
164  if (!legacyGlOnly && (GLEW_VERSION_3_0
165  || (GLEW_VERSION_2_0 && GLEW_EXT_transform_feedback)))
166  {
167  // We require both shaders and transform feedback EXT_transform_feedback
168  // was made core in OpenGL 3.0
169  PRINT_DEBUG("Loading CoreGLDevice..." << endl);
170  renderer->setDevice<gl3::CoreGLDevice>();
171  }
172  else
173  {
174  PRINT_DEBUG("Shader support missing, loading FFGLDevice..." << endl);
175  renderer->setDevice<gl3::FFGLDevice>();
176  }
177 
178 #else
179  renderer->setDevice<gl3::CoreGLDevice>();
180 #endif
181 
182  return true;
183 }
184 
186 {
187  // Let the main SDL thread delete the handles
188  GetMainThread().DeleteHandle(std::move(handle));
189 }
190 
191 void SdlWindow::windowEvent(SDL_WindowEvent& ew)
192 {
193  switch (ew.event)
194  {
195  case SDL_WINDOWEVENT_EXPOSED:
196  case SDL_WINDOWEVENT_RESIZED:
197  update_before_expose = true;
198  if (onExpose)
199  {
200  wnd_state = RenderState::ExposePending;
201  }
202  break;
203  case SDL_WINDOWEVENT_CLOSE:
204  running = false;
205  break;
206  case SDL_WINDOWEVENT_MOVED:
207  update_before_expose = true;
208  break;
209  default:
210  break;
211  }
212 }
213 
214 void SdlWindow::motionEvent(SDL_MouseMotionEvent& em)
215 {
216  EventInfo info =
217  {
218  em.x, em.y,
219  SDL_GetModState()
220  };
221  if (em.state & SDL_BUTTON_LMASK)
222  {
223  if (onMouseMove[SDL_BUTTON_LEFT])
224  {
225  onMouseMove[SDL_BUTTON_LEFT](&info);
226  }
227  }
228  else if (em.state & SDL_BUTTON_RMASK)
229  {
230  if (onMouseMove[SDL_BUTTON_RIGHT])
231  {
232  onMouseMove[SDL_BUTTON_RIGHT](&info);
233  }
234  }
235  else if (em.state & SDL_BUTTON_MMASK)
236  {
237  if (onMouseMove[SDL_BUTTON_MIDDLE])
238  {
239  onMouseMove[SDL_BUTTON_MIDDLE](&info);
240  }
241  }
242 }
243 
244 void SdlWindow::mouseEventDown(SDL_MouseButtonEvent& eb)
245 {
246  if (onMouseDown[eb.button])
247  {
248  EventInfo info =
249  {
250  eb.x, eb.y,
251  SDL_GetModState()
252  };
253  onMouseDown[eb.button](&info);
254  }
255 }
256 
257 void SdlWindow::mouseEventUp(SDL_MouseButtonEvent& eb)
258 {
259  if (onMouseUp[eb.button])
260  {
261  EventInfo info =
262  {
263  eb.x, eb.y,
264  SDL_GetModState()
265  };
266  onMouseUp[eb.button](&info);
267  }
268 }
269 
270 void SdlWindow::keyEvent(SDL_Keysym& ks)
271 {
272  bool handled = false;
273  if (ks.sym >= 128 || ks.sym < 32)
274  {
275  if (onKeyDown[ks.sym])
276  {
277  onKeyDown[ks.sym](ks.mod);
278  handled = true;
279  }
280  }
281  else if (ks.sym < 256 && std::isdigit(ks.sym))
282  {
283  if (!(SDL_GetModState() & KMOD_SHIFT))
284  {
285  // handle number key event here
286  onKeyDown[ks.sym](ks.mod);
287  handled = true;
288  }
289  }
290  else if (ctrlDown)
291  {
292  if (onKeyDown[ks.sym])
293  {
294  onKeyDown[ks.sym](ks.mod);
295  handled = true;
296  }
297  }
298  if (ks.sym == SDLK_RCTRL || ks.sym == SDLK_LCTRL)
299  {
300  ctrlDown = true;
301  }
302  if (handled)
303  {
304  bool isAlt = ks.mod & (KMOD_ALT);
305  bool isCtrl = ks.mod & (KMOD_CTRL);
306  saved_keys += "[";
307  if (isCtrl) { saved_keys += "C-"; }
308  if (isAlt) { saved_keys += "Alt-"; }
309  if (ks.sym < 256 && std::isalpha(ks.sym))
310  {
311  // key with corresponding text output
312  char c = ks.sym;
313  if (!(ks.mod & KMOD_SHIFT)) { c = std::tolower(c); }
314  saved_keys += c;
315  }
316  else
317  {
318  saved_keys += SDL_GetKeyName(ks.sym);
319  }
320  saved_keys += "]";
321  }
322 }
323 
324 void SdlWindow::keyEvent(char c)
325 {
326  if (!std::isdigit(c) && onKeyDown[c])
327  {
328  SDL_Keymod mods = SDL_GetModState();
329  bool isAlt = mods & (KMOD_ALT);
330  bool isCtrl = mods & (KMOD_CTRL);
331  onKeyDown[c](mods);
332  if (isAlt || isCtrl)
333  {
334  saved_keys += "[";
335  if (isCtrl) { saved_keys += "C-"; }
336  if (isAlt) { saved_keys += "Alt-"; }
337  }
338  saved_keys += c;
339  if (isAlt || isCtrl)
340  {
341  saved_keys += "]";
342  }
343  }
344 }
345 
346 void SdlWindow::multiGestureEvent(SDL_MultiGestureEvent & e)
347 {
348  if (e.numFingers == 2)
349  {
350  if (onTouchPinch && fabs(e.dDist) > 0.00002)
351  {
352  onTouchPinch(e);
353  }
354 
355  if (onTouchRotate)
356  {
357  onTouchRotate(e);
358  }
359  }
360 }
361 
363 {
364  if (!is_multithreaded)
365  {
366  // Pull events from GetMainThread() object
368  }
369  bool events_pending = false;
370  bool sleep = false;
371  {
372  lock_guard<mutex> evt_guard{event_mutex};
373  events_pending = !waiting_events.empty();
374  }
375  if (events_pending)
376  {
377  bool keep_going;
378  do
379  {
380  SDL_Event e;
381  // Fetch next event from the queue
382  {
383  lock_guard<mutex> evt_guard{event_mutex};
384  e = waiting_events.front();
385  waiting_events.pop_front();
386  events_pending = !waiting_events.empty();
387  }
388  keep_going = false;
389  switch (e.type)
390  {
391  case SDL_QUIT:
392  running = false;
393  break;
394  case SDL_WINDOWEVENT:
395  windowEvent(e.window);
396  if (wnd_state != RenderState::ExposePending)
397  {
398  keep_going = true;
399  }
400  break;
401  case SDL_MULTIGESTURE:
402  multiGestureEvent(e.mgesture);
403  keep_going = true;
404  break;
405  case SDL_KEYDOWN:
406  keyEvent(e.key.keysym);
407  break;
408  case SDL_KEYUP:
409  if (e.key.keysym.sym == SDLK_LCTRL
410  || e.key.keysym.sym == SDLK_RCTRL)
411  {
412  ctrlDown = false;
413  }
414  break;
415  case SDL_TEXTINPUT:
416  keyEvent(e.text.text[0]);
417  break;
418  case SDL_MOUSEMOTION:
419  motionEvent(e.motion);
420  // continue processing events
421  keep_going = true;
422  break;
423  case SDL_MOUSEBUTTONDOWN:
424  mouseEventDown(e.button);
425  break;
426  case SDL_MOUSEBUTTONUP:
427  mouseEventUp(e.button);
428  break;
429  }
430  }
431  while (keep_going && events_pending);
432  }
433  else if (onIdle)
434  {
435  {
436  unique_lock<mutex> event_lock{event_mutex};
437  call_idle_func = false;
438  }
439  sleep = onIdle();
440  }
441  else
442  {
443  // No actions performed this iteration.
444  sleep = true;
445  }
446  if (wnd_state == RenderState::ExposePending)
447  {
448 #ifdef SDL_VIDEO_DRIVER_COCOA
449  if (update_before_expose)
450  {
451  // On SDL, when the OpenGL context is on a separate thread from the
452  // main thread, the call to [NSOpenGLContext update] after a resize or
453  // move event is only scheduled for after the next swap event. Any
454  // rendering/OpenGL commands occuring before this update will be
455  // corrupted.
456  //
457  // To avoid this issue, we just call [NSOpenGLContext update]
458  // immediately before the expose event.
459  SdlCocoaPlatform* platform =
460  dynamic_cast<SdlCocoaPlatform*>(GetMainThread().GetPlatform());
461  if (platform)
462  {
463  platform->ContextUpdate();
464  }
465  update_before_expose = false;
466  }
467 #endif
468  onExpose();
469  wnd_state = RenderState::SwapPending;
470  }
471  else if (is_multithreaded && sleep)
472  {
473  // No updates to vis, wait for next wakeup event from glvis_command or WM
474  unique_lock<mutex> event_lock{event_mutex};
475  events_available.wait(event_lock, [this]()
476  {
477  // Sleep until events from WM or glvis_command can be handled
478  return !waiting_events.empty() || call_idle_func;
479  });
480  }
481 }
482 
484 {
485  running = true;
486 #ifdef __EMSCRIPTEN__
487  emscripten_set_main_loop_arg([](void* arg)
488  {
489  ((SdlWindow*) arg)->mainIter();
490  }, this, 0, true);
491 #else
492  while (running)
493  {
494  mainIter();
495  if (takeScreenshot)
496  {
497  Screenshot(screenshot_file.c_str(), screenshot_convert);
498  takeScreenshot = false;
499  }
500  if (wnd_state == RenderState::SwapPending)
501  {
502 #ifdef SDL_VIDEO_DRIVER_COCOA
503  // TODO: Temporary workaround - after merge, everyone should update to
504  // latest SDL
505  SdlCocoaPlatform* mac_platform
506  = dynamic_cast<SdlCocoaPlatform*>(GetMainThread().GetPlatform());
507  if (mac_platform && mac_platform->UseThreadWorkaround())
508  {
509  mac_platform->SwapWindow();
510  }
511  else
512  {
513  SDL_GL_SwapWindow(handle.hwnd);
514  }
515 #else
516  SDL_GL_SwapWindow(handle.hwnd);
517 #endif
518  wnd_state = RenderState::Updated;
519  }
520  }
521 #endif
522 }
523 
525 {
526  // Note: not executed from the main thread
527  {
528  lock_guard<mutex> evt_guard{event_mutex};
529  call_idle_func = true;
530  }
531  events_available.notify_all();
532 }
533 
534 void SdlWindow::getWindowSize(int& w, int& h)
535 {
536  w = 0;
537  h = 0;
538  if (handle.isInitialized())
539  {
540 #ifdef __EMSCRIPTEN__
541  if (canvas_id_.empty())
542  {
543  std::cerr << "error: id is undefined: " << canvas_id_ << std::endl;
544  return;
545  }
546  double dw, dh;
547  auto err = emscripten_get_element_css_size(canvas_id_.c_str(), &dw, &dh);
548  w = int(dw);
549  h = int(dh);
550  if (err != EMSCRIPTEN_RESULT_SUCCESS)
551  {
552  std::cerr << "error (emscripten_get_element_css_size): " << err << std::endl;
553  return;
554  }
555 #else
556  SDL_GetWindowSize(handle.hwnd, &w, &h);
557 #endif
558  w /= pixel_scale_x;
559  h /= pixel_scale_y;
560  }
561 }
562 
563 void SdlWindow::getGLDrawSize(int& w, int& h)
564 {
565  SDL_GL_GetDrawableSize(handle.hwnd, &w, &h);
566 }
567 
568 void SdlWindow::getDpi(int& w, int& h)
569 {
570  w = default_dpi;
571  h = default_dpi;
572  if (!handle.isInitialized())
573  {
574  PRINT_DEBUG("warning: unable to get dpi: handle is null" << endl);
575  return;
576  }
577  int disp = SDL_GetWindowDisplayIndex(handle.hwnd);
578  if (disp < 0)
579  {
580  PRINT_DEBUG("warning: problem getting display index: " << SDL_GetError()
581  << endl);
582  PRINT_DEBUG("returning default dpi of " << default_dpi << endl);
583  return;
584  }
585 
586  float f_w, f_h;
587  if (SDL_GetDisplayDPI(disp, NULL, &f_w, &f_h))
588  {
589  PRINT_DEBUG("warning: problem getting dpi, setting to " << default_dpi
590  << ": " << SDL_GetError() << endl);
591  }
592  else
593  {
594  PRINT_DEBUG("Screen DPI: w = " << f_w << " ppi, h = " << f_h << " ppi");
595  w = f_w + 0.5f;
596  h = f_h + 0.5f;
597  PRINT_DEBUG(" (" << w << " x " << h << ')' << endl);
598  }
599 }
600 
601 void SdlWindow::setWindowTitle(std::string& title)
602 {
603  setWindowTitle(title.c_str());
604 }
605 
606 void SdlWindow::setWindowTitle(const char * title)
607 {
608  GetMainThread().SetWindowTitle(handle, title);
609 }
610 
611 void SdlWindow::setWindowSize(int w, int h)
612 {
613  GetMainThread().SetWindowSize(handle, pixel_scale_x*w, pixel_scale_y*h);
614  update_before_expose = true;
615 
616 }
617 
618 void SdlWindow::setWindowPos(int x, int y)
619 {
620  bool uc_x = SDL_WINDOWPOS_ISUNDEFINED(x) ||
621  SDL_WINDOWPOS_ISCENTERED(x);
622  bool uc_y = SDL_WINDOWPOS_ISUNDEFINED(y) ||
623  SDL_WINDOWPOS_ISCENTERED(y);
625  uc_x ? x : pixel_scale_x*x,
626  uc_y ? y : pixel_scale_y*y);
627  update_before_expose = true;
628 }
629 
630 void SdlWindow::signalKeyDown(SDL_Keycode k, SDL_Keymod m)
631 {
632  SDL_Event event;
633  if (k >= 32 && k < 128)
634  {
635  event.type = SDL_TEXTINPUT;
636  event.text.windowID = window_id;
637  event.text.text[0] = k;
638  }
639  else
640  {
641  event.type = SDL_KEYDOWN;
642  event.key.windowID = window_id;
643  event.key.keysym.sym = k;
644  event.key.keysym.mod = m;
645  }
646  queueEvents({ event });
647 }
648 
650 {
651  SDL_GL_SwapWindow(handle.hwnd);
652 }
SdlMainThread & GetMainThread()
Definition: sdl.cpp:70
void signalKeyDown(SDL_Keycode k, SDL_Keymod m=KMOD_NONE)
Definition: sdl.cpp:630
void SwapWindow()
Definition: sdl_mac.mm:111
bool UseThreadWorkaround() const
Definition: sdl_mac.mm:66
int Screenshot(const char *fname, bool convert)
Take a screenshot using libtiff, libpng or sdl2.
Definition: aux_vis.cpp:970
void getGLDrawSize(int &w, int &h)
Definition: sdl.cpp:563
bool isGlInitialized()
Returns true if the OpenGL context was successfully initialized.
Definition: sdl.cpp:76
void mainLoop()
Runs the window loop.
Definition: sdl.cpp:483
const int default_dpi
Definition: sdl.cpp:88
~SdlWindow()
Definition: sdl.cpp:185
void setWindowTitle(std::string &title)
Definition: sdl.cpp:601
void signalLoop()
Definition: sdl.cpp:524
void setWindowPos(int x, int y)
Definition: sdl.cpp:618
void SetWindowTitle(const Handle &handle, std::string title)
Definition: sdl_main.cpp:334
int GetMultisample()
Definition: aux_vis.cpp:1573
void swapBuffer()
Definition: sdl.cpp:649
void getWindowSize(int &w, int &h)
Definition: sdl.cpp:534
SdlNativePlatform * GetPlatform() const
Definition: sdl_main.hpp:60
Handle GetHandle(SdlWindow *wnd, const std::string &title, int x, int y, int w, int h, bool legacyGlOnly)
Definition: sdl_main.cpp:270
static void StartSDL(bool server_mode)
Definition: sdl.cpp:83
void MainLoop(bool server_mode)
Definition: sdl_main.cpp:98
void SetWindowPosition(const Handle &handle, int x, int y)
Definition: sdl_main.cpp:360
bool createWindow(const char *title, int x, int y, int w, int h, bool legacyGlOnly)
Definition: sdl.cpp:90
void DeleteHandle(Handle to_delete)
Definition: sdl_main.cpp:316
string screenshot_file
Definition: glvis_driver.py:45
void DispatchSDLEvents()
Definition: sdl_main.cpp:188
void SetWindowSize(const Handle &handle, int w, int h)
Definition: sdl_main.cpp:347
void ContextUpdate()
Definition: sdl_mac.mm:80
SdlWindow()
Definition: sdl.cpp:81
void getDpi(int &wdpi, int &hdpi)
Definition: sdl.cpp:568
void mainIter()
Definition: sdl.cpp:362
void setWindowSize(int w, int h)
Definition: sdl.cpp:611