Bug 1329815 - GeneratePerfWarning and warn on completed-FB invalidation. - r=kvark draft
authorJeff Gilbert <jgilbert@mozilla.com>
Wed, 11 Jan 2017 17:10:48 -0800
changeset 460910 d1cb69855a9fdbd10d824fda661d611b31cf2750
parent 458733 20094311ab5ec56d5be7afc6d5c2e2b09e656fb2
child 542169 20e260ab6df7bc6bf78a63247032dc3548fa3c49
push id41524
push userbmo:jgilbert@mozilla.com
push dateSat, 14 Jan 2017 01:59:22 +0000
reviewerskvark
bugs1329815
milestone53.0a1
Bug 1329815 - GeneratePerfWarning and warn on completed-FB invalidation. - r=kvark MozReview-Commit-ID: C9J3qdnsaZF
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLContextUtils.cpp
dom/canvas/WebGLFramebuffer.cpp
dom/canvas/WebGLFramebuffer.h
dom/canvas/WebGLFramebufferAttachable.cpp
dom/canvas/WebGLFramebufferAttachable.h
dom/canvas/WebGLRenderbuffer.cpp
dom/canvas/WebGLTexture.cpp
dom/canvas/WebGLTexture.h
dom/canvas/WebGLTextureUpload.cpp
gfx/thebes/gfxPrefs.h
modules/libpref/init/all.js
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -109,16 +109,19 @@ WebGLContextOptions::WebGLContextOptions
 }
 
 
 /*static*/ const uint32_t WebGLContext::kMinMaxColorAttachments = 4;
 /*static*/ const uint32_t WebGLContext::kMinMaxDrawBuffers = 4;
 
 WebGLContext::WebGLContext()
     : WebGLContextUnchecked(nullptr)
+    , mMaxPerfWarnings(gfxPrefs::WebGLMaxPerfWarnings())
+    , mNumPerfWarnings(0)
+    , mMaxAcceptableFBStatusInvals(gfxPrefs::WebGLMaxAcceptableFBStatusInvals())
     , mBufferFetchingIsVerified(false)
     , mBufferFetchingHasPerVertex(false)
     , mMaxFetchedVertices(0)
     , mMaxFetchedInstances(0)
     , mLayerIsMirror(false)
     , mBypassShaderValidation(false)
     , mEmptyTFO(0)
     , mContextLossHandler(this)
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -326,16 +326,20 @@ class WebGLContext
         BROWSER_DEFAULT_WEBGL = 0x9244,
         UNMASKED_VENDOR_WEBGL = 0x9245,
         UNMASKED_RENDERER_WEBGL = 0x9246
     };
 
     static const uint32_t kMinMaxColorAttachments;
     static const uint32_t kMinMaxDrawBuffers;
 
+    const uint32_t mMaxPerfWarnings;
+    mutable uint64_t mNumPerfWarnings;
+    const uint32_t mMaxAcceptableFBStatusInvals;
+
 public:
     WebGLContext();
 
 protected:
     virtual ~WebGLContext();
 
 public:
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -1930,16 +1934,20 @@ protected:
     int mDrawCallsSinceLastFlush;
 
     int mAlreadyGeneratedWarnings;
     int mMaxWarnings;
     bool mAlreadyWarnedAboutFakeVertexAttrib0;
 
     bool ShouldGenerateWarnings() const;
 
+    bool ShouldGeneratePerfWarnings() const {
+        return mNumPerfWarnings < mMaxPerfWarnings;
+    }
+
     uint64_t mLastUseIndex;
 
     bool mNeedsFakeNoAlpha;
     bool mNeedsFakeNoDepth;
     bool mNeedsFakeNoStencil;
     bool mNeedsEmulatedLoneDepthStencil;
 
     const bool mAllowFBInvalidation;
@@ -2022,16 +2030,18 @@ protected:
     ForceDiscreteGPUHelperCGL mForceDiscreteGPUHelper;
 #endif
 
 public:
     // console logging helpers
     void GenerateWarning(const char* fmt, ...);
     void GenerateWarning(const char* fmt, va_list ap);
 
