Bug 1286348 - Reintroduce support for UNPACK_{FLIP_Y,PREMULTIPLY_ALPHA}. - r=mtseng
authorJeff Gilbert <jgilbert@mozilla.com>
Mon, 11 Jul 2016 22:51:19 -0700
changeset 387157 8d95a6d0855871245bd01df1f81c5ab93ec48700
parent 387156 4be1c47c9cbc1e827667226574cb00391e1a124c
child 387158 b15f05a94c716fdbed45b5f46a0fd5b8ae6443ef
push id22898
push userCallek@gmail.com
push dateWed, 13 Jul 2016 13:20:13 +0000
reviewersmtseng
bugs1286348
milestone50.0a1
Bug 1286348 - Reintroduce support for UNPACK_{FLIP_Y,PREMULTIPLY_ALPHA}. - r=mtseng MozReview-Commit-ID: 2KGHc3I2HzT
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextUtils.cpp
dom/canvas/WebGLTexture.h
dom/canvas/WebGLTextureUpload.cpp
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -14,284 +14,326 @@
 #include "nsLayoutUtils.h"
 #include "WebGLContext.h"
 #include "WebGLTexelConversions.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 namespace webgl {
 
-TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, uint32_t alignment,
-                             uint32_t rowLength, uint32_t imageHeight, uint32_t width,
-                             uint32_t height, uint32_t depth, bool hasData)
-    : mAlignment(alignment)
-    , mRowLength(rowLength)
-    , mImageHeight(imageHeight)
-
-    , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
-    , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
-    , mSkipImages(webgl->mPixelStore_UnpackSkipImages)
-
-    , mWidth(width)
-    , mHeight(height)
-    , mDepth(depth)
-
-    , mHasData(hasData)
-{ }
-
-static GLenum
-DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level,
-                const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset,
-                GLsizei width, GLsizei height, GLsizei depth, const void* data)
-{
-    if (isSubImage) {
-        return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height,
-                             depth, dui->ToPacking(), data);
-    } else {
-        return DoTexImage(gl, target, level, dui, width, height, depth, data);
-    }
-}
-
-/*static*/ void
-TexUnpackBlob::OriginsForDOM(WebGLContext* webgl, gl::OriginPos* const out_src,
-                             gl::OriginPos* const out_dst)
-{
-    // Our surfaces are TopLeft.
-    *out_src = gl::OriginPos::TopLeft;
-
-    // WebGL specs the default as passing DOM elements top-left first.
-    // Thus y-flip would give us bottom-left.
-    *out_dst = webgl->mPixelStore_FlipY ? gl::OriginPos::BottomLeft
-                                        : gl::OriginPos::TopLeft;
-}
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// TexUnpackBytes
-
-static uint32_t
-FallbackOnZero(uint32_t val, uint32_t fallback)
-{
-    return (val ? val : fallback);
-}
-
-TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, uint32_t width, uint32_t height,
-                               uint32_t depth, const void* bytes)
-    : TexUnpackBlob(webgl, webgl->mPixelStore_UnpackAlignment,
-                    FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
-                    FallbackOnZero(webgl->mPixelStore_UnpackImageHeight, height),
-                    width, height, depth, bool(bytes))
-    , mBytes(bytes)
-{ }
-
 static bool
 UnpackFormatHasAlpha(GLenum unpackFormat)
 {
     switch (unpackFormat) {
     case LOCAL_GL_ALPHA:
     case LOCAL_GL_LUMINANCE_ALPHA:
     case LOCAL_GL_RGBA:
         return true;
 
     default:
         return false;
     }
 }
 
 static WebGLTexelFormat
-FormatFromPacking(const webgl::PackingInfo& pi)
+FormatForPackingInfo(const PackingInfo& pi)
 {
     switch (pi.type) {
+    case LOCAL_GL_UNSIGNED_BYTE:
+        switch (pi.format) {
+        case LOCAL_GL_RED:
+        case LOCAL_GL_LUMINANCE:
+        case LOCAL_GL_RED_INTEGER:
+            return WebGLTexelFormat::R8;
+
+        case LOCAL_GL_ALPHA:
+            return WebGLTexelFormat::A8;
+
+        case LOCAL_GL_LUMINANCE_ALPHA:
+            return WebGLTexelFormat::RA8;
+
+        case LOCAL_GL_RGB:
+        case LOCAL_GL_RGB_INTEGER:
+            return WebGLTexelFormat::RGB8;
+
+        case LOCAL_GL_RGBA:
+        case LOCAL_GL_RGBA_INTEGER:
+            return WebGLTexelFormat::RGBA8;
+
+        case LOCAL_GL_RG:
+        case LOCAL_GL_RG_INTEGER:
+            return WebGLTexelFormat::RG8;
+
+        default:
+            break;
+        }
+        break;
+
     case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
-        return WebGLTexelFormat::RGB565;
+        if (pi.format == LOCAL_GL_RGB)
+            return WebGLTexelFormat::RGB565;
+        break;
 
     case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
-        return WebGLTexelFormat::RGBA5551;
+        if (pi.format == LOCAL_GL_RGBA)
+            return WebGLTexelFormat::RGBA5551;
+        break;
 
     case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
-        return WebGLTexelFormat::RGBA4444;
-
-    case LOCAL_GL_UNSIGNED_BYTE:
-        switch (pi.format) {
-        case LOCAL_GL_LUMINANCE:        return WebGLTexelFormat::R8;
-        case LOCAL_GL_ALPHA:            return WebGLTexelFormat::A8;
-        case LOCAL_GL_LUMINANCE_ALPHA:  return WebGLTexelFormat::RA8;
-        case LOCAL_GL_RGB:              return WebGLTexelFormat::RGB8;
-        case LOCAL_GL_SRGB:             return WebGLTexelFormat::RGB8;
-        case LOCAL_GL_RGBA:             return WebGLTexelFormat::RGBA8;
-        case LOCAL_GL_SRGB_ALPHA:       return WebGLTexelFormat::RGBA8;
-        }
+        if (pi.format == LOCAL_GL_RGBA)
+            return WebGLTexelFormat::RGBA4444;
+        break;
 
     case LOCAL_GL_HALF_FLOAT:
     case LOCAL_GL_HALF_FLOAT_OES:
         switch (pi.format) {
-        case LOCAL_GL_LUMINANCE:        return WebGLTexelFormat::R16F;
-        case LOCAL_GL_ALPHA:            return WebGLTexelFormat::A16F;
-        case LOCAL_GL_LUMINANCE_ALPHA:  return WebGLTexelFormat::RA16F;
-        case LOCAL_GL_RGB:              return WebGLTexelFormat::RGB16F;
-        case LOCAL_GL_RGBA:             return WebGLTexelFormat::RGBA16F;
+        case LOCAL_GL_RED:
+        case LOCAL_GL_LUMINANCE:
+            return WebGLTexelFormat::R16F;
+
+        case LOCAL_GL_ALPHA:           return WebGLTexelFormat::A16F;
+        case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA16F;
+        case LOCAL_GL_RG:              return WebGLTexelFormat::RG16F;
+        case LOCAL_GL_RGB:             return WebGLTexelFormat::RGB16F;
+        case LOCAL_GL_RGBA:            return WebGLTexelFormat::RGBA16F;
+
+        default:
+            break;
         }
+        break;
 
     case LOCAL_GL_FLOAT:
         switch (pi.format) {
-        case LOCAL_GL_LUMINANCE:        return WebGLTexelFormat::R32F;
-        case LOCAL_GL_ALPHA:            return WebGLTexelFormat::A32F;
-        case LOCAL_GL_LUMINANCE_ALPHA:  return WebGLTexelFormat::RA32F;
-        case LOCAL_GL_RGB:              return WebGLTexelFormat::RGB32F;
-        case LOCAL_GL_RGBA:             return WebGLTexelFormat::RGBA32F;
+        case LOCAL_GL_RED:
+        case LOCAL_GL_LUMINANCE:
+            return WebGLTexelFormat::R32F;
+
+        case LOCAL_GL_ALPHA:           return WebGLTexelFormat::A32F;
+        case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA32F;
+        case LOCAL_GL_RG:              return WebGLTexelFormat::RG32F;
+        case LOCAL_GL_RGB:             return WebGLTexelFormat::RGB32F;
+        case LOCAL_GL_RGBA:            return WebGLTexelFormat::RGBA32F;
+
+        default:
+            break;
         }
+        break;
+
+    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
+        if (pi.format == LOCAL_GL_RGB)
+            return WebGLTexelFormat::RGB11F11F10F;
+        break;
+
+    default:
+        break;
     }
 
     return WebGLTexelFormat::FormatNotSupportingAnyConversion;
 }
 
