Bug 1280499 - Support paranoid uploading for nVidia. - r=jrmuizel draft
authorJeff Gilbert <jgilbert@mozilla.com>
Thu, 07 Jul 2016 18:08:37 -0700
changeset 385251 0596ddcc955f8b69d83c945a6ca99b239e9732b1
parent 385250 a0f1ccd3cff012f7c80f11a3f92df7dfc3e176a2
child 524894 3bd3d34e62c43dda79f47540d094ba6fdd93b2c1
push id22467
push userbmo:jgilbert@mozilla.com
push dateFri, 08 Jul 2016 01:17:53 +0000
reviewersjrmuizel
bugs1280499
milestone50.0a1
Bug 1280499 - Support paranoid uploading for nVidia. - r=jrmuizel MozReview-Commit-ID: DBpSHGoI8F4
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
dom/canvas/WebGLTextureUpload.cpp
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -7,16 +7,17 @@
 
 #include "GLBlitHelper.h"
 #include "GLContext.h"
 #include "GLDefs.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/RefPtr.h"
 #include "nsLayoutUtils.h"
+#include "WebGLBuffer.h"
 #include "WebGLContext.h"
 #include "WebGLTexelConversions.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 namespace webgl {
 
 TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, uint32_t alignment,
@@ -28,16 +29,18 @@ TexUnpackBlob::TexUnpackBlob(const WebGL
 
     , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
     , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
     , mSkipImages(webgl->mPixelStore_UnpackSkipImages)
 
     , mWidth(width)
     , mHeight(height)
     , mDepth(depth)
+
+    , mNeedsExactUpload(false)
 { }
 
 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) {
@@ -145,33 +148,34 @@ 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* uploadPtr = mPtr;
+    const webgl::PackingInfo pi = { dui->unpackFormat, dui->unpackType };
+    const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+
+    auto uploadPtr = (const uint8_t*)mPtr;
     UniqueBuffer tempBuffer;
 
     do {
         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;
 
         if (!needsYFlip && !needsAlphaPremult)
             break;
@@ -196,18 +200,16 @@ TexUnpackBytes::TexOrSubImage(bool isSub
                                          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);
@@ -230,17 +232,17 @@ TexUnpackBytes::TexOrSubImage(bool isSub
             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();
             }
 
-            uploadPtr = tempBuffer.get();
+            uploadPtr = (const uint8_t*)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.",
@@ -266,21 +268,90 @@ TexUnpackBytes::TexOrSubImage(bool isSub
         {
             MOZ_ASSERT(false, "ConvertImage failed unexpectedly.");
             webgl->ErrorOutOfMemory("%s: ConvertImage failed during PREMULTIPLY_ALPHA"
                                     " handling.",
                                     funcName);
             return false;
         }
 
-        uploadPtr = tempBuffer.get();
+        uploadPtr = (const uint8_t*)tempBuffer.get();
     } while (false);
 
-    *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
-                                 zOffset, mWidth, mHeight, mDepth, uploadPtr);
+    //////
+
+    bool useParanoidHandling = false;
+    if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
+        webgl->GenerateWarning("%s: Uploads from a buffer with a final row with a byte"
+                               " count smaller than the row stride can incur extra"
+                               " overhead.",
+                               funcName);
+
+        if (gl->WorkAroundDriverBugs()) {
+            useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA);
+        }
+    }
+
+    if (!useParanoidHandling) {
+        *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
+                                     zOffset, mWidth, mHeight, mDepth, uploadPtr);
+        return true;
+    }
+
+    //////
+
+    MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
+
+    if (!isSubImage) {
+        // Alloc first to catch OOMs.
+        gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
+        *out_error = DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
+                                     zOffset, mWidth, mHeight, mDepth, nullptr);
+        gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER,
+                        webgl->mBoundPixelUnpackBuffer->mGLName);
+    }
+
+    //////
+
+    if (mDepth > 1) {
+        *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
+                                     zOffset, mWidth, mHeight, mDepth-1, uploadPtr);
+    }
+
+    if (mHeight > 1) {
+        *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
+                                     zOffset+mDepth-1, mWidth, mHeight-1, 1, uploadPtr);
+    }
+
+    const uint32_t bytesPerRow = mRowLength * bytesPerPixel;
+    const uint32_t rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
+    const uint32_t imageStride = rowStride * mImageHeight;
+
+    const uint32_t usedImages = mSkipImages + mDepth;
+    const uint32_t usedRows = mSkipRows + mHeight;
+
+    uploadPtr += (usedImages - 1) * imageStride;
+    uploadPtr += (usedRows - 1) * rowStride;
+
+    //////
+
+    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, 0);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0);
+
+    *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
+                                 yOffset+mHeight-1, zOffset+mDepth-1, mWidth, 1, 1,
+                                 uploadPtr);
+
+    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, webgl->mPixelStore_UnpackImageHeight);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, webgl->mPixelStore_UnpackSkipImages);
+    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, webgl->mPixelStore_UnpackSkipRows);
+
     return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackImage
 
 TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, uint32_t imageHeight,
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -52,28 +52,30 @@ public:
     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;
 
