Bug 922810 - Part 1: track image initialization with a WebGLImageDataStatus enum - r=jgilbert
authorBenoit Jacob <bjacob@mozilla.com>
Fri, 11 Oct 2013 09:16:43 -0400
changeset 164269 8ac8abefc8dd497867b0cc5ed65fd56d00f2289e
parent 164268 e48702148c5a1f61fe1501dd8d92fe04353987c1
child 164270 11edccaa94275850608c2bf84c850e922e46eb7f
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgilbert
bugs922810
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 922810 - Part 1: track image initialization with a WebGLImageDataStatus enum - r=jgilbert
content/canvas/src/WebGLContextFramebufferOperations.cpp
content/canvas/src/WebGLContextGL.cpp
content/canvas/src/WebGLContextVertices.cpp
content/canvas/src/WebGLFramebuffer.cpp
content/canvas/src/WebGLFramebuffer.h
content/canvas/src/WebGLRenderbuffer.cpp
content/canvas/src/WebGLRenderbuffer.h
content/canvas/src/WebGLTexture.cpp
content/canvas/src/WebGLTexture.h
content/canvas/src/WebGLTypes.h
--- a/content/canvas/src/WebGLContextFramebufferOperations.cpp
+++ b/content/canvas/src/WebGLContextFramebufferOperations.cpp
@@ -25,17 +25,17 @@ WebGLContext::Clear(GLbitfield mask)
 
     if (mask == 0) {
         GenerateWarning("Calling gl.clear(0) has no effect.");
     } else if (mRasterizerDiscardEnabled) {
         GenerateWarning("Calling gl.clear() with RASTERIZER_DISCARD enabled has no effects.");
     }
 
     if (mBoundFramebuffer) {
-        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
+        if (!mBoundFramebuffer->CheckAndInitializeAttachments())
             return ErrorInvalidFramebufferOperation("clear: incomplete framebuffer");
 
         gl->fClear(mask);
         return;
     }
 
     // Ok, we're clearing the default framebuffer/screen.
 
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -503,17 +503,17 @@ WebGLContext::CopyTexImage2D(GLenum targ
         return ErrorInvalidOperation("copyTexImage2D: texture format requires an alpha channel "
                                      "but the framebuffer doesn't have one");
 
     if (internalformat == LOCAL_GL_DEPTH_COMPONENT ||
         internalformat == LOCAL_GL_DEPTH_STENCIL)
         return ErrorInvalidOperation("copyTexImage2D: a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL isn't supported");
 
     if (mBoundFramebuffer)
-        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
+        if (!mBoundFramebuffer->CheckAndInitializeAttachments())
             return ErrorInvalidFramebufferOperation("copyTexImage2D: incomplete framebuffer");
 
     WebGLTexture *tex = activeBoundTextureForTarget(target);
     if (!tex)
         return ErrorInvalidOperation("copyTexImage2D: no texture bound to this target");
 
     // copyTexImage2D only generates textures with type = UNSIGNED_BYTE
     GLenum type = LOCAL_GL_UNSIGNED_BYTE;
@@ -532,22 +532,23 @@ WebGLContext::CopyTexImage2D(GLenum targ
     if (sizeMayChange) {
         UpdateWebGLErrorAndClearGLError();
         CopyTexSubImage2D_base(target, level, internalformat, 0, 0, x, y, width, height, false);
         GLenum error = LOCAL_GL_NO_ERROR;
         UpdateWebGLErrorAndClearGLError(&error);
         if (error) {
             GenerateWarning("copyTexImage2D generated error %s", ErrorName(error));
             return;
-        }          
+        }
     } else {
         CopyTexSubImage2D_base(target, level, internalformat, 0, 0, x, y, width, height, false);
     }
-    
-    tex->SetImageInfo(target, level, width, height, internalformat, type);
+
+    tex->SetImageInfo(target, level, width, height, internalformat, type,
+                      WebGLImageDataStatus::InitializedImageData);
 }
 
 void
 WebGLContext::CopyTexSubImage2D(GLenum target,
                                 GLint level,
                                 GLint xoffset,
                                 GLint yoffset,
                                 GLint x,
@@ -612,17 +613,17 @@ WebGLContext::CopyTexSubImage2D(GLenum t
         return ErrorInvalidOperation("copyTexSubImage2D: texture format requires an alpha channel "
                                      "but the framebuffer doesn't have one");
 
     if (format == LOCAL_GL_DEPTH_COMPONENT ||
         format == LOCAL_GL_DEPTH_STENCIL)
         return ErrorInvalidOperation("copyTexSubImage2D: a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL isn't supported");
 
     if (mBoundFramebuffer)
-        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
+        if (!mBoundFramebuffer->CheckAndInitializeAttachments())
             return ErrorInvalidFramebufferOperation("copyTexSubImage2D: incomplete framebuffer");
 
     return CopyTexSubImage2D_base(target, level, format, xoffset, yoffset, x, y, width, height, true);
 }
 
 
 already_AddRefed<WebGLProgram>
 WebGLContext::CreateProgram()
@@ -2249,17 +2250,17 @@ WebGLContext::ReadPixels(GLint x, GLint 
         default:
             return ErrorInvalidOperation("readPixels: Invalid format/type pair");
     }
 
     MakeContextCurrent();
 
     if (mBoundFramebuffer) {
         // prevent readback of arbitrary video memory through uninitialized renderbuffers!
-        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
+        if (!mBoundFramebuffer->CheckAndInitializeAttachments())
             return ErrorInvalidFramebufferOperation("readPixels: incomplete framebuffer");
     }
     // Now that the errors are out of the way, on to actually reading
 
     // If we won't be reading any pixels anyways, just skip the actual reading
     if (width == 0 || height == 0)
         return DummyFramebufferOperation("readPixels");
 
@@ -2435,17 +2436,17 @@ WebGLContext::RenderbufferStorage(GLenum
         }
     } else {
         mBoundRenderbuffer->RenderbufferStorage(internalformatForGL, width, height);
     }
 
     mBoundRenderbuffer->SetInternalFormat(internalformat);
     mBoundRenderbuffer->SetInternalFormatForGL(internalformatForGL);
     mBoundRenderbuffer->setDimensions(width, height);
