LibreOffice Module vcl (master)  1
x11/context.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #include <memory>
11 #include <vcl/lazydelete.hxx>
12 
13 #include <svdata.hxx>
14 
15 #include <unx/saldisp.hxx>
16 #include <unx/salframe.h>
17 #include <unx/salgdi.h>
18 #include <unx/salinst.h>
19 #include <unx/salvd.h>
20 #include <unx/x11/xlimits.hxx>
21 
22 #include <opengl/zone.hxx>
23 
26 #include <sal/log.hxx>
27 
28 static std::vector<GLXContext> g_vShareList;
29 static bool g_bAnyCurrent;
30 
31 namespace {
32 
33 class X11OpenGLContext : public OpenGLContext
34 {
35 public:
36  virtual void initWindow() override;
37 private:
38  GLX11Window m_aGLWin;
39  virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
40  virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
41  virtual bool ImplInit() override;
42  void initGLWindow(Visual* pVisual);
43  virtual SystemWindowData generateWinData(vcl::Window* pParent, bool bRequestLegacyContext) override;
44  virtual void makeCurrent() override;
45  virtual void destroyCurrentContext() override;
46  virtual bool isCurrent() override;
47  virtual bool isAnyCurrent() override;
48  virtual void sync() override;
49  virtual void resetCurrent() override;
50  virtual void swapBuffers() override;
51 };
52 
53 #ifdef DBG_UTIL
54  int unxErrorHandler(Display* dpy, XErrorEvent* event)
55  {
56  char err[256];
57  char req[256];
58  char minor[256];
59  XGetErrorText(dpy, event->error_code, err, 256);
60  XGetErrorText(dpy, event->request_code, req, 256);
61  XGetErrorText(dpy, event->minor_code, minor, 256);
62  SAL_WARN("vcl.opengl", "Error: " << err << ", Req: " << req << ", Minor: " << minor);
63  return 0;
64  }
65 #endif
66 
67  typedef int (*errorHandler)(Display* /*dpy*/, XErrorEvent* /*evnt*/);
68 
69  class TempErrorHandler
70  {
71  private:
72  errorHandler oldErrorHandler;
73  Display* mdpy;
74 
75  public:
76  TempErrorHandler(Display* dpy, errorHandler newErrorHandler)
77  : oldErrorHandler(nullptr)
78  , mdpy(dpy)
79  {
80  if (mdpy)
81  {
82  XLockDisplay(dpy);
83  XSync(dpy, false);
84  oldErrorHandler = XSetErrorHandler(newErrorHandler);
85  }
86  }
87 
88  ~TempErrorHandler()
89  {
90  if (mdpy)
91  {
92  // sync so that we possibly get an XError
93  glXWaitGL();
94  XSync(mdpy, false);
95  XSetErrorHandler(oldErrorHandler);
96  XUnlockDisplay(mdpy);
97  }
98  }
99  };
100 
101  bool errorTriggered;
102  int oglErrorHandler( Display* /*dpy*/, XErrorEvent* /*evnt*/ )
103  {
104  errorTriggered = true;
105 
106  return 0;
107  }
108 
109  GLXFBConfig* getFBConfig(Display* dpy, Window win, int& nBestFBC)
110  {
111  OpenGLZone aZone;
112 
113  if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) )
114  return nullptr;
115 
116  VCL_GL_INFO("window: " << win);
117 
118  XWindowAttributes xattr;
119  if( !XGetWindowAttributes( dpy, win, &xattr ) )
120  {
121  SAL_WARN("vcl.opengl", "Failed to get window attributes for fbconfig " << win);
122  xattr.screen = nullptr;
123  xattr.visual = nullptr;
124  }
125 
126  int screen = XScreenNumberOfScreen( xattr.screen );
127 
128  // TODO: moggi: Select colour channel depth based on visual attributes, not hardcoded */
129  static int visual_attribs[] =
130  {
131  GLX_DOUBLEBUFFER, True,
132  GLX_X_RENDERABLE, True,
133  GLX_RED_SIZE, 8,
134  GLX_GREEN_SIZE, 8,
135  GLX_BLUE_SIZE, 8,
136  GLX_ALPHA_SIZE, 8,
137  GLX_DEPTH_SIZE, 24,
138  GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
139  None
140  };
141 
142  int fbCount = 0;
143  GLXFBConfig* pFBC = glXChooseFBConfig( dpy,
144  screen,
145  visual_attribs, &fbCount );
146 
147  if(!pFBC)
148  {
149  SAL_WARN("vcl.opengl", "no suitable fb format found");
150  return nullptr;
151  }
152 
153  int best_num_samp = -1;
154  for(int i = 0; i < fbCount; ++i)
155  {
156  XVisualInfo* pVi = glXGetVisualFromFBConfig( dpy, pFBC[i] );
157  if(pVi && (xattr.visual && pVi->visualid == xattr.visual->visualid) )
158  {
159  // pick the one with the most samples per pixel
160  int nSampleBuf = 0;
161  int nSamples = 0;
162  glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLE_BUFFERS, &nSampleBuf );
163  glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLES , &nSamples );
164 
165  if ( nBestFBC < 0 || (nSampleBuf && ( nSamples > best_num_samp )) )
166  {
167  nBestFBC = i;
168  best_num_samp = nSamples;
169  }
170  }
171  XFree( pVi );
172  }
173 
174  return pFBC;
175  }
176 }
177 
178 void X11OpenGLContext::sync()
179 {
180  OpenGLZone aZone;
181  glXWaitGL();
182  XSync(m_aGLWin.dpy, false);
183 }
184 
185 void X11OpenGLContext::swapBuffers()
186 {
187  OpenGLZone aZone;
188 
189  glXSwapBuffers(m_aGLWin.dpy, m_aGLWin.win);
190 
191  BuffersSwapped();
192 }
193 
194 void X11OpenGLContext::resetCurrent()
195 {
196  clearCurrent();
197 
198  OpenGLZone aZone;
199 
200  if (m_aGLWin.dpy)
201  {
202  glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
203  g_bAnyCurrent = false;
204  }
205 }
206 
207 bool X11OpenGLContext::isCurrent()
208 {
209  OpenGLZone aZone;
210  return g_bAnyCurrent && m_aGLWin.ctx && glXGetCurrentContext() == m_aGLWin.ctx &&
211  glXGetCurrentDrawable() == m_aGLWin.win;
212 }
213 
214 bool X11OpenGLContext::isAnyCurrent()
215 {
216  return g_bAnyCurrent && glXGetCurrentContext() != None;
217 }
218 
219 SystemWindowData X11OpenGLContext::generateWinData(vcl::Window* pParent, bool /*bRequestLegacyContext*/)
220 {
221  OpenGLZone aZone;
222 
223  SystemWindowData aWinData;
224  aWinData.pVisual = nullptr;
225  aWinData.bClipUsingNativeWidget = false;
226 
227  const SystemEnvData* sysData(pParent->GetSystemData());
228 
229  Display *dpy = static_cast<Display*>(sysData->pDisplay);
230  Window win = sysData->GetWindowHandle(pParent->ImplGetFrame());
231 
232  if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) )
233  return aWinData;
234 
235  int best_fbc = -1;
236  GLXFBConfig* pFBC = getFBConfig(dpy, win, best_fbc);
237 
238  if (!pFBC)
239  return aWinData;
240 
241  XVisualInfo* vi = nullptr;
242  if( best_fbc != -1 )
243  vi = glXGetVisualFromFBConfig( dpy, pFBC[best_fbc] );
244 
245  XFree(pFBC);
246 
247  if( vi )
248  {
249  VCL_GL_INFO("using VisualID " << vi->visualid);
250  aWinData.pVisual = static_cast<void*>(vi->visual);
251  }
252 
253  return aWinData;
254 }
255 
256 bool X11OpenGLContext::ImplInit()
257 {
258  if (!m_aGLWin.dpy)
259  return false;
260 
261  OpenGLZone aZone;
262 
263  GLXContext pSharedCtx( nullptr );
264 #ifdef DBG_UTIL
265  TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler);
266 #endif
267 
268  VCL_GL_INFO("OpenGLContext::ImplInit----start");
269 
270  if (!g_vShareList.empty())
271  pSharedCtx = g_vShareList.front();
272 
273  //tdf#112166 for, e.g. VirtualBox GL, claiming OpenGL 2.1
274  static bool hasCreateContextAttribsARB = glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB")) != nullptr;
275  if (hasCreateContextAttribsARB && !mbRequestLegacyContext)
276  {
277  int best_fbc = -1;
278  GLXFBConfig* pFBC = getFBConfig(m_aGLWin.dpy, m_aGLWin.win, best_fbc);
279 
280  if (pFBC && best_fbc != -1)
281  {
282  int const pContextAttribs[] =
283  {
284 #if 0 // defined(DBG_UTIL)
285  GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
286  GLX_CONTEXT_MINOR_VERSION_ARB, 2,
287 #endif
288  None
289 
290  };
291  m_aGLWin.ctx = glXCreateContextAttribsARB(m_aGLWin.dpy, pFBC[best_fbc], pSharedCtx, /* direct, not via X */ GL_TRUE, pContextAttribs);
292  SAL_INFO_IF(m_aGLWin.ctx, "vcl.opengl", "created a 3.2 core context");
293  }
294  else
295  SAL_WARN("vcl.opengl", "unable to find correct FBC");
296  }
297 
298  if (!m_aGLWin.ctx)
299  {
300  if (!m_aGLWin.vi)
301  return false;
302 
303  SAL_WARN("vcl.opengl", "attempting to create a non-double-buffered "
304  "visual matching the context");
305 
306  m_aGLWin.ctx = glXCreateContext(m_aGLWin.dpy,
307  m_aGLWin.vi,
308  pSharedCtx,
309  GL_TRUE /* direct, not via X server */);
310  }
311 
312  if( m_aGLWin.ctx )
313  {
314  g_vShareList.push_back( m_aGLWin.ctx );
315  }
316  else
317  {
318  SAL_WARN("vcl.opengl", "unable to create GLX context");
319  return false;
320  }
321 
322  if( !glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx ) )
323  {
324  g_bAnyCurrent = false;
325  SAL_WARN("vcl.opengl", "unable to select current GLX context");
326  return false;
327  }
328 
329  g_bAnyCurrent = true;
330 
331  int glxMinor, glxMajor;
332  double nGLXVersion = 0;
333  if( glXQueryVersion( m_aGLWin.dpy, &glxMajor, &glxMinor ) )
334  nGLXVersion = glxMajor + 0.1*glxMinor;
335  SAL_INFO("vcl.opengl", "available GLX version: " << nGLXVersion);
336 
337  SAL_INFO("vcl.opengl", "available GL extensions: " << glGetString(GL_EXTENSIONS));
338 
339  XWindowAttributes aWinAttr;
340  if( !XGetWindowAttributes( m_aGLWin.dpy, m_aGLWin.win, &aWinAttr ) )
341  {
342  SAL_WARN("vcl.opengl", "Failed to get window attributes on " << m_aGLWin.win);
343  m_aGLWin.Width = 0;
344  m_aGLWin.Height = 0;
345  }
346  else
347  {
348  m_aGLWin.Width = aWinAttr.width;
349  m_aGLWin.Height = aWinAttr.height;
350  }
351 
352  if( m_aGLWin.HasGLXExtension("GLX_SGI_swap_control" ) )
353  {
354  // enable vsync
355  typedef GLint (*glXSwapIntervalProc)(GLint);
356  glXSwapIntervalProc glXSwapInterval = reinterpret_cast<glXSwapIntervalProc>(glXGetProcAddress( reinterpret_cast<const GLubyte*>("glXSwapIntervalSGI") ));
357  if( glXSwapInterval )
358  {
359  TempErrorHandler aLocalErrorHandler(m_aGLWin.dpy, oglErrorHandler);
360 
361  errorTriggered = false;
362 
363  glXSwapInterval( 1 );
364 
365  if( errorTriggered )
366  SAL_WARN("vcl.opengl", "error when trying to set swap interval, NVIDIA or Mesa bug?");
367  else
368  VCL_GL_INFO("set swap interval to 1 (enable vsync)");
369  }
370  }
371 
372  bool bRet = InitGL();
373  InitGLDebugging();
374 
375  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
376 
377  registerAsCurrent();
378 
379  return bRet;
380 }
381 
382 void X11OpenGLContext::makeCurrent()
383 {
384  if (isCurrent())
385  return;
386 
387  OpenGLZone aZone;
388 
389  clearCurrent();
390 
391 #ifdef DBG_UTIL
392  TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler);
393 #endif
394 
395  if (m_aGLWin.dpy)
396  {
397  if (!glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx ))
398  {
399  g_bAnyCurrent = false;
400  SAL_WARN("vcl.opengl", "OpenGLContext::makeCurrent failed "
401  "on drawable " << m_aGLWin.win);
402  return;
403  }
404  g_bAnyCurrent = true;
405  }
406 
407  registerAsCurrent();
408 }
409 
410 void X11OpenGLContext::destroyCurrentContext()
411 {
412  if(!m_aGLWin.ctx)
413  return;
414 
415  std::vector<GLXContext>::iterator itr = std::remove( g_vShareList.begin(), g_vShareList.end(), m_aGLWin.ctx );
416  if (itr != g_vShareList.end())
417  g_vShareList.erase(itr);
418 
419  glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
420  g_bAnyCurrent = false;
421  if( glGetError() != GL_NO_ERROR )
422  {
423  SAL_WARN("vcl.opengl", "glError: " << glGetError());
424  }
425  glXDestroyContext(m_aGLWin.dpy, m_aGLWin.ctx);
426  m_aGLWin.ctx = nullptr;
427 }
428 
429 void X11OpenGLContext::initGLWindow(Visual* pVisual)
430 {
431  OpenGLZone aZone;
432 
433  // Get visual info
434  {
435  XVisualInfo aTemplate;
436  aTemplate.visualid = XVisualIDFromVisual( pVisual );
437  int nVisuals = 0;
438  XVisualInfo* pInfo = XGetVisualInfo( m_aGLWin.dpy, VisualIDMask, &aTemplate, &nVisuals );
439  if( nVisuals != 1 )
440  SAL_WARN( "vcl.opengl", "match count for visual id is not 1" );
441  m_aGLWin.vi = pInfo;
442  }
443 
444  // Check multisample support
445  /* TODO: moggi: This is not necessarily correct in the DBG_UTIL path, as it picks
446  * an FBConfig instead ... */
447  int nSamples = 0;
448  glXGetConfig(m_aGLWin.dpy, m_aGLWin.vi, GLX_SAMPLES, &nSamples);
449  if( nSamples > 0 )
450  m_aGLWin.bMultiSampleSupported = true;
451 
452  m_aGLWin.GLXExtensions = glXQueryExtensionsString( m_aGLWin.dpy, m_aGLWin.screen );
453  SAL_INFO("vcl.opengl", "available GLX extensions: " << m_aGLWin.GLXExtensions);
454 }
455 
456 void X11OpenGLContext::initWindow()
457 {
458  const SystemEnvData* pChildSysData = nullptr;
459  SystemWindowData winData = generateWinData(mpWindow, false);
460  if( winData.pVisual )
461  {
462  if( !m_pChildWindow )
463  {
464  m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
465  }
466  pChildSysData = m_pChildWindow->GetSystemData();
467  }
468 
469  if (!m_pChildWindow || !pChildSysData)
470  return;
471 
472  InitChildWindow(m_pChildWindow.get());
473 
474  m_aGLWin.dpy = static_cast<Display*>(pChildSysData->pDisplay);
475  m_aGLWin.win = pChildSysData->GetWindowHandle(m_pChildWindow->ImplGetFrame());
476  m_aGLWin.screen = pChildSysData->nScreen;
477 
478  Visual* pVisual = static_cast<Visual*>(pChildSysData->pVisual);
479  initGLWindow(pVisual);
480 }
481 
483  : dpy(nullptr)
484  , screen(0)
485  , win(0)
486  , vi(nullptr)
487  , ctx(nullptr)
488 {
489 }
490 
491 bool GLX11Window::HasGLXExtension( const char* name ) const
492 {
493  for (sal_Int32 i = 0; i != -1;) {
494  if (GLXExtensions.getToken(0, ' ', i) == name) {
495  return true;
496  }
497  }
498  return false;
499 }
500 
502 {
503  XFree(vi);
504 }
505 
506 bool GLX11Window::Synchronize(bool bOnoff) const
507 {
508  XSynchronize(dpy, bOnoff);
509  return true;
510 }
511 
513 {
514  return new X11OpenGLContext;
515 }
516 
517 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
OString GLXExtensions
Definition: saldisp.hxx:218
virtual void sync()
virtual ~GLX11Window() override
static std::vector< GLXContext > g_vShareList
Definition: x11/context.cxx:28
#define SAL_INFO_IF(condition, area, stream)
const wchar_t *typedef int(__stdcall *DllNativeUnregProc)(int
void * pDisplay
Definition: sysdata.hxx:67
virtual void initWindow()
sal_uIntPtr GetWindowHandle(const SalFrame *pReference) const
Definition: syschild.cxx:192
None
void * pVisual
Definition: sysdata.hxx:70
virtual bool isAnyCurrent()
Is any GL context the current context ?
bool HasGLXExtension(const char *name) const
static bool g_bAnyCurrent
Definition: x11/context.cxx:29
virtual GLWindow & getModifiableOpenGLWindow()=0
virtual bool Synchronize(bool bOnoff) const override
err
virtual void resetCurrent()
reset the GL context so this context is not implicit in subsequent GL calls.
Holds the information of our new child window.
#define VCL_GL_INFO(stream)
Helper to do a SAL_INFO as well as a GL log.
int i
Display
virtual void swapBuffers()
tuple ctx
virtual const GLWindow & getOpenGLWindow() const =0
virtual const SystemEnvData * GetSystemData() const
Definition: window.cxx:3852
We want to be able to detect if a given crash came from the OpenGL code, so use this helper to track ...
Definition: opengl/zone.hxx:22
virtual void makeCurrent()
make this GL context current - so it is implicit in subsequent GL calls
SalFrame * ImplGetFrame() const
Definition: window2.cxx:846
virtual void destroyCurrentContext()
virtual OpenGLContext * CreateOpenGLContext() override
#define SAL_INFO(area, stream)
static VclPtr< reference_type > Create(Arg &&...arg)
A construction helper for VclPtr.
Definition: vclptr.hxx:127
bool bClipUsingNativeWidget
Definition: sysdata.hxx:195
#define SAL_WARN(area, stream)
virtual SystemWindowData generateWinData(vcl::Window *pParent, bool bRequestLegacyContext)
virtual bool isCurrent()
Is this GL context the current context ?
virtual bool ImplInit()