Bug 1280499 - Implement PBOs for textures. - r=jrmuizel draft
authorJeff Gilbert <jgilbert@mozilla.com>
Thu, 07 Jul 2016 15:22:40 -0700
changeset 385249 33bb06bc3144236db535d375e2ac771e37d25650
parent 385248 8c58e6a14ff33bf81092c22deb74522bc4c1a131
child 385250 a0f1ccd3cff012f7c80f11a3f92df7dfc3e176a2
push id22467
push userbmo:jgilbert@mozilla.com
push dateFri, 08 Jul 2016 01:17:53 +0000
reviewersjrmuizel
bugs1280499
milestone50.0a1
Bug 1280499 - Implement PBOs for textures. - r=jrmuizel MozReview-Commit-ID: G69xr883Cca
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
dom/canvas/WebGLContext.h
dom/canvas/WebGLTexture.h
dom/canvas/WebGLTextureUpload.cpp
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -16,30 +16,28 @@
 #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)
+                             uint32_t height, uint32_t depth)
     : 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) {
@@ -68,22 +66,23 @@ TexUnpackBlob::OriginsForDOM(WebGLContex
 
 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)
+                               uint32_t depth, bool isClientData, const void* ptr)
     : TexUnpackBlob(webgl, webgl->mPixelStore_UnpackAlignment,
                     FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
                     FallbackOnZero(webgl->mPixelStore_UnpackImageHeight, height),
-                    width, height, depth, bool(bytes))
-    , mBytes(bytes)
+                    width, height, depth)
+    , mIsClientData(isClientData)
+    , mPtr(ptr)
 { }
 
 static bool
 UnpackFormatHasAlpha(GLenum unpackFormat)
 {
     switch (unpackFormat) {
     case LOCAL_GL_ALPHA:
     case LOCAL_GL_LUMINANCE_ALPHA:
@@ -146,26 +145,30 @@ 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;
+    const void* uploadPtr = mPtr;
     UniqueBuffer tempBuffer;
 
     do {
-        if (!mBytes || !mWidth || !mHeight || !mDepth)
+        if (!mWidth || !mHeight || !mDepth)
             break;
 
         if (webgl->IsWebGL2())
             break;
         MOZ_ASSERT(mDepth == 1);
+        MOZ_ASSERT(mIsClientData);
+
+        if (!mPtr)
+            break;
 
         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;
@@ -216,28 +219,28 @@ TexUnpackBytes::TexOrSubImage(bool isSub
             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* src = (const uint8_t*)mPtr;
             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();
+            uploadPtr = tempBuffer.get();
             break;
         }
 
         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.",
@@ -251,45 +254,44 @@ TexUnpackBytes::TexOrSubImage(bool isSub
 
         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,
+                          mPtr, 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();
+        uploadPtr = tempBuffer.get();
     } while (false);
 
     *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
-                                 zOffset, mWidth, mHeight, mDepth, uploadBytes);
+                                 zOffset, mWidth, mHeight, mDepth, uploadPtr);
     return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackImage
 
 TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, uint32_t imageHeight,
                                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)
+    : TexUnpackBlob(webgl, 0, image->GetSize().width, imageHeight, width, height, depth)
     , 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,
@@ -369,18 +371,17 @@ TexUnpackImage::TexOrSubImage(bool isSub
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackSurface
 
 TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, uint32_t imageHeight,
                                    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)
+    : TexUnpackBlob(webgl, 0, surf->GetSize().width, imageHeight, width, height, depth)
     , mSurf(surf)
     , mIsAlphaPremult(isAlphaPremult)
 { }
 
 //////////
 
 static bool
 GuessAlignment(const void* data, size_t bytesPerRow, size_t stride, size_t maxAlignment,
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -51,81 +51,89 @@ 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;
 
 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);
+                  uint32_t imageHeight, uint32_t width, uint32_t height, uint32_t depth);
 
 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);
 };
 
 class TexUnpackBytes : public TexUnpackBlob
 {
 public:
-    const void* const mBytes;
+    const bool mIsClientData;
+    const void* const mPtr;
 
     TexUnpackBytes(const WebGLContext* webgl, uint32_t width, uint32_t height,
-                   uint32_t depth, const void* bytes);
+                   uint32_t depth, bool isClientData, const void* ptr);
+
+    virtual bool HasData() const override {
+        if (mIsClientData && !mPtr)
+            return false;
+
+        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 override;
+                               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;
+                               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;
+                               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,
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -937,20 +937,16 @@ public:
                   GLenum unpackFormat, GLenum unpackType, T& elem, ErrorResult& out_error)
     {
         TexSubImage2D(texImageTarget, level, xOffset, yOffset, unpackFormat, unpackType,
                       &elem, &out_error);
     }
 
     //////
     // WebGLTextureUpload.cpp