+    void GeneratePerfWarning(const char* fmt, ...) const;
+
 public:
     UniquePtr<webgl::FormatUsageAuthority> mFormatUsage;
 
     virtual UniquePtr<webgl::FormatUsageAuthority>
     CreateFormatUsage(gl::GLContext* gl) const = 0;
 
 
     const decltype(mBound2DTextures)* TexListForElemType(GLenum elemType) const;
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -343,44 +343,46 @@ WebGLContext::DeleteFramebuffer(WebGLFra
         BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
                         static_cast<WebGLFramebuffer*>(nullptr));
     }
 }
 
 void
 WebGLContext::DeleteRenderbuffer(WebGLRenderbuffer* rbuf)
 {
-    if (!ValidateDeleteObject("deleteRenderbuffer", rbuf))
+    const char funcName[] = "deleteRenderbuffer";
+    if (!ValidateDeleteObject(funcName, rbuf))
         return;
 
     if (mBoundDrawFramebuffer)
-        mBoundDrawFramebuffer->DetachRenderbuffer(rbuf);
+        mBoundDrawFramebuffer->DetachRenderbuffer(funcName, rbuf);
 
     if (mBoundReadFramebuffer)
-        mBoundReadFramebuffer->DetachRenderbuffer(rbuf);
-
-    rbuf->InvalidateStatusOfAttachedFBs();
+        mBoundReadFramebuffer->DetachRenderbuffer(funcName, rbuf);
+
+    rbuf->InvalidateStatusOfAttachedFBs(funcName);
 
     if (mBoundRenderbuffer == rbuf)
         BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
 
     rbuf->RequestDelete();
 }
 
 void
 WebGLContext::DeleteTexture(WebGLTexture* tex)
 {
-    if (!ValidateDeleteObject("deleteTexture", tex))
+    const char funcName[] = "deleteTexture";
+    if (!ValidateDeleteObject(funcName, tex))
         return;
 
     if (mBoundDrawFramebuffer)
-        mBoundDrawFramebuffer->DetachTexture(tex);
+        mBoundDrawFramebuffer->DetachTexture(funcName, tex);
 
     if (mBoundReadFramebuffer)
-        mBoundReadFramebuffer->DetachTexture(tex);
+        mBoundReadFramebuffer->DetachTexture(funcName, tex);
 
     GLuint activeTexture = mActiveTexture;
     for (int32_t i = 0; i < mGLMaxTextureUnits; i++) {
         if (mBound2DTextures[i] == tex ||
             mBoundCubeMapTextures[i] == tex ||
             mBound3DTextures[i] == tex ||
             mBound2DArrayTextures[i] == tex)
         {
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -85,17 +85,17 @@ WebGLContext::GenerateWarning(const char
     }
 
     dom::AutoJSAPI api;
     if (!api.Init(mCanvasElement->OwnerDoc()->GetScopeObject())) {
         return;
     }
 
     JSContext* cx = api.cx();
-    JS_ReportWarningASCII(cx, "WebGL: %s", buf);
+    JS_ReportWarningASCII(cx, "WebGL warning: %s", buf);
     if (!ShouldGenerateWarnings()) {
         JS_ReportWarningASCII(cx,
                               "WebGL: No further warnings will be reported for"
                               " this WebGL context."
                               " (already reported %d warnings)",
                               mAlreadyGeneratedWarnings);
     }
 }
@@ -105,16 +105,53 @@ WebGLContext::ShouldGenerateWarnings() c
 {
     if (mMaxWarnings == -1)
         return true;
 
     return mAlreadyGeneratedWarnings < mMaxWarnings;
 }
 
 void
+WebGLContext::GeneratePerfWarning(const char* fmt, ...) const
+{
+    if (!ShouldGeneratePerfWarnings())
+        return;
+
+    if (!mCanvasElement)
+        return;
+
+    dom::AutoJSAPI api;
+    if (!api.Init(mCanvasElement->OwnerDoc()->GetScopeObject()))
+        return;
+    JSContext* cx = api.cx();
+
+    ////
+
+    va_list ap;
+    va_start(ap, fmt);
+
+    char buf[1024];
+    PR_vsnprintf(buf, 1024, fmt, ap);
+
+    va_end(ap);
+
+    ////
+
+    JS_ReportWarningASCII(cx, "WebGL perf warning: %s", buf);
+    mNumPerfWarnings++;
+
+    if (!ShouldGeneratePerfWarnings()) {
+        JS_ReportWarningASCII(cx,
+                              "WebGL: After reporting %u, no further perf warnings will"
+                              " be reported for this WebGL context.",
+                              uint32_t(mNumPerfWarnings));
+    }
+}
+
+void
 WebGLContext::SynthesizeGLError(GLenum err)
 {
     /* ES2 section 2.5 "GL Errors" states that implementations can have
      * multiple 'flags', as errors might be caught in different parts of
      * a distributed implementation.
      * We're signing up as a distributed implementation here, with
      * separate flags for WebGL and the underlying GLContext.
      */
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -42,17 +42,18 @@ WebGLFBAttachPoint::~WebGLFBAttachPoint(
     MOZ_ASSERT(mFB, "Should have been Init'd.");
     MOZ_ASSERT(!mRenderbufferPtr);
     MOZ_ASSERT(!mTexturePtr);
 }
 
 void
 WebGLFBAttachPoint::Unlink()
 {
-    Clear();
+    const char funcName[] = "WebGLFramebuffer::GC";
+    Clear(funcName);
 }
 
 bool
 WebGLFBAttachPoint::IsDeleteRequested() const
 {
     return Texture() ? Texture()->IsDeleteRequested()
          : Renderbuffer() ? Renderbuffer()->IsDeleteRequested()
          : false;
@@ -109,51 +110,51 @@ WebGLFBAttachPoint::IsReadableFloat() co
     auto format = formatUsage->format;
     if (!format->IsColorFormat())
         return false;
 
     return format->componentType == webgl::ComponentType::Float;
 }
 
 void
-WebGLFBAttachPoint::Clear()
+WebGLFBAttachPoint::Clear(const char* funcName)
 {
     if (mRenderbufferPtr) {
         MOZ_ASSERT(!mTexturePtr);
         mRenderbufferPtr->UnmarkAttachment(*this);
     } else if (mTexturePtr) {
         mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).RemoveAttachPoint(this);
     }
 
     mTexturePtr = nullptr;
     mRenderbufferPtr = nullptr;
 
-    OnBackingStoreRespecified();
+    OnBackingStoreRespecified(funcName);
 }
 
 void
-WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level,
-                                GLint layer)
+WebGLFBAttachPoint::SetTexImage(const char* funcName, WebGLTexture* tex,
+                                TexImageTarget target, GLint level, GLint layer)
 {
-    Clear();
+    Clear(funcName);
 
     mTexturePtr = tex;
     mTexImageTarget = target;
     mTexImageLevel = level;
     mTexImageLayer = layer;
 
     if (mTexturePtr) {
         mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).AddAttachPoint(this);
     }
 }
 
 void
-WebGLFBAttachPoint::SetRenderbuffer(WebGLRenderbuffer* rb)
+WebGLFBAttachPoint::SetRenderbuffer(const char* funcName, WebGLRenderbuffer* rb)
 {
-    Clear();
+    Clear(funcName);
 
     mRenderbufferPtr = rb;
 
     if (mRenderbufferPtr) {
         mRenderbufferPtr->MarkAttachment(*this);
     }
 }
 
