diff options
Diffstat (limited to 'dom/canvas/TexUnpackBlob.cpp')
-rw-r--r-- | dom/canvas/TexUnpackBlob.cpp | 982 |
1 files changed, 982 insertions, 0 deletions
diff --git a/dom/canvas/TexUnpackBlob.cpp b/dom/canvas/TexUnpackBlob.cpp new file mode 100644 index 0000000000..73746cca5e --- /dev/null +++ b/dom/canvas/TexUnpackBlob.cpp @@ -0,0 +1,982 @@ +/* -*- 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/layers/ImageDataSerializer.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 webgl::PixelPackingState::AssertCurrentUnpack(gl::GLContext& gl, + const bool isWebgl2) const { + auto actual = PixelPackingState{}; + gl.GetInt(LOCAL_GL_UNPACK_ALIGNMENT, &actual.alignmentInTypeElems); + if (isWebgl2) { + gl.GetInt(LOCAL_GL_UNPACK_ROW_LENGTH, &actual.rowLength); + gl.GetInt(LOCAL_GL_UNPACK_IMAGE_HEIGHT, &actual.imageHeight); + + gl.GetInt(LOCAL_GL_UNPACK_SKIP_PIXELS, &actual.skipPixels); + gl.GetInt(LOCAL_GL_UNPACK_SKIP_ROWS, &actual.skipRows); + gl.GetInt(LOCAL_GL_UNPACK_SKIP_IMAGES, &actual.skipImages); + } + if (*this == actual) return true; + + const auto ToStr = [](const PixelPackingState& x) { + const auto text = nsPrintfCString( + "%u,%u,%u;%u,%u,%u", x.alignmentInTypeElems, x.rowLength, x.imageHeight, + x.skipPixels, x.skipRows, x.skipImages); + return mozilla::ToString(text); + }; + + const auto was = ToStr(actual); + const auto expected = ToStr(*this); + gfxCriticalError() << "PixelUnpackStateGl was not current. Was " << was + << ". Expected << " << expected << "."; + return false; +} + +void webgl::PixelPackingState::ApplyUnpack(gl::GLContext& gl, + const bool isWebgl2, + const uvec3& uploadSize) const { + gl.fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, + AssertedCast<GLsizei>(alignmentInTypeElems)); + if (!isWebgl2) return; + + // Re-simplify. (ANGLE seems to have an issue with imageHeight == + // uploadSize.y) + auto rowLengthOrZero = rowLength; + auto imageHeightOrZero = imageHeight; + if (rowLengthOrZero == uploadSize.x) { + rowLengthOrZero = 0; + } + if (imageHeightOrZero == uploadSize.y) { + imageHeightOrZero = 0; + } + + gl.fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, + AssertedCast<GLsizei>(rowLengthOrZero)); + gl.fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, + AssertedCast<GLsizei>(imageHeightOrZero)); + + gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, + AssertedCast<GLsizei>(skipPixels)); + gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, AssertedCast<GLsizei>(skipRows)); + gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, + AssertedCast<GLsizei>(skipImages)); +} + +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, + const webgl::PackingInfo& pi, + const uint32_t availRows, + const webgl::TexUnpackBlob& blob) { + const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {}); + if (!unpackingRes.isOk()) { + webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str()); + return false; + } + const auto& unpacking = unpackingRes.inspect(); + + if (availRows < unpacking.metrics.totalRows) { + webgl->ErrorInvalidOperation( + "Desired upload requires more rows (%zu) than is" + " available (%zu).", + unpacking.metrics.totalRows, availRows); + return false; + } + + return true; +} + +static bool ValidateUnpackBytes(const WebGLContext* const webgl, + const webgl::PackingInfo& pi, + const size_t availByteCount, + const webgl::TexUnpackBlob& blob) { + const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {}); + if (!unpackingRes.isOk()) { + webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str()); + return false; + } + const auto& unpacking = unpackingRes.inspect(); + + if (availByteCount < unpacking.metrics.totalBytesUsed) { + webgl->ErrorInvalidOperation( + "Desired upload requires more bytes (%zu) than are" + " available (%zu).", + unpacking.metrics.totalBytesUsed, availByteCount); + return false; + } + + return true; +} + +//////////////////// + +// Check if the surface descriptor describes a memory which contains a single +// RGBA data source. +static bool SDIsRGBBuffer(const layers::SurfaceDescriptor& sd) { + return sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer && + sd.get_SurfaceDescriptorBuffer().desc().type() == + layers::BufferDescriptor::TRGBDescriptor; +} + +// 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.alignmentInTypeElems) { + case 1: + case 2: + case 4: + case 8: + break; + default: + MOZ_ASSERT(false); + return nullptr; + } + + if (desc.sd) { + // Shmem buffers need to be treated as if they were a DataSourceSurface. + // Otherwise, TexUnpackImage will try to blit the surface descriptor as + // if it can be mapped as a framebuffer, whereas the Shmem is still CPU + // data. + if (SDIsRGBBuffer(*desc.sd)) return new TexUnpackSurface(desc); + 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); + auto dstIsPremult = unpacking.premultiplyAlpha; + const auto fnHasPremultMismatch = [&]() { + if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false; + + if (!HasColorAndAlpha(srcFormat)) return false; + + return srcIsPremult != dstIsPremult; + }; + + const auto srcOrigin = + (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft); + auto dstOrigin = gl::OriginPos::BottomLeft; + + if (!mDesc.applyUnpackTransforms) { + dstIsPremult = srcIsPremult; + dstOrigin = srcOrigin; + } + + 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; + } + + auto dstBuffer = UniqueBuffer::Take(calloc(1u, 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& webglUnpackState = mDesc.unpacking; + + const auto unpackingRes = mDesc.ExplicitUnpacking(pi, {}); + + const auto format = FormatForPackingInfo(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()); + } + } else if (mDesc.pboOffset) { + uploadPtr = reinterpret_cast<const uint8_t*>(*mDesc.pboOffset); + } + + UniqueBuffer tempBuffer; + + do { + if (mDesc.pboOffset || !uploadPtr) break; + + if (!webglUnpackState.flipY && !webglUnpackState.premultiplyAlpha) { + break; + } + + webgl->GenerateWarning( + "Alpha-premult and y-flip are deprecated for" + " non-DOM-Element uploads."); + + MOZ_RELEASE_ASSERT(unpackingRes.isOk()); + const auto& unpacking = unpackingRes.inspect(); + const auto stride = unpacking.metrics.bytesPerRowStride; + // clang-format off + if (!ConvertIfNeeded(webgl, unpacking.state.rowLength, + unpacking.metrics.totalRows, + format, uploadPtr, AutoAssertCast(stride), + format, AutoAssertCast(stride), &uploadPtr, &tempBuffer)) { + return false; + } + // clang-format on + } 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) { + const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, + webgl->mBoundPixelUnpackBuffer); + + *out_error = + DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset, + zOffset, size.x, size.y, size.z, uploadPtr); + return true; + } + + ////// + + MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer); + + if (!isSubImage) { + // Alloc first to catch OOMs. + AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING, 0); + *out_error = + DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset, + zOffset, size.x, size.y, size.z, nullptr); + if (*out_error) return true; + } + if (!size.x || !size.y || !size.z) { + // Nothing to do. + return true; + } + + MOZ_RELEASE_ASSERT(unpackingRes.isOk()); + const auto& unpacking = unpackingRes.inspect(); + + 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, + AutoAssertCast(unpacking.state.rowLength)); + gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, + AutoAssertCast(unpacking.state.imageHeight)); + + 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.state.skipImages); + 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 lastRowOffset = + unpacking.metrics.totalBytesStrided - unpacking.metrics.bytesPerRowStride; + const auto lastRowPtr = uploadPtr + lastRowOffset; + + 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, + AutoAssertCast(size.x), 1, 1, lastRowPtr); + + // 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; + + if (!mDesc.structuredSrcSize) { + gfxCriticalError() << "TexUnpackImage missing structuredSrcSize."; + return false; + } + const auto& elemSize = *mDesc.structuredSrcSize; + if (mDesc.dataSurf) { + const auto& surfSize = mDesc.dataSurf->GetSize(); + const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>(); + if (uvec2{elemSize.x, elemSize.y} != surfSize2) { + gfxCriticalError() + << "TexUnpackImage mismatched structuredSrcSize for dataSurf."; + return false; + } + } + + const auto fullRows = elemSize.y; + return ValidateUnpackPixels(webgl, pi, fullRows, *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.skipPixels || unpacking.skipRows || unpacking.skipImages) { + 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.premultiplyAlpha; + 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.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft); + if (!gl->BlitHelper()->BlitSdToFramebuffer(sd, {size.x, size.y}, + dstOrigin)) { + gfxCriticalNote << "BlitSdToFramebuffer failed for type " + << int(sd.type()); + // Maybe the resource isn't valid anymore? + gl->fClearColor(0.2, 0.0, 0.2, 1.0); + gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT); + const auto& cur = webgl->mColorClearValue; + gl->fClearColor(cur[0], cur[1], cur[2], cur[3]); + webgl->GenerateWarning( + "Fast Tex(Sub)Image upload failed without recourse, clearing to " + "[0.2, 0.0, 0.2, 1.0]. Please file a bug!"); + } + } + + 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; + + if (!mDesc.structuredSrcSize) { + gfxCriticalError() << "TexUnpackSurface missing structuredSrcSize."; + return false; + } + const auto& elemSize = *mDesc.structuredSrcSize; + if (mDesc.dataSurf) { + const auto& surfSize = mDesc.dataSurf->GetSize(); + const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>(); + if (uvec2{elemSize.x, elemSize.y} != surfSize2) { + gfxCriticalError() + << "TexUnpackSurface mismatched structuredSrcSize for dataSurf."; + return false; + } + } + + const auto fullRows = elemSize.y; + return ValidateUnpackPixels(webgl, pi, fullRows, *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; + RefPtr<gfx::DataSourceSurface> surf; + if (mDesc.sd) { + // If we get here, we assume the SD describes an RGBA Shmem. + const auto& sd = *(mDesc.sd); + MOZ_ASSERT(SDIsRGBBuffer(sd)); + const auto& sdb = sd.get_SurfaceDescriptorBuffer(); + const auto& rgb = sdb.desc().get_RGBDescriptor(); + const auto& data = sdb.data(); + MOZ_ASSERT(data.type() == layers::MemoryOrShmem::TShmem); + const auto& shmem = data.get_Shmem(); + surf = gfx::Factory::CreateWrappingDataSourceSurface( + shmem.get<uint8_t>(), layers::ImageDataSerializer::GetRGBStride(rgb), + rgb.size(), rgb.format()); + if (!surf) { + gfxCriticalError() << "TexUnpackSurface failed to create wrapping " + "DataSourceSurface for Shmem."; + return false; + } + } else { + surf = mDesc.dataSurf; + } + + //// + + 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 = static_cast<size_t>(map.GetStride()); + + // - + + const auto dstFormat = FormatForPackingInfo(dstPI); + const auto dstBpp = BytesPerPixel(dstPI); + const size_t dstUsedBytesPerRow = dstBpp * surf->GetSize().width; + auto dstStride = dstUsedBytesPerRow; + if (dstFormat == srcFormat) { + dstStride = srcStride; // Try to match. + } + + // - + + auto dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride)); + if (dstUnpackingRes.isOk()) { + const auto& dstUnpacking = dstUnpackingRes.inspect(); + if (!webgl->IsWebGL2() && dstUnpacking.state.rowLength != size.x) { + dstUnpackingRes = Err("WebGL1 can't handle rowLength != size.x"); + } + } + if (!dstUnpackingRes.isOk()) { + dstStride = dstUsedBytesPerRow; + dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride)); + } + if (!dstUnpackingRes.isOk()) { + gfxCriticalError() << dstUnpackingRes.inspectErr(); + webgl->ErrorImplementationBug("ExplicitUnpacking failed: %s", + dstUnpackingRes.inspectErr().c_str()); + return false; + } + const auto& dstUnpacking = dstUnpackingRes.inspect(); + MOZ_ASSERT(dstUnpacking.metrics.bytesPerRowStride == dstStride); + + // - + + const uint8_t* dstBegin = srcBegin; + UniqueBuffer tempBuffer; + // clang-format off + if (!ConvertIfNeeded(webgl, surf->GetSize().width, surf->GetSize().height, + srcFormat, srcBegin, AutoAssertCast(srcStride), + dstFormat, AutoAssertCast(dstUnpacking.metrics.bytesPerRowStride), &dstBegin, + &tempBuffer)) { + return false; + } + // clang-format on + + //// + + const auto& gl = webgl->gl; + if (!gl->MakeCurrent()) { + *out_error = LOCAL_GL_CONTEXT_LOST; + return true; + } + + dstUnpacking.state.ApplyUnpack(*gl, webgl->IsWebGL2(), size); + + *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 |