-public:
-    bool ValidateUnpackPixels(const char* funcName, uint32_t fullRows,
-                              uint32_t tailPixels, const webgl::TexUnpackBlob* blob);
-
 protected:
     bool ValidateTexImageSpecification(const char* funcName, uint8_t funcDims,
                                        GLenum texImageTarget, GLint level,
                                        GLsizei width, GLsizei height, GLsizei depth,
                                        GLint border,
                                        TexImageTarget* const out_target,
                                        WebGLTexture** const out_texture,
                                        WebGLTexture::ImageInfo** const out_imageInfo);
@@ -961,18 +957,18 @@ protected:
                                    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);
+    bool ValidateUnpackInfo(const char* funcName, bool usePBOs, 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,
                              GLsizei primcount);
     void DrawElements(GLenum mode, GLsizei count, GLenum type,
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -220,16 +220,21 @@ public:
     ////////////////////////////////////
     // WebGLTextureUpload.cpp
 
     void TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                        GLint level, GLenum internalFormat, GLint xOffset, GLint yOffset,
                        GLint zOffset, GLsizei width, GLsizei height, GLsizei depth,
                        GLint border, GLenum unpackFormat, GLenum unpackType,
                        const dom::Nullable<dom::ArrayBufferView>& maybeView);
+    void TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
+                       GLint level, GLenum internalFormat, GLint xOffset, GLint yOffset,
+                       GLint zOffset, GLsizei rawWidth, GLsizei rawHeight,
+                       GLsizei rawDepth, GLint border, GLenum unpackFormat,
+                       GLenum unpackType, WebGLsizeiptr offset);
 
     void 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);
 
     void TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                        GLint level, GLenum internalFormat, GLint xOffset, GLint yOffset,
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -77,20 +77,117 @@ ValidateExtents(WebGLContext* webgl, con
     }
 
     *out_width = width;
     *out_height = height;
     *out_depth = depth;
     return true;
 }
 
+static bool
+ValidateUnpackPixels(WebGLContext* webgl, const char* funcName, uint32_t fullRows,
+                     uint32_t tailPixels, const webgl::TexUnpackBlob* blob)
+{
+    const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
+    const auto usedRowsPerImage = CheckedUint32(blob->mSkipRows) + blob->mHeight;
+    const auto usedImages = CheckedUint32(blob->mSkipImages) + blob->mDepth;
+
+    bool unpackVarsValid = false;
+    if (!usedPixelsPerRow.isValid() ||
+        !usedRowsPerImage.isValid() ||
+        !usedImages.isValid())
+    {
+        webgl->ErrorOutOfMemory("%s: Invalid calculation for e.g. UNPACK_SKIP_PIXELS +"
+                                " width.",
+                                funcName);
+        return false;
+    }
+
+    //////
+
+    if (usedPixelsPerRow.value() > blob->mRowLength ||
+        usedRowsPerImage.value() > blob->mImageHeight)
+    {
+        webgl->ErrorInvalidOperation("%s: UNPACK_ROW_LENGTH or UNPACK_IMAGE_HEIGHT too"
+                                     " small.",
+                                     funcName);
+        return false;
+    }
+
+    //////
+
+    auto fullRowsNeeded = (usedImages - 1) * blob->mImageHeight;
+    fullRowsNeeded += usedRowsPerImage - 1;
+
+    if (!fullRowsNeeded.isValid()) {
+        webgl->ErrorOutOfMemory("%s: Invalid calculation for required row count.",
+                                funcName);
+        return false;
+    }
+
+    if (fullRows > fullRowsNeeded.value())
+        return true;
+
+    if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value())
+        return true;
+
+    webgl->ErrorInvalidOperation("%s: Desired upload requires more data than is"
+                                 " available: (%u rows plus %u pixels needed, %u rows"
+                                 " plus %u pixels available)",
+                                 funcName, fullRowsNeeded.value(),
+                                 usedPixelsPerRow.value(), fullRows, tailPixels);
+    return false;
+}
+
+static bool
+ValidateUnpackBytes(WebGLContext* webgl, const char* funcName, uint32_t width,
+                    uint32_t height, uint32_t depth, const webgl::PackingInfo& pi,
+                    uint32_t byteCount, const webgl::TexUnpackBlob* blob)
+{
+    const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+    const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
+    const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
+
+    const auto fullRows = byteCount / rowStride;
+    if (!fullRows.isValid()) {
+        webgl->ErrorOutOfMemory("%s: Unacceptable upload size calculated.");
+        return nullptr;
+    }
+
+    const auto bodyBytes = fullRows.value() * rowStride.value();
+    const auto tailPixels = (byteCount - bodyBytes) / bytesPerPixel;
+
+    return ValidateUnpackPixels(webgl, funcName, fullRows.value(), tailPixels, blob);
+}
+
+bool
+WebGLContext::ValidateUnpackInfo(const char* funcName, bool usePBOs, GLenum format,
+                                 GLenum type, webgl::PackingInfo* const out)
+{
+    if (usePBOs != bool(mBoundPixelUnpackBuffer)) {
+        ErrorInvalidOperation("%s: PACK_BUFFER must be %s.", funcName,
+                              (usePBOs ? "non-null" : "null"));
+        return false;
+    }
+
+    if (!mFormatUsage->AreUnpackEnumsValid(format, type)) {
+        ErrorInvalidEnum("%s: Invalid unpack format/type: 0x%04x/0x%04x", funcName,
+                         format, type);
+        return false;
+    }
+
+    out->format = format;
+    out->type = type;
+    return true;
+}
+
 ////////////////////////////////////////
 // ArrayBufferView?
 
