diff options
Diffstat (limited to 'gfx/gl/GLContextProviderGLX.cpp')
-rw-r--r-- | gfx/gl/GLContextProviderGLX.cpp | 908 |
1 files changed, 908 insertions, 0 deletions
diff --git a/gfx/gl/GLContextProviderGLX.cpp b/gfx/gl/GLContextProviderGLX.cpp new file mode 100644 index 0000000000..cdc5e7ce1a --- /dev/null +++ b/gfx/gl/GLContextProviderGLX.cpp @@ -0,0 +1,908 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef MOZ_WIDGET_GTK +# include <gdk/gdk.h> +# include <gdk/gdkx.h> +# define GET_NATIVE_WINDOW(aWidget) \ + GDK_WINDOW_XID((GdkWindow*)aWidget->GetNativeData(NS_NATIVE_WINDOW)) +#endif + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "X11UndefineNone.h" + +#include "mozilla/MathAlgorithms.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/layers/CompositorOptions.h" +#include "mozilla/Range.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/widget/CompositorWidget.h" +#include "mozilla/widget/GtkCompositorWidget.h" +#include "mozilla/Unused.h" + +#include "prenv.h" +#include "GLContextProvider.h" +#include "GLLibraryLoader.h" +#include "nsDebug.h" +#include "nsIWidget.h" +#include "GLXLibrary.h" +#include "gfxContext.h" +#include "gfxEnv.h" +#include "gfxPlatform.h" +#include "GLContextGLX.h" +#include "gfxUtils.h" +#include "gfx2DGlue.h" +#include "GLScreenBuffer.h" + +#include "gfxCrashReporterUtils.h" + +#ifdef MOZ_WIDGET_GTK +# include "gfxPlatformGtk.h" +#endif + +namespace mozilla::gl { + +using namespace mozilla::gfx; +using namespace mozilla::widget; + +GLXLibrary sGLXLibrary; + +static inline bool HasExtension(const char* aExtensions, + const char* aRequiredExtension) { + return GLContext::ListHasExtension( + reinterpret_cast<const GLubyte*>(aExtensions), aRequiredExtension); +} + +bool GLXLibrary::EnsureInitialized(Display* aDisplay) { + if (mInitialized) { + return true; + } + + // Don't repeatedly try to initialize. + if (mTriedInitializing) { + return false; + } + mTriedInitializing = true; + + MOZ_ASSERT(aDisplay); + if (!aDisplay) { + return false; + } + + // Force enabling s3 texture compression. (Bug 774134) + PR_SetEnv("force_s3tc_enable=true"); + + if (!mOGLLibrary) { + // see e.g. bug 608526: it is intrinsically interesting to know whether we + // have dynamically linked to libGL.so.1 because at least the NVIDIA + // implementation requires an executable stack, which causes mprotect calls, + // which trigger glibc bug + // http://sourceware.org/bugzilla/show_bug.cgi?id=12225 + const char* libGLfilename = "libGL.so.1"; +#if defined(__OpenBSD__) || defined(__NetBSD__) + libGLfilename = "libGL.so"; +#endif + + const bool forceFeatureReport = false; + ScopedGfxFeatureReporter reporter(libGLfilename, forceFeatureReport); + mOGLLibrary = PR_LoadLibrary(libGLfilename); + if (!mOGLLibrary) { + NS_WARNING("Couldn't load OpenGL shared library."); + return false; + } + reporter.SetSuccessful(); + } + + if (gfxEnv::MOZ_GLX_DEBUG()) { + mDebug = true; + } + +#define SYMBOL(X) \ + { \ + (PRFuncPtr*)&mSymbols.f##X, { \ + { "glX" #X } \ + } \ + } +#define END_OF_SYMBOLS \ + { \ + nullptr, {} \ + } + + const SymLoadStruct symbols[] = { + /* functions that were in GLX 1.0 */ + SYMBOL(DestroyContext), + SYMBOL(MakeCurrent), + SYMBOL(SwapBuffers), + SYMBOL(QueryVersion), + SYMBOL(GetConfig), + SYMBOL(GetCurrentContext), + SYMBOL(WaitGL), + SYMBOL(WaitX), + + /* functions introduced in GLX 1.1 */ + SYMBOL(QueryExtensionsString), + SYMBOL(GetClientString), + SYMBOL(QueryServerString), + + /* functions introduced in GLX 1.3 */ + SYMBOL(ChooseFBConfig), + SYMBOL(ChooseVisual), + SYMBOL(GetFBConfigAttrib), + SYMBOL(GetFBConfigs), + SYMBOL(CreatePixmap), + SYMBOL(DestroyPixmap), + SYMBOL(CreateNewContext), + + // Core in GLX 1.4, ARB extension before. + {(PRFuncPtr*)&mSymbols.fGetProcAddress, + {{"glXGetProcAddress", "glXGetProcAddressARB"}}}, + END_OF_SYMBOLS}; + + { + const SymbolLoader libLoader(*mOGLLibrary); + if (!libLoader.LoadSymbols(symbols)) { + NS_WARNING("Couldn't load required GLX symbols."); + return false; + } + } + const SymbolLoader pfnLoader(mSymbols.fGetProcAddress); + + int screen = DefaultScreen(aDisplay); + + { + int major, minor; + if (!fQueryVersion(aDisplay, &major, &minor) || major != 1 || minor < 3) { + NS_ERROR("GLX version older than 1.3. (released in 1998)"); + return false; + } + } + + const SymLoadStruct symbols_createcontext[] = { + SYMBOL(CreateContextAttribsARB), END_OF_SYMBOLS}; + + const SymLoadStruct symbols_videosync[] = { + SYMBOL(GetVideoSyncSGI), SYMBOL(WaitVideoSyncSGI), END_OF_SYMBOLS}; + + const SymLoadStruct symbols_swapcontrol[] = {SYMBOL(SwapIntervalEXT), + END_OF_SYMBOLS}; + + const SymLoadStruct symbols_querydrawable[] = {SYMBOL(QueryDrawable), + END_OF_SYMBOLS}; + + const auto fnLoadSymbols = [&](const SymLoadStruct* symbols) { + if (pfnLoader.LoadSymbols(symbols)) return true; + + ClearSymbols(symbols); + return false; + }; + + const char* clientVendor = fGetClientString(aDisplay, LOCAL_GLX_VENDOR); + const char* serverVendor = + fQueryServerString(aDisplay, screen, LOCAL_GLX_VENDOR); + const char* extensionsStr = fQueryExtensionsString(aDisplay, screen); + + if (HasExtension(extensionsStr, "GLX_ARB_create_context") && + HasExtension(extensionsStr, "GLX_ARB_create_context_profile") && + fnLoadSymbols(symbols_createcontext)) { + mHasCreateContextAttribs = true; + } + + if (HasExtension(extensionsStr, "GLX_ARB_create_context_robustness")) { + mHasRobustness = true; + } + + if (HasExtension(extensionsStr, "GLX_NV_robustness_video_memory_purge")) { + mHasVideoMemoryPurge = true; + } + + if (HasExtension(extensionsStr, "GLX_SGI_video_sync") && + fnLoadSymbols(symbols_videosync)) { + mHasVideoSync = true; + } + + if (!HasExtension(extensionsStr, "GLX_EXT_swap_control") || + !fnLoadSymbols(symbols_swapcontrol)) { + NS_WARNING( + "GLX_swap_control unsupported, ASAP mode may still block on buffer " + "swaps."); + } + + if (HasExtension(extensionsStr, "GLX_EXT_buffer_age") && + fnLoadSymbols(symbols_querydrawable)) { + mHasBufferAge = true; + } + + mIsATI = serverVendor && DoesStringMatch(serverVendor, "ATI"); + mIsNVIDIA = + serverVendor && DoesStringMatch(serverVendor, "NVIDIA Corporation"); + mClientIsMesa = clientVendor && DoesStringMatch(clientVendor, "Mesa"); + + mInitialized = true; + + // This needs to be after `fQueryServerString` is called so that the + // driver is loaded. + MesaMemoryLeakWorkaround(); + + return true; +} + +bool GLXLibrary::SupportsVideoSync(Display* aDisplay) { + if (!EnsureInitialized(aDisplay)) { + return false; + } + + return mHasVideoSync; +} + +static int (*sOldErrorHandler)(Display*, XErrorEvent*); +static XErrorEvent sErrorEvent = {}; + +static int GLXErrorHandler(Display* display, XErrorEvent* ev) { + if (!sErrorEvent.error_code) { + sErrorEvent = *ev; + } + return 0; +} + +GLXLibrary::WrapperScope::WrapperScope(const GLXLibrary& glx, + const char* const funcName, + Display* aDisplay) + : mGlx(glx), mFuncName(funcName), mDisplay(aDisplay) { + if (mGlx.mDebug) { + sOldErrorHandler = XSetErrorHandler(GLXErrorHandler); + } +} + +GLXLibrary::WrapperScope::~WrapperScope() { + if (mGlx.mDebug) { + if (mDisplay) { + FinishX(mDisplay); + } + if (sErrorEvent.error_code) { + char buffer[100] = {}; + if (mDisplay) { + XGetErrorText(mDisplay, sErrorEvent.error_code, buffer, sizeof(buffer)); + } else { + SprintfLiteral(buffer, "%d", sErrorEvent.error_code); + } + printf_stderr("X ERROR after %s: %s (%i) - Request: %i.%i, Serial: %lu", + mFuncName, buffer, sErrorEvent.error_code, + sErrorEvent.request_code, sErrorEvent.minor_code, + sErrorEvent.serial); + MOZ_ASSERT_UNREACHABLE("AfterGLXCall sErrorEvent"); + } + const auto was = XSetErrorHandler(sOldErrorHandler); + if (was != GLXErrorHandler) { + NS_WARNING("Concurrent XSetErrorHandlers"); + } + } +} + +// Returns the GTK display if available; otherwise, if a display was +// previously opened by this method and is still open, returns a +// reference to it; otherwise, opens a new connection. (The non-GTK +// cases are similar to what we do for EGL.) +std::shared_ptr<XlibDisplay> GLXLibrary::GetDisplay() { + std::shared_ptr<XlibDisplay> display; + +#ifdef MOZ_WIDGET_GTK + static const bool kHaveGtk = !!gdk_display_get_default(); + if (kHaveGtk) { + display = XlibDisplay::Borrow(DefaultXDisplay()); + } +#endif + if (display) { + return display; + } + + auto ownDisplay = mOwnDisplay.Lock(); + display = ownDisplay->lock(); + if (display) { + return display; + } + + display = XlibDisplay::Open(nullptr); + if (NS_WARN_IF(!display)) { + return nullptr; + } + *ownDisplay = display; + return display; +} + +already_AddRefed<GLContextGLX> GLContextGLX::CreateGLContext( + const GLContextDesc& desc, std::shared_ptr<XlibDisplay> display, + GLXDrawable drawable, GLXFBConfig cfg, Drawable ownedPixmap) { + GLXLibrary& glx = sGLXLibrary; + + int isDoubleBuffered = 0; + int err = glx.fGetFBConfigAttrib(*display, cfg, LOCAL_GLX_DOUBLEBUFFER, + &isDoubleBuffered); + if (LOCAL_GLX_BAD_ATTRIBUTE != err) { + if (ShouldSpew()) { + printf("[GLX] FBConfig is %sdouble-buffered\n", + isDoubleBuffered ? "" : "not "); + } + } + + if (!glx.HasCreateContextAttribs()) { + NS_WARNING("Cannot create GLContextGLX without glxCreateContextAttribs"); + return nullptr; + } + + // - + + const auto CreateWithAttribs = + [&](const std::vector<int>& attribs) -> RefPtr<GLContextGLX> { + auto terminated = attribs; + terminated.push_back(0); + + const auto glxContext = glx.fCreateContextAttribs( + *display, cfg, nullptr, X11True, terminated.data()); + if (!glxContext) return nullptr; + const RefPtr<GLContextGLX> ret = new GLContextGLX( + desc, display, drawable, glxContext, isDoubleBuffered, ownedPixmap); + + if (!ret->Init()) return nullptr; + + return ret; + }; + + // - + + RefPtr<GLContextGLX> glContext; + + std::vector<int> attribs; + attribs.insert(attribs.end(), { + LOCAL_GLX_RENDER_TYPE, + LOCAL_GLX_RGBA_TYPE, + }); + if (glx.HasVideoMemoryPurge()) { + attribs.insert(attribs.end(), + { + LOCAL_GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV, + LOCAL_GL_TRUE, + }); + } + const bool useCore = + !(desc.flags & CreateContextFlags::REQUIRE_COMPAT_PROFILE); + if (useCore) { + attribs.insert(attribs.end(), { + LOCAL_GLX_CONTEXT_MAJOR_VERSION_ARB, + 3, + LOCAL_GLX_CONTEXT_MINOR_VERSION_ARB, + 2, + LOCAL_GLX_CONTEXT_PROFILE_MASK_ARB, + LOCAL_GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + }); + } + + if (glx.HasRobustness()) { + auto withRobustness = attribs; + withRobustness.insert(withRobustness.end(), + { + LOCAL_GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, + LOCAL_GLX_LOSE_CONTEXT_ON_RESET_ARB, + }); + + { + auto withRBAB = withRobustness; + withRBAB.insert(withRBAB.end(), + { + LOCAL_GLX_CONTEXT_FLAGS_ARB, + LOCAL_GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, + }); + if (!glContext) { + glContext = CreateWithAttribs(withRBAB); + if (!glContext) { + NS_WARNING("Failed to create+init GLContextGLX with RBAB"); + } + } + } + + if (!glContext) { + glContext = CreateWithAttribs(withRobustness); + if (!glContext) { + NS_WARNING("Failed to create+init GLContextGLX with Robustness"); + } + } + } + + if (!glContext) { + glContext = CreateWithAttribs(attribs); + if (!glContext) { + NS_WARNING("Failed to create+init GLContextGLX with required attribs"); + } + } + + return glContext.forget(); +} + +GLContextGLX::~GLContextGLX() { + MarkDestroyed(); + + // Wrapped context should not destroy glxContext/Surface + if (!mOwnsContext) { + return; + } + + // see bug 659842 comment 76 + bool success = mGLX->fMakeCurrent(*mDisplay, X11None, nullptr); + if (!success) { + NS_WARNING( + "glXMakeCurrent failed to release GL context before we call " + "glXDestroyContext!"); + } + + mGLX->fDestroyContext(*mDisplay, mContext); + + // If we own the enclosed X pixmap, then free it after we free the enclosing + // GLX pixmap. + if (mOwnedPixmap) { + mGLX->fDestroyPixmap(*mDisplay, mDrawable); + XFreePixmap(*mDisplay, mOwnedPixmap); + } +} + +bool GLContextGLX::Init() { + if (!GLContext::Init()) { + return false; + } + + // EXT_framebuffer_object is not supported on Core contexts + // so we'll also check for ARB_framebuffer_object + if (!IsExtensionSupported(EXT_framebuffer_object) && + !IsSupported(GLFeature::framebuffer_object)) + return false; + + return true; +} + +bool GLContextGLX::MakeCurrentImpl() const { + if (mGLX->IsMesa()) { + // Read into the event queue to ensure that Mesa receives a + // DRI2InvalidateBuffers event before drawing. See bug 1280653. + Unused << XPending(*mDisplay); + } + + const bool succeeded = mGLX->fMakeCurrent(*mDisplay, mDrawable, mContext); + if (!succeeded) { + NS_WARNING("Failed to make GL context current!"); + } + + if (!IsOffscreen() && mGLX->SupportsSwapControl()) { + // Many GLX implementations default to blocking until the next + // VBlank when calling glXSwapBuffers. We want to run unthrottled + // in ASAP mode. See bug 1280744. + const bool swapInterval = gfxVars::SwapIntervalGLX(); + const bool isASAP = (StaticPrefs::layout_frame_rate() == 0); + const int interval = (swapInterval && !isASAP) ? 1 : 0; + mGLX->fSwapInterval(*mDisplay, mDrawable, interval); + } + return succeeded; +} + +bool GLContextGLX::IsCurrentImpl() const { + return mGLX->fGetCurrentContext() == mContext; +} + +Maybe<SymbolLoader> GLContextGLX::GetSymbolLoader() const { + const auto pfn = sGLXLibrary.GetGetProcAddress(); + return Some(SymbolLoader(pfn)); +} + +bool GLContextGLX::IsDoubleBuffered() const { return mDoubleBuffered; } + +bool GLContextGLX::SwapBuffers() { + if (!mDoubleBuffered) return false; + mGLX->fSwapBuffers(*mDisplay, mDrawable); + return true; +} + +GLint GLContextGLX::GetBufferAge() const { + if (!sGLXLibrary.SupportsBufferAge()) { + return 0; + } + + GLuint result = 0; + mGLX->fQueryDrawable(*mDisplay, mDrawable, LOCAL_GLX_BACK_BUFFER_AGE_EXT, + &result); + if (result > INT32_MAX) { + // If the result can't fit, just assume the buffer cannot be reused. + return 0; + } + return result; +} + +void GLContextGLX::GetWSIInfo(nsCString* const out) const { + int screen = DefaultScreen(mDisplay->get()); + + int majorVersion, minorVersion; + sGLXLibrary.fQueryVersion(*mDisplay, &majorVersion, &minorVersion); + + out->Append(nsPrintfCString("GLX %u.%u", majorVersion, minorVersion)); + + out->AppendLiteral("\nGLX_VENDOR(client): "); + out->Append(sGLXLibrary.fGetClientString(*mDisplay, LOCAL_GLX_VENDOR)); + + out->AppendLiteral("\nGLX_VENDOR(server): "); + out->Append( + sGLXLibrary.fQueryServerString(*mDisplay, screen, LOCAL_GLX_VENDOR)); + + out->AppendLiteral("\nExtensions: "); + out->Append(sGLXLibrary.fQueryExtensionsString(*mDisplay, screen)); +} + +bool GLContextGLX::OverrideDrawable(GLXDrawable drawable) { + return mGLX->fMakeCurrent(*mDisplay, drawable, mContext); +} + +bool GLContextGLX::RestoreDrawable() { + return mGLX->fMakeCurrent(*mDisplay, mDrawable, mContext); +} + +GLContextGLX::GLContextGLX(const GLContextDesc& desc, + std::shared_ptr<XlibDisplay> aDisplay, + GLXDrawable aDrawable, GLXContext aContext, + bool aDoubleBuffered, Drawable aOwnedPixmap) + : GLContext(desc, nullptr), + mContext(aContext), + mDisplay(aDisplay), + mDrawable(aDrawable), + mOwnedPixmap(aOwnedPixmap), + mDoubleBuffered(aDoubleBuffered), + mGLX(&sGLXLibrary) {} + +static bool AreCompatibleVisuals(Visual* one, Visual* two) { + if (one->c_class != two->c_class) { + return false; + } + + if (one->red_mask != two->red_mask || one->green_mask != two->green_mask || + one->blue_mask != two->blue_mask) { + return false; + } + + if (one->bits_per_rgb != two->bits_per_rgb) { + return false; + } + + return true; +} + +already_AddRefed<GLContext> CreateForWidget(Display* aXDisplay, Window aXWindow, + bool aHardwareWebRender, + bool aForceAccelerated) { + if (!sGLXLibrary.EnsureInitialized(aXDisplay)) { + return nullptr; + } + + // Currently, we take whatever Visual the window already has, and + // try to create an fbconfig for that visual. This isn't + // necessarily what we want in the long run; an fbconfig may not + // be available for the existing visual, or if it is, the GL + // performance might be suboptimal. But using the existing visual + // is a relatively safe intermediate step. + + if (!aXDisplay) { + NS_ERROR("X Display required for GLX Context provider"); + return nullptr; + } + + if (!aXWindow) { + NS_ERROR("X window required for GLX Context provider"); + return nullptr; + } + + int xscreen = DefaultScreen(aXDisplay); + + GLXFBConfig config; + int visid; + if (!GLContextGLX::FindFBConfigForWindow(aXDisplay, xscreen, aXWindow, + &config, &visid, + aHardwareWebRender)) { + return nullptr; + } + + CreateContextFlags flags; + if (aHardwareWebRender) { + flags = CreateContextFlags::NONE; // WR needs GL3.2+ + } else { + flags = CreateContextFlags::REQUIRE_COMPAT_PROFILE; + } + return GLContextGLX::CreateGLContext( + {{flags}, false}, XlibDisplay::Borrow(aXDisplay), aXWindow, config); +} + +already_AddRefed<GLContext> GLContextProviderGLX::CreateForCompositorWidget( + CompositorWidget* aCompositorWidget, bool aHardwareWebRender, + bool aForceAccelerated) { + if (!aCompositorWidget) { + MOZ_ASSERT(false); + return nullptr; + } + GtkCompositorWidget* compWidget = aCompositorWidget->AsGTK(); + MOZ_ASSERT(compWidget); + + return CreateForWidget(DefaultXDisplay(), compWidget->XWindow(), + aHardwareWebRender, aForceAccelerated); +} + +static bool ChooseConfig(GLXLibrary* glx, Display* display, int screen, + GLXFBConfig* const out_config, int* const out_visid) { + const int attribs[] = { + LOCAL_GLX_RENDER_TYPE, + LOCAL_GLX_RGBA_BIT, + LOCAL_GLX_DRAWABLE_TYPE, + LOCAL_GLX_PIXMAP_BIT, + LOCAL_GLX_X_RENDERABLE, + X11True, + LOCAL_GLX_RED_SIZE, + 8, + LOCAL_GLX_GREEN_SIZE, + 8, + LOCAL_GLX_BLUE_SIZE, + 8, + LOCAL_GLX_ALPHA_SIZE, + 8, + LOCAL_GLX_DEPTH_SIZE, + 0, + LOCAL_GLX_STENCIL_SIZE, + 0, + 0, + }; + + int numConfigs = 0; + const auto scopedConfigArr = glx->fChooseFBConfig(display, screen, attribs, &numConfigs); + const auto freeConfigList = MakeScopeExit([&]() { + if (scopedConfigArr) { + XFree(scopedConfigArr); + } + }); + if (!scopedConfigArr || !numConfigs) return false; + + // Issues with glxChooseFBConfig selection and sorting: + // * ALPHA_SIZE is sorted as 'largest total RGBA bits first'. If we don't + // request + // alpha bits, we'll probably get RGBA anyways, since 32 is more than 24. + // * DEPTH_SIZE is sorted largest first, including for `0` inputs. + // * STENCIL_SIZE is smallest first, but it might return `8` even though we + // ask for + // `0`. + + // For now, we don't care about these. We *will* care when we do XPixmap + // sharing. + + for (int i = 0; i < numConfigs; ++i) { + GLXFBConfig curConfig = scopedConfigArr[i]; + + int visid; + if (glx->fGetFBConfigAttrib(display, curConfig, LOCAL_GLX_VISUAL_ID, + &visid) != Success) { + continue; + } + + if (!visid) continue; + + *out_config = curConfig; + *out_visid = visid; + return true; + } + + return false; +} + +bool GLContextGLX::FindVisual(Display* display, int screen, + int* const out_visualId) { + if (!sGLXLibrary.EnsureInitialized(display)) { + return false; + } + + XVisualInfo visualTemplate; + visualTemplate.screen = screen; + + // Get all visuals of screen + + int visualsLen = 0; + XVisualInfo* xVisuals = + XGetVisualInfo(display, VisualScreenMask, &visualTemplate, &visualsLen); + if (!xVisuals) { + return false; + } + const Range<XVisualInfo> visualInfos(xVisuals, visualsLen); + auto cleanupVisuals = MakeScopeExit([&] { XFree(xVisuals); }); + + // Get default visual info + + Visual* defaultVisual = DefaultVisual(display, screen); + const auto defaultVisualInfo = [&]() -> const XVisualInfo* { + for (const auto& cur : visualInfos) { + if (cur.visual == defaultVisual) { + return &cur; + } + } + return nullptr; + }(); + if (!defaultVisualInfo) { + MOZ_ASSERT(false); + return false; + } + + const int bpp = 32; + + for (auto& cur : visualInfos) { + const auto fnConfigMatches = [&](const int pname, const int expected) { + int actual; + if (sGLXLibrary.fGetConfig(display, &cur, pname, &actual)) { + return false; + } + return actual == expected; + }; + + // Check if visual is compatible. + if (cur.depth != bpp || cur.c_class != defaultVisualInfo->c_class) { + continue; + } + + // Check if visual is compatible to GL requests. + if (fnConfigMatches(LOCAL_GLX_USE_GL, 1) && + fnConfigMatches(LOCAL_GLX_DOUBLEBUFFER, 1) && + fnConfigMatches(LOCAL_GLX_RED_SIZE, 8) && + fnConfigMatches(LOCAL_GLX_GREEN_SIZE, 8) && + fnConfigMatches(LOCAL_GLX_BLUE_SIZE, 8) && + fnConfigMatches(LOCAL_GLX_ALPHA_SIZE, 8)) { + *out_visualId = cur.visualid; + return true; + } + } + + return false; +} + +bool GLContextGLX::FindFBConfigForWindow( + Display* display, int screen, Window window, + GLXFBConfig* const out_config, int* const out_visid, bool aWebRender) { + // XXX the visual ID is almost certainly the LOCAL_GLX_FBCONFIG_ID, so + // we could probably do this first and replace the glXGetFBConfigs + // with glXChooseConfigs. Docs are sparklingly clear as always. + XWindowAttributes windowAttrs; + if (!XGetWindowAttributes(display, window, &windowAttrs)) { + NS_WARNING("[GLX] XGetWindowAttributes() failed"); + return false; + } + + GLXFBConfig* cfgs = nullptr; + const auto freeConfigList = MakeScopeExit([&]() { + if (cfgs) { + XFree(cfgs); + } + }); + int numConfigs; + const int webrenderAttribs[] = {LOCAL_GLX_ALPHA_SIZE, + windowAttrs.depth == 32 ? 8 : 0, + LOCAL_GLX_DOUBLEBUFFER, X11True, 0}; + + if (aWebRender) { + cfgs = sGLXLibrary.fChooseFBConfig(display, screen, webrenderAttribs, + &numConfigs); + } else { + cfgs = sGLXLibrary.fGetFBConfigs(display, screen, &numConfigs); + } + + if (!cfgs) { + NS_WARNING("[GLX] glXGetFBConfigs() failed"); + return false; + } + NS_ASSERTION(numConfigs > 0, "No FBConfigs found!"); + + const VisualID windowVisualID = XVisualIDFromVisual(windowAttrs.visual); +#ifdef DEBUG + printf("[GLX] window %lx has VisualID 0x%lx\n", window, windowVisualID); +#endif + + for (int i = 0; i < numConfigs; i++) { + int visid = X11None; + sGLXLibrary.fGetFBConfigAttrib(display, cfgs[i], LOCAL_GLX_VISUAL_ID, + &visid); + if (visid) { + // WebRender compatible GLX visual is configured + // at nsWindow::Create() by GLContextGLX::FindVisual(), + // just reuse it here. + if (windowVisualID == static_cast<VisualID>(visid)) { + *out_config = cfgs[i]; + *out_visid = visid; + return true; + } + } + } + + // We don't have a frame buffer visual which matches the GLX visual + // from GLContextGLX::FindVisual(). Let's try to find a near one and hope + // we're not on NVIDIA (Bug 1478454) as it causes X11 BadMatch error there. + for (int i = 0; i < numConfigs; i++) { + int visid = X11None; + sGLXLibrary.fGetFBConfigAttrib(display, cfgs[i], LOCAL_GLX_VISUAL_ID, + &visid); + if (visid) { + int depth; + Visual* visual; + FindVisualAndDepth(display, visid, &visual, &depth); + if (depth == windowAttrs.depth && + AreCompatibleVisuals(windowAttrs.visual, visual)) { + *out_config = cfgs[i]; + *out_visid = visid; + return true; + } + } + } + + NS_WARNING("[GLX] Couldn't find a FBConfig matching window visual"); + return false; +} + +static already_AddRefed<GLContextGLX> CreateOffscreenPixmapContext( + const GLContextCreateDesc& desc, const IntSize& size, + nsACString* const out_failureId) { + GLXLibrary* glx = &sGLXLibrary; + auto display = glx->GetDisplay(); + + if (!display || !glx->EnsureInitialized(*display)) return nullptr; + + int screen = DefaultScreen(display->get()); + + GLXFBConfig config; + int visid; + if (!ChooseConfig(glx, *display, screen, &config, &visid)) { + NS_WARNING("Failed to find a compatible config."); + return nullptr; + } + + Visual* visual; + int depth; + FindVisualAndDepth(*display, visid, &visual, &depth); + + gfx::IntSize dummySize(16, 16); + const auto drawable = + XCreatePixmap(*display, DefaultRootWindow(display->get()), + dummySize.width, dummySize.height, depth); + if (!drawable) { + return nullptr; + } + + // Handle slightly different signature between glXCreatePixmap and + // its pre-GLX-1.3 extension equivalent (though given the ABI, we + // might not need to). + const auto pixmap = glx->fCreatePixmap(*display, config, drawable, nullptr); + if (pixmap == 0) { + XFreePixmap(*display, drawable); + return nullptr; + } + + auto fullDesc = GLContextDesc{desc}; + fullDesc.isOffscreen = true; + return GLContextGLX::CreateGLContext(fullDesc, display, pixmap, config, + drawable); +} + +/*static*/ +already_AddRefed<GLContext> GLContextProviderGLX::CreateHeadless( + const GLContextCreateDesc& desc, nsACString* const out_failureId) { + IntSize dummySize = IntSize(16, 16); + return CreateOffscreenPixmapContext(desc, dummySize, out_failureId); +} + +/*static*/ +GLContext* GLContextProviderGLX::GetGlobalContext() { + // Context sharing not supported. + return nullptr; +} + +/*static*/ +void GLContextProviderGLX::Shutdown() {} + +} // namespace mozilla::gl |