зеркало из https://github.com/mozilla/gecko-dev.git
899 строки
28 KiB
C++
899 строки
28 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/RefPtr.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "WebGLBuffer.h"
|
|
#include "WebGLContext.h"
|
|
#include "WebGLFormats.h"
|
|
#include "WebGLTexelConversions.h"
|
|
#include "WebGLTexture.h"
|
|
|
|
namespace mozilla {
|
|
namespace webgl {
|
|
|
|
static bool IsPIValidForDOM(const webgl::PackingInfo& pi) {
|
|
// https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
|
|
|
|
// Just check for invalid individual formats and types, not combinations.
|
|
switch (pi.format) {
|
|
case LOCAL_GL_RGB:
|
|
case LOCAL_GL_RGBA:
|
|
case LOCAL_GL_LUMINANCE_ALPHA:
|
|
case LOCAL_GL_LUMINANCE:
|
|
case LOCAL_GL_ALPHA:
|
|
case LOCAL_GL_RED:
|
|
case LOCAL_GL_RED_INTEGER:
|
|
case LOCAL_GL_RG:
|
|
case LOCAL_GL_RG_INTEGER:
|
|
case LOCAL_GL_RGB_INTEGER:
|
|
case LOCAL_GL_RGBA_INTEGER:
|
|
break;
|
|
|
|
case LOCAL_GL_SRGB:
|
|
case LOCAL_GL_SRGB_ALPHA:
|
|
// Allowed in WebGL1+EXT_srgb
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
switch (pi.type) {
|
|
case LOCAL_GL_UNSIGNED_BYTE:
|
|
case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
|
|
case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
|
|
case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
|
|
case LOCAL_GL_HALF_FLOAT:
|
|
case LOCAL_GL_HALF_FLOAT_OES:
|
|
case LOCAL_GL_FLOAT:
|
|
case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ValidatePIForDOM(WebGLContext* webgl,
|
|
const 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 bool ValidateUnpackPixels(WebGLContext* webgl, uint32_t fullRows,
|
|
uint32_t tailPixels,
|
|
webgl::TexUnpackBlob* blob) {
|
|
if (!blob->mWidth || !blob->mHeight || !blob->mDepth) return true;
|
|
|
|
const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
|
|
if (!usedPixelsPerRow.isValid() ||
|
|
usedPixelsPerRow.value() > blob->mRowLength) {
|
|
webgl->ErrorInvalidOperation(
|
|
"UNPACK_SKIP_PIXELS + width >"
|
|
" UNPACK_ROW_LENGTH.");
|
|
return false;
|
|
}
|
|
|
|
if (blob->mHeight > blob->mImageHeight) {
|
|
webgl->ErrorInvalidOperation("height > UNPACK_IMAGE_HEIGHT.");
|
|
return false;
|
|
}
|
|
|
|
//////
|
|
|
|
// The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
|
|
auto skipFullRows = CheckedUint32(blob->mSkipImages) * blob->mImageHeight;
|
|
skipFullRows += blob->mSkipRows;
|
|
|
|
MOZ_ASSERT(blob->mDepth >= 1);
|
|
MOZ_ASSERT(blob->mHeight >= 1);
|
|
auto usedFullRows = CheckedUint32(blob->mDepth - 1) * blob->mImageHeight;
|
|
usedFullRows +=
|
|
blob->mHeight - 1; // Full rows in the final image, excluding the tail.
|
|
|
|
const auto fullRowsNeeded = skipFullRows + usedFullRows;
|
|
if (!fullRowsNeeded.isValid()) {
|
|
webgl->ErrorOutOfMemory("Invalid calculation for required row count.");
|
|
return false;
|
|
}
|
|
|
|
if (fullRows > fullRowsNeeded.value()) return true;
|
|
|
|
if (fullRows == fullRowsNeeded.value() &&
|
|
tailPixels >= usedPixelsPerRow.value()) {
|
|
blob->mNeedsExactUpload = true;
|
|
return true;
|
|
}
|
|
|
|
webgl->ErrorInvalidOperation(
|
|
"Desired upload requires more data than is"
|
|
" available: (%u rows plus %u pixels needed, %u rows"
|
|
" plus %u pixels available)",
|
|
fullRowsNeeded.value(), usedPixelsPerRow.value(), fullRows, tailPixels);
|
|
return false;
|
|
}
|
|
|
|
static bool ValidateUnpackBytes(WebGLContext* webgl,
|
|
const webgl::PackingInfo& pi,
|
|
size_t availByteCount,
|
|
webgl::TexUnpackBlob* blob) {
|
|
if (!blob->mWidth || !blob->mHeight || !blob->mDepth) return true;
|
|
|
|
const auto bytesPerPixel = webgl::BytesPerPixel(pi);
|
|
const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
|
|
const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
|
|
|
|
const auto fullRows = availByteCount / rowStride;
|
|
if (!fullRows.isValid()) {
|
|
webgl->ErrorOutOfMemory("Unacceptable upload size calculated.");
|
|
return false;
|
|
}
|
|
|
|
const auto bodyBytes = fullRows.value() * rowStride.value();
|
|
const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel;
|
|
|
|
return ValidateUnpackPixels(webgl, fullRows.value(), tailPixels, blob);
|
|
}
|
|
|
|
////////////////////
|
|
|
|
static uint32_t ZeroOn2D(TexImageTarget target, uint32_t val) {
|
|
return (IsTarget3D(target) ? val : 0);
|
|
}
|
|
|
|
static uint32_t FallbackOnZero(uint32_t val, uint32_t fallback) {
|
|
return (val ? val : fallback);
|
|
}
|
|
|
|
TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
|
|
uint32_t rowLength, uint32_t width,
|
|
uint32_t height, uint32_t depth,
|
|
gfxAlphaType srcAlphaType)
|
|
: mAlignment(webgl->mPixelStore_UnpackAlignment),
|
|
mRowLength(rowLength),
|
|
mImageHeight(FallbackOnZero(
|
|
ZeroOn2D(target, webgl->mPixelStore_UnpackImageHeight), height))
|
|
|
|
,
|
|
mSkipPixels(webgl->mPixelStore_UnpackSkipPixels),
|
|
mSkipRows(webgl->mPixelStore_UnpackSkipRows),
|
|
mSkipImages(ZeroOn2D(target, webgl->mPixelStore_UnpackSkipImages))
|
|
|
|
,
|
|
mWidth(width),
|
|
mHeight(height),
|
|
mDepth(depth)
|
|
|
|
,
|
|
mSrcAlphaType(srcAlphaType)
|
|
|
|
,
|
|
mNeedsExactUpload(false) {
|
|
MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
|
|
}
|
|
|
|
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(
|
|
WebGLContext* 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;
|
|
|
|
if (!rowLength || !rowCount) return true;
|
|
|
|
const auto srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
|
|
const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
|
|
const auto fnHasPremultMismatch = [&]() {
|
|
if (mSrcAlphaType == gfxAlphaType::Opaque) return false;
|
|
|
|
if (!HasColorAndAlpha(srcFormat)) return false;
|
|
|
|
return srcIsPremult != dstIsPremult;
|
|
};
|
|
|
|
const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
|
|
: gl::OriginPos::BottomLeft);
|
|
const auto dstOrigin = gl::OriginPos::BottomLeft;
|
|
|
|
if (srcFormat != dstFormat) {
|
|
webgl->GeneratePerfWarning(
|
|
"Conversion requires pixel reformatting. (%u->%u)", uint32_t(srcFormat),
|
|
uint32_t(dstFormat));
|
|
} else if (fnHasPremultMismatch()) {
|
|
webgl->GeneratePerfWarning(
|
|
"Conversion requires change in"
|
|
" alpha-premultiplication.");
|
|
} else if (srcOrigin != dstOrigin) {
|
|
webgl->GeneratePerfWarning("Conversion requires y-flip.");
|
|
} else if (srcStride != dstStride) {
|
|
webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)",
|
|
uint32_t(srcStride), uint32_t(dstStride));
|
|
} else {
|
|
return true;
|
|
}
|
|
|
|
////
|
|
|
|
const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride;
|
|
if (!dstTotalBytes.isValid()) {
|
|
webgl->ErrorOutOfMemory("Calculation failed.");
|
|
return false;
|
|
}
|
|
|
|
UniqueBuffer dstBuffer = calloc(1, 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
|
|
|
|
TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
|
|
uint32_t width, uint32_t height, uint32_t depth,
|
|
bool isClientData, const uint8_t* ptr,
|
|
size_t availBytes)
|
|
: TexUnpackBlob(webgl, target,
|
|
FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
|
|
width, height, depth, gfxAlphaType::NonPremult),
|
|
mIsClientData(isClientData),
|
|
mPtr(ptr),
|
|
mAvailBytes(availBytes) {}
|
|
|
|
bool TexUnpackBytes::Validate(WebGLContext* webgl,
|
|
const webgl::PackingInfo& pi) {
|
|
if (mIsClientData && !mPtr) return true;
|
|
|
|
return ValidateUnpackBytes(webgl, pi, mAvailBytes, this);
|
|
}
|
|
|
|
bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
|
|
WebGLTexture* tex, TexImageTarget target,
|
|
GLint level,
|
|
const webgl::DriverUnpackInfo* dui,
|
|
GLint xOffset, GLint yOffset, GLint zOffset,
|
|
const webgl::PackingInfo& pi,
|
|
GLenum* const out_error) const {
|
|
WebGLContext* webgl = tex->mContext;
|
|
|
|
const auto format = FormatForPackingInfo(pi);
|
|
const auto bytesPerPixel = webgl::BytesPerPixel(pi);
|
|
|
|
const uint8_t* uploadPtr = mPtr;
|
|
UniqueBuffer tempBuffer;
|
|
|
|
do {
|
|
if (!mIsClientData || !mPtr) break;
|
|
|
|
if (!webgl->mPixelStore_FlipY && !webgl->mPixelStore_PremultiplyAlpha) {
|
|
break;
|
|
}
|
|
|
|
if (webgl->mPixelStore_UnpackImageHeight ||
|
|
webgl->mPixelStore_UnpackSkipImages ||
|
|
webgl->mPixelStore_UnpackRowLength ||
|
|
webgl->mPixelStore_UnpackSkipRows ||
|
|
webgl->mPixelStore_UnpackSkipPixels) {
|
|
webgl->ErrorInvalidOperation(
|
|
"Non-DOM-Element uploads with alpha-premult"
|
|
" or y-flip do not support subrect selection.");
|
|
return false;
|
|
}
|
|
|
|
webgl->GenerateWarning(
|
|
"Alpha-premult and y-flip are deprecated for"
|
|
" non-DOM-Element uploads.");
|
|
|
|
const uint32_t rowLength = mWidth;
|
|
const uint32_t rowCount = mHeight * mDepth;
|
|
const auto stride =
|
|
RoundUpToMultipleOf(rowLength * bytesPerPixel, mAlignment);
|
|
if (!ConvertIfNeeded(webgl, rowLength, rowCount, format, mPtr, stride,
|
|
format, stride, &uploadPtr, &tempBuffer)) {
|
|
return false;
|
|
}
|
|
} while (false);
|
|
|
|
//////
|
|
|
|
const auto& gl = webgl->gl;
|
|
|
|
bool useParanoidHandling = false;
|
|
if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) {
|
|
webgl->GenerateWarning(
|
|
"Uploads from a buffer with a final row with a byte"
|
|
" count smaller than the row stride can incur extra"
|
|
" overhead.");
|
|
|
|
if (gl->WorkAroundDriverBugs()) {
|
|
useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA);
|
|
}
|
|
}
|
|
|
|
if (!useParanoidHandling) {
|
|
if (webgl->mBoundPixelUnpackBuffer) {
|
|
gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER,
|
|
webgl->mBoundPixelUnpackBuffer->mGLName);
|
|
}
|
|
|
|
*out_error =
|
|
DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
|
|
zOffset, mWidth, mHeight, mDepth, uploadPtr);
|
|
|
|
if (webgl->mBoundPixelUnpackBuffer) {
|
|
gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////
|
|
|
|
MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
|
|
|
|
if (!isSubImage) {
|
|
// Alloc first to catch OOMs.
|
|
AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
|
|
*out_error =
|
|
DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
|
|
zOffset, mWidth, mHeight, mDepth, nullptr);
|
|
if (*out_error) return true;
|
|
}
|
|
|
|
const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
|
|
webgl->mBoundPixelUnpackBuffer);
|
|
|
|
//////
|
|
|
|
// Make our sometimes-implicit values explicit. Also this keeps them constant
|
|
// when we ask for height=mHeight-1 and such.
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength);
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mImageHeight);
|
|
|
|
if (mDepth > 1) {
|
|
*out_error =
|
|
DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset,
|
|
mWidth, mHeight, mDepth - 1, uploadPtr);
|
|
}
|
|
|
|
// Skip the images we uploaded.
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mSkipImages + mDepth - 1);
|
|
|
|
if (mHeight > 1) {
|
|
*out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
|
|
zOffset + mDepth - 1, mWidth, mHeight - 1, 1,
|
|
uploadPtr);
|
|
}
|
|
|
|
const auto totalSkipRows =
|
|
CheckedUint32(mSkipImages) * mImageHeight + mSkipRows;
|
|
const auto totalFullRows =
|
|
CheckedUint32(mDepth - 1) * mImageHeight + mHeight - 1;
|
|
const auto tailOffsetRows = totalSkipRows + totalFullRows;
|
|
|
|
const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
|
|
const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
|
|
if (!rowStride.isValid()) {
|
|
MOZ_CRASH("Should be checked earlier.");
|
|
}
|
|
const auto tailOffsetBytes = tailOffsetRows * rowStride;
|
|
|
|
uploadPtr += tailOffsetBytes.value();
|
|
|
|
//////
|
|
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // No stride padding.
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); // No padding in general.
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images,
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS,
|
|
0); // or rows.
|
|
// Keep skipping pixels though!
|
|
|
|
*out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
|
|
yOffset + mHeight - 1, zOffset + mDepth - 1,
|
|
mWidth, 1, 1, uploadPtr);
|
|
|
|
// Reset all our modified state.
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
|
|
webgl->mPixelStore_UnpackAlignment);
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT,
|
|
webgl->mPixelStore_UnpackImageHeight);
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
|
|
webgl->mPixelStore_UnpackRowLength);
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES,
|
|
webgl->mPixelStore_UnpackSkipImages);
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS,
|
|
webgl->mPixelStore_UnpackSkipRows);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// TexUnpackImage
|
|
|
|
TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, TexImageTarget target,
|
|
uint32_t width, uint32_t height, uint32_t depth,
|
|
layers::Image* image, gfxAlphaType srcAlphaType)
|
|
: TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth,
|
|
srcAlphaType),
|
|
mImage(image) {}
|
|
|
|
TexUnpackImage::~TexUnpackImage() {}
|
|
|
|
bool TexUnpackImage::Validate(WebGLContext* webgl,
|
|
const webgl::PackingInfo& pi) {
|
|
if (!ValidatePIForDOM(webgl, pi)) return false;
|
|
|
|
const auto fullRows = mImage->GetSize().height;
|
|
return ValidateUnpackPixels(webgl, fullRows, 0, this);
|
|
}
|
|
|
|
bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
|
|
WebGLTexture* tex, TexImageTarget target,
|
|
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);
|
|
|
|
WebGLContext* webgl = tex->mContext;
|
|
|
|
gl::GLContext* gl = webgl->GL();
|
|
|
|
if (needsRespec) {
|
|
*out_error =
|
|
DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
|
|
yOffset, zOffset, mWidth, mHeight, mDepth, nullptr);
|
|
if (*out_error) return true;
|
|
}
|
|
|
|
const char* fallbackReason;
|
|
do {
|
|
if (mDepth != 1) {
|
|
fallbackReason = "depth is not 1";
|
|
break;
|
|
}
|
|
if (xOffset != 0 || yOffset != 0 || zOffset != 0) {
|
|
fallbackReason = "x/y/zOffset is not 0";
|
|
break;
|
|
}
|
|
|
|
if (webgl->mPixelStore_UnpackSkipPixels ||
|
|
webgl->mPixelStore_UnpackSkipRows ||
|
|
webgl->mPixelStore_UnpackSkipImages) {
|
|
fallbackReason = "non-zero UNPACK_SKIP_* not yet supported";
|
|
break;
|
|
}
|
|
|
|
const auto fnHasPremultMismatch = [&]() {
|
|
if (mSrcAlphaType == gfxAlphaType::Opaque) return false;
|
|
|
|
const bool srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
|
|
const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
|
|
if (srcIsPremult == dstIsPremult) return false;
|
|
|
|
if (dstIsPremult) {
|
|
fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
|
|
} else {
|
|
fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
|
|
}
|
|
return true;
|
|
};
|
|
if (fnHasPremultMismatch()) break;
|
|
|
|
if (dui->unpackFormat != LOCAL_GL_RGB &&
|
|
dui->unpackFormat != LOCAL_GL_RGBA) {
|
|
fallbackReason = "`format` is not RGB or RGBA";
|
|
break;
|
|
}
|
|
|
|
if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE) {
|
|
fallbackReason = "`type` is not UNSIGNED_BYTE";
|
|
break;
|
|
}
|
|
|
|
gl::ScopedFramebuffer scopedFB(gl);
|
|
gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
|
|
|
|
{
|
|
gl::GLContext::LocalErrorScope errorScope(*gl);
|
|
|
|
gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
|
|
LOCAL_GL_COLOR_ATTACHMENT0, target.get(),
|
|
tex->mGLName, level);
|
|
|
|
if (errorScope.GetError()) {
|
|
fallbackReason = "bug: failed to attach to FB for blit";
|
|
break;
|
|
}
|
|
}
|
|
|
|
const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
|
|
if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
|
|
fallbackReason = "bug: failed to confirm FB for blit";
|
|
break;
|
|
}
|
|
|
|
const gfx::IntSize dstSize(mWidth, mHeight);
|
|
const auto dstOrigin =
|
|
(webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
|
|
: gl::OriginPos::BottomLeft);
|
|
if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, dstSize, dstOrigin)) {
|
|
fallbackReason = "likely bug: failed to blit";
|
|
break;
|
|
}
|
|
|
|
// Blitting was successful, so we're done!
|
|
*out_error = 0;
|
|
return true;
|
|
} while (false);
|
|
|
|
const nsPrintfCString perfMsg(
|
|
"Failed to hit GPU-copy fast-path: %s (src type %u)", fallbackReason,
|
|
uint32_t(mImage->GetFormat()));
|
|
|
|
if (webgl->mPixelStore_RequireFastPath) {
|
|
webgl->ErrorInvalidOperation("%s", perfMsg.BeginReading());
|
|
return false;
|
|
}
|
|
|
|
webgl->GeneratePerfWarning("%s Falling back to CPU upload.",
|
|
perfMsg.BeginReading());
|
|
|
|
const RefPtr<gfx::SourceSurface> surf = mImage->GetAsSourceSurface();
|
|
|
|
RefPtr<gfx::DataSourceSurface> dataSurf;
|
|
if (surf) {
|
|
// WARNING: OSX can lose our MakeCurrent here.
|
|
dataSurf = surf->GetDataSurface();
|
|
}
|
|
if (!dataSurf) {
|
|
webgl->ErrorOutOfMemory(
|
|
"GetAsSourceSurface or GetDataSurface failed after"
|
|
" blit failed for TexUnpackImage.");
|
|
return false;
|
|
}
|
|
|
|
const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth,
|
|
dataSurf, mSrcAlphaType);
|
|
|
|
return surfBlob.TexOrSubImage(isSubImage, needsRespec, tex, target, level,
|
|
dui, xOffset, yOffset, zOffset, pi, out_error);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// TexUnpackSurface
|
|
|
|
TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl,
|
|
TexImageTarget target, uint32_t width,
|
|
uint32_t height, uint32_t depth,
|
|
gfx::DataSourceSurface* surf,
|
|
gfxAlphaType srcAlphaType)
|
|
: TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth,
|
|
srcAlphaType),
|
|
mSurf(surf) {}
|
|
|
|
//////////
|
|
|
|
static bool GetFormatForSurf(gfx::SourceSurface* surf,
|
|
WebGLTexelFormat* const out_texelFormat,
|
|
uint8_t* const out_bpp) {
|
|
const auto surfFormat = surf->GetFormat();
|
|
switch (surfFormat) {
|
|
case gfx::SurfaceFormat::B8G8R8A8:
|
|
*out_texelFormat = WebGLTexelFormat::BGRA8;
|
|
*out_bpp = 4;
|
|
return true;
|
|
|
|
case gfx::SurfaceFormat::B8G8R8X8:
|
|
*out_texelFormat = WebGLTexelFormat::BGRX8;
|
|
*out_bpp = 4;
|
|
return true;
|
|
|
|
case gfx::SurfaceFormat::R8G8B8A8:
|
|
*out_texelFormat = WebGLTexelFormat::RGBA8;
|
|
*out_bpp = 4;
|
|
return true;
|
|
|
|
case gfx::SurfaceFormat::R8G8B8X8:
|
|
*out_texelFormat = WebGLTexelFormat::RGBX8;
|
|
*out_bpp = 4;
|
|
return true;
|
|
|
|
case gfx::SurfaceFormat::R5G6B5_UINT16:
|
|
*out_texelFormat = WebGLTexelFormat::RGB565;
|
|
*out_bpp = 2;
|
|
return true;
|
|
|
|
case gfx::SurfaceFormat::A8:
|
|
*out_texelFormat = WebGLTexelFormat::A8;
|
|
*out_bpp = 1;
|
|
return true;
|
|
|
|
case gfx::SurfaceFormat::YUV:
|
|
// Ugh...
|
|
NS_ERROR("We don't handle uploads from YUV sources yet.");
|
|
// When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically
|
|
// GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB)
|
|
return false;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////
|
|
|
|
bool TexUnpackSurface::Validate(WebGLContext* webgl,
|
|
const webgl::PackingInfo& pi) {
|
|
if (!ValidatePIForDOM(webgl, pi)) return false;
|
|
|
|
const auto fullRows = mSurf->GetSize().height;
|
|
return ValidateUnpackPixels(webgl, fullRows, 0, this);
|
|
}
|
|
|
|
bool TexUnpackSurface::TexOrSubImage(
|
|
bool isSubImage, bool needsRespec, WebGLTexture* tex, TexImageTarget target,
|
|
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 rowLength = mSurf->GetSize().width;
|
|
const auto rowCount = mSurf->GetSize().height;
|
|
|
|
const auto& dstBPP = webgl::BytesPerPixel(dstPI);
|
|
const auto dstFormat = FormatForPackingInfo(dstPI);
|
|
|
|
////
|
|
|
|
WebGLTexelFormat srcFormat;
|
|
uint8_t srcBPP;
|
|
if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) {
|
|
webgl->ErrorImplementationBug(
|
|
"GetFormatForSurf failed for"
|
|
" WebGLTexelFormat::%u.",
|
|
uint32_t(mSurf->GetFormat()));
|
|
return false;
|
|
}
|
|
|
|
gfx::DataSourceSurface::ScopedMap map(mSurf,
|
|
gfx::DataSourceSurface::MapType::READ);
|
|
if (!map.IsMapped()) {
|
|
webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
|
|
return false;
|
|
}
|
|
|
|
const auto& srcBegin = map.GetData();
|
|
const auto& srcStride = map.GetStride();
|
|
|
|
////
|
|
|
|
const auto srcRowLengthBytes = rowLength * srcBPP;
|
|
|
|
const uint8_t maxGLAlignment = 8;
|
|
uint8_t srcAlignment = 1;
|
|
for (; srcAlignment <= maxGLAlignment; srcAlignment *= 2) {
|
|
const auto strideGuess =
|
|
RoundUpToMultipleOf(srcRowLengthBytes, srcAlignment);
|
|
if (strideGuess == srcStride) break;
|
|
}
|
|
const uint32_t dstAlignment =
|
|
(srcAlignment > maxGLAlignment) ? 1 : srcAlignment;
|
|
|
|
const auto dstRowLengthBytes = rowLength * dstBPP;
|
|
const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment);
|
|
|
|
////
|
|
|
|
const uint8_t* dstBegin = srcBegin;
|
|
UniqueBuffer tempBuffer;
|
|
if (!ConvertIfNeeded(webgl, rowLength, rowCount, srcFormat, srcBegin,
|
|
srcStride, dstFormat, dstStride, &dstBegin,
|
|
&tempBuffer)) {
|
|
return false;
|
|
}
|
|
|
|
////
|
|
|
|
const auto& gl = webgl->gl;
|
|
if (!gl->MakeCurrent()) {
|
|
*out_error = LOCAL_GL_CONTEXT_LOST;
|
|
return true;
|
|
}
|
|
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
|
|
if (webgl->IsWebGL2()) {
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
|
|
}
|
|
|
|
*out_error =
|
|
DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
|
|
yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin);
|
|
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
|
|
webgl->mPixelStore_UnpackAlignment);
|
|
if (webgl->IsWebGL2()) {
|
|
gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
|
|
webgl->mPixelStore_UnpackRowLength);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace webgl
|
|
} // namespace mozilla
|