mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 07:13:20 +00:00
983 lines
31 KiB
C++
983 lines
31 KiB
C++
/* -*- 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
|