/* 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 "WebGLBuffer.h"
#include "WebGLContext.h"
#include "WebGLFormats.h"
#include "WebGLTexelConversions.h"
#include "WebGLTexture.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/gfx/CanvasManagerParent.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/SharedSurfacesParent.h"
#include "mozilla/layers/TextureHost.h"
#include "mozilla/layers/VideoBridgeParent.h"
#include "nsLayoutUtils.h"
namespace mozilla {
bool webgl::PixelPackingState::AssertCurrentUnpack(gl::GLContext& gl,
const bool isWebgl2)
const {
if (!kIsDebug)
return true;
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;
}
// Check if the surface descriptor describes a GPUVideo texture for which we
// only have an opaque source/handle from SurfaceDescriptorRemoteDecoder to
// derive the actual texture from.
static bool SDIsNullRemoteDecoder(
const layers::SurfaceDescriptor& sd) {
return sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo &&
sd.get_SurfaceDescriptorGPUVideo()
.get_SurfaceDescriptorRemoteDecoder()
.subdesc()
.type() == layers::RemoteDecoderVideoSubDescriptor::Tnull_t;
}
// Check if the surface descriptor describes an ExternalImage surface for which
// we only have an opaque source/handle to derive the actual surface from.
static bool SDIsExternalImage(
const layers::SurfaceDescriptor& sd) {
return sd.type() ==
layers::SurfaceDescriptor::TSurfaceDescriptorExternalImage &&
sd.get_SurfaceDescriptorExternalImage().source() ==
wr::ExternalImageSource::SharedSurfaces;
}
static bool SDIsCanvasSurface(
const layers::SurfaceDescriptor& sd) {
return sd.type() ==
layers::SurfaceDescriptor::TSurfaceDescriptorCanvasSurface;
}
// 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 || desc.sourceSurf) {
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 size_t rowLength,
const size_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;
auto minSrcStride =
CheckedInt<size_t>(
WebGLTexelConversions::TexelBytesForFormat(srcFormat)) *
rowLength;
auto minDstStride =
CheckedInt<size_t>(
WebGLTexelConversions::TexelBytesForFormat(dstFormat)) *
rowLength;
if (srcStride <=
0 || dstStride <=
0 || !minSrcStride.isValid() ||
!minDstStride.isValid() || size_t(srcStride) < minSrcStride.value() ||
size_t(dstStride) < minDstStride.value()) {
webgl->ErrorInvalidOperation(
"Invalid stride.");
return false;
}
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;
}
// TODO (Bug 754256): Figure out the source colorSpace.
dom::PredefinedColorSpace srcColorSpace = dom::PredefinedColorSpace::Srgb;
dom::PredefinedColorSpace dstColorSpace =
webgl->mUnpackColorSpace ? *webgl->mUnpackColorSpace
: dom::PredefinedColorSpace::Srgb;
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 if (srcColorSpace != dstColorSpace) {
webgl->GeneratePerfWarning(
"Conversion requires colorSpace conversion. (%u->%u)",
uint32_t(srcColorSpace), uint32_t(dstColorSpace));
}
else {
return true;
}
////
const auto dstTotalBytes = CheckedInt<size_t>(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, srcColorSpace, dstColorSpace,
&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) {
availBytes = mDesc.cpuData->size();
}
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) {
uploadPtr = mDesc.cpuData->data();
}
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.metrics.usedPixelsPerRow,
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 =
mDesc.pboOffset
?
reinterpret_cast<
const uint8_t*>(*mDesc.pboOffset + lastRowOffset)
: (uploadPtr ? uploadPtr + lastRowOffset : nullptr);
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;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// TexUnpackSurface
TexUnpackSurface::~TexUnpackSurface() =
default;
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.sourceSurf) {
const auto& surfSize = mDesc.sourceSurf->GetSize();
const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast<uvec2>();
if (uvec2{elemSize.x, elemSize.y} != surfSize2) {
gfxCriticalError()
<<
"TexUnpackSurface mismatched structuredSrcSize for sourceSurf.";
return false;
}
}
const auto fullRows = elemSize.y;
return ValidateUnpackPixels(webgl, pi, fullRows, *
this);
}
const char* BlitPreventReason(
GLenum target, int32_t level,
const ivec3& offset, GLenum internalFormat,
const webgl::PackingInfo& pi,
const TexUnpackBlobDesc& desc,
OptionalRenderableFormatBits optionalRenderableFormatBits,
bool sameColorSpace,
bool allowConversion,
bool allowSRGB,
bool allow3D) {
const auto& size = desc.size;
const auto& unpacking = desc.unpacking;
if (size.z !=
1) {
return "depth is not 1";
}
if (offset.z !=
0) {
return "zOffset is not 0";
}
if (unpacking.skipPixels || unpacking.skipRows || unpacking.skipImages) {
return "non-zero UNPACK_SKIP_* not yet supported";
}
if (desc.srcAlphaType != gfxAlphaType::Opaque) {
const bool srcIsPremult = (desc.srcAlphaType == gfxAlphaType::Premult);
const auto& dstIsPremult = unpacking.premultiplyAlpha;
if (srcIsPremult != dstIsPremult && !allowConversion) {
return dstIsPremult ?
"UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true"
:
"UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
}
}
if (!sameColorSpace) {
return "not same colorSpace";
}
const auto formatReason = [&]() ->
const char* {
if (pi.type != LOCAL_GL_UNSIGNED_BYTE) {
return "`unpackType` must be `UNSIGNED_BYTE`";
}
// GL_FRAMEBUFFER_SRGB is not available in some GLES implementations,
// which is required for blitting to emulate Tex(Sub)Image, which does no
// encoding when uploading to an sRGB texture. In GLES (but not desktop
// GL) blitting to an sRGB framebuffer encodes by default unless
// explicitly disabled. To workaround the availability of this extension,
// just disallow it for now. Note, this is also an optional boolean as
// some internal data transfers intentionally rely on this conversion.
switch (internalFormat) {
case LOCAL_GL_SRGB:
case LOCAL_GL_SRGB8:
case LOCAL_GL_SRGB_ALPHA:
case LOCAL_GL_SRGB8_ALPHA8:
if (!allowSRGB) {
return "sRGB-encoded internal formats are not supported";
}
break;
}
switch (pi.format) {
case LOCAL_GL_RGBA:
return nullptr;
// All internalFormats for unpackFormat=RGBA are
// renderable.
case LOCAL_GL_RGB:
break;
default:
return "`unpackFormat` must be `RGBA` or maybe `RGB`";
}
// -
struct {
OptionalRenderableFormatBits bits;
const char* errorMsg;
} required;
switch (internalFormat) {
case LOCAL_GL_RGB565:
return nullptr;
case LOCAL_GL_RGB:
case LOCAL_GL_RGB8:
required = {
OptionalRenderableFormatBits::RGB8,
"Unavailable, as blitting internalFormats RGB or RGB8 requires "
"that RGB8 must be a renderable format.",
};
break;
case LOCAL_GL_SRGB:
case LOCAL_GL_SRGB8:
required = {
OptionalRenderableFormatBits::SRGB8,
"Unavailable, as blitting internalFormats SRGB or SRGB8 requires "
"that SRGB8 must be a renderable format.",
};
break;
case 0:
// texSubImage, so internalFormat is unknown, and could be anything!
required = {
OptionalRenderableFormatBits::RGB8 |
OptionalRenderableFormatBits::SRGB8,
"Unavailable, as blitting texSubImage with unpackFormat=RGB "
"requires that RGB8 and SRGB8 must be renderable formats.",
};
break;
default:
gfxCriticalError()
<<
"Unexpected internalFormat for unpackFormat=RGB: 0x"
<< gfx::hexa(internalFormat);
return "Unexpected internalFormat for unpackFormat=RGB";
}
const auto availableBits = optionalRenderableFormatBits;
if ((required.bits | availableBits) != availableBits) {
return required.errorMsg;
}
// -
return nullptr;
}();
if (formatReason)
return formatReason;
// If the texture has multiple faces or layers that are not allocated,
// then the framebuffer attachment required to blit the descriptor may
// be determined incomplete. Avoid this for now by restricting to 2D
// targets.
if (!allow3D) {
bool is2D = ImageToTexTarget(target) == target && !IsTexTarget3D(target);
if (!is2D) {
return "texture is not 2D";
}
}
return nullptr;
}
bool TexUnpackSurface::AllowBlitSd(WebGLContext*
const webgl,
const GLenum target,
const int32_t level,
const ivec3& offset,
const GLenum internalFormat,
const webgl::PackingInfo& pi,
bool allowConversion,
bool allowSRGB,
bool allow3D,
bool warn)
const {
dom::PredefinedColorSpace srcColorSpace = dom::PredefinedColorSpace::Srgb;
dom::PredefinedColorSpace dstColorSpace =
webgl->mUnpackColorSpace ? *webgl->mUnpackColorSpace
: dom::PredefinedColorSpace::Srgb;
bool sameColorSpace = srcColorSpace == dstColorSpace;
if (
const char* reason = BlitPreventReason(
target, level, offset, internalFormat, pi, mDesc,
webgl->mOptionalRenderableFormatBits, sameColorSpace, allowConversion,
allowSRGB, allow3D)) {
if (warn) {
webgl->GeneratePerfWarning(
"Failed to hit GPU-copy fast-path."
" (%s) Falling back to CPU upload.",
reason);
}
return false;
}
return true;
}
// The texture may be mipmap incomplete which will prevent the framebuffer
// from being complete while drawing to it. To avoid this scenario, override
// the texture base and max level temporarily to ignore incomplete mipmaps
// while blitting to it. Depending on GL implementation (desktop vs ES), the
// min filter may contribute to mipmap completeness.
struct AutoRestoreMipmapState {
AutoRestoreMipmapState(gl::GLContext* gl, GLenum target, GLint level)
: mGL(gl), mTarget(target), mLevel(level) {
mGL->fGetTexParameteriv(mTarget, LOCAL_GL_TEXTURE_MIN_FILTER, &mMinFilter);
if (IsTexMipmapFilter(mMinFilter)) {
mGL->fTexParameteri(mTarget, LOCAL_GL_TEXTURE_MIN_FILTER,
LOCAL_GL_NEAREST);
}
if (mGL->HasTexParamMipmapLevel()) {
mGL->fGetTexParameteriv(mTarget, LOCAL_GL_TEXTURE_BASE_LEVEL,
&mLevelBase);
mGL->fGetTexParameteriv(mTarget, LOCAL_GL_TEXTURE_MAX_LEVEL, &mLevelMax);
if (mLevelBase != mLevel) {
mGL->fTexParameteri(mTarget, LOCAL_GL_TEXTURE_BASE_LEVEL, mLevel);
}
if (mLevelMax != mLevel) {
mGL->fTexParameteri(mTarget, LOCAL_GL_TEXTURE_MAX_LEVEL, mLevel);
}
}
}
~AutoRestoreMipmapState() {
if (IsTexMipmapFilter(mMinFilter)) {
mGL->fTexParameteri(mTarget, LOCAL_GL_TEXTURE_MIN_FILTER, mMinFilter);
}
if (mGL->HasTexParamMipmapLevel()) {
if (mLevelBase != mLevel) {
mGL->fTexParameteri(mTarget, LOCAL_GL_TEXTURE_BASE_LEVEL, mLevelBase);
}
if (mLevelMax != mLevel) {
mGL->fTexParameteri(mTarget, LOCAL_GL_TEXTURE_MAX_LEVEL, mLevelMax);
}
}
}
gl::GLContext* mGL = nullptr;
GLenum mTarget =
0;
GLint mLevel =
0;
GLint mMinFilter =
0;
GLint mLevelBase =
0;
GLint mLevelMax =
0;
};
bool TexUnpackSurface::BlitSd(
const layers::SurfaceDescriptor& sd,
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,
bool allowFallback)
const {
MOZ_ASSERT_IF(needsRespec, !isSubImage);
const auto& webgl = tex->mContext;
const auto& target = mDesc.imageTarget;
const auto& size = mDesc.size;
const auto& unpacking = mDesc.unpacking;
const auto& gl = webgl->GL();
// -
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::ScopedBindTexture scopedTex(gl, tex->mGLName, target);
AutoRestoreMipmapState restoreMipmapState(gl, target, level);
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();
if (err) {
if (allowFallback) {
return false;
}
MOZ_DIAGNOSTIC_CRASH(
"BlitSd failed attaching texture to framebuffer");
}
}
const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
if (allowFallback) {
return false;
}
MOZ_DIAGNOSTIC_CRASH(
"BlitSd framebuffer is not complete");
}
const auto dstOrigin =
(unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
gfx::IntSize fbSize(size.x, size.y);
if (isSubImage) {
const auto& imageInfo = tex->ImageInfoAt(target, level);
fbSize = gfx::IntSize(imageInfo.mWidth, imageInfo.mHeight);
}
Maybe<gfxAlphaType> convertAlpha;
if (mDesc.srcAlphaType != gfxAlphaType::Opaque) {
const bool srcIsPremult = mDesc.srcAlphaType == gfxAlphaType::Premult;
const bool dstIsPremult = unpacking.premultiplyAlpha;
if (srcIsPremult != dstIsPremult) {
convertAlpha = Some(dstIsPremult ? gfxAlphaType::Premult
: gfxAlphaType::NonPremult);
}
}
if (!gl->BlitHelper()->BlitSdToFramebuffer(
sd, {xOffset, yOffset, size.x, size.y}, dstOrigin, fbSize,
convertAlpha)) {
gfxCriticalNote <<
"BlitSdToFramebuffer failed for type "
<<
int(sd.type());
if (allowFallback) {
return false;
}
// 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;
}
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::YUV420:
// 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::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) {
// First check if the SD describes an RGBA Shmem.
const auto& sd = *(mDesc.sd);
if (SDIsCanvasSurface(sd)) {
// The canvas surface resides on a 2D canvas within the same content
// process as the WebGL canvas. Query it for the surface.
const auto& sdc = sd.get_SurfaceDescriptorCanvasSurface();
uint32_t managerId = sdc.managerId();
mozilla::ipc::ActorId canvasId = sdc.canvasId();
uintptr_t surfaceId = sdc.surfaceId();
Maybe<layers::SurfaceDescriptor> exportSd;
// If the texture has multiple faces or layers that are not allocated,
// then the framebuffer attachment required to blit the descriptor may
// be determined incomplete. Avoid this for now by restricting to 2D
// targets.
bool allowBlit = AllowBlitSd(
webgl, mDesc.imageTarget, level, {xOffset, yOffset, zOffset},
dui->internalFormat, dstPI,
true,
false,
false,
false);
if (RefPtr<gfx::SourceSurface> data =
gfx::CanvasManagerParent::GetCanvasSurface(
webgl->GetContentId(), managerId, canvasId, surfaceId,
allowBlit ? &exportSd : nullptr)) {
if (exportSd && !SDIsRGBBuffer(*exportSd) &&
BlitSd(*exportSd, isSubImage, needsRespec, tex, level, dui, xOffset,
yOffset, zOffset, dstPI, out_error,
true)) {
return true;
}
surf = data->GetDataSurface();
}
if (!surf) {
gfxCriticalError() <<
"TexUnpackSurface failed to get CanvasSurface";
return false;
}
}
else if (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();
size_t shmemSize = shmem.Size<uint8_t>();
auto stride = layers::ImageDataSerializer::GetRGBStride(rgb);
if (stride.isNothing()) {
gfxCriticalError() <<
"TexUnpackSurface failed to get rgb stride";
return false;
}
Maybe<size_t> bufSize = layers::ImageDataSerializer::ComputeRGBBufferSize(
rgb.size(), rgb.format());
if (bufSize.isNothing() || bufSize.value() > shmemSize) {
gfxCriticalError() <<
"TexUnpackSurface failed to get rgb buffer size";
return false;
}
surf = gfx::Factory::CreateWrappingDataSourceSurface(
shmem.get<uint8_t>(), stride.value(), rgb.size(), rgb.format());
}
else if (SDIsNullRemoteDecoder(sd)) {
const auto& sdrd = sd.get_SurfaceDescriptorGPUVideo()
.get_SurfaceDescriptorRemoteDecoder();
RefPtr<layers::VideoBridgeParent> parent =
layers::VideoBridgeParent::GetSingleton(sdrd.source());
if (!parent) {
gfxCriticalNote <<
"TexUnpackSurface failed to get VideoBridgeParent";
return false;
}
RefPtr<layers::TextureHost> texture =
parent->LookupTexture(webgl->GetContentId(), sdrd.handle());
if (!texture) {
gfxCriticalNote <<
"TexUnpackSurface failed to get TextureHost";
return false;
}
surf = texture->GetAsSurface();
}
else if (SDIsExternalImage(sd)) {
const auto& sdei = sd.get_SurfaceDescriptorExternalImage();
if (
auto* sharedSurfacesHolder = webgl->GetSharedSurfacesHolder()) {
surf = sharedSurfacesHolder->Get(sdei.id());
}
if (!surf) {
// Most likely the content process crashed before it was able to finish
// sharing the surface with the compositor process.
gfxCriticalNote <<
"TexUnpackSurface failed to get ExternalImage";
return false;
}
}
else if (webgl->IsUploadableSdType(sd) &&
AllowBlitSd(webgl, mDesc.imageTarget, level,
{xOffset, yOffset, zOffset}, dui->internalFormat,
dstPI,
false,
true,
true,
true) &&
BlitSd(sd, isSubImage, needsRespec, tex, level, dui, xOffset,
yOffset, zOffset, dstPI, out_error)) {
// The SD wasn't an RGBA shmem, but were able to blit the SD directly to
// the texture.
return true;
}
else if (mDesc.sourceSurf) {
// In case neither a wrapper was created nor a blit succeeded, check for a
// backup source surface.
surf = mDesc.sourceSurf->GetDataSurface();
}
if (!surf) {
gfxCriticalNote <<
"TexUnpackSurface failed to create wrapping "
"DataSourceSurface.";
return false;
}
}
else if (mDesc.sourceSurf) {
surf = mDesc.sourceSurf->GetDataSurface();
if (!surf) {
gfxCriticalError() <<
"TexUnpackSurface failed to get data for "
"sourceSurf.";
return false;
}
}
////
const auto surfSize = surf->GetSize();
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 size_t dstBpp = BytesPerPixel(dstPI);
const size_t dstUsedBytesPerRow = dstBpp * surfSize.width;
size_t dstStride = dstFormat == srcFormat ? srcStride
// Try To match
: dstUsedBytesPerRow;
// -
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);
if (uint32_t(surfSize.width) < dstUnpacking.metrics.usedPixelsPerRow ||
uint32_t(surfSize.height) < dstUnpacking.metrics.totalRows) {
gfxCriticalError() <<
"Source surface size too small for upload.";
return false;
}
// -
const uint8_t* dstBegin = srcBegin;
UniqueBuffer tempBuffer;
// clang-format off
if (!ConvertIfNeeded(webgl, surfSize.width, surfSize.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