@@ -221,19 +222,19 @@ WebGLFBAttachPoint::Size(uint32_t* const
     MOZ_ASSERT(Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).IsDefined());
     const auto& imageInfo = Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel);
 
     *out_width = imageInfo.mWidth;
     *out_height = imageInfo.mHeight;
 }
 
 void
-WebGLFBAttachPoint::OnBackingStoreRespecified() const
+WebGLFBAttachPoint::OnBackingStoreRespecified(const char* funcName) const
 {
-    mFB->InvalidateFramebufferStatus();
+    mFB->InvalidateFramebufferStatus(funcName);
 }
 
 void
 WebGLFBAttachPoint::AttachmentName(nsCString* out) const
 {
     switch (mAttachmentPoint) {
     case LOCAL_GL_DEPTH_ATTACHMENT:
         out->AssignLiteral("DEPTH_ATTACHMENT");
@@ -606,16 +607,17 @@ WebGLFBAttachPoint::GetParameter(const c
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // WebGLFramebuffer
 
 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
     : WebGLRefCountedObject(webgl)
     , mGLName(fbo)
+    , mNumFBStatusInvals(0)
 #ifdef ANDROID
     , mIsFB(false)
 #endif
     , mDepthAttachment(this, LOCAL_GL_DEPTH_ATTACHMENT)
     , mStencilAttachment(this, LOCAL_GL_STENCIL_ATTACHMENT)
     , mDepthStencilAttachment(this, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
 {
     mContext->mFramebuffers.insertBack(this);
@@ -628,24 +630,26 @@ WebGLFramebuffer::WebGLFramebuffer(WebGL
 
     mColorDrawBuffers.push_back(&mColorAttachments[0]);
     mColorReadBuffer = &mColorAttachments[0];
 }
 
 void
 WebGLFramebuffer::Delete()
 {
-    InvalidateFramebufferStatus();
+    const char funcName[] = "WebGLFramebuffer::Delete";
+
+    InvalidateFramebufferStatus(funcName);
 
-    mDepthAttachment.Clear();
-    mStencilAttachment.Clear();
-    mDepthStencilAttachment.Clear();
+    mDepthAttachment.Clear(funcName);
+    mStencilAttachment.Clear(funcName);
+    mDepthStencilAttachment.Clear(funcName);
 
     for (auto& cur : mColorAttachments) {
-        cur.Clear();
+        cur.Clear(funcName);
     }
 
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteFramebuffers(1, &mGLName);
 
     LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);
 
 #ifdef ANDROID
@@ -696,33 +700,33 @@ WebGLFramebuffer::GetAttachPoint(GLenum 
     X(mStencilAttachment);                \
     X(mDepthStencilAttachment);           \
                                           \
     for (auto& cur : mColorAttachments) { \
         X(cur);                           \
     }
 
 void
-WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
+WebGLFramebuffer::DetachTexture(const char* funcName, const WebGLTexture* tex)
 {
     const auto fnDetach = [&](WebGLFBAttachPoint& attach) {
         if (attach.Texture() == tex) {
-            attach.Clear();
+            attach.Clear(funcName);
         }
     };
 
     FOR_EACH_ATTACHMENT(fnDetach)
 }
 
 void
-WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
+WebGLFramebuffer::DetachRenderbuffer(const char* funcName, const WebGLRenderbuffer* rb)
 {
     const auto fnDetach = [&](WebGLFBAttachPoint& attach) {
         if (attach.Renderbuffer() == rb) {
-            attach.Clear();
+            attach.Clear(funcName);
         }
     };
 
     FOR_EACH_ATTACHMENT(fnDetach)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Completeness
@@ -1123,16 +1127,31 @@ WebGLFramebuffer::ResolvedData::Resolved
         if (!fnCommon(attach))
             return;
 
         readSet.insert(WebGLFBAttachPoint::Ordered(attach));
     }
 }
 
 void
+WebGLFramebuffer::InvalidateFramebufferStatus(const char* funcName)
+{
+    if (mResolvedCompleteData) {
+        mNumFBStatusInvals++;
+        if (mNumFBStatusInvals > mContext->mMaxAcceptableFBStatusInvals) {
+            mContext->GeneratePerfWarning("%s: FB was invalidated after being complete %u"
+                                          " times.",
+                                          funcName, uint32_t(mNumFBStatusInvals));
+        }
+    }
+
+    mResolvedCompleteData = nullptr;
+}
+
+void
 WebGLFramebuffer::RefreshResolvedData()
 {
     if (mResolvedCompleteData) {
         mResolvedCompleteData.reset(new ResolvedData(*this));
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1341,23 +1360,23 @@ WebGLFramebuffer::FramebufferRenderbuffe
 
     // `rb`
     if (rb && !mContext->ValidateObject("framebufferRenderbuffer: rb", *rb))
         return;
 
     // End of validation.
 
     if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetRenderbuffer(rb);
-        mStencilAttachment.SetRenderbuffer(rb);
+        mDepthAttachment.SetRenderbuffer(funcName, rb);
+        mStencilAttachment.SetRenderbuffer(funcName, rb);
     } else {
-        attach->SetRenderbuffer(rb);
+        attach->SetRenderbuffer(funcName, rb);
     }
 
-    InvalidateFramebufferStatus();
+    InvalidateFramebufferStatus(funcName);
 }
 
 void
 WebGLFramebuffer::FramebufferTexture2D(const char* funcName, GLenum attachEnum,
                                        GLenum texImageTarget, WebGLTexture* tex,
                                        GLint level)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
@@ -1429,23 +1448,23 @@ WebGLFramebuffer::FramebufferTexture2D(c
         }
     } else if (level != 0) {
         return mContext->ErrorInvalidValue("%s: `level` must be 0.", funcName);
     }
 
     // End of validation.
 
     if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetTexImage(tex, texImageTarget, level);
-        mStencilAttachment.SetTexImage(tex, texImageTarget, level);
+        mDepthAttachment.SetTexImage(funcName, tex, texImageTarget, level);
+        mStencilAttachment.SetTexImage(funcName, tex, texImageTarget, level);
     } else {
-        attach->SetTexImage(tex, texImageTarget, level);
+        attach->SetTexImage(funcName, tex, texImageTarget, level);
     }
 
-    InvalidateFramebufferStatus();
+    InvalidateFramebufferStatus(funcName);
 }
 
 void
 WebGLFramebuffer::FramebufferTextureLayer(const char* funcName, GLenum attachEnum,
                                           WebGLTexture* tex, GLint level, GLint layer)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
@@ -1513,23 +1532,23 @@ WebGLFramebuffer::FramebufferTextureLaye
                                             funcName);
             return;
         }
     }
 
     // End of validation.
 
     if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetTexImage(tex, texImageTarget, level, layer);
