Bug 1305832 - Simplify texture data zeroing. (flattened) - a=gchang
authorJeff Gilbert <jgilbert@mozilla.com>
Thu, 15 Dec 2016 18:42:35 -0800
changeset 394319 bfcf5b0b42992cec72a4728bb3a2145121aa86af
parent 394318 b86bbd7cd5f515c29a0b9abab3b751a275c00924
child 394320 8f5198636104f839d4f5021590cad684ba4773fa
push id56
push userfmarier@mozilla.com
push dateFri, 19 May 2017 22:38:52 +0000
reviewersgchang
bugs1305832
milestone51.0
Bug 1305832 - Simplify texture data zeroing. (flattened) - a=gchang MozReview-Commit-ID: Dhuv5mSJviI
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLTexture.cpp
dom/canvas/WebGLTextureUpload.cpp
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -2217,200 +2217,16 @@ Intersect(uint32_t srcSize, int32_t dstS
     // [=====|==]    // dst box
     // ^-----^
     *out_intStartInDst = std::max<int32_t>(0, 0 - dstStartInSrc);
 
     int32_t intEndInSrc = std::min<int32_t>(srcSize, dstStartInSrc + dstSize);
     *out_intSize = std::max<int32_t>(0, intEndInSrc - *out_intStartInSrc);
 }
 
