diff options
Diffstat (limited to 'gfx/gl/GLContextProviderEGL.cpp')
-rw-r--r-- | gfx/gl/GLContextProviderEGL.cpp | 1254 |
1 files changed, 1254 insertions, 0 deletions
diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp new file mode 100644 index 0000000000..a685d94eea --- /dev/null +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -0,0 +1,1254 @@ +/* -*- 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/. */ + +#if defined(MOZ_WIDGET_GTK) +# define GET_NATIVE_WINDOW_FROM_REAL_WIDGET(aWidget) \ + ((EGLNativeWindowType)aWidget->GetNativeData(NS_NATIVE_EGL_WINDOW)) +# define GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aWidget) \ + (aWidget->AsGTK()->GetEGLNativeWindow()) +#elif defined(MOZ_WIDGET_ANDROID) +# define GET_NATIVE_WINDOW_FROM_REAL_WIDGET(aWidget) \ + ((EGLNativeWindowType)aWidget->GetNativeData(NS_JAVA_SURFACE)) +# define GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aWidget) \ + (aWidget->AsAndroid()->GetEGLNativeWindow()) +#elif defined(XP_WIN) +# define GET_NATIVE_WINDOW_FROM_REAL_WIDGET(aWidget) \ + ((EGLNativeWindowType)aWidget->GetNativeData(NS_NATIVE_WINDOW)) +# define GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aWidget) \ + ((EGLNativeWindowType)aWidget->AsWindows()->GetHwnd()) +#else +# define GET_NATIVE_WINDOW_FROM_REAL_WIDGET(aWidget) \ + ((EGLNativeWindowType)aWidget->GetNativeData(NS_NATIVE_WINDOW)) +# define GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aWidget) \ + ((EGLNativeWindowType)aWidget->RealWidget()->GetNativeData( \ + NS_NATIVE_WINDOW)) +#endif + +#if defined(XP_UNIX) +# ifdef MOZ_WIDGET_ANDROID +# include <android/native_window.h> +# include <android/native_window_jni.h> +# include "mozilla/widget/AndroidCompositorWidget.h" +# endif + +# define GLES2_LIB "libGLESv2.so" +# define GLES2_LIB2 "libGLESv2.so.2" + +#elif defined(XP_WIN) +# include "mozilla/widget/WinCompositorWidget.h" +# include "nsIFile.h" + +# define GLES2_LIB "libGLESv2.dll" + +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +# endif + +# include <windows.h> +#else +# error "Platform not recognized" +#endif + +#include "gfxCrashReporterUtils.h" +#include "gfxFailure.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "GLBlitHelper.h" +#include "GLContextEGL.h" +#include "GLContextProvider.h" +#include "GLLibraryEGL.h" +#include "GLLibraryLoader.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/BuildConstants.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/CompositorOptions.h" +#include "mozilla/widget/CompositorWidget.h" +#include "nsDebug.h" +#include "nsIWidget.h" +#include "nsThreadUtils.h" +#include "ScopedGLHelpers.h" + +#if defined(MOZ_WIDGET_GTK) +# include "mozilla/widget/GtkCompositorWidget.h" +# if defined(MOZ_WAYLAND) +# include <gdk/gdkwayland.h> +# include <wayland-egl.h> +# include "mozilla/WidgetUtilsGtk.h" +# include "mozilla/widget/nsWaylandDisplay.h" +# endif +#endif + +struct wl_egl_window; + +using namespace mozilla::gfx; + +namespace mozilla { +namespace gl { + +using namespace mozilla::widget; + +#if defined(MOZ_WAYLAND) +class SavedGLSurface { + public: + SavedGLSurface(struct wl_surface* aWaylandSurface, + struct wl_egl_window* aEGLWindow); + ~SavedGLSurface(); + + private: + struct wl_surface* mWaylandSurface = nullptr; + struct wl_egl_window* mEGLWindow = nullptr; +}; + +static nsTHashMap<nsPtrHashKey<void>, SavedGLSurface*> sSavedGLSurfaces; + +void DeleteSavedGLSurface(EGLSurface surface) { + auto entry = sSavedGLSurfaces.Lookup(surface); + if (entry) { + delete entry.Data(); + entry.Remove(); + } +} +#endif + +static bool CreateConfigScreen(EglDisplay&, EGLConfig* const aConfig, + const bool aEnableDepthBuffer, + const bool aUseGles); + +// append three zeros at the end of attribs list to work around +// EGL implementation bugs that iterate until they find 0, instead of +// EGL_NONE. See bug 948406. +#define EGL_ATTRIBS_LIST_SAFE_TERMINATION_WORKING_AROUND_BUGS \ + LOCAL_EGL_NONE, 0, 0, 0 + +static EGLint kTerminationAttribs[] = { + EGL_ATTRIBS_LIST_SAFE_TERMINATION_WORKING_AROUND_BUGS}; + +static int next_power_of_two(int v) { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + + return v; +} + +static bool is_power_of_two(int v) { + NS_ASSERTION(v >= 0, "bad value"); + + if (v == 0) return true; + + return (v & (v - 1)) == 0; +} + +static void DestroySurface(EglDisplay& egl, const EGLSurface oldSurface) { + if (oldSurface != EGL_NO_SURFACE) { + // TODO: This breaks TLS MakeCurrent caching. + egl.fMakeCurrent(EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + egl.fDestroySurface(oldSurface); +#if defined(MOZ_WAYLAND) + DeleteSavedGLSurface(oldSurface); +#endif + } +} + +static EGLSurface CreateFallbackSurface(EglDisplay& egl, + const EGLConfig& config) { + if (egl.IsExtensionSupported(EGLExtension::KHR_surfaceless_context)) { + // We don't need a PBuffer surface in this case + return EGL_NO_SURFACE; + } + + std::vector<EGLint> pbattrs; + pbattrs.push_back(LOCAL_EGL_WIDTH); + pbattrs.push_back(1); + pbattrs.push_back(LOCAL_EGL_HEIGHT); + pbattrs.push_back(1); + + for (const auto& cur : kTerminationAttribs) { + pbattrs.push_back(cur); + } + + EGLSurface surface = egl.fCreatePbufferSurface(config, pbattrs.data()); + if (!surface) { + MOZ_CRASH("Failed to create fallback EGLSurface"); + } + + return surface; +} + +static EGLSurface CreateSurfaceFromNativeWindow( + EglDisplay& egl, const EGLNativeWindowType window, const EGLConfig config) { + MOZ_ASSERT(window); + EGLSurface newSurface = EGL_NO_SURFACE; + +#ifdef MOZ_WIDGET_ANDROID + JNIEnv* const env = jni::GetEnvForThread(); + ANativeWindow* const nativeWindow = + ANativeWindow_fromSurface(env, reinterpret_cast<jobject>(window)); + if (!nativeWindow) { + gfxCriticalNote << "Failed to obtain native window from Surface"; + return EGL_NO_SURFACE; + } + const auto& display = egl.mLib->fGetDisplay(EGL_DEFAULT_DISPLAY); + newSurface = egl.mLib->fCreateWindowSurface(display, config, nativeWindow, 0); + ANativeWindow_release(nativeWindow); +#else + newSurface = egl.fCreateWindowSurface(config, window, 0); +#endif + if (!newSurface) { + const auto err = egl.mLib->fGetError(); + gfxCriticalNote << "Failed to create EGLSurface!: " << gfx::hexa(err); + } + return newSurface; +} + +/* GLContextEGLFactory class was added as a friend of GLContextEGL + * so that it could access GLContextEGL::CreateGLContext. This was + * done so that a new function would not need to be added to the shared + * GLContextProvider interface. + */ +class GLContextEGLFactory { + public: + static already_AddRefed<GLContext> Create(EGLNativeWindowType aWindow, + bool aHardwareWebRender); + static already_AddRefed<GLContext> CreateImpl(EGLNativeWindowType aWindow, + bool aHardwareWebRender, + bool aUseGles); + + private: + GLContextEGLFactory() = default; + ~GLContextEGLFactory() = default; +}; + +already_AddRefed<GLContext> GLContextEGLFactory::CreateImpl( + EGLNativeWindowType aWindow, bool aHardwareWebRender, bool aUseGles) { + nsCString failureId; + const auto lib = GLLibraryEGL::Get(&failureId); + if (!lib) { + gfxCriticalNote << "Failed[3] to load EGL library: " << failureId.get(); + return nullptr; + } + const auto egl = lib->CreateDisplay(true, &failureId); + if (!egl) { + gfxCriticalNote << "Failed[3] to create EGL library display: " + << failureId.get(); + return nullptr; + } + + bool doubleBuffered = true; + + EGLConfig config; + if (aHardwareWebRender && egl->mLib->IsANGLE()) { + // Force enable alpha channel to make sure ANGLE use correct framebuffer + // formart + const int bpp = 32; + if (!CreateConfig(*egl, &config, bpp, false, aUseGles)) { + gfxCriticalNote << "Failed to create EGLConfig for WebRender ANGLE!"; + return nullptr; + } + } else if (kIsWayland || kIsX11) { + const int bpp = 32; + if (!CreateConfig(*egl, &config, bpp, false, aUseGles)) { + gfxCriticalNote << "Failed to create EGLConfig for WebRender!"; + return nullptr; + } + } else { + if (!CreateConfigScreen(*egl, &config, + /* aEnableDepthBuffer */ false, aUseGles)) { + gfxCriticalNote << "Failed to create EGLConfig!"; + return nullptr; + } + } + + EGLSurface surface = EGL_NO_SURFACE; + if (aWindow) { + surface = mozilla::gl::CreateSurfaceFromNativeWindow(*egl, aWindow, config); + if (!surface) { + return nullptr; + } + } + + CreateContextFlags flags = CreateContextFlags::NONE; + if (aHardwareWebRender && + StaticPrefs::gfx_webrender_prefer_robustness_AtStartup()) { + flags |= CreateContextFlags::PREFER_ROBUSTNESS; + } + if (aHardwareWebRender && aUseGles) { + flags |= CreateContextFlags::PREFER_ES3; + } + if (!aHardwareWebRender) { + flags |= CreateContextFlags::REQUIRE_COMPAT_PROFILE; + } + + const auto desc = GLContextDesc{{flags}, false}; + RefPtr<GLContextEGL> gl = GLContextEGL::CreateGLContext( + egl, desc, config, surface, aUseGles, config, &failureId); + if (!gl) { + const auto err = egl->mLib->fGetError(); + gfxCriticalNote << "Failed to create EGLContext!: " << gfx::hexa(err); + mozilla::gl::DestroySurface(*egl, surface); + return nullptr; + } + + gl->MakeCurrent(); + gl->SetIsDoubleBuffered(doubleBuffered); + +#ifdef MOZ_WIDGET_GTK + if (surface) { + const int interval = gfxVars::SwapIntervalEGL() ? 1 : 0; + egl->fSwapInterval(interval); + } +#endif + if (aHardwareWebRender && egl->mLib->IsANGLE()) { + MOZ_ASSERT(doubleBuffered); + const int interval = gfxVars::SwapIntervalEGL() ? 1 : 0; + egl->fSwapInterval(interval); + } + return gl.forget(); +} + +already_AddRefed<GLContext> GLContextEGLFactory::Create( + EGLNativeWindowType aWindow, bool aHardwareWebRender) { + bool preferGles; +#if defined(MOZ_WIDGET_ANDROID) + preferGles = true; +#else + preferGles = StaticPrefs::gfx_egl_prefer_gles_enabled_AtStartup(); +#endif // defined(MOZ_WIDGET_ANDROID) + + RefPtr<GLContext> glContext = + CreateImpl(aWindow, aHardwareWebRender, preferGles); +#if !defined(MOZ_WIDGET_ANDROID) + if (!glContext) { + glContext = CreateImpl(aWindow, aHardwareWebRender, !preferGles); + } +#endif // !defined(MOZ_WIDGET_ANDROID) + return glContext.forget(); +} + +/* static */ +EGLSurface GLContextEGL::CreateEGLSurfaceForCompositorWidget( + widget::CompositorWidget* aCompositorWidget, const EGLConfig aConfig) { + nsCString discardFailureId; + const auto egl = DefaultEglDisplay(&discardFailureId); + if (!egl) { + gfxCriticalNote << "Failed to load EGL library 6!"; + return EGL_NO_SURFACE; + } + + MOZ_ASSERT(aCompositorWidget); +#ifdef MOZ_WAYLAND + // RenderCompositorEGL does not like EGL_NO_SURFACE as it fallbacks + // to SW rendering or claims itself as paused. + // In case we're missing valid native window because aCompositorWidget hidden, + // just create a fallback EGLSurface. + // Actual EGLSurface will be created by widget code later when + // aCompositorWidget becomes visible. + if (widget::GdkIsWaylandDisplay() && aCompositorWidget->IsHidden()) { + mozilla::gfx::IntSize pbSize(16, 16); + return CreateWaylandBufferSurface(*egl, aConfig, pbSize); + } +#endif + EGLNativeWindowType window = + GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aCompositorWidget); + if (!window) { + gfxCriticalNote << "window is null"; + return EGL_NO_SURFACE; + } + + return mozilla::gl::CreateSurfaceFromNativeWindow(*egl, window, aConfig); +} + +GLContextEGL::GLContextEGL(const std::shared_ptr<EglDisplay> egl, + const GLContextDesc& desc, EGLConfig surfaceConfig, + EGLSurface surface, EGLContext context) + : GLContext(desc, nullptr, false), + mEgl(egl), + mSurfaceConfig(surfaceConfig), + mContext(context), + mSurface(surface), + mFallbackSurface(CreateFallbackSurface(*mEgl, mSurfaceConfig)) { +#ifdef DEBUG + printf_stderr("Initializing context %p surface %p on display %p\n", mContext, + mSurface, mEgl->mDisplay); +#endif +} + +void GLContextEGL::OnMarkDestroyed() { + if (mSurfaceOverride != EGL_NO_SURFACE) { + SetEGLSurfaceOverride(EGL_NO_SURFACE); + } +} + +GLContextEGL::~GLContextEGL() { + MarkDestroyed(); + + // Wrapped context should not destroy eglContext/Surface + if (!mOwnsContext) { + return; + } + +#ifdef DEBUG + printf_stderr("Destroying context %p surface %p on display %p\n", mContext, + mSurface, mEgl->mDisplay); +#endif + + mEgl->fDestroyContext(mContext); + + mozilla::gl::DestroySurface(*mEgl, mSurface); + mozilla::gl::DestroySurface(*mEgl, mFallbackSurface); +} + +bool GLContextEGL::Init() { + if (!GLContext::Init()) return false; + + bool current = MakeCurrent(); + if (!current) { + gfx::LogFailure("Couldn't get device attachments for device."_ns); + return false; + } + + mShareWithEGLImage = + mEgl->HasKHRImageBase() && + mEgl->IsExtensionSupported(EGLExtension::KHR_gl_texture_2D_image) && + IsExtensionSupported(OES_EGL_image); + + return true; +} + +bool GLContextEGL::BindTexImage() { + if (!mSurface) return false; + + if (mBound && !ReleaseTexImage()) return false; + + EGLBoolean success = + mEgl->fBindTexImage((EGLSurface)mSurface, LOCAL_EGL_BACK_BUFFER); + if (success == LOCAL_EGL_FALSE) return false; + + mBound = true; + return true; +} + +bool GLContextEGL::ReleaseTexImage() { + if (!mBound) return true; + + if (!mSurface) return false; + + EGLBoolean success; + success = mEgl->fReleaseTexImage((EGLSurface)mSurface, LOCAL_EGL_BACK_BUFFER); + if (success == LOCAL_EGL_FALSE) return false; + + mBound = false; + return true; +} + +void GLContextEGL::SetEGLSurfaceOverride(EGLSurface surf) { + mSurfaceOverride = surf; + DebugOnly<bool> ok = MakeCurrent(true); + MOZ_ASSERT(ok); +} + +bool GLContextEGL::MakeCurrentImpl() const { + EGLSurface surface = + (mSurfaceOverride != EGL_NO_SURFACE) ? mSurfaceOverride : mSurface; + if (!surface) { + surface = mFallbackSurface; + } + + const bool succeeded = mEgl->fMakeCurrent(surface, surface, mContext); + if (!succeeded) { + const auto eglError = mEgl->mLib->fGetError(); + if (eglError == LOCAL_EGL_CONTEXT_LOST) { + OnContextLostError(); + } else { + NS_WARNING("Failed to make GL context current!"); +#ifdef DEBUG + printf_stderr("EGL Error: 0x%04x\n", eglError); +#endif + } + } + + return succeeded; +} + +bool GLContextEGL::IsCurrentImpl() const { + return mEgl->mLib->fGetCurrentContext() == mContext; +} + +bool GLContextEGL::RenewSurface(CompositorWidget* aWidget) { + if (!mOwnsContext) { + return false; + } + // unconditionally release the surface and create a new one. Don't try to + // optimize this away. If we get here, then by definition we know that we want + // to get a new surface. + ReleaseSurface(); + MOZ_ASSERT(aWidget); + + EGLNativeWindowType nativeWindow = + GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aWidget); +#ifdef MOZ_WAYLAND + // In case we're missing native window on Wayland CompositorWidget is hidden. + // Don't create a fallback EGL surface but fails here. + // We need to repeat RenewSurface() when native window is available + // (CompositorWidget becomes visible). + if (GdkIsWaylandDisplay()) { + NS_WARNING("Failed to get native window"); + return false; + } +#endif + if (nativeWindow) { + mSurface = mozilla::gl::CreateSurfaceFromNativeWindow(*mEgl, nativeWindow, + mSurfaceConfig); + if (!mSurface) { + NS_WARNING("Failed to create EGLSurface from native window"); + return false; + } + } + const bool ok = MakeCurrent(true); + MOZ_ASSERT(ok); +#ifdef MOZ_WIDGET_GTK + if (mSurface) { + const int interval = gfxVars::SwapIntervalEGL() ? 1 : 0; + mEgl->fSwapInterval(interval); + } +#endif + return ok; +} + +void GLContextEGL::ReleaseSurface() { + if (mOwnsContext) { + mozilla::gl::DestroySurface(*mEgl, mSurface); + } + if (mSurface == mSurfaceOverride) { + mSurfaceOverride = EGL_NO_SURFACE; + } + mSurface = EGL_NO_SURFACE; +} + +Maybe<SymbolLoader> GLContextEGL::GetSymbolLoader() const { + return mEgl->mLib->GetSymbolLoader(); +} + +bool GLContextEGL::SwapBuffers() { + EGLSurface surface = + mSurfaceOverride != EGL_NO_SURFACE ? mSurfaceOverride : mSurface; + if (surface) { + if ((mEgl->IsExtensionSupported( + EGLExtension::EXT_swap_buffers_with_damage) || + mEgl->IsExtensionSupported( + EGLExtension::KHR_swap_buffers_with_damage))) { + std::vector<EGLint> rects; + for (auto iter = mDamageRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + rects.push_back(r.X()); + rects.push_back(r.Y()); + rects.push_back(r.Width()); + rects.push_back(r.Height()); + } + mDamageRegion.SetEmpty(); + return mEgl->fSwapBuffersWithDamage(surface, rects.data(), + rects.size() / 4); + } + return mEgl->fSwapBuffers(surface); + } else { + return false; + } +} + +void GLContextEGL::SetDamage(const nsIntRegion& aDamageRegion) { + mDamageRegion = aDamageRegion; +} + +void GLContextEGL::GetWSIInfo(nsCString* const out) const { + out->AppendLiteral("EGL_VENDOR: "); + out->Append( + (const char*)mEgl->mLib->fQueryString(mEgl->mDisplay, LOCAL_EGL_VENDOR)); + + out->AppendLiteral("\nEGL_VERSION: "); + out->Append( + (const char*)mEgl->mLib->fQueryString(mEgl->mDisplay, LOCAL_EGL_VERSION)); + + out->AppendLiteral("\nEGL_EXTENSIONS: "); + out->Append((const char*)mEgl->mLib->fQueryString(mEgl->mDisplay, + LOCAL_EGL_EXTENSIONS)); + +#ifndef ANDROID // This query will crash some old android. + out->AppendLiteral("\nEGL_EXTENSIONS(nullptr): "); + out->Append( + (const char*)mEgl->mLib->fQueryString(nullptr, LOCAL_EGL_EXTENSIONS)); +#endif +} + +bool GLContextEGL::HasExtBufferAge() const { + return mEgl->IsExtensionSupported(EGLExtension::EXT_buffer_age); +} + +bool GLContextEGL::HasKhrPartialUpdate() const { + return mEgl->IsExtensionSupported(EGLExtension::KHR_partial_update); +} + +GLint GLContextEGL::GetBufferAge() const { + EGLSurface surface = + mSurfaceOverride != EGL_NO_SURFACE ? mSurfaceOverride : mSurface; + + if (surface && (HasExtBufferAge() || HasKhrPartialUpdate())) { + EGLint result; + mEgl->fQuerySurface(surface, LOCAL_EGL_BUFFER_AGE_EXT, &result); + return result; + } + + return 0; +} + +#define LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ 0x6000 + +RefPtr<GLContextEGL> GLContextEGL::CreateGLContext( + const std::shared_ptr<EglDisplay> egl, const GLContextDesc& desc, + EGLConfig surfaceConfig, EGLSurface surface, const bool useGles, + EGLConfig contextConfig, nsACString* const out_failureId) { + const auto& flags = desc.flags; + + std::vector<EGLint> required_attribs; + + if (useGles) { + // TODO: This fBindAPI could be more thread-safe + if (egl->mLib->fBindAPI(LOCAL_EGL_OPENGL_ES_API) == LOCAL_EGL_FALSE) { + *out_failureId = "FEATURE_FAILURE_EGL_ES"_ns; + NS_WARNING("Failed to bind API to GLES!"); + return nullptr; + } + required_attribs.push_back(LOCAL_EGL_CONTEXT_MAJOR_VERSION); + if (flags & CreateContextFlags::PREFER_ES3) { + required_attribs.push_back(3); + } else { + required_attribs.push_back(2); + } + } else { + if (egl->mLib->fBindAPI(LOCAL_EGL_OPENGL_API) == LOCAL_EGL_FALSE) { + *out_failureId = "FEATURE_FAILURE_EGL"_ns; + NS_WARNING("Failed to bind API to GL!"); + return nullptr; + } + if (flags & CreateContextFlags::REQUIRE_COMPAT_PROFILE) { + required_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_PROFILE_MASK); + required_attribs.push_back( + LOCAL_EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + required_attribs.push_back(LOCAL_EGL_CONTEXT_MAJOR_VERSION); + required_attribs.push_back(2); + } else { + // !REQUIRE_COMPAT_PROFILE means core profle. + required_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_PROFILE_MASK); + required_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); + required_attribs.push_back(LOCAL_EGL_CONTEXT_MAJOR_VERSION); + required_attribs.push_back(3); + required_attribs.push_back(LOCAL_EGL_CONTEXT_MINOR_VERSION); + required_attribs.push_back(2); + } + } + + if ((flags & CreateContextFlags::PREFER_EXACT_VERSION) && + egl->mLib->IsANGLE()) { + required_attribs.push_back( + LOCAL_EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE); + required_attribs.push_back(LOCAL_EGL_FALSE); + } + + const auto debugFlags = GLContext::ChooseDebugFlags(flags); + if (!debugFlags && flags & CreateContextFlags::NO_VALIDATION && + egl->IsExtensionSupported(EGLExtension::KHR_create_context_no_error)) { + required_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_NO_ERROR_KHR); + required_attribs.push_back(LOCAL_EGL_TRUE); + } + + if (flags & CreateContextFlags::PROVOKING_VERTEX_DONT_CARE && + egl->IsExtensionSupported( + EGLExtension::MOZ_create_context_provoking_vertex_dont_care)) { + required_attribs.push_back( + LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ); + required_attribs.push_back(LOCAL_EGL_TRUE); + } + + std::vector<EGLint> ext_robustness_attribs; + std::vector<EGLint> ext_rbab_attribs; // RBAB: Robust Buffer Access Behavior + std::vector<EGLint> khr_robustness_attribs; + std::vector<EGLint> khr_rbab_attribs; // RBAB: Robust Buffer Access Behavior + if (flags & CreateContextFlags::PREFER_ROBUSTNESS) { + std::vector<EGLint> base_robustness_attribs = required_attribs; + if (egl->IsExtensionSupported( + EGLExtension::NV_robustness_video_memory_purge)) { + base_robustness_attribs.push_back( + LOCAL_EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV); + base_robustness_attribs.push_back(LOCAL_EGL_TRUE); + } + + if (egl->IsExtensionSupported( + EGLExtension::EXT_create_context_robustness)) { + ext_robustness_attribs = base_robustness_attribs; + ext_robustness_attribs.push_back( + LOCAL_EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT); + ext_robustness_attribs.push_back(LOCAL_EGL_LOSE_CONTEXT_ON_RESET_EXT); + + if (gfxVars::AllowEglRbab()) { + ext_rbab_attribs = ext_robustness_attribs; + ext_rbab_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT); + ext_rbab_attribs.push_back(LOCAL_EGL_TRUE); + } + } + + if (egl->IsExtensionSupported(EGLExtension::KHR_create_context)) { + khr_robustness_attribs = base_robustness_attribs; + khr_robustness_attribs.push_back( + LOCAL_EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR); + khr_robustness_attribs.push_back(LOCAL_EGL_LOSE_CONTEXT_ON_RESET_KHR); + + khr_rbab_attribs = khr_robustness_attribs; + khr_rbab_attribs.push_back(LOCAL_EGL_CONTEXT_FLAGS_KHR); + khr_rbab_attribs.push_back( + LOCAL_EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR); + } + } + + const auto fnCreate = [&](const std::vector<EGLint>& attribs) { + auto terminated_attribs = attribs; + + for (const auto& cur : kTerminationAttribs) { + terminated_attribs.push_back(cur); + } + + return egl->fCreateContext(contextConfig, EGL_NO_CONTEXT, + terminated_attribs.data()); + }; + + EGLContext context; + do { + if (!khr_rbab_attribs.empty()) { + context = fnCreate(khr_rbab_attribs); + if (context) break; + NS_WARNING("Failed to create EGLContext with khr_rbab_attribs"); + } + + if (!ext_rbab_attribs.empty()) { + context = fnCreate(ext_rbab_attribs); + if (context) break; + NS_WARNING("Failed to create EGLContext with ext_rbab_attribs"); + } + + if (!khr_robustness_attribs.empty()) { + context = fnCreate(khr_robustness_attribs); + if (context) break; + NS_WARNING("Failed to create EGLContext with khr_robustness_attribs"); + } + + if (!ext_robustness_attribs.empty()) { + context = fnCreate(ext_robustness_attribs); + if (context) break; + NS_WARNING("Failed to create EGLContext with ext_robustness_attribs"); + } + + context = fnCreate(required_attribs); + if (context) break; + NS_WARNING("Failed to create EGLContext with required_attribs"); + + *out_failureId = "FEATURE_FAILURE_EGL_CREATE"_ns; + return nullptr; + } while (false); + MOZ_ASSERT(context); + + RefPtr<GLContextEGL> glContext = + new GLContextEGL(egl, desc, surfaceConfig, surface, context); + if (!glContext->Init()) { + *out_failureId = "FEATURE_FAILURE_EGL_INIT"_ns; + return nullptr; + } + + if (GLContext::ShouldSpew()) { + printf_stderr("new GLContextEGL %p on EGLDisplay %p\n", glContext.get(), + egl->mDisplay); + } + + return glContext; +} + +// static +EGLSurface GLContextEGL::CreatePBufferSurfaceTryingPowerOfTwo( + EglDisplay& egl, EGLConfig config, EGLenum bindToTextureFormat, + mozilla::gfx::IntSize& pbsize) { + nsTArray<EGLint> pbattrs(16); + EGLSurface surface = nullptr; + +TRY_AGAIN_POWER_OF_TWO: + pbattrs.Clear(); + pbattrs.AppendElement(LOCAL_EGL_WIDTH); + pbattrs.AppendElement(pbsize.width); + pbattrs.AppendElement(LOCAL_EGL_HEIGHT); + pbattrs.AppendElement(pbsize.height); + + if (bindToTextureFormat != LOCAL_EGL_NONE) { + pbattrs.AppendElement(LOCAL_EGL_TEXTURE_TARGET); + pbattrs.AppendElement(LOCAL_EGL_TEXTURE_2D); + + pbattrs.AppendElement(LOCAL_EGL_TEXTURE_FORMAT); + pbattrs.AppendElement(bindToTextureFormat); + } + + for (const auto& cur : kTerminationAttribs) { + pbattrs.AppendElement(cur); + } + + surface = egl.fCreatePbufferSurface(config, &pbattrs[0]); + if (!surface) { + if (!is_power_of_two(pbsize.width) || !is_power_of_two(pbsize.height)) { + if (!is_power_of_two(pbsize.width)) + pbsize.width = next_power_of_two(pbsize.width); + if (!is_power_of_two(pbsize.height)) + pbsize.height = next_power_of_two(pbsize.height); + + NS_WARNING("Failed to create pbuffer, trying power of two dims"); + goto TRY_AGAIN_POWER_OF_TWO; + } + + NS_WARNING("Failed to create pbuffer surface"); + return nullptr; + } + + return surface; +} + +#if defined(MOZ_WAYLAND) +SavedGLSurface::SavedGLSurface(struct wl_surface* aWaylandSurface, + struct wl_egl_window* aEGLWindow) + : mWaylandSurface(aWaylandSurface), mEGLWindow(aEGLWindow) {} + +SavedGLSurface::~SavedGLSurface() { + if (mEGLWindow) { + wl_egl_window_destroy(mEGLWindow); + } + if (mWaylandSurface) { + wl_surface_destroy(mWaylandSurface); + } +} + +// static +EGLSurface GLContextEGL::CreateWaylandBufferSurface( + EglDisplay& egl, EGLConfig config, mozilla::gfx::IntSize& pbsize) { + wl_egl_window* eglwindow = nullptr; + + struct wl_compositor* compositor = + gdk_wayland_display_get_wl_compositor(gdk_display_get_default()); + struct wl_surface* wlsurface = wl_compositor_create_surface(compositor); + eglwindow = wl_egl_window_create(wlsurface, pbsize.width, pbsize.height); + if (!eglwindow) return nullptr; + + const auto surface = egl.fCreateWindowSurface( + config, reinterpret_cast<EGLNativeWindowType>(eglwindow), 0); + if (surface) { + MOZ_ASSERT(!sSavedGLSurfaces.Contains(surface)); + sSavedGLSurfaces.LookupOrInsert(surface, + new SavedGLSurface(wlsurface, eglwindow)); + } + return surface; +} +#endif + +static const EGLint kEGLConfigAttribsRGB16[] = { + LOCAL_EGL_SURFACE_TYPE, LOCAL_EGL_WINDOW_BIT, + LOCAL_EGL_RED_SIZE, 5, + LOCAL_EGL_GREEN_SIZE, 6, + LOCAL_EGL_BLUE_SIZE, 5, + LOCAL_EGL_ALPHA_SIZE, 0}; + +static const EGLint kEGLConfigAttribsRGB24[] = { + LOCAL_EGL_SURFACE_TYPE, LOCAL_EGL_WINDOW_BIT, + LOCAL_EGL_RED_SIZE, 8, + LOCAL_EGL_GREEN_SIZE, 8, + LOCAL_EGL_BLUE_SIZE, 8, + LOCAL_EGL_ALPHA_SIZE, 0}; + +static const EGLint kEGLConfigAttribsRGBA32[] = { + LOCAL_EGL_SURFACE_TYPE, LOCAL_EGL_WINDOW_BIT, + LOCAL_EGL_RED_SIZE, 8, + LOCAL_EGL_GREEN_SIZE, 8, + LOCAL_EGL_BLUE_SIZE, 8, + LOCAL_EGL_ALPHA_SIZE, 8}; + +bool CreateConfig(EglDisplay& aEgl, EGLConfig* aConfig, int32_t aDepth, + bool aEnableDepthBuffer, bool aUseGles, bool aAllowFallback) { + EGLConfig configs[64]; + std::vector<EGLint> attribs; + EGLint ncfg = ArrayLength(configs); + + switch (aDepth) { + case 16: + for (const auto& cur : kEGLConfigAttribsRGB16) { + attribs.push_back(cur); + } + break; + case 24: + for (const auto& cur : kEGLConfigAttribsRGB24) { + attribs.push_back(cur); + } + break; + case 32: + for (const auto& cur : kEGLConfigAttribsRGBA32) { + attribs.push_back(cur); + } + break; + default: + NS_ERROR("Unknown pixel depth"); + return false; + } + + if (aUseGles) { + attribs.push_back(LOCAL_EGL_RENDERABLE_TYPE); + attribs.push_back(LOCAL_EGL_OPENGL_ES2_BIT); + } + for (const auto& cur : kTerminationAttribs) { + attribs.push_back(cur); + } + + if (!aEgl.fChooseConfig(attribs.data(), configs, ncfg, &ncfg) || ncfg < 1) { + return false; + } + + Maybe<EGLConfig> fallbackConfig; + + for (int j = 0; j < ncfg; ++j) { + EGLConfig config = configs[j]; + EGLint r, g, b, a; + if (aEgl.fGetConfigAttrib(config, LOCAL_EGL_RED_SIZE, &r) && + aEgl.fGetConfigAttrib(config, LOCAL_EGL_GREEN_SIZE, &g) && + aEgl.fGetConfigAttrib(config, LOCAL_EGL_BLUE_SIZE, &b) && + aEgl.fGetConfigAttrib(config, LOCAL_EGL_ALPHA_SIZE, &a) && + ((aDepth == 16 && r == 5 && g == 6 && b == 5) || + (aDepth == 24 && r == 8 && g == 8 && b == 8) || + (aDepth == 32 && r == 8 && g == 8 && b == 8 && a == 8))) { + EGLint z; + if (aEnableDepthBuffer) { + if (!aEgl.fGetConfigAttrib(config, LOCAL_EGL_DEPTH_SIZE, &z) || + z != 24) { + continue; + } + } +#ifdef MOZ_X11 + if (GdkIsX11Display()) { + int configVisualID; + if (!aEgl.fGetConfigAttrib(config, LOCAL_EGL_NATIVE_VISUAL_ID, + &configVisualID)) { + continue; + } + + XVisualInfo visual_info_template, *visual_info; + int num_visuals; + + visual_info_template.visualid = configVisualID; + visual_info = + XGetVisualInfo(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), + VisualIDMask, &visual_info_template, &num_visuals); + + if (!visual_info || visual_info->depth != aDepth) { + if (aAllowFallback && !fallbackConfig) { + fallbackConfig = Some(config); + } + continue; + } + } +#endif + *aConfig = config; + return true; + } + } + + if (kIsX11 && fallbackConfig) { + *aConfig = fallbackConfig.value(); + return true; + } + + return false; +} + +// Return true if a suitable EGLConfig was found and pass it out +// through aConfig. Return false otherwise. +// +// NB: It's entirely legal for the returned EGLConfig to be valid yet +// have the value null. +static bool CreateConfigScreen(EglDisplay& egl, EGLConfig* const aConfig, + const bool aEnableDepthBuffer, + const bool aUseGles) { + int32_t depth = gfxVars::ScreenDepth(); + if (CreateConfig(egl, aConfig, depth, aEnableDepthBuffer, aUseGles)) { + return true; + } +#ifdef MOZ_WIDGET_ANDROID + // Bug 736005 + // Android doesn't always support 16 bit so also try 24 bit + if (depth == 16) { + return CreateConfig(egl, aConfig, 24, aEnableDepthBuffer, aUseGles); + } + // Bug 970096 + // Some devices that have 24 bit screens only support 16 bit OpenGL? + if (depth == 24) { + return CreateConfig(egl, aConfig, 16, aEnableDepthBuffer, aUseGles); + } +#endif + return false; +} + +already_AddRefed<GLContext> GLContextProviderEGL::CreateForCompositorWidget( + CompositorWidget* aCompositorWidget, bool aHardwareWebRender, + bool /*aForceAccelerated*/) { + EGLNativeWindowType window = nullptr; + if (aCompositorWidget) { + window = GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aCompositorWidget); + } + return GLContextEGLFactory::Create(window, aHardwareWebRender); +} + +EGLSurface GLContextEGL::CreateCompatibleSurface(void* aWindow) const { + MOZ_ASSERT(aWindow); + MOZ_RELEASE_ASSERT(mSurfaceConfig != EGL_NO_CONFIG); + + // NOTE: aWindow is an ANativeWindow + EGLSurface surface = mEgl->fCreateWindowSurface( + mSurfaceConfig, reinterpret_cast<EGLNativeWindowType>(aWindow), nullptr); + if (!surface) { + gfxCriticalError() << "CreateCompatibleSurface failed: " + << hexa(GetError()); + } + return surface; +} + +static void FillContextAttribs(bool es3, bool useGles, nsTArray<EGLint>* out) { + out->AppendElement(LOCAL_EGL_SURFACE_TYPE); +#ifdef MOZ_WAYLAND + if (GdkIsWaylandDisplay()) { + // Wayland on desktop does not support PBuffer or FBO. + // We create a dummy wl_egl_window instead. + out->AppendElement(LOCAL_EGL_WINDOW_BIT); + } else +#endif + { + out->AppendElement(LOCAL_EGL_PBUFFER_BIT); + } + + if (useGles) { + out->AppendElement(LOCAL_EGL_RENDERABLE_TYPE); + if (es3) { + out->AppendElement(LOCAL_EGL_OPENGL_ES3_BIT_KHR); + } else { + out->AppendElement(LOCAL_EGL_OPENGL_ES2_BIT); + } + } + + out->AppendElement(LOCAL_EGL_RED_SIZE); + out->AppendElement(8); + + out->AppendElement(LOCAL_EGL_GREEN_SIZE); + out->AppendElement(8); + + out->AppendElement(LOCAL_EGL_BLUE_SIZE); + out->AppendElement(8); + + out->AppendElement(LOCAL_EGL_ALPHA_SIZE); + out->AppendElement(8); + + out->AppendElement(LOCAL_EGL_DEPTH_SIZE); + out->AppendElement(0); + + out->AppendElement(LOCAL_EGL_STENCIL_SIZE); + out->AppendElement(0); + + // EGL_ATTRIBS_LIST_SAFE_TERMINATION_WORKING_AROUND_BUGS + out->AppendElement(LOCAL_EGL_NONE); + out->AppendElement(0); + + out->AppendElement(0); + out->AppendElement(0); +} + +/* +/// Useful for debugging, but normally unused. +static GLint GetAttrib(GLLibraryEGL* egl, EGLConfig config, EGLint attrib) { + EGLint bits = 0; + egl->fGetConfigAttrib(config, attrib, &bits); + MOZ_ASSERT(egl->fGetError() == LOCAL_EGL_SUCCESS); + + return bits; +} +*/ + +static EGLConfig ChooseConfig(EglDisplay& egl, const GLContextCreateDesc& desc, + const bool useGles) { + nsTArray<EGLint> configAttribList; + FillContextAttribs(bool(desc.flags & CreateContextFlags::PREFER_ES3), useGles, + &configAttribList); + + const EGLint* configAttribs = configAttribList.Elements(); + + // The sorting dictated by the spec for eglChooseConfig reasonably assures + // that a reasonable 'best' config is on top. + const EGLint kMaxConfigs = 1; + EGLConfig configs[kMaxConfigs]; + EGLint foundConfigs = 0; + if (!egl.fChooseConfig(configAttribs, configs, kMaxConfigs, &foundConfigs) || + foundConfigs == 0) { + return EGL_NO_CONFIG; + } + + EGLConfig config = configs[0]; + return config; +} + +#ifdef MOZ_X11 +/* static */ +bool GLContextEGL::FindVisual(int* const out_visualId) { + nsCString discardFailureId; + const auto egl = DefaultEglDisplay(&discardFailureId); + if (!egl) { + gfxCriticalNote + << "GLContextEGL::FindVisual(): Failed to load EGL library!"; + return false; + } + + EGLConfig config; + const int bpp = 32; + if (!CreateConfig(*egl, &config, bpp, /* aEnableDepthBuffer */ false, + /* aUseGles */ false, /* aAllowFallback */ false)) { + // We are on a buggy driver. Do not return a visual so a fallback path can + // be used. See https://gitlab.freedesktop.org/mesa/mesa/-/issues/149 + return false; + } + if (egl->fGetConfigAttrib(config, LOCAL_EGL_NATIVE_VISUAL_ID, out_visualId)) { + return true; + } + return false; +} +#endif + +/*static*/ +RefPtr<GLContextEGL> GLContextEGL::CreateWithoutSurface( + const std::shared_ptr<EglDisplay> egl, const GLContextCreateDesc& desc, + nsACString* const out_failureId) { + const auto WithUseGles = [&](const bool useGles) -> RefPtr<GLContextEGL> { +#ifdef MOZ_WIDGET_GTK + // First try creating a context with no config and no surface, this is what + // we really want, and seems to be the only way to make selecting software + // Mesa init properly when it's not the first device. + if (egl->IsExtensionSupported(EGLExtension::KHR_no_config_context) && + egl->IsExtensionSupported(EGLExtension::KHR_surfaceless_context)) { + // These extensions have been supported by mesa and nvidia drivers + // since 2014 or earlier, this is the preferred code path + auto fullDesc = GLContextDesc{desc}; + fullDesc.isOffscreen = true; + RefPtr<GLContextEGL> gl = GLContextEGL::CreateGLContext( + egl, fullDesc, EGL_NO_CONFIG, EGL_NO_SURFACE, useGles, EGL_NO_CONFIG, + out_failureId); + if (gl) { + return gl; + } + NS_WARNING( + "Failed to create GLContext with no config and no surface, will try " + "ChooseConfig"); + } +#endif + + const EGLConfig surfaceConfig = ChooseConfig(*egl, desc, useGles); + if (surfaceConfig == EGL_NO_CONFIG) { + *out_failureId = "FEATURE_FAILURE_EGL_NO_CONFIG"_ns; + NS_WARNING("Failed to find a compatible config."); + return nullptr; + } + + if (GLContext::ShouldSpew()) { + egl->DumpEGLConfig(surfaceConfig); + } + const EGLConfig contextConfig = + egl->IsExtensionSupported(EGLExtension::KHR_no_config_context) + ? nullptr + : surfaceConfig; + + auto dummySize = mozilla::gfx::IntSize{16, 16}; + EGLSurface surface = nullptr; +#ifdef MOZ_WAYLAND + if (GdkIsWaylandDisplay()) { + surface = GLContextEGL::CreateWaylandBufferSurface(*egl, surfaceConfig, + dummySize); + } else +#endif + { + surface = GLContextEGL::CreatePBufferSurfaceTryingPowerOfTwo( + *egl, surfaceConfig, LOCAL_EGL_NONE, dummySize); + } + if (!surface) { + *out_failureId = "FEATURE_FAILURE_EGL_POT"_ns; + NS_WARNING("Failed to create PBuffer for context!"); + return nullptr; + } + + auto fullDesc = GLContextDesc{desc}; + fullDesc.isOffscreen = true; + RefPtr<GLContextEGL> gl = + GLContextEGL::CreateGLContext(egl, fullDesc, surfaceConfig, surface, + useGles, contextConfig, out_failureId); + if (!gl) { + NS_WARNING("Failed to create GLContext from PBuffer"); + egl->fDestroySurface(surface); +#if defined(MOZ_WAYLAND) + DeleteSavedGLSurface(surface); +#endif + return nullptr; + } + + return gl; + }; + + bool preferGles; +#if defined(MOZ_WIDGET_ANDROID) + preferGles = true; +#else + preferGles = StaticPrefs::gfx_egl_prefer_gles_enabled_AtStartup(); +#endif // defined(MOZ_WIDGET_ANDROID) + RefPtr<GLContextEGL> gl = WithUseGles(preferGles); +#if !defined(MOZ_WIDGET_ANDROID) + if (!gl) { + gl = WithUseGles(!preferGles); + } +#endif // !defined(MOZ_WIDGET_ANDROID) + return gl; +} + +/*static*/ +already_AddRefed<GLContext> GLContextProviderEGL::CreateHeadless( + const GLContextCreateDesc& desc, nsACString* const out_failureId) { + const auto display = DefaultEglDisplay(out_failureId); + if (!display) { + return nullptr; + } + auto ret = GLContextEGL::CreateWithoutSurface(display, desc, out_failureId); + return ret.forget(); +} + +// Don't want a global context on Android as 1) share groups across 2 threads +// fail on many Tegra drivers (bug 759225) and 2) some mobile devices have a +// very strict limit on global number of GL contexts (bug 754257) and 3) each +// EGL context eats 750k on B2G (bug 813783) +/*static*/ +GLContext* GLContextProviderEGL::GetGlobalContext() { return nullptr; } + +// - + +/*static*/ void GLContextProviderEGL::Shutdown() { GLLibraryEGL::Shutdown(); } + +} /* namespace gl */ +} /* namespace mozilla */ + +#undef EGL_ATTRIBS_LIST_SAFE_TERMINATION_WORKING_AROUND_BUGS |