diff options
Diffstat (limited to 'gfx/gl/GLLibraryEGL.cpp')
-rw-r--r-- | gfx/gl/GLLibraryEGL.cpp | 1079 |
1 files changed, 1079 insertions, 0 deletions
diff --git a/gfx/gl/GLLibraryEGL.cpp b/gfx/gl/GLLibraryEGL.cpp new file mode 100644 index 0000000000..e27081dc95 --- /dev/null +++ b/gfx/gl/GLLibraryEGL.cpp @@ -0,0 +1,1079 @@ +/* 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/. */ + +#include "GLLibraryEGL.h" + +#include "gfxConfig.h" +#include "gfxCrashReporterUtils.h" +#include "gfxEnv.h" +#include "gfxUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Assertions.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Tokenizer.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_webgl.h" +#include "mozilla/Unused.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsPrintfCString.h" +#ifdef XP_WIN +# include "mozilla/gfx/DeviceManagerDx.h" +# include "nsWindowsHelpers.h" +# include "prerror.h" + +# include <d3d11.h> +#endif +#include "OGLShaderProgram.h" +#include "prenv.h" +#include "prsystem.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "GLLibraryLoader.h" +#include "GLReadTexImageHelper.h" +#include "ScopedGLHelpers.h" +#ifdef MOZ_WIDGET_GTK +# include "mozilla/WidgetUtilsGtk.h" +# include "mozilla/widget/DMABufLibWrapper.h" +# ifdef MOZ_WAYLAND +# include "mozilla/widget/nsWaylandDisplay.h" +# endif // MOZ_WIDGET_GTK +# include <gdk/gdk.h> +#endif // MOZ_WAYLAND + +#include <mutex> // for call_once + +namespace mozilla { +namespace gl { + +StaticMutex GLLibraryEGL::sMutex; +StaticRefPtr<GLLibraryEGL> GLLibraryEGL::sInstance; + +// should match the order of EGLExtensions, and be null-terminated. +static const char* sEGLLibraryExtensionNames[] = { + "EGL_ANDROID_get_native_client_buffer", + "EGL_ANGLE_device_creation", + "EGL_ANGLE_device_creation_d3d11", + "EGL_ANGLE_platform_angle", + "EGL_ANGLE_platform_angle_d3d", + "EGL_EXT_device_enumeration", + "EGL_EXT_device_query", + "EGL_EXT_platform_device", + "EGL_MESA_platform_surfaceless"}; + +// should match the order of EGLExtensions, and be null-terminated. +static const char* sEGLExtensionNames[] = { + "EGL_KHR_image_base", + "EGL_KHR_image_pixmap", + "EGL_KHR_gl_texture_2D_image", + "EGL_ANGLE_surface_d3d_texture_2d_share_handle", + "EGL_EXT_create_context_robustness", + "EGL_KHR_image", + "EGL_KHR_fence_sync", + "EGL_KHR_wait_sync", + "EGL_ANDROID_native_fence_sync", + "EGL_ANDROID_image_crop", + "EGL_ANGLE_d3d_share_handle_client_buffer", + "EGL_KHR_create_context", + "EGL_KHR_stream", + "EGL_KHR_stream_consumer_gltexture", + "EGL_NV_stream_consumer_gltexture_yuv", + "EGL_ANGLE_stream_producer_d3d_texture", + "EGL_KHR_surfaceless_context", + "EGL_KHR_create_context_no_error", + "EGL_MOZ_create_context_provoking_vertex_dont_care", + "EGL_EXT_swap_buffers_with_damage", + "EGL_KHR_swap_buffers_with_damage", + "EGL_EXT_buffer_age", + "EGL_KHR_partial_update", + "EGL_NV_robustness_video_memory_purge", + "EGL_EXT_image_dma_buf_import", + "EGL_EXT_image_dma_buf_import_modifiers", + "EGL_MESA_image_dma_buf_export", + "EGL_KHR_no_config_context", +}; + +PRLibrary* LoadApitraceLibrary() { + const char* path = nullptr; + +#ifdef ANDROID + // We only need to explicitly dlopen egltrace + // on android as we can use LD_PRELOAD or other tricks + // on other platforms. We look for it in /data/local + // as that's writeable by all users. + path = "/data/local/tmp/egltrace.so"; +#endif + if (!path) return nullptr; + + // Initialization of gfx prefs here is only needed during the unit tests... + if (!StaticPrefs::gfx_apitrace_enabled_AtStartup()) { + return nullptr; + } + + static PRLibrary* sApitraceLibrary = nullptr; + if (sApitraceLibrary) return sApitraceLibrary; + + nsAutoCString logFile; + Preferences::GetCString("gfx.apitrace.logfile", logFile); + if (logFile.IsEmpty()) { + logFile = "firefox.trace"; + } + + // The firefox process can't write to /data/local, but it can write + // to $GRE_HOME/ + nsAutoCString logPath; + logPath.AppendPrintf("%s/%s", getenv("GRE_HOME"), logFile.get()); + +#ifndef XP_WIN // Windows is missing setenv and forbids PR_LoadLibrary. + // apitrace uses the TRACE_FILE environment variable to determine where + // to log trace output to + printf_stderr("Logging GL tracing output to %s", logPath.get()); + setenv("TRACE_FILE", logPath.get(), false); + + printf_stderr("Attempting load of %s\n", path); + sApitraceLibrary = PR_LoadLibrary(path); +#endif + + return sApitraceLibrary; +} + +#ifdef XP_WIN +// see the comment in GLLibraryEGL::EnsureInitialized() for the rationale here. +static PRLibrary* LoadLibraryForEGLOnWindows(const nsAString& filename) { + nsAutoString path(gfx::gfxVars::GREDirectory()); + path.Append(PR_GetDirectorySeparator()); + path.Append(filename); + + PRLibSpec lspec; + lspec.type = PR_LibSpec_PathnameU; + lspec.value.pathname_u = path.get(); + PRLibrary* lib = PR_LoadLibraryWithFlags(lspec, PR_LD_LAZY | PR_LD_LOCAL); + if (!lib) { + gfxCriticalNote << "Failed to load " << path.get() << " " << PR_GetError() + << " " << PR_GetOSError(); + } + return lib; +} + +#endif // XP_WIN + +static std::shared_ptr<EglDisplay> GetAndInitDisplay( + GLLibraryEGL& egl, void* displayType, + const StaticMutexAutoLock& aProofOfLock) { + const auto display = egl.fGetDisplay(displayType); + if (!display) return nullptr; + return EglDisplay::Create(egl, display, false, aProofOfLock); +} + +#ifdef MOZ_WIDGET_GTK +static std::shared_ptr<EglDisplay> GetAndInitDeviceDisplay( + GLLibraryEGL& egl, const StaticMutexAutoLock& aProofOfLock) { + nsAutoCString drmRenderDevice(gfx::gfxVars::DrmRenderDevice()); + if (drmRenderDevice.IsEmpty() || + !egl.IsExtensionSupported(EGLLibExtension::EXT_platform_device) || + !egl.IsExtensionSupported(EGLLibExtension::EXT_device_enumeration)) { + return nullptr; + } + + EGLint maxDevices; + if (!egl.fQueryDevicesEXT(0, nullptr, &maxDevices)) { + return nullptr; + } + + std::vector<EGLDeviceEXT> devices(maxDevices); + EGLint numDevices; + if (!egl.fQueryDevicesEXT(devices.size(), devices.data(), &numDevices)) { + return nullptr; + } + devices.resize(numDevices); + + EGLDisplay display = EGL_NO_DISPLAY; + for (const auto& device : devices) { + const char* renderNodeString = + egl.fQueryDeviceStringEXT(device, LOCAL_EGL_DRM_RENDER_NODE_FILE_EXT); + if (renderNodeString && + strcmp(renderNodeString, drmRenderDevice.get()) == 0) { + const EGLAttrib attrib_list[] = {LOCAL_EGL_NONE}; + display = egl.fGetPlatformDisplay(LOCAL_EGL_PLATFORM_DEVICE_EXT, device, + attrib_list); + break; + } + } + if (!display) { + return nullptr; + } + + return EglDisplay::Create(egl, display, true, aProofOfLock); +} + +static std::shared_ptr<EglDisplay> GetAndInitSoftwareDisplay( + GLLibraryEGL& egl, const StaticMutexAutoLock& aProofOfLock) { + if (!egl.IsExtensionSupported(EGLLibExtension::EXT_platform_device) || + !egl.IsExtensionSupported(EGLLibExtension::EXT_device_enumeration)) { + return nullptr; + } + + EGLint maxDevices; + if (!egl.fQueryDevicesEXT(0, nullptr, &maxDevices)) { + return nullptr; + } + + std::vector<EGLDeviceEXT> devices(maxDevices); + EGLint numDevices; + if (!egl.fQueryDevicesEXT(devices.size(), devices.data(), &numDevices)) { + return nullptr; + } + devices.resize(numDevices); + + EGLDisplay display = EGL_NO_DISPLAY; + for (const auto& device : devices) { + const char* renderNodeString = + egl.fQueryDeviceStringEXT(device, LOCAL_EGL_DRM_RENDER_NODE_FILE_EXT); + // We are looking for a device with no file + if (!renderNodeString || *renderNodeString == 0) { + const EGLAttrib attrib_list[] = {LOCAL_EGL_NONE}; + display = egl.fGetPlatformDisplay(LOCAL_EGL_PLATFORM_DEVICE_EXT, device, + attrib_list); + break; + } + } + if (!display) { + return nullptr; + } + + return EglDisplay::Create(egl, display, true, aProofOfLock); +} + +static std::shared_ptr<EglDisplay> GetAndInitSurfacelessDisplay( + GLLibraryEGL& egl, const StaticMutexAutoLock& aProofOfLock) { + if (!egl.IsExtensionSupported(EGLLibExtension::MESA_platform_surfaceless)) { + return nullptr; + } + + const EGLAttrib attrib_list[] = {LOCAL_EGL_NONE}; + const EGLDisplay display = egl.fGetPlatformDisplay( + LOCAL_EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, attrib_list); + if (display == EGL_NO_DISPLAY) { + return nullptr; + } + return EglDisplay::Create(egl, display, true, aProofOfLock); +} +#endif + +static auto EglDebugLayersEnabled() { + EGLAttrib ret = LOCAL_EGL_FALSE; + if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup()) { + ret = LOCAL_EGL_TRUE; + } + return ret; +} + +static std::shared_ptr<EglDisplay> GetAndInitWARPDisplay( + GLLibraryEGL& egl, void* displayType, + const StaticMutexAutoLock& aProofOfLock) { + const EGLAttrib attrib_list[] = { + LOCAL_EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, + LOCAL_EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE, + LOCAL_EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED_ANGLE, + EglDebugLayersEnabled(), + // Requires: + LOCAL_EGL_PLATFORM_ANGLE_TYPE_ANGLE, + LOCAL_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, LOCAL_EGL_NONE}; + const EGLDisplay display = egl.fGetPlatformDisplay( + LOCAL_EGL_PLATFORM_ANGLE_ANGLE, displayType, attrib_list); + + if (display == EGL_NO_DISPLAY) { + const EGLint err = egl.fGetError(); + if (err != LOCAL_EGL_SUCCESS) { + gfxCriticalError() << "Unexpected GL error: " << gfx::hexa(err); + MOZ_CRASH("GFX: Unexpected GL error."); + } + return nullptr; + } + + return EglDisplay::Create(egl, display, true, aProofOfLock); +} + +std::shared_ptr<EglDisplay> GLLibraryEGL::CreateDisplay( + ID3D11Device* const d3d11Device) { + StaticMutexAutoLock lock(sMutex); + EGLDeviceEXT eglDevice = + fCreateDeviceANGLE(LOCAL_EGL_D3D11_DEVICE_ANGLE, d3d11Device, nullptr); + if (!eglDevice) { + gfxCriticalNote << "Failed to get EGLDeviceEXT of D3D11Device"; + return nullptr; + } + const char* features[] = {"allowES3OnFL10_0", nullptr}; + // Create an EGLDisplay using the EGLDevice + const EGLAttrib attrib_list[] = {LOCAL_EGL_FEATURE_OVERRIDES_ENABLED_ANGLE, + reinterpret_cast<EGLAttrib>(features), + LOCAL_EGL_NONE}; + const auto display = fGetPlatformDisplay(LOCAL_EGL_PLATFORM_DEVICE_EXT, + eglDevice, attrib_list); + if (!display) { + gfxCriticalNote << "Failed to get EGLDisplay of D3D11Device"; + return nullptr; + } + + if (!display) { + const EGLint err = fGetError(); + if (err != LOCAL_EGL_SUCCESS) { + gfxCriticalError() << "Unexpected GL error: " << gfx::hexa(err); + MOZ_CRASH("GFX: Unexpected GL error."); + } + return nullptr; + } + + const auto ret = EglDisplay::Create(*this, display, false, lock); + + if (!ret) { + const EGLint err = fGetError(); + if (err != LOCAL_EGL_SUCCESS) { + gfxCriticalError() + << "Failed to initialize EGLDisplay for WebRender error: " + << gfx::hexa(err); + } + return nullptr; + } + return ret; +} + +static bool IsAccelAngleSupported(nsACString* const out_failureId) { + if (!gfx::gfxVars::AllowWebglAccelAngle()) { + if (out_failureId->IsEmpty()) { + *out_failureId = "FEATURE_FAILURE_ACCL_ANGLE_NOT_OK"_ns; + } + return false; + } + return true; +} + +class AngleErrorReporting { + public: + AngleErrorReporting() : mFailureId(nullptr) { + // No static constructor + } + + void SetFailureId(nsACString* const aFailureId) { mFailureId = aFailureId; } + + void logError(const char* errorMessage) { + if (!mFailureId) { + return; + } + + nsCString str(errorMessage); + Tokenizer tokenizer(str); + + // Parse "ANGLE Display::initialize error " << error.getID() << ": " + // << error.getMessage() + nsCString currWord; + Tokenizer::Token intToken; + if (tokenizer.CheckWord("ANGLE") && tokenizer.CheckWhite() && + tokenizer.CheckWord("Display") && tokenizer.CheckChar(':') && + tokenizer.CheckChar(':') && tokenizer.CheckWord("initialize") && + tokenizer.CheckWhite() && tokenizer.CheckWord("error") && + tokenizer.CheckWhite() && + tokenizer.Check(Tokenizer::TOKEN_INTEGER, intToken)) { + *mFailureId = "FAILURE_ID_ANGLE_ID_"; + mFailureId->AppendPrintf("%" PRIu64, intToken.AsInteger()); + } else { + *mFailureId = "FAILURE_ID_ANGLE_UNKNOWN"; + } + } + + private: + nsACString* mFailureId; +}; + +AngleErrorReporting gAngleErrorReporter; + +static std::shared_ptr<EglDisplay> GetAndInitDisplayForAccelANGLE( + GLLibraryEGL& egl, nsACString* const out_failureId, + const StaticMutexAutoLock& aProofOfLock) { + gfx::FeatureState& d3d11ANGLE = + gfx::gfxConfig::GetFeature(gfx::Feature::D3D11_HW_ANGLE); + + if (!StaticPrefs::webgl_angle_try_d3d11()) { + d3d11ANGLE.UserDisable("User disabled D3D11 ANGLE by pref", + "FAILURE_ID_ANGLE_PREF"_ns); + } + if (StaticPrefs::webgl_angle_force_d3d11()) { + d3d11ANGLE.UserForceEnable( + "User force-enabled D3D11 ANGLE on disabled hardware"); + } + gAngleErrorReporter.SetFailureId(out_failureId); + + auto guardShutdown = mozilla::MakeScopeExit([&] { + gAngleErrorReporter.SetFailureId(nullptr); + // NOTE: Ideally we should be calling ANGLEPlatformShutdown after the + // ANGLE display is destroyed. However gAngleErrorReporter + // will live longer than the ANGLE display so we're fine. + }); + + if (gfx::gfxConfig::IsForcedOnByUser(gfx::Feature::D3D11_HW_ANGLE)) { + return GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ONLY_DISPLAY_ANGLE, + aProofOfLock); + } + + std::shared_ptr<EglDisplay> ret; + if (d3d11ANGLE.IsEnabled()) { + ret = GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE, + aProofOfLock); + } + + if (!ret) { + ret = GetAndInitDisplay(egl, EGL_DEFAULT_DISPLAY, aProofOfLock); + } + + if (!ret && out_failureId->IsEmpty()) { + *out_failureId = "FEATURE_FAILURE_ACCL_ANGLE_NO_DISP"_ns; + } + + return ret; +} + +// - + +#if defined(XP_UNIX) +# define GLES2_LIB "libGLESv2.so" +# define GLES2_LIB2 "libGLESv2.so.2" +# define GL_LIB "libGL.so" +# define GL_LIB2 "libGL.so.1" +#elif defined(XP_WIN) +# define GLES2_LIB "libGLESv2.dll" +#else +# error "Platform not recognized" +#endif + +Maybe<SymbolLoader> GLLibraryEGL::GetSymbolLoader() const { + auto ret = SymbolLoader(mSymbols.fGetProcAddress); + ret.mLib = mGLLibrary; + return Some(ret); +} + +// - + +/* static */ +RefPtr<GLLibraryEGL> GLLibraryEGL::Get(nsACString* const out_failureId) { + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + sInstance = new GLLibraryEGL; + if (NS_WARN_IF(!sInstance->Init(out_failureId))) { + sInstance = nullptr; + } + } + return sInstance; +} + +/* static */ void GLLibraryEGL::Shutdown() { + StaticMutexAutoLock lock(sMutex); + sInstance = nullptr; +} + +bool GLLibraryEGL::Init(nsACString* const out_failureId) { + MOZ_RELEASE_ASSERT(!mSymbols.fTerminate); + + mozilla::ScopedGfxFeatureReporter reporter("EGL"); + +#ifdef XP_WIN + if (!mEGLLibrary) { + // On Windows, the GLESv2, EGL and DXSDK libraries are shipped with libxul + // and we should look for them there. We have to load the libs in this + // order, because libEGL.dll depends on libGLESv2.dll which depends on the + // DXSDK libraries. This matters especially for WebRT apps which are in a + // different directory. See bug 760323 and bug 749459 + + // Also note that we intentionally leak the libs we load. + + do { + // Windows 8.1+ has d3dcompiler_47.dll in the system directory. + if (LoadLibrarySystem32(L"d3dcompiler_47.dll")) break; + + MOZ_ASSERT(false, "d3dcompiler DLL loading failed."); + } while (false); + + mGLLibrary = LoadLibraryForEGLOnWindows(u"libGLESv2.dll"_ns); + + mEGLLibrary = LoadLibraryForEGLOnWindows(u"libEGL.dll"_ns); + } + +#else // !Windows + + // On non-Windows (Android) we use system copies of libEGL. We look for + // the APITrace lib, libEGL.so, and libEGL.so.1 in that order. + +# if defined(ANDROID) + if (!mEGLLibrary) mEGLLibrary = LoadApitraceLibrary(); +# endif + + if (!mEGLLibrary) { + mEGLLibrary = PR_LoadLibrary("libEGL.so"); + } +# if defined(XP_UNIX) + if (!mEGLLibrary) { + mEGLLibrary = PR_LoadLibrary("libEGL.so.1"); + } +# endif + +# ifdef APITRACE_LIB + if (!mGLLibrary) { + mGLLibrary = PR_LoadLibrary(APITRACE_LIB); + } +# endif + +# ifdef GL_LIB + if (!mGLLibrary) { + mGLLibrary = PR_LoadLibrary(GL_LIB); + } +# endif + +# ifdef GL_LIB2 + if (!mGLLibrary) { + mGLLibrary = PR_LoadLibrary(GL_LIB2); + } +# endif + + if (!mGLLibrary) { + mGLLibrary = PR_LoadLibrary(GLES2_LIB); + } + +# ifdef GLES2_LIB2 + if (!mGLLibrary) { + mGLLibrary = PR_LoadLibrary(GLES2_LIB2); + } +# endif + +#endif // !Windows + + if (!mEGLLibrary || !mGLLibrary) { + NS_WARNING("Couldn't load EGL LIB."); + *out_failureId = "FEATURE_FAILURE_EGL_LOAD_3"_ns; + return false; + } + +#define SYMBOL(X) \ + { \ + (PRFuncPtr*)&mSymbols.f##X, { \ + { "egl" #X } \ + } \ + } +#define END_OF_SYMBOLS \ + { \ + nullptr, {} \ + } + + SymLoadStruct earlySymbols[] = {SYMBOL(GetDisplay), + SYMBOL(Terminate), + SYMBOL(GetCurrentSurface), + SYMBOL(GetCurrentContext), + SYMBOL(MakeCurrent), + SYMBOL(DestroyContext), + SYMBOL(CreateContext), + SYMBOL(DestroySurface), + SYMBOL(CreateWindowSurface), + SYMBOL(CreatePbufferSurface), + SYMBOL(CreatePbufferFromClientBuffer), + SYMBOL(CreatePixmapSurface), + SYMBOL(BindAPI), + SYMBOL(Initialize), + SYMBOL(ChooseConfig), + SYMBOL(GetError), + SYMBOL(GetConfigs), + SYMBOL(GetConfigAttrib), + SYMBOL(WaitNative), + SYMBOL(GetProcAddress), + SYMBOL(SwapBuffers), + SYMBOL(CopyBuffers), + SYMBOL(QueryString), + SYMBOL(QueryContext), + SYMBOL(BindTexImage), + SYMBOL(ReleaseTexImage), + SYMBOL(SwapInterval), + SYMBOL(QuerySurface), + END_OF_SYMBOLS}; + + { + const SymbolLoader libLoader(*mEGLLibrary); + if (!libLoader.LoadSymbols(earlySymbols)) { + NS_WARNING( + "Couldn't find required entry points in EGL library (early init)"); + *out_failureId = "FEATURE_FAILURE_EGL_SYM"_ns; + return false; + } + } + + { + const char internalFuncName[] = + "_Z35eglQueryStringImplementationANDROIDPvi"; + const auto& internalFunc = + PR_FindFunctionSymbol(mEGLLibrary, internalFuncName); + if (internalFunc) { + *(PRFuncPtr*)&mSymbols.fQueryString = internalFunc; + } + } + + // - + + InitLibExtensions(); + + const SymbolLoader pfnLoader(mSymbols.fGetProcAddress); + + const auto fnLoadSymbols = [&](const SymLoadStruct* symbols) { + const bool shouldWarn = gfxEnv::MOZ_GL_SPEW(); + if (pfnLoader.LoadSymbols(symbols, shouldWarn)) return true; + + ClearSymbols(symbols); + return false; + }; + + // Check the ANGLE support the system has + mIsANGLE = IsExtensionSupported(EGLLibExtension::ANGLE_platform_angle); + + // Client exts are ready. (But not display exts!) + + if (mIsANGLE) { + MOZ_ASSERT(IsExtensionSupported(EGLLibExtension::ANGLE_platform_angle_d3d)); + const SymLoadStruct angleSymbols[] = {SYMBOL(GetPlatformDisplay), + END_OF_SYMBOLS}; + if (!fnLoadSymbols(angleSymbols)) { + gfxCriticalError() << "Failed to load ANGLE symbols!"; + return false; + } + MOZ_ASSERT(IsExtensionSupported(EGLLibExtension::ANGLE_platform_angle_d3d)); + const SymLoadStruct createDeviceSymbols[] = { + SYMBOL(CreateDeviceANGLE), SYMBOL(ReleaseDeviceANGLE), END_OF_SYMBOLS}; + if (!fnLoadSymbols(createDeviceSymbols)) { + NS_ERROR( + "EGL supports ANGLE_device_creation without exposing its functions!"); + MarkExtensionUnsupported(EGLLibExtension::ANGLE_device_creation); + } + } + + // ANDROID_get_native_client_buffer isn't necessarily enumerated in lib exts, + // but it is one. + { + const SymLoadStruct symbols[] = {SYMBOL(GetNativeClientBufferANDROID), + END_OF_SYMBOLS}; + if (fnLoadSymbols(symbols)) { + mAvailableExtensions[UnderlyingValue( + EGLLibExtension::ANDROID_get_native_client_buffer)] = true; + } + } + + // - + // Load possible display ext symbols. + + { + const SymLoadStruct symbols[] = {SYMBOL(QuerySurfacePointerANGLE), + END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = { + SYMBOL(CreateSyncKHR), SYMBOL(DestroySyncKHR), + SYMBOL(ClientWaitSyncKHR), SYMBOL(GetSyncAttribKHR), END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = {SYMBOL(CreateImageKHR), + SYMBOL(DestroyImageKHR), END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = {SYMBOL(WaitSyncKHR), END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = {SYMBOL(DupNativeFenceFDANDROID), + END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = {SYMBOL(CreateStreamKHR), + SYMBOL(DestroyStreamKHR), + SYMBOL(QueryStreamKHR), END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = {SYMBOL(StreamConsumerGLTextureExternalKHR), + SYMBOL(StreamConsumerAcquireKHR), + SYMBOL(StreamConsumerReleaseKHR), + END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = { + SYMBOL(QueryDisplayAttribEXT), SYMBOL(QueryDeviceAttribEXT), + SYMBOL(QueryDeviceStringEXT), END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = { + SYMBOL(StreamConsumerGLTextureExternalAttribsNV), END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = { + SYMBOL(CreateStreamProducerD3DTextureANGLE), + SYMBOL(StreamPostD3DTextureANGLE), END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = { + {(PRFuncPtr*)&mSymbols.fSwapBuffersWithDamage, + {{"eglSwapBuffersWithDamageEXT"}}}, + END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = { + {(PRFuncPtr*)&mSymbols.fSwapBuffersWithDamage, + {{"eglSwapBuffersWithDamageKHR"}}}, + END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = { + {(PRFuncPtr*)&mSymbols.fSetDamageRegion, {{"eglSetDamageRegionKHR"}}}, + END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = {SYMBOL(GetPlatformDisplay), + END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = {SYMBOL(ExportDMABUFImageQueryMESA), + SYMBOL(ExportDMABUFImageMESA), + END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + { + const SymLoadStruct symbols[] = {SYMBOL(QueryDevicesEXT), END_OF_SYMBOLS}; + (void)fnLoadSymbols(symbols); + } + + return true; +} + +// - + +template <size_t N> +static void MarkExtensions(const char* rawExtString, bool shouldDumpExts, + const char* extType, const char* const (&names)[N], + std::bitset<N>* const out) { + MOZ_ASSERT(rawExtString); + + const nsDependentCString extString(rawExtString); + + std::vector<nsCString> extList; + SplitByChar(extString, ' ', &extList); + + if (shouldDumpExts) { + printf_stderr("%u EGL %s extensions: (*: recognized)\n", + (uint32_t)extList.size(), extType); + } + + MarkBitfieldByStrings(extList, shouldDumpExts, names, out); +} + +// - + +// static +std::shared_ptr<EglDisplay> EglDisplay::Create( + GLLibraryEGL& lib, const EGLDisplay display, const bool isWarp, + const StaticMutexAutoLock& aProofOfLock) { + // Retrieve the EglDisplay if it already exists + { + const auto itr = lib.mActiveDisplays.find(display); + if (itr != lib.mActiveDisplays.end()) { + const auto ret = itr->second.lock(); + if (ret) { + return ret; + } + } + } + + if (!lib.fInitialize(display, nullptr, nullptr)) { + return nullptr; + } + + static std::once_flag sMesaLeakFlag; + std::call_once(sMesaLeakFlag, MesaMemoryLeakWorkaround); + + const auto ret = + std::make_shared<EglDisplay>(PrivateUseOnly{}, lib, display, isWarp); + lib.mActiveDisplays.insert({display, ret}); + return ret; +} + +EglDisplay::EglDisplay(const PrivateUseOnly&, GLLibraryEGL& lib, + const EGLDisplay disp, const bool isWarp) + : mLib(&lib), mDisplay(disp), mIsWARP(isWarp) { + const bool shouldDumpExts = GLContext::ShouldDumpExts(); + + auto rawExtString = + (const char*)mLib->fQueryString(mDisplay, LOCAL_EGL_EXTENSIONS); + if (!rawExtString) { + NS_WARNING("Failed to query EGL display extensions!."); + rawExtString = ""; + } + MarkExtensions(rawExtString, shouldDumpExts, "display", sEGLExtensionNames, + &mAvailableExtensions); + + // - + + if (!HasKHRImageBase()) { + MarkExtensionUnsupported(EGLExtension::KHR_image_pixmap); + } + + if (IsExtensionSupported(EGLExtension::KHR_surfaceless_context)) { + const auto vendor = + (const char*)mLib->fQueryString(mDisplay, LOCAL_EGL_VENDOR); + + // Bug 1464610: Mali T720 (Amazon Fire 8 HD) claims to support this + // extension, but if you actually eglMakeCurrent() with EGL_NO_SURFACE, it + // fails to render anything when a real surface is provided later on. We + // only have the EGL vendor available here, so just avoid using this + // extension on all Mali devices. + if (vendor && (strcmp(vendor, "ARM") == 0)) { + MarkExtensionUnsupported(EGLExtension::KHR_surfaceless_context); + } + } + + // ANDROID_native_fence_sync isn't necessarily enumerated in display ext, + // but it is one. + if (mLib->mSymbols.fDupNativeFenceFDANDROID) { + mAvailableExtensions[UnderlyingValue( + EGLExtension::ANDROID_native_fence_sync)] = true; + } +} + +EglDisplay::~EglDisplay() { + StaticMutexAutoLock lock(GLLibraryEGL::sMutex); + fTerminate(); + mLib->mActiveDisplays.erase(mDisplay); +} + +// - + +std::shared_ptr<EglDisplay> GLLibraryEGL::DefaultDisplay( + nsACString* const out_failureId) { + StaticMutexAutoLock lock(sMutex); + auto ret = mDefaultDisplay.lock(); + if (ret) return ret; + + ret = CreateDisplayLocked(false, out_failureId, lock); + mDefaultDisplay = ret; + return ret; +} + +std::shared_ptr<EglDisplay> GLLibraryEGL::CreateDisplay( + const bool forceAccel, nsACString* const out_failureId) { + StaticMutexAutoLock lock(sMutex); + return CreateDisplayLocked(forceAccel, out_failureId, lock); +} + +std::shared_ptr<EglDisplay> GLLibraryEGL::CreateDisplayLocked( + const bool forceAccel, nsACString* const out_failureId, + const StaticMutexAutoLock& aProofOfLock) { + std::shared_ptr<EglDisplay> ret; + + if (IsExtensionSupported(EGLLibExtension::ANGLE_platform_angle_d3d)) { + nsCString accelAngleFailureId; + bool accelAngleSupport = IsAccelAngleSupported(&accelAngleFailureId); + bool shouldTryAccel = forceAccel || accelAngleSupport; + bool shouldTryWARP = !forceAccel; // Only if ANGLE not supported or fails + + // If WARP preferred, will override ANGLE support + if (StaticPrefs::webgl_angle_force_warp()) { + shouldTryWARP = true; + shouldTryAccel = false; + if (accelAngleFailureId.IsEmpty()) { + accelAngleFailureId = "FEATURE_FAILURE_FORCE_WARP"_ns; + } + } + + // Hardware accelerated ANGLE path (supported or force accel) + if (shouldTryAccel) { + ret = GetAndInitDisplayForAccelANGLE(*this, out_failureId, aProofOfLock); + } + + // Report the acceleration status to telemetry + if (!ret) { + if (accelAngleFailureId.IsEmpty()) { + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID, + "FEATURE_FAILURE_ACCL_ANGLE_UNKNOWN"_ns); + } else { + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID, + accelAngleFailureId); + } + } else { + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID, + "SUCCESS"_ns); + } + + // Fallback to a WARP display if ANGLE fails, or if WARP is forced + if (!ret && shouldTryWARP) { + ret = GetAndInitWARPDisplay(*this, EGL_DEFAULT_DISPLAY, aProofOfLock); + if (!ret) { + if (out_failureId->IsEmpty()) { + *out_failureId = "FEATURE_FAILURE_WARP_FALLBACK"_ns; + } + NS_ERROR("Fallback WARP context failed to initialize."); + return nullptr; + } + } + } else { + void* nativeDisplay = EGL_DEFAULT_DISPLAY; +#ifdef MOZ_WIDGET_GTK + if (!ret && !gfx::gfxVars::WebglUseHardware()) { + // Initialize a swrast egl device such as llvmpipe + ret = GetAndInitSoftwareDisplay(*this, aProofOfLock); + } + // Initialize the display the normal way + if (!ret && !gdk_display_get_default()) { + ret = GetAndInitDeviceDisplay(*this, aProofOfLock); + if (!ret) { + ret = GetAndInitSurfacelessDisplay(*this, aProofOfLock); + } + } +# ifdef MOZ_WAYLAND + else if (!ret && widget::GdkIsWaylandDisplay()) { + // Wayland does not support EGL_DEFAULT_DISPLAY + nativeDisplay = widget::WaylandDisplayGetWLDisplay(); + if (!nativeDisplay) { + NS_WARNING("Failed to get wl_display."); + return nullptr; + } + } +# endif +#endif + if (!ret) { + ret = GetAndInitDisplay(*this, nativeDisplay, aProofOfLock); + } + } + + if (!ret) { + if (out_failureId->IsEmpty()) { + *out_failureId = "FEATURE_FAILURE_NO_DISPLAY"_ns; + } + NS_WARNING("Failed to initialize a display."); + return nullptr; + } + + return ret; +} + +void GLLibraryEGL::InitLibExtensions() { + const bool shouldDumpExts = GLContext::ShouldDumpExts(); + + const char* rawExtString = nullptr; + +#ifndef ANDROID + // Bug 1209612: Crashes on a number of android drivers. + // Ideally we would only blocklist this there, but for now we don't need the + // client extension list on ANDROID (we mostly need it on ANGLE), and we'd + // rather not crash. + rawExtString = (const char*)fQueryString(nullptr, LOCAL_EGL_EXTENSIONS); +#endif + + if (!rawExtString) { + if (shouldDumpExts) { + printf_stderr("No EGL lib extensions.\n"); + } + return; + } + + MarkExtensions(rawExtString, shouldDumpExts, "lib", sEGLLibraryExtensionNames, + &mAvailableExtensions); +} + +void EglDisplay::DumpEGLConfig(EGLConfig cfg) const { +#define ATTR(_x) \ + do { \ + int attrval = 0; \ + mLib->fGetConfigAttrib(mDisplay, cfg, LOCAL_EGL_##_x, &attrval); \ + const auto err = mLib->fGetError(); \ + if (err != 0x3000) { \ + printf_stderr(" %s: ERROR (0x%04x)\n", #_x, err); \ + } else { \ + printf_stderr(" %s: %d (0x%04x)\n", #_x, attrval, attrval); \ + } \ + } while (0) + + printf_stderr("EGL Config: %d [%p]\n", (int)(intptr_t)cfg, cfg); + + ATTR(BUFFER_SIZE); + ATTR(ALPHA_SIZE); + ATTR(BLUE_SIZE); + ATTR(GREEN_SIZE); + ATTR(RED_SIZE); + ATTR(DEPTH_SIZE); + ATTR(STENCIL_SIZE); + ATTR(CONFIG_CAVEAT); + ATTR(CONFIG_ID); + ATTR(LEVEL); + ATTR(MAX_PBUFFER_HEIGHT); + ATTR(MAX_PBUFFER_PIXELS); + ATTR(MAX_PBUFFER_WIDTH); + ATTR(NATIVE_RENDERABLE); + ATTR(NATIVE_VISUAL_ID); + ATTR(NATIVE_VISUAL_TYPE); + ATTR(PRESERVED_RESOURCES); + ATTR(SAMPLES); + ATTR(SAMPLE_BUFFERS); + ATTR(SURFACE_TYPE); + ATTR(TRANSPARENT_TYPE); + ATTR(TRANSPARENT_RED_VALUE); + ATTR(TRANSPARENT_GREEN_VALUE); + ATTR(TRANSPARENT_BLUE_VALUE); + ATTR(BIND_TO_TEXTURE_RGB); + ATTR(BIND_TO_TEXTURE_RGBA); + ATTR(MIN_SWAP_INTERVAL); + ATTR(MAX_SWAP_INTERVAL); + ATTR(LUMINANCE_SIZE); + ATTR(ALPHA_MASK_SIZE); + ATTR(COLOR_BUFFER_TYPE); + ATTR(RENDERABLE_TYPE); + ATTR(CONFORMANT); + +#undef ATTR +} + +void EglDisplay::DumpEGLConfigs() const { + int nc = 0; + mLib->fGetConfigs(mDisplay, nullptr, 0, &nc); + std::vector<EGLConfig> ec(nc); + mLib->fGetConfigs(mDisplay, ec.data(), ec.size(), &nc); + + for (int i = 0; i < nc; ++i) { + printf_stderr("========= EGL Config %d ========\n", i); + DumpEGLConfig(ec[i]); + } +} + +static bool ShouldTrace() { + static bool ret = gfxEnv::MOZ_GL_DEBUG_VERBOSE(); + return ret; +} + +void BeforeEGLCall(const char* glFunction) { + if (ShouldTrace()) { + printf_stderr("[egl] > %s\n", glFunction); + } +} + +void AfterEGLCall(const char* glFunction) { + if (ShouldTrace()) { + printf_stderr("[egl] < %s\n", glFunction); + } +} + +} /* namespace gl */ +} /* namespace mozilla */ |