-    mBoundRenderbuffer->SetInitialized(false);
+    mBoundRenderbuffer->SetImageDataStatus(WebGLImageDataStatus::UninitializedImageData);
 }
 
 void
 WebGLContext::Scissor(GLint x, GLint y, GLsizei width, GLsizei height)
 {
     if (IsContextLost())
         return;
 
@@ -3298,17 +3299,18 @@ WebGLContext::CompressedTexImage2D(GLenu
     }
 
     uint32_t byteLength = view.Length();
     if (!ValidateCompressedTextureSize(target, level, internalformat, width, height, byteLength, "compressedTexImage2D")) {
         return;
     }
 
     gl->fCompressedTexImage2D(target, level, internalformat, width, height, border, byteLength, view.Data());
-    tex->SetImageInfo(target, level, width, height, internalformat, LOCAL_GL_UNSIGNED_BYTE);
+    tex->SetImageInfo(target, level, width, height, internalformat, LOCAL_GL_UNSIGNED_BYTE,
+                      WebGLImageDataStatus::InitializedImageData);
 
     ReattachTextureToAnyFramebufferToWorkAroundBugs(tex, level);
 }
 
 void
 WebGLContext::CompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset,
                                       GLint yoffset, GLsizei width, GLsizei height,
                                       GLenum format, const ArrayBufferView& view)