+////////////////////
+
+static uint32_t
+FallbackOnZero(uint32_t val, uint32_t fallback)
+{
+    return (val ? val : fallback);
+}
+
+TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
+                             uint32_t rowLength, uint32_t width, uint32_t height,
+                             uint32_t depth, bool isSrcPremult)
+    : mAlignment(webgl->mPixelStore_UnpackAlignment)
+    , mRowLength(rowLength)
+    , mImageHeight(FallbackOnZero(webgl->mPixelStore_UnpackImageHeight, height))
+
+    , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
+    , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
+    , mSkipImages(IsTarget3D(target) ? webgl->mPixelStore_UnpackSkipImages : 0)
+
+    , mWidth(width)
+    , mHeight(height)
+    , mDepth(depth)
+
+    , mIsSrcPremult(isSrcPremult)
+{
+    MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
+}
+
+bool
+TexUnpackBlob::ConvertIfNeeded(WebGLContext* webgl, const char* funcName,
+                               const void* srcBytes, uint32_t srcStride, uint8_t srcBPP,
+                               WebGLTexelFormat srcFormat,
+                               const webgl::DriverUnpackInfo* dstDUI,
+                               const void** const out_bytes,
+                               UniqueBuffer* const out_anchoredBuffer) const
+{
+    *out_bytes = srcBytes;
+
+    if (!HasData() || !mWidth || !mHeight || !mDepth)
+        return true;
+
+    //////
+
+    const auto totalSkipRows = mSkipRows + CheckedUint32(mSkipImages) * mImageHeight;
+    const auto offset = mSkipPixels * CheckedUint32(srcBPP) + totalSkipRows * srcStride;
+    if (!offset.isValid()) {
+        webgl->ErrorOutOfMemory("%s: Invalid offset calculation during conversion.",
+                                funcName);
+        return false;
+    }
+    const uint32_t skipBytes = offset.value();
+
+    auto const srcBegin = (const uint8_t*)srcBytes + skipBytes;
+
+    //////
+
+    const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
+                                                     : gl::OriginPos::BottomLeft);
+    const auto dstOrigin = gl::OriginPos::BottomLeft;
+    const bool isDstPremult = webgl->mPixelStore_PremultiplyAlpha;
+
+    const auto pi = dstDUI->ToPacking();
+    const auto dstFormat = FormatForPackingInfo(pi);
+
+    const auto dstBPP = webgl::BytesPerPixel(pi);
+    const auto dstWidthBytes = CheckedUint32(dstBPP) * mWidth;
+    const auto dstRowLengthBytes = CheckedUint32(dstBPP) * mRowLength;
+
+    const auto dstAlignment = mAlignment;
+    const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
+
+    //////
+
+    const auto dstTotalRows = CheckedUint32(mDepth - 1) * mImageHeight + mHeight;
+
+    const auto dstSize = skipBytes + (dstTotalRows - 1) * dstStride + dstWidthBytes;
+    if (!dstSize.isValid()) {
+        webgl->ErrorOutOfMemory("%s: Invalid dstSize calculation during conversion.",
+                                funcName);
+        return false;
+    }
+
+    //////
+
+    bool needsConvert = (srcOrigin != dstOrigin ||
+                         srcFormat != dstFormat ||
+                         srcStride != dstStride.value());
+
+    if (UnpackFormatHasAlpha(dstDUI->unpackFormat)) {
+        needsConvert |= (mIsSrcPremult != isDstPremult);
+    }
+
+    if (!needsConvert)
+        return true;
+
+    ////////////
+    // Ugh, ok, fine!
+
+    webgl->GenerateWarning("%s: Incurred CPU data conversion, which is slow.",
+                           funcName);
+
+    //////
+
+    *out_anchoredBuffer = calloc(1, dstSize.value());
+    if (!out_anchoredBuffer->get()) {
+        webgl->ErrorOutOfMemory("%s: Unable to allocate buffer during conversion.",
+                                funcName);
+        return false;
+    }
+    const auto dstBegin = (uint8_t*)out_anchoredBuffer->get() + skipBytes;
+
+    //////
+
+    // And go!:
+    bool wasTrivial;
+    if (!ConvertImage(mWidth, dstTotalRows.value(),
+                      srcBegin, srcStride, srcOrigin, srcFormat, mIsSrcPremult,
+                      dstBegin, dstStride.value(), dstOrigin, dstFormat, isDstPremult,
+                      &wasTrivial))
+    {
+        webgl->ErrorImplementationBug("%s: ConvertImage failed.", funcName);
+        return false;
+    }
+
+    if (!wasTrivial) {
+        webgl->GenerateWarning("%s: Chosen format/type incurred an expensive reformat:"
+                               " 0x%04x/0x%04x",
+                               funcName, dstDUI->unpackFormat, dstDUI->unpackType);
+    }
+
+    *out_bytes = out_anchoredBuffer->get();
+    return true;
+}
+
+static GLenum
+DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level,
+                const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset,
+                GLsizei width, GLsizei height, GLsizei depth, const void* data)
+{
+    if (isSubImage) {
+        return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height,
+                             depth, dui->ToPacking(), data);
+    } else {
+        return DoTexImage(gl, target, level, dui, width, height, depth, data);
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+// TexUnpackBytes
+
+TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
+                               uint32_t width, uint32_t height, uint32_t depth,
+                               const void* bytes)
+    : TexUnpackBlob(webgl, target,
+                    FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width), width,
+                    height, depth, false)
+    , mBytes(bytes)
+{ }
+
 bool
 TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                               WebGLTexture* tex, TexImageTarget target, GLint level,
                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
                               GLint yOffset, GLint zOffset, GLenum* const out_error) const
 {
     WebGLContext* webgl = tex->mContext;
-    gl::GLContext* gl = webgl->gl;
 
-    const void* uploadBytes = mBytes;
-    UniqueBuffer tempBuffer;
-
-    do {
-        if (!mBytes || !mWidth || !mHeight || !mDepth)
-            break;
-
-        if (webgl->IsWebGL2())
-            break;
-        MOZ_ASSERT(mDepth == 1);
-
-        const webgl::PackingInfo pi = { dui->unpackFormat, dui->unpackType };
-
-        const bool needsYFlip = webgl->mPixelStore_FlipY;
-
-        bool needsAlphaPremult = webgl->mPixelStore_PremultiplyAlpha;
-        if (!UnpackFormatHasAlpha(pi.format))
-            needsAlphaPremult = false;
-
-        if (!needsYFlip && !needsAlphaPremult)
-            break;
-
-        ////////////
-        // This is literally the worst.
+    const auto pi = dui->ToPacking();
+    const auto format = FormatForPackingInfo(pi);
 
-        if (mSkipPixels || mSkipRows || mSkipImages ||
-            mRowLength != mWidth ||
-            mImageHeight != mHeight)
-        {
-            webgl->ErrorInvalidOperation("%s: FLIP_Y and PREMULTIPLY_ALPHA are"
-                                         " incompatible with WebGL 2's new UNPACK_*"
-                                         " settings.",
-                                         funcName);
-            return false;
-        }
-
-        if (mDepth != 1) {
-            webgl->ErrorInvalidOperation("%s: FLIP_Y and PREMULTIPLY_ALPHA are"
-                                         " incompatible with 3D textures.",
-                                         funcName);
-            return false;
-        }
-
-        webgl->GenerateWarning("%s: Uploading ArrayBuffers with FLIP_Y or"
-                               " PREMULTIPLY_ALPHA is slow.",
-                               funcName);
-
-        const auto bytesPerPixel = webgl::BytesPerPixel(pi);
-
-        const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
-        const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
-        const auto imageStride = rowStride * mImageHeight;
-
-        if (!imageStride.isValid()) {
-            webgl->ErrorOutOfMemory("%s: Invalid calculation during"
-                                    " FLIP_Y/PREMULTIPLY_ALPHA handling.",
-                                    funcName);
-            return false;
-        }
+    const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+    const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
+    const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
+    if (!rowStride.isValid()) {
+        MOZ_CRASH("Should be checked earlier.");
+    }
 
-        tempBuffer = malloc(imageStride.value());
-        if (!tempBuffer) {
-            webgl->ErrorOutOfMemory("%s: OOM during FLIP_Y/PREMULTIPLY_ALPHA handling.",
-                                    funcName);
-            return false;
-        }
-
-        if (!needsAlphaPremult) {
-            MOZ_ASSERT(needsYFlip);
-
-            const uint8_t* src = (const uint8_t*)mBytes;
-            const uint8_t* const srcEnd = src + rowStride.value() * mHeight;
-
-            uint8_t* dst = (uint8_t*)tempBuffer.get() + rowStride.value() * (mHeight - 1);
-
-            while (src != srcEnd) {
-                memcpy(dst, src, bytesPerRow.value());
-                src += rowStride.value();
-                dst -= rowStride.value();
-            }
-
-            uploadBytes = tempBuffer.get();
-            break;
-        }
+    const void* uploadBytes;
+    UniqueBuffer tempBuffer;
+    if (!ConvertIfNeeded(webgl, funcName, mBytes, rowStride.value(), bytesPerPixel,
+                         format, dui, &uploadBytes, &tempBuffer))
+    {
+        return false;
+    }
 
-        const auto texelFormat = FormatFromPacking(pi);
-        if (texelFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
-            MOZ_ASSERT(false, "Bad texelFormat from pi.");
-            webgl->ErrorOutOfMemory("%s: FormatFromPacking failed during"
-                                    " PREMULTIPLY_ALPHA handling.",
-                                    funcName);
-            return false;
-        }
-
-        const auto srcOrigin = gl::OriginPos::BottomLeft;
-        const auto dstOrigin = (needsYFlip ? gl::OriginPos::TopLeft
-                                           : gl::OriginPos::BottomLeft);
-
-        const bool srcPremultiplied = false;
-        const bool dstPremultiplied = needsAlphaPremult; // Always true here.
-
-        // And go!:
-        MOZ_ASSERT(srcOrigin != dstOrigin || srcPremultiplied != dstPremultiplied);
-        bool unused_wasTrivial;
-        if (!ConvertImage(mWidth, mHeight,
-                          mBytes, rowStride.value(), srcOrigin, texelFormat,
-                          srcPremultiplied,
-                          tempBuffer.get(), rowStride.value(), dstOrigin, texelFormat,
-                          dstPremultiplied, &unused_wasTrivial))
-        {
-            MOZ_ASSERT(false, "ConvertImage failed unexpectedly.");
-            webgl->ErrorOutOfMemory("%s: ConvertImage failed during PREMULTIPLY_ALPHA"
-                                    " handling.",
-                                    funcName);
-            return false;
-        }
-
-        uploadBytes = tempBuffer.get();
-    } while (false);
-
-    *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
-                                 zOffset, mWidth, mHeight, mDepth, uploadBytes);
+    *out_error = DoTexOrSubImage(isSubImage, webgl->gl, target, level, dui, xOffset,
+                                 yOffset, zOffset, mWidth, mHeight, mDepth, uploadBytes);
     return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackImage
 
-TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, uint32_t imageHeight,
+TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, TexImageTarget target,
                                uint32_t width, uint32_t height, uint32_t depth,
-                               const RefPtr<layers::Image>& image, bool isAlphaPremult)
-    : TexUnpackBlob(webgl, 0, image->GetSize().width, imageHeight, width, height, depth,
-                    true)
+                               layers::Image* image, bool isAlphaPremult)
+    : TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth,
+                    isAlphaPremult)
     , mImage(image)
-    , mIsAlphaPremult(isAlphaPremult)
 { }
 
 bool
 TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                               WebGLTexture* tex, TexImageTarget target, GLint level,
                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
                               GLint yOffset, GLint zOffset, GLenum* const out_error) const
 {
@@ -306,16 +348,23 @@ TexUnpackImage::TexOrSubImage(bool isSub
         *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
                                      yOffset, zOffset, mWidth, mHeight, mDepth,
                                      nullptr);
         if (*out_error)
             return false;
     }
 
     do {
+        if (mDepth != 1)
+            break;
+
+        const bool isDstPremult = webgl->mPixelStore_PremultiplyAlpha;
+        if (mIsSrcPremult != isDstPremult)
+            break;
+
         if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA)
             break;
 
         if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE)
             break;
 
         gl::ScopedFramebuffer scopedFB(gl);
         gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
@@ -329,530 +378,166 @@ TexUnpackImage::TexOrSubImage(bool isSub
             if (errorScope.GetError())
                 break;
         }
 
         const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
         if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
             break;
 
-        gl::OriginPos srcOrigin, dstOrigin;
-        OriginsForDOM(webgl, &srcOrigin, &dstOrigin);
-
         const gfx::IntSize destSize(mWidth, mHeight);
+        const auto dstOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
+                                                         : gl::OriginPos::BottomLeft);
         if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, destSize, scopedFB.FB(),
                                                       dstOrigin))
         {
             break;
         }
 
         // Blitting was successful, so we're done!
         *out_error = 0;
         return true;
     } while (false);
 
     webgl->GenerateWarning("%s: Failed to hit GPU-copy fast-path. Falling back to CPU"
                            " upload.",
                            funcName);
 
-    RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
-    if (!surface) {
-        webgl->ErrorOutOfMemory("%s: GetAsSourceSurface failed after blit failed for"
-                                " TexUnpackImage.",
+    const RefPtr<SourceSurface> surf = mImage->GetAsSourceSurface();
+
+    RefPtr<DataSourceSurface> dataSurf;
+    if (surf) {
+        // WARNING: OSX can lose our MakeCurrent here.
+        dataSurf = surf->GetDataSurface();
+    }
+    if (!dataSurf) {
+        webgl->ErrorOutOfMemory("%s: GetAsSourceSurface or GetDataSurface failed after"
+                                " blit failed for TexUnpackImage.",
                                 funcName);
         return false;
     }
 
-    TexUnpackSurface surfBlob(webgl, mImageHeight, mWidth, mHeight, mDepth, surface,
-                              mIsAlphaPremult);
+    const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf,
+                                    mIsSrcPremult);
 
     return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level,
                                   dui, xOffset, yOffset, zOffset, out_error);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackSurface
 
-TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, uint32_t imageHeight,
+TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target,
                                    uint32_t width, uint32_t height, uint32_t depth,
-                                   gfx::SourceSurface* surf, bool isAlphaPremult)
-    : TexUnpackBlob(webgl, 0, surf->GetSize().width, imageHeight, width, height, depth,
-                    true)
+                                   gfx::DataSourceSurface* surf, bool isAlphaPremult)
+    : TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth,
+                    isAlphaPremult)
     , mSurf(surf)
-    , mIsAlphaPremult(isAlphaPremult)
 { }
 
 //////////
 
 static bool