-static inline bool
+static bool
 DoesJSTypeMatchUnpackType(GLenum unpackType, js::Scalar::Type jsType)
 {
     switch (unpackType) {
     case LOCAL_GL_BYTE:
         return jsType == js::Scalar::Type::Int8;
 
     case LOCAL_GL_UNSIGNED_BYTE:
         return jsType == js::Scalar::Type::Uint8 ||
@@ -120,167 +217,127 @@ DoesJSTypeMatchUnpackType(GLenum unpackT
     case LOCAL_GL_FLOAT:
         return jsType == js::Scalar::Type::Float32;
 
     default:
         return false;
     }
 }
 
-bool
-WebGLContext::ValidateUnpackPixels(const char* funcName, uint32_t fullRows,
-                                   uint32_t tailPixels, const webgl::TexUnpackBlob* blob)
+void
+WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
+                            GLint level, GLenum internalFormat, GLint xOffset,
+                            GLint yOffset, GLint zOffset, GLsizei rawWidth,
+                            GLsizei rawHeight, GLsizei rawDepth, GLint border,
+                            GLenum unpackFormat, GLenum unpackType,
+                            const dom::Nullable<dom::ArrayBufferView>& maybeView)
 {
-    const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
-    const auto usedRowsPerImage = CheckedUint32(blob->mSkipRows) + blob->mHeight;
-    const auto usedImages = CheckedUint32(blob->mSkipImages) + blob->mDepth;
+    const bool usePBOs = false;
+    webgl::PackingInfo pi;
+    if (!mContext->ValidateUnpackInfo(funcName, usePBOs, unpackFormat, unpackType, &pi))
+        return;
 
-    bool unpackVarsValid = false;
-    if (!usedPixelsPerRow.isValid() ||
-        !usedRowsPerImage.isValid() ||
-        !usedImages.isValid())
+    uint32_t width, height, depth;
+    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, border,
+                         &width, &height, &depth))
     {
-        ErrorOutOfMemory("%s: Invalid calculation for e.g. UNPACK_SKIP_PIXELS + width.",
-                         funcName);
-        return false;
+        return;
     }
 
     //////
 