+    bool mNeedsExactUpload;
+
 protected:
     TexUnpackBlob(const WebGLContext* webgl, uint32_t alignment, uint32_t rowLength,
                   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;
+                               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:
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -79,17 +79,17 @@ 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)
+                     uint32_t tailPixels, 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() ||
@@ -121,40 +121,42 @@ ValidateUnpackPixels(WebGLContext* webgl
         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())
+    if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value()) {
+        blob->mNeedsExactUpload = true;
         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)
+                    uint32_t byteCount, 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;
+        return false;
     }
 
     const auto bodyBytes = fullRows.value() * rowStride.value();
     const auto tailPixels = (byteCount - bodyBytes) / bytesPerPixel;
 
     return ValidateUnpackPixels(webgl, funcName, fullRows.value(), tailPixels, blob);
 }
 
@@ -262,17 +264,17 @@ WebGLTexture::TexOrSubImage(bool isSubIm
             view.ComputeLengthAndData();
 
             bytes = view.DataAllowShared();
             byteCount = view.LengthAllowShared();
         }
     }
 
     const bool isClientData = true;
-    const webgl::TexUnpackBytes blob(mContext, width, height, depth, isClientData, bytes);
+    webgl::TexUnpackBytes blob(mContext, width, height, depth, isClientData, bytes);
 
     //////
 
     if (bytes && !ValidateUnpackBytes(mContext, funcName, width, height, depth, pi,
                                       byteCount, &blob))
     {
         return;
     }
@@ -308,17 +310,17 @@ WebGLTexture::TexOrSubImage(bool isSubIm
 
     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);
+    webgl::TexUnpackBytes blob(mContext, width, height, depth, isClientData, ptr);
 
     //////
 
     const auto& packBuffer = mContext->mBoundPixelUnpackBuffer;
     const auto bufferByteCount = packBuffer->ByteLength();
 
     uint32_t byteCount = 0;
     if (bufferByteCount >= offset) {
@@ -436,18 +438,18 @@ WebGLTexture::TexOrSubImage(bool isSubIm
     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 webgl::TexUnpackSurface blob(mContext, imageHeight, width, height, depth, surf,
-                                       surfIsAlphaPremult);
+    webgl::TexUnpackSurface blob(mContext, imageHeight, width, height, depth, surf,
+                                 surfIsAlphaPremult);
 
     const uint32_t fullRows = imageData->Height();
     const uint32_t tailPixels = 0;
     if (!ValidateUnpackPixels(mContext, funcName, fullRows, tailPixels, &blob))
         return;
 
     TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
                       yOffset, zOffset, pi, &blob);
@@ -534,17 +536,17 @@ WebGLTexture::TexOrSubImage(bool isSubIm
 
     uint32_t rowLength, imageHeight;
     if (!mContext->GetUnpackValuesForImage(funcName, elemWidth, elemHeight, &rowLength,
                                            &imageHeight))
     {
         return;
     }
 
-    UniquePtr<const webgl::TexUnpackBlob> blob;
+    UniquePtr<webgl::TexUnpackBlob> blob;
     const bool isAlphaPremult = sfer.mIsPremultiplied;
 
     if (layersImage) {
         blob.reset(new webgl::TexUnpackImage(mContext, imageHeight, width, height, depth,
                                              layersImage, isAlphaPremult));
     } else {
         MOZ_ASSERT(surf);
         blob.reset(new webgl::TexUnpackSurface(mContext, imageHeight, width, height,