-GuessAlignment(const void* data, size_t bytesPerRow, size_t stride, size_t maxAlignment,
-               size_t* const out_alignment)
-{
-    size_t alignmentGuess = maxAlignment;
-    while (alignmentGuess) {
-        size_t guessStride = RoundUpToMultipleOf(bytesPerRow, alignmentGuess);
-        if (guessStride == stride &&
-            uintptr_t(data) % alignmentGuess == 0)
-        {
-            *out_alignment = alignmentGuess;
-            return true;
-        }
-        alignmentGuess /= 2;
-    }
-    return false;
-}
-
-static bool
-SupportsBGRA(gl::GLContext* gl)
-{
-    if (gl->IsANGLE())
-        return true;
-
-    return false;
-}
-
-/*static*/ bool
-TexUnpackSurface::UploadDataSurface(bool isSubImage, WebGLContext* webgl,
-                                    TexImageTarget target, GLint level,
-                                    const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                                    GLint yOffset, GLint zOffset, GLsizei width,
-                                    GLsizei height, gfx::DataSourceSurface* surf,
-                                    bool isSurfAlphaPremult, GLenum* const out_glError)
+GetFormatForSurf(gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat,
+                 uint8_t* const out_bpp)
 {
-    gl::GLContext* gl = webgl->GL();
-    MOZ_ASSERT(gl->IsCurrent());
-    *out_glError = 0;
-
-    if (isSurfAlphaPremult != webgl->mPixelStore_PremultiplyAlpha)
-        return false;
-
-    gl::OriginPos srcOrigin, dstOrigin;
-    OriginsForDOM(webgl, &srcOrigin, &dstOrigin);
-    if (srcOrigin != dstOrigin)
-        return false;
-
-    // This differs from the raw-data upload in that we choose how we do the unpack.
-    // (alignment, etc.)
-
-    // Uploading RGBX as RGBA and blitting to RGB is faster than repacking RGBX into
-    // RGB on the CPU. However, this is optimization is out-of-scope for now.
-
-    static const webgl::DriverUnpackInfo kInfoBGRA = {
-        LOCAL_GL_BGRA,
-        LOCAL_GL_BGRA,
-        LOCAL_GL_UNSIGNED_BYTE,
-    };
-
-    const webgl::DriverUnpackInfo* chosenDUI = nullptr;
-
-    switch (surf->GetFormat()) {
-    case gfx::SurfaceFormat::B8G8R8A8:
-        if (SupportsBGRA(gl) &&
-            dui->internalFormat == LOCAL_GL_RGBA &&
-            dui->unpackFormat == LOCAL_GL_RGBA &&
-            dui->unpackType == LOCAL_GL_UNSIGNED_BYTE)
-        {
-            chosenDUI = &kInfoBGRA;
-        }
-        break;
-
-    case gfx::SurfaceFormat::R8G8B8A8:
-        if (dui->unpackFormat == LOCAL_GL_RGBA &&
-            dui->unpackType == LOCAL_GL_UNSIGNED_BYTE)
-        {
-            chosenDUI = dui;
-        }
-        break;
-
-    case gfx::SurfaceFormat::R5G6B5_UINT16:
-        if (dui->unpackFormat == LOCAL_GL_RGB &&
-            dui->unpackType == LOCAL_GL_UNSIGNED_SHORT_5_6_5)
-        {
-            chosenDUI = dui;
-        }
-        break;
-
-    default:
-        break;
-    }
-
-    if (!chosenDUI)
-        return false;
-
-    gfx::DataSourceSurface::ScopedMap map(surf, gfx::DataSourceSurface::MapType::READ);
-    if (!map.IsMapped())
-        return false;
-
-    const webgl::PackingInfo pi = {chosenDUI->unpackFormat, chosenDUI->unpackType};
-    const auto bytesPerPixel = webgl::BytesPerPixel(pi);
-    const size_t bytesPerRow = width * bytesPerPixel;
-
-    const GLint kMaxUnpackAlignment = 8;
-    size_t unpackAlignment;
-    if (!GuessAlignment(map.GetData(), bytesPerRow, map.GetStride(), kMaxUnpackAlignment,
-                        &unpackAlignment))
-    {
-        return false;
-        // TODO: Consider using UNPACK_ settings to set the stride based on the too-large
-        // alignment used for some SourceSurfaces. (D2D allegedy likes alignment=16)
-    }
-
-    MOZ_ALWAYS_TRUE( webgl->gl->MakeCurrent() );
-    ScopedUnpackReset scopedReset(webgl);
-    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, unpackAlignment);
-
-    const GLsizei depth = 1;
-    GLenum error = DoTexOrSubImage(isSubImage, gl, target.get(), level, chosenDUI,
-                                   xOffset, yOffset, zOffset, width, height, depth,
-                                   map.GetData());
-    if (error) {
-        *out_glError = error;
-        return false;
-    }
-
-    return true;
-}
-
-////////////////////
-
-static bool
-GetFormatForSurf(gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat)
-{
-    gfx::SurfaceFormat surfFormat = surf->GetFormat();
-
+    const auto surfFormat = surf->GetFormat();
     switch (surfFormat) {
     case gfx::SurfaceFormat::B8G8R8A8:
         *out_texelFormat = WebGLTexelFormat::BGRA8;
+        *out_bpp = 4;
         return true;
 
     case gfx::SurfaceFormat::B8G8R8X8:
         *out_texelFormat = WebGLTexelFormat::BGRX8;
+        *out_bpp = 4;
         return true;
 
     case gfx::SurfaceFormat::R8G8B8A8:
         *out_texelFormat = WebGLTexelFormat::RGBA8;
+        *out_bpp = 4;
         return true;
 
     case gfx::SurfaceFormat::R8G8B8X8:
         *out_texelFormat = WebGLTexelFormat::RGBX8;
+        *out_bpp = 4;
         return true;
 
     case gfx::SurfaceFormat::R5G6B5_UINT16:
         *out_texelFormat = WebGLTexelFormat::RGB565;
+        *out_bpp = 2;
         return true;
 
     case gfx::SurfaceFormat::A8:
         *out_texelFormat = WebGLTexelFormat::A8;
+        *out_bpp = 1;
         return true;
 
     case gfx::SurfaceFormat::YUV:
         // Ugh...
         NS_ERROR("We don't handle uploads from YUV sources yet.");
         // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
         // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
         return false;
 
     default:
         return false;
     }
 }
 
-static bool
-GetFormatForPackingTuple(GLenum packingFormat, GLenum packingType,
-                         WebGLTexelFormat* const out_texelFormat)
-{
-    switch (packingType) {
-    case LOCAL_GL_UNSIGNED_BYTE:
-        switch (packingFormat) {
-        case LOCAL_GL_RED:
-        case LOCAL_GL_LUMINANCE:
-        case LOCAL_GL_RED_INTEGER:
-            *out_texelFormat = WebGLTexelFormat::R8;
-            return true;
-
-        case LOCAL_GL_ALPHA:
-            *out_texelFormat = WebGLTexelFormat::A8;
-            return true;
-
-        case LOCAL_GL_LUMINANCE_ALPHA:
-            *out_texelFormat = WebGLTexelFormat::RA8;
-            return true;
-
-        case LOCAL_GL_RGB:
-        case LOCAL_GL_RGB_INTEGER:
-            *out_texelFormat = WebGLTexelFormat::RGB8;
-            return true;
-
-        case LOCAL_GL_RGBA:
-        case LOCAL_GL_RGBA_INTEGER:
-            *out_texelFormat = WebGLTexelFormat::RGBA8;
-            return true;
-
-        case LOCAL_GL_RG:
-        case LOCAL_GL_RG_INTEGER:
-            *out_texelFormat = WebGLTexelFormat::RG8;
-            return true;
-
-        default:
-            break;
-        }
-        break;
-
-    case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
-        switch (packingFormat) {
-        case LOCAL_GL_RGB:
-            *out_texelFormat = WebGLTexelFormat::RGB565;
-            return true;
-
-        default:
-            break;
-        }
-        break;
-
-    case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
-        switch (packingFormat) {
-        case LOCAL_GL_RGBA:
-            *out_texelFormat = WebGLTexelFormat::RGBA5551;
-            return true;
-
-        default:
-            break;
-        }
-        break;
-
-    case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
-        switch (packingFormat) {
-        case LOCAL_GL_RGBA:
-            *out_texelFormat = WebGLTexelFormat::RGBA4444;
-            return true;
-
-        default:
-            break;
-        }
-        break;
-
-    case LOCAL_GL_HALF_FLOAT:
-    case LOCAL_GL_HALF_FLOAT_OES:
-        switch (packingFormat) {
-        case LOCAL_GL_RED:
-        case LOCAL_GL_LUMINANCE:
-            *out_texelFormat = WebGLTexelFormat::R16F;
-            return true;
-
-        case LOCAL_GL_ALPHA:
-            *out_texelFormat = WebGLTexelFormat::A16F;
-            return true;
-
-        case LOCAL_GL_LUMINANCE_ALPHA:
-            *out_texelFormat = WebGLTexelFormat::RA16F;
-            return true;
-
-        case LOCAL_GL_RGB:
-            *out_texelFormat = WebGLTexelFormat::RGB16F;
-            return true;
-
-        case LOCAL_GL_RGBA:
-            *out_texelFormat = WebGLTexelFormat::RGBA16F;
-            return true;
-
-        case LOCAL_GL_RG:
-            *out_texelFormat = WebGLTexelFormat::RG16F;
-            return true;
-
-        default:
-            break;
-        }
-        break;
-
-    case LOCAL_GL_FLOAT:
-        switch (packingFormat) {
-        case LOCAL_GL_RED:
-        case LOCAL_GL_LUMINANCE:
-            *out_texelFormat = WebGLTexelFormat::R32F;
-            return true;
-
-        case LOCAL_GL_ALPHA:
-            *out_texelFormat = WebGLTexelFormat::A32F;
-            return true;
-
-        case LOCAL_GL_LUMINANCE_ALPHA:
-            *out_texelFormat = WebGLTexelFormat::RA32F;
-            return true;
-
-        case LOCAL_GL_RGB:
-            *out_texelFormat = WebGLTexelFormat::RGB32F;
-            return true;
-
-        case LOCAL_GL_RGBA:
-            *out_texelFormat = WebGLTexelFormat::RGBA32F;
-            return true;
-
-        case LOCAL_GL_RG:
-            *out_texelFormat = WebGLTexelFormat::RG32F;
-            return true;
-
-        default:
-            break;
-        }
-        break;
-
-    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
-        switch (packingFormat) {
-        case LOCAL_GL_RGB:
-            *out_texelFormat = WebGLTexelFormat::RGB11F11F10F;
-            return true;
-
-        default:
-            break;
-        }
-        break;
-    default:
-        break;
-    }
-
-    NS_ERROR("Unsupported EffectiveFormat dest format for DOM element upload.");
-    return false;
-}
-
-/*static*/ bool
-TexUnpackSurface::ConvertSurface(WebGLContext* webgl, const webgl::DriverUnpackInfo* dui,
-                                 gfx::DataSourceSurface* surf, bool isSurfAlphaPremult,
-                                 UniqueBuffer* const out_convertedBuffer,
-                                 uint8_t* const out_convertedAlignment,
-                                 bool* const out_wasTrivial, bool* const out_outOfMemory)
-{
-    *out_outOfMemory = false;
-
-    const size_t width = surf->GetSize().width;
-    const size_t height = surf->GetSize().height;
-
-    // Source args:
-
-    // After we call this, on OSX, our GLContext will no longer be current.
-    gfx::DataSourceSurface::ScopedMap srcMap(surf, gfx::DataSourceSurface::MapType::READ);
-    if (!srcMap.IsMapped())
-        return false;
-
-    const void* const srcBegin = srcMap.GetData();
-    const size_t srcStride = srcMap.GetStride();
-
-    WebGLTexelFormat srcFormat;
-    if (!GetFormatForSurf(surf, &srcFormat))
-        return false;
-
-    const bool srcPremultiplied = isSurfAlphaPremult;
-
-    // Dest args:
-
-    WebGLTexelFormat dstFormat;
-    if (!GetFormatForPackingTuple(dui->unpackFormat, dui->unpackType, &dstFormat))
-        return false;
-
-    const auto bytesPerPixel = webgl::BytesPerPixel({dui->unpackFormat, dui->unpackType});
-    const size_t dstRowBytes = bytesPerPixel * width;
-
-    const size_t dstAlignment = 8; // Just use the max!
-    const size_t dstStride = RoundUpToMultipleOf(dstRowBytes, dstAlignment);
-
-    CheckedUint32 checkedDstSize = dstStride;
-    checkedDstSize *= height;
-    if (!checkedDstSize.isValid()) {
-        *out_outOfMemory = true;
-        return false;
-    }
-
-    const size_t dstSize = checkedDstSize.value();
-
-    UniqueBuffer dstBuffer = malloc(dstSize);
-    if (!dstBuffer) {
-        *out_outOfMemory = true;
-        return false;
-    }
-    void* const dstBegin = dstBuffer.get();
-
-    gl::OriginPos srcOrigin, dstOrigin;
-    OriginsForDOM(webgl, &srcOrigin, &dstOrigin);
-
-    const bool dstPremultiplied = webgl->mPixelStore_PremultiplyAlpha;
-
-    // And go!:
-    bool wasTrivial;
-    if (!ConvertImage(width, height,
-                      srcBegin, srcStride, srcOrigin, srcFormat, srcPremultiplied,
-                      dstBegin, dstStride, dstOrigin, dstFormat, dstPremultiplied,
-                      &wasTrivial))
-    {
-        MOZ_ASSERT(false, "ConvertImage failed unexpectedly.");
-        NS_ERROR("ConvertImage failed unexpectedly.");
-        *out_outOfMemory = true;
-        return false;
-    }
-
-    *out_convertedBuffer = Move(dstBuffer);
-    *out_convertedAlignment = dstAlignment;
-    *out_wasTrivial = wasTrivial;
-    return true;
-}
-
-
-////////////////////
+//////////
 
 bool
 TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                 WebGLTexture* tex, TexImageTarget target, GLint level,
