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 346958 8d95a6d0855871245bd01df1f81c5ab93ec48700
parent 346957 4be1c47c9cbc1e827667226574cb00391e1a124c
child 346959 b15f05a94c716fdbed45b5f46a0fd5b8ae6443ef
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmtseng
bugs1286348
milestone50.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 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;