/* 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 "angle/Platform.h" #include "gfxConfig.h" #include "gfxUtils.h" #include "mozilla/Preferences.h" #include "mozilla/Assertions.h" #include "mozilla/Tokenizer.h" #include "mozilla/ScopeExit.h" #include "mozilla/Unused.h" #include "mozilla/gfx/Logging.h" #include "nsDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" #include "nsIGfxInfo.h" #include "nsPrintfCString.h" #ifdef XP_WIN #include "nsWindowsHelpers.h" #endif #include "OGLShaderProgram.h" #include "prenv.h" #include "GLContext.h" #include "GLContextProvider.h" #include "gfxEnv.h" #include "gfxPrefs.h" #include "ScopedGLHelpers.h" #include "GLReadTexImageHelper.h" using namespace mozilla::gfx; using namespace mozilla::layers; namespace mozilla { namespace gl { StaticMutex GLLibraryEGL::sMutex; GLLibraryEGL sEGLLibrary; // 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_KHR_lock_surface", "EGL_ANGLE_surface_d3d_texture_2d_share_handle", "EGL_EXT_create_context_robustness", "EGL_KHR_image", "EGL_KHR_fence_sync", "EGL_ANGLE_platform_angle", "EGL_ANGLE_platform_angle_d3d" }; #ifdef XP_WIN // see the comment in GLLibraryEGL::EnsureInitialized() for the rationale here. static PRLibrary* LoadLibraryForEGLOnWindows(const nsAString& filename) { nsCOMPtr file; nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(file)); if (NS_FAILED(rv)) return nullptr; file->Append(filename); PRLibrary* lib = nullptr; rv = file->Load(&lib); if (NS_FAILED(rv)) { nsPrintfCString msg("Failed to load %s - Expect EGL initialization to fail", NS_LossyConvertUTF16toASCII(filename).get()); NS_WARNING(msg.get()); } return lib; } #endif // XP_WIN static EGLDisplay GetAndInitWARPDisplay(GLLibraryEGL& egl, void* displayType) { EGLint attrib_list[] = { LOCAL_EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, LOCAL_EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE, // Requires: LOCAL_EGL_PLATFORM_ANGLE_TYPE_ANGLE, LOCAL_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, LOCAL_EGL_NONE }; EGLDisplay display = egl.fGetPlatformDisplayEXT(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 EGL_NO_DISPLAY; } if (!egl.fInitialize(display, nullptr, nullptr)) return EGL_NO_DISPLAY; return display; } static bool IsAccelAngleSupported(const nsCOMPtr& gfxInfo, nsACString* const out_failureId) { int32_t angleSupport; nsCString failureId; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_ANGLE, failureId, &angleSupport); if (failureId.IsEmpty() && angleSupport != nsIGfxInfo::FEATURE_STATUS_OK) { // This shouldn't happen, if we see this it's because we've missed // some failure paths failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_ACCL_ANGLE_NOT_OK"); } if (out_failureId->IsEmpty()) { *out_failureId = failureId; } return (angleSupport == nsIGfxInfo::FEATURE_STATUS_OK); } static EGLDisplay GetAndInitDisplay(GLLibraryEGL& egl, void* displayType) { EGLDisplay display = egl.fGetDisplay(displayType); if (display == EGL_NO_DISPLAY) return EGL_NO_DISPLAY; if (!egl.fInitialize(display, nullptr, nullptr)) return EGL_NO_DISPLAY; return display; } class AngleErrorReporting: public angle::Platform { public: AngleErrorReporting() { // No static constructor } void SetFailureId(nsACString* const aFailureId) { mFailureId = aFailureId; } void logError(const char *errorMessage) override { 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("%i", intToken.AsInteger()); } else { *mFailureId = "FAILURE_ID_ANGLE_UNKNOWN"; } } private: nsACString* mFailureId; }; AngleErrorReporting gAngleErrorReporter; static EGLDisplay GetAndInitDisplayForAccelANGLE(GLLibraryEGL& egl, nsACString* const out_failureId) { EGLDisplay ret = 0; FeatureState& d3d11ANGLE = gfxConfig::GetFeature(Feature::D3D11_HW_ANGLE); if (!gfxPrefs::WebGLANGLETryD3D11()) d3d11ANGLE.UserDisable("User disabled D3D11 ANGLE by pref", NS_LITERAL_CSTRING("FAILURE_ID_ANGLE_PREF")); if (gfxPrefs::WebGLANGLEForceD3D11()) d3d11ANGLE.UserForceEnable("User force-enabled D3D11 ANGLE on disabled hardware"); gAngleErrorReporter.SetFailureId(out_failureId); egl.fANGLEPlatformInitialize(&gAngleErrorReporter); 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 (gfxConfig::IsForcedOnByUser(Feature::D3D11_HW_ANGLE)) { return GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ONLY_DISPLAY_ANGLE); } if (d3d11ANGLE.IsEnabled()) { ret = GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE); } if (!ret) { ret = GetAndInitDisplay(egl, EGL_DEFAULT_DISPLAY); } if (!ret && out_failureId->IsEmpty()) { *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_ACCL_ANGLE_NO_DISP"); } return ret; } bool GLLibraryEGL::ReadbackEGLImage(EGLImage image, gfx::DataSourceSurface* out_surface) { StaticMutexAutoUnlock lock(sMutex); if (!mReadbackGL) { nsCString discardFailureId; mReadbackGL = gl::GLContextProvider::CreateHeadless(gl::CreateContextFlags::NONE, &discardFailureId); } ScopedTexture destTex(mReadbackGL); const GLuint target = mReadbackGL->GetPreferredEGLImageTextureTarget(); ScopedBindTexture autoTex(mReadbackGL, destTex.Texture(), target); mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE); mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE); mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST); mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST); mReadbackGL->fEGLImageTargetTexture2D(target, image); ShaderConfigOGL config = ShaderConfigFromTargetAndFormat(target, out_surface->GetFormat()); int shaderConfig = config.mFeatures; mReadbackGL->ReadTexImageHelper()->ReadTexImage(out_surface, 0, target, out_surface->GetSize(), shaderConfig); return true; } bool GLLibraryEGL::EnsureInitialized(bool forceAccel, nsACString* const out_failureId) { if (mInitialized) { return true; } #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. // Try it first. Note that _46 will never be in the system // directory and we ship with at least _43. So there is no point // trying _46 and _43 in the system directory. if (LoadLibrarySystem32(L"d3dcompiler_47.dll")) break; #ifdef MOZ_D3DCOMPILER_VISTA_DLL if (LoadLibraryForEGLOnWindows(NS_LITERAL_STRING(NS_STRINGIFY(MOZ_D3DCOMPILER_VISTA_DLL)))) break; #endif #ifdef MOZ_D3DCOMPILER_XP_DLL if (LoadLibraryForEGLOnWindows(NS_LITERAL_STRING(NS_STRINGIFY(MOZ_D3DCOMPILER_XP_DLL)))) break; #endif MOZ_ASSERT(false, "d3dcompiler DLL loading failed."); } while (false); LoadLibraryForEGLOnWindows(NS_LITERAL_STRING("libGLESv2.dll")); mEGLLibrary = LoadLibraryForEGLOnWindows(NS_LITERAL_STRING("libEGL.dll")); if (!mEGLLibrary) { *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_LOAD"); return false; } } #else // !Windows // On non-Windows we use system copies of libEGL. We look for // libEGL.so and libEGL.so.1 in that order. if (!mEGLLibrary) { printf_stderr("Attempting load of libEGL.so\n"); mEGLLibrary = PR_LoadLibrary("libEGL.so"); } #if defined(XP_UNIX) if (!mEGLLibrary) { mEGLLibrary = PR_LoadLibrary("libEGL.so.1"); } #endif if (!mEGLLibrary) { NS_WARNING("Couldn't load EGL LIB."); *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_LOAD_2"); return false; } #endif // !Windows #define SYMBOL(name) \ { (PRFuncPtr*) &mSymbols.f##name, { "egl" #name, nullptr } } GLLibraryLoader::SymLoadStruct earlySymbols[] = { SYMBOL(GetDisplay), SYMBOL(Terminate), SYMBOL(GetCurrentSurface), SYMBOL(GetCurrentContext), SYMBOL(MakeCurrent), SYMBOL(DestroyContext), SYMBOL(CreateContext), SYMBOL(DestroySurface), SYMBOL(CreateWindowSurface), SYMBOL(CreatePbufferSurface), 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(QuerySurface), { nullptr, { nullptr } } }; if (!GLLibraryLoader::LoadSymbols(mEGLLibrary, &earlySymbols[0])) { NS_WARNING("Couldn't find required entry points in EGL library (early init)"); *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_SYM"); return false; } InitClientExtensions(); const auto lookupFunction = (GLLibraryLoader::PlatformLookupFunction)mSymbols.fGetProcAddress; // Client exts are ready. (But not display exts!) if (IsExtensionSupported(ANGLE_platform_angle_d3d)) { GLLibraryLoader::SymLoadStruct d3dSymbols[] = { { (PRFuncPtr*)&mSymbols.fANGLEPlatformInitialize, { "ANGLEPlatformInitialize", nullptr } }, { (PRFuncPtr*)&mSymbols.fANGLEPlatformShutdown, { "ANGLEPlatformShutdown", nullptr } }, { (PRFuncPtr*)&mSymbols.fGetPlatformDisplayEXT, { "eglGetPlatformDisplayEXT", nullptr } }, { nullptr, { nullptr } } }; bool success = GLLibraryLoader::LoadSymbols(mEGLLibrary, &d3dSymbols[0], lookupFunction); if (!success) { NS_ERROR("EGL supports ANGLE_platform_angle_d3d without exposing its functions!"); MarkExtensionUnsupported(ANGLE_platform_angle_d3d); mSymbols.fGetPlatformDisplayEXT = nullptr; } } // Check the ANGLE support the system has nsCOMPtr gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); mIsANGLE = IsExtensionSupported(ANGLE_platform_angle); EGLDisplay chosenDisplay = nullptr; if (IsExtensionSupported(ANGLE_platform_angle_d3d)) { nsCString accelAngleFailureId; bool accelAngleSupport = IsAccelAngleSupported(gfxInfo, &accelAngleFailureId); bool shouldTryAccel = forceAccel || accelAngleSupport; bool shouldTryWARP = !forceAccel; // Only if ANGLE not supported or fails // If WARP preferred, will override ANGLE support if (gfxPrefs::WebGLANGLEForceWARP()) { shouldTryWARP = true; shouldTryAccel = false; if (accelAngleFailureId.IsEmpty()) { accelAngleFailureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_FORCE_WARP"); } } // Hardware accelerated ANGLE path (supported or force accel) if (shouldTryAccel) { chosenDisplay = GetAndInitDisplayForAccelANGLE(*this, out_failureId); } // Fallback to a WARP display if ANGLE fails, or if WARP is forced if (!chosenDisplay && shouldTryWARP) { chosenDisplay = GetAndInitWARPDisplay(*this, EGL_DEFAULT_DISPLAY); if (!chosenDisplay) { if (out_failureId->IsEmpty()) { *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WARP_FALLBACK"); } NS_ERROR("Fallback WARP context failed to initialize."); return false; } mIsWARP = true; } } else { chosenDisplay = GetAndInitDisplay(*this, EGL_DEFAULT_DISPLAY); } if (!chosenDisplay) { if (out_failureId->IsEmpty()) { *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_NO_DISPLAY"); } NS_WARNING("Failed to initialize a display."); return false; } mEGLDisplay = chosenDisplay; InitDisplayExtensions(); //////////////////////////////////// // Alright, load display exts. if (IsExtensionSupported(KHR_lock_surface)) { GLLibraryLoader::SymLoadStruct lockSymbols[] = { { (PRFuncPtr*) &mSymbols.fLockSurface, { "eglLockSurfaceKHR", nullptr } }, { (PRFuncPtr*) &mSymbols.fUnlockSurface, { "eglUnlockSurfaceKHR", nullptr } }, { nullptr, { nullptr } } }; bool success = GLLibraryLoader::LoadSymbols(mEGLLibrary, &lockSymbols[0], lookupFunction); if (!success) { NS_ERROR("EGL supports KHR_lock_surface without exposing its functions!"); MarkExtensionUnsupported(KHR_lock_surface); mSymbols.fLockSurface = nullptr; mSymbols.fUnlockSurface = nullptr; } } if (IsExtensionSupported(ANGLE_surface_d3d_texture_2d_share_handle)) { GLLibraryLoader::SymLoadStruct d3dSymbols[] = { { (PRFuncPtr*) &mSymbols.fQuerySurfacePointerANGLE, { "eglQuerySurfacePointerANGLE", nullptr } }, { nullptr, { nullptr } } }; bool success = GLLibraryLoader::LoadSymbols(mEGLLibrary, &d3dSymbols[0], lookupFunction); if (!success) { NS_ERROR("EGL supports ANGLE_surface_d3d_texture_2d_share_handle without exposing its functions!"); MarkExtensionUnsupported(ANGLE_surface_d3d_texture_2d_share_handle); mSymbols.fQuerySurfacePointerANGLE = nullptr; } } if (IsExtensionSupported(KHR_fence_sync)) { GLLibraryLoader::SymLoadStruct syncSymbols[] = { { (PRFuncPtr*) &mSymbols.fCreateSync, { "eglCreateSyncKHR", nullptr } }, { (PRFuncPtr*) &mSymbols.fDestroySync, { "eglDestroySyncKHR", nullptr } }, { (PRFuncPtr*) &mSymbols.fClientWaitSync, { "eglClientWaitSyncKHR", nullptr } }, { (PRFuncPtr*) &mSymbols.fGetSyncAttrib, { "eglGetSyncAttribKHR", nullptr } }, { nullptr, { nullptr } } }; bool success = GLLibraryLoader::LoadSymbols(mEGLLibrary, &syncSymbols[0], lookupFunction); if (!success) { NS_ERROR("EGL supports KHR_fence_sync without exposing its functions!"); MarkExtensionUnsupported(KHR_fence_sync); mSymbols.fCreateSync = nullptr; mSymbols.fDestroySync = nullptr; mSymbols.fClientWaitSync = nullptr; mSymbols.fGetSyncAttrib = nullptr; } } if (IsExtensionSupported(KHR_image) || IsExtensionSupported(KHR_image_base)) { GLLibraryLoader::SymLoadStruct imageSymbols[] = { { (PRFuncPtr*) &mSymbols.fCreateImage, { "eglCreateImageKHR", nullptr } }, { (PRFuncPtr*) &mSymbols.fDestroyImage, { "eglDestroyImageKHR", nullptr } }, { nullptr, { nullptr } } }; bool success = GLLibraryLoader::LoadSymbols(mEGLLibrary, &imageSymbols[0], lookupFunction); if (!success) { NS_ERROR("EGL supports KHR_image(_base) without exposing its functions!"); MarkExtensionUnsupported(KHR_image); MarkExtensionUnsupported(KHR_image_base); MarkExtensionUnsupported(KHR_image_pixmap); mSymbols.fCreateImage = nullptr; mSymbols.fDestroyImage = nullptr; } } else { MarkExtensionUnsupported(KHR_image_pixmap); } mInitialized = true; return true; } template static void MarkExtensions(const char* rawExtString, bool shouldDumpExts, const char* extType, std::bitset* const out) { MOZ_ASSERT(rawExtString); const nsDependentCString extString(rawExtString); std::vector extList; SplitByChar(extString, ' ', &extList); if (shouldDumpExts) { printf_stderr("%u EGL %s extensions: (*: recognized)\n", (uint32_t)extList.size(), extType); } MarkBitfieldByStrings(extList, shouldDumpExts, sEGLExtensionNames, out); } void GLLibraryEGL::InitClientExtensions() { const bool shouldDumpExts = GLContext::ShouldDumpExts(); const char* rawExtString = nullptr; rawExtString = (const char*)fQueryString(nullptr, LOCAL_EGL_EXTENSIONS); if (!rawExtString) { if (shouldDumpExts) { printf_stderr("No EGL client extensions.\n"); } return; } MarkExtensions(rawExtString, shouldDumpExts, "client", &mAvailableExtensions); } void GLLibraryEGL::InitDisplayExtensions() { MOZ_ASSERT(mEGLDisplay); const bool shouldDumpExts = GLContext::ShouldDumpExts(); const auto rawExtString = (const char*)fQueryString(mEGLDisplay, LOCAL_EGL_EXTENSIONS); if (!rawExtString) { NS_WARNING("Failed to query EGL display extensions!."); return; } MarkExtensions(rawExtString, shouldDumpExts, "display", &mAvailableExtensions); } void GLLibraryEGL::DumpEGLConfig(EGLConfig cfg) { int attrval; int err; #define ATTR(_x) do { \ fGetConfigAttrib(mEGLDisplay, cfg, LOCAL_EGL_##_x, &attrval); \ if ((err = fGetError()) != 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 GLLibraryEGL::DumpEGLConfigs() { int nc = 0; fGetConfigs(mEGLDisplay, nullptr, 0, &nc); EGLConfig* ec = new EGLConfig[nc]; fGetConfigs(mEGLDisplay, ec, nc, &nc); for (int i = 0; i < nc; ++i) { printf_stderr("========= EGL Config %d ========\n", i); DumpEGLConfig(ec[i]); } delete [] ec; } #ifdef DEBUG static bool ShouldTrace() { static bool ret = gfxEnv::GlDebugVerbose(); return ret; } /*static*/ void GLLibraryEGL::BeforeGLCall(const char* glFunction) { if (ShouldTrace()) { printf_stderr("[egl] > %s\n", glFunction); } } /*static*/ void GLLibraryEGL::AfterGLCall(const char* glFunction) { if (ShouldTrace()) { printf_stderr("[egl] < %s\n", glFunction); } } #endif } /* namespace gl */ } /* namespace mozilla */