-                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                                const webgl::DriverUnpackInfo* dstDUI, GLint xOffset,
                                 GLint yOffset, GLint zOffset,
                                 GLenum* const out_error) const
 {
     WebGLContext* webgl = tex->mContext;
 
-    // MakeCurrent is a big mess in here, because mapping (and presumably unmapping) on
-    // OSX can lose our MakeCurrent. Therefore it's easiest to MakeCurrent just before we
-    // call into GL, instead of trying to keep MakeCurrent-ed.
-
-    RefPtr<gfx::DataSourceSurface> dataSurf = mSurf->GetDataSurface();
-    if (!dataSurf) {
-        // Since GetDataSurface didn't return error code, assume system
-        // is out of memory
-        webgl->ErrorOutOfMemory("%s: OOM in GetDataSurface for TexUnpackSurface.",
-                                funcName);
+    WebGLTexelFormat srcFormat;
+    uint8_t srcBPP;
+    if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) {
+        webgl->ErrorImplementationBug("%s: GetFormatForSurf failed for"
+                                      " WebGLTexelFormat::%u.",
+                                      funcName, uint32_t(mSurf->GetFormat()));
         return false;
     }
 
-    if (UploadDataSurface(isSubImage, webgl, target, level, dui, xOffset, yOffset,
-                          zOffset, mWidth, mHeight, dataSurf, mIsAlphaPremult, out_error))
-    {
-        return true;
-    }
+    gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ);
+    if (!map.IsMapped())
+        return false;
+
+    const auto srcBytes = map.GetData();
+    const auto srcStride = map.GetStride();
 
     // CPU conversion. (++numCopies)
 
-    UniqueBuffer convertedBuffer;
-    uint8_t convertedAlignment;
-    bool wasTrivial;
-    bool outOfMemory;
-    if (!ConvertSurface(webgl, dui, dataSurf, mIsAlphaPremult, &convertedBuffer,
-                        &convertedAlignment, &wasTrivial, &outOfMemory))
+    webgl->GenerateWarning("%s: Incurred CPU-side conversion, which is very slow.",
+                           funcName);
+
+    const void* uploadBytes;
+    UniqueBuffer tempBuffer;
+    if (!ConvertIfNeeded(webgl, funcName, srcBytes, srcStride, srcBPP, srcFormat,
+                         dstDUI, &uploadBytes, &tempBuffer))
     {
-        webgl->ErrorOutOfMemory("%s: %s in ConvertSurface for TexUnpackSurface.",
-                                funcName, outOfMemory ? "OOM" : "Failure");
         return false;
     }
 
-    if (!wasTrivial) {
-        webgl->GenerateWarning("%s: Chosen format/type incured an expensive reformat:"
-                               " 0x%04x/0x%04x",
-                               funcName, dui->unpackFormat, dui->unpackType);
-    }
+    //////
+
+    gl::GLContext* const gl = webgl->gl;
+    MOZ_ALWAYS_TRUE( gl->MakeCurrent() );
 
-    MOZ_ALWAYS_TRUE( webgl->gl->MakeCurrent() );
-    ScopedUnpackReset scopedReset(webgl);
-    webgl->gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, convertedAlignment);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength);
 
-    *out_error = DoTexOrSubImage(isSubImage, webgl->gl, target.get(), level, dui, xOffset,
-                                 yOffset, zOffset, mWidth, mHeight, mDepth,
-                                 convertedBuffer.get());
+    *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dstDUI, xOffset,
+                                 yOffset, zOffset, mWidth, mHeight, mDepth, uploadBytes);
+
+    gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
+
     return true;
 }
 
 } // namespace webgl
 } // namespace mozilla
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef TEX_UNPACK_BLOB_H_
 #define TEX_UNPACK_BLOB_H_
 
 #include "GLContextTypes.h"
 #include "GLTypes.h"
 #include "WebGLStrongTypes.h"
+#include "WebGLTypes.h"
 
 
 template <class T>
 class RefPtr;
 
 namespace mozilla {
 
 class UniqueBuffer;
@@ -51,94 +52,87 @@ public:
     const uint32_t mRowLength;
     const uint32_t mImageHeight;
     const uint32_t mSkipPixels;
     const uint32_t mSkipRows;
     const uint32_t mSkipImages;
     const uint32_t mWidth;
     const uint32_t mHeight;
     const uint32_t mDepth;
-    const bool mHasData;
+    const bool mIsSrcPremult;
 
 protected:
-    TexUnpackBlob(const WebGLContext* webgl, uint32_t alignment, uint32_t rowLength,
-                  uint32_t imageHeight, uint32_t width, uint32_t height, uint32_t depth,
-                  bool hasData);
+    TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target, uint32_t rowLength,
+                  uint32_t width, uint32_t height, uint32_t depth, bool isSrcPremult);
 
 public:
     virtual ~TexUnpackBlob() { }
 
+protected:
+    bool ConvertIfNeeded(WebGLContext* webgl, const char* funcName, const void* srcBytes,
+                         uint32_t srcStride, uint8_t srcBPP, WebGLTexelFormat srcFormat,
+                         const webgl::DriverUnpackInfo* dstDUI,
+                         const void** const out_bytes,
+                         UniqueBuffer* const out_anchoredBuffer) const;
+
+public:
+    virtual bool HasData() const { return true; }
+
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
-                                 WebGLTexture* tex, TexImageTarget target, GLint level,
-                                 const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                                 GLint yOffset, GLint zOffset,
-                                 GLenum* const out_error) const = 0;
-
-    static void OriginsForDOM(WebGLContext* webgl, gl::OriginPos* const out_src,
-                              gl::OriginPos* const out_dst);
+                               WebGLTexture* tex, TexImageTarget target, GLint level,
+                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                               GLint yOffset, GLint zOffset,
+                               GLenum* const out_error) const = 0;
 };
 
-class TexUnpackBytes : public TexUnpackBlob
+class TexUnpackBytes final : public TexUnpackBlob
 {
 public:
     const void* const mBytes;
 
-    TexUnpackBytes(const WebGLContext* webgl, uint32_t width, uint32_t height,
-                   uint32_t depth, const void* bytes);
+    TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
+                   uint32_t height, uint32_t depth, const void* bytes);
+
+    virtual bool HasData() const override { return bool(mBytes); }
+
+    virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
+                               WebGLTexture* tex, TexImageTarget target, GLint level,
+                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                               GLint yOffset, GLint zOffset,
+                               GLenum* const out_error) const override;
+};
+
+class TexUnpackImage final : public TexUnpackBlob
+{
+public:
+    const RefPtr<layers::Image> mImage;
+
+    TexUnpackImage(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
+                   uint32_t height, uint32_t depth, layers::Image* image,
+                   bool isAlphaPremult);
+
+    virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
+                               WebGLTexture* tex, TexImageTarget target, GLint level,
+                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                               GLint yOffset, GLint zOffset,
+                               GLenum* const out_error) const override;
+};
+
+class TexUnpackSurface final : public TexUnpackBlob
+{
+public:
+    const RefPtr<gfx::DataSourceSurface> mSurf;
+
+    TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
+                     uint32_t height, uint32_t depth, gfx::DataSourceSurface* surf,
+                     bool isAlphaPremult);
 
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                  WebGLTexture* tex, TexImageTarget target, GLint level,
                                  const webgl::DriverUnpackInfo* dui, GLint xOffset,
                                  GLint yOffset, GLint zOffset,
                                  GLenum* const out_error) const override;
 };
 
