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.cpp955
1 files changed, 955 insertions, 0 deletions
diff --git a/dom/canvas/TexUnpackBlob.cpp b/dom/canvas/TexUnpackBlob.cpp
new file mode 100644
index 0000000000..a2a60aa61f
--- /dev/null
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -0,0 +1,955 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/gfx/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "nsLayoutUtils.h"
+#include "WebGLBuffer.h"
+#include "WebGLContext.h"
+#include "WebGLFormats.h"
+#include "WebGLTexelConversions.h"
+#include "WebGLTexture.h"
+
+namespace mozilla {
+
+bool WebGLPixelStore::AssertCurrent(gl::GLContext& gl,
+ const bool isWebgl2) const {
+ WebGLPixelStore actual;
+ gl.GetInt(LOCAL_GL_UNPACK_ALIGNMENT, &actual.mUnpackAlignment);
+ if (isWebgl2) {
+ gl.GetInt(LOCAL_GL_UNPACK_ROW_LENGTH, &actual.mUnpackRowLength);
+ gl.GetInt(LOCAL_GL_UNPACK_IMAGE_HEIGHT, &actual.mUnpackImageHeight);
+
+ gl.GetInt(LOCAL_GL_UNPACK_SKIP_PIXELS, &actual.mUnpackSkipPixels);
+ gl.GetInt(LOCAL_GL_UNPACK_SKIP_ROWS, &actual.mUnpackSkipRows);
+ gl.GetInt(LOCAL_GL_UNPACK_SKIP_IMAGES, &actual.mUnpackSkipImages);
+ }
+
+ bool ok = true;
+ ok &= (mUnpackAlignment == actual.mUnpackAlignment);
+ ok &= (mUnpackRowLength == actual.mUnpackRowLength);
+ ok &= (mUnpackImageHeight == actual.mUnpackImageHeight);
+ ok &= (mUnpackSkipPixels == actual.mUnpackSkipPixels);
+ ok &= (mUnpackSkipRows == actual.mUnpackSkipRows);
+ ok &= (mUnpackSkipImages == actual.mUnpackSkipImages);
+ if (ok) return ok;
+
+ const auto fnToStr = [](const WebGLPixelStore& x) {
+ const auto text = nsPrintfCString("%u,%u,%u;%u,%u,%u", x.mUnpackAlignment,
+ x.mUnpackRowLength, x.mUnpackImageHeight,
+ x.mUnpackSkipPixels, x.mUnpackSkipRows,
+ x.mUnpackSkipImages);
+ return ToString(text);
+ };
+
+ const auto was = fnToStr(actual);
+ const auto expected = fnToStr(*this);
+ gfxCriticalError() << "WebGLPixelStore not current. Was " << was
+ << ". Expected << " << expected << ".";
+ return ok;
+}
+
+void WebGLPixelStore::Apply(gl::GLContext& gl, const bool isWebgl2,
+ const uvec3& uploadSize) const {
+ gl.fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, mUnpackAlignment);
+ if (!isWebgl2) return;
+
+ // Re-simplify. (ANGLE seems to have an issue with imageHeight ==
+ // uploadSize.y)
+ auto rowLength = mUnpackRowLength;
+ auto imageHeight = mUnpackImageHeight;
+ if (rowLength == uploadSize.x) {
+ rowLength = 0;
+ }
+ if (imageHeight == uploadSize.y) {
+ imageHeight = 0;
+ }
+
+ gl.fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
+ gl.fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, imageHeight);
+
+ gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, mUnpackSkipPixels);
+ gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, mUnpackSkipRows);
+ gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mUnpackSkipImages);
+}
+
+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(const WebGLContext* const webgl,
+ const webgl::PackingInfo& pi) {
+ if (!IsPIValidForDOM(pi)) {
+ webgl->ErrorInvalidValue("Format or type is invalid for DOM sources.");
+ 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:
+ case LOCAL_GL_SRGB:
+ return WebGLTexelFormat::RGB8;
+
+ case LOCAL_GL_RGBA:
+ case LOCAL_GL_RGBA_INTEGER:
+ case LOCAL_GL_SRGB_ALPHA:
+ 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 uint32_t ZeroOn2D(const GLenum target, const uint32_t val) {
+ const bool is2d = !IsTexTarget3D(target);
+ if (is2d) return 0;
+ return val;
+}
+
+static bool ValidateUnpackPixels(const WebGLContext* webgl, uint32_t fullRows,
+ uint32_t tailPixels,
+ webgl::TexUnpackBlob* const blob) {
+ const auto& size = blob->mDesc.size;
+ if (!size.x || !size.y || !size.z) return true;
+
+ const auto& unpacking = blob->mDesc.unpacking;
+
+ // -
+
+ const auto usedPixelsPerRow =
+ CheckedUint32(unpacking.mUnpackSkipPixels) + size.x;
+ if (!usedPixelsPerRow.isValid() ||
+ usedPixelsPerRow.value() > unpacking.mUnpackRowLength) {
+ webgl->ErrorInvalidOperation(
+ "UNPACK_SKIP_PIXELS + width >"
+ " UNPACK_ROW_LENGTH.");
+ return false;
+ }
+
+ if (size.y > unpacking.mUnpackImageHeight) {
+ webgl->ErrorInvalidOperation("height > UNPACK_IMAGE_HEIGHT.");
+ return false;
+ }
+
+ //////
+
+ // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
+ auto skipFullRows =
+ CheckedUint32(unpacking.mUnpackSkipImages) * unpacking.mUnpackImageHeight;
+ skipFullRows += unpacking.mUnpackSkipRows;
+
+ // Full rows in the final image, excluding the tail.
+ MOZ_ASSERT(size.y >= 1);
+ MOZ_ASSERT(size.z >= 1);
+ auto usedFullRows = CheckedUint32(size.z - 1) * unpacking.mUnpackImageHeight;
+ usedFullRows += size.y - 1;
+
+ const auto fullRowsNeeded = skipFullRows + usedFullRows;
+ if (!fullRowsNeeded.isValid()) {
+ webgl->ErrorOutOfMemory("Invalid calculation for required row count.");
+ return false;
+ }
+
+ if (fullRows > fullRowsNeeded.value()) {
+ blob->mNeedsExactUpload = false;
+ return true;
+ }
+
+ if (fullRows == fullRowsNeeded.value() &&
+ tailPixels >= usedPixelsPerRow.value()) {
+ MOZ_ASSERT(blob->mNeedsExactUpload);
+ return true;
+ }
+
+ webgl->ErrorInvalidOperation(
+ "Desired upload requires more data than is"
+ " available: (%u rows plus %u pixels needed, %u rows"
+ " plus %u pixels available)",
+ fullRowsNeeded.value(), usedPixelsPerRow.value(), fullRows, tailPixels);
+ return false;
+}
+
+static bool ValidateUnpackBytes(const WebGLContext* const webgl,
+ const webgl::PackingInfo& pi,
+ size_t availByteCount,
+ webgl::TexUnpackBlob* const blob) {
+ const auto& size = blob->mDesc.size;
+ if (!size.x || !size.y || !size.z) return true;
+ const auto& unpacking = blob->mDesc.unpacking;
+
+ const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+ const auto bytesPerRow =
+ CheckedUint32(unpacking.mUnpackRowLength) * bytesPerPixel;
+ const auto rowStride =
+ RoundUpToMultipleOf(bytesPerRow, unpacking.mUnpackAlignment);
+
+ const auto fullRows = availByteCount / rowStride;
+ if (!fullRows.isValid()) {
+ webgl->ErrorOutOfMemory("Unacceptable upload size calculated.");
+ return false;
+ }
+
+ const auto bodyBytes = fullRows.value() * rowStride.value();
+ const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel;
+
+ return ValidateUnpackPixels(webgl, fullRows.value(), tailPixels, blob);
+}
+
+////////////////////
+
+// static
+std::unique_ptr<TexUnpackBlob> TexUnpackBlob::Create(
+ const TexUnpackBlobDesc& desc) {
+ return std::unique_ptr<TexUnpackBlob>{[&]() -> TexUnpackBlob* {
+ if (!IsTarget3D(desc.imageTarget) && desc.size.z != 1) {
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+
+ switch (desc.unpacking.mUnpackAlignment) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ break;
+ default:
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+
+ if (desc.sd) {
+ return new TexUnpackImage(desc);
+ }
+ if (desc.dataSurf) {
+ return new TexUnpackSurface(desc);
+ }
+
+ if (desc.srcAlphaType != gfxAlphaType::NonPremult) {
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+ return new TexUnpackBytes(desc);
+ }()};
+}
+
+static bool HasColorAndAlpha(const WebGLTexelFormat format) {
+ switch (format) {
+ case WebGLTexelFormat::RA8:
+ case WebGLTexelFormat::RA16F:
+ case WebGLTexelFormat::RA32F:
+ case WebGLTexelFormat::RGBA8:
+ case WebGLTexelFormat::RGBA5551:
+ case WebGLTexelFormat::RGBA4444:
+ case WebGLTexelFormat::RGBA16F:
+ case WebGLTexelFormat::RGBA32F:
+ case WebGLTexelFormat::BGRA8:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool TexUnpackBlob::ConvertIfNeeded(
+ const WebGLContext* const webgl, 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;
+
+ const auto& unpacking = mDesc.unpacking;
+
+ if (!rowLength || !rowCount) return true;
+
+ const auto srcIsPremult = (mDesc.srcAlphaType == gfxAlphaType::Premult);
+ const auto& dstIsPremult = unpacking.mPremultiplyAlpha;
+ const auto fnHasPremultMismatch = [&]() {
+ if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false;
+
+ if (!HasColorAndAlpha(srcFormat)) return false;
+
+ return srcIsPremult != dstIsPremult;
+ };
+
+ const auto srcOrigin =
+ (unpacking.mFlipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
+ const auto dstOrigin = gl::OriginPos::BottomLeft;
+
+ if (srcFormat != dstFormat) {
+ webgl->GeneratePerfWarning(
+ "Conversion requires pixel reformatting. (%u->%u)", uint32_t(srcFormat),
+ uint32_t(dstFormat));
+ } else if (fnHasPremultMismatch()) {
+ webgl->GeneratePerfWarning(
+ "Conversion requires change in"
+ " alpha-premultiplication.");
+ } else if (srcOrigin != dstOrigin) {
+ webgl->GeneratePerfWarning("Conversion requires y-flip.");
+ } else if (srcStride != dstStride) {
+ webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)",
+ uint32_t(srcStride), uint32_t(dstStride));
+ } else {
+ return true;
+ }
+
+ ////
+
+ const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
+ if (!dstTotalBytes.isValid()) {
+ webgl->ErrorOutOfMemory("Calculation failed.");
+ return false;
+ }
+
+ UniqueBuffer dstBuffer = calloc(1u, (size_t)dstTotalBytes.value());
+ if (!dstBuffer.get()) {
+ webgl->ErrorOutOfMemory("Failed to allocate dest buffer.");
+ return false;
+ }
+ const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
+
+ ////
+
+ // And go!:
+ bool wasTrivial;
+ if (!ConvertImage(rowLength, rowCount, srcBegin, srcStride, srcOrigin,
+ srcFormat, srcIsPremult, dstBegin, dstStride, dstOrigin,
+ dstFormat, dstIsPremult, &wasTrivial)) {
+ webgl->ErrorImplementationBug("ConvertImage failed.");
+ return false;
+ }
+
+ *out_begin = dstBegin;
+ *out_anchoredBuffer = std::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
+
+bool TexUnpackBytes::Validate(const WebGLContext* const webgl,
+ const webgl::PackingInfo& pi) {
+ if (!HasData()) return true;
+
+ CheckedInt<size_t> availBytes = 0;
+ if (mDesc.cpuData) {
+ const auto& range = mDesc.cpuData->Data();
+ availBytes = range.length();
+ } else if (mDesc.pboOffset) {
+ const auto& pboOffset = *mDesc.pboOffset;
+
+ const auto& pbo =
+ webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
+ if (!pbo) return false; // Might be invalid e.g. due to in-use by TF.
+ availBytes = pbo->ByteLength();
+ availBytes -= pboOffset;
+ } else {
+ MOZ_ASSERT(false, "Must be one of the above");
+ }
+ if (!availBytes.isValid()) {
+ webgl->ErrorInvalidOperation("Offset is passed end of buffer.");
+ return false;
+ }
+
+ return ValidateUnpackBytes(webgl, pi, availBytes.value(), this);
+}
+
+bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
+ WebGLTexture* tex, GLint level,
+ const webgl::DriverUnpackInfo* dui,
+ GLint xOffset, GLint yOffset, GLint zOffset,
+ const webgl::PackingInfo& pi,
+ GLenum* const out_error) const {
+ const auto& webgl = tex->mContext;
+ const auto& target = mDesc.imageTarget;
+ const auto& size = mDesc.size;
+ const auto& unpacking = mDesc.unpacking;
+
+ const auto format = FormatForPackingInfo(pi);
+ const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+
+ const uint8_t* uploadPtr = nullptr;
+ if (mDesc.cpuData) {
+ const auto range = mDesc.cpuData->Data();
+ uploadPtr = range.begin().get();
+ if (!uploadPtr) {
+ MOZ_ASSERT(!range.length());
+ }
+ }
+
+ UniqueBuffer tempBuffer;
+
+ do {
+ if (mDesc.pboOffset || !uploadPtr) break;
+
+ if (!unpacking.mFlipY && !unpacking.mPremultiplyAlpha) {
+ break;
+ }
+
+ webgl->GenerateWarning(
+ "Alpha-premult and y-flip are deprecated for"
+ " non-DOM-Element uploads.");
+
+ const uint32_t rowLength = size.x;
+ const uint32_t rowCount = size.y * size.z;
+ const auto stride = RoundUpToMultipleOf(rowLength * bytesPerPixel,
+ unpacking.mUnpackAlignment);
+ const auto srcPtr = uploadPtr;
+ if (!ConvertIfNeeded(webgl, rowLength, rowCount, format, srcPtr, stride,
+ format, stride, &uploadPtr, &tempBuffer)) {
+ return false;
+ }
+ } while (false);
+
+ //////
+
+ const auto& gl = webgl->gl;
+
+ bool useParanoidHandling = false;
+ if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
+ webgl->GenerateWarning(
+ "Uploads from a buffer with a final row with a byte"
+ " count smaller than the row stride can incur extra"
+ " overhead.");
+
+ 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, size.x, size.y, size.z, 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, size.x, size.y, size.z, 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, unpacking.mUnpackRowLength);
+ gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, unpacking.mUnpackImageHeight);
+
+ if (size.z > 1) {
+ *out_error =
+ DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset,
+ size.x, size.y, size.z - 1, uploadPtr);
+ }
+
+ // Skip the images we uploaded.
+ const auto skipImages = ZeroOn2D(target, unpacking.mUnpackSkipImages);
+ gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, skipImages + size.z - 1);
+
+ if (size.y > 1) {
+ *out_error =
+ DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
+ zOffset + size.z - 1, size.x, size.y - 1, 1, uploadPtr);
+ }
+
+ const auto totalSkipRows =
+ CheckedUint32(skipImages) * unpacking.mUnpackImageHeight +
+ unpacking.mUnpackSkipRows;
+ const auto totalFullRows =
+ CheckedUint32(size.z - 1) * unpacking.mUnpackImageHeight + size.y - 1;
+ const auto tailOffsetRows = totalSkipRows + totalFullRows;
+
+ const auto bytesPerRow =
+ CheckedUint32(unpacking.mUnpackRowLength) * bytesPerPixel;
+ const auto rowStride =
+ RoundUpToMultipleOf(bytesPerRow, unpacking.mUnpackAlignment);
+ 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 + size.y - 1, zOffset + size.z - 1,
+ size.x, 1, 1, uploadPtr);
+
+ // Caller will reset all our modified PixelStorei state.
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+// TexUnpackImage
+
+TexUnpackImage::~TexUnpackImage() = default;
+
+bool TexUnpackImage::Validate(const WebGLContext* const webgl,
+ const webgl::PackingInfo& pi) {
+ if (!ValidatePIForDOM(webgl, pi)) return false;
+
+ const auto fullRows = mDesc.imageSize.y;
+ return ValidateUnpackPixels(webgl, fullRows, 0, this);
+}
+
+Maybe<std::string> BlitPreventReason(const int32_t level, const ivec3& offset,
+ const webgl::PackingInfo& pi,
+ const TexUnpackBlobDesc& desc) {
+ const auto& size = desc.size;
+ const auto& unpacking = desc.unpacking;
+
+ const auto ret = [&]() -> const char* {
+ if (size.z != 1) {
+ return "depth is not 1";
+ }
+ if (offset.x != 0 || offset.y != 0 || offset.z != 0) {
+ return "x/y/zOffset is not 0";
+ }
+
+ if (unpacking.mUnpackSkipPixels || unpacking.mUnpackSkipRows ||
+ unpacking.mUnpackSkipImages) {
+ return "non-zero UNPACK_SKIP_* not yet supported";
+ }
+
+ const auto premultReason = [&]() -> const char* {
+ if (desc.srcAlphaType == gfxAlphaType::Opaque) return nullptr;
+
+ const bool srcIsPremult = (desc.srcAlphaType == gfxAlphaType::Premult);
+ const auto& dstIsPremult = unpacking.mPremultiplyAlpha;
+ if (srcIsPremult == dstIsPremult) return nullptr;
+
+ if (dstIsPremult) {
+ return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
+ } else {
+ return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
+ }
+ }();
+ if (premultReason) return premultReason;
+
+ if (pi.format != LOCAL_GL_RGBA) {
+ return "`format` is not RGBA";
+ }
+
+ if (pi.type != LOCAL_GL_UNSIGNED_BYTE) {
+ return "`type` is not UNSIGNED_BYTE";
+ }
+ return nullptr;
+ }();
+ if (ret) {
+ return Some(std::string(ret));
+ }
+ return {};
+}
+
+bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
+ WebGLTexture* tex, GLint level,
+ const webgl::DriverUnpackInfo* dui,
+ GLint xOffset, GLint yOffset, GLint zOffset,
+ const webgl::PackingInfo& pi,
+ GLenum* const out_error) const {
+ MOZ_ASSERT_IF(needsRespec, !isSubImage);
+
+ const auto& webgl = tex->mContext;
+ const auto& target = mDesc.imageTarget;
+ const auto& size = mDesc.size;
+ const auto& sd = *(mDesc.sd);
+ const auto& unpacking = mDesc.unpacking;
+
+ const auto& gl = webgl->GL();
+
+ // -
+
+ const auto reason =
+ BlitPreventReason(level, {xOffset, yOffset, zOffset}, pi, mDesc);
+ if (reason) {
+ webgl->GeneratePerfWarning(
+ "Failed to hit GPU-copy fast-path."
+ " (%s) Falling back to CPU upload.",
+ reason->c_str());
+ return false;
+ }
+
+ // -
+
+ if (needsRespec) {
+ *out_error =
+ DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
+ zOffset, size.x, size.y, size.z, nullptr);
+ if (*out_error) return true;
+ }
+
+ {
+ 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,
+ tex->mGLName, level);
+
+ const auto err = errorScope.GetError();
+ MOZ_ALWAYS_TRUE(!err);
+ }
+
+ const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+ MOZ_ALWAYS_TRUE(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
+
+ const auto dstOrigin =
+ (unpacking.mFlipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
+ if (!gl->BlitHelper()->BlitSdToFramebuffer(sd, {size.x, size.y},
+ dstOrigin)) {
+ webgl->ErrorImplementationBug("BlitSdToFramebuffer failed for type %i.",
+ int(sd.type()));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+// TexUnpackSurface
+
+TexUnpackSurface::~TexUnpackSurface() = default;
+
+//////////
+
+static bool GetFormatForSurf(const 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(const WebGLContext* const webgl,
+ const webgl::PackingInfo& pi) {
+ if (!ValidatePIForDOM(webgl, pi)) return false;
+
+ const auto fullRows = mDesc.dataSurf->GetSize().height;
+ return ValidateUnpackPixels(webgl, fullRows, 0, this);
+}
+
+bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
+ WebGLTexture* tex, GLint level,
+ const webgl::DriverUnpackInfo* dui,
+ GLint xOffset, GLint yOffset,
+ GLint zOffset,
+ const webgl::PackingInfo& dstPI,
+ GLenum* const out_error) const {
+ const auto& webgl = tex->mContext;
+ const auto& size = mDesc.size;
+ auto& surf = *(mDesc.dataSurf);
+
+ ////
+
+ const auto rowLength = surf.GetSize().width;
+ const auto rowCount = surf.GetSize().height;
+
+ const auto& dstBPP = webgl::BytesPerPixel(dstPI);
+ const auto dstFormat = FormatForPackingInfo(dstPI);
+
+ ////
+
+ WebGLTexelFormat srcFormat;
+ uint8_t srcBPP;
+ if (!GetFormatForSurf(&surf, &srcFormat, &srcBPP)) {
+ webgl->ErrorImplementationBug(
+ "GetFormatForSurf failed for"
+ " WebGLTexelFormat::%u.",
+ uint32_t(surf.GetFormat()));
+ return false;
+ }
+
+ gfx::DataSourceSurface::ScopedMap map(&surf,
+ gfx::DataSourceSurface::MapType::READ);
+ if (!map.IsMapped()) {
+ webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
+ 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, rowLength, rowCount, srcFormat, srcBegin,
+ srcStride, dstFormat, dstStride, &dstBegin,
+ &tempBuffer)) {
+ return false;
+ }
+
+ ////
+
+ const auto& gl = webgl->gl;
+ if (!gl->MakeCurrent()) {
+ *out_error = LOCAL_GL_CONTEXT_LOST;
+ return true;
+ }
+
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
+ if (webgl->IsWebGL2()) {
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
+ }
+
+ *out_error =
+ DoTexOrSubImage(isSubImage, gl, mDesc.imageTarget, level, dui, xOffset,
+ yOffset, zOffset, size.x, size.y, size.z, dstBegin);
+
+ // Caller will reset all our modified PixelStorei state.
+
+ return true;
+}
+
+} // namespace webgl
+} // namespace mozilla