/* -*- 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/. */ #include "LayerManagerOGL.h" #include "mozilla/layers/PLayers.h" #include /* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */ #include "mozilla/Util.h" #include "Composer2D.h" #include "ThebesLayerOGL.h" #include "ContainerLayerOGL.h" #include "ImageLayerOGL.h" #include "ColorLayerOGL.h" #include "CanvasLayerOGL.h" #include "TiledThebesLayerOGL.h" #include "mozilla/TimeStamp.h" #include "mozilla/Preferences.h" #include "TexturePoolOGL.h" #include "gfxContext.h" #include "gfxUtils.h" #include "gfxPlatform.h" #include "nsIWidget.h" #include "GLContext.h" #include "GLContextProvider.h" #include "nsIServiceManager.h" #include "nsIConsoleService.h" #include "gfxCrashReporterUtils.h" #include "GeckoProfiler.h" #ifdef MOZ_WIDGET_ANDROID #include #endif #ifdef XP_MACOSX #include "gfxPlatformMac.h" #endif namespace mozilla { namespace layers { using namespace mozilla::gfx; using namespace mozilla::gl; #ifdef CHECK_CURRENT_PROGRAM int ShaderProgramOGL::sCurrentProgramKey = 0; #endif bool LayerManagerOGL::Initialize(bool force) { return Initialize(CreateContext(), force); } int32_t LayerManagerOGL::GetMaxTextureSize() const { int32_t maxSize; mGLContext->MakeCurrent(); mGLContext->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &maxSize); return maxSize; } void LayerManagerOGL::MakeCurrent(bool aForce) { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return; } mGLContext->MakeCurrent(aForce); } void* LayerManagerOGL::GetNSOpenGLContext() const { return gl()->GetNativeData(GLContext::NativeGLContext); } void LayerManagerOGL::BindQuadVBO() { mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); } void LayerManagerOGL::QuadVBOVerticesAttrib(GLuint aAttribIndex) { mGLContext->fVertexAttribPointer(aAttribIndex, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, (GLvoid*) QuadVBOVertexOffset()); } void LayerManagerOGL::QuadVBOTexCoordsAttrib(GLuint aAttribIndex) { mGLContext->fVertexAttribPointer(aAttribIndex, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, (GLvoid*) QuadVBOTexCoordOffset()); } void LayerManagerOGL::QuadVBOFlippedTexCoordsAttrib(GLuint aAttribIndex) { mGLContext->fVertexAttribPointer(aAttribIndex, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, (GLvoid*) QuadVBOFlippedTexCoordOffset()); } // Super common void LayerManagerOGL::BindAndDrawQuad(GLuint aVertAttribIndex, GLuint aTexCoordAttribIndex, bool aFlipped) { BindQuadVBO(); QuadVBOVerticesAttrib(aVertAttribIndex); if (aTexCoordAttribIndex != GLuint(-1)) { if (aFlipped) QuadVBOFlippedTexCoordsAttrib(aTexCoordAttribIndex); else QuadVBOTexCoordsAttrib(aTexCoordAttribIndex); mGLContext->fEnableVertexAttribArray(aTexCoordAttribIndex); } mGLContext->fEnableVertexAttribArray(aVertAttribIndex); mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); mGLContext->fDisableVertexAttribArray(aVertAttribIndex); if (aTexCoordAttribIndex != GLuint(-1)) { mGLContext->fDisableVertexAttribArray(aTexCoordAttribIndex); } } static const double kFpsWindowMs = 250.0; static const size_t kNumFrameTimeStamps = 16; struct FPSCounter { FPSCounter() : mCurrentFrameIndex(0) {} // We keep a circular buffer of the time points at which the last K // frames were drawn. To estimate FPS, we count the number of // frames we've drawn within the last kFPSWindowMs milliseconds and // divide by the amount time since the first of those frames. TimeStamp mFrames[kNumFrameTimeStamps]; size_t mCurrentFrameIndex; void AddFrame(TimeStamp aNewFrame) { mFrames[mCurrentFrameIndex] = aNewFrame; mCurrentFrameIndex = (mCurrentFrameIndex + 1) % kNumFrameTimeStamps; } double AddFrameAndGetFps(TimeStamp aCurrentFrame) { AddFrame(aCurrentFrame); return EstimateFps(aCurrentFrame); } double GetFpsAt(TimeStamp aNow) { return EstimateFps(aNow); } private: double EstimateFps(TimeStamp aNow) { TimeStamp beginningOfWindow = (aNow - TimeDuration::FromMilliseconds(kFpsWindowMs)); TimeStamp earliestFrameInWindow = aNow; size_t numFramesDrawnInWindow = 0; for (size_t i = 0; i < kNumFrameTimeStamps; ++i) { const TimeStamp& frame = mFrames[i]; if (!frame.IsNull() && frame > beginningOfWindow) { ++numFramesDrawnInWindow; earliestFrameInWindow = std::min(earliestFrameInWindow, frame); } } double realWindowSecs = (aNow - earliestFrameInWindow).ToSeconds(); if (realWindowSecs == 0.0 || numFramesDrawnInWindow == 1) { return 0.0; } return double(numFramesDrawnInWindow - 1) / realWindowSecs; } }; struct FPSState { GLuint mTexture; FPSCounter mCompositionFps; FPSCounter mTransactionFps; FPSState() : mTexture(0) { } void DrawFPS(TimeStamp, GLContext*, ShaderProgramOGL*); static void DrawFrameCounter(GLContext* context); void NotifyShadowTreeTransaction() { mTransactionFps.AddFrame(TimeStamp::Now()); } }; void FPSState::DrawFPS(TimeStamp aNow, GLContext* context, ShaderProgramOGL* copyprog) { int fps = int(mCompositionFps.AddFrameAndGetFps(aNow)); int txnFps = int(mTransactionFps.GetFpsAt(aNow)); GLint viewport[4]; context->fGetIntegerv(LOCAL_GL_VIEWPORT, viewport); if (!mTexture) { // Bind the number of textures we need, in this case one. context->fGenTextures(1, &mTexture); context->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); context->fTexParameteri(LOCAL_GL_TEXTURE_2D,LOCAL_GL_TEXTURE_MIN_FILTER,LOCAL_GL_NEAREST); context->fTexParameteri(LOCAL_GL_TEXTURE_2D,LOCAL_GL_TEXTURE_MAG_FILTER,LOCAL_GL_NEAREST); unsigned char text[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 255, 255, 0, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 0, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 255, 0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 255, 0, 255, 0, 0, 255, 0, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 255, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 255, 0, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; // convert from 8 bit to 32 bit so that don't have to write the text above out in 32 bit format // we rely on int being 32 bits unsigned int* buf = (unsigned int*)malloc(64 * 8 * 4); for (int i = 0; i < 7; i++) { for (int j = 0; j < 41; j++) { unsigned int purple = 0xfff000ff; unsigned int white = 0xffffffff; buf[i * 64 + j] = (text[i * 41 + j] == 0) ? purple : white; } } context->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 64, 8, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, buf); free(buf); } struct Vertex2D { float x,y; }; const Vertex2D vertices[] = { { -1.0f, 1.0f - 42.f / viewport[3] }, { -1.0f, 1.0f}, { -1.0f + 22.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 22.f / viewport[2], 1.0f }, { -1.0f + 22.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 22.f / viewport[2], 1.0f }, { -1.0f + 44.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 44.f / viewport[2], 1.0f }, { -1.0f + 44.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 44.f / viewport[2], 1.0f }, { -1.0f + 66.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 66.f / viewport[2], 1.0f } }; const Vertex2D vertices2[] = { { -1.0f + 80.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 80.f / viewport[2], 1.0f }, { -1.0f + 102.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 102.f / viewport[2], 1.0f }, { -1.0f + 102.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 102.f / viewport[2], 1.0f }, { -1.0f + 124.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 124.f / viewport[2], 1.0f }, { -1.0f + 124.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 124.f / viewport[2], 1.0f }, { -1.0f + 146.f / viewport[2], 1.0f - 42.f / viewport[3] }, { -1.0f + 146.f / viewport[2], 1.0f }, }; int v1 = fps % 10; int v10 = (fps % 100) / 10; int v100 = (fps % 1000) / 100; int txn1 = txnFps % 10; int txn10 = (txnFps % 100) / 10; int txn100 = (txnFps % 1000) / 100; // Feel free to comment these texture coordinates out and use one // of the ones below instead, or play around with your own values. const GLfloat texCoords[] = { (v100 * 4.f) / 64, 7.f / 8, (v100 * 4.f) / 64, 0.0f, (v100 * 4.f + 4) / 64, 7.f / 8, (v100 * 4.f + 4) / 64, 0.0f, (v10 * 4.f) / 64, 7.f / 8, (v10 * 4.f) / 64, 0.0f, (v10 * 4.f + 4) / 64, 7.f / 8, (v10 * 4.f + 4) / 64, 0.0f, (v1 * 4.f) / 64, 7.f / 8, (v1 * 4.f) / 64, 0.0f, (v1 * 4.f + 4) / 64, 7.f / 8, (v1 * 4.f + 4) / 64, 0.0f, }; const GLfloat texCoords2[] = { (txn100 * 4.f) / 64, 7.f / 8, (txn100 * 4.f) / 64, 0.0f, (txn100 * 4.f + 4) / 64, 7.f / 8, (txn100 * 4.f + 4) / 64, 0.0f, (txn10 * 4.f) / 64, 7.f / 8, (txn10 * 4.f) / 64, 0.0f, (txn10 * 4.f + 4) / 64, 7.f / 8, (txn10 * 4.f + 4) / 64, 0.0f, (txn1 * 4.f) / 64, 7.f / 8, (txn1 * 4.f) / 64, 0.0f, (txn1 * 4.f + 4) / 64, 7.f / 8, (txn1 * 4.f + 4) / 64, 0.0f, }; // Turn necessary features on context->fEnable(LOCAL_GL_BLEND); context->fBlendFunc(LOCAL_GL_ONE, LOCAL_GL_SRC_COLOR); context->fActiveTexture(LOCAL_GL_TEXTURE0); context->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); copyprog->Activate(); copyprog->SetTextureUnit(0); // we're going to use client-side vertex arrays for this. context->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); // "COPY" context->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ZERO, LOCAL_GL_ONE, LOCAL_GL_ZERO); // enable our vertex attribs; we'll call glVertexPointer below // to fill with the correct data. GLint vcattr = copyprog->AttribLocation(ShaderProgramOGL::VertexCoordAttrib); GLint tcattr = copyprog->AttribLocation(ShaderProgramOGL::TexCoordAttrib); context->fEnableVertexAttribArray(vcattr); context->fEnableVertexAttribArray(tcattr); context->fVertexAttribPointer(vcattr, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, vertices); context->fVertexAttribPointer(tcattr, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, texCoords); context->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 12); context->fVertexAttribPointer(vcattr, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, vertices2); context->fVertexAttribPointer(tcattr, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, texCoords2); context->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 12); } /** * LayerManagerOGL */ LayerManagerOGL::LayerManagerOGL(nsIWidget *aWidget, int aSurfaceWidth, int aSurfaceHeight, bool aIsRenderingToEGLSurface) : mWidget(aWidget) , mWidgetSize(-1, -1) , mSurfaceSize(aSurfaceWidth, aSurfaceHeight) , mBackBufferFBO(0) , mBackBufferTexture(0) , mBackBufferSize(-1, -1) , mHasBGRA(0) , mIsRenderingToEGLSurface(aIsRenderingToEGLSurface) #ifdef DEBUG , mMaybeInvalidTree(false) #endif { } LayerManagerOGL::~LayerManagerOGL() { Destroy(); } void LayerManagerOGL::Destroy() { if (mDestroyed) return; if (mRoot) { RootLayer()->Destroy(); mRoot = nullptr; } if (!mGLContext) return; nsRefPtr ctx = mGLContext->GetSharedContext(); if (!ctx) { ctx = mGLContext; } ctx->MakeCurrent(); for (uint32_t i = 0; i < mPrograms.Length(); ++i) { for (uint32_t type = MaskNone; type < NumMaskTypes; ++type) { delete mPrograms[i].mVariations[type]; } } mPrograms.Clear(); ctx->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); if (mBackBufferFBO) { ctx->fDeleteFramebuffers(1, &mBackBufferFBO); mBackBufferFBO = 0; } if (mBackBufferTexture) { ctx->fDeleteTextures(1, &mBackBufferTexture); mBackBufferTexture = 0; } if (mQuadVBO) { ctx->fDeleteBuffers(1, &mQuadVBO); mQuadVBO = 0; } mGLContext = nullptr; mDestroyed = true; } already_AddRefed LayerManagerOGL::CreateContext() { nsRefPtr context; #ifdef XP_WIN if (PR_GetEnv("MOZ_LAYERS_PREFER_EGL")) { printf_stderr("Trying GL layers...\n"); context = gl::GLContextProviderEGL::CreateForWindow(mWidget); } #endif if (!context) context = gl::GLContextProvider::CreateForWindow(mWidget); if (!context) { NS_WARNING("Failed to create LayerManagerOGL context"); } return context.forget(); } void LayerManagerOGL::AddPrograms(ShaderProgramType aType) { for (uint32_t maskType = MaskNone; maskType < NumMaskTypes; ++maskType) { if (ProgramProfileOGL::ProgramExists(aType, static_cast(maskType))) { mPrograms[aType].mVariations[maskType] = new ShaderProgramOGL(this->gl(), ProgramProfileOGL::GetProfileFor(aType, static_cast(maskType))); } else { mPrograms[aType].mVariations[maskType] = nullptr; } } } // Impl of a a helper-runnable's "Run" method, used in Initialize() NS_IMETHODIMP LayerManagerOGL::ReadDrawFPSPref::Run() { // NOTE: This must match the code in Initialize()'s NS_IsMainThread check. Preferences::AddBoolVarCache(&sDrawFPS, "layers.acceleration.draw-fps"); Preferences::AddBoolVarCache(&sFrameCounter, "layers.acceleration.frame-counter"); return NS_OK; } bool LayerManagerOGL::Initialize(nsRefPtr aContext, bool force) { ScopedGfxFeatureReporter reporter("GL Layers", force); // Do not allow double initialization NS_ABORT_IF_FALSE(mGLContext == nullptr, "Don't reinitialize layer managers"); #ifdef MOZ_WIDGET_ANDROID if (!aContext) NS_RUNTIMEABORT("We need a context on Android"); #endif if (!aContext) return false; mGLContext = aContext; mGLContext->SetFlipped(true); MakeCurrent(); mHasBGRA = mGLContext->IsExtensionSupported(gl::GLContext::EXT_texture_format_BGRA8888) || mGLContext->IsExtensionSupported(gl::GLContext::EXT_bgra); mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, LOCAL_GL_ONE, LOCAL_GL_ONE); mGLContext->fEnable(LOCAL_GL_BLEND); mPrograms.AppendElements(NumProgramTypes); for (int type = 0; type < NumProgramTypes; ++type) { AddPrograms(static_cast(type)); } // initialise a common shader to check that we can actually compile a shader if (!mPrograms[gl::RGBALayerProgramType].mVariations[MaskNone]->Initialize()) { #ifdef MOZ_WIDGET_ANDROID NS_RUNTIMEABORT("Shader initialization failed"); #endif return false; } mGLContext->fGenFramebuffers(1, &mBackBufferFBO); if (mGLContext->WorkAroundDriverBugs()) { /** * We'll test the ability here to bind NPOT textures to a framebuffer, if * this fails we'll try ARB_texture_rectangle. */ GLenum textureTargets[] = { LOCAL_GL_TEXTURE_2D, LOCAL_GL_NONE }; if (mGLContext->IsGLES2()) { textureTargets[1] = LOCAL_GL_TEXTURE_RECTANGLE_ARB; } mFBOTextureTarget = LOCAL_GL_NONE; for (uint32_t i = 0; i < ArrayLength(textureTargets); i++) { GLenum target = textureTargets[i]; if (!target) continue; mGLContext->fGenTextures(1, &mBackBufferTexture); mGLContext->fBindTexture(target, mBackBufferTexture); mGLContext->fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST); mGLContext->fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST); mGLContext->fTexImage2D(target, 0, LOCAL_GL_RGBA, 5, 3, /* sufficiently NPOT */ 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, NULL); // unbind this texture, in preparation for binding it to the FBO mGLContext->fBindTexture(target, 0); mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mBackBufferFBO); mGLContext->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, target, mBackBufferTexture, 0); if (mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) == LOCAL_GL_FRAMEBUFFER_COMPLETE) { mFBOTextureTarget = target; break; } // We weren't succesful with this texture, so we don't need it // any more. mGLContext->fDeleteTextures(1, &mBackBufferTexture); } if (mFBOTextureTarget == LOCAL_GL_NONE) { /* Unable to find a texture target that works with FBOs and NPOT textures */ #ifdef MOZ_WIDGET_ANDROID NS_RUNTIMEABORT("No texture target"); #endif return false; } } else { // not trying to work around driver bugs, so TEXTURE_2D should just work mFBOTextureTarget = LOCAL_GL_TEXTURE_2D; } // back to default framebuffer, to avoid confusion mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); if (mFBOTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB) { /* If we're using TEXTURE_RECTANGLE, then we must have the ARB * extension -- the EXT variant does not provide support for * texture rectangle access inside GLSL (sampler2DRect, * texture2DRect). */ if (!mGLContext->IsExtensionSupported(gl::GLContext::ARB_texture_rectangle)) #ifdef MOZ_WIDGET_ANDROID NS_RUNTIMEABORT("No texture rectangle"); #endif return false; } // If we're double-buffered, we don't need this fbo anymore. if (mGLContext->IsDoubleBuffered()) { mGLContext->fDeleteFramebuffers(1, &mBackBufferFBO); mBackBufferFBO = 0; } /* Create a simple quad VBO */ mGLContext->fGenBuffers(1, &mQuadVBO); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); GLfloat vertices[] = { /* First quad vertices */ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, /* Then quad texcoords */ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, /* Then flipped quad texcoords */ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER, sizeof(vertices), vertices, LOCAL_GL_STATIC_DRAW); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); nsCOMPtr console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); if (console) { nsString msg; msg += NS_LITERAL_STRING("OpenGL LayerManager Initialized Succesfully.\nVersion: "); msg += NS_ConvertUTF8toUTF16( nsDependentCString((const char*)mGLContext->fGetString(LOCAL_GL_VERSION))); msg += NS_LITERAL_STRING("\nVendor: "); msg += NS_ConvertUTF8toUTF16( nsDependentCString((const char*)mGLContext->fGetString(LOCAL_GL_VENDOR))); msg += NS_LITERAL_STRING("\nRenderer: "); msg += NS_ConvertUTF8toUTF16( nsDependentCString((const char*)mGLContext->fGetString(LOCAL_GL_RENDERER))); msg += NS_LITERAL_STRING("\nFBO Texture Target: "); if (mFBOTextureTarget == LOCAL_GL_TEXTURE_2D) msg += NS_LITERAL_STRING("TEXTURE_2D"); else msg += NS_LITERAL_STRING("TEXTURE_RECTANGLE"); console->LogStringMessage(msg.get()); } if (NS_IsMainThread()) { // NOTE: This must match the code in ReadDrawFPSPref::Run(). Preferences::AddBoolVarCache(&sDrawFPS, "layers.acceleration.draw-fps"); Preferences::AddBoolVarCache(&sFrameCounter, "layers.acceleration.frame-counter"); } else { // We have to dispatch an event to the main thread to read the pref. NS_DispatchToMainThread(new ReadDrawFPSPref()); } mComposer2D = mWidget->GetComposer2D(); reporter.SetSuccessful(); return true; } void LayerManagerOGL::SetClippingRegion(const nsIntRegion& aClippingRegion) { mClippingRegion = aClippingRegion; } void LayerManagerOGL::BeginTransaction() { mInTransaction = true; #ifdef DEBUG mMaybeInvalidTree = false; #endif } void LayerManagerOGL::BeginTransactionWithTarget(gfxContext *aTarget) { mInTransaction = true; #ifdef DEBUG mMaybeInvalidTree = false; #endif #ifdef MOZ_LAYERS_HAVE_LOG MOZ_LAYERS_LOG(("[----- BeginTransaction")); Log(); #endif if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return; } mTarget = aTarget; } bool LayerManagerOGL::EndEmptyTransaction(EndTransactionFlags aFlags) { // NB: this makes the somewhat bogus assumption that pure // compositing txns don't call BeginTransaction(), because that's // the behavior of CompositorParent. MOZ_ASSERT(!mMaybeInvalidTree); mInTransaction = false; if (!mRoot) return false; EndTransaction(nullptr, nullptr, aFlags); return true; } void LayerManagerOGL::EndTransaction(DrawThebesLayerCallback aCallback, void* aCallbackData, EndTransactionFlags aFlags) { mInTransaction = false; #ifdef MOZ_LAYERS_HAVE_LOG MOZ_LAYERS_LOG((" ----- (beginning paint)")); Log(); #endif if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return; } if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) { if (aFlags & END_NO_COMPOSITE) { // Apply pending tree updates before recomputing effective // properties. mRoot->ApplyPendingUpdatesToSubtree(); } // The results of our drawing always go directly into a pixel buffer, // so we don't need to pass any global transform here. mRoot->ComputeEffectiveTransforms(gfx3DMatrix()); mThebesLayerCallback = aCallback; mThebesLayerCallbackData = aCallbackData; SetCompositingDisabled(aFlags & END_NO_COMPOSITE); bool needGLRender = true; if (mComposer2D && mComposer2D->TryRender(mRoot, mWorldMatrix)) { needGLRender = false; if (sDrawFPS) { if (!mFPS) { mFPS = new FPSState(); } double fps = mFPS->mCompositionFps.AddFrameAndGetFps(TimeStamp::Now()); printf_stderr("HWComposer: FPS is %g\n", fps); } // This lets us reftest and screenshot content rendered by the // 2d composer. if (mTarget) { MakeCurrent(); CopyToTarget(mTarget); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); } MOZ_ASSERT(!needGLRender); } if (needGLRender) { Render(); } mThebesLayerCallback = nullptr; mThebesLayerCallbackData = nullptr; } mTarget = NULL; #ifdef MOZ_LAYERS_HAVE_LOG Log(); MOZ_LAYERS_LOG(("]----- EndTransaction")); #endif } already_AddRefed LayerManagerOGL::CreateOptimalMaskSurface(const gfxIntSize &aSize) { return gfxPlatform::GetPlatform()-> CreateOffscreenImageSurface(aSize, gfxASurface::CONTENT_ALPHA); } already_AddRefed LayerManagerOGL::CreateThebesLayer() { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } nsRefPtr layer = new ThebesLayerOGL(this); return layer.forget(); } already_AddRefed LayerManagerOGL::CreateContainerLayer() { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } nsRefPtr layer = new ContainerLayerOGL(this); return layer.forget(); } already_AddRefed LayerManagerOGL::CreateImageLayer() { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } nsRefPtr layer = new ImageLayerOGL(this); return layer.forget(); } already_AddRefed LayerManagerOGL::CreateColorLayer() { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } nsRefPtr layer = new ColorLayerOGL(this); return layer.forget(); } already_AddRefed LayerManagerOGL::CreateCanvasLayer() { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } nsRefPtr layer = new CanvasLayerOGL(this); return layer.forget(); } static LayerOGL* ToLayerOGL(Layer* aLayer) { return static_cast(aLayer->ImplData()); } static void ClearSubtree(Layer* aLayer) { ToLayerOGL(aLayer)->CleanupResources(); for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { ClearSubtree(child); } } void LayerManagerOGL::ClearCachedResources(Layer* aSubtree) { MOZ_ASSERT(!aSubtree || aSubtree->Manager() == this); Layer* subtree = aSubtree ? aSubtree : mRoot.get(); if (!subtree) { return; } ClearSubtree(subtree); #ifdef DEBUG // If this subtree is reachable from the root layer, then it's // possibly onscreen, and the resource clear means that composites // until the next received transaction may draw garbage to the // framebuffer. for(; subtree && subtree != mRoot; subtree = subtree->GetParent()); mMaybeInvalidTree = (subtree == mRoot); #endif // DEBUG } LayerOGL* LayerManagerOGL::RootLayer() const { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return ToLayerOGL(mRoot); } bool LayerManagerOGL::sDrawFPS = false; bool LayerManagerOGL::sFrameCounter = false; static uint16_t sFrameCount = 0; void FPSState::DrawFrameCounter(GLContext* context) { profiler_set_frame_number(sFrameCount); context->fEnable(LOCAL_GL_SCISSOR_TEST); uint16_t frameNumber = sFrameCount; for (size_t i = 0; i < 16; i++) { context->fScissor(3*i, 0, 3, 3); // We should do this using a single draw call // instead of 16 glClear() if ((frameNumber >> i) & 0x1) { context->fClearColor(0.0, 0.0, 0.0, 0.0); } else { context->fClearColor(1.0, 1.0, 1.0, 0.0); } context->fClear(LOCAL_GL_COLOR_BUFFER_BIT); } // We intentionally overflow at 2^16. sFrameCount++; } // |aTexCoordRect| is the rectangle from the texture that we want to // draw using the given program. The program already has a necessary // offset and scale, so the geometry that needs to be drawn is a unit // square from 0,0 to 1,1. // // |aTexSize| is the actual size of the texture, as it can be larger // than the rectangle given by |aTexCoordRect|. void LayerManagerOGL::BindAndDrawQuadWithTextureRect(ShaderProgramOGL *aProg, const nsIntRect& aTexCoordRect, const nsIntSize& aTexSize, GLenum aWrapMode /* = LOCAL_GL_REPEAT */, bool aFlipped /* = false */) { NS_ASSERTION(aProg->HasInitialized(), "Shader program not correctly initialized"); GLuint vertAttribIndex = aProg->AttribLocation(ShaderProgramOGL::VertexCoordAttrib); GLuint texCoordAttribIndex = aProg->AttribLocation(ShaderProgramOGL::TexCoordAttrib); NS_ASSERTION(texCoordAttribIndex != GLuint(-1), "no texture coords?"); // clear any bound VBO so that glVertexAttribPointer() goes back to // "pointer mode" mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); // Given what we know about these textures and coordinates, we can // compute fmod(t, 1.0f) to get the same texture coordinate out. If // the texCoordRect dimension is < 0 or > width/height, then we have // wraparound that we need to deal with by drawing multiple quads, // because we can't rely on full non-power-of-two texture support // (which is required for the REPEAT wrap mode). GLContext::RectTriangles rects; nsIntSize realTexSize = aTexSize; if (!mGLContext->CanUploadNonPowerOfTwo()) { realTexSize = nsIntSize(NextPowerOfTwo(aTexSize.width), NextPowerOfTwo(aTexSize.height)); } if (aWrapMode == LOCAL_GL_REPEAT) { rects.addRect(/* dest rectangle */ 0.0f, 0.0f, 1.0f, 1.0f, /* tex coords */ aTexCoordRect.x / GLfloat(realTexSize.width), aTexCoordRect.y / GLfloat(realTexSize.height), aTexCoordRect.XMost() / GLfloat(realTexSize.width), aTexCoordRect.YMost() / GLfloat(realTexSize.height), aFlipped); } else { GLContext::DecomposeIntoNoRepeatTriangles(aTexCoordRect, realTexSize, rects, aFlipped); } mGLContext->fVertexAttribPointer(vertAttribIndex, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, rects.vertexPointer()); mGLContext->fVertexAttribPointer(texCoordAttribIndex, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, rects.texCoordPointer()); { mGLContext->fEnableVertexAttribArray(texCoordAttribIndex); { mGLContext->fEnableVertexAttribArray(vertAttribIndex); mGLContext->fDrawArrays(LOCAL_GL_TRIANGLES, 0, rects.elements()); mGLContext->fDisableVertexAttribArray(vertAttribIndex); } mGLContext->fDisableVertexAttribArray(texCoordAttribIndex); } } void LayerManagerOGL::NotifyShadowTreeTransaction() { if (mFPS) { mFPS->NotifyShadowTreeTransaction(); } } void LayerManagerOGL::Render() { PROFILER_LABEL("LayerManagerOGL", "Render"); if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return; } nsIntRect rect; if (mIsRenderingToEGLSurface) { rect = nsIntRect(0, 0, mSurfaceSize.width, mSurfaceSize.height); } else { rect = mRenderBounds; // If render bounds is not updated explicitly, try to infer it from widget if (rect.width == 0 || rect.height == 0) { // FIXME/bug XXXXXX this races with rotation changes on the main // thread, and undoes all the care we take with layers txns being // sent atomically with rotation changes mWidget->GetClientBounds(rect); } } WorldTransformRect(rect); GLint width = rect.width; GLint height = rect.height; // We can't draw anything to something with no area // so just return if (width == 0 || height == 0) return; // If the widget size changed, we have to force a MakeCurrent // to make sure that GL sees the updated widget size. if (mWidgetSize.width != width || mWidgetSize.height != height) { MakeCurrent(true); mWidgetSize.width = width; mWidgetSize.height = height; } else { MakeCurrent(); } #if MOZ_WIDGET_ANDROID TexturePoolOGL::Fill(gl()); #endif SetupBackBuffer(width, height); SetupPipeline(width, height, ApplyWorldTransform); // Default blend function implements "OVER" mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, LOCAL_GL_ONE, LOCAL_GL_ONE); mGLContext->fEnable(LOCAL_GL_BLEND); const nsIntRect *clipRect = mRoot->GetClipRect(); if (clipRect) { nsIntRect r = *clipRect; WorldTransformRect(r); mGLContext->fScissor(r.x, r.y, r.width, r.height); } else { mGLContext->fScissor(0, 0, width, height); } if (CompositingDisabled()) { RootLayer()->RenderLayer(mGLContext->IsDoubleBuffered() ? 0 : mBackBufferFBO, nsIntPoint(0, 0)); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); return; } mGLContext->fEnable(LOCAL_GL_SCISSOR_TEST); // If the Android compositor is being used, this clear will be done in // DrawWindowUnderlay. Make sure the bits used here match up with those used // in mobile/android/base/gfx/LayerRenderer.java #ifndef MOZ_ANDROID_OMTC mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0); mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); #endif // Allow widget to render a custom background. mWidget->DrawWindowUnderlay(this, rect); // Reset some state that might of been clobbered by the underlay. mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, LOCAL_GL_ONE, LOCAL_GL_ONE); // Render our layers. RootLayer()->RenderLayer(mGLContext->IsDoubleBuffered() ? 0 : mBackBufferFBO, nsIntPoint(0, 0)); // Allow widget to render a custom foreground too. mWidget->DrawWindowOverlay(this, rect); #ifdef MOZ_DUMP_PAINTING if (gfxUtils::sDumpPainting) { nsIntRect rect; if (mIsRenderingToEGLSurface) { rect = nsIntRect(0, 0, mSurfaceSize.width, mSurfaceSize.height); } else { mWidget->GetClientBounds(rect); } nsRefPtr surf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(rect.Size(), gfxASurface::CONTENT_COLOR_ALPHA); nsRefPtr ctx = new gfxContext(surf); CopyToTarget(ctx); WriteSnapshotToDumpFile(this, surf); } #endif if (mTarget) { CopyToTarget(mTarget); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); return; } if (sDrawFPS && !mFPS) { mFPS = new FPSState(); } else if (!sDrawFPS && mFPS) { mFPS = nullptr; } if (mFPS) { mFPS->DrawFPS(TimeStamp::Now(), mGLContext, GetProgram(Copy2DProgramType)); } else if (sFrameCounter) { FPSState::DrawFrameCounter(mGLContext); } if (mGLContext->IsDoubleBuffered()) { mGLContext->SwapBuffers(); LayerManager::PostPresent(); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); return; } mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); ShaderProgramOGL *copyprog = GetProgram(Copy2DProgramType); if (mFBOTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB) { copyprog = GetProgram(Copy2DRectProgramType); } mGLContext->fBindTexture(mFBOTextureTarget, mBackBufferTexture); copyprog->Activate(); copyprog->SetTextureUnit(0); if (copyprog->GetTexCoordMultiplierUniformLocation() != -1) { copyprog->SetTexCoordMultiplier(width, height); } // we're going to use client-side vertex arrays for this. mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); // "COPY" mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ZERO, LOCAL_GL_ONE, LOCAL_GL_ZERO); // enable our vertex attribs; we'll call glVertexPointer below // to fill with the correct data. GLint vcattr = copyprog->AttribLocation(ShaderProgramOGL::VertexCoordAttrib); GLint tcattr = copyprog->AttribLocation(ShaderProgramOGL::TexCoordAttrib); mGLContext->fEnableVertexAttribArray(vcattr); mGLContext->fEnableVertexAttribArray(tcattr); const nsIntRect *r; nsIntRegionRectIterator iter(mClippingRegion); while ((r = iter.Next()) != nullptr) { nsIntRect cRect = *r; r = &cRect; WorldTransformRect(cRect); float left = (GLfloat)r->x / width; float right = (GLfloat)r->XMost() / width; float top = (GLfloat)r->y / height; float bottom = (GLfloat)r->YMost() / height; float vertices[] = { left * 2.0f - 1.0f, -(top * 2.0f - 1.0f), right * 2.0f - 1.0f, -(top * 2.0f - 1.0f), left * 2.0f - 1.0f, -(bottom * 2.0f - 1.0f), right * 2.0f - 1.0f, -(bottom * 2.0f - 1.0f) }; // Use inverted texture coordinates since our projection matrix also has a // flip and we need to cancel that out. float coords[] = { left, 1 - top, right, 1 - top, left, 1 - bottom, right, 1 - bottom }; mGLContext->fVertexAttribPointer(vcattr, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, vertices); mGLContext->fVertexAttribPointer(tcattr, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, coords); mGLContext->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); } mGLContext->fDisableVertexAttribArray(vcattr); mGLContext->fDisableVertexAttribArray(tcattr); mGLContext->fFlush(); mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); } void LayerManagerOGL::SetWorldTransform(const gfxMatrix& aMatrix) { NS_ASSERTION(aMatrix.PreservesAxisAlignedRectangles(), "SetWorldTransform only accepts matrices that satisfy PreservesAxisAlignedRectangles"); NS_ASSERTION(!aMatrix.HasNonIntegerScale(), "SetWorldTransform only accepts matrices with integer scale"); mWorldMatrix = aMatrix; } gfxMatrix& LayerManagerOGL::GetWorldTransform(void) { return mWorldMatrix; } void LayerManagerOGL::WorldTransformRect(nsIntRect& aRect) { gfxRect grect(aRect.x, aRect.y, aRect.width, aRect.height); grect = mWorldMatrix.TransformBounds(grect); aRect.SetRect(grect.X(), grect.Y(), grect.Width(), grect.Height()); } void LayerManagerOGL::UpdateRenderBounds(const nsIntRect& aRect) { mRenderBounds = aRect; } void LayerManagerOGL::SetSurfaceSize(int width, int height) { mSurfaceSize.width = width; mSurfaceSize.height = height; } void LayerManagerOGL::SetupPipeline(int aWidth, int aHeight, WorldTransforPolicy aTransformPolicy) { // Set the viewport correctly. mGLContext->fViewport(0, 0, aWidth, aHeight); // We flip the view matrix around so that everything is right-side up; we're // drawing directly into the window's back buffer, so this keeps things // looking correct. // // XXX: We keep track of whether the window size changed, so we could skip // this update if it hadn't changed since the last call. We will need to // track changes to aTransformPolicy and mWorldMatrix for this to work // though. // Matrix to transform (0, 0, aWidth, aHeight) to viewport space (-1.0, 1.0, // 2, 2) and flip the contents. gfxMatrix viewMatrix; viewMatrix.Translate(-gfxPoint(1.0, -1.0)); viewMatrix.Scale(2.0f / float(aWidth), 2.0f / float(aHeight)); viewMatrix.Scale(1.0f, -1.0f); if (aTransformPolicy == ApplyWorldTransform) { viewMatrix = mWorldMatrix * viewMatrix; } gfx3DMatrix matrix3d = gfx3DMatrix::From2D(viewMatrix); matrix3d._33 = 0.0f; SetLayerProgramProjectionMatrix(matrix3d); } void LayerManagerOGL::SetupBackBuffer(int aWidth, int aHeight) { if (mGLContext->IsDoubleBuffered()) { mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); return; } // Do we have a FBO of the right size already? if (mBackBufferSize.width == aWidth && mBackBufferSize.height == aHeight) { mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mBackBufferFBO); return; } // we already have a FBO, but we need to resize its texture. mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); mGLContext->fBindTexture(mFBOTextureTarget, mBackBufferTexture); mGLContext->fTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, aWidth, aHeight, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, NULL); mGLContext->fBindTexture(mFBOTextureTarget, 0); mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mBackBufferFBO); mGLContext->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, mFBOTextureTarget, mBackBufferTexture, 0); GLenum result = mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { nsAutoCString msg; msg.Append("Framebuffer not complete -- error 0x"); msg.AppendInt(result, 16); NS_RUNTIMEABORT(msg.get()); } mBackBufferSize.width = aWidth; mBackBufferSize.height = aHeight; } void LayerManagerOGL::CopyToTarget(gfxContext *aTarget) { nsIntRect rect; if (mIsRenderingToEGLSurface) { rect = nsIntRect(0, 0, mSurfaceSize.width, mSurfaceSize.height); } else { rect = nsIntRect(0, 0, mWidgetSize.width, mWidgetSize.height); } GLint width = rect.width; GLint height = rect.height; if ((int64_t(width) * int64_t(height) * int64_t(4)) > INT32_MAX) { NS_ERROR("Widget size too big - integer overflow!"); return; } nsRefPtr imageSurface = new gfxImageSurface(gfxIntSize(width, height), gfxASurface::ImageFormatARGB32); mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLContext->IsDoubleBuffered() ? 0 : mBackBufferFBO); if (!mGLContext->IsGLES2()) { // GLES2 promises that binding to any custom FBO will attach // to GL_COLOR_ATTACHMENT0 attachment point. if (mGLContext->IsDoubleBuffered()) { mGLContext->fReadBuffer(LOCAL_GL_BACK); } else { mGLContext->fReadBuffer(LOCAL_GL_COLOR_ATTACHMENT0); } } NS_ASSERTION(imageSurface->Stride() == width * 4, "Image Surfaces being created with weird stride!"); mGLContext->ReadPixelsIntoImageSurface(imageSurface); // Map from GL space to Cairo space and reverse the world transform. gfxMatrix glToCairoTransform = mWorldMatrix; glToCairoTransform.Invert(); glToCairoTransform.Scale(1.0, -1.0); glToCairoTransform.Translate(-gfxPoint(0.0, height)); gfxContextAutoSaveRestore restore(aTarget); aTarget->SetOperator(gfxContext::OPERATOR_SOURCE); aTarget->SetMatrix(glToCairoTransform); aTarget->SetSource(imageSurface); aTarget->Paint(); } void LayerManagerOGL::SetLayerProgramProjectionMatrix(const gfx3DMatrix& aMatrix) { for (unsigned int i = 0; i < mPrograms.Length(); ++i) { for (uint32_t mask = MaskNone; mask < NumMaskTypes; ++mask) { if (mPrograms[i].mVariations[mask]) { mPrograms[i].mVariations[mask]->CheckAndSetProjectionMatrix(aMatrix); } } } } static GLenum GetFrameBufferInternalFormat(GLContext* gl, GLuint aCurrentFrameBuffer, nsIWidget* aWidget) { if (aCurrentFrameBuffer == 0) { // default framebuffer return aWidget->GetGLFrameBufferFormat(); } return LOCAL_GL_RGBA; } void LayerManagerOGL::CreateFBOWithTexture(const nsIntRect& aRect, InitMode aInit, GLuint aCurrentFrameBuffer, GLuint *aFBO, GLuint *aTexture) { GLuint tex, fbo; mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); mGLContext->fGenTextures(1, &tex); mGLContext->fBindTexture(mFBOTextureTarget, tex); if (aInit == InitModeCopy) { // We're going to create an RGBA temporary fbo. But to // CopyTexImage() from the current framebuffer, the framebuffer's // format has to be compatible with the new texture's. So we // check the format of the framebuffer here and take a slow path // if it's incompatible. GLenum format = GetFrameBufferInternalFormat(gl(), aCurrentFrameBuffer, mWidget); bool isFormatCompatibleWithRGBA = gl()->IsGLES2() ? (format == LOCAL_GL_RGBA) : true; if (isFormatCompatibleWithRGBA) { mGLContext->fCopyTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, aRect.x, aRect.y, aRect.width, aRect.height, 0); } else { // Curses, incompatible formats. Take a slow path. // RGBA size_t bufferSize = aRect.width * aRect.height * 4; nsAutoArrayPtr buf(new uint8_t[bufferSize]); mGLContext->fReadPixels(aRect.x, aRect.y, aRect.width, aRect.height, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, buf); mGLContext->fTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, aRect.width, aRect.height, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, buf); } } else { mGLContext->fTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, aRect.width, aRect.height, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, NULL); } mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR); mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR); mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE); mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE); mGLContext->fBindTexture(mFBOTextureTarget, 0); mGLContext->fGenFramebuffers(1, &fbo); mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fbo); mGLContext->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, mFBOTextureTarget, tex, 0); // Making this call to fCheckFramebufferStatus prevents a crash on // PowerVR. See bug 695246. GLenum result = mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { nsAutoCString msg; msg.Append("Framebuffer not complete -- error 0x"); msg.AppendInt(result, 16); msg.Append(", mFBOTextureTarget 0x"); msg.AppendInt(mFBOTextureTarget, 16); msg.Append(", aRect.width "); msg.AppendInt(aRect.width); msg.Append(", aRect.height "); msg.AppendInt(aRect.height); NS_RUNTIMEABORT(msg.get()); } SetupPipeline(aRect.width, aRect.height, DontApplyWorldTransform); mGLContext->fScissor(0, 0, aRect.width, aRect.height); if (aInit == InitModeClear) { mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0); mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT); } *aFBO = fbo; *aTexture = tex; } already_AddRefed LayerManagerOGL::CreateShadowThebesLayer() { if (LayerManagerOGL::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } #ifdef FORCE_BASICTILEDTHEBESLAYER return nsRefPtr(new TiledThebesLayerOGL(this)).forget(); #else return nsRefPtr(new ShadowThebesLayerOGL(this)).forget(); #endif } already_AddRefed LayerManagerOGL::CreateShadowContainerLayer() { if (LayerManagerOGL::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return nsRefPtr(new ShadowContainerLayerOGL(this)).forget(); } already_AddRefed LayerManagerOGL::CreateShadowImageLayer() { if (LayerManagerOGL::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return nsRefPtr(new ShadowImageLayerOGL(this)).forget(); } already_AddRefed LayerManagerOGL::CreateShadowColorLayer() { if (LayerManagerOGL::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return nsRefPtr(new ShadowColorLayerOGL(this)).forget(); } already_AddRefed LayerManagerOGL::CreateShadowCanvasLayer() { if (LayerManagerOGL::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return nsRefPtr(new ShadowCanvasLayerOGL(this)).forget(); } already_AddRefed LayerManagerOGL::CreateShadowRefLayer() { if (LayerManagerOGL::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return nsRefPtr(new ShadowRefLayerOGL(this)).forget(); } TemporaryRef LayerManagerOGL::CreateDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) { #ifdef XP_MACOSX // We don't want to accelerate if the surface is too small which indicates // that it's likely used for an icon/static image. We also don't want to // accelerate anything that is above the maximum texture size of weakest gpu. // Safari uses 5000 area as the minimum for acceleration, we decided 64^2 is more logical. bool useAcceleration = aSize.width <= 4096 && aSize.height <= 4096 && aSize.width > 64 && aSize.height > 64 && gfxPlatformMac::GetPlatform()->UseAcceleratedCanvas(); if (useAcceleration) { return Factory::CreateDrawTarget(BACKEND_COREGRAPHICS_ACCELERATED, aSize, aFormat); } #endif return LayerManager::CreateDrawTarget(aSize, aFormat); } static void SubtractTransformedRegion(nsIntRegion& aRegion, const nsIntRegion& aRegionToSubtract, const gfx3DMatrix& aTransform) { if (aRegionToSubtract.IsEmpty()) { return; } // For each rect in the region, find out its bounds in screen space and // subtract it from the screen region. nsIntRegionRectIterator it(aRegionToSubtract); while (const nsIntRect* rect = it.Next()) { gfxRect incompleteRect = aTransform.TransformBounds(gfxRect(*rect)); aRegion.Sub(aRegion, nsIntRect(incompleteRect.x, incompleteRect.y, incompleteRect.width, incompleteRect.height)); } } /* static */ void LayerManagerOGL::ComputeRenderIntegrityInternal(Layer* aLayer, nsIntRegion& aScreenRegion, nsIntRegion& aLowPrecisionScreenRegion, const gfx3DMatrix& aTransform) { if (aLayer->GetOpacity() <= 0.f || (aScreenRegion.IsEmpty() && aLowPrecisionScreenRegion.IsEmpty())) { return; } // If the layer's a container, recurse into all of its children ContainerLayer* container = aLayer->AsContainerLayer(); if (container) { // Accumulate the transform of intermediate surfaces gfx3DMatrix transform = aTransform; if (container->UseIntermediateSurface()) { transform = aLayer->GetEffectiveTransform(); transform.PreMultiply(aTransform); } for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { ComputeRenderIntegrityInternal(child, aScreenRegion, aLowPrecisionScreenRegion, transform); } return; } // Only thebes layers can be incomplete ThebesLayer* thebesLayer = aLayer->AsThebesLayer(); if (!thebesLayer) { return; } // See if there's any incomplete rendering nsIntRegion incompleteRegion = aLayer->GetEffectiveVisibleRegion(); incompleteRegion.Sub(incompleteRegion, thebesLayer->GetValidRegion()); if (!incompleteRegion.IsEmpty()) { // Calculate the transform to get between screen and layer space gfx3DMatrix transformToScreen = aLayer->GetEffectiveTransform(); transformToScreen.PreMultiply(aTransform); SubtractTransformedRegion(aScreenRegion, incompleteRegion, transformToScreen); // See if there's any incomplete low-precision rendering TiledLayerComposer* composer = nullptr; ShadowLayer* shadow = aLayer->AsShadowLayer(); if (shadow) { composer = shadow->AsTiledLayerComposer(); if (composer) { incompleteRegion.Sub(incompleteRegion, composer->GetValidLowPrecisionRegion()); if (!incompleteRegion.IsEmpty()) { SubtractTransformedRegion(aLowPrecisionScreenRegion, incompleteRegion, transformToScreen); } } } // If we can't get a valid low precision region, assume it's the same as // the high precision region. if (!composer) { SubtractTransformedRegion(aLowPrecisionScreenRegion, incompleteRegion, transformToScreen); } } } static int GetRegionArea(const nsIntRegion& aRegion) { int area = 0; nsIntRegionRectIterator it(aRegion); while (const nsIntRect* rect = it.Next()) { area += rect->width * rect->height; } return area; } #ifdef MOZ_ANDROID_OMTC static float GetDisplayportCoverage(const gfx::Rect& aDisplayPort, const gfx3DMatrix& aTransformToScreen, const nsIntRect& aScreenRect) { gfxRect transformedDisplayport = aTransformToScreen.TransformBounds(gfxRect(aDisplayPort.x, aDisplayPort.y, aDisplayPort.width, aDisplayPort.height)); transformedDisplayport.RoundOut(); nsIntRect displayport = nsIntRect(transformedDisplayport.x, transformedDisplayport.y, transformedDisplayport.width, transformedDisplayport.height); if (!displayport.Contains(aScreenRect)) { nsIntRegion coveredRegion; coveredRegion.And(aScreenRect, displayport); return GetRegionArea(coveredRegion) / (float)(aScreenRect.width * aScreenRect.height); } return 1.0f; } #endif // MOZ_ANDROID_OMTC float LayerManagerOGL::ComputeRenderIntegrity() { // We only ever have incomplete rendering when progressive tiles are enabled. Layer* root = GetRoot(); if (!gfxPlatform::UseProgressiveTilePainting() || !root) { return 1.f; } const FrameMetrics& rootMetrics = root->AsContainerLayer()->GetFrameMetrics(); nsIntRect screenRect(rootMetrics.mCompositionBounds.x, rootMetrics.mCompositionBounds.y, rootMetrics.mCompositionBounds.width, rootMetrics.mCompositionBounds.height); float lowPrecisionMultiplier = 1.0f; float highPrecisionMultiplier = 1.0f; #ifdef MOZ_ANDROID_OMTC // Use the transform on the primary scrollable layer and its FrameMetrics // to find out how much of the viewport the current displayport covers Layer* primaryScrollable = GetPrimaryScrollableLayer(); if (primaryScrollable) { // This is derived from the code in // gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree. const gfx3DMatrix& rootTransform = root->GetTransform(); float devPixelRatioX = 1 / rootTransform.GetXScale(); float devPixelRatioY = 1 / rootTransform.GetYScale(); gfx3DMatrix transform = primaryScrollable->GetEffectiveTransform(); transform.ScalePost(devPixelRatioX, devPixelRatioY, 1); const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics(); // Clip the screen rect to the document bounds gfxRect documentBounds = transform.TransformBounds(gfxRect(metrics.mScrollableRect.x - metrics.mScrollOffset.x, metrics.mScrollableRect.y - metrics.mScrollOffset.y, metrics.mScrollableRect.width, metrics.mScrollableRect.height)); documentBounds.RoundOut(); screenRect = screenRect.Intersect(nsIntRect(documentBounds.x, documentBounds.y, documentBounds.width, documentBounds.height)); // If the screen rect is empty, the user has scrolled entirely into // over-scroll and so we can be considered to have full integrity. if (screenRect.IsEmpty()) { return 1.0f; } // Work out how much of the critical display-port covers the screen bool hasLowPrecision = false; if (!metrics.mCriticalDisplayPort.IsEmpty()) { hasLowPrecision = true; highPrecisionMultiplier = GetDisplayportCoverage(metrics.mCriticalDisplayPort, transform, screenRect); } // Work out how much of the display-port covers the screen if (!metrics.mDisplayPort.IsEmpty()) { if (hasLowPrecision) { lowPrecisionMultiplier = GetDisplayportCoverage(metrics.mDisplayPort, transform, screenRect); } else { lowPrecisionMultiplier = highPrecisionMultiplier = GetDisplayportCoverage(metrics.mDisplayPort, transform, screenRect); } } } // If none of the screen is covered, we have zero integrity. if (highPrecisionMultiplier <= 0.0f && lowPrecisionMultiplier <= 0.0f) { return 0.0f; } #endif // MOZ_ANDROID_OMTC nsIntRegion screenRegion(screenRect); nsIntRegion lowPrecisionScreenRegion(screenRect); gfx3DMatrix transform; ComputeRenderIntegrityInternal(root, screenRegion, lowPrecisionScreenRegion, transform); if (!screenRegion.IsEqual(screenRect)) { // Calculate the area of the region. All rects in an nsRegion are // non-overlapping. float screenArea = screenRect.width * screenRect.height; float highPrecisionIntegrity = GetRegionArea(screenRegion) / screenArea; float lowPrecisionIntegrity = 1.f; if (!lowPrecisionScreenRegion.IsEqual(screenRect)) { lowPrecisionIntegrity = GetRegionArea(lowPrecisionScreenRegion) / screenArea; } return ((highPrecisionIntegrity * highPrecisionMultiplier) + (lowPrecisionIntegrity * lowPrecisionMultiplier)) / 2; } return 1.f; } } /* layers */ } /* mozilla */