-class TexUnpackImage : public TexUnpackBlob
-{
-public:
-    const RefPtr<layers::Image> mImage;
-    const bool mIsAlphaPremult;
-
-    TexUnpackImage(const WebGLContext* webgl, uint32_t imageHeight, uint32_t width,
-                   uint32_t height, uint32_t depth, const RefPtr<layers::Image>& image,
-                   bool isAlphaPremult);
-
-    virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
-                                 WebGLTexture* tex, TexImageTarget target, GLint level,
-                                 const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                                 GLint yOffset, GLint zOffset,
-                                 GLenum* const out_error) const override;
-};
-
-class TexUnpackSurface : public TexUnpackBlob
-{
-public:
-    const RefPtr<gfx::SourceSurface> mSurf;
-    const bool mIsAlphaPremult;
-
-    TexUnpackSurface(const WebGLContext* webgl, uint32_t imageHeight, uint32_t width,
-                     uint32_t height, uint32_t depth, gfx::SourceSurface* surf,
-                     bool isAlphaPremult);
-
-    virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
-                                 WebGLTexture* tex, TexImageTarget target, GLint level,
-                                 const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                                 GLint yOffset, GLint zOffset,
-                                 GLenum* const out_error) const override;
-
-protected:
-    static bool ConvertSurface(WebGLContext* webgl, const webgl::DriverUnpackInfo* dui,
-                               gfx::DataSourceSurface* surf, bool isSurfAlphaPremult,
-                               UniqueBuffer* const out_convertedBuffer,
-                               uint8_t* const out_convertedAlignment,
-                               bool* const out_wasTrivial, bool* const out_outOfMemory);
-    static bool UploadDataSurface(bool isSubImage, WebGLContext* webgl,
-                                  TexImageTarget target, GLint level,
-                                  const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                                  GLint yOffset, GLint zOffset, GLsizei width,
-                                  GLsizei height, gfx::DataSourceSurface* surf,
-                                  bool isSurfAlphaPremult, GLenum* const out_glError);
-};
-
 } // namespace webgl
 } // namespace mozilla
 
 #endif // TEX_UNPACK_BLOB_H_
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -283,16 +283,17 @@ public:
     void ErrorInvalidEnum(const char* fmt = 0, ...);
     void ErrorInvalidOperation(const char* fmt = 0, ...);
     void ErrorInvalidValue(const char* fmt = 0, ...);
     void ErrorInvalidFramebufferOperation(const char* fmt = 0, ...);
     void ErrorInvalidEnumInfo(const char* info, GLenum enumValue);
     void ErrorInvalidEnumInfo(const char* info, const char* funcName,
                               GLenum enumValue);
     void ErrorOutOfMemory(const char* fmt = 0, ...);
+    void ErrorImplementationBug(const char* fmt = 0, ...);
 
     const char* ErrorName(GLenum error);
 
     /**
      * Return displayable name for GLenum.
      * This version is like gl::GLenumToStr but with out the GL_ prefix to
      * keep consistency with how errors are reported from WebGL.
      */
@@ -958,20 +959,16 @@ protected:
     bool ValidateTexImageSelection(const char* funcName, uint8_t funcDims,
                                    GLenum texImageTarget, GLint level, GLint xOffset,
                                    GLint yOffset, GLint zOffset, GLsizei width,
                                    GLsizei height, GLsizei depth,
                                    TexImageTarget* const out_target,
                                    WebGLTexture** const out_texture,
                                    WebGLTexture::ImageInfo** const out_imageInfo);
 
-    bool GetUnpackValuesForImage(const char* funcName, uint32_t srcImageWidth,
-                                 uint32_t srcImageHeight, uint32_t* const out_rowLength,
-                                 uint32_t* const out_imageHeight);
-
     bool ValidateUnpackInfo(const char* funcName, GLenum format, GLenum type,
                             webgl::PackingInfo* const out);
 
 // -----------------------------------------------------------------------------
 // Vertices Feature (WebGLContextVertices.cpp)
 public:
     void DrawArrays(GLenum mode, GLint first, GLsizei count);
     void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count,
@@ -1329,34 +1326,18 @@ protected:
     // helpers
 
     bool ConvertImage(size_t width, size_t height, size_t srcStride,
                       size_t dstStride, const uint8_t* src, uint8_t* dst,
                       WebGLTexelFormat srcFormat, bool srcPremultiplied,
                       WebGLTexelFormat dstFormat, bool dstPremultiplied,
                       size_t dstTexelSize);
 
-public:
-    nsLayoutUtils::SurfaceFromElementResult
-    SurfaceFromElement(dom::Element* elem)
-    {
-        uint32_t flags = nsLayoutUtils::SFE_WANT_IMAGE_SURFACE |
-                         nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR;
+    //////
 
-        if (mPixelStore_ColorspaceConversion == LOCAL_GL_NONE)
-            flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
-
-        if (!mPixelStore_PremultiplyAlpha)
-            flags |= nsLayoutUtils::SFE_PREFER_NO_PREMULTIPLY_ALPHA;
-
-        RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
-        return nsLayoutUtils::SurfaceFromElement(elem, flags, idealDrawTarget);
-    }
-
-protected:
     // Returns false if `object` is null or not valid.
     template<class ObjectType>
     bool ValidateObject(const char* info, ObjectType* object);
 
     // Returns false if `object` is not valid.  Considers null to be valid.
     template<class ObjectType>
     bool ValidateObjectAllowNull(const char* info, ObjectType* object);
 
@@ -1624,16 +1605,17 @@ public:
     CreateFormatUsage(gl::GLContext* gl) const = 0;
 
     // Friend list
     friend class ScopedCopyTexImageSource;
     friend class ScopedResolveTexturesForDraw;
     friend class ScopedUnpackReset;
     friend class webgl::TexUnpackBlob;
     friend class webgl::TexUnpackBytes;
+    friend class webgl::TexUnpackImage;
     friend class webgl::TexUnpackSurface;
     friend class WebGLTexture;
     friend class WebGLFBAttachPoint;
     friend class WebGLFramebuffer;
     friend class WebGLRenderbuffer;
     friend class WebGLProgram;
     friend class WebGLQuery;
     friend class WebGLBuffer;
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -1,13 +1,14 @@
 /* -*- Mode: C++; tab-width: 20; 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/. */
 
+#include "WebGLContextUtils.h"
 #include "WebGLContext.h"
 
 #include "GLContext.h"
 #include "jsapi.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/Preferences.h"
 #include "nsIDOMDataContainerEvent.h"
 #include "nsIDOMEvent.h"
@@ -18,17 +19,16 @@
 #include "prprf.h"
 #include <stdarg.h>
 #include "WebGLBuffer.h"
 #include "WebGLExtensions.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
 #include "WebGLTexture.h"
 #include "WebGLVertexArray.h"