@@ -3705,16 +3707,18 @@ WebGLContext::TexImage2D_base(GLenum tar
     MakeContextCurrent();
 
     // Handle ES2 and GL differences in floating point internal formats.  Note that
     // format == internalformat, as checked above and as required by ES.
     internalformat = InternalFormatForFormatAndType(format, type, gl->IsGLES2());
 
     GLenum error = LOCAL_GL_NO_ERROR;
 
+    WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::NoImageData;
+
     if (byteLength) {
         size_t srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value();
 
         size_t dstPlainRowSize = dstTexelSize * width;
         size_t unpackAlignment = mPixelStoreUnpackAlignment;
         size_t dstStride = ((dstPlainRowSize + unpackAlignment-1) / unpackAlignment) * unpackAlignment;
 
         if (actualSrcFormat == dstFormat &&
@@ -3732,16 +3736,17 @@ WebGLContext::TexImage2D_base(GLenum tar
             nsAutoArrayPtr<uint8_t> convertedData(new uint8_t[convertedDataSize]);
             ConvertImage(width, height, srcStride, dstStride,
                         static_cast<uint8_t*>(data), convertedData,
                         actualSrcFormat, srcPremultiplied,
                         dstFormat, mPixelStorePremultiplyAlpha, dstTexelSize);
             error = CheckedTexImage2D(target, level, internalformat,
                                       width, height, border, format, type, convertedData);
         }
+        imageInfoStatusIfSuccess = WebGLImageDataStatus::InitializedImageData;
     } else {
         if (isDepthTexture && !gl->IsSupported(GLFeature::depth_texture)) {
             // There's only one way that we can we supporting depth textures without
             // supporting the regular depth_texture feature set: that's
             // with ANGLE_depth_texture.
 
             // It should be impossible to get here without ANGLE_depth_texture support
             MOZ_ASSERT(gl->IsExtensionSupported(GLContext::ANGLE_depth_texture));
@@ -3789,37 +3794,44 @@ WebGLContext::TexImage2D_base(GLenum tar
                 success = true;
             } while(false);
 
             gl->fDeleteFramebuffers(1, &fb);
 
             if (!success) {
                 return ErrorOutOfMemory("texImage2D: sorry, ran out of ways to initialize a depth texture.");
             }
+            imageInfoStatusIfSuccess = WebGLImageDataStatus::InitializedImageData;
         } else {
             // We need some zero pages, because GL doesn't guarantee the
             // contents of a texture allocated with nullptr data.
             // Hopefully calloc will just mmap zero pages here.
             void *tempZeroData = calloc(1, bytesNeeded);
             if (!tempZeroData)
                 return ErrorOutOfMemory("texImage2D: could not allocate %d bytes (for zero fill)", bytesNeeded);
 
             error = CheckedTexImage2D(target, level, internalformat,
                                       width, height, border, format, type, tempZeroData);
 
             free(tempZeroData);
+            imageInfoStatusIfSuccess = WebGLImageDataStatus::InitializedImageData;
         }
     }
 
     if (error) {
         GenerateWarning("texImage2D generated error %s", ErrorName(error));
         return;
     }
 
-    tex->SetImageInfo(target, level, width, height, format, type);
+    // in all of the code paths above, we should have either initialized data,
+    // or allocated data and left it uninitialized, but in any case we shouldn't
+    // have NoImageData at this point.
+    MOZ_ASSERT(imageInfoStatusIfSuccess != WebGLImageDataStatus::NoImageData);
+
+    tex->SetImageInfo(target, level, width, height, format, type, imageInfoStatusIfSuccess);
 
     ReattachTextureToAnyFramebufferToWorkAroundBugs(tex, level);
 }
 
 void
 WebGLContext::TexImage2D(GLenum target, GLint level,
                          GLenum internalformat, GLsizei width,
                          GLsizei height, GLint border, GLenum format,
--- a/content/canvas/src/WebGLContextVertices.cpp
+++ b/content/canvas/src/WebGLContextVertices.cpp
@@ -493,17 +493,17 @@ bool WebGLContext::DrawArrays_check(GLin
     if (uint32_t(primcount) > mMaxFetchedInstances) {
         ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
         return false;
     }
 
     MakeContextCurrent();
 
     if (mBoundFramebuffer) {
-        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers()) {
+        if (!mBoundFramebuffer->CheckAndInitializeAttachments()) {
             ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
             return false;
         }
     }
 
     if (!DoFakeVertexAttrib0(checked_firstPlusCount.value())) {
         return false;
     }
@@ -650,17 +650,17 @@ WebGLContext::DrawElements_check(GLsizei
     if (uint32_t(primcount) > mMaxFetchedInstances) {
         ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
         return false;
     }
 
     MakeContextCurrent();
 
     if (mBoundFramebuffer) {
-        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers()) {
+        if (!mBoundFramebuffer->CheckAndInitializeAttachments()) {
             ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
             return false;
         }
     }
 
     if (!DoFakeVertexAttrib0(mMaxFetchedVertices)) {
         return false;
     }
--- a/content/canvas/src/WebGLFramebuffer.cpp
+++ b/content/canvas/src/WebGLFramebuffer.cpp
@@ -62,18 +62,37 @@ void
 WebGLFramebuffer::Attachment::SetTexImage(WebGLTexture *tex, GLenum target, GLint level) {
     mTexturePtr = tex;
     mRenderbufferPtr = nullptr;
     mTexImageTarget = target;
     mTexImageLevel = level;
 }
 
 bool
-WebGLFramebuffer::Attachment::HasUninitializedRenderbuffer() const {
-    return mRenderbufferPtr && !mRenderbufferPtr->Initialized();
+WebGLFramebuffer::Attachment::HasUninitializedImageData() const {
+    if (mRenderbufferPtr) {
+        return mRenderbufferPtr->HasUninitializedImageData();
+    } else if (mTexturePtr) {
+        if (!mTexturePtr->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
+            return false;
+        return mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).HasUninitializedImageData();
+    } else {
+        return false;
+    }
+}
+
+void
+WebGLFramebuffer::Attachment::SetImageDataStatus(WebGLImageDataStatus newStatus) {
+    if (mRenderbufferPtr) {
+        mRenderbufferPtr->SetImageDataStatus(newStatus);
+    } else if (mTexturePtr) {
+        mTexturePtr->SetImageDataStatus(mTexImageTarget, mTexImageLevel, newStatus);
+    } else {
+        MOZ_ASSERT(false); // should not get here, worth crashing a debug build.
+    }
 }
 
 const WebGLRectangleObject*
 WebGLFramebuffer::Attachment::RectangleObject() const {
     if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
         return &Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel);
     else if (Renderbuffer())
         return Renderbuffer();
@@ -363,17 +382,17 @@ WebGLFramebuffer::DetachRenderbuffer(con
         FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nullptr);
     if (mStencilAttachment.Renderbuffer() == rb)
         FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nullptr);
     if (mDepthStencilAttachment.Renderbuffer() == rb)
         FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nullptr);
 }
 
 bool
-WebGLFramebuffer::CheckAndInitializeRenderbuffers()
+WebGLFramebuffer::CheckAndInitializeAttachments()
 {
     MOZ_ASSERT(mContext->mBoundFramebuffer == this);
     // enforce WebGL section 6.5 which is WebGL-specific, hence OpenGL itself would not
     // generate the INVALID_FRAMEBUFFER_OPERATION that we need here
     if (HasDepthStencilConflict())
         return false;
 
     if (HasIncompleteAttachment())
@@ -382,26 +401,26 @@ WebGLFramebuffer::CheckAndInitializeRend
     mContext->MakeContextCurrent();
 
     // Ok, attach our chosen flavor of {DEPTH, STENCIL, DEPTH_STENCIL}.
     FinalizeAttachments();
 
     size_t colorAttachmentCount = size_t(mColorAttachments.Length());
 
     {
-        bool hasUnitializedRenderbuffers = false;
+        bool hasUnitializedAttachments = false;
 
         for (size_t i = 0; i < colorAttachmentCount; i++) {
-            hasUnitializedRenderbuffers |= mColorAttachments[i].HasUninitializedRenderbuffer();
+            hasUnitializedAttachments |= mColorAttachments[i].HasUninitializedImageData();
         }
 
-        if (!hasUnitializedRenderbuffers &&
-            !mDepthAttachment.HasUninitializedRenderbuffer() &&
-            !mStencilAttachment.HasUninitializedRenderbuffer() &&
-            !mDepthStencilAttachment.HasUninitializedRenderbuffer())
+        if (!hasUnitializedAttachments &&
+            !mDepthAttachment.HasUninitializedImageData() &&
+            !mStencilAttachment.HasUninitializedImageData() &&
+            !mDepthStencilAttachment.HasUninitializedImageData())
         {
             return true;
         }
     }
 
     // ensure INVALID_FRAMEBUFFER_OPERATION in zero-size case
     const WebGLRectangleObject *rect = mColorAttachments[0].RectangleObject();
     if (!rect ||
@@ -415,52 +434,49 @@ WebGLFramebuffer::CheckAndInitializeRend
 
     uint32_t mask = 0;
     bool colorAttachmentsMask[WebGLContext::sMaxColorAttachments] = { false };
 
     MOZ_ASSERT( colorAttachmentCount <= WebGLContext::sMaxColorAttachments );
 
     for (size_t i = 0; i < colorAttachmentCount; i++)
     {
-        colorAttachmentsMask[i] = mColorAttachments[i].HasUninitializedRenderbuffer();
+        colorAttachmentsMask[i] = mColorAttachments[i].HasUninitializedImageData();
 
         if (colorAttachmentsMask[i]) {
             mask |= LOCAL_GL_COLOR_BUFFER_BIT;
         }
     }
 
-    if (mDepthAttachment.HasUninitializedRenderbuffer() ||
-        mDepthStencilAttachment.HasUninitializedRenderbuffer())
+    if (mDepthAttachment.HasUninitializedImageData() ||
+        mDepthStencilAttachment.HasUninitializedImageData())
     {
         mask |= LOCAL_GL_DEPTH_BUFFER_BIT;
     }
 
-    if (mStencilAttachment.HasUninitializedRenderbuffer() ||
-        mDepthStencilAttachment.HasUninitializedRenderbuffer())
+    if (mStencilAttachment.HasUninitializedImageData() ||
+        mDepthStencilAttachment.HasUninitializedImageData())
     {
         mask |= LOCAL_GL_STENCIL_BUFFER_BIT;
     }
 
     mContext->ForceClearFramebufferWithDefaultValues(mask, colorAttachmentsMask);
 
     for (size_t i = 0; i < colorAttachmentCount; i++)
     {
-        if (colorAttachmentsMask[i]) {
-            mColorAttachments[i].Renderbuffer()->SetInitialized(true);
-        }
+        if (mColorAttachments[i].HasUninitializedImageData())
+            mColorAttachments[i].SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     }
 
-    if (mDepthAttachment.HasUninitializedRenderbuffer())
-        mDepthAttachment.Renderbuffer()->SetInitialized(true);
-
-    if (mStencilAttachment.HasUninitializedRenderbuffer())
-        mStencilAttachment.Renderbuffer()->SetInitialized(true);
-
-    if (mDepthStencilAttachment.HasUninitializedRenderbuffer())
-        mDepthStencilAttachment.Renderbuffer()->SetInitialized(true);
+    if (mDepthAttachment.HasUninitializedImageData())
+        mDepthAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+    if (mStencilAttachment.HasUninitializedImageData())
+        mStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+    if (mDepthStencilAttachment.HasUninitializedImageData())
+        mDepthStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
 
     return true;
 }
 
 bool WebGLFramebuffer::CheckColorAttachementNumber(GLenum attachment, const char * functionName) const
 {
     const char* const errorFormating = "%s: attachment: invalid enum value 0x%x";
 
--- a/content/canvas/src/WebGLFramebuffer.h
+++ b/content/canvas/src/WebGLFramebuffer.h
@@ -73,17 +73,18 @@ public:
         }
         GLenum TexImageTarget() const {
             return mTexImageTarget;
         }
         GLint TexImageLevel() const {
             return mTexImageLevel;
         }
 
-        bool HasUninitializedRenderbuffer() const;
+        bool HasUninitializedImageData() const;
+        void SetImageDataStatus(WebGLImageDataStatus x);
 
         void Reset() {
             mTexturePtr = nullptr;
             mRenderbufferPtr = nullptr;
         }
 
         const WebGLRectangleObject* RectangleObject() const;
         bool HasSameDimensionsAs(const Attachment& other) const;
@@ -156,17 +157,17 @@ public:
     void FinalizeAttachments() const;
 
     virtual JSObject* WrapObject(JSContext *cx,
                                  JS::Handle<JSObject*> scope) MOZ_OVERRIDE;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLFramebuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLFramebuffer)
 
-    bool CheckAndInitializeRenderbuffers();
+    bool CheckAndInitializeAttachments();
 
     bool CheckColorAttachementNumber(GLenum attachment, const char * functionName) const;
 
     GLuint mGLName;
     bool mHasEverBeenBound;
 
     void EnsureColorAttachments(size_t colorAttachmentId);
 
--- a/content/canvas/src/WebGLRenderbuffer.cpp
+++ b/content/canvas/src/WebGLRenderbuffer.cpp
@@ -43,17 +43,17 @@ WebGLRenderbuffer::WrapObject(JSContext 
 
 WebGLRenderbuffer::WebGLRenderbuffer(WebGLContext *context)
     : WebGLContextBoundObject(context)
     , mPrimaryRB(0)
     , mSecondaryRB(0)
     , mInternalFormat(0)
     , mInternalFormatForGL(0)
     , mHasEverBeenBound(false)
-    , mInitialized(false)
+    , mImageDataStatus(WebGLImageDataStatus::NoImageData)
 {
     SetIsDOMBinding();
     mContext->MakeContextCurrent();
 
     mContext->gl->fGenRenderbuffers(1, &mPrimaryRB);
     if (!SupportsDepthStencil(mContext->gl))
         mContext->gl->fGenRenderbuffers(1, &mSecondaryRB);
 
--- a/content/canvas/src/WebGLRenderbuffer.h
+++ b/content/canvas/src/WebGLRenderbuffer.h
@@ -28,18 +28,23 @@ public:
         DeleteOnce();
     }
 
     void Delete();
 
     bool HasEverBeenBound() { return mHasEverBeenBound; }
     void SetHasEverBeenBound(bool x) { mHasEverBeenBound = x; }
 
-    bool Initialized() const { return mInitialized; }
-    void SetInitialized(bool aInitialized) { mInitialized = aInitialized; }
+    bool HasUninitializedImageData() const { return mImageDataStatus == WebGLImageDataStatus::UninitializedImageData; }
+    void SetImageDataStatus(WebGLImageDataStatus x) {
+        // there is no way to go from having image data to not having any
+        MOZ_ASSERT(x != WebGLImageDataStatus::NoImageData ||
+                   mImageDataStatus == WebGLImageDataStatus::NoImageData);
+        mImageDataStatus = x;
+    }
 
     GLenum InternalFormat() const { return mInternalFormat; }
     void SetInternalFormat(GLenum aInternalFormat) { mInternalFormat = aInternalFormat; }
 
     GLenum InternalFormatForGL() const { return mInternalFormatForGL; }
     void SetInternalFormatForGL(GLenum aInternalFormatForGL) { mInternalFormatForGL = aInternalFormatForGL; }
 
     int64_t MemoryUsage() const;
@@ -61,15 +66,15 @@ public:
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLRenderbuffer)
 
 protected:
     GLuint mPrimaryRB;
     GLuint mSecondaryRB;
     GLenum mInternalFormat;
     GLenum mInternalFormatForGL;
     bool mHasEverBeenBound;
-    bool mInitialized;
+    WebGLImageDataStatus mImageDataStatus;
 
     friend class WebGLFramebuffer;
 };
 } // namespace mozilla
 
 #endif
