Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 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);
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
// Android doesn't always support 16 bit so also try 24 bit
if (depth == 16) {
return CreateConfig(egl, aConfig, 24, aEnableDepthBuffer, aUseGles);
}
// 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
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