summaryrefslogtreecommitdiffstats
path: root/dom/canvas/TexUnpackBlob.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/TexUnpackBlob.cpp')
-rw-r--r--dom/canvas/TexUnpackBlob.cpp831
1 files changed, 831 insertions, 0 deletions
diff --git a/dom/canvas/TexUnpackBlob.cpp b/dom/canvas/TexUnpackBlob.cpp
new file mode 100644
index 000000000..e964a58fd
--- /dev/null
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -0,0 +1,831 @@
+/* -*- 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 "TexUnpackBlob.h"
+
+#include "GLBlitHelper.h"
+#include "GLContext.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 {
+
+static bool
+IsPIValidForDOM(const webgl::PackingInfo& pi)
+{
+ // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
+
+ // Just check for invalid individual formats and types, not combinations.
+ switch (pi.format) {
+ case LOCAL_GL_RGB:
+ case LOCAL_GL_RGBA:
+ case LOCAL_GL_LUMINANCE_ALPHA:
+ case LOCAL_GL_LUMINANCE:
+ case LOCAL_GL_ALPHA:
+ case LOCAL_GL_RED:
+ case LOCAL_GL_RED_INTEGER:
+ case LOCAL_GL_RG:
+ case LOCAL_GL_RG_INTEGER:
+ case LOCAL_GL_RGB_INTEGER:
+ case LOCAL_GL_RGBA_INTEGER:
+ break;
+
+ case LOCAL_GL_SRGB:
+ case LOCAL_GL_SRGB_ALPHA:
+ // Allowed in WebGL1+EXT_srgb
+ break;
+
+ default:
+ return false;
+ }
+
+ switch (pi.type) {
+ case LOCAL_GL_UNSIGNED_BYTE:
+ case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
+ case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
+ case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
+ case LOCAL_GL_HALF_FLOAT:
+ case LOCAL_GL_HALF_FLOAT_OES:
+ case LOCAL_GL_FLOAT:
+ case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+ValidatePIForDOM(WebGLContext* webgl, const char* funcName,
+ const webgl::PackingInfo& pi)
+{
+ if (!IsPIValidForDOM(pi)) {
+ webgl->ErrorInvalidOperation("%s: Format or type is invalid for DOM sources.",
+ funcName);
+ return false;
+ }
+ return true;
+}
+
+static WebGLTexelFormat
+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:
+ if (pi.format == LOCAL_GL_RGB)
+ return WebGLTexelFormat::RGB565;
+ break;
+
+ case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
+ if (pi.format == LOCAL_GL_RGBA)
+ return WebGLTexelFormat::RGBA5551;
+ break;
+
+ case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
+ 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_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_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 bool
+ValidateUnpackPixels(WebGLContext* webgl, const char* funcName, uint32_t fullRows,
+ uint32_t tailPixels, webgl::TexUnpackBlob* blob)
+{
+ if (!blob->mWidth || !blob->mHeight || !blob->mDepth)
+ return true;
+
+ const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
+ if (!usedPixelsPerRow.isValid() || usedPixelsPerRow.value() > blob->mRowLength) {
+ webgl->ErrorInvalidOperation("%s: UNPACK_SKIP_PIXELS + width >"
+ " UNPACK_ROW_LENGTH.",
+ funcName);
+ return false;
+ }
+
+ if (blob->mHeight > blob->mImageHeight) {
+ webgl->ErrorInvalidOperation("%s: height > UNPACK_IMAGE_HEIGHT.", funcName);
+ return false;
+ }
+
+ //////
+
+ // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
+ auto skipFullRows = CheckedUint32(blob->mSkipImages) * blob->mImageHeight;
+ skipFullRows += blob->mSkipRows;
+
+ MOZ_ASSERT(blob->mDepth >= 1);
+ MOZ_ASSERT(blob->mHeight >= 1);
+ auto usedFullRows = CheckedUint32(blob->mDepth - 1) * blob->mImageHeight;
+ usedFullRows += blob->mHeight - 1; // Full rows in the final image, excluding the tail.
+
+ const auto fullRowsNeeded = skipFullRows + usedFullRows;
+ 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()) {
+ 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,
+ const webgl::PackingInfo& pi, size_t availByteCount,
+ webgl::TexUnpackBlob* blob)
+{
+ if (!blob->mWidth || !blob->mHeight || !blob->mDepth)
+ return true;
+
+ const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+ const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
+ const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
+
+ const auto fullRows = availByteCount / rowStride;
+ if (!fullRows.isValid()) {
+ webgl->ErrorOutOfMemory("%s: Unacceptable upload size calculated.");
+ return false;
+ }
+
+ const auto bodyBytes = fullRows.value() * rowStride.value();
+ const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel;
+
+ return ValidateUnpackPixels(webgl, funcName, fullRows.value(), tailPixels, blob);
+}
+
+////////////////////
+
+static uint32_t
+ZeroOn2D(TexImageTarget target, uint32_t val)
+{
+ return (IsTarget3D(target) ? val : 0);
+}
+
+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 srcIsPremult)
+ : mAlignment(webgl->mPixelStore_UnpackAlignment)
+ , mRowLength(rowLength)
+ , mImageHeight(FallbackOnZero(ZeroOn2D(target, webgl->mPixelStore_UnpackImageHeight),
+ height))
+
+ , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
+ , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
+ , mSkipImages(ZeroOn2D(target, webgl->mPixelStore_UnpackSkipImages))
+
+ , mWidth(width)
+ , mHeight(height)
+ , mDepth(depth)
+
+ , mSrcIsPremult(srcIsPremult)
+
+ , mNeedsExactUpload(false)
+{
+ MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
+}
+
+bool
+TexUnpackBlob::ConvertIfNeeded(WebGLContext* webgl, const char* funcName,
+ const uint32_t rowLength, const uint32_t rowCount,
+ WebGLTexelFormat srcFormat,
+ const uint8_t* const srcBegin, const ptrdiff_t srcStride,
+ WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
+ const uint8_t** const out_begin,
+ UniqueBuffer* const out_anchoredBuffer) const
+{
+ MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
+ MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
+
+ *out_begin = srcBegin;
+
+ if (!rowLength || !rowCount)
+ return true;
+
+ const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
+ const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
+ : gl::OriginPos::BottomLeft);
+ const auto dstOrigin = gl::OriginPos::BottomLeft;
+
+ if (srcFormat != dstFormat) {
+ webgl->GenerateWarning("%s: Conversion requires pixel reformatting.", funcName);
+ } else if (mSrcIsPremult != dstIsPremult) {
+ webgl->GenerateWarning("%s: Conversion requires change in"
+ "alpha-premultiplication.",
+ funcName);
+ } else if (srcOrigin != dstOrigin) {
+ webgl->GenerateWarning("%s: Conversion requires y-flip.", funcName);
+ } else if (srcStride != dstStride) {
+ webgl->GenerateWarning("%s: Conversion requires change in stride.", funcName);
+ } else {
+ return true;
+ }
+
+ ////
+
+ const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
+ if (!dstTotalBytes.isValid()) {
+ webgl->ErrorOutOfMemory("%s: Calculation failed.", funcName);
+ return false;
+ }
+
+ UniqueBuffer dstBuffer = calloc(1, dstTotalBytes.value());
+ if (!dstBuffer.get()) {
+ webgl->ErrorOutOfMemory("%s: Failed to allocate dest buffer.", funcName);
+ return false;
+ }
+ const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
+
+ ////
+
+ // And go!:
+ bool wasTrivial;
+ if (!ConvertImage(rowLength, rowCount,
+ srcBegin, srcStride, srcOrigin, srcFormat, mSrcIsPremult,
+ dstBegin, dstStride, dstOrigin, dstFormat, dstIsPremult,
+ &wasTrivial))
+ {
+ webgl->ErrorImplementationBug("%s: ConvertImage failed.", funcName);
+ return false;
+ }
+
+ *out_begin = dstBegin;
+ *out_anchoredBuffer = Move(dstBuffer);
+ 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,
+ bool isClientData, const uint8_t* ptr, size_t availBytes)
+ : TexUnpackBlob(webgl, target,
+ FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
+ width, height, depth, false)
+ , mIsClientData(isClientData)
+ , mPtr(ptr)
+ , mAvailBytes(availBytes)
+{ }
+
+bool
+TexUnpackBytes::Validate(WebGLContext* webgl, const char* funcName,
+ const webgl::PackingInfo& pi)
+{
+ if (mIsClientData && !mPtr)
+ return true;
+
+ return ValidateUnpackBytes(webgl, funcName, pi, mAvailBytes, this);
+}
+
+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;
+
+ const auto pi = dui->ToPacking();
+ const auto format = FormatForPackingInfo(pi);
+ const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+
+ const uint8_t* uploadPtr = mPtr;
+ UniqueBuffer tempBuffer;
+
+ do {
+ if (!mIsClientData || !mPtr)
+ break;
+
+ if (!webgl->mPixelStore_FlipY &&
+ !webgl->mPixelStore_PremultiplyAlpha)
+ {
+ break;
+ }
+
+ if (webgl->mPixelStore_UnpackImageHeight ||
+ webgl->mPixelStore_UnpackSkipImages ||
+ webgl->mPixelStore_UnpackRowLength ||
+ webgl->mPixelStore_UnpackSkipRows ||
+ webgl->mPixelStore_UnpackSkipPixels)
+ {
+ webgl->ErrorInvalidOperation("%s: Non-DOM-Element uploads with alpha-premult"
+ " or y-flip do not support subrect selection.",
+ funcName);
+ return false;
+ }
+
+ webgl->GenerateWarning("%s: Alpha-premult and y-flip are deprecated for"
+ " non-DOM-Element uploads.",
+ funcName);
+
+ const uint32_t rowLength = mWidth;
+ const uint32_t rowCount = mHeight * mDepth;
+ const auto stride = RoundUpToMultipleOf(rowLength * bytesPerPixel, mAlignment);
+ if (!ConvertIfNeeded(webgl, funcName, rowLength, rowCount, format, mPtr, stride,
+ format, stride, &uploadPtr, &tempBuffer))
+ {
+ return false;
+ }
+ } while (false);
+
+ //////
+
+ const auto& gl = webgl->gl;
+
+ 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) {
+ if (webgl->mBoundPixelUnpackBuffer) {
+ gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER,
+ webgl->mBoundPixelUnpackBuffer->mGLName);
+ }
+
+ *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
+ zOffset, mWidth, mHeight, mDepth, uploadPtr);
+
+ if (webgl->mBoundPixelUnpackBuffer) {
+ gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+ return true;
+ }
+
+ //////
+
+ MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
+
+ if (!isSubImage) {
+ // Alloc first to catch OOMs.
+ AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
+ *out_error = DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
+ zOffset, mWidth, mHeight, mDepth, nullptr);
+ if (*out_error)
+ return true;
+ }
+
+ const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
+ webgl->mBoundPixelUnpackBuffer);
+
+ //////
+
+ // Make our sometimes-implicit values explicit. Also this keeps them constant when we
+ // ask for height=mHeight-1 and such.
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength);
+ gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mImageHeight);
+
+ if (mDepth > 1) {
+ *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
+ zOffset, mWidth, mHeight, mDepth-1, uploadPtr);
+ }
+
+ // Skip the images we uploaded.
+ gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mSkipImages + mDepth - 1);
+
+ if (mHeight > 1) {
+ *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
+ zOffset+mDepth-1, mWidth, mHeight-1, 1, uploadPtr);
+ }
+
+ const auto totalSkipRows = CheckedUint32(mSkipImages) * mImageHeight + mSkipRows;
+ const auto totalFullRows = CheckedUint32(mDepth - 1) * mImageHeight + mHeight - 1;
+ const auto tailOffsetRows = totalSkipRows + totalFullRows;
+
+ const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
+ const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
+ if (!rowStride.isValid()) {
+ MOZ_CRASH("Should be checked earlier.");
+ }
+ const auto tailOffsetBytes = tailOffsetRows * rowStride;
+
+ uploadPtr += tailOffsetBytes.value();
+
+ //////
+
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // No stride padding.
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); // No padding in general.
+ gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images,
+ gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0); // or rows.
+ // Keep skipping pixels though!
+
+ *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
+ yOffset+mHeight-1, zOffset+mDepth-1, mWidth, 1, 1,
+ uploadPtr);
+
+ // Reset all our modified state.
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
+ gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, webgl->mPixelStore_UnpackImageHeight);
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
+ 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, TexImageTarget target,
+ uint32_t width, uint32_t height, uint32_t depth,
+ layers::Image* image, bool isAlphaPremult)
+ : TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth,
+ isAlphaPremult)
+ , mImage(image)
+{ }
+
+TexUnpackImage::~TexUnpackImage()
+{ }
+
+bool
+TexUnpackImage::Validate(WebGLContext* webgl, const char* funcName,
+ const webgl::PackingInfo& pi)
+{
+ if (!ValidatePIForDOM(webgl, funcName, pi))
+ return false;
+
+ const auto fullRows = mImage->GetSize().height;
+ return ValidateUnpackPixels(webgl, funcName, fullRows, 0, this);
+}
+
+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
+{
+ MOZ_ASSERT_IF(needsRespec, !isSubImage);
+
+ WebGLContext* webgl = tex->mContext;
+
+ gl::GLContext* gl = webgl->GL();
+ gl->MakeCurrent();
+
+ if (needsRespec) {
+ *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
+ yOffset, zOffset, mWidth, mHeight, mDepth,
+ nullptr);
+ if (*out_error)
+ return true;
+ }
+
+ do {
+ if (mDepth != 1)
+ break;
+
+ const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
+ if (mSrcIsPremult != dstIsPremult)
+ 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());
+
+ {
+ gl::GLContext::LocalErrorScope errorScope(*gl);
+
+ gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
+ target.get(), tex->mGLName, level);
+
+ if (errorScope.GetError())
+ break;
+ }
+
+ const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+ if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
+ break;
+
+ 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);
+
+ const RefPtr<gfx::SourceSurface> surf = mImage->GetAsSourceSurface();
+
+ RefPtr<gfx::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;
+ }
+
+ const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf,
+ mSrcIsPremult);
+
+ return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level,
+ dui, xOffset, yOffset, zOffset, out_error);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+// TexUnpackSurface
+
+TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target,
+ uint32_t width, uint32_t height, uint32_t depth,
+ gfx::DataSourceSurface* surf, bool isAlphaPremult)
+ : TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth,
+ isAlphaPremult)
+ , mSurf(surf)
+{ }
+
+//////////
+
+static bool
+GetFormatForSurf(gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat,
+ uint8_t* const out_bpp)
+{
+ 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;
+ }
+}
+
+//////////
+
+bool
+TexUnpackSurface::Validate(WebGLContext* webgl, const char* funcName,
+ const webgl::PackingInfo& pi)
+{
+ if (!ValidatePIForDOM(webgl, funcName, pi))
+ return false;
+
+ const auto fullRows = mSurf->GetSize().height;
+ return ValidateUnpackPixels(webgl, funcName, fullRows, 0, this);
+}
+
+bool
+TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
+ WebGLTexture* tex, TexImageTarget target, GLint level,
+ const webgl::DriverUnpackInfo* dstDUI, GLint xOffset,
+ GLint yOffset, GLint zOffset,
+ GLenum* const out_error) const
+{
+ const auto& webgl = tex->mContext;
+
+ ////
+
+ const auto rowLength = mSurf->GetSize().width;
+ const auto rowCount = mSurf->GetSize().height;
+
+ const auto& dstPI = dstDUI->ToPacking();
+ const auto& dstBPP = webgl::BytesPerPixel(dstPI);
+ const auto dstFormat = FormatForPackingInfo(dstPI);
+
+ ////
+
+ 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;
+ }
+
+ gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ);
+ if (!map.IsMapped()) {
+ webgl->ErrorOutOfMemory("%s: Failed to map source surface for upload.", funcName);
+ return false;
+ }
+
+ const auto& srcBegin = map.GetData();
+ const auto& srcStride = map.GetStride();
+
+ ////
+
+ const auto srcRowLengthBytes = rowLength * srcBPP;
+
+ const uint8_t maxGLAlignment = 8;
+ uint8_t srcAlignment = 1;
+ for (; srcAlignment <= maxGLAlignment; srcAlignment *= 2) {
+ const auto strideGuess = RoundUpToMultipleOf(srcRowLengthBytes, srcAlignment);
+ if (strideGuess == srcStride)
+ break;
+ }
+ const uint32_t dstAlignment = (srcAlignment > maxGLAlignment) ? 1 : srcAlignment;
+
+ const auto dstRowLengthBytes = rowLength * dstBPP;
+ const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
+
+ ////
+
+ const uint8_t* dstBegin = srcBegin;
+ UniqueBuffer tempBuffer;
+ if (!ConvertIfNeeded(webgl, funcName, rowLength, rowCount, srcFormat, srcBegin,
+ srcStride, dstFormat, dstStride, &dstBegin, &tempBuffer))
+ {
+ return false;
+ }
+
+ ////
+
+ const auto& gl = webgl->gl;
+ MOZ_ALWAYS_TRUE( gl->MakeCurrent() );
+
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
+ if (webgl->IsWebGL2()) {
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
+ }
+
+ *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dstDUI, xOffset,
+ yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin);
+
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment);
+ if (webgl->IsWebGL2()) {
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength);
+ }
+
+ return true;
+}
+
+} // namespace webgl
+} // namespace mozilla