| // |
| // Copyright 2015 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| |
| // DisplayGLX.cpp: GLX implementation of egl::Display |
| |
| #include "libANGLE/renderer/gl/glx/DisplayGLX.h" |
| |
| #include <EGL/eglext.h> |
| #include <algorithm> |
| #include <cstring> |
| #include <fstream> |
| |
| #include "common/debug.h" |
| #include "libANGLE/Config.h" |
| #include "libANGLE/Context.h" |
| #include "libANGLE/Display.h" |
| #include "libANGLE/Surface.h" |
| #include "libANGLE/renderer/gl/ContextGL.h" |
| #include "libANGLE/renderer/gl/glx/PbufferSurfaceGLX.h" |
| #include "libANGLE/renderer/gl/glx/PixmapSurfaceGLX.h" |
| #include "libANGLE/renderer/gl/glx/RendererGLX.h" |
| #include "libANGLE/renderer/gl/glx/WindowSurfaceGLX.h" |
| #include "libANGLE/renderer/gl/glx/glx_utils.h" |
| #include "libANGLE/renderer/gl/renderergl_utils.h" |
| |
| namespace |
| { |
| |
| rx::RobustnessVideoMemoryPurgeStatus GetRobustnessVideoMemoryPurge(const egl::AttributeMap &attribs) |
| { |
| return static_cast<rx::RobustnessVideoMemoryPurgeStatus>( |
| attribs.get(GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV, GL_FALSE)); |
| } |
| |
| bool HasParallelShaderCompileExtension(const rx::FunctionsGL *functions) |
| { |
| return functions->maxShaderCompilerThreadsKHR != nullptr || |
| functions->maxShaderCompilerThreadsARB != nullptr; |
| } |
| |
| } // anonymous namespace |
| |
| namespace rx |
| { |
| |
| static int IgnoreX11Errors(Display *, XErrorEvent *) |
| { |
| return 0; |
| } |
| |
| class FunctionsGLGLX : public FunctionsGL |
| { |
| public: |
| FunctionsGLGLX(PFNGETPROCPROC getProc) : mGetProc(getProc) {} |
| |
| ~FunctionsGLGLX() override {} |
| |
| private: |
| void *loadProcAddress(const std::string &function) const override |
| { |
| return reinterpret_cast<void *>(mGetProc(function.c_str())); |
| } |
| |
| PFNGETPROCPROC mGetProc; |
| }; |
| |
| DisplayGLX::DisplayGLX(const egl::DisplayState &state) |
| : DisplayGL(state), |
| mRequestedVisual(-1), |
| mContextConfig(nullptr), |
| mVisuals(nullptr), |
| mContext(nullptr), |
| mSharedContext(nullptr), |
| mCurrentNativeContexts(), |
| mInitPbuffer(0), |
| mUsesNewXDisplay(false), |
| mIsMesa(false), |
| mHasMultisample(false), |
| mHasARBCreateContext(false), |
| mHasARBCreateContextProfile(false), |
| mHasARBCreateContextRobustness(false), |
| mHasEXTCreateContextES2Profile(false), |
| mHasNVRobustnessVideoMemoryPurge(false), |
| mSwapControl(SwapControl::Absent), |
| mMinSwapInterval(0), |
| mMaxSwapInterval(0), |
| mCurrentSwapInterval(-1), |
| mCurrentDrawable(0), |
| mXDisplay(nullptr), |
| mEGLDisplay(nullptr) |
| {} |
| |
| DisplayGLX::~DisplayGLX() {} |
| |
| egl::Error DisplayGLX::initialize(egl::Display *display) |
| { |
| mEGLDisplay = display; |
| mXDisplay = reinterpret_cast<Display *>(display->getNativeDisplayId()); |
| const auto &attribMap = display->getAttributeMap(); |
| |
| // ANGLE_platform_angle allows the creation of a default display |
| // using EGL_DEFAULT_DISPLAY (= nullptr). In this case just open |
| // the display specified by the DISPLAY environment variable. |
| if (mXDisplay == reinterpret_cast<Display *>(EGL_DEFAULT_DISPLAY)) |
| { |
| mUsesNewXDisplay = true; |
| mXDisplay = XOpenDisplay(nullptr); |
| if (!mXDisplay) |
| { |
| return egl::EglNotInitialized() << "Could not open the default X display."; |
| } |
| } |
| |
| std::string glxInitError; |
| if (!mGLX.initialize(mXDisplay, DefaultScreen(mXDisplay), &glxInitError)) |
| { |
| return egl::EglNotInitialized() << glxInitError; |
| } |
| |
| mHasMultisample = mGLX.minorVersion > 3 || mGLX.hasExtension("GLX_ARB_multisample"); |
| mHasARBCreateContext = mGLX.hasExtension("GLX_ARB_create_context"); |
| mHasARBCreateContextProfile = mGLX.hasExtension("GLX_ARB_create_context_profile"); |
| mHasARBCreateContextRobustness = mGLX.hasExtension("GLX_ARB_create_context_robustness"); |
| mHasEXTCreateContextES2Profile = mGLX.hasExtension("GLX_EXT_create_context_es2_profile"); |
| mHasNVRobustnessVideoMemoryPurge = mGLX.hasExtension("GLX_NV_robustness_video_memory_purge"); |
| |
| std::string clientVendor = mGLX.getClientString(GLX_VENDOR); |
| mIsMesa = clientVendor.find("Mesa") != std::string::npos; |
| |
| // Choose the swap_control extension to use, if any. |
| // The EXT version is better as it allows glXSwapInterval to be called per |
| // window, while we'll potentially need to change the swap interval on each |
| // swap buffers when using the SGI or MESA versions. |
| if (mGLX.hasExtension("GLX_EXT_swap_control")) |
| { |
| mSwapControl = SwapControl::EXT; |
| |
| // In GLX_EXT_swap_control querying these is done on a GLXWindow so we just |
| // set default values. |
| mMinSwapInterval = 0; |
| mMaxSwapInterval = 4; |
| } |
| else if (mGLX.hasExtension("GLX_MESA_swap_control")) |
| { |
| // If we have the Mesa or SGI extension, assume that you can at least set |
| // a swap interval of 0 or 1. |
| mSwapControl = SwapControl::Mesa; |
| mMinSwapInterval = 0; |
| mMinSwapInterval = 1; |
| } |
| else if (mGLX.hasExtension("GLX_SGI_swap_control")) |
| { |
| mSwapControl = SwapControl::SGI; |
| mMinSwapInterval = 0; |
| mMinSwapInterval = 1; |
| } |
| else |
| { |
| mSwapControl = SwapControl::Absent; |
| mMinSwapInterval = 1; |
| mMinSwapInterval = 1; |
| } |
| |
| if (attribMap.contains(EGL_X11_VISUAL_ID_ANGLE)) |
| { |
| mRequestedVisual = static_cast<EGLint>(attribMap.get(EGL_X11_VISUAL_ID_ANGLE, -1)); |
| // There is no direct way to get the GLXFBConfig matching an X11 visual ID |
| // so we have to iterate over all the GLXFBConfigs to find the right one. |
| int nConfigs; |
| int attribList[] = { |
| None, |
| }; |
| glx::FBConfig *allConfigs = mGLX.chooseFBConfig(attribList, &nConfigs); |
| |
| for (int i = 0; i < nConfigs; ++i) |
| { |
| if (getGLXFBConfigAttrib(allConfigs[i], GLX_VISUAL_ID) == mRequestedVisual) |
| { |
| mContextConfig = allConfigs[i]; |
| break; |
| } |
| } |
| XFree(allConfigs); |
| |
| if (mContextConfig == nullptr) |
| { |
| return egl::EglNotInitialized() << "Invalid visual ID requested."; |
| } |
| } |
| else |
| { |
| // When glXMakeCurrent is called, the context and the surface must be |
| // compatible which in glX-speak means that their config have the same |
| // color buffer type, are both RGBA or ColorIndex, and their buffers have |
| // the same depth, if they exist. |
| // Since our whole EGL implementation is backed by only one GL context, this |
| // context must be compatible with all the GLXFBConfig corresponding to the |
| // EGLconfigs that we will be exposing. |
| int nConfigs; |
| int attribList[] = {// We want RGBA8 and DEPTH24_STENCIL8 |
| GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8, |
| GLX_DEPTH_SIZE, 24, GLX_STENCIL_SIZE, 8, |
| // We want RGBA rendering (vs COLOR_INDEX) and doublebuffer |
| GLX_RENDER_TYPE, GLX_RGBA_BIT, |
| // Double buffer is not strictly required as a non-doublebuffer |
| // context can work with a doublebuffered surface, but it still |
| // flickers and all applications want doublebuffer anyway. |
| GLX_DOUBLEBUFFER, True, |
| // All of these must be supported for full EGL support |
| GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT | GLX_PIXMAP_BIT, |
| // This makes sure the config have an associated visual Id |
| GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, GLX_NONE, None}; |
| glx::FBConfig *candidates = mGLX.chooseFBConfig(attribList, &nConfigs); |
| if (nConfigs == 0) |
| { |
| XFree(candidates); |
| return egl::EglNotInitialized() |
| << "Could not find a decent GLX FBConfig to create the context."; |
| } |
| mContextConfig = candidates[0]; |
| XFree(candidates); |
| } |
| |
| const auto &eglAttributes = display->getAttributeMap(); |
| if (mHasARBCreateContext) |
| { |
| egl::Error error = initializeContext(mContextConfig, eglAttributes, &mContext); |
| if (error.isError()) |
| { |
| return error; |
| } |
| } |
| else |
| { |
| if (eglAttributes.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, |
| EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE) == |
| EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE) |
| { |
| return egl::EglNotInitialized() << "Cannot create an OpenGL ES platform on GLX without " |
| "the GLX_ARB_create_context extension."; |
| } |
| |
| XVisualInfo visualTemplate; |
| visualTemplate.visualid = getGLXFBConfigAttrib(mContextConfig, GLX_VISUAL_ID); |
| |
| int numVisuals = 0; |
| mVisuals = XGetVisualInfo(mXDisplay, VisualIDMask, &visualTemplate, &numVisuals); |
| if (numVisuals <= 0) |
| { |
| return egl::EglNotInitialized() << "Could not get the visual info from the fb config"; |
| } |
| ASSERT(numVisuals == 1); |
| |
| mContext = mGLX.createContext(&mVisuals[0], nullptr, true); |
| |
| if (!mContext) |
| { |
| return egl::EglNotInitialized() << "Could not create GL context."; |
| } |
| |
| mSharedContext = mGLX.createContext(&mVisuals[0], mContext, True); |
| } |
| ASSERT(mContext); |
| |
| mCurrentNativeContexts[std::this_thread::get_id()] = mContext; |
| |
| // FunctionsGL and DisplayGL need to make a few GL calls, for example to |
| // query the version of the context so we need to make the context current. |
| // glXMakeCurrent requires a GLXDrawable so we create a temporary Pbuffer |
| // (of size 1, 1) for the duration of these calls. |
| // Ideally we would want to unset the current context and destroy the pbuffer |
| // before going back to the application but this is TODO |
| // We could use a pbuffer of size (0, 0) but it fails on the Intel Mesa driver |
| // as commented on https://bugs.freedesktop.org/show_bug.cgi?id=38869 so we |
| // use (1, 1) instead. |
| |
| int initPbufferAttribs[] = { |
| GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, None, |
| }; |
| mInitPbuffer = mGLX.createPbuffer(mContextConfig, initPbufferAttribs); |
| if (!mInitPbuffer) |
| { |
| return egl::EglNotInitialized() << "Could not create the initialization pbuffer."; |
| } |
| |
| if (!mGLX.makeCurrent(mInitPbuffer, mContext)) |
| { |
| return egl::EglNotInitialized() << "Could not make the initialization pbuffer current."; |
| } |
| |
| std::unique_ptr<FunctionsGL> functionsGL(new FunctionsGLGLX(mGLX.getProc)); |
| functionsGL->initialize(eglAttributes); |
| if (mHasNVRobustnessVideoMemoryPurge) |
| { |
| GLenum status = functionsGL->getGraphicsResetStatus(); |
| if (status != GL_NO_ERROR && status != GL_PURGED_CONTEXT_RESET_NV) |
| { |
| return egl::EglNotInitialized() << "Context lost for unknown reason."; |
| } |
| } |
| // TODO(cwallez, angleproject:1303) Disable the OpenGL ES backend on Linux NVIDIA and Intel as |
| // it has problems on our automated testing. An OpenGL ES backend might not trigger this test if |
| // there is no Desktop OpenGL support, but that's not the case in our automated testing. |
| VendorID vendor = GetVendorID(functionsGL.get()); |
| bool isOpenGLES = |
| eglAttributes.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE) == |
| EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE; |
| if (isOpenGLES && (IsIntel(vendor) || IsNvidia(vendor))) |
| { |
| return egl::EglNotInitialized() << "Intel or NVIDIA OpenGL ES drivers are not supported."; |
| } |
| |
| if (mSharedContext) |
| { |
| if (HasParallelShaderCompileExtension(functionsGL.get())) |
| { |
| mGLX.destroyContext(mSharedContext); |
| mSharedContext = nullptr; |
| } |
| else |
| { |
| for (unsigned int i = 0; i < RendererGL::getMaxWorkerContexts(); ++i) |
| { |
| glx::Pbuffer workerPbuffer = mGLX.createPbuffer(mContextConfig, initPbufferAttribs); |
| if (!workerPbuffer) |
| { |
| return egl::EglNotInitialized() << "Could not create the worker pbuffers."; |
| } |
| mWorkerPbufferPool.push_back(workerPbuffer); |
| } |
| } |
| } |
| |
| syncXCommands(false); |
| |
| mRenderer.reset(new RendererGLX(std::move(functionsGL), eglAttributes, this)); |
| const gl::Version &maxVersion = mRenderer->getMaxSupportedESVersion(); |
| if (maxVersion < gl::Version(2, 0)) |
| { |
| return egl::EglNotInitialized() << "OpenGL ES 2.0 is not supportable."; |
| } |
| |
| return DisplayGL::initialize(display); |
| } |
| |
| void DisplayGLX::terminate() |
| { |
| DisplayGL::terminate(); |
| |
| if (mVisuals) |
| { |
| XFree(mVisuals); |
| mVisuals = 0; |
| } |
| |
| if (mInitPbuffer) |
| { |
| mGLX.destroyPbuffer(mInitPbuffer); |
| mInitPbuffer = 0; |
| } |
| |
| for (auto &workerPbuffer : mWorkerPbufferPool) |
| { |
| mGLX.destroyPbuffer(workerPbuffer); |
| } |
| mWorkerPbufferPool.clear(); |
| |
| mCurrentNativeContexts.clear(); |
| |
| if (mContext) |
| { |
| mGLX.destroyContext(mContext); |
| mContext = nullptr; |
| } |
| |
| if (mSharedContext) |
| { |
| mGLX.destroyContext(mSharedContext); |
| mSharedContext = nullptr; |
| } |
| |
| mGLX.terminate(); |
| |
| mRenderer.reset(); |
| |
| if (mUsesNewXDisplay) |
| { |
| XCloseDisplay(mXDisplay); |
| } |
| } |
| |
| egl::Error DisplayGLX::makeCurrent(egl::Display *display, |
| egl::Surface *drawSurface, |
| egl::Surface *readSurface, |
| gl::Context *context) |
| { |
| glx::Drawable newDrawable = |
| (drawSurface ? GetImplAs<SurfaceGLX>(drawSurface)->getDrawable() : mInitPbuffer); |
| glx::Context newContext = mContext; |
| // If the thread calling makeCurrent does not have the correct context current (either mContext |
| // or 0), we need to set it current. |
| if (!context) |
| { |
| newDrawable = 0; |
| newContext = 0; |
| } |
| if (newDrawable != mCurrentDrawable || |
| newContext != mCurrentNativeContexts[std::this_thread::get_id()]) |
| { |
| if (mGLX.makeCurrent(newDrawable, newContext) != True) |
| { |
| return egl::EglContextLost() << "Failed to make the GLX context current"; |
| } |
| mCurrentNativeContexts[std::this_thread::get_id()] = newContext; |
| mCurrentDrawable = newDrawable; |
| } |
| |
| return DisplayGL::makeCurrent(display, drawSurface, readSurface, context); |
| } |
| |
| SurfaceImpl *DisplayGLX::createWindowSurface(const egl::SurfaceState &state, |
| EGLNativeWindowType window, |
| const egl::AttributeMap &attribs) |
| { |
| ASSERT(configIdToGLXConfig.count(state.config->configID) > 0); |
| glx::FBConfig fbConfig = configIdToGLXConfig[state.config->configID]; |
| |
| return new WindowSurfaceGLX(state, mGLX, this, window, mGLX.getDisplay(), fbConfig); |
| } |
| |
| SurfaceImpl *DisplayGLX::createPbufferSurface(const egl::SurfaceState &state, |
| const egl::AttributeMap &attribs) |
| { |
| ASSERT(configIdToGLXConfig.count(state.config->configID) > 0); |
| glx::FBConfig fbConfig = configIdToGLXConfig[state.config->configID]; |
| |
| EGLint width = static_cast<EGLint>(attribs.get(EGL_WIDTH, 0)); |
| EGLint height = static_cast<EGLint>(attribs.get(EGL_HEIGHT, 0)); |
| bool largest = (attribs.get(EGL_LARGEST_PBUFFER, EGL_FALSE) == EGL_TRUE); |
| |
| return new PbufferSurfaceGLX(state, width, height, largest, mGLX, fbConfig); |
| } |
| |
| SurfaceImpl *DisplayGLX::createPbufferFromClientBuffer(const egl::SurfaceState &state, |
| EGLenum buftype, |
| EGLClientBuffer clientBuffer, |
| const egl::AttributeMap &attribs) |
| { |
| UNIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| SurfaceImpl *DisplayGLX::createPixmapSurface(const egl::SurfaceState &state, |
| NativePixmapType nativePixmap, |
| const egl::AttributeMap &attribs) |
| { |
| ASSERT(configIdToGLXConfig.count(state.config->configID) > 0); |
| glx::FBConfig fbConfig = configIdToGLXConfig[state.config->configID]; |
| return new PixmapSurfaceGLX(state, nativePixmap, mGLX.getDisplay(), mGLX, fbConfig); |
| } |
| |
| egl::Error DisplayGLX::validatePixmap(const egl::Config *config, |
| EGLNativePixmapType pixmap, |
| const egl::AttributeMap &attributes) const |
| { |
| Window rootWindow; |
| int x = 0; |
| int y = 0; |
| unsigned int width = 0; |
| unsigned int height = 0; |
| unsigned int borderWidth = 0; |
| unsigned int depth = 0; |
| int status = XGetGeometry(mGLX.getDisplay(), pixmap, &rootWindow, &x, &y, &width, &height, |
| &borderWidth, &depth); |
| if (!status) |
| { |
| return egl::EglBadNativePixmap() << "Invalid native pixmap, XGetGeometry failed: " |
| << x11::XErrorToString(mXDisplay, status); |
| } |
| |
| return egl::NoError(); |
| } |
| |
| ContextImpl *DisplayGLX::createContext(const gl::State &state, |
| gl::ErrorSet *errorSet, |
| const egl::Config *configuration, |
| const gl::Context *shareContext, |
| const egl::AttributeMap &attribs) |
| { |
| RobustnessVideoMemoryPurgeStatus robustnessVideoMemoryPurgeStatus = |
| GetRobustnessVideoMemoryPurge(attribs); |
| return new ContextGL(state, errorSet, mRenderer, robustnessVideoMemoryPurgeStatus); |
| } |
| |
| egl::Error DisplayGLX::initializeContext(glx::FBConfig config, |
| const egl::AttributeMap &eglAttributes, |
| glx::Context *context) |
| { |
| int profileMask = 0; |
| |
| EGLint requestedDisplayType = static_cast<EGLint>( |
| eglAttributes.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE)); |
| if (requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE) |
| { |
| if (!mHasEXTCreateContextES2Profile) |
| { |
| return egl::EglNotInitialized() << "Cannot create an OpenGL ES platform on GLX without " |
| "the GLX_EXT_create_context_es_profile extension."; |
| } |
| |
| ASSERT(mHasARBCreateContextProfile); |
| profileMask |= GLX_CONTEXT_ES2_PROFILE_BIT_EXT; |
| } |
| |
| // Create a context of the requested version, if any. |
| gl::Version requestedVersion(static_cast<EGLint>(eglAttributes.get( |
| EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, EGL_DONT_CARE)), |
| static_cast<EGLint>(eglAttributes.get( |
| EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, EGL_DONT_CARE))); |
| if (static_cast<EGLint>(requestedVersion.major) != EGL_DONT_CARE && |
| static_cast<EGLint>(requestedVersion.minor) != EGL_DONT_CARE) |
| { |
| if (!(profileMask & GLX_CONTEXT_ES2_PROFILE_BIT_EXT) && |
| requestedVersion >= gl::Version(3, 2)) |
| { |
| profileMask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; |
| } |
| return createContextAttribs(config, requestedVersion, profileMask, context); |
| } |
| |
| // The only way to get a core profile context of the highest version using |
| // glXCreateContextAttrib is to try creationg contexts in decreasing version |
| // numbers. It might look that asking for a core context of version (0, 0) |
| // works on some driver but it create a _compatibility_ context of the highest |
| // version instead. The cost of failing a context creation is small (< 0.1 ms) |
| // on Mesa but is unfortunately a bit expensive on the Nvidia driver (~3ms). |
| // Also try to get any Desktop GL context, but if that fails fallback to |
| // asking for OpenGL ES contexts. |
| |
| // NOTE: below we return as soon as we're able to create a context so the |
| // "error" variable is EGL_SUCCESS when returned contrary to the common idiom |
| // of returning "error" when there is an actual error. |
| for (const auto &info : GenerateContextCreationToTry(requestedDisplayType, mIsMesa)) |
| { |
| int profileFlag = 0; |
| if (info.type == ContextCreationTry::Type::DESKTOP_CORE) |
| { |
| profileFlag |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; |
| } |
| else if (info.type == ContextCreationTry::Type::ES) |
| { |
| profileFlag |= GLX_CONTEXT_ES2_PROFILE_BIT_EXT; |
| } |
| |
| egl::Error error = createContextAttribs(config, info.version, profileFlag, context); |
| if (!error.isError()) |
| { |
| return error; |
| } |
| } |
| |
| return egl::EglNotInitialized() << "Could not create a backing OpenGL context."; |
| } |
| |
| egl::ConfigSet DisplayGLX::generateConfigs() |
| { |
| egl::ConfigSet configs; |
| configIdToGLXConfig.clear(); |
| |
| const gl::Version &maxVersion = getMaxSupportedESVersion(); |
| ASSERT(maxVersion >= gl::Version(2, 0)); |
| bool supportsES3 = maxVersion >= gl::Version(3, 0); |
| |
| int contextRedSize = getGLXFBConfigAttrib(mContextConfig, GLX_RED_SIZE); |
| int contextGreenSize = getGLXFBConfigAttrib(mContextConfig, GLX_GREEN_SIZE); |
| int contextBlueSize = getGLXFBConfigAttrib(mContextConfig, GLX_BLUE_SIZE); |
| int contextAlphaSize = getGLXFBConfigAttrib(mContextConfig, GLX_ALPHA_SIZE); |
| |
| int contextDepthSize = getGLXFBConfigAttrib(mContextConfig, GLX_DEPTH_SIZE); |
| int contextStencilSize = getGLXFBConfigAttrib(mContextConfig, GLX_STENCIL_SIZE); |
| |
| int contextSamples = mHasMultisample ? getGLXFBConfigAttrib(mContextConfig, GLX_SAMPLES) : 0; |
| int contextSampleBuffers = |
| mHasMultisample ? getGLXFBConfigAttrib(mContextConfig, GLX_SAMPLE_BUFFERS) : 0; |
| |
| int contextAccumRedSize = getGLXFBConfigAttrib(mContextConfig, GLX_ACCUM_RED_SIZE); |
| int contextAccumGreenSize = getGLXFBConfigAttrib(mContextConfig, GLX_ACCUM_GREEN_SIZE); |
| int contextAccumBlueSize = getGLXFBConfigAttrib(mContextConfig, GLX_ACCUM_BLUE_SIZE); |
| int contextAccumAlphaSize = getGLXFBConfigAttrib(mContextConfig, GLX_ACCUM_ALPHA_SIZE); |
| |
| int attribList[] = { |
| GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_X_RENDERABLE, True, GLX_DOUBLEBUFFER, True, None, |
| }; |
| |
| int glxConfigCount; |
| glx::FBConfig *glxConfigs = mGLX.chooseFBConfig(attribList, &glxConfigCount); |
| |
| for (int i = 0; i < glxConfigCount; i++) |
| { |
| glx::FBConfig glxConfig = glxConfigs[i]; |
| egl::Config config; |
| |
| // Native stuff |
| config.nativeVisualID = getGLXFBConfigAttrib(glxConfig, GLX_VISUAL_ID); |
| config.nativeVisualType = getGLXFBConfigAttrib(glxConfig, GLX_X_VISUAL_TYPE); |
| config.nativeRenderable = EGL_TRUE; |
| |
| // When a visual ID has been specified with EGL_ANGLE_x11_visual we should |
| // only return configs with this visual: it will maximize performance by avoid |
| // blits in the driver when showing the window on the screen. |
| if (mRequestedVisual != -1 && config.nativeVisualID != mRequestedVisual) |
| { |
| continue; |
| } |
| |
| // Buffer sizes |
| config.redSize = getGLXFBConfigAttrib(glxConfig, GLX_RED_SIZE); |
| config.greenSize = getGLXFBConfigAttrib(glxConfig, GLX_GREEN_SIZE); |
| config.blueSize = getGLXFBConfigAttrib(glxConfig, GLX_BLUE_SIZE); |
| config.alphaSize = getGLXFBConfigAttrib(glxConfig, GLX_ALPHA_SIZE); |
| config.depthSize = getGLXFBConfigAttrib(glxConfig, GLX_DEPTH_SIZE); |
| config.stencilSize = getGLXFBConfigAttrib(glxConfig, GLX_STENCIL_SIZE); |
| |
| // We require RGBA8 and the D24S8 (or no DS buffer) |
| if (config.redSize != contextRedSize || config.greenSize != contextGreenSize || |
| config.blueSize != contextBlueSize || config.alphaSize != contextAlphaSize) |
| { |
| continue; |
| } |
| // The GLX spec says that it is ok for a whole buffer to not be present |
| // however the Mesa Intel driver (and probably on other Mesa drivers) |
| // fails to make current when the Depth stencil doesn't exactly match the |
| // configuration. |
| bool hasSameDepthStencil = |
| config.depthSize == contextDepthSize && config.stencilSize == contextStencilSize; |
| bool hasNoDepthStencil = config.depthSize == 0 && config.stencilSize == 0; |
| if (!hasSameDepthStencil && (mIsMesa || !hasNoDepthStencil)) |
| { |
| continue; |
| } |
| |
| config.colorBufferType = EGL_RGB_BUFFER; |
| config.luminanceSize = 0; |
| config.alphaMaskSize = 0; |
| |
| config.bufferSize = config.redSize + config.greenSize + config.blueSize + config.alphaSize; |
| |
| // Multisample and accumulation buffers |
| int samples = mHasMultisample ? getGLXFBConfigAttrib(glxConfig, GLX_SAMPLES) : 0; |
| int sampleBuffers = |
| mHasMultisample ? getGLXFBConfigAttrib(glxConfig, GLX_SAMPLE_BUFFERS) : 0; |
| |
| int accumRedSize = getGLXFBConfigAttrib(glxConfig, GLX_ACCUM_RED_SIZE); |
| int accumGreenSize = getGLXFBConfigAttrib(glxConfig, GLX_ACCUM_GREEN_SIZE); |
| int accumBlueSize = getGLXFBConfigAttrib(glxConfig, GLX_ACCUM_BLUE_SIZE); |
| int accumAlphaSize = getGLXFBConfigAttrib(glxConfig, GLX_ACCUM_ALPHA_SIZE); |
| |
| if (samples != contextSamples || sampleBuffers != contextSampleBuffers || |
| accumRedSize != contextAccumRedSize || accumGreenSize != contextAccumGreenSize || |
| accumBlueSize != contextAccumBlueSize || accumAlphaSize != contextAccumAlphaSize) |
| { |
| continue; |
| } |
| |
| config.samples = samples; |
| config.sampleBuffers = sampleBuffers; |
| |
| // Transparency |
| if (getGLXFBConfigAttrib(glxConfig, GLX_TRANSPARENT_TYPE) == GLX_TRANSPARENT_RGB) |
| { |
| config.transparentType = EGL_TRANSPARENT_RGB; |
| config.transparentRedValue = getGLXFBConfigAttrib(glxConfig, GLX_TRANSPARENT_RED_VALUE); |
| config.transparentGreenValue = |
| getGLXFBConfigAttrib(glxConfig, GLX_TRANSPARENT_GREEN_VALUE); |
| config.transparentBlueValue = |
| getGLXFBConfigAttrib(glxConfig, GLX_TRANSPARENT_BLUE_VALUE); |
| } |
| else |
| { |
| config.transparentType = EGL_NONE; |
| } |
| |
| // Pbuffer |
| config.maxPBufferWidth = getGLXFBConfigAttrib(glxConfig, GLX_MAX_PBUFFER_WIDTH); |
| config.maxPBufferHeight = getGLXFBConfigAttrib(glxConfig, GLX_MAX_PBUFFER_HEIGHT); |
| config.maxPBufferPixels = getGLXFBConfigAttrib(glxConfig, GLX_MAX_PBUFFER_PIXELS); |
| |
| // Caveat |
| config.configCaveat = EGL_NONE; |
| |
| int caveat = getGLXFBConfigAttrib(glxConfig, GLX_CONFIG_CAVEAT); |
| if (caveat == GLX_SLOW_CONFIG) |
| { |
| config.configCaveat = EGL_SLOW_CONFIG; |
| } |
| else if (caveat == GLX_NON_CONFORMANT_CONFIG) |
| { |
| continue; |
| } |
| |
| // Misc |
| config.level = getGLXFBConfigAttrib(glxConfig, GLX_LEVEL); |
| |
| config.minSwapInterval = mMinSwapInterval; |
| config.maxSwapInterval = mMaxSwapInterval; |
| |
| // TODO(cwallez) wildly guessing these formats, another TODO says they should be removed |
| // anyway |
| config.renderTargetFormat = GL_RGBA8; |
| config.depthStencilFormat = GL_DEPTH24_STENCIL8; |
| |
| config.conformant = EGL_OPENGL_ES2_BIT | (supportsES3 ? EGL_OPENGL_ES3_BIT_KHR : 0); |
| config.renderableType = config.conformant; |
| |
| // TODO(cwallez) I have no idea what this is |
| config.matchNativePixmap = EGL_NONE; |
| |
| config.colorComponentType = EGL_COLOR_COMPONENT_TYPE_FIXED_EXT; |
| |
| // GLX doesn't support binding pbuffers to textures and there is no way to differentiate in |
| // EGL that pixmaps can be bound but pbuffers cannot. If both pixmaps and pbuffers are |
| // supported, generate extra configs with either pbuffer or pixmap support. |
| int glxDrawable = getGLXFBConfigAttrib(glxConfig, GLX_DRAWABLE_TYPE); |
| bool pbufferSupport = (glxDrawable & EGL_PBUFFER_BIT) != 0; |
| bool pixmapSupport = (glxDrawable & GLX_PIXMAP_BIT) != 0; |
| bool pixmapBindToTextureSupport = |
| pixmapSupport && mGLX.hasExtension("GLX_EXT_texture_from_pixmap"); |
| |
| if (pbufferSupport && pixmapBindToTextureSupport) |
| { |
| // Generate the pixmap-only config |
| config.surfaceType = (glxDrawable & GLX_WINDOW_BIT ? EGL_WINDOW_BIT : 0) | |
| (pixmapSupport ? EGL_PIXMAP_BIT : 0); |
| |
| config.bindToTextureRGB = getGLXFBConfigAttrib(glxConfig, GLX_BIND_TO_TEXTURE_RGB_EXT); |
| config.bindToTextureRGBA = |
| getGLXFBConfigAttrib(glxConfig, GLX_BIND_TO_TEXTURE_RGBA_EXT); |
| config.yInverted = getGLXFBConfigAttrib(glxConfig, GLX_Y_INVERTED_EXT); |
| |
| int id = configs.add(config); |
| configIdToGLXConfig[id] = glxConfig; |
| } |
| |
| // Generate the pbuffer config. It can support pixmaps but not bind-to-texture. |
| config.surfaceType = (glxDrawable & GLX_WINDOW_BIT ? EGL_WINDOW_BIT : 0) | |
| (pbufferSupport ? EGL_PBUFFER_BIT : 0) | |
| (pixmapSupport ? EGL_PIXMAP_BIT : 0); |
| |
| config.bindToTextureRGB = false; |
| config.bindToTextureRGBA = false; |
| config.yInverted = false; |
| |
| int id = configs.add(config); |
| configIdToGLXConfig[id] = glxConfig; |
| } |
| |
| XFree(glxConfigs); |
| |
| return configs; |
| } |
| |
| bool DisplayGLX::testDeviceLost() |
| { |
| return false; |
| } |
| |
| egl::Error DisplayGLX::restoreLostDevice(const egl::Display *display) |
| { |
| return egl::EglBadDisplay(); |
| } |
| |
| bool DisplayGLX::isValidNativeWindow(EGLNativeWindowType window) const |
| { |
| |
| // Check the validity of the window by calling a getter function on the window that |
| // returns a status code. If the window is bad the call return a status of zero. We |
| // need to set a temporary X11 error handler while doing this because the default |
| // X11 error handler exits the program on any error. |
| auto oldErrorHandler = XSetErrorHandler(IgnoreX11Errors); |
| XWindowAttributes attributes; |
| int status = XGetWindowAttributes(mXDisplay, window, &attributes); |
| XSetErrorHandler(oldErrorHandler); |
| |
| return status != 0; |
| } |
| |
| egl::Error DisplayGLX::waitClient(const gl::Context *context) |
| { |
| mGLX.waitGL(); |
| return egl::NoError(); |
| } |
| |
| egl::Error DisplayGLX::waitNative(const gl::Context *context, EGLint engine) |
| { |
| // eglWaitNative is used to notice the driver of changes in X11 for the current surface, such as |
| // changes of the window size. We use this event to update the child window of WindowSurfaceGLX |
| // to match its parent window's size. |
| // Handling eglWaitNative this way helps the application control when resize happens. This is |
| // important because drivers have a tendency to clobber the back buffer when the windows are |
| // resized. See http://crbug.com/326995 |
| egl::Surface *drawSurface = context->getCurrentDrawSurface(); |
| egl::Surface *readSurface = context->getCurrentReadSurface(); |
| if (drawSurface != nullptr) |
| { |
| SurfaceGLX *glxDrawSurface = GetImplAs<SurfaceGLX>(drawSurface); |
| ANGLE_TRY(glxDrawSurface->checkForResize()); |
| } |
| |
| if (readSurface != drawSurface && readSurface != nullptr) |
| { |
| SurfaceGLX *glxReadSurface = GetImplAs<SurfaceGLX>(readSurface); |
| ANGLE_TRY(glxReadSurface->checkForResize()); |
| } |
| |
| // We still need to forward the resizing of the child window to the driver. |
| mGLX.waitX(); |
| return egl::NoError(); |
| } |
| |
| gl::Version DisplayGLX::getMaxSupportedESVersion() const |
| { |
| return mRenderer->getMaxSupportedESVersion(); |
| } |
| |
| void DisplayGLX::syncXCommands(bool alwaysSync) const |
| { |
| if (mUsesNewXDisplay || alwaysSync) |
| { |
| XSync(mGLX.getDisplay(), False); |
| } |
| } |
| |
| void DisplayGLX::setSwapInterval(glx::Drawable drawable, SwapControlData *data) |
| { |
| ASSERT(data != nullptr); |
| |
| // TODO(cwallez) error checking? |
| if (mSwapControl == SwapControl::EXT) |
| { |
| // Prefer the EXT extension, it gives per-drawable swap intervals, which will |
| // minimize the number of driver calls. |
| if (data->maxSwapInterval < 0) |
| { |
| unsigned int maxSwapInterval = 0; |
| mGLX.queryDrawable(drawable, GLX_MAX_SWAP_INTERVAL_EXT, &maxSwapInterval); |
| data->maxSwapInterval = static_cast<int>(maxSwapInterval); |
| } |
| |
| // When the egl configs were generated we had to guess what the max swap interval |
| // was because we didn't have a window to query it one (and that this max could |
| // depend on the monitor). This means that the target interval might be higher |
| // than the max interval and needs to be clamped. |
| const int realInterval = std::min(data->targetSwapInterval, data->maxSwapInterval); |
| if (data->currentSwapInterval != realInterval) |
| { |
| mGLX.swapIntervalEXT(drawable, realInterval); |
| data->currentSwapInterval = realInterval; |
| } |
| } |
| else if (mCurrentSwapInterval != data->targetSwapInterval) |
| { |
| // With the Mesa or SGI extensions we can still do per-drawable swap control |
| // manually but it is more expensive in number of driver calls. |
| if (mSwapControl == SwapControl::Mesa) |
| { |
| mGLX.swapIntervalMESA(data->targetSwapInterval); |
| } |
| else if (mSwapControl == SwapControl::SGI) |
| { |
| mGLX.swapIntervalSGI(data->targetSwapInterval); |
| } |
| mCurrentSwapInterval = data->targetSwapInterval; |
| } |
| } |
| |
| bool DisplayGLX::isWindowVisualIdSpecified() const |
| { |
| return mRequestedVisual != -1; |
| } |
| |
| bool DisplayGLX::isMatchingWindowVisualId(unsigned long visualId) const |
| { |
| return isWindowVisualIdSpecified() && static_cast<unsigned long>(mRequestedVisual) == visualId; |
| } |
| |
| void DisplayGLX::generateExtensions(egl::DisplayExtensions *outExtensions) const |
| { |
| outExtensions->createContextRobustness = mHasARBCreateContextRobustness; |
| |
| // Contexts are virtualized so textures ans semaphores can be shared globally |
| outExtensions->displayTextureShareGroup = true; |
| outExtensions->displaySemaphoreShareGroup = true; |
| |
| outExtensions->surfacelessContext = true; |
| |
| if (!mRenderer->getFeatures().disableSyncControlSupport.enabled) |
| { |
| const bool hasSyncControlOML = mGLX.hasExtension("GLX_OML_sync_control"); |
| outExtensions->syncControlCHROMIUM = hasSyncControlOML; |
| outExtensions->syncControlRateANGLE = hasSyncControlOML; |
| } |
| |
| outExtensions->textureFromPixmapNOK = mGLX.hasExtension("GLX_EXT_texture_from_pixmap"); |
| |
| outExtensions->robustnessVideoMemoryPurgeNV = mHasNVRobustnessVideoMemoryPurge; |
| |
| DisplayGL::generateExtensions(outExtensions); |
| } |
| |
| void DisplayGLX::generateCaps(egl::Caps *outCaps) const |
| { |
| outCaps->textureNPOT = true; |
| } |
| |
| egl::Error DisplayGLX::makeCurrentSurfaceless(gl::Context *context) |
| { |
| // Nothing to do because GLX always uses the same context and the previous surface can be left |
| // current. |
| return egl::NoError(); |
| } |
| |
| int DisplayGLX::getGLXFBConfigAttrib(glx::FBConfig config, int attrib) const |
| { |
| int result; |
| mGLX.getFBConfigAttrib(config, attrib, &result); |
| return result; |
| } |
| |
| egl::Error DisplayGLX::createContextAttribs(glx::FBConfig, |
| const Optional<gl::Version> &version, |
| int profileMask, |
| glx::Context *context) |
| { |
| mAttribs.clear(); |
| |
| if (mHasARBCreateContextRobustness) |
| { |
| mAttribs.push_back(GLX_CONTEXT_FLAGS_ARB); |
| mAttribs.push_back(GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB); |
| mAttribs.push_back(GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB); |
| mAttribs.push_back(GLX_LOSE_CONTEXT_ON_RESET_ARB); |
| if (mHasNVRobustnessVideoMemoryPurge) |
| { |
| mAttribs.push_back(GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV); |
| mAttribs.push_back(GL_TRUE); |
| } |
| } |
| |
| if (version.valid()) |
| { |
| mAttribs.push_back(GLX_CONTEXT_MAJOR_VERSION_ARB); |
| mAttribs.push_back(version.value().major); |
| |
| mAttribs.push_back(GLX_CONTEXT_MINOR_VERSION_ARB); |
| mAttribs.push_back(version.value().minor); |
| } |
| |
| if (profileMask != 0 && mHasARBCreateContextProfile) |
| { |
| mAttribs.push_back(GLX_CONTEXT_PROFILE_MASK_ARB); |
| mAttribs.push_back(profileMask); |
| } |
| |
| mAttribs.push_back(None); |
| |
| // When creating a context with glXCreateContextAttribsARB, a variety of X11 errors can |
| // be generated. To prevent these errors from crashing our process, we simply ignore |
| // them and only look if GLXContext was created. |
| // Process all events before setting the error handler to avoid desynchronizing XCB instances |
| // (the error handler is NOT per-display). |
| XSync(mXDisplay, False); |
| auto oldErrorHandler = XSetErrorHandler(IgnoreX11Errors); |
| *context = mGLX.createContextAttribsARB(mContextConfig, nullptr, True, mAttribs.data()); |
| XSetErrorHandler(oldErrorHandler); |
| |
| if (!*context) |
| { |
| return egl::EglNotInitialized() << "Could not create GL context."; |
| } |
| |
| mSharedContext = mGLX.createContextAttribsARB(mContextConfig, mContext, True, mAttribs.data()); |
| |
| return egl::NoError(); |
| } |
| |
| class WorkerContextGLX final : public WorkerContext |
| { |
| public: |
| WorkerContextGLX(glx::Context context, FunctionsGLX *functions, glx::Pbuffer buffer); |
| ~WorkerContextGLX() override; |
| |
| bool makeCurrent() override; |
| void unmakeCurrent() override; |
| |
| private: |
| glx::Context mContext; |
| FunctionsGLX *mFunctions; |
| glx::Pbuffer mBuffer; |
| }; |
| |
| WorkerContextGLX::WorkerContextGLX(glx::Context context, |
| FunctionsGLX *functions, |
| glx::Pbuffer buffer) |
| : mContext(context), mFunctions(functions), mBuffer(buffer) |
| {} |
| |
| WorkerContextGLX::~WorkerContextGLX() |
| { |
| mFunctions->destroyContext(mContext); |
| mFunctions->destroyPbuffer(mBuffer); |
| } |
| |
| bool WorkerContextGLX::makeCurrent() |
| { |
| Bool result = mFunctions->makeCurrent(mBuffer, mContext); |
| if (result != True) |
| { |
| ERR() << "Unable to make the GLX context current."; |
| return false; |
| } |
| return true; |
| } |
| |
| void WorkerContextGLX::unmakeCurrent() |
| { |
| mFunctions->makeCurrent(0, nullptr); |
| } |
| |
| WorkerContext *DisplayGLX::createWorkerContext(std::string *infoLog) |
| { |
| if (!mSharedContext) |
| { |
| *infoLog += "No shared context."; |
| return nullptr; |
| } |
| if (mWorkerPbufferPool.empty()) |
| { |
| *infoLog += "No worker pbuffers."; |
| return nullptr; |
| } |
| glx::Context context = nullptr; |
| if (mHasARBCreateContext) |
| { |
| context = |
| mGLX.createContextAttribsARB(mContextConfig, mSharedContext, True, mAttribs.data()); |
| } |
| else |
| { |
| context = mGLX.createContext(&mVisuals[0], mSharedContext, True); |
| } |
| |
| if (!context) |
| { |
| *infoLog += "Unable to create the glx context."; |
| return nullptr; |
| } |
| |
| glx::Pbuffer workerPbuffer = mWorkerPbufferPool.back(); |
| mWorkerPbufferPool.pop_back(); |
| |
| return new WorkerContextGLX(context, &mGLX, workerPbuffer); |
| } |
| |
| void DisplayGLX::initializeFrontendFeatures(angle::FrontendFeatures *features) const |
| { |
| mRenderer->initializeFrontendFeatures(features); |
| } |
| |
| void DisplayGLX::populateFeatureList(angle::FeatureList *features) |
| { |
| mRenderer->getFeatures().populateFeatureList(features); |
| } |
| |
| RendererGL *DisplayGLX::getRenderer() const |
| { |
| return mRenderer.get(); |
| } |
| |
| bool DisplayGLX::isX11() const |
| { |
| return true; |
| } |
| |
| } // namespace rx |