-static bool
-ZeroTexImageWithClear(WebGLContext* webgl, GLContext* gl, TexImageTarget target,
-                      GLuint tex, uint32_t level, const webgl::FormatUsageInfo* usage,
-                      uint32_t width, uint32_t height)
-{
-    MOZ_ASSERT(gl->IsCurrent());
-
-    ScopedFramebuffer scopedFB(gl);
-    ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
-
-    const auto format = usage->format;
-
-    GLenum attachPoint = 0;
-    GLbitfield clearBits = 0;
-
-    if (format->IsColorFormat()) {
-        attachPoint = LOCAL_GL_COLOR_ATTACHMENT0;
-        clearBits = LOCAL_GL_COLOR_BUFFER_BIT;
-    }
-
-    if (format->d) {
-        attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
-        clearBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
-    }
-
-    if (format->s) {
-        attachPoint = (format->d ? LOCAL_GL_DEPTH_STENCIL_ATTACHMENT
-                                 : LOCAL_GL_STENCIL_ATTACHMENT);
-        clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
-    }
-
-    MOZ_RELEASE_ASSERT(attachPoint && clearBits, "GFX: No bits cleared.");
-
-    {
-        gl::GLContext::LocalErrorScope errorScope(*gl);
-        gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint, target.get(), tex,
-                                  level);
-        if (errorScope.GetError()) {
-            MOZ_ASSERT(false);
-            return false;
-        }
-    }
-
-    auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
-    if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
-        return false;
-
-    {
-        gl::GLContext::LocalErrorScope errorScope(*gl);
-
-        const bool fakeNoAlpha = false;
-        webgl->ForceClearFramebufferWithDefaultValues(clearBits, fakeNoAlpha);
-        if (errorScope.GetError()) {
-            MOZ_ASSERT(false);
-            return false;
-        }
-    }
-
-    return true;
-}
-
-bool
-ZeroTextureData(WebGLContext* webgl, const char* funcName, GLuint tex,
-                TexImageTarget target, uint32_t level,
-                const webgl::FormatUsageInfo* usage, uint32_t xOffset, uint32_t yOffset,
-                uint32_t zOffset, uint32_t width, uint32_t height, uint32_t depth)
-{
-    // This has two usecases:
-    // 1. Lazy zeroing of uninitialized textures:
-    //    a. Before draw, when FakeBlack isn't viable. (TexStorage + Draw*)
-    //    b. Before partial upload. (TexStorage + TexSubImage)
-    // 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image)
-
-    // We have no sympathy for any of these cases.
-
-    // "Doctor, it hurts when I do this!" "Well don't do that!"
-    webgl->GenerateWarning("%s: This operation requires zeroing texture data. This is"
-                           " slow.",
-                           funcName);
-
-    gl::GLContext* gl = webgl->GL();
-    gl->MakeCurrent();
-
-    GLenum scopeBindTarget;
-    switch (target.get()) {
-    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:
-    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
-        scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
-        break;
-    default:
-        scopeBindTarget = target.get();
-        break;
-    }
-    ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
-    auto compression = usage->format->compression;
-    if (compression) {
-        MOZ_RELEASE_ASSERT(!xOffset && !yOffset && !zOffset, "GFX: Can't zero compressed texture with offsets.");
-
-        auto sizedFormat = usage->format->sizedFormat;
-        MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
-
-        const auto fnSizeInBlocks = [](CheckedUint32 pixels, uint8_t pixelsPerBlock) {
-            return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
-        };
-
-        const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth);
-        const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight);
-
-        CheckedUint32 checkedByteCount = compression->bytesPerBlock;
-        checkedByteCount *= widthBlocks;
-        checkedByteCount *= heightBlocks;
-        checkedByteCount *= depth;
-
-        if (!checkedByteCount.isValid())
-            return false;
-
-        const size_t byteCount = checkedByteCount.value();
-
-        UniqueBuffer zeros = calloc(1, byteCount);
-        if (!zeros)
-            return false;
-
-        ScopedUnpackReset scopedReset(webgl);
-        gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it
-                                                        // well.
-
-        GLenum error = DoCompressedTexSubImage(gl, target.get(), level, xOffset, yOffset,
-                                               zOffset, width, height, depth, sizedFormat,
-                                               byteCount, zeros.get());
-        if (error)
-            return false;
-
-        return true;
-    }
-
-    const auto driverUnpackInfo = usage->idealUnpack;
-    MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
-
-    if (usage->IsRenderable() && depth == 1 &&
-        !xOffset && !yOffset && !zOffset)
-    {
-        // While we would like to skip the extra complexity of trying to zero with an FB
-        // clear, ANGLE_depth_texture requires this.
-        do {
-            if (ZeroTexImageWithClear(webgl, gl, target, tex, level, usage, width,
-                                      height))
-            {
-                return true;
-            }
-        } while (false);
-    }
-
-    const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
-
-    const auto bytesPerPixel = webgl::BytesPerPixel(packing);
-
-    CheckedUint32 checkedByteCount = bytesPerPixel;
-    checkedByteCount *= width;
-    checkedByteCount *= height;
-    checkedByteCount *= depth;
-
-    if (!checkedByteCount.isValid())
-        return false;
-
-    const size_t byteCount = checkedByteCount.value();
-
-    UniqueBuffer zeros = calloc(1, byteCount);
-    if (!zeros)
-        return false;
-
-    ScopedUnpackReset scopedReset(webgl);
-    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it well.
-    const auto error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width,
-                                     height, depth, packing, zeros.get());
-    if (error)
-        return false;
-
-    return true;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 
 CheckedUint32
 WebGLContext::GetUnpackSize(bool isFunc3D, uint32_t width, uint32_t height,
                             uint32_t depth, uint8_t bytesPerPixel)
 {
     if (!width || !height || !depth)
         return 0;
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -2124,22 +2124,16 @@ private:
 
 ////
 
 void
 Intersect(uint32_t srcSize, int32_t dstStartInSrc, uint32_t dstSize,
           uint32_t* const out_intStartInSrc, uint32_t* const out_intStartInDst,
           uint32_t* const out_intSize);
 
-bool
-ZeroTextureData(WebGLContext* webgl, const char* funcName, GLuint tex,
-                TexImageTarget target, uint32_t level,
-                const webgl::FormatUsageInfo* usage, uint32_t xOffset, uint32_t yOffset,
-                uint32_t zOffset, uint32_t width, uint32_t height, uint32_t depth);
-
 ////
 
 void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                             const std::vector<IndexedBufferBinding>& field,
                             const char* name, uint32_t flags = 0);
 
 void
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -578,31 +578,185 @@ WebGLTexture::EnsureImageDataInitialized
     MOZ_ASSERT(imageInfo.IsDefined());
 
     if (imageInfo.IsDataInitialized())
         return true;
 
     return InitializeImageData(funcName, target, level);
 }
 
