fune/gfx/gl/GLContextProviderEGL.cpp
Jamie Nicol c3f6044a08 Bug 1903810 - Leak EGLDisplays on all Samsung Xclipse GPUs. a=dmeehan
In bug 1868825 we encountered crashes on a specific device when
calling eglTerminate. As a workaround, we avoided calling eglTerminate
and instead deliberately leaked the EGLDisplay. As we had only
encountered this crash on a specific device, we limited the workaround
to just that device.

We are now seeing the same crash on a range of devices, all with
Samsung Xclipse GPUs. This patch therefore makes us leak the
EGLDisplays on all devices with an Xclipse GPU.

As the GPU model is not available in EglDisplay code, we instead set a
flag on the EglDisplay object when the GLContext is initialized. Prior
to terminating the display we check whether this flag has been set.
Terminating a display which has not had a context created does not
reproduce the crash, so this is sufficient.

Original Revision: https://phabricator.services.mozilla.com/D214539

Differential Revision: https://phabricator.services.mozilla.com/D214800
2024-06-25 13:26:51 +00:00

1267 lines
39 KiB
C++

/* -*- 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/jni/Utils.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 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<nsPtrHashKey<void>, 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<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 (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<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);
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<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);
EGLNativeWindowType window =
GET_NATIVE_WINDOW_FROM_COMPOSITOR_WIDGET(aCompositorWidget);
if (!window) {
#ifdef MOZ_WIDGET_GTK
// 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.
mozilla::gfx::IntSize pbSize(16, 16);
# ifdef MOZ_WAYLAND
if (GdkIsWaylandDisplay()) {
return CreateWaylandOffscreenSurface(*egl, aConfig, pbSize);
} else
# endif
{
return CreatePBufferSurfaceTryingPowerOfTwo(*egl, aConfig, LOCAL_EGL_NONE,
pbSize);
}
#else
gfxCriticalNote << "window is null";
return EGL_NO_SURFACE;
#endif
}
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);
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);
#if MOZ_WIDGET_ANDROID
// We see crashes in eglTerminate on devices with Xclipse GPUs running
// Android 14. Choose to leak the EGLDisplays in order to avoid the crashes.
// See bug 1868825 and bug 1903810.
if (Renderer() == GLRenderer::SamsungXclipse && jni::GetAPIVersion() >= 34) {
mEgl->SetShouldLeakEGLDisplay();
}
#endif
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);
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<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)
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<EGLNativeWindowType>(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<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 (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::PrimaryScreenDepth();
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::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<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)
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<GLContextEGL> 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<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