--- a/content/canvas/src/WebGLTexture.cpp
+++ b/content/canvas/src/WebGLTexture.cpp
@@ -40,17 +40,17 @@ WebGLTexture::Delete() {
     mImageInfos.Clear();
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteTextures(1, &mGLName);
     LinkedListElement<WebGLTexture>::removeFrom(mContext->mTextures);
 }
 
 int64_t
 WebGLTexture::ImageInfo::MemoryUsage() const {
-    if (!mIsDefined)
+    if (mImageDataStatus == WebGLImageDataStatus::NoImageData)
         return 0;
     int64_t texelSizeInBits = WebGLContext::GetBitsPerTexel(mFormat, mType);
     return int64_t(mWidth) * int64_t(mHeight) * texelSizeInBits / 8;
 }
 
 int64_t
 WebGLTexture::MemoryUsage() const {
     if (IsDeleted())
@@ -134,24 +134,24 @@ WebGLTexture::Bind(GLenum aTarget) {
     }
 
     mHasEverBeenBound = true;
 }
 
 void
 WebGLTexture::SetImageInfo(GLenum aTarget, GLint aLevel,
                   GLsizei aWidth, GLsizei aHeight,
-                  GLenum aFormat, GLenum aType)
+                  GLenum aFormat, GLenum aType, WebGLImageDataStatus aStatus)
 {
     if ( (aTarget == LOCAL_GL_TEXTURE_2D) != (mTarget == LOCAL_GL_TEXTURE_2D) )
         return;
 
     EnsureMaxLevelWithCustomImagesAtLeast(aLevel);
 
-    ImageInfoAt(aTarget, aLevel) = ImageInfo(aWidth, aHeight, aFormat, aType);
+    ImageInfoAt(aTarget, aLevel) = ImageInfo(aWidth, aHeight, aFormat, aType, aStatus);
 
     if (aLevel > 0)
         SetCustomMipmap();
 
     SetDontKnowIfNeedFakeBlack();
 }
 
 void