-#include "WebGLContextUtils.h"
 
 namespace mozilla {
 
 TexTarget
 TexImageTargetToTexTarget(TexImageTarget texImageTarget)
 {
     switch (texImageTarget.get()) {
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
@@ -202,16 +202,32 @@ WebGLContext::ErrorOutOfMemory(const cha
     va_list va;
     va_start(va, fmt);
     GenerateWarning(fmt, va);
     va_end(va);
 
     return SynthesizeGLError(LOCAL_GL_OUT_OF_MEMORY);
 }
 
+void
+WebGLContext::ErrorImplementationBug(const char* fmt, ...)
+{
+    const nsPrintfCString warning("Implementation bug, please file at %s! %s",
+                                  "https://bugzilla.mozilla.org/", fmt);
+
+    va_list va;
+    va_start(va, fmt);
+    GenerateWarning(warning.BeginReading(), va);
+    va_end(va);
+
+    MOZ_ASSERT(false, "WebGLContext::ErrorImplementationBug");
+    NS_ERROR("WebGLContext::ErrorImplementationBug");
+    return SynthesizeGLError(LOCAL_GL_OUT_OF_MEMORY);
+}
+
 const char*
 WebGLContext::ErrorName(GLenum error)
 {
     switch(error) {
     case LOCAL_GL_INVALID_ENUM:
         return "INVALID_ENUM";
     case LOCAL_GL_INVALID_OPERATION:
         return "INVALID_OPERATION";
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -407,16 +407,19 @@ TexImageTargetForTargetAndFace(TexTarget
     default:
         MOZ_CRASH("GFX: TexImageTargetForTargetAndFace");
     }
 }
 
 already_AddRefed<mozilla::layers::Image>
 ImageFromVideo(dom::HTMLVideoElement* elem);
 
+bool
+IsTarget3D(TexImageTarget target);
+
 GLenum
 DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
            const webgl::DriverUnpackInfo* dui, GLsizei width, GLsizei height,
            GLsizei depth, const void* data);
 GLenum
 DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset,
               GLint yOffset, GLint zOffset, GLsizei width, GLsizei height,
               GLsizei depth, const webgl::PackingInfo& pi, const void* data);
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -173,21 +173,22 @@ WebGLContext::ValidateUnpackPixels(const
                           " rows plus %u pixels needed, %u rows plus %u pixels"
                           " available)",
                           funcName, fullRowsNeeded.value(), usedPixelsPerRow.value(),
                           fullRows, tailPixels);
     return false;
 }
 
 static UniquePtr<webgl::TexUnpackBlob>
-BlobFromView(WebGLContext* webgl, const char* funcName, uint32_t width, uint32_t height,
-             uint32_t depth, const webgl::PackingInfo& pi,
+BlobFromView(WebGLContext* webgl, const char* funcName, TexImageTarget target,
+             uint32_t width, uint32_t height, uint32_t depth,
+             const webgl::PackingInfo& pi,
              const dom::Nullable<dom::ArrayBufferView>& maybeView)
 {
-    const void* bytes = nullptr;
+    const uint8_t* bytes = nullptr;
     uint32_t byteCount = 0;
 
     if (!maybeView.IsNull()) {
         const auto& view = maybeView.Value();
 
         const auto jsType = JS_GetArrayBufferViewType(view.Obj());
         if (!DoesJSTypeMatchUnpackType(pi.type, jsType)) {
             webgl->ErrorInvalidOperation("%s: `pixels` must be compatible with `type`.",
@@ -198,18 +199,18 @@ BlobFromView(WebGLContext* webgl, const 
         if (width && height && depth) {
             view.ComputeLengthAndData();
 
             bytes = view.DataAllowShared();
             byteCount = view.LengthAllowShared();
         }
     }
 
-    UniquePtr<webgl::TexUnpackBlob> blob(new webgl::TexUnpackBytes(webgl, width, height,
-                                                                   depth, bytes));
+    UniquePtr<webgl::TexUnpackBlob> blob(new webgl::TexUnpackBytes(webgl, target, width,
+                                                                   height, depth, bytes));
 
     //////
 
     if (bytes) {
         const auto bytesPerPixel = webgl::BytesPerPixel(pi);
         const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
         const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
 
@@ -263,29 +264,29 @@ WebGLTexture::TexOrSubImage(bool isSubIm
     {
         return;
     }
 
     webgl::PackingInfo pi;
     if (!mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
         return;
 
-    const auto blob = BlobFromView(mContext, funcName, width, height, depth, pi,
+    const auto blob = BlobFromView(mContext, funcName, target, width, height, depth, pi,
                                    maybeView);
     if (!blob)
         return;
 
     TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
                       yOffset, zOffset, pi, blob.get());
 }
 
 ////////////////////////////////////////
 // ImageData
 
-static already_AddRefed<gfx::SourceSurface>
+static already_AddRefed<gfx::DataSourceSurface>
 FromImageData(WebGLContext* webgl, const char* funcName, GLenum unpackType,
               dom::ImageData* imageData, dom::Uint8ClampedArray* scopedArr)
 {
     DebugOnly<bool> inited = scopedArr->Init(imageData->GetDataObject());
     MOZ_ASSERT(inited);
 
     scopedArr->ComputeLengthAndData();
     const DebugOnly<size_t> dataSize = scopedArr->Length();
@@ -294,58 +295,29 @@ FromImageData(WebGLContext* webgl, const
     const gfx::IntSize size(imageData->Width(), imageData->Height());
     const size_t stride = size.width * 4;
     const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
 
     MOZ_ASSERT(dataSize == stride * size.height);
 
     uint8_t* wrappableData = (uint8_t*)data;
 
-    RefPtr<gfx::SourceSurface> surf =
+    RefPtr<gfx::DataSourceSurface> surf =
         gfx::Factory::CreateWrappingDataSourceSurface(wrappableData,
                                                       stride,
                                                       size,
                                                       surfFormat);
     if (!surf) {
         webgl->ErrorOutOfMemory("%s: OOM in FromImageData.", funcName);
         return nullptr;
     }
 
     return surf.forget();
 }
 
-bool
-WebGLContext::GetUnpackValuesForImage(const char* funcName, uint32_t srcImageWidth,
-                                      uint32_t srcImageHeight,
-                                      uint32_t* const out_rowLength,
-                                      uint32_t* const out_imageHeight)
-{
-    uint32_t rowLength = mPixelStore_UnpackRowLength;
-    if (!rowLength) {
-        rowLength = srcImageWidth;
-    } else if (rowLength != srcImageWidth) {
-        ErrorInvalidOperation("%s: UNPACK_ROW_LENGTH, if set, must be == width of"
-                              " object.");
-        return false;
-    }
-
-    uint32_t imageHeight = mPixelStore_UnpackImageHeight;
-    if (!imageHeight) {
-        imageHeight = srcImageHeight;
-    } else if (imageHeight > srcImageHeight) {
-        ErrorInvalidOperation("%s: UNPACK_IMAGE_HEIGHT, if set, must be <= height of"
-                              " object");
-        return false;
-    }
-
-    *out_rowLength = rowLength;
-    *out_imageHeight = imageHeight;
-    return true;
-}
-
 void
 WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                             GLint level, GLenum internalFormat, GLint xOffset,
                             GLint yOffset, GLint zOffset, GLenum unpackFormat,
                             GLenum unpackType, dom::ImageData* imageData)
 {
     webgl::PackingInfo pi;
     if (!mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
@@ -357,37 +329,30 @@ WebGLTexture::TexOrSubImage(bool isSubIm
         return;
     }
 
     // Eventually, these will be args.
     const uint32_t width = imageData->Width();
     const uint32_t height = imageData->Height();
     const uint32_t depth = 1;
 
-    uint32_t rowLength, imageHeight;
-    if (!mContext->GetUnpackValuesForImage(funcName, imageData->Width(),
-                                           imageData->Height(), &rowLength, &imageHeight))
-    {
-        return;
-    }
-
-    dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(
-      nsContentUtils::RootingCx());
-    const RefPtr<gfx::SourceSurface> surf = FromImageData(mContext, funcName, unpackType,
-                                                          imageData, &scopedArr);
+    dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(nsContentUtils::RootingCx());
+    const RefPtr<gfx::DataSourceSurface> surf = FromImageData(mContext, funcName,
+                                                              unpackType, imageData,
+                                                              &scopedArr);
     if (!surf)
         return;
 
     // WhatWG "HTML Living Standard" (30 October 2015):
     // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
     //  non-premultiplied alpha values."
-    const bool surfIsAlphaPremult = false;
+    const bool isAlphaPremult = false;
 
-    const webgl::TexUnpackSurface blob(mContext, imageHeight, width, height, depth, surf,
-                                       surfIsAlphaPremult);
+    const webgl::TexUnpackSurface blob(mContext, target, width, height, depth, surf,
+                                       isAlphaPremult);
 
     const uint32_t fullRows = imageData->Height();
     const uint32_t tailPixels = 0;
     if (!mContext->ValidateUnpackPixels(funcName, fullRows, tailPixels, &blob))
         return;
 
     TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
                       yOffset, zOffset, pi, &blob);
@@ -402,51 +367,71 @@ WebGLTexture::TexOrSubImage(bool isSubIm
                             GLint yOffset, GLint zOffset, GLenum unpackFormat,
                             GLenum unpackType, dom::Element* elem,
                             ErrorResult* const out_error)
 {
     webgl::PackingInfo pi;
     if (!mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
         return;
 
-    auto sfer = mContext->SurfaceFromElement(elem);
+    //////
+
+    uint32_t flags = nsLayoutUtils::SFE_WANT_IMAGE_SURFACE |
+                     nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR;
+
+    if (mContext->mPixelStore_ColorspaceConversion == LOCAL_GL_NONE)
+        flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
+
+    if (!mContext->mPixelStore_PremultiplyAlpha)
+        flags |= nsLayoutUtils::SFE_PREFER_NO_PREMULTIPLY_ALPHA;
+
+    RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
+    auto sfer = nsLayoutUtils::SurfaceFromElement(elem, flags, idealDrawTarget);
+
+    //////
 
     uint32_t elemWidth = 0;
     uint32_t elemHeight = 0;
     layers::Image* layersImage = nullptr;
     if (!gfxPrefs::WebGLDisableDOMBlitUploads() && sfer.mLayersImage) {
         layersImage = sfer.mLayersImage;
         elemWidth = layersImage->GetSize().width;
         elemHeight = layersImage->GetSize().height;
     }
 
-    gfx::SourceSurface* surf = nullptr;
+    RefPtr<gfx::DataSourceSurface> dataSurf;
     if (!layersImage && sfer.GetSourceSurface()) {
-        surf = sfer.GetSourceSurface();
+        const auto surf = sfer.GetSourceSurface();
         elemWidth = surf->GetSize().width;
         elemHeight = surf->GetSize().height;
+
+        // WARNING: OSX can lose our MakeCurrent here.
+        dataSurf = surf->GetDataSurface();
     }
 
+    //////
+
     // Eventually, these will be args.
     const uint32_t width = elemWidth;
     const uint32_t height = elemHeight;
     const uint32_t depth = 1;
 
-    // While it's counter-intuitive, the shape of the SFEResult API means that we should
-    // try to pull out a surface first, and then, if we do pull out a surface, check
-    // CORS/write-only/etc..
-    if (!layersImage && !surf) {
-        webgl::TexUnpackBytes blob(mContext, width, height, depth, nullptr);
+    if (!layersImage && !dataSurf) {
+        const webgl::TexUnpackBytes blob(mContext, target, width, height, depth, nullptr);
         TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
                           yOffset, zOffset, pi, &blob);
         return;
     }
 
     //////
 
+    // While it's counter-intuitive, the shape of the SFEResult API means that we should
+    // try to pull out a surface first, and then, if we do pull out a surface, check
+    // CORS/write-only/etc..
+
     if (!sfer.mCORSUsed) {
         auto& srcPrincipal = sfer.mPrincipal;
         nsIPrincipal* dstPrincipal = mContext->GetCanvas()->NodePrincipal();
 
         if (!dstPrincipal->Subsumes(srcPrincipal)) {
             mContext->GenerateWarning("%s: Cross-origin elements require CORS.",
                                       funcName);
             out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
@@ -463,33 +448,26 @@ WebGLTexture::TexOrSubImage(bool isSubIm
                                   funcName);
         out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
         return;
     }
 
     //////
     // Ok, we're good!
 
-    uint32_t rowLength, imageHeight;
-    if (!mContext->GetUnpackValuesForImage(funcName, elemWidth, elemHeight, &rowLength,
-                                           &imageHeight))
-    {
-        return;
-    }
-
     UniquePtr<const webgl::TexUnpackBlob> blob;
     const bool isAlphaPremult = sfer.mIsPremultiplied;
 
     if (layersImage) {
-        blob.reset(new webgl::TexUnpackImage(mContext, imageHeight, width, height, depth,
+        blob.reset(new webgl::TexUnpackImage(mContext, target, width, height, depth,
                                              layersImage, isAlphaPremult));
     } else {
-        MOZ_ASSERT(surf);
-        blob.reset(new webgl::TexUnpackSurface(mContext, imageHeight, width, height,
-                                               depth, surf, isAlphaPremult));
+        MOZ_ASSERT(dataSurf);
+        blob.reset(new webgl::TexUnpackSurface(mContext, target, width, height, depth,
+                                               dataSurf, isAlphaPremult));
     }
 
     const uint32_t fullRows = elemHeight;
     const uint32_t tailPixels = 0;
     if (!mContext->ValidateUnpackPixels(funcName, fullRows, tailPixels, blob.get()))
         return;
 
     TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
@@ -831,18 +809,18 @@ DoTexStorage(gl::GLContext* gl, TexTarge
 
     default:
         MOZ_CRASH("GFX: bad target");
     }
 
     return errorScope.GetError();
 }
 
-static bool
-Is3D(TexImageTarget target)
+bool
+IsTarget3D(TexImageTarget target)
 {
     switch (target.get()) {
     case LOCAL_GL_TEXTURE_2D:
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
     case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
     case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
@@ -862,17 +840,17 @@ GLenum
 DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
            const webgl::DriverUnpackInfo* dui, GLsizei width, GLsizei height,
            GLsizei depth, const void* data)
 {
     const GLint border = 0;
 
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
-    if (Is3D(target)) {
+    if (IsTarget3D(target)) {
         gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height, depth,
                         border, dui->unpackFormat, dui->unpackType, data);
     } else {
         MOZ_ASSERT(depth == 1);
         gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height, border,
                         dui->unpackFormat, dui->unpackType, data);
     }
 
@@ -881,17 +859,17 @@ DoTexImage(gl::GLContext* gl, TexImageTa
 
 GLenum
 DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset,
               GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth,
               const webgl::PackingInfo& pi, const void* data)
 {
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
-    if (Is3D(target)) {
+    if (IsTarget3D(target)) {
         gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width, height,
                            depth, pi.format, pi.type, data);
     } else {
         MOZ_ASSERT(zOffset == 0);
         MOZ_ASSERT(depth == 1);
         gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
                            pi.format, pi.type, data);
     }
@@ -903,17 +881,17 @@ static inline GLenum
 DoCompressedTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
                      GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth,
                      GLsizei dataSize, const void* data)
 {
     const GLint border = 0;
 
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
-    if (Is3D(target)) {
+    if (IsTarget3D(target)) {
         gl->fCompressedTexImage3D(target.get(), level, internalFormat, width, height,
                                   depth, border, dataSize, data);
     } else {
         MOZ_ASSERT(depth == 1);
         gl->fCompressedTexImage2D(target.get(), level, internalFormat, width, height,
                                   border, dataSize, data);
     }
 
@@ -923,17 +901,17 @@ DoCompressedTexImage(gl::GLContext* gl, 
 GLenum
 DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
                         GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
                         GLsizei height, GLsizei depth, GLenum sizedUnpackFormat,
                         GLsizei dataSize, const void* data)
 {
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
-    if (Is3D(target)) {
+    if (IsTarget3D(target)) {
         gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset,
                                      width, height, depth, sizedUnpackFormat, dataSize,
                                      data);
     } else {
         MOZ_ASSERT(zOffset == 0);
         MOZ_ASSERT(depth == 1);
         gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width,
                                      height, sizedUnpackFormat, dataSize, data);
@@ -945,31 +923,31 @@ DoCompressedTexSubImage(gl::GLContext* g
 static inline GLenum
 DoCopyTexImage2D(gl::GLContext* gl, TexImageTarget target, GLint level,
                  GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height)
 {
     const GLint border = 0;
 
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
-    MOZ_ASSERT(!Is3D(target));
+    MOZ_ASSERT(!IsTarget3D(target));
     gl->fCopyTexImage2D(target.get(), level, internalFormat, x, y, width, height,
                         border);
 
     return errorScope.GetError();
 }
 
 static inline GLenum
 DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset,
                   GLint yOffset, GLint zOffset, GLint x, GLint y, GLsizei width,
                   GLsizei height)
 {
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
-    if (Is3D(target)) {
+    if (IsTarget3D(target)) {
         gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y,
                                width, height);
     } else {
         MOZ_ASSERT(zOffset == 0);
         gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width,
                                height);
     }
 
@@ -1258,17 +1236,17 @@ WebGLTexture::TexImage(const char* funcN
     // Check that source and dest info are compatible
     auto dstFormat = dstUsage->format;
 
     if (!ValidateTargetForFormat(funcName, mContext, target, dstFormat))
         return;
 
     if (!mContext->IsWebGL2() && dstFormat->d) {
         if (target != LOCAL_GL_TEXTURE_2D ||
-            blob->mHasData ||
+            blob->HasData() ||
             level != 0)
         {
             mContext->ErrorInvalidOperation("%s: With format %s, this function may only"
                                             " be called with target=TEXTURE_2D,"
                                             " data=null, and level=0.",
                                             funcName, dstFormat->name);
             return;
         }
@@ -1279,17 +1257,17 @@ WebGLTexture::TexImage(const char* funcN
 
     MOZ_ALWAYS_TRUE( mContext->gl->MakeCurrent() );
     MOZ_ASSERT(mContext->gl->IsCurrent());
 
     // It's tempting to do allocation first, and TexSubImage second, but this is generally
     // slower.
 
     const ImageInfo newImageInfo(dstUsage, blob->mWidth, blob->mHeight, blob->mDepth,
-                                 blob->mHasData);
+                                 blob->HasData());
 
     const bool isSubImage = false;
     const bool needsRespec = (imageInfo->mWidth  != newImageInfo.mWidth ||
                               imageInfo->mHeight != newImageInfo.mHeight ||
                               imageInfo->mDepth  != newImageInfo.mDepth ||
                               imageInfo->mFormat != newImageInfo.mFormat);
     const GLint xOffset = 0;
     const GLint yOffset = 0;