+static void
+ZeroANGLEDepthTexture(WebGLContext* webgl, GLuint tex,
+                      const webgl::FormatUsageInfo* usage, uint32_t width,
+                      uint32_t height)
+{
+    const auto& format = usage->format;
+    GLenum attachPoint = 0;
+    GLbitfield clearBits = 0;
+
+    if (format->d) {
+        attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
+        clearBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
+    }
+
+    if (format->s) {
+        attachPoint = (format->d ? LOCAL_GL_DEPTH_STENCIL_ATTACHMENT
+                                 : LOCAL_GL_STENCIL_ATTACHMENT);
+        clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
+    }
+
+    MOZ_RELEASE_ASSERT(attachPoint && clearBits, "GFX: No bits cleared.");
+
+    ////
+    const auto& gl = webgl->gl;
+    MOZ_ASSERT(gl->IsCurrent());
+
+    gl::ScopedFramebuffer scopedFB(gl);
+    const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
+
+    gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint, LOCAL_GL_TEXTURE_2D,
+                              tex, 0);
+
+    const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+    MOZ_RELEASE_ASSERT(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
+
+    ////
+
+    const bool fakeNoAlpha = false;
+    webgl->ForceClearFramebufferWithDefaultValues(clearBits, fakeNoAlpha);
+}
+
+static bool
+ZeroTextureData(WebGLContext* webgl, const char* funcName, GLuint tex,
+                TexImageTarget target, uint32_t level,
+                const webgl::FormatUsageInfo* usage, uint32_t width, uint32_t height,
+                uint32_t depth)
+{
+    // This has two usecases:
+    // 1. Lazy zeroing of uninitialized textures:
+    //    a. Before draw, when FakeBlack isn't viable. (TexStorage + Draw*)
+    //    b. Before partial upload. (TexStorage + TexSubImage)
+    // 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image)
+
+    // We have no sympathy for any of these cases.
+
+    // "Doctor, it hurts when I do this!" "Well don't do that!"
+    webgl->GenerateWarning("%s: This operation requires zeroing texture data. This is"
+                           " slow.",
+                           funcName);
+
+    gl::GLContext* gl = webgl->GL();
+    gl->MakeCurrent();
+
+    GLenum scopeBindTarget;
+    switch (target.get()) {
+    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:
+    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
+        scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
+        break;
+    default:
+        scopeBindTarget = target.get();
+        break;
+    }
+    const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
+    auto compression = usage->format->compression;
+    if (compression) {
+        auto sizedFormat = usage->format->sizedFormat;
+        MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
+
+        const auto fnSizeInBlocks = [](CheckedUint32 pixels, uint8_t pixelsPerBlock) {
+            return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
+        };
+
+        const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth);
+        const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight);
+
+        CheckedUint32 checkedByteCount = compression->bytesPerBlock;
+        checkedByteCount *= widthBlocks;
+        checkedByteCount *= heightBlocks;
+        checkedByteCount *= depth;
+
+        if (!checkedByteCount.isValid())
+            return false;
+
+        const size_t byteCount = checkedByteCount.value();
+
+        UniqueBuffer zeros = calloc(1, byteCount);
+        if (!zeros)
+            return false;
+
+        ScopedUnpackReset scopedReset(webgl);
+        gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it
+                                                        // well.
+
+        const auto error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, 0,
+                                                   width, height, depth, sizedFormat,
+                                                   byteCount, zeros.get());
+        return !error;
+    }
+
+    const auto driverUnpackInfo = usage->idealUnpack;
+    MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
+
+    if (webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture) &&
+        gl->IsANGLE() &&
+        usage->format->d)
+    {
+        // ANGLE_depth_texture does not allow uploads, so we have to clear.
+        // (Restriction because of D3D9)
+        MOZ_ASSERT(target == LOCAL_GL_TEXTURE_2D);
+        MOZ_ASSERT(level == 0);
+        ZeroANGLEDepthTexture(webgl, tex, usage, width, height);
+        return true;
+    }
+
+    const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
+
+    const auto bytesPerPixel = webgl::BytesPerPixel(packing);
+
+    CheckedUint32 checkedByteCount = bytesPerPixel;
+    checkedByteCount *= width;
+    checkedByteCount *= height;
+    checkedByteCount *= depth;
+
+    if (!checkedByteCount.isValid())
+        return false;
+
+    const size_t byteCount = checkedByteCount.value();
+
+    UniqueBuffer zeros = calloc(1, byteCount);
+    if (!zeros)
+        return false;
+
+    ScopedUnpackReset scopedReset(webgl);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it well.
+    const auto error = DoTexSubImage(gl, target, level, 0, 0, 0, width, height, depth,
+                                     packing, zeros.get());
+    return !error;
+}
+
 bool
 WebGLTexture::InitializeImageData(const char* funcName, TexImageTarget target,
                                   uint32_t level)
 {
     auto& imageInfo = ImageInfoAt(target, level);
     MOZ_ASSERT(imageInfo.IsDefined());
     MOZ_ASSERT(!imageInfo.IsDataInitialized());
 
     const auto& usage = imageInfo.mFormat;
     const auto& width = imageInfo.mWidth;
     const auto& height = imageInfo.mHeight;
     const auto& depth = imageInfo.mDepth;
 
-    if (!ZeroTextureData(mContext, funcName, mGLName, target, level, usage, 0, 0, 0,
-                         width, height, depth))
+    if (!ZeroTextureData(mContext, funcName, mGLName, target, level, usage, width, height,
+                         depth))
     {
         return false;
     }
 
     imageInfo.SetIsDataInitialized(true, this);
     return true;
 }
 
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -1915,16 +1915,100 @@ WebGLTexture::ValidateCopyTexImageForFee
                                             " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
                                             funcName, attach->mAttachmentPoint);
             return false;
         }
     }
     return true;
 }
 