-        mStencilAttachment.SetTexImage(tex, texImageTarget, level, layer);
+        mDepthAttachment.SetTexImage(funcName, tex, texImageTarget, level, layer);
+        mStencilAttachment.SetTexImage(funcName, tex, texImageTarget, level, layer);
     } else {
-        attach->SetTexImage(tex, texImageTarget, level, layer);
+        attach->SetTexImage(funcName, tex, texImageTarget, level, layer);
     }
 
-    InvalidateFramebufferStatus();
+    InvalidateFramebufferStatus(funcName);
 }
 
 JS::Value
 WebGLFramebuffer::GetAttachmentParameter(const char* funcName, JSContext* cx,
                                          GLenum target, GLenum attachEnum, GLenum pname,
                                          ErrorResult* const out_error)
 {
     const auto maybeAttach = GetAttachPoint(attachEnum);
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -61,21 +61,21 @@ public:
     bool IsDeleteRequested() const;
 
     const webgl::FormatUsageInfo* Format() const;
     uint32_t Samples() const;
 
     bool HasAlpha() const;
     bool IsReadableFloat() const;
 
-    void Clear();
+    void Clear(const char* funcName);
 
-    void SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level,
-                     GLint layer = 0);
-    void SetRenderbuffer(WebGLRenderbuffer* rb);
+    void SetTexImage(const char* funcName, WebGLTexture* tex, TexImageTarget target,
+                     GLint level, GLint layer = 0);
+    void SetRenderbuffer(const char* funcName, WebGLRenderbuffer* rb);
 
     WebGLTexture* Texture() const { return mTexturePtr; }
     WebGLRenderbuffer* Renderbuffer() const { return mRenderbufferPtr; }
 
     TexImageTarget ImageTarget() const {
         return mTexImageTarget;
     }
     GLint Layer() const {
@@ -95,17 +95,17 @@ public:
     bool IsComplete(WebGLContext* webgl, nsCString* const out_info) const;
 
     void Resolve(gl::GLContext* gl) const;
 
     JS::Value GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx,
                            GLenum target, GLenum attachment, GLenum pname,
                            ErrorResult* const out_error) const;
 
-    void OnBackingStoreRespecified() const;
+    void OnBackingStoreRespecified(const char* funcName) const;
 
     bool IsEquivalentForFeedback(const WebGLFBAttachPoint& other) const {
         if (!IsDefined() || !other.IsDefined())
             return false;
 
 #define _(X) X == other.X
         return ( _(mRenderbufferPtr) &&
                  _(mTexturePtr) &&
@@ -149,16 +149,19 @@ class WebGLFramebuffer final
 {
     friend class WebGLContext;
 
 public:
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLFramebuffer)
 
     const GLuint mGLName;
 
+private:
+    uint64_t mNumFBStatusInvals;
+
 protected:
 #ifdef ANDROID
     // Bug 1140459: Some drivers (including our test slaves!) don't
     // give reasonable answers for IsRenderbuffer, maybe others.
     // This shows up on Android 2.3 emulator.
     //
     // So we track the `is a Framebuffer` state ourselves.
     bool mIsFB;
@@ -225,18 +228,18 @@ protected:
     Maybe<WebGLFBAttachPoint*> GetAttachPoint(GLenum attachment); // Fallible
     Maybe<WebGLFBAttachPoint*> GetColorAttachPoint(GLenum attachment); // Fallible
     void ResolveAttachments() const;
     void RefreshDrawBuffers() const;
     void RefreshReadBuffer() const;
     bool ResolveAttachmentData(const char* funcName) const;
 
 public:
-    void DetachTexture(const WebGLTexture* tex);
-    void DetachRenderbuffer(const WebGLRenderbuffer* rb);
+    void DetachTexture(const char* funcName, const WebGLTexture* tex);
+    void DetachRenderbuffer(const char* funcName, const WebGLRenderbuffer* rb);
     bool ValidateAndInitAttachments(const char* funcName);
     bool ValidateClearBufferType(const char* funcName, GLenum buffer, uint32_t drawBuffer,
                                  GLenum funcType) const;
 
     bool ValidateForRead(const char* info,
                          const webgl::FormatUsageInfo** const out_format,
                          uint32_t* const out_width, uint32_t* const out_height);
 
@@ -253,21 +256,17 @@ public:
     GETTER(ResolvedCompleteData)
 
 #undef GETTER
 
     ////////////////
     // Invalidation
 
     bool IsResolvedComplete() const { return bool(mResolvedCompleteData); }
-
-    void InvalidateFramebufferStatus() {
-        mResolvedCompleteData = nullptr;
-    }
-
+    void InvalidateFramebufferStatus(const char* funcName);
     void RefreshResolvedData();
 
     ////////////////
     // WebGL funcs
 
     FBStatus CheckFramebufferStatus(const char* funcName);
     void FramebufferRenderbuffer(const char* funcName, GLenum attachment, GLenum rbtarget,
                                  WebGLRenderbuffer* rb);
--- a/dom/canvas/WebGLFramebufferAttachable.cpp
+++ b/dom/canvas/WebGLFramebufferAttachable.cpp
@@ -26,18 +26,18 @@ WebGLFramebufferAttachable::UnmarkAttach
         MOZ_ASSERT(false, "Is not attached to FB");
         return;
     }
 
     mAttachmentPoints.RemoveElementAt(i);
 }
 
 void