@@ -250,17 +250,17 @@ WebGLTexture::NeedFakeBlack() {
     if (mFakeBlackStatus == DoNotNeedFakeBlack)
         return false;
 
     if (mFakeBlackStatus == DontKnowIfNeedFakeBlack) {
         // Determine if the texture needs to be faked as a black texture.
         // See 3.8.2 Shader Execution in the OpenGL ES 2.0.24 spec.
 
         for (size_t face = 0; face < mFacesCount; ++face) {
-            if (!ImageInfoAtFace(face, 0).mIsDefined) {
+            if (ImageInfoAtFace(face, 0).mImageDataStatus != WebGLImageDataStatus::InitializedImageData) {
                 // In case of undefined texture image, we don't print any message because this is a very common
                 // and often legitimate case, for example when doing asynchronous texture loading.
                 // An extreme case of this is the photowall google demo.
                 // Exiting early here allows us to avoid making noise on valid webgl code.
                 mFakeBlackStatus = DoNeedFakeBlack;
                 return true;
             }
         }
--- a/content/canvas/src/WebGLTexture.h
+++ b/content/canvas/src/WebGLTexture.h
@@ -61,58 +61,66 @@ protected:
     bool mHasEverBeenBound;
     GLuint mGLName;
 
     // we store information about the various images that are part of
     // this texture (cubemap faces, mipmap levels)
 
 public:
 
-    class ImageInfo : public WebGLRectangleObject {
+    class ImageInfo
+        : public WebGLRectangleObject
+    {
     public:
         ImageInfo()
             : mFormat(0)
             , mType(0)
-            , mIsDefined(false)
+            , mImageDataStatus(WebGLImageDataStatus::NoImageData)
         {}
 
         ImageInfo(GLsizei width, GLsizei height,
-                  GLenum format, GLenum type)
+                  GLenum format, GLenum type, WebGLImageDataStatus status)
             : WebGLRectangleObject(width, height)
             , mFormat(format)
             , mType(type)
-            , mIsDefined(true)
-        {}
+            , mImageDataStatus(status)
+        {
+            // shouldn't use this constructor to construct a null ImageInfo
+            MOZ_ASSERT(status != WebGLImageDataStatus::NoImageData);
+        }
 
         bool operator==(const ImageInfo& a) const {
-            return mIsDefined == a.mIsDefined &&
-                   mWidth     == a.mWidth &&
-                   mHeight    == a.mHeight &&
-                   mFormat    == a.mFormat &&
-                   mType      == a.mType;
+            return mImageDataStatus == a.mImageDataStatus &&
+                   mWidth  == a.mWidth &&
+                   mHeight == a.mHeight &&
+                   mFormat == a.mFormat &&
+                   mType   == a.mType;
         }
         bool operator!=(const ImageInfo& a) const {
             return !(*this == a);
         }
         bool IsSquare() const {
             return mWidth == mHeight;
         }
         bool IsPositive() const {
             return mWidth > 0 && mHeight > 0;
         }
         bool IsPowerOfTwo() const {
             return is_pot_assuming_nonnegative(mWidth) &&
                    is_pot_assuming_nonnegative(mHeight); // negative sizes should never happen (caught in texImage2D...)
         }
+        bool HasUninitializedImageData() const {
+            return mImageDataStatus == WebGLImageDataStatus::UninitializedImageData;
+        }
         int64_t MemoryUsage() const;
         GLenum Format() const { return mFormat; }
         GLenum Type() const { return mType; }
     protected:
         GLenum mFormat, mType;
-        bool mIsDefined;
+        WebGLImageDataStatus mImageDataStatus;
 
         friend class WebGLTexture;
     };
 
 private:
     static size_t FaceForTarget(GLenum target) {
         // Call this out explicitly:
         MOZ_ASSERT(target != LOCAL_GL_TEXTURE_CUBE_MAP);
@@ -144,34 +152,43 @@ public:
     }
 
     const ImageInfo& ImageInfoAt(GLenum imageTarget, GLint level) const {
         return const_cast<WebGLTexture*>(this)->ImageInfoAt(imageTarget, level);
     }
 
     bool HasImageInfoAt(GLenum imageTarget, GLint level) const {
         MOZ_ASSERT(imageTarget);
-        
+
         size_t face = FaceForTarget(imageTarget);
         CheckedUint32 checked_index = CheckedUint32(level) * mFacesCount + face;
         return checked_index.isValid() &&
                checked_index.value() < mImageInfos.Length() &&
-               ImageInfoAt(imageTarget, level).mIsDefined;
+               ImageInfoAt(imageTarget, level).mImageDataStatus != WebGLImageDataStatus::NoImageData;
     }
 
     ImageInfo& ImageInfoBase() {
         return ImageInfoAtFace(0, 0);
     }
 
     const ImageInfo& ImageInfoBase() const {
         return ImageInfoAtFace(0, 0);
     }
 
     int64_t MemoryUsage() const;
 
+    void SetImageDataStatus(GLenum imageTarget, GLint level, WebGLImageDataStatus newStatus) {
+        MOZ_ASSERT(HasImageInfoAt(imageTarget, level));
+        ImageInfo& imageInfo = ImageInfoAt(imageTarget, level);
+        // there is no way to go from having image data to not having any
+        MOZ_ASSERT(newStatus != WebGLImageDataStatus::NoImageData ||
+                   imageInfo.mImageDataStatus == WebGLImageDataStatus::NoImageData);
+        imageInfo.mImageDataStatus = newStatus;
+    }
+
 protected:
 
     GLenum mTarget;
     GLenum mMinFilter, mMagFilter, mWrapS, mWrapT;
 
     size_t mFacesCount, mMaxLevelWithCustomImages;
     nsTArray<ImageInfo> mImageInfos;
 
@@ -198,17 +215,17 @@ protected:
 public:
 
     void SetDontKnowIfNeedFakeBlack();
 
     void Bind(GLenum aTarget);
 
     void SetImageInfo(GLenum aTarget, GLint aLevel,
                       GLsizei aWidth, GLsizei aHeight,
-                      GLenum aFormat, GLenum aType);
+                      GLenum aFormat, GLenum aType, WebGLImageDataStatus aStatus);
 
     void SetMinFilter(GLenum aMinFilter) {
         mMinFilter = aMinFilter;
         SetDontKnowIfNeedFakeBlack();
     }
     void SetMagFilter(GLenum aMagFilter) {
         mMagFilter = aMagFilter;
         SetDontKnowIfNeedFakeBlack();
--- a/content/canvas/src/WebGLTypes.h
+++ b/content/canvas/src/WebGLTypes.h
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #ifndef WEBGLTYPES_H_
 #define WEBGLTYPES_H_
 
+#include "mozilla/TypedEnum.h"
+
 // Most WebIDL typedefs are identical to their OpenGL counterparts.
 #include "GLTypes.h"
 
 // Manual reflection of WebIDL typedefs that are different from their
 // OpenGL counterparts.
 typedef int64_t WebGLsizeiptr;
 typedef int64_t WebGLintptr;
 typedef bool WebGLboolean;
@@ -18,16 +20,32 @@ typedef bool WebGLboolean;
 namespace mozilla {
 
 enum FakeBlackStatus { DoNotNeedFakeBlack, DoNeedFakeBlack, DontKnowIfNeedFakeBlack };
 
 struct VertexAttrib0Status {
     enum { Default, EmulatedUninitializedArray, EmulatedInitializedArray };
 };
 
+/*
+ * Enum to track the status of image data (renderbuffer or texture image) presence
+ * and initialization.
+ *
+ * - NoImageData is the initial state before any image data is allocated.
+ * - InitializedImageData is the state after image data is allocated and initialized.
+ * - UninitializedImageData is an intermediate state where data is allocated but not
+ *   initialized. It is the state that renderbuffers are in after a renderbufferStorage call,
+ *   and it is the state that texture images are in after a texImage2D call with null data.
+ */
+MOZ_BEGIN_ENUM_CLASS(WebGLImageDataStatus, int)
+    NoImageData,
+    UninitializedImageData,
+    InitializedImageData
+MOZ_END_ENUM_CLASS(WebGLImageDataStatus)
+
 namespace WebGLTexelConversions {
 
 /*
  * The formats that may participate, either as source or destination formats,
  * in WebGL texture conversions. This includes:
  *  - all the formats accepted by WebGL.texImage2D, e.g. RGBA4444
  *  - additional formats provided by extensions, e.g. RGB32F
  *  - additional source formats, depending on browser details, used when uploading