/* -*- 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 # include # 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 #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 # include # 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 WaylandOffscreenGLSurface { public: WaylandOffscreenGLSurface(struct wl_surface* aWaylandSurface, struct wl_egl_window* aEGLWindow); ~WaylandOffscreenGLSurface(); private: struct wl_surface* mWaylandSurface = nullptr; struct wl_egl_window* mEGLWindow = nullptr; }; static nsTHashMap, WaylandOffscreenGLSurface*> sWaylandOffscreenGLSurfaces; void DeleteWaylandOffscreenGLSurface(EGLSurface surface) { auto entry = sWaylandOffscreenGLSurfaces.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 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 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(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 Create(EGLNativeWindowType aWindow, bool aHardwareWebRender); static already_AddRefed CreateImpl(EGLNativeWindowType aWindow, bool aHardwareWebRender, bool aUseGles); private: GLContextEGLFactory() = default; ~GLContextEGLFactory() = default; }; already_AddRefed 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 (kIsLinux) { 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 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); GLContextEGL::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 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 = 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 CreateWaylandOffscreenSurface(*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 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); DestroySurface(*mEgl, mSurface); 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 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) { DestroySurface(*mEgl, mSurface); } if (mSurface == mSurfaceOverride) { mSurfaceOverride = EGL_NO_SURFACE; } mSurface = EGL_NO_SURFACE; } Maybe 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 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::CreateGLContext( const std::shared_ptr egl, const GLContextDesc& desc, EGLConfig surfaceConfig, EGLSurface surface, const bool useGles, EGLConfig contextConfig, nsACString* const out_failureId) { const auto& flags = desc.flags; std::vector 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 ext_robustness_attribs; std::vector ext_rbab_attribs; // RBAB: Robust Buffer Access Behavior std::vector khr_robustness_attribs; std::vector khr_rbab_attribs; // RBAB: Robust Buffer Access Behavior if (flags & CreateContextFlags::PREFER_ROBUSTNESS) { std::vector 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& 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 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 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) WaylandOffscreenGLSurface::WaylandOffscreenGLSurface( struct wl_surface* aWaylandSurface, struct wl_egl_window* aEGLWindow) : mWaylandSurface(aWaylandSurface), mEGLWindow(aEGLWindow) {} WaylandOffscreenGLSurface::~WaylandOffscreenGLSurface() { if (mEGLWindow) { wl_egl_window_destroy(mEGLWindow); } if (mWaylandSurface) { wl_surface_destroy(mWaylandSurface); } } // static EGLSurface GLContextEGL::CreateWaylandOffscreenSurface( 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(eglwindow), 0); if (surface) { MOZ_DIAGNOSTIC_ASSERT(!sWaylandOffscreenGLSurfaces.Contains(surface)); sWaylandOffscreenGLSurfaces.LookupOrInsert( surface, new WaylandOffscreenGLSurface(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 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 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 (kIsLinux && 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 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(aWindow), nullptr); if (!surface) { gfxCriticalError() << "CreateCompatibleSurface failed: " << hexa(GetError()); } return surface; } static void FillContextAttribs(bool es3, bool useGles, nsTArray* 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 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::CreateWithoutSurface( const std::shared_ptr egl, const GLContextCreateDesc& desc, nsACString* const out_failureId) { const auto WithUseGles = [&](const bool useGles) -> RefPtr { #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 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::CreateWaylandOffscreenSurface(*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 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) DeleteWaylandOffscreenGLSurface(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 gl = WithUseGles(preferGles); #if !defined(MOZ_WIDGET_ANDROID) if (!gl) { gl = WithUseGles(!preferGles); } #endif // !defined(MOZ_WIDGET_ANDROID) return gl; } /*static*/ void GLContextEGL::DestroySurface(EglDisplay& aEgl, const EGLSurface aSurface) { if (aSurface != EGL_NO_SURFACE) { if (!aEgl.fMakeCurrent(EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { const EGLint err = aEgl.mLib->fGetError(); gfxCriticalNote << "Error in eglMakeCurrent: " << gfx::hexa(err); } if (!aEgl.fDestroySurface(aSurface)) { const EGLint err = aEgl.mLib->fGetError(); gfxCriticalNote << "Error in eglDestroySurface: " << gfx::hexa(err); } #if defined(MOZ_WAYLAND) DeleteWaylandOffscreenGLSurface(aSurface); #endif } } /*static*/ already_AddRefed 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