-WebGLFramebufferAttachable::InvalidateStatusOfAttachedFBs() const
+WebGLFramebufferAttachable::InvalidateStatusOfAttachedFBs(const char* funcName) const
 {
     const size_t count = mAttachmentPoints.Length();
     for (size_t i = 0; i < count; ++i) {
         MOZ_ASSERT(mAttachmentPoints[i]->mFB);
-        mAttachmentPoints[i]->mFB->InvalidateFramebufferStatus();
+        mAttachmentPoints[i]->mFB->InvalidateFramebufferStatus(funcName);
     }
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLFramebufferAttachable.h
+++ b/dom/canvas/WebGLFramebufferAttachable.h
@@ -14,14 +14,14 @@ class WebGLFBAttachPoint;
 class WebGLFramebufferAttachable
 {
     nsTArray<const WebGLFBAttachPoint*> mAttachmentPoints;
 
 public:
     // Track FBO/Attachment combinations
     void MarkAttachment(const WebGLFBAttachPoint& attachment);
     void UnmarkAttachment(const WebGLFBAttachPoint& attachment);
-    void InvalidateStatusOfAttachedFBs() const;
+    void InvalidateStatusOfAttachedFBs(const char* funcName) const;
 };
 
 } // namespace mozilla
 
 #endif // !WEBGLFRAMEBUFFERATTACHABLE_H_
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -219,17 +219,17 @@ WebGLRenderbuffer::RenderbufferStorage(c
     }
 
     mSamples = samples;
     mFormat = usage;
     mWidth = width;
     mHeight = height;
     mImageDataStatus = WebGLImageDataStatus::UninitializedImageData;
 