-    if (usedPixelsPerRow.value() > blob->mRowLength ||
-        usedRowsPerImage.value() > blob->mImageHeight)
-    {
-        ErrorInvalidOperation("%s: UNPACK_ROW_LENGTH or UNPACK_IMAGE_HEIGHT too small.",
-                              funcName);
-        return false;
-    }
-
-    //////
-
-    auto fullRowsNeeded = (usedImages - 1) * blob->mImageHeight;
-    fullRowsNeeded += usedRowsPerImage - 1;
-
-    if (!fullRowsNeeded.isValid()) {
-        ErrorOutOfMemory("%s: Invalid calculation for required row count.",
-                         funcName);
-        return false;
-    }
-
-    if (fullRows > fullRowsNeeded.value())
-        return true;
-
-    if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value())
-        return true;
-
-    ErrorInvalidOperation("%s: Desired upload requires more data than is available: (%u"
-                          " 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,
-             const dom::Nullable<dom::ArrayBufferView>& maybeView)
-{
     const void* 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`.",
-                                         funcName);
-            return nullptr;
+            mContext->ErrorInvalidOperation("%s: `pixels` must be compatible with"
+                                            " `type`.",
+                                            funcName);
+            return;
         }
 
         if (width && height && depth) {
             view.ComputeLengthAndData();
 
             bytes = view.DataAllowShared();
             byteCount = view.LengthAllowShared();
         }
     }
 
-    UniquePtr<webgl::TexUnpackBlob> blob(new webgl::TexUnpackBytes(webgl, width, height,
-                                                                   depth, bytes));
+    const bool isClientData = true;
+    const webgl::TexUnpackBytes blob(mContext, width, height, depth, isClientData, bytes);
 
     //////
 
-    if (bytes) {
-        const auto bytesPerPixel = webgl::BytesPerPixel(pi);
-        const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
-        const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
-
-        const auto fullRows = byteCount / rowStride;
-        if (!fullRows.isValid()) {
-            webgl->ErrorOutOfMemory("%s: Unacceptable upload size calculated.");
-            return nullptr;
-        }
-
-        const auto bodyBytes = fullRows.value() * rowStride.value();
-        const auto tailPixels = (byteCount - bodyBytes) / bytesPerPixel;
-
-        if (!webgl->ValidateUnpackPixels(funcName, fullRows.value(), tailPixels,
-                                         blob.get()))
-        {
-            return nullptr;
-        }
+    if (bytes && !ValidateUnpackBytes(mContext, funcName, width, height, depth, pi,
+                                      byteCount, &blob))
+    {
+        return;
     }
 
-    //////
-
-    return Move(blob);
+    TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
+                      yOffset, zOffset, pi, &blob);
 }
 
-bool
-WebGLContext::ValidateUnpackInfo(const char* funcName, GLenum format, GLenum type,
-                                 webgl::PackingInfo* const out)
-{
-    if (!mFormatUsage->AreUnpackEnumsValid(format, type)) {
-        ErrorInvalidEnum("%s: Invalid unpack format/type: 0x%04x/0x%04x", funcName,
-                         format, type);
-        return false;
-    }
-
-    out->format = format;
-    out->type = type;
-    return true;
-}
+////////////////////////////////////////
+// GLintptr offset
 
 void
 WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                             GLint level, GLenum internalFormat, GLint xOffset,
                             GLint yOffset, GLint zOffset, GLsizei rawWidth,
                             GLsizei rawHeight, GLsizei rawDepth, GLint border,
                             GLenum unpackFormat, GLenum unpackType,
-                            const dom::Nullable<dom::ArrayBufferView>& maybeView)
+                            WebGLsizeiptr offset)
 {
+    const bool usePBOs = true;
+    webgl::PackingInfo pi;
+    if (!mContext->ValidateUnpackInfo(funcName, usePBOs, unpackFormat, unpackType, &pi))
+        return;
+
     uint32_t width, height, depth;
     if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, border,
                          &width, &height, &depth))
     {
         return;
     }
 
-    webgl::PackingInfo pi;
-    if (!mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
+    //////
+
+    if (offset < 0) {
+        mContext->ErrorInvalidValue("%s: offset cannot be negative.", funcName);
         return;
+    }
+
+    const bool isClientData = false;
+    const void* ptr = (const void*)offset;
+    const webgl::TexUnpackBytes blob(mContext, width, height, depth, isClientData, ptr);
+
+    //////
 
-    const auto blob = BlobFromView(mContext, funcName, width, height, depth, pi,
-                                   maybeView);
-    if (!blob)
+    const auto& packBuffer = mContext->mBoundPixelUnpackBuffer;
+    const auto bufferByteCount = packBuffer->ByteLength();
+
+    uint32_t byteCount = 0;
+    if (bufferByteCount >= offset) {
+        byteCount = bufferByteCount - offset;
+    }
+
+    if (!ValidateUnpackBytes(mContext, funcName, width, height, depth, pi, byteCount,
+                             &blob))
+    {
         return;
+    }
 
     TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
-                      yOffset, zOffset, pi, blob.get());
+                      yOffset, zOffset, pi, &blob);
 }
 
 ////////////////////////////////////////
 // ImageData
 
 static already_AddRefed<gfx::SourceSurface>
 FromImageData(WebGLContext* webgl, const char* funcName, GLenum unpackType,
               dom::ImageData* imageData, dom::Uint8ClampedArray* scopedArr)
@@ -343,18 +400,19 @@ WebGLContext::GetUnpackValuesForImage(co
 }
 
 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)
 {
+    const bool usePBOs = false;
     webgl::PackingInfo pi;
-    if (mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
+    if (!mContext->ValidateUnpackInfo(funcName, usePBOs, unpackFormat, unpackType, &pi))
         return;
 
     if (!imageData) {
         // Spec says to generate an INVALID_VALUE error
         mContext->ErrorInvalidValue("%s: Null ImageData.", funcName);
         return;
     }
 
@@ -383,35 +441,36 @@ WebGLTexture::TexOrSubImage(bool isSubIm
     //  non-premultiplied alpha values."
     const bool surfIsAlphaPremult = false;
 
     const webgl::TexUnpackSurface blob(mContext, imageHeight, width, height, depth, surf,
                                        surfIsAlphaPremult);
 
     const uint32_t fullRows = imageData->Height();
     const uint32_t tailPixels = 0;
-    if (!mContext->ValidateUnpackPixels(funcName, fullRows, tailPixels, &blob))
+    if (!ValidateUnpackPixels(mContext, funcName, fullRows, tailPixels, &blob))
         return;
 
     TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
                       yOffset, zOffset, pi, &blob);
 }
 
 ////////////////////////////////////////
 // dom::Element
 
 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::Element* elem,
                             ErrorResult* const out_error)
 {
+    const bool usePBOs = false;
     webgl::PackingInfo pi;
-    if (mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
+    if (!mContext->ValidateUnpackInfo(funcName, usePBOs, unpackFormat, unpackType, &pi))
         return;
 
     auto sfer = mContext->SurfaceFromElement(elem);
 
     uint32_t elemWidth = 0;
     uint32_t elemHeight = 0;
     layers::Image* layersImage = nullptr;
     if (!gfxPrefs::WebGLDisableDOMBlitUploads() && sfer.mLayersImage) {
@@ -432,17 +491,19 @@ WebGLTexture::TexOrSubImage(bool isSubIm
     const uint32_t height = elemHeight;
     const uint32_t depth = 1;
     const GLint border = 0;
 
     // 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);
+        const bool isClientData = true;
+        const webgl::TexUnpackBytes blob(mContext, width, height, depth, isClientData,
+                                         nullptr);
         TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
                           yOffset, zOffset, pi, &blob);
         return;
     }
 
     //////
 
     if (!sfer.mCORSUsed) {
@@ -487,17 +548,17 @@ WebGLTexture::TexOrSubImage(bool isSubIm
     } else {
         MOZ_ASSERT(surf);
         blob.reset(new webgl::TexUnpackSurface(mContext, imageHeight, width, height,
                                                depth, surf, isAlphaPremult));
     }
 
     const uint32_t fullRows = elemHeight;
     const uint32_t tailPixels = 0;
-    if (!mContext->ValidateUnpackPixels(funcName, fullRows, tailPixels, blob.get()))
+    if (!ValidateUnpackPixels(mContext, funcName, fullRows, tailPixels, blob.get()))
         return;
 
     TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
                       yOffset, zOffset, pi, blob.get());
 }
 
 
 //////////////////////////////////////////////////////////////////////////////////////////
@@ -1260,19 +1321,20 @@ WebGLTexture::TexImage(const char* funcN
 
     ////////////////////////////////////
     // Check that source and dest info are compatible
     auto dstFormat = dstUsage->format;
 
     if (!ValidateTargetForFormat(funcName, mContext, target, dstFormat))
         return;
 
+    const bool hasData = blob->HasData();
     if (!mContext->IsWebGL2() && dstFormat->hasDepth) {
         if (target != LOCAL_GL_TEXTURE_2D ||
-            blob->mHasData ||
+            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;
         }
@@ -1283,17 +1345,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);
+                                 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;