• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2015 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 
7 // DisplayGLX.cpp: GLX implementation of egl::Display
8 
9 #include "libANGLE/renderer/gl/glx/DisplayGLX.h"
10 
11 #include <EGL/eglext.h>
12 #include <algorithm>
13 #include <cstring>
14 #include <fstream>
15 
16 #include "common/debug.h"
17 #include "common/system_utils.h"
18 #include "libANGLE/Config.h"
19 #include "libANGLE/Context.h"
20 #include "libANGLE/Display.h"
21 #include "libANGLE/Surface.h"
22 #include "libANGLE/renderer/gl/ContextGL.h"
23 #include "libANGLE/renderer/gl/glx/PbufferSurfaceGLX.h"
24 #include "libANGLE/renderer/gl/glx/PixmapSurfaceGLX.h"
25 #include "libANGLE/renderer/gl/glx/RendererGLX.h"
26 #include "libANGLE/renderer/gl/glx/WindowSurfaceGLX.h"
27 #include "libANGLE/renderer/gl/glx/glx_utils.h"
28 #include "libANGLE/renderer/gl/renderergl_utils.h"
29 
30 namespace
31 {
32 
GetRobustnessVideoMemoryPurge(const egl::AttributeMap & attribs)33 rx::RobustnessVideoMemoryPurgeStatus GetRobustnessVideoMemoryPurge(const egl::AttributeMap &attribs)
34 {
35     return static_cast<rx::RobustnessVideoMemoryPurgeStatus>(
36         attribs.get(GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV, GL_FALSE));
37 }
38 
HasParallelShaderCompileExtension(const rx::FunctionsGL * functions)39 bool HasParallelShaderCompileExtension(const rx::FunctionsGL *functions)
40 {
41     return functions->maxShaderCompilerThreadsKHR != nullptr ||
42            functions->maxShaderCompilerThreadsARB != nullptr;
43 }
44 
45 }  // anonymous namespace
46 
47 namespace rx
48 {
49 
IgnoreX11Errors(Display *,XErrorEvent *)50 static int IgnoreX11Errors(Display *, XErrorEvent *)
51 {
52     return 0;
53 }
54 
55 class FunctionsGLGLX : public FunctionsGL
56 {
57   public:
FunctionsGLGLX(PFNGETPROCPROC getProc)58     FunctionsGLGLX(PFNGETPROCPROC getProc) : mGetProc(getProc) {}
59 
~FunctionsGLGLX()60     ~FunctionsGLGLX() override {}
61 
62   private:
loadProcAddress(const std::string & function) const63     void *loadProcAddress(const std::string &function) const override
64     {
65         return reinterpret_cast<void *>(mGetProc(function.c_str()));
66     }
67 
68     PFNGETPROCPROC mGetProc;
69 };
70 
DisplayGLX(const egl::DisplayState & state)71 DisplayGLX::DisplayGLX(const egl::DisplayState &state)
72     : DisplayGL(state),
73       mRequestedVisual(-1),
74       mContextConfig(nullptr),
75       mVisuals(nullptr),
76       mContext(nullptr),
77       mSharedContext(nullptr),
78       mCurrentNativeContexts(),
79       mInitPbuffer(0),
80       mUsesNewXDisplay(false),
81       mIsMesa(false),
82       mHasMultisample(false),
83       mHasARBCreateContext(false),
84       mHasARBCreateContextProfile(false),
85       mHasARBCreateContextRobustness(false),
86       mHasEXTCreateContextES2Profile(false),
87       mHasNVRobustnessVideoMemoryPurge(false),
88       mSwapControl(SwapControl::Absent),
89       mMinSwapInterval(0),
90       mMaxSwapInterval(0),
91       mCurrentSwapInterval(-1),
92       mCurrentDrawable(0),
93       mXDisplay(nullptr),
94       mEGLDisplay(nullptr)
95 {}
96 
~DisplayGLX()97 DisplayGLX::~DisplayGLX() {}
98 
initialize(egl::Display * display)99 egl::Error DisplayGLX::initialize(egl::Display *display)
100 {
101     mEGLDisplay           = display;
102     mXDisplay             = reinterpret_cast<Display *>(display->getNativeDisplayId());
103     const auto &attribMap = display->getAttributeMap();
104 
105     // ANGLE_platform_angle allows the creation of a default display
106     // using EGL_DEFAULT_DISPLAY (= nullptr). In this case just open
107     // the display specified by the DISPLAY environment variable.
108     if (mXDisplay == reinterpret_cast<Display *>(EGL_DEFAULT_DISPLAY))
109     {
110         mUsesNewXDisplay = true;
111         mXDisplay        = XOpenDisplay(nullptr);
112         if (!mXDisplay)
113         {
114             return egl::EglNotInitialized() << "Could not open the default X display.";
115         }
116     }
117 
118     std::string glxInitError;
119     if (!mGLX.initialize(mXDisplay, DefaultScreen(mXDisplay), &glxInitError))
120     {
121         return egl::EglNotInitialized() << glxInitError;
122     }
123 
124     mHasMultisample             = mGLX.minorVersion > 3 || mGLX.hasExtension("GLX_ARB_multisample");
125     mHasARBCreateContext        = mGLX.hasExtension("GLX_ARB_create_context");
126     mHasARBCreateContextProfile = mGLX.hasExtension("GLX_ARB_create_context_profile");
127     mHasARBCreateContextRobustness   = mGLX.hasExtension("GLX_ARB_create_context_robustness");
128     mHasEXTCreateContextES2Profile   = mGLX.hasExtension("GLX_EXT_create_context_es2_profile");
129     mHasNVRobustnessVideoMemoryPurge = mGLX.hasExtension("GLX_NV_robustness_video_memory_purge");
130 
131     std::string clientVendor = mGLX.getClientString(GLX_VENDOR);
132     mIsMesa                  = clientVendor.find("Mesa") != std::string::npos;
133 
134     // Choose the swap_control extension to use, if any.
135     // The EXT version is better as it allows glXSwapInterval to be called per
136     // window, while we'll potentially need to change the swap interval on each
137     // swap buffers when using the SGI or MESA versions.
138     if (mGLX.hasExtension("GLX_EXT_swap_control"))
139     {
140         mSwapControl = SwapControl::EXT;
141 
142         // In GLX_EXT_swap_control querying these is done on a GLXWindow so we just
143         // set default values.
144         mMinSwapInterval = 0;
145         mMaxSwapInterval = 4;
146     }
147     else if (mGLX.hasExtension("GLX_MESA_swap_control"))
148     {
149         // If we have the Mesa or SGI extension, assume that you can at least set
150         // a swap interval of 0 or 1.
151         mSwapControl     = SwapControl::Mesa;
152         mMinSwapInterval = 0;
153         mMinSwapInterval = 1;
154     }
155     else if (mGLX.hasExtension("GLX_SGI_swap_control"))
156     {
157         mSwapControl     = SwapControl::SGI;
158         mMinSwapInterval = 0;
159         mMinSwapInterval = 1;
160     }
161     else
162     {
163         mSwapControl     = SwapControl::Absent;
164         mMinSwapInterval = 1;
165         mMinSwapInterval = 1;
166     }
167 
168     if (attribMap.contains(EGL_X11_VISUAL_ID_ANGLE))
169     {
170         mRequestedVisual = static_cast<EGLint>(attribMap.get(EGL_X11_VISUAL_ID_ANGLE, -1));
171         // There is no direct way to get the GLXFBConfig matching an X11 visual ID
172         // so we have to iterate over all the GLXFBConfigs to find the right one.
173         int nConfigs;
174         int attribList[] = {
175             None,
176         };
177         glx::FBConfig *allConfigs = mGLX.chooseFBConfig(attribList, &nConfigs);
178 
179         for (int i = 0; i < nConfigs; ++i)
180         {
181             if (getGLXFBConfigAttrib(allConfigs[i], GLX_VISUAL_ID) == mRequestedVisual)
182             {
183                 mContextConfig = allConfigs[i];
184                 break;
185             }
186         }
187         XFree(allConfigs);
188 
189         if (mContextConfig == nullptr)
190         {
191             return egl::EglNotInitialized() << "Invalid visual ID requested.";
192         }
193     }
194     else
195     {
196         // When glXMakeCurrent is called, the context and the surface must be
197         // compatible which in glX-speak means that their config have the same
198         // color buffer type, are both RGBA or ColorIndex, and their buffers have
199         // the same depth, if they exist.
200         // Since our whole EGL implementation is backed by only one GL context, this
201         // context must be compatible with all the GLXFBConfig corresponding to the
202         // EGLconfigs that we will be exposing.
203         int nConfigs;
204         int attribList[]          = {// We want RGBA8 and DEPTH24_STENCIL8
205                             GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8,
206                             GLX_DEPTH_SIZE, 24, GLX_STENCIL_SIZE, 8,
207                             // We want RGBA rendering (vs COLOR_INDEX) and doublebuffer
208                             GLX_RENDER_TYPE, GLX_RGBA_BIT,
209                             // Double buffer is not strictly required as a non-doublebuffer
210                             // context can work with a doublebuffered surface, but it still
211                             // flickers and all applications want doublebuffer anyway.
212                             GLX_DOUBLEBUFFER, True,
213                             // All of these must be supported for full EGL support
214                             GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT | GLX_PIXMAP_BIT,
215                             // This makes sure the config have an associated visual Id
216                             GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, GLX_NONE, None};
217         glx::FBConfig *candidates = mGLX.chooseFBConfig(attribList, &nConfigs);
218         if (nConfigs == 0)
219         {
220             XFree(candidates);
221             return egl::EglNotInitialized()
222                    << "Could not find a decent GLX FBConfig to create the context.";
223         }
224         mContextConfig = candidates[0];
225         XFree(candidates);
226     }
227 
228     const auto &eglAttributes = display->getAttributeMap();
229     if (mHasARBCreateContext)
230     {
231         egl::Error error = initializeContext(mContextConfig, eglAttributes, &mContext);
232         if (error.isError())
233         {
234             return error;
235         }
236     }
237     else
238     {
239         if (eglAttributes.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE,
240                               EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE) ==
241             EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE)
242         {
243             return egl::EglNotInitialized() << "Cannot create an OpenGL ES platform on GLX without "
244                                                "the GLX_ARB_create_context extension.";
245         }
246 
247         XVisualInfo visualTemplate;
248         visualTemplate.visualid = getGLXFBConfigAttrib(mContextConfig, GLX_VISUAL_ID);
249 
250         int numVisuals = 0;
251         mVisuals       = XGetVisualInfo(mXDisplay, VisualIDMask, &visualTemplate, &numVisuals);
252         if (numVisuals <= 0)
253         {
254             return egl::EglNotInitialized() << "Could not get the visual info from the fb config";
255         }
256         ASSERT(numVisuals == 1);
257 
258         mContext = mGLX.createContext(&mVisuals[0], nullptr, true);
259 
260         if (!mContext)
261         {
262             return egl::EglNotInitialized() << "Could not create GL context.";
263         }
264 
265         mSharedContext = mGLX.createContext(&mVisuals[0], mContext, True);
266     }
267     ASSERT(mContext);
268 
269     mCurrentNativeContexts[angle::GetCurrentThreadUniqueId()] = mContext;
270 
271     // FunctionsGL and DisplayGL need to make a few GL calls, for example to
272     // query the version of the context so we need to make the context current.
273     // glXMakeCurrent requires a GLXDrawable so we create a temporary Pbuffer
274     // (of size 1, 1) for the duration of these calls.
275     // Ideally we would want to unset the current context and destroy the pbuffer
276     // before going back to the application but this is TODO
277     // We could use a pbuffer of size (0, 0) but it fails on the Intel Mesa driver
278     // as commented on https://bugs.freedesktop.org/show_bug.cgi?id=38869 so we
279     // use (1, 1) instead.
280 
281     int initPbufferAttribs[] = {
282         GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, None,
283     };
284     mInitPbuffer = mGLX.createPbuffer(mContextConfig, initPbufferAttribs);
285     if (!mInitPbuffer)
286     {
287         return egl::EglNotInitialized() << "Could not create the initialization pbuffer.";
288     }
289 
290     if (!mGLX.makeCurrent(mInitPbuffer, mContext))
291     {
292         return egl::EglNotInitialized() << "Could not make the initialization pbuffer current.";
293     }
294 
295     std::unique_ptr<FunctionsGL> functionsGL(new FunctionsGLGLX(mGLX.getProc));
296     functionsGL->initialize(eglAttributes);
297     if (mHasNVRobustnessVideoMemoryPurge)
298     {
299         GLenum status = functionsGL->getGraphicsResetStatus();
300         if (status != GL_NO_ERROR && status != GL_PURGED_CONTEXT_RESET_NV)
301         {
302             return egl::EglNotInitialized() << "Context lost for unknown reason.";
303         }
304     }
305     // TODO(cwallez, angleproject:1303) Disable the OpenGL ES backend on Linux NVIDIA and Intel as
306     // it has problems on our automated testing. An OpenGL ES backend might not trigger this test if
307     // there is no Desktop OpenGL support, but that's not the case in our automated testing.
308     VendorID vendor = GetVendorID(functionsGL.get());
309     bool isOpenGLES =
310         eglAttributes.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE) ==
311         EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE;
312     if (isOpenGLES && (IsIntel(vendor) || IsNvidia(vendor)))
313     {
314         return egl::EglNotInitialized() << "Intel or NVIDIA OpenGL ES drivers are not supported.";
315     }
316 
317     if (mSharedContext)
318     {
319         if (HasParallelShaderCompileExtension(functionsGL.get()))
320         {
321             mGLX.destroyContext(mSharedContext);
322             mSharedContext = nullptr;
323         }
324         else
325         {
326             for (unsigned int i = 0; i < RendererGL::getMaxWorkerContexts(); ++i)
327             {
328                 glx::Pbuffer workerPbuffer = mGLX.createPbuffer(mContextConfig, initPbufferAttribs);
329                 if (!workerPbuffer)
330                 {
331                     return egl::EglNotInitialized() << "Could not create the worker pbuffers.";
332                 }
333                 mWorkerPbufferPool.push_back(workerPbuffer);
334             }
335         }
336     }
337 
338     syncXCommands(false);
339 
340     mRenderer.reset(new RendererGLX(std::move(functionsGL), eglAttributes, this));
341     const gl::Version &maxVersion = mRenderer->getMaxSupportedESVersion();
342     if (maxVersion < gl::Version(2, 0))
343     {
344         return egl::EglNotInitialized() << "OpenGL ES 2.0 is not supportable.";
345     }
346 
347     return DisplayGL::initialize(display);
348 }
349 
terminate()350 void DisplayGLX::terminate()
351 {
352     DisplayGL::terminate();
353 
354     if (mVisuals)
355     {
356         XFree(mVisuals);
357         mVisuals = 0;
358     }
359 
360     if (mInitPbuffer)
361     {
362         mGLX.destroyPbuffer(mInitPbuffer);
363         mInitPbuffer = 0;
364     }
365 
366     for (auto &workerPbuffer : mWorkerPbufferPool)
367     {
368         mGLX.destroyPbuffer(workerPbuffer);
369     }
370     mWorkerPbufferPool.clear();
371 
372     mCurrentNativeContexts.clear();
373 
374     if (mContext)
375     {
376         mGLX.destroyContext(mContext);
377         mContext = nullptr;
378     }
379 
380     if (mSharedContext)
381     {
382         mGLX.destroyContext(mSharedContext);
383         mSharedContext = nullptr;
384     }
385 
386     mGLX.terminate();
387 
388     mRenderer.reset();
389 
390     if (mUsesNewXDisplay)
391     {
392         XCloseDisplay(mXDisplay);
393     }
394 }
395 
makeCurrent(egl::Display * display,egl::Surface * drawSurface,egl::Surface * readSurface,gl::Context * context)396 egl::Error DisplayGLX::makeCurrent(egl::Display *display,
397                                    egl::Surface *drawSurface,
398                                    egl::Surface *readSurface,
399                                    gl::Context *context)
400 {
401     glx::Drawable newDrawable =
402         (drawSurface ? GetImplAs<SurfaceGLX>(drawSurface)->getDrawable() : mInitPbuffer);
403     glx::Context newContext = mContext;
404     // If the thread calling makeCurrent does not have the correct context current (either mContext
405     // or 0), we need to set it current.
406     if (!context)
407     {
408         newDrawable = 0;
409         newContext  = 0;
410     }
411     if (newDrawable != mCurrentDrawable ||
412         newContext != mCurrentNativeContexts[angle::GetCurrentThreadUniqueId()])
413     {
414         if (mGLX.makeCurrent(newDrawable, newContext) != True)
415         {
416             return egl::EglContextLost() << "Failed to make the GLX context current";
417         }
418         mCurrentNativeContexts[angle::GetCurrentThreadUniqueId()] = newContext;
419         mCurrentDrawable                                          = newDrawable;
420     }
421 
422     return DisplayGL::makeCurrent(display, drawSurface, readSurface, context);
423 }
424 
createWindowSurface(const egl::SurfaceState & state,EGLNativeWindowType window,const egl::AttributeMap & attribs)425 SurfaceImpl *DisplayGLX::createWindowSurface(const egl::SurfaceState &state,
426                                              EGLNativeWindowType window,
427                                              const egl::AttributeMap &attribs)
428 {
429     ASSERT(configIdToGLXConfig.count(state.config->configID) > 0);
430     glx::FBConfig fbConfig = configIdToGLXConfig[state.config->configID];
431 
432     return new WindowSurfaceGLX(state, mGLX, this, window, mGLX.getDisplay(), fbConfig);
433 }
434 
createPbufferSurface(const egl::SurfaceState & state,const egl::AttributeMap & attribs)435 SurfaceImpl *DisplayGLX::createPbufferSurface(const egl::SurfaceState &state,
436                                               const egl::AttributeMap &attribs)
437 {
438     ASSERT(configIdToGLXConfig.count(state.config->configID) > 0);
439     glx::FBConfig fbConfig = configIdToGLXConfig[state.config->configID];
440 
441     EGLint width  = static_cast<EGLint>(attribs.get(EGL_WIDTH, 0));
442     EGLint height = static_cast<EGLint>(attribs.get(EGL_HEIGHT, 0));
443     bool largest  = (attribs.get(EGL_LARGEST_PBUFFER, EGL_FALSE) == EGL_TRUE);
444 
445     return new PbufferSurfaceGLX(state, width, height, largest, mGLX, fbConfig);
446 }
447 
createPbufferFromClientBuffer(const egl::SurfaceState & state,EGLenum buftype,EGLClientBuffer clientBuffer,const egl::AttributeMap & attribs)448 SurfaceImpl *DisplayGLX::createPbufferFromClientBuffer(const egl::SurfaceState &state,
449                                                        EGLenum buftype,
450                                                        EGLClientBuffer clientBuffer,
451                                                        const egl::AttributeMap &attribs)
452 {
453     UNIMPLEMENTED();
454     return nullptr;
455 }
456 
createPixmapSurface(const egl::SurfaceState & state,NativePixmapType nativePixmap,const egl::AttributeMap & attribs)457 SurfaceImpl *DisplayGLX::createPixmapSurface(const egl::SurfaceState &state,
458                                              NativePixmapType nativePixmap,
459                                              const egl::AttributeMap &attribs)
460 {
461     ASSERT(configIdToGLXConfig.count(state.config->configID) > 0);
462     glx::FBConfig fbConfig = configIdToGLXConfig[state.config->configID];
463     return new PixmapSurfaceGLX(state, nativePixmap, mGLX.getDisplay(), mGLX, fbConfig);
464 }
465 
validatePixmap(const egl::Config * config,EGLNativePixmapType pixmap,const egl::AttributeMap & attributes) const466 egl::Error DisplayGLX::validatePixmap(const egl::Config *config,
467                                       EGLNativePixmapType pixmap,
468                                       const egl::AttributeMap &attributes) const
469 {
470     Window rootWindow;
471     int x                    = 0;
472     int y                    = 0;
473     unsigned int width       = 0;
474     unsigned int height      = 0;
475     unsigned int borderWidth = 0;
476     unsigned int depth       = 0;
477     int status = XGetGeometry(mGLX.getDisplay(), pixmap, &rootWindow, &x, &y, &width, &height,
478                               &borderWidth, &depth);
479     if (!status)
480     {
481         return egl::EglBadNativePixmap() << "Invalid native pixmap, XGetGeometry failed: "
482                                          << x11::XErrorToString(mXDisplay, status);
483     }
484 
485     return egl::NoError();
486 }
487 
createContext(const gl::State & state,gl::ErrorSet * errorSet,const egl::Config * configuration,const gl::Context * shareContext,const egl::AttributeMap & attribs)488 ContextImpl *DisplayGLX::createContext(const gl::State &state,
489                                        gl::ErrorSet *errorSet,
490                                        const egl::Config *configuration,
491                                        const gl::Context *shareContext,
492                                        const egl::AttributeMap &attribs)
493 {
494     RobustnessVideoMemoryPurgeStatus robustnessVideoMemoryPurgeStatus =
495         GetRobustnessVideoMemoryPurge(attribs);
496     return new ContextGL(state, errorSet, mRenderer, robustnessVideoMemoryPurgeStatus);
497 }
498 
initializeContext(glx::FBConfig config,const egl::AttributeMap & eglAttributes,glx::Context * context)499 egl::Error DisplayGLX::initializeContext(glx::FBConfig config,
500                                          const egl::AttributeMap &eglAttributes,
501                                          glx::Context *context)
502 {
503     int profileMask = 0;
504 
505     EGLint requestedDisplayType = static_cast<EGLint>(
506         eglAttributes.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE));
507     if (requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE)
508     {
509         if (!mHasEXTCreateContextES2Profile)
510         {
511             return egl::EglNotInitialized() << "Cannot create an OpenGL ES platform on GLX without "
512                                                "the GLX_EXT_create_context_es_profile extension.";
513         }
514 
515         ASSERT(mHasARBCreateContextProfile);
516         profileMask |= GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
517     }
518 
519     // Create a context of the requested version, if any.
520     gl::Version requestedVersion(static_cast<EGLint>(eglAttributes.get(
521                                      EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, EGL_DONT_CARE)),
522                                  static_cast<EGLint>(eglAttributes.get(
523                                      EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, EGL_DONT_CARE)));
524     if (static_cast<EGLint>(requestedVersion.major) != EGL_DONT_CARE &&
525         static_cast<EGLint>(requestedVersion.minor) != EGL_DONT_CARE)
526     {
527         if (!(profileMask & GLX_CONTEXT_ES2_PROFILE_BIT_EXT) &&
528             requestedVersion >= gl::Version(3, 2))
529         {
530             profileMask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
531         }
532         return createContextAttribs(config, requestedVersion, profileMask, context);
533     }
534 
535     // The only way to get a core profile context of the highest version using
536     // glXCreateContextAttrib is to try creationg contexts in decreasing version
537     // numbers. It might look that asking for a core context of version (0, 0)
538     // works on some driver but it create a _compatibility_ context of the highest
539     // version instead. The cost of failing a context creation is small (< 0.1 ms)
540     // on Mesa but is unfortunately a bit expensive on the Nvidia driver (~3ms).
541     // Also try to get any Desktop GL context, but if that fails fallback to
542     // asking for OpenGL ES contexts.
543 
544     // NOTE: below we return as soon as we're able to create a context so the
545     // "error" variable is EGL_SUCCESS when returned contrary to the common idiom
546     // of returning "error" when there is an actual error.
547     for (const auto &info : GenerateContextCreationToTry(requestedDisplayType, mIsMesa))
548     {
549         int profileFlag = 0;
550         if (info.type == ContextCreationTry::Type::DESKTOP_CORE)
551         {
552             profileFlag |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
553         }
554         else if (info.type == ContextCreationTry::Type::ES)
555         {
556             profileFlag |= GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
557         }
558 
559         egl::Error error = createContextAttribs(config, info.version, profileFlag, context);
560         if (!error.isError())
561         {
562             return error;
563         }
564     }
565 
566     return egl::EglNotInitialized() << "Could not create a backing OpenGL context.";
567 }
568 
generateConfigs()569 egl::ConfigSet DisplayGLX::generateConfigs()
570 {
571     egl::ConfigSet configs;
572     configIdToGLXConfig.clear();
573 
574     const gl::Version &maxVersion = getMaxSupportedESVersion();
575     ASSERT(maxVersion >= gl::Version(2, 0));
576     bool supportsES3 = maxVersion >= gl::Version(3, 0);
577 
578     int contextRedSize   = getGLXFBConfigAttrib(mContextConfig, GLX_RED_SIZE);
579     int contextGreenSize = getGLXFBConfigAttrib(mContextConfig, GLX_GREEN_SIZE);
580     int contextBlueSize  = getGLXFBConfigAttrib(mContextConfig, GLX_BLUE_SIZE);
581     int contextAlphaSize = getGLXFBConfigAttrib(mContextConfig, GLX_ALPHA_SIZE);
582 
583     int contextDepthSize   = getGLXFBConfigAttrib(mContextConfig, GLX_DEPTH_SIZE);
584     int contextStencilSize = getGLXFBConfigAttrib(mContextConfig, GLX_STENCIL_SIZE);
585 
586     int contextSamples = mHasMultisample ? getGLXFBConfigAttrib(mContextConfig, GLX_SAMPLES) : 0;
587     int contextSampleBuffers =
588         mHasMultisample ? getGLXFBConfigAttrib(mContextConfig, GLX_SAMPLE_BUFFERS) : 0;
589 
590     int contextAccumRedSize   = getGLXFBConfigAttrib(mContextConfig, GLX_ACCUM_RED_SIZE);
591     int contextAccumGreenSize = getGLXFBConfigAttrib(mContextConfig, GLX_ACCUM_GREEN_SIZE);
592     int contextAccumBlueSize  = getGLXFBConfigAttrib(mContextConfig, GLX_ACCUM_BLUE_SIZE);
593     int contextAccumAlphaSize = getGLXFBConfigAttrib(mContextConfig, GLX_ACCUM_ALPHA_SIZE);
594 
595     int attribList[] = {
596         GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_X_RENDERABLE, True, GLX_DOUBLEBUFFER, True, None,
597     };
598 
599     int glxConfigCount;
600     glx::FBConfig *glxConfigs = mGLX.chooseFBConfig(attribList, &glxConfigCount);
601 
602     for (int i = 0; i < glxConfigCount; i++)
603     {
604         glx::FBConfig glxConfig = glxConfigs[i];
605         egl::Config config;
606 
607         // Native stuff
608         config.nativeVisualID   = getGLXFBConfigAttrib(glxConfig, GLX_VISUAL_ID);
609         config.nativeVisualType = getGLXFBConfigAttrib(glxConfig, GLX_X_VISUAL_TYPE);
610         config.nativeRenderable = EGL_TRUE;
611 
612         // When a visual ID has been specified with EGL_ANGLE_x11_visual we should
613         // only return configs with this visual: it will maximize performance by avoid
614         // blits in the driver when showing the window on the screen.
615         if (mRequestedVisual != -1 && config.nativeVisualID != mRequestedVisual)
616         {
617             continue;
618         }
619 
620         // Buffer sizes
621         config.redSize     = getGLXFBConfigAttrib(glxConfig, GLX_RED_SIZE);
622         config.greenSize   = getGLXFBConfigAttrib(glxConfig, GLX_GREEN_SIZE);
623         config.blueSize    = getGLXFBConfigAttrib(glxConfig, GLX_BLUE_SIZE);
624         config.alphaSize   = getGLXFBConfigAttrib(glxConfig, GLX_ALPHA_SIZE);
625         config.depthSize   = getGLXFBConfigAttrib(glxConfig, GLX_DEPTH_SIZE);
626         config.stencilSize = getGLXFBConfigAttrib(glxConfig, GLX_STENCIL_SIZE);
627 
628         // We require RGBA8 and the D24S8 (or no DS buffer)
629         if (config.redSize != contextRedSize || config.greenSize != contextGreenSize ||
630             config.blueSize != contextBlueSize || config.alphaSize != contextAlphaSize)
631         {
632             continue;
633         }
634         // The GLX spec says that it is ok for a whole buffer to not be present
635         // however the Mesa Intel driver (and probably on other Mesa drivers)
636         // fails to make current when the Depth stencil doesn't exactly match the
637         // configuration.
638         bool hasSameDepthStencil =
639             config.depthSize == contextDepthSize && config.stencilSize == contextStencilSize;
640         bool hasNoDepthStencil = config.depthSize == 0 && config.stencilSize == 0;
641         if (!hasSameDepthStencil && (mIsMesa || !hasNoDepthStencil))
642         {
643             continue;
644         }
645 
646         config.colorBufferType = EGL_RGB_BUFFER;
647         config.luminanceSize   = 0;
648         config.alphaMaskSize   = 0;
649 
650         config.bufferSize = config.redSize + config.greenSize + config.blueSize + config.alphaSize;
651 
652         // Multisample and accumulation buffers
653         int samples = mHasMultisample ? getGLXFBConfigAttrib(glxConfig, GLX_SAMPLES) : 0;
654         int sampleBuffers =
655             mHasMultisample ? getGLXFBConfigAttrib(glxConfig, GLX_SAMPLE_BUFFERS) : 0;
656 
657         int accumRedSize   = getGLXFBConfigAttrib(glxConfig, GLX_ACCUM_RED_SIZE);
658         int accumGreenSize = getGLXFBConfigAttrib(glxConfig, GLX_ACCUM_GREEN_SIZE);
659         int accumBlueSize  = getGLXFBConfigAttrib(glxConfig, GLX_ACCUM_BLUE_SIZE);
660         int accumAlphaSize = getGLXFBConfigAttrib(glxConfig, GLX_ACCUM_ALPHA_SIZE);
661 
662         if (samples != contextSamples || sampleBuffers != contextSampleBuffers ||
663             accumRedSize != contextAccumRedSize || accumGreenSize != contextAccumGreenSize ||
664             accumBlueSize != contextAccumBlueSize || accumAlphaSize != contextAccumAlphaSize)
665         {
666             continue;
667         }
668 
669         config.samples       = samples;
670         config.sampleBuffers = sampleBuffers;
671 
672         // Transparency
673         if (getGLXFBConfigAttrib(glxConfig, GLX_TRANSPARENT_TYPE) == GLX_TRANSPARENT_RGB)
674         {
675             config.transparentType     = EGL_TRANSPARENT_RGB;
676             config.transparentRedValue = getGLXFBConfigAttrib(glxConfig, GLX_TRANSPARENT_RED_VALUE);
677             config.transparentGreenValue =
678                 getGLXFBConfigAttrib(glxConfig, GLX_TRANSPARENT_GREEN_VALUE);
679             config.transparentBlueValue =
680                 getGLXFBConfigAttrib(glxConfig, GLX_TRANSPARENT_BLUE_VALUE);
681         }
682         else
683         {
684             config.transparentType = EGL_NONE;
685         }
686 
687         // Pbuffer
688         config.maxPBufferWidth  = getGLXFBConfigAttrib(glxConfig, GLX_MAX_PBUFFER_WIDTH);
689         config.maxPBufferHeight = getGLXFBConfigAttrib(glxConfig, GLX_MAX_PBUFFER_HEIGHT);
690         config.maxPBufferPixels = getGLXFBConfigAttrib(glxConfig, GLX_MAX_PBUFFER_PIXELS);
691 
692         // Caveat
693         config.configCaveat = EGL_NONE;
694 
695         int caveat = getGLXFBConfigAttrib(glxConfig, GLX_CONFIG_CAVEAT);
696         if (caveat == GLX_SLOW_CONFIG)
697         {
698             config.configCaveat = EGL_SLOW_CONFIG;
699         }
700         else if (caveat == GLX_NON_CONFORMANT_CONFIG)
701         {
702             continue;
703         }
704 
705         // Misc
706         config.level = getGLXFBConfigAttrib(glxConfig, GLX_LEVEL);
707 
708         config.minSwapInterval = mMinSwapInterval;
709         config.maxSwapInterval = mMaxSwapInterval;
710 
711         // TODO(cwallez) wildly guessing these formats, another TODO says they should be removed
712         // anyway
713         config.renderTargetFormat = GL_RGBA8;
714         config.depthStencilFormat = GL_DEPTH24_STENCIL8;
715 
716         config.conformant     = EGL_OPENGL_ES2_BIT | (supportsES3 ? EGL_OPENGL_ES3_BIT_KHR : 0);
717         config.renderableType = config.conformant;
718 
719         // TODO(cwallez) I have no idea what this is
720         config.matchNativePixmap = EGL_NONE;
721 
722         config.colorComponentType = EGL_COLOR_COMPONENT_TYPE_FIXED_EXT;
723 
724         // GLX doesn't support binding pbuffers to textures and there is no way to differentiate in
725         // EGL that pixmaps can be bound but pbuffers cannot.  If both pixmaps and pbuffers are
726         // supported, generate extra configs with either pbuffer or pixmap support.
727         int glxDrawable     = getGLXFBConfigAttrib(glxConfig, GLX_DRAWABLE_TYPE);
728         bool pbufferSupport = (glxDrawable & EGL_PBUFFER_BIT) != 0;
729         bool pixmapSupport  = (glxDrawable & GLX_PIXMAP_BIT) != 0;
730         bool pixmapBindToTextureSupport =
731             pixmapSupport && mGLX.hasExtension("GLX_EXT_texture_from_pixmap");
732 
733         if (pbufferSupport && pixmapBindToTextureSupport)
734         {
735             // Generate the pixmap-only config
736             config.surfaceType = (glxDrawable & GLX_WINDOW_BIT ? EGL_WINDOW_BIT : 0) |
737                                  (pixmapSupport ? EGL_PIXMAP_BIT : 0);
738 
739             config.bindToTextureRGB = getGLXFBConfigAttrib(glxConfig, GLX_BIND_TO_TEXTURE_RGB_EXT);
740             config.bindToTextureRGBA =
741                 getGLXFBConfigAttrib(glxConfig, GLX_BIND_TO_TEXTURE_RGBA_EXT);
742             config.yInverted = getGLXFBConfigAttrib(glxConfig, GLX_Y_INVERTED_EXT);
743 
744             int id                  = configs.add(config);
745             configIdToGLXConfig[id] = glxConfig;
746         }
747 
748         // Generate the pbuffer config. It can support pixmaps but not bind-to-texture.
749         config.surfaceType = (glxDrawable & GLX_WINDOW_BIT ? EGL_WINDOW_BIT : 0) |
750                              (pbufferSupport ? EGL_PBUFFER_BIT : 0) |
751                              (pixmapSupport ? EGL_PIXMAP_BIT : 0);
752 
753         config.bindToTextureRGB  = false;
754         config.bindToTextureRGBA = false;
755         config.yInverted         = false;
756 
757         int id                  = configs.add(config);
758         configIdToGLXConfig[id] = glxConfig;
759     }
760 
761     XFree(glxConfigs);
762 
763     return configs;
764 }
765 
testDeviceLost()766 bool DisplayGLX::testDeviceLost()
767 {
768     return false;
769 }
770 
restoreLostDevice(const egl::Display * display)771 egl::Error DisplayGLX::restoreLostDevice(const egl::Display *display)
772 {
773     return egl::EglBadDisplay();
774 }
775 
isValidNativeWindow(EGLNativeWindowType window) const776 bool DisplayGLX::isValidNativeWindow(EGLNativeWindowType window) const
777 {
778 
779     // Check the validity of the window by calling a getter function on the window that
780     // returns a status code. If the window is bad the call return a status of zero. We
781     // need to set a temporary X11 error handler while doing this because the default
782     // X11 error handler exits the program on any error.
783     auto oldErrorHandler = XSetErrorHandler(IgnoreX11Errors);
784     XWindowAttributes attributes;
785     int status = XGetWindowAttributes(mXDisplay, window, &attributes);
786     XSetErrorHandler(oldErrorHandler);
787 
788     return status != 0;
789 }
790 
waitClient(const gl::Context * context)791 egl::Error DisplayGLX::waitClient(const gl::Context *context)
792 {
793     mGLX.waitGL();
794     return egl::NoError();
795 }
796 
waitNative(const gl::Context * context,EGLint engine)797 egl::Error DisplayGLX::waitNative(const gl::Context *context, EGLint engine)
798 {
799     // eglWaitNative is used to notice the driver of changes in X11 for the current surface, such as
800     // changes of the window size. We use this event to update the child window of WindowSurfaceGLX
801     // to match its parent window's size.
802     // Handling eglWaitNative this way helps the application control when resize happens. This is
803     // important because drivers have a tendency to clobber the back buffer when the windows are
804     // resized. See http://crbug.com/326995
805     egl::Surface *drawSurface = context->getCurrentDrawSurface();
806     egl::Surface *readSurface = context->getCurrentReadSurface();
807     if (drawSurface != nullptr)
808     {
809         SurfaceGLX *glxDrawSurface = GetImplAs<SurfaceGLX>(drawSurface);
810         ANGLE_TRY(glxDrawSurface->checkForResize());
811     }
812 
813     if (readSurface != drawSurface && readSurface != nullptr)
814     {
815         SurfaceGLX *glxReadSurface = GetImplAs<SurfaceGLX>(readSurface);
816         ANGLE_TRY(glxReadSurface->checkForResize());
817     }
818 
819     // We still need to forward the resizing of the child window to the driver.
820     mGLX.waitX();
821     return egl::NoError();
822 }
823 
getMaxSupportedESVersion() const824 gl::Version DisplayGLX::getMaxSupportedESVersion() const
825 {
826     return mRenderer->getMaxSupportedESVersion();
827 }
828 
syncXCommands(bool alwaysSync) const829 void DisplayGLX::syncXCommands(bool alwaysSync) const
830 {
831     if (mUsesNewXDisplay || alwaysSync)
832     {
833         XSync(mGLX.getDisplay(), False);
834     }
835 }
836 
setSwapInterval(glx::Drawable drawable,SwapControlData * data)837 void DisplayGLX::setSwapInterval(glx::Drawable drawable, SwapControlData *data)
838 {
839     ASSERT(data != nullptr);
840 
841     // TODO(cwallez) error checking?
842     if (mSwapControl == SwapControl::EXT)
843     {
844         // Prefer the EXT extension, it gives per-drawable swap intervals, which will
845         // minimize the number of driver calls.
846         if (data->maxSwapInterval < 0)
847         {
848             unsigned int maxSwapInterval = 0;
849             mGLX.queryDrawable(drawable, GLX_MAX_SWAP_INTERVAL_EXT, &maxSwapInterval);
850             data->maxSwapInterval = static_cast<int>(maxSwapInterval);
851         }
852 
853         // When the egl configs were generated we had to guess what the max swap interval
854         // was because we didn't have a window to query it one (and that this max could
855         // depend on the monitor). This means that the target interval might be higher
856         // than the max interval and needs to be clamped.
857         const int realInterval = std::min(data->targetSwapInterval, data->maxSwapInterval);
858         if (data->currentSwapInterval != realInterval)
859         {
860             mGLX.swapIntervalEXT(drawable, realInterval);
861             data->currentSwapInterval = realInterval;
862         }
863     }
864     else if (mCurrentSwapInterval != data->targetSwapInterval)
865     {
866         // With the Mesa or SGI extensions we can still do per-drawable swap control
867         // manually but it is more expensive in number of driver calls.
868         if (mSwapControl == SwapControl::Mesa)
869         {
870             mGLX.swapIntervalMESA(data->targetSwapInterval);
871         }
872         else if (mSwapControl == SwapControl::SGI)
873         {
874             mGLX.swapIntervalSGI(data->targetSwapInterval);
875         }
876         mCurrentSwapInterval = data->targetSwapInterval;
877     }
878 }
879 
isWindowVisualIdSpecified() const880 bool DisplayGLX::isWindowVisualIdSpecified() const
881 {
882     return mRequestedVisual != -1;
883 }
884 
isMatchingWindowVisualId(unsigned long visualId) const885 bool DisplayGLX::isMatchingWindowVisualId(unsigned long visualId) const
886 {
887     return isWindowVisualIdSpecified() && static_cast<unsigned long>(mRequestedVisual) == visualId;
888 }
889 
generateExtensions(egl::DisplayExtensions * outExtensions) const890 void DisplayGLX::generateExtensions(egl::DisplayExtensions *outExtensions) const
891 {
892     outExtensions->createContextRobustness = mHasARBCreateContextRobustness;
893 
894     // Contexts are virtualized so textures ans semaphores can be shared globally
895     outExtensions->displayTextureShareGroup   = true;
896     outExtensions->displaySemaphoreShareGroup = true;
897 
898     outExtensions->surfacelessContext = true;
899 
900     if (!mRenderer->getFeatures().disableSyncControlSupport.enabled)
901     {
902         const bool hasSyncControlOML        = mGLX.hasExtension("GLX_OML_sync_control");
903         outExtensions->syncControlCHROMIUM  = hasSyncControlOML;
904         outExtensions->syncControlRateANGLE = hasSyncControlOML;
905     }
906 
907     outExtensions->textureFromPixmapNOK = mGLX.hasExtension("GLX_EXT_texture_from_pixmap");
908 
909     outExtensions->robustnessVideoMemoryPurgeNV = mHasNVRobustnessVideoMemoryPurge;
910 
911     DisplayGL::generateExtensions(outExtensions);
912 }
913 
generateCaps(egl::Caps * outCaps) const914 void DisplayGLX::generateCaps(egl::Caps *outCaps) const
915 {
916     outCaps->textureNPOT = true;
917 }
918 
makeCurrentSurfaceless(gl::Context * context)919 egl::Error DisplayGLX::makeCurrentSurfaceless(gl::Context *context)
920 {
921     // Nothing to do because GLX always uses the same context and the previous surface can be left
922     // current.
923     return egl::NoError();
924 }
925 
getGLXFBConfigAttrib(glx::FBConfig config,int attrib) const926 int DisplayGLX::getGLXFBConfigAttrib(glx::FBConfig config, int attrib) const
927 {
928     int result;
929     mGLX.getFBConfigAttrib(config, attrib, &result);
930     return result;
931 }
932 
createContextAttribs(glx::FBConfig,const Optional<gl::Version> & version,int profileMask,glx::Context * context)933 egl::Error DisplayGLX::createContextAttribs(glx::FBConfig,
934                                             const Optional<gl::Version> &version,
935                                             int profileMask,
936                                             glx::Context *context)
937 {
938     mAttribs.clear();
939 
940     if (mHasARBCreateContextRobustness)
941     {
942         mAttribs.push_back(GLX_CONTEXT_FLAGS_ARB);
943         mAttribs.push_back(GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB);
944         mAttribs.push_back(GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB);
945         mAttribs.push_back(GLX_LOSE_CONTEXT_ON_RESET_ARB);
946         if (mHasNVRobustnessVideoMemoryPurge)
947         {
948             mAttribs.push_back(GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV);
949             mAttribs.push_back(GL_TRUE);
950         }
951     }
952 
953     if (version.valid())
954     {
955         mAttribs.push_back(GLX_CONTEXT_MAJOR_VERSION_ARB);
956         mAttribs.push_back(version.value().major);
957 
958         mAttribs.push_back(GLX_CONTEXT_MINOR_VERSION_ARB);
959         mAttribs.push_back(version.value().minor);
960     }
961 
962     if (profileMask != 0 && mHasARBCreateContextProfile)
963     {
964         mAttribs.push_back(GLX_CONTEXT_PROFILE_MASK_ARB);
965         mAttribs.push_back(profileMask);
966     }
967 
968     mAttribs.push_back(None);
969 
970     // When creating a context with glXCreateContextAttribsARB, a variety of X11 errors can
971     // be generated. To prevent these errors from crashing our process, we simply ignore
972     // them and only look if GLXContext was created.
973     // Process all events before setting the error handler to avoid desynchronizing XCB instances
974     // (the error handler is NOT per-display).
975     XSync(mXDisplay, False);
976     auto oldErrorHandler = XSetErrorHandler(IgnoreX11Errors);
977     *context = mGLX.createContextAttribsARB(mContextConfig, nullptr, True, mAttribs.data());
978     XSetErrorHandler(oldErrorHandler);
979 
980     if (!*context)
981     {
982         return egl::EglNotInitialized() << "Could not create GL context.";
983     }
984 
985     mSharedContext = mGLX.createContextAttribsARB(mContextConfig, mContext, True, mAttribs.data());
986 
987     return egl::NoError();
988 }
989 
990 class WorkerContextGLX final : public WorkerContext
991 {
992   public:
993     WorkerContextGLX(glx::Context context, FunctionsGLX *functions, glx::Pbuffer buffer);
994     ~WorkerContextGLX() override;
995 
996     bool makeCurrent() override;
997     void unmakeCurrent() override;
998 
999   private:
1000     glx::Context mContext;
1001     FunctionsGLX *mFunctions;
1002     glx::Pbuffer mBuffer;
1003 };
1004 
WorkerContextGLX(glx::Context context,FunctionsGLX * functions,glx::Pbuffer buffer)1005 WorkerContextGLX::WorkerContextGLX(glx::Context context,
1006                                    FunctionsGLX *functions,
1007                                    glx::Pbuffer buffer)
1008     : mContext(context), mFunctions(functions), mBuffer(buffer)
1009 {}
1010 
~WorkerContextGLX()1011 WorkerContextGLX::~WorkerContextGLX()
1012 {
1013     mFunctions->destroyContext(mContext);
1014     mFunctions->destroyPbuffer(mBuffer);
1015 }
1016 
makeCurrent()1017 bool WorkerContextGLX::makeCurrent()
1018 {
1019     Bool result = mFunctions->makeCurrent(mBuffer, mContext);
1020     if (result != True)
1021     {
1022         ERR() << "Unable to make the GLX context current.";
1023         return false;
1024     }
1025     return true;
1026 }
1027 
unmakeCurrent()1028 void WorkerContextGLX::unmakeCurrent()
1029 {
1030     mFunctions->makeCurrent(0, nullptr);
1031 }
1032 
createWorkerContext(std::string * infoLog)1033 WorkerContext *DisplayGLX::createWorkerContext(std::string *infoLog)
1034 {
1035     if (!mSharedContext)
1036     {
1037         *infoLog += "No shared context.";
1038         return nullptr;
1039     }
1040     if (mWorkerPbufferPool.empty())
1041     {
1042         *infoLog += "No worker pbuffers.";
1043         return nullptr;
1044     }
1045     glx::Context context = nullptr;
1046     if (mHasARBCreateContext)
1047     {
1048         context =
1049             mGLX.createContextAttribsARB(mContextConfig, mSharedContext, True, mAttribs.data());
1050     }
1051     else
1052     {
1053         context = mGLX.createContext(&mVisuals[0], mSharedContext, True);
1054     }
1055 
1056     if (!context)
1057     {
1058         *infoLog += "Unable to create the glx context.";
1059         return nullptr;
1060     }
1061 
1062     glx::Pbuffer workerPbuffer = mWorkerPbufferPool.back();
1063     mWorkerPbufferPool.pop_back();
1064 
1065     return new WorkerContextGLX(context, &mGLX, workerPbuffer);
1066 }
1067 
initializeFrontendFeatures(angle::FrontendFeatures * features) const1068 void DisplayGLX::initializeFrontendFeatures(angle::FrontendFeatures *features) const
1069 {
1070     mRenderer->initializeFrontendFeatures(features);
1071 }
1072 
populateFeatureList(angle::FeatureList * features)1073 void DisplayGLX::populateFeatureList(angle::FeatureList *features)
1074 {
1075     mRenderer->getFeatures().populateFeatureList(features);
1076 }
1077 
getRenderer() const1078 RendererGL *DisplayGLX::getRenderer() const
1079 {
1080     return mRenderer.get();
1081 }
1082 
isX11() const1083 bool DisplayGLX::isX11() const
1084 {
1085     return true;
1086 }
1087 
1088 }  // namespace rx
1089