-    InvalidateStatusOfAttachedFBs();
+    InvalidateStatusOfAttachedFBs(funcName);
 }
 
 void
 WebGLRenderbuffer::DoFramebufferRenderbuffer(FBTarget target, GLenum attachment) const
 {
     gl::GLContext* gl = mContext->gl;
 
     if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -28,48 +28,46 @@ namespace mozilla {
 template <typename T>
 static inline T&
 Mutable(const T& x)
 {
     return const_cast<T&>(x);
 }
 
 void
-WebGLTexture::ImageInfo::Clear()
+WebGLTexture::ImageInfo::Clear(const char* funcName)
 {
     if (!IsDefined())
         return;
 
-    OnRespecify();
+    OnRespecify(funcName);
 
     Mutable(mFormat) = LOCAL_GL_NONE;
     Mutable(mWidth) = 0;
     Mutable(mHeight) = 0;
     Mutable(mDepth) = 0;
 
     MOZ_ASSERT(!IsDefined());
 }
 
-WebGLTexture::ImageInfo&
-WebGLTexture::ImageInfo::operator =(const ImageInfo& a)
+void
+WebGLTexture::ImageInfo::Set(const char* funcName, const ImageInfo& a)
 {
     MOZ_ASSERT(a.IsDefined());
 
     Mutable(mFormat) = a.mFormat;
     Mutable(mWidth) = a.mWidth;
     Mutable(mHeight) = a.mHeight;
     Mutable(mDepth) = a.mDepth;
 
     mIsDataInitialized = a.mIsDataInitialized;
 
     // But *don't* transfer mAttachPoints!
     MOZ_ASSERT(a.mAttachPoints.empty());
-    OnRespecify();
-
-    return *this;
+    OnRespecify(funcName);
 }
 
 bool
 WebGLTexture::ImageInfo::IsPowerOfTwo() const
 {
     return mozilla::IsPowerOfTwo(mWidth) &&
            mozilla::IsPowerOfTwo(mHeight) &&
            mozilla::IsPowerOfTwo(mDepth);
@@ -86,20 +84,20 @@ WebGLTexture::ImageInfo::AddAttachPoint(
 void
 WebGLTexture::ImageInfo::RemoveAttachPoint(WebGLFBAttachPoint* attachPoint)
 {
     DebugOnly<size_t> numElemsErased = mAttachPoints.erase(attachPoint);
     MOZ_ASSERT_IF(IsDefined(), numElemsErased == 1);
 }
 
 void
-WebGLTexture::ImageInfo::OnRespecify() const
+WebGLTexture::ImageInfo::OnRespecify(const char* funcName) const
 {
     for (auto cur : mAttachPoints) {
-        cur->OnBackingStoreRespecified();
+        cur->OnBackingStoreRespecified(funcName);
     }
 }
 
 size_t
 WebGLTexture::ImageInfo::MemoryUsage() const
 {
     if (!IsDefined())
         return 0;
@@ -144,18 +142,19 @@ WebGLTexture::WebGLTexture(WebGLContext*
     , mResolved_Swizzle(nullptr)
 {
     mContext->mTextures.insertBack(this);
 }
 
 void
 WebGLTexture::Delete()
 {
+    const char funcName[] = "WebGLTexture::Delete";
     for (auto& cur : mImageInfoArr) {
-        cur.Clear();
+        cur.Clear(funcName);
     }
 
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteTextures(1, &mGLName);
 
     LinkedListElement<WebGLTexture>::removeFrom(mContext->mTextures);
 }
 
@@ -168,28 +167,30 @@ WebGLTexture::MemoryUsage() const
     size_t accum = 0;
     for (const auto& cur : mImageInfoArr) {
         accum += cur.MemoryUsage();
     }
     return accum;
 }
 
 void
-WebGLTexture::SetImageInfo(ImageInfo* target, const ImageInfo& newInfo)
+WebGLTexture::SetImageInfo(const char* funcName, ImageInfo* target,
+                           const ImageInfo& newInfo)
 {
-    *target = newInfo;
+    target->Set(funcName, newInfo);
 
     InvalidateResolveCache();
 }
 
 void
-WebGLTexture::SetImageInfosAtLevel(uint32_t level, const ImageInfo& newInfo)
+WebGLTexture::SetImageInfosAtLevel(const char* funcName, uint32_t level,
+                                   const ImageInfo& newInfo)
 {
     for (uint8_t i = 0; i < mFaceCount; i++) {
-        ImageInfoAtFace(i, level) = newInfo;
+        ImageInfoAtFace(i, level).Set(funcName, newInfo);
     }
 
     InvalidateResolveCache();
 }
 
 bool
 WebGLTexture::IsMipmapComplete(const char* funcName, uint32_t texUnit,
                                bool* const out_initFailed)
@@ -768,17 +769,18 @@ WebGLTexture::ClampLevelBaseAndMax()
     //  `[level_base, levels-1]`, where `levels` is the parameter passed to
     //   TexStorage* for the texture object."
     mBaseMipmapLevel = Clamp<uint32_t>(mBaseMipmapLevel, 0, mImmutableLevelCount - 1);
     mMaxMipmapLevel = Clamp<uint32_t>(mMaxMipmapLevel, mBaseMipmapLevel,
                                       mImmutableLevelCount - 1);
 }
 
 void
-WebGLTexture::PopulateMipChain(uint32_t firstLevel, uint32_t lastLevel)
+WebGLTexture::PopulateMipChain(const char* funcName, uint32_t firstLevel,
+                               uint32_t lastLevel)
 {
     const ImageInfo& baseImageInfo = ImageInfoAtFace(0, firstLevel);
     MOZ_ASSERT(baseImageInfo.IsDefined());
 
     uint32_t refWidth = baseImageInfo.mWidth;
     uint32_t refHeight = baseImageInfo.mHeight;
     uint32_t refDepth = baseImageInfo.mDepth;
     if (!refWidth || !refHeight || !refDepth)
@@ -799,17 +801,17 @@ WebGLTexture::PopulateMipChain(uint32_t 
         refHeight = std::max(uint32_t(1), refHeight / 2);
         if (mTarget == LOCAL_GL_TEXTURE_3D) { // But not TEXTURE_2D_ARRAY!
             refDepth = std::max(uint32_t(1), refDepth / 2);
         }
 
         const ImageInfo cur(baseImageInfo.mFormat, refWidth, refHeight, refDepth,
                             baseImageInfo.IsDataInitialized());
 
-        SetImageInfosAtLevel(level, cur);
+        SetImageInfosAtLevel(funcName, level, cur);
     }
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 // GL calls
 
 bool
 WebGLTexture::BindTexture(TexTarget texTarget)
@@ -849,50 +851,53 @@ WebGLTexture::BindTexture(TexTarget texT
 
     return true;
 }
 
 
 void
 WebGLTexture::GenerateMipmap(TexTarget texTarget)
 {
+    const char funcName[] = "generateMipmap";
     // GLES 3.0.4 p160:
     // "Mipmap generation replaces texel array levels level base + 1 through q with arrays
     //  derived from the level base array, regardless of their previous contents. All
     //  other mipmap arrays, including the level base array, are left unchanged by this
     //  computation."
     const ImageInfo& baseImageInfo = BaseImageInfo();
     if (!baseImageInfo.IsDefined()) {
-        mContext->ErrorInvalidOperation("generateMipmap: The base level of the texture is"
-                                        " not defined.");
+        mContext->ErrorInvalidOperation("%s: The base level of the texture is not"
+                                        " defined.",
+                                        funcName);
         return;
     }
 
     if (IsCubeMap() && !IsCubeComplete()) {
-      mContext->ErrorInvalidOperation("generateMipmap: Cube maps must be \"cube"
-                                      " complete\".");
+      mContext->ErrorInvalidOperation("%s: Cube maps must be \"cube complete\".",
+                                      funcName);
       return;
     }
 
     if (!mContext->IsWebGL2() && !baseImageInfo.IsPowerOfTwo()) {
-        mContext->ErrorInvalidOperation("generateMipmap: The base level of the texture"
-                                        " does not have power-of-two dimensions.");
+        mContext->ErrorInvalidOperation("%s: The base level of the texture does not have"
+                                        " power-of-two dimensions.",
+                                        funcName);
         return;
     }
 
     auto format = baseImageInfo.mFormat->format;
     if (format->compression) {
-        mContext->ErrorInvalidOperation("generateMipmap: Texture data at base level is"
-                                        " compressed.");
+        mContext->ErrorInvalidOperation("%s: Texture data at base level is compressed.",
+                                        funcName);
         return;
     }
 
     if (format->d) {
-        mContext->ErrorInvalidOperation("generateMipmap: Depth textures are not"
-                                        " supported.");
+        mContext->ErrorInvalidOperation("%s: Depth textures are not supported.",
+                                        funcName);
         return;
     }
 
     // OpenGL ES 3.0.4 p160:
     // If the level base array was not specified with an unsized internal format from
     // table 3.3 or a sized internal format that is both color-renderable and
     // texture-filterable according to table 3.13, an INVALID_OPERATION error
     // is generated.
@@ -905,19 +910,20 @@ WebGLTexture::GenerateMipmap(TexTarget t
         // Non-color-renderable formats from Table 3.3.
         canGenerateMipmap = true;
         break;
     default:
         break;
     }
 
     if (!canGenerateMipmap) {
-        mContext->ErrorInvalidOperation("generateMipmap: Texture at base level is not unsized"
+        mContext->ErrorInvalidOperation("%s: Texture at base level is not unsized"
                                         " internal format or is not"
-                                        " color-renderable or texture-filterable.");
+                                        " color-renderable or texture-filterable.",
+                                        funcName);
         return;
     }
 
     // Done with validation. Do the operation.
 
     mContext->MakeContextCurrent();
     gl::GLContext* gl = mContext->gl;
 
@@ -935,17 +941,17 @@ WebGLTexture::GenerateMipmap(TexTarget t
     } else {
         gl->fGenerateMipmap(texTarget.get());
     }
 
     // Record the results.
     // Note that we don't use MaxEffectiveMipmapLevel() here, since that returns
     // mBaseMipmapLevel if the min filter doesn't require mipmaps.
     const uint32_t maxLevel = mBaseMipmapLevel + baseImageInfo.PossibleMipmapLevels() - 1;
-    PopulateMipChain(mBaseMipmapLevel, maxLevel);
+    PopulateMipChain(funcName, mBaseMipmapLevel, maxLevel);
 }
 
 JS::Value
 WebGLTexture::GetTexParameter(TexTarget texTarget, GLenum pname)
 {
     mContext->MakeContextCurrent();
 
     GLint i = 0;
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -100,27 +100,29 @@ public:
     // numLevels(16k) = log2(16k) + 1 = 14 + 1 = 15
     // numLevels(1M) = log2(1M) + 1 = 19.9 + 1 ~= 21
     // Or we can just max this out to 31, which is the number of unsigned bits in GLsizei.
     static const uint8_t kMaxLevelCount = 31;
 
     // And in turn, it needs these forwards:
 protected:
     // We need to forward these.
-    void SetImageInfo(ImageInfo* target, const ImageInfo& newInfo);
-    void SetImageInfosAtLevel(uint32_t level, const ImageInfo& newInfo);
+    void SetImageInfo(const char* funcName, ImageInfo* target, const ImageInfo& newInfo);
+    void SetImageInfosAtLevel(const char* funcName, uint32_t level,
+                              const ImageInfo& newInfo);
 
 public:
     // We store information about the various images that are part of this
     // texture. (cubemap faces, mipmap levels)
     class ImageInfo
     {
-        friend void WebGLTexture::SetImageInfo(ImageInfo* target,
+        friend void WebGLTexture::SetImageInfo(const char* funcName, ImageInfo* target,
                                                const ImageInfo& newInfo);
-        friend void WebGLTexture::SetImageInfosAtLevel(uint32_t level,
+        friend void WebGLTexture::SetImageInfosAtLevel(const char* funcName,
+                                                       uint32_t level,
                                                        const ImageInfo& newInfo);
 
     public:
         static const ImageInfo kUndefined;
 
         // This is the "effective internal format" of the texture, an official
         // OpenGL spec concept, see OpenGL ES 3.0.3 spec, section 3.8.3, page
         // 126 and below.
@@ -150,39 +152,38 @@ public:
             , mWidth(width)
             , mHeight(height)
             , mDepth(depth)
             , mIsDataInitialized(isDataInitialized)
         {
             MOZ_ASSERT(mFormat);
         }
 
-        void Clear();
+        void Clear(const char* funcName);
 
         ~ImageInfo() {
-            if (!IsDefined())
-                Clear();
+            MOZ_ASSERT(!mAttachPoints.size());
         }
 
     protected:
-        ImageInfo& operator =(const ImageInfo& a);
+        void Set(const char* funcName, const ImageInfo& a);
 
     public:
         uint32_t PossibleMipmapLevels() const {
             // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1`
             const uint32_t largest = std::max(std::max(mWidth, mHeight), mDepth);
             MOZ_ASSERT(largest != 0);
             return FloorLog2Size(largest) + 1;
         }
 
         bool IsPowerOfTwo() const;
 
         void AddAttachPoint(WebGLFBAttachPoint* attachPoint);
         void RemoveAttachPoint(WebGLFBAttachPoint* attachPoint);
-        void OnRespecify() const;
+        void OnRespecify(const char* funcName) const;
 
         size_t MemoryUsage() const;
 
         bool IsDefined() const {
             if (mFormat == LOCAL_GL_NONE) {
                 MOZ_ASSERT(!mWidth && !mHeight && !mDepth);
                 return false;
             }
@@ -284,17 +285,17 @@ public:
                          GLint xOffset, GLint yOffset, GLint zOffset, GLint x, GLint y,
                          GLsizei width, GLsizei height);
 
     ////////////////////////////////////
 
 protected:
     void ClampLevelBaseAndMax();
 
-    void PopulateMipChain(uint32_t baseLevel, uint32_t maxLevel);
+    void PopulateMipChain(const char* funcName, uint32_t baseLevel, uint32_t maxLevel);
 
     bool MaxEffectiveMipmapLevel(uint32_t texUnit, uint32_t* const out) const;
 
     static uint8_t FaceForTarget(TexImageTarget texImageTarget) {
         GLenum rawTexImageTarget = texImageTarget.get();
         switch (rawTexImageTarget) {
         case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
         case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
@@ -325,21 +326,21 @@ public:
         auto face = FaceForTarget(texImageTarget);
         return ImageInfoAtFace(face, level);
     }
 
     const ImageInfo& ImageInfoAt(TexImageTarget texImageTarget, GLint level) const {
         return const_cast<WebGLTexture*>(this)->ImageInfoAt(texImageTarget, level);
     }
 
-    void SetImageInfoAt(TexImageTarget texImageTarget, GLint level,
+    void SetImageInfoAt(const char* funcName, TexImageTarget texImageTarget, GLint level,
                         const ImageInfo& val)
     {
         ImageInfo* target = &ImageInfoAt(texImageTarget, level);
-        SetImageInfo(target, val);
+        SetImageInfo(funcName, target, val);
     }
 
     const ImageInfo& BaseImageInfo() const {
         if (mBaseMipmapLevel >= kMaxLevelCount)
             return ImageInfo::kUndefined;
 
         return ImageInfoAtFace(0, mBaseMipmapLevel);
     }
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -1180,19 +1180,19 @@ WebGLTexture::TexStorage(const char* fun
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
     const bool isDataInitialized = false;
     const WebGLTexture::ImageInfo newInfo(dstUsage, width, height, depth,
                                           isDataInitialized);
-    SetImageInfosAtLevel(0, newInfo);
+    SetImageInfosAtLevel(funcName, 0, newInfo);
 
-    PopulateMipChain(0, levels-1);
+    PopulateMipChain(funcName, 0, levels-1);
 
     mImmutable = true;
     mImmutableLevelCount = levels;
 }
 
 ////////////////////////////////////////
 // Tex(Sub)Image
 
@@ -1312,17 +1312,17 @@ WebGLTexture::TexImage(const char* funcN
                       driverUnpackInfo->unpackFormat, driverUnpackInfo->unpackType);
         MOZ_ASSERT(false, "Unexpected GL error.");
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
-    SetImageInfo(imageInfo, newImageInfo);
+    SetImageInfo(funcName, imageInfo, newImageInfo);
 }
 
 void
 WebGLTexture::TexSubImage(const char* funcName, TexImageTarget target, GLint level,
                           GLint xOffset, GLint yOffset, GLint zOffset,
                           const webgl::PackingInfo& pi, const webgl::TexUnpackBlob* blob)
 {
     ////////////////////////////////////
@@ -1518,17 +1518,17 @@ WebGLTexture::CompressedTexImage(const c
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
     const bool isDataInitialized = true;
     const ImageInfo newImageInfo(usage, blob->mWidth, blob->mHeight, blob->mDepth,
                                  isDataInitialized);
-    SetImageInfo(imageInfo, newImageInfo);
+    SetImageInfo(funcName, imageInfo, newImageInfo);
 }
 
 static inline bool
 IsSubImageBlockAligned(const webgl::CompressedFormatInfo* compression,
                        const WebGLTexture::ImageInfo* imageInfo, GLint xOffset,
                        GLint yOffset, uint32_t width, uint32_t height)
 {
     if (xOffset % compression->blockWidth != 0 ||
@@ -2165,17 +2165,17 @@ WebGLTexture::CopyTexImage2D(TexImageTar
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
     const bool isDataInitialized = true;
     const ImageInfo newImageInfo(dstUsage, width, height, depth, isDataInitialized);
-    SetImageInfo(imageInfo, newImageInfo);
+    SetImageInfo(funcName, imageInfo, newImageInfo);
 }
 
 void
 WebGLTexture::CopyTexSubImage(const char* funcName, TexImageTarget target, GLint level,
                               GLint xOffset, GLint yOffset, GLint zOffset, GLint x,
                               GLint y, GLsizei rawWidth, GLsizei rawHeight)
 {
     uint32_t width, height, depth;
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -611,16 +611,19 @@ private:
   DECL_GFX_PREF(Live, "webgl.max-warnings-per-context",        WebGLMaxWarningsPerContext, uint32_t, 32);
   DECL_GFX_PREF(Live, "webgl.min_capability_mode",             WebGLMinCapabilityMode, bool, false);
   DECL_GFX_PREF(Live, "webgl.msaa-force",                      WebGLForceMSAA, bool, false);
   DECL_GFX_PREF(Live, "webgl.prefer-16bpp",                    WebGLPrefer16bpp, bool, false);
   DECL_GFX_PREF(Live, "webgl.restore-context-when-visible",    WebGLRestoreWhenVisible, bool, true);
   DECL_GFX_PREF(Live, "webgl.allow-immediate-queries",         WebGLImmediateQueries, bool, false);
   DECL_GFX_PREF(Live, "webgl.allow-fb-invalidation",           WebGLFBInvalidation, bool, false);
 
+  DECL_GFX_PREF(Live, "webgl.max-perf-warnings",               WebGLMaxPerfWarnings, int32_t, 0);
+  DECL_GFX_PREF(Live, "webgl.max-acceptable-fb-status-invals", WebGLMaxAcceptableFBStatusInvals, int32_t, 0);
+
   DECL_GFX_PREF(Live, "webgl.webgl2-compat-mode",              WebGL2CompatMode, bool, false);
 
   // WARNING:
   // Please make sure that you've added your new preference to the list above in alphabetical order.
   // Please do not just append it to the end of the list.
 
 public:
   // Manage the singleton:
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4502,16 +4502,19 @@ pref("webgl.max-warnings-per-context", 3
 pref("webgl.enable-draft-extensions", false);
 pref("webgl.enable-privileged-extensions", false);
 pref("webgl.bypass-shader-validation", false);
 pref("webgl.disable-fail-if-major-performance-caveat", false);
 pref("webgl.disable-DOM-blit-uploads", false);
 pref("webgl.allow-fb-invalidation", false);
 pref("webgl.webgl2-compat-mode", false);
 
+pref("webgl.max-perf-warnings", 0);
+pref("webgl.max-acceptable-fb-status-invals", 0);
+
 pref("webgl.enable-webgl2", true);
 
 #ifdef RELEASE_OR_BETA
 // Keep this disabled on Release and Beta for now. (see bug 1171228)
 pref("webgl.enable-debug-renderer-info", false);
 #else
 pref("webgl.enable-debug-renderer-info", true);
 #endif