+static bool
+DoCopyTexOrSubImage(WebGLContext* webgl, const char* funcName, bool isSubImage,
+                    const WebGLTexture* tex, TexImageTarget target, GLint level,
+                    GLint xWithinSrc, GLint yWithinSrc,
+                    uint32_t srcTotalWidth, uint32_t srcTotalHeight,
+                    const webgl::FormatUsageInfo* srcUsage,
+                    GLint xOffset, GLint yOffset, GLint zOffset,
+                    uint32_t dstWidth, uint32_t dstHeight,
+                    const webgl::FormatUsageInfo* dstUsage)
+{
+    gl::GLContext* gl = webgl->gl;
+    gl->MakeCurrent();
+
+    ////
+
+    uint32_t readX, readY;
+    uint32_t writeX, writeY;
+    uint32_t rwWidth, rwHeight;
+    Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX, &rwWidth);
+    Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY, &rwHeight);
+
+    ////
+
+    GLenum error = 0;
+    do {
+        const auto& idealUnpack = dstUsage->idealUnpack;
+        if (!isSubImage) {
+            UniqueBuffer buffer;
+
+            if (rwWidth != dstWidth || rwHeight != dstHeight) {
+                const auto& pi = idealUnpack->ToPacking();
+                CheckedUint32 byteCount = BytesPerPixel(pi);
+                byteCount *= dstWidth;
+                byteCount *= dstHeight;
+
+                if (byteCount.isValid()) {
+                    buffer = calloc(1, byteCount.value());
+                }
+
+                if (!buffer.get()) {
+                    webgl->ErrorOutOfMemory("%s: Ran out of memory allocating zeros.",
+                                            funcName);
+                    return false;
+                }
+            }
+
+            const ScopedUnpackReset unpackReset(webgl);
+            gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
+            error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight, 1,
+                               buffer.get());
+            if (error)
+                break;
+        }
+
+        if (!rwWidth || !rwHeight) {
+            // There aren't any pixels to copy, so we're 'done'.
+            return true;
+        }
+
+        const auto& srcFormat = srcUsage->format;
+        ScopedCopyTexImageSource maybeSwizzle(webgl, funcName, srcTotalWidth,
+                                              srcTotalHeight, srcFormat, dstUsage);
+
+        const uint8_t zOffset = 0;
+        error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
+                                  readY, rwWidth, rwHeight);
+        if (error)
+            break;
+
+        return true;
+    } while (false);
+
+    if (error == LOCAL_GL_OUT_OF_MEMORY) {
+        webgl->ErrorOutOfMemory("%s: Ran out of memory during texture copy.", funcName);
+        return false;
+    }
+
+    MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors.");
+    webgl->GenerateWarning("%s: Unexpected error during texture copy. Context lost.",
+                           funcName);
+    webgl->ForceLoseContext();
+    return false;
+}
+
 // There is no CopyTexImage3D.
 void
 WebGLTexture::CopyTexImage2D(TexImageTarget target, GLint level, GLenum internalFormat,
                              GLint x, GLint y, GLsizei rawWidth, GLsizei rawHeight,
                              GLint border)
 {
     const char funcName[] = "copyTexImage2D";
 
@@ -1945,107 +2029,57 @@ WebGLTexture::CopyTexImage2D(TexImageTar
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     ////////////////////////////////////
     // Get source info
 
     const webgl::FormatUsageInfo* srcUsage;
-    uint32_t srcWidth;
-    uint32_t srcHeight;
-    if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcWidth, &srcHeight))
+    uint32_t srcTotalWidth;
+    uint32_t srcTotalHeight;
+    if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcTotalWidth,
+                                        &srcTotalHeight))
+    {
         return;
-    auto srcFormat = srcUsage->format;
+    }
 
     if (!ValidateCopyTexImageForFeedback(funcName, level))
         return;
 
     ////////////////////////////////////
     // Check that source and dest info are compatible
 
+    const auto& srcFormat = srcUsage->format;
     const auto dstUsage = ValidateCopyDestUsage(funcName, mContext, srcFormat,
                                                 internalFormat);
     if (!dstUsage)
         return;
 
-    const auto dstFormat = dstUsage->format;
-
+    const auto& dstFormat = dstUsage->format;
     if (!ValidateTargetForFormat(funcName, mContext, target, dstFormat))
         return;
 
     if (!mContext->IsWebGL2() && dstFormat->d) {
         mContext->ErrorInvalidOperation("%s: Function may not be called with format %s.",
                                         funcName, dstFormat->name);
         return;
     }
 
     if (!ValidateCopyTexImageFormats(mContext, funcName, srcFormat, dstFormat))
         return;
 
     ////////////////////////////////////
     // Do the thing!
 
-    gl::GLContext* gl = mContext->gl;
-    gl->MakeCurrent();
-
-    ScopedCopyTexImageSource maybeSwizzle(mContext, funcName, srcWidth, srcHeight,
-                                          srcFormat, dstUsage);
-
-    uint32_t readX, readY;
-    uint32_t writeX, writeY;
-    uint32_t rwWidth, rwHeight;
-    Intersect(srcWidth, x, width, &readX, &writeX, &rwWidth);
-    Intersect(srcHeight, y, height, &readY, &writeY, &rwHeight);
-
-    const auto& idealUnpack = dstUsage->idealUnpack;
-    const auto& driverInternalFormat = idealUnpack->internalFormat;
-
-    GLenum error = DoCopyTexImage2D(gl, target, level, driverInternalFormat, x, y, width,
-                                    height);
-    do {
-        if (rwWidth == uint32_t(width) && rwHeight == uint32_t(height))
-            break;
-
-        if (error)
-            break;
-
-        // 1. Zero the texture data.
-        // 2. CopyTexSubImage the subrect.
-
-        const uint8_t zOffset = 0;
-        if (!ZeroTextureData(mContext, funcName, mGLName, target, level, dstUsage, 0, 0,
-                             zOffset, width, height, depth))
-        {
-            mContext->ErrorOutOfMemory("%s: Failed to zero texture data.", funcName);
-            MOZ_ASSERT(false, "Failed to zero texture data.");
-            return;
-        }
-
-        if (!rwWidth || !rwHeight) {
-            // There aren't any, so we're 'done'.
-            mContext->DummyReadFramebufferOperation(funcName);
-            return;
-        }
-
-        error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX,
-                                  readY, rwWidth, rwHeight);
-    } while (false);
-
-    if (error == LOCAL_GL_OUT_OF_MEMORY) {
-        mContext->ErrorOutOfMemory("%s: Ran out of memory during texture copy.",
-                                   funcName);
-        return;
-    }
-    if (error) {
-        MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors.");
-        mContext->GenerateWarning("%s: Unexpected error during texture copy. Context"
-                                  " lost.",
-                                  funcName);
-        mContext->ForceLoseContext();
+    const bool isSubImage = false;
+    if (!DoCopyTexOrSubImage(mContext, funcName, isSubImage, this, target, level, x, y,
+                             srcTotalWidth, srcTotalHeight, srcUsage, 0, 0, 0, width,
+                             height, dstUsage))
+    {
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
     const bool isDataInitialized = true;
     const ImageInfo newImageInfo(dstUsage, width, height, depth, isDataInitialized);
@@ -2072,87 +2106,63 @@ WebGLTexture::CopyTexSubImage(const char
                                    width, height, depth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     auto dstUsage = imageInfo->mFormat;
     MOZ_ASSERT(dstUsage);
+
     auto dstFormat = dstUsage->format;
-
     if (!mContext->IsWebGL2() && dstFormat->d) {
         mContext->ErrorInvalidOperation("%s: Function may not be called on a texture of"
                                         " format %s.",
                                         funcName, dstFormat->name);
         return;
     }
 
     ////////////////////////////////////
     // Get source info
 
     const webgl::FormatUsageInfo* srcUsage;
-    uint32_t srcWidth;
-    uint32_t srcHeight;
-    if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcWidth, &srcHeight))
+    uint32_t srcTotalWidth;
+    uint32_t srcTotalHeight;
+    if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcTotalWidth,
+                                        &srcTotalHeight))
+    {
         return;
-    auto srcFormat = srcUsage->format;
+    }
 
     if (!ValidateCopyTexImageForFeedback(funcName, level, zOffset))
         return;
 
     ////////////////////////////////////
     // Check that source and dest info are compatible
 
+    auto srcFormat = srcUsage->format;
     if (!ValidateCopyTexImageFormats(mContext, funcName, srcFormat, dstFormat))
         return;
 
     ////////////////////////////////////
     // Do the thing!
 
-    mContext->gl->MakeCurrent();
-
-    ScopedCopyTexImageSource maybeSwizzle(mContext, funcName, srcWidth, srcHeight,
-                                          srcFormat, dstUsage);
-
-    uint32_t readX, readY;
-    uint32_t writeX, writeY;
-    uint32_t rwWidth, rwHeight;
-    Intersect(srcWidth, x, width, &readX, &writeX, &rwWidth);
-    Intersect(srcHeight, y, height, &readY, &writeY, &rwHeight);
-
-    if (!rwWidth || !rwHeight) {
-        // There aren't any, so we're 'done'.
-        mContext->DummyReadFramebufferOperation(funcName);
-        return;
-    }
-
     bool uploadWillInitialize;
     if (!EnsureImageDataInitializedForUpload(this, funcName, target, level, xOffset,
                                              yOffset, zOffset, width, height, depth,
                                              imageInfo, &uploadWillInitialize))
     {
         return;
     }
 
-    GLenum error = DoCopyTexSubImage(mContext->gl, target, level, xOffset + writeX,
-                                     yOffset + writeY, zOffset, readX, readY, rwWidth,
-                                     rwHeight);
-
-    if (error == LOCAL_GL_OUT_OF_MEMORY) {
-        mContext->ErrorOutOfMemory("%s: Ran out of memory during texture copy.",
-                                   funcName);
-        return;
-    }
-    if (error) {
-        MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors.");
-        mContext->GenerateWarning("%s: Unexpected error during texture copy. Context"
-                                  " lost.",
-                                  funcName);
-        mContext->ForceLoseContext();
+    const bool isSubImage = true;
+    if (!DoCopyTexOrSubImage(mContext, funcName, isSubImage, this, target, level, x, y,
+                             srcTotalWidth, srcTotalHeight, srcUsage, xOffset, yOffset,
+                             zOffset, width, height, dstUsage))
+    {
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data?
 
     if (uploadWillInitialize) {
         imageInfo->SetIsDataInitialized(true, this);