diff options
Diffstat (limited to 'dom/canvas/WebGLTextureUpload.cpp')
-rw-r--r-- | dom/canvas/WebGLTextureUpload.cpp | 2242 |
1 files changed, 2242 insertions, 0 deletions
diff --git a/dom/canvas/WebGLTextureUpload.cpp b/dom/canvas/WebGLTextureUpload.cpp new file mode 100644 index 000000000..612d5889d --- /dev/null +++ b/dom/canvas/WebGLTextureUpload.cpp @@ -0,0 +1,2242 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "WebGLTexture.h" + +#include <algorithm> + +#include "CanvasUtils.h" +#include "gfxPrefs.h" +#include "GLBlitHelper.h" +#include "GLContext.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/dom/HTMLVideoElement.h" +#include "mozilla/dom/ImageBitmap.h" +#include "mozilla/dom/ImageData.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Scoped.h" +#include "mozilla/Unused.h" +#include "ScopedGLHelpers.h" +#include "TexUnpackBlob.h" +#include "WebGLBuffer.h" +#include "WebGLContext.h" +#include "WebGLContextUtils.h" +#include "WebGLFramebuffer.h" +#include "WebGLTexelConversions.h" + +namespace mozilla { + +/* This file handles: + * TexStorage2D(texTarget, levels, internalFormat, width, height) + * TexStorage3D(texTarget, levels, intenralFormat, width, height, depth) + * + * TexImage2D(texImageTarget, level, internalFormat, width, height, border, unpackFormat, + * unpackType, data) + * TexImage3D(texImageTarget, level, internalFormat, width, height, depth, border, + * unpackFormat, unpackType, data) + * TexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height, unpackFormat, + * unpackType, data) + * TexSubImage3D(texImageTarget, level, xOffset, yOffset, zOffset, width, height, depth, + * unpackFormat, unpackType, data) + * + * CompressedTexImage2D(texImageTarget, level, internalFormat, width, height, border, + * imageSize, data) + * CompressedTexImage3D(texImageTarget, level, internalFormat, width, height, depth, + * border, imageSize, data) + * CompressedTexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height, + * sizedUnpackFormat, imageSize, data) + * CompressedTexSubImage3D(texImageTarget, level, xOffset, yOffset, zOffset, width, + * height, depth, sizedUnpackFormat, imageSize, data) + * + * CopyTexImage2D(texImageTarget, level, internalFormat, x, y, width, height, border) + * CopyTexImage3D - "Because the framebuffer is inhererntly two-dimensional, there is no + * CopyTexImage3D command." + * CopyTexSubImage2D(texImageTarget, level, xOffset, yOffset, x, y, width, height) + * CopyTexSubImage3D(texImageTarget, level, xOffset, yOffset, zOffset, x, y, width, + * height) + */ + +static bool +ValidateExtents(WebGLContext* webgl, const char* funcName, GLsizei width, GLsizei height, + GLsizei depth, GLint border, uint32_t* const out_width, + uint32_t* const out_height, uint32_t* const out_depth) +{ + // Check border + if (border != 0) { + webgl->ErrorInvalidValue("%s: `border` must be 0.", funcName); + return false; + } + + if (width < 0 || height < 0 || depth < 0) { + /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification + * "If wt and ht are the specified image width and height, + * and if either wt or ht are less than zero, then the error + * INVALID_VALUE is generated." + */ + webgl->ErrorInvalidValue("%s: `width`/`height`/`depth` must be >= 0.", funcName); + return false; + } + + *out_width = width; + *out_height = height; + *out_depth = depth; + return true; +} + +//////////////////////////////////////// +// ArrayBufferView? + +static inline bool +DoesJSTypeMatchUnpackType(GLenum unpackType, js::Scalar::Type jsType) +{ + switch (unpackType) { + case LOCAL_GL_BYTE: + return jsType == js::Scalar::Type::Int8; + + case LOCAL_GL_UNSIGNED_BYTE: + return jsType == js::Scalar::Type::Uint8 || + jsType == js::Scalar::Type::Uint8Clamped; + + case LOCAL_GL_SHORT: + return jsType == js::Scalar::Type::Int16; + + case LOCAL_GL_UNSIGNED_SHORT: + case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4: + case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1: + case LOCAL_GL_UNSIGNED_SHORT_5_6_5: + case LOCAL_GL_HALF_FLOAT: + case LOCAL_GL_HALF_FLOAT_OES: + return jsType == js::Scalar::Type::Uint16; + + case LOCAL_GL_INT: + return jsType == js::Scalar::Type::Int32; + + case LOCAL_GL_UNSIGNED_INT: + case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV: + case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV: + case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV: + case LOCAL_GL_UNSIGNED_INT_24_8: + return jsType == js::Scalar::Type::Uint32; + + case LOCAL_GL_FLOAT: + return jsType == js::Scalar::Type::Float32; + + default: + return false; + } +} + +static bool +ValidateViewType(WebGLContext* webgl, const char* funcName, GLenum unpackType, + const TexImageSource& src) +{ + if (!src.mView) + return true; + const auto& view = *(src.mView); + + const auto& jsType = view.Type(); + if (!DoesJSTypeMatchUnpackType(unpackType, jsType)) { + webgl->ErrorInvalidOperation("%s: ArrayBufferView type not compatible with" + " `type`.", + funcName); + return false; + } + + return true; +} + +static bool +ValidateUnpackInfo(WebGLContext* webgl, const char* funcName, + const webgl::PackingInfo& pi) +{ + if (!webgl->mFormatUsage->AreUnpackEnumsValid(pi.format, pi.type)) { + webgl->ErrorInvalidEnum("%s: Invalid unpack format/type: 0x%04x/0x%04x", funcName, + pi.format, pi.type); + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +static UniquePtr<webgl::TexUnpackBytes> +FromView(WebGLContext* webgl, const char* funcName, TexImageTarget target, + uint32_t width, uint32_t height, uint32_t depth, + const dom::ArrayBufferView* view, GLuint viewElemOffset, + GLuint viewElemLengthOverride) +{ + const bool isClientData = true; + const uint8_t* bytes = nullptr; + size_t availByteCount = 0; + if (view) { + if (!webgl->ValidateArrayBufferView(funcName, *view, viewElemOffset, + viewElemLengthOverride, + const_cast<uint8_t**>(&bytes), + &availByteCount)) + { + return nullptr; + } + } + return MakeUnique<webgl::TexUnpackBytes>(webgl, target, width, height, depth, + isClientData, bytes, availByteCount); +} + +static UniquePtr<webgl::TexUnpackBytes> +FromPboOffset(WebGLContext* webgl, const char* funcName, TexImageTarget target, + uint32_t width, uint32_t height, uint32_t depth, WebGLsizeiptr pboOffset) +{ + if (pboOffset < 0) { + webgl->ErrorInvalidValue("%s: offset cannot be negative.", funcName); + return nullptr; + } + + const auto& buffer = webgl->ValidateBufferSelection(funcName, + LOCAL_GL_PIXEL_UNPACK_BUFFER); + if (!buffer) + return nullptr; + + size_t availBufferBytes = buffer->ByteLength(); + if (size_t(pboOffset) > availBufferBytes) { + webgl->ErrorInvalidOperation("%s: Offset is passed end of buffer.", funcName); + return nullptr; + } + availBufferBytes -= pboOffset; + + const bool isClientData = false; + const auto ptr = (const uint8_t*)pboOffset; + return MakeUnique<webgl::TexUnpackBytes>(webgl, target, width, height, depth, + isClientData, ptr, availBufferBytes); +} + +static UniquePtr<webgl::TexUnpackBlob> +FromImageBitmap(WebGLContext* webgl, const char* funcName, TexImageTarget target, + uint32_t width, uint32_t height, uint32_t depth, + const dom::ImageBitmap& imageBitmap) +{ + UniquePtr<dom::ImageBitmapCloneData> cloneData = Move(imageBitmap.ToCloneData()); + const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface; + + //// + + if (!width) { + width = surf->GetSize().width; + } + + if (!height) { + height = surf->GetSize().height; + } + + //// + + + // WhatWG "HTML Living Standard" (30 October 2015): + // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as + // non-premultiplied alpha values." + const bool isAlphaPremult = cloneData->mIsPremultipliedAlpha; + return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf, + isAlphaPremult); +} + +static UniquePtr<webgl::TexUnpackBlob> +FromImageData(WebGLContext* webgl, const char* funcName, TexImageTarget target, + uint32_t width, uint32_t height, uint32_t depth, + const dom::ImageData& imageData, dom::Uint8ClampedArray* scopedArr) +{ + DebugOnly<bool> inited = scopedArr->Init(imageData.GetDataObject()); + MOZ_ASSERT(inited); + + scopedArr->ComputeLengthAndData(); + const DebugOnly<size_t> dataSize = scopedArr->Length(); + const void* const data = scopedArr->Data(); + + const gfx::IntSize size(imageData.Width(), imageData.Height()); + const size_t stride = size.width * 4; + const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8; + + MOZ_ASSERT(dataSize == stride * size.height); + + uint8_t* wrappableData = (uint8_t*)data; + + const RefPtr<gfx::DataSourceSurface> surf = + gfx::Factory::CreateWrappingDataSourceSurface(wrappableData, stride, size, + surfFormat); + if (!surf) { + webgl->ErrorOutOfMemory("%s: OOM in FromImageData.", funcName); + return nullptr; + } + + //// + + if (!width) { + width = imageData.Width(); + } + + if (!height) { + height = imageData.Height(); + } + + //// + + // WhatWG "HTML Living Standard" (30 October 2015): + // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as + // non-premultiplied alpha values." + const bool isAlphaPremult = false; + return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf, + isAlphaPremult); +} + +UniquePtr<webgl::TexUnpackBlob> +WebGLContext::FromDomElem(const char* funcName, TexImageTarget target, uint32_t width, + uint32_t height, uint32_t depth, const dom::Element& elem, + ErrorResult* const out_error) +{ + uint32_t flags = nsLayoutUtils::SFE_WANT_IMAGE_SURFACE | + nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR; + + if (mPixelStore_ColorspaceConversion == LOCAL_GL_NONE) + flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION; + + if (!mPixelStore_PremultiplyAlpha) + flags |= nsLayoutUtils::SFE_PREFER_NO_PREMULTIPLY_ALPHA; + + RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now. + auto sfer = nsLayoutUtils::SurfaceFromElement(const_cast<dom::Element*>(&elem), flags, + idealDrawTarget); + + ////// + + uint32_t elemWidth = 0; + uint32_t elemHeight = 0; + layers::Image* layersImage = nullptr; + if (!gfxPrefs::WebGLDisableDOMBlitUploads() && sfer.mLayersImage) { + layersImage = sfer.mLayersImage; + elemWidth = layersImage->GetSize().width; + elemHeight = layersImage->GetSize().height; + } + + RefPtr<gfx::DataSourceSurface> dataSurf; + if (!layersImage && sfer.GetSourceSurface()) { + const auto surf = sfer.GetSourceSurface(); + elemWidth = surf->GetSize().width; + elemHeight = surf->GetSize().height; + + // WARNING: OSX can lose our MakeCurrent here. + dataSurf = surf->GetDataSurface(); + } + + ////// + + if (!width) { + width = elemWidth; + } + + if (!height) { + height = elemHeight; + } + + //// + + if (!layersImage && !dataSurf) { + const bool isClientData = true; + return MakeUnique<webgl::TexUnpackBytes>(this, target, width, height, depth, + isClientData, nullptr, 0); + } + + ////// + + // While it's counter-intuitive, the shape of the SFEResult API means that we should + // try to pull out a surface first, and then, if we do pull out a surface, check + // CORS/write-only/etc.. + + if (!sfer.mCORSUsed) { + auto& srcPrincipal = sfer.mPrincipal; + nsIPrincipal* dstPrincipal = GetCanvas()->NodePrincipal(); + + if (!dstPrincipal->Subsumes(srcPrincipal)) { + GenerateWarning("%s: Cross-origin elements require CORS.", funcName); + out_error->Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + } + + if (sfer.mIsWriteOnly) { + // mIsWriteOnly defaults to true, and so will be true even if SFE merely + // failed. Thus we must test mIsWriteOnly after successfully retrieving an + // Image or SourceSurface. + GenerateWarning("%s: Element is write-only, thus cannot be uploaded.", funcName); + out_error->Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + ////// + // Ok, we're good! + + const bool isAlphaPremult = sfer.mIsPremultiplied; + + if (layersImage) { + return MakeUnique<webgl::TexUnpackImage>(this, target, width, height, depth, + layersImage, isAlphaPremult); + } + + MOZ_ASSERT(dataSurf); + return MakeUnique<webgl::TexUnpackSurface>(this, target, width, height, depth, + dataSurf, isAlphaPremult); +} + +//////////////////////////////////////// + +UniquePtr<webgl::TexUnpackBlob> +WebGLContext::From(const char* funcName, TexImageTarget target, GLsizei rawWidth, + GLsizei rawHeight, GLsizei rawDepth, GLint border, + const TexImageSource& src, dom::Uint8ClampedArray* const scopedArr) +{ + uint32_t width, height, depth; + if (!ValidateExtents(this, funcName, rawWidth, rawHeight, rawDepth, border, &width, + &height, &depth)) + { + return nullptr; + } + + if (src.mPboOffset) { + return FromPboOffset(this, funcName, target, width, height, depth, + *(src.mPboOffset)); + } + + if (mBoundPixelUnpackBuffer) { + ErrorInvalidOperation("%s: PIXEL_UNPACK_BUFFER must be null.", funcName); + return nullptr; + } + + if (src.mImageBitmap) { + return FromImageBitmap(this, funcName, target, width, height, depth, + *(src.mImageBitmap)); + } + + if (src.mImageData) { + return FromImageData(this, funcName, target, width, height, depth, + *(src.mImageData), scopedArr); + } + + if (src.mDomElem) { + return FromDomElem(funcName, target, width, height, depth, *(src.mDomElem), + src.mOut_error); + } + + return FromView(this, funcName, target, width, height, depth, src.mView, + src.mViewElemOffset, src.mViewElemLengthOverride); +} + +//////////////////////////////////////////////////////////////////////////////// + +static UniquePtr<webgl::TexUnpackBlob> +ValidateTexOrSubImage(WebGLContext* webgl, const char* funcName, TexImageTarget target, + GLsizei rawWidth, GLsizei rawHeight, GLsizei rawDepth, + GLint border, const webgl::PackingInfo& pi, + const TexImageSource& src, dom::Uint8ClampedArray* const scopedArr) +{ + if (!ValidateUnpackInfo(webgl, funcName, pi)) + return nullptr; + + if (!ValidateViewType(webgl, funcName, pi.type, src)) + return nullptr; + + auto blob = webgl->From(funcName, target, rawWidth, rawHeight, rawDepth, border, src, + scopedArr); + if (!blob || !blob->Validate(webgl, funcName, pi)) + return nullptr; + + return Move(blob); +} + +void +WebGLTexture::TexImage(const char* funcName, TexImageTarget target, GLint level, + GLenum internalFormat, GLsizei width, GLsizei height, + GLsizei depth, GLint border, const webgl::PackingInfo& pi, + const TexImageSource& src) +{ + dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx()); + const auto blob = ValidateTexOrSubImage(mContext, funcName, target, width, height, + depth, border, pi, src, &scopedArr); + if (!blob) + return; + + TexImage(funcName, target, level, internalFormat, pi, blob.get()); +} + +void +WebGLTexture::TexSubImage(const char* funcName, TexImageTarget target, GLint level, + GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, + GLsizei height, GLsizei depth, + const webgl::PackingInfo& pi, const TexImageSource& src) +{ + const GLint border = 0; + dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx()); + const auto blob = ValidateTexOrSubImage(mContext, funcName, target, width, height, + depth, border, pi, src, &scopedArr); + if (!blob) + return; + + if (!blob->HasData()) { + mContext->ErrorInvalidValue("%s: Source must not be null.", funcName); + return; + } + + TexSubImage(funcName, target, level, xOffset, yOffset, zOffset, pi, blob.get()); +} + +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// + +static bool +ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture, const char* funcName, + TexImageTarget target, GLint level, + WebGLTexture::ImageInfo** const out_imageInfo) +{ + // Check level + if (level < 0) { + webgl->ErrorInvalidValue("%s: `level` must be >= 0.", funcName); + return false; + } + + if (level >= WebGLTexture::kMaxLevelCount) { + webgl->ErrorInvalidValue("%s: `level` is too large.", funcName); + return false; + } + + WebGLTexture::ImageInfo& imageInfo = texture->ImageInfoAt(target, level); + + *out_imageInfo = &imageInfo; + return true; +} + +// For *TexImage* +bool +WebGLTexture::ValidateTexImageSpecification(const char* funcName, TexImageTarget target, + GLint rawLevel, uint32_t width, + uint32_t height, uint32_t depth, + WebGLTexture::ImageInfo** const out_imageInfo) +{ + if (mImmutable) { + mContext->ErrorInvalidOperation("%s: Specified texture is immutable.", funcName); + return false; + } + + // Do this early to validate `level`. + WebGLTexture::ImageInfo* imageInfo; + if (!ValidateTexImage(mContext, this, funcName, target, rawLevel, &imageInfo)) + return false; + const uint32_t level(rawLevel); + + if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && + width != height) + { + mContext->ErrorInvalidValue("%s: Cube map images must be square.", funcName); + return false; + } + + /* GLES 3.0.4, p133-134: + * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is the + * max (width/height) size guaranteed not to generate an INVALID_VALUE for too-large + * dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may not* result in an + * INVALID_VALUE, or possibly GL_OOM. + * + * However, we have needed to set our maximums lower in the past to prevent resource + * corruption. Therefore we have mImplMaxTextureSize, which is neither necessarily + * lower nor higher than MAX_TEXTURE_SIZE. + * + * Note that mImplMaxTextureSize must be >= than the advertized MAX_TEXTURE_SIZE. + * For simplicity, we advertize MAX_TEXTURE_SIZE as mImplMaxTextureSize. + */ + + uint32_t maxWidthHeight = 0; + uint32_t maxDepth = 0; + uint32_t maxLevel = 0; + + MOZ_ASSERT(level <= 31); + switch (target.get()) { + case LOCAL_GL_TEXTURE_2D: + maxWidthHeight = mContext->mImplMaxTextureSize >> level; + maxDepth = 1; + maxLevel = CeilingLog2(mContext->mImplMaxTextureSize); + break; + + case LOCAL_GL_TEXTURE_3D: + maxWidthHeight = mContext->mImplMax3DTextureSize >> level; + maxDepth = maxWidthHeight; + maxLevel = CeilingLog2(mContext->mImplMax3DTextureSize); + break; + + case LOCAL_GL_TEXTURE_2D_ARRAY: + maxWidthHeight = mContext->mImplMaxTextureSize >> level; + // "The maximum number of layers for two-dimensional array textures (depth) + // must be at least MAX_ARRAY_TEXTURE_LAYERS for all levels." + maxDepth = mContext->mImplMaxArrayTextureLayers; + maxLevel = CeilingLog2(mContext->mImplMaxTextureSize); + break; + + default: // cube maps + MOZ_ASSERT(IsCubeMap()); + maxWidthHeight = mContext->mImplMaxCubeMapTextureSize >> level; + maxDepth = 1; + maxLevel = CeilingLog2(mContext->mImplMaxCubeMapTextureSize); + break; + } + + if (level > maxLevel) { + mContext->ErrorInvalidValue("%s: Requested level is not supported for target.", + funcName); + return false; + } + + if (width > maxWidthHeight || + height > maxWidthHeight || + depth > maxDepth) + { + mContext->ErrorInvalidValue("%s: Requested size at this level is unsupported.", + funcName); + return false; + } + + { + /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification + * "If level is greater than zero, and either width or + * height is not a power-of-two, the error INVALID_VALUE is + * generated." + * + * This restriction does not apply to GL ES Version 3.0+. + */ + bool requirePOT = (!mContext->IsWebGL2() && level != 0); + + if (requirePOT) { + if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height)) { + mContext->ErrorInvalidValue("%s: For level > 0, width and height must be" + " powers of two.", + funcName); + return false; + } + } + } + + *out_imageInfo = imageInfo; + return true; +} + +// For *TexSubImage* +bool +WebGLTexture::ValidateTexImageSelection(const char* funcName, TexImageTarget target, + GLint level, GLint xOffset, GLint yOffset, + GLint zOffset, uint32_t width, uint32_t height, + uint32_t depth, + WebGLTexture::ImageInfo** const out_imageInfo) +{ + // The conformance test wants bad arg checks before imageInfo checks. + if (xOffset < 0 || yOffset < 0 || zOffset < 0) { + mContext->ErrorInvalidValue("%s: Offsets must be >=0.", funcName); + return false; + } + + WebGLTexture::ImageInfo* imageInfo; + if (!ValidateTexImage(mContext, this, funcName, target, level, &imageInfo)) + return false; + + if (!imageInfo->IsDefined()) { + mContext->ErrorInvalidOperation("%s: The specified TexImage has not yet been" + " specified.", + funcName); + return false; + } + + const auto totalX = CheckedUint32(xOffset) + width; + const auto totalY = CheckedUint32(yOffset) + height; + const auto totalZ = CheckedUint32(zOffset) + depth; + + if (!totalX.isValid() || totalX.value() > imageInfo->mWidth || + !totalY.isValid() || totalY.value() > imageInfo->mHeight || + !totalZ.isValid() || totalZ.value() > imageInfo->mDepth) + { + mContext->ErrorInvalidValue("%s: Offset+size must be <= the size of the existing" + " specified image.", + funcName); + return false; + } + + *out_imageInfo = imageInfo; + return true; +} + +static bool +ValidateCompressedTexUnpack(WebGLContext* webgl, const char* funcName, GLsizei width, + GLsizei height, GLsizei depth, + const webgl::FormatInfo* format, size_t dataSize) +{ + auto compression = format->compression; + + auto bytesPerBlock = compression->bytesPerBlock; + auto blockWidth = compression->blockWidth; + auto blockHeight = compression->blockHeight; + + auto widthInBlocks = CheckedUint32(width) / blockWidth; + auto heightInBlocks = CheckedUint32(height) / blockHeight; + if (width % blockWidth) widthInBlocks += 1; + if (height % blockHeight) heightInBlocks += 1; + + const CheckedUint32 blocksPerImage = widthInBlocks * heightInBlocks; + const CheckedUint32 bytesPerImage = bytesPerBlock * blocksPerImage; + const CheckedUint32 bytesNeeded = bytesPerImage * depth; + + if (!bytesNeeded.isValid()) { + webgl->ErrorOutOfMemory("%s: Overflow while computing the needed buffer size.", + funcName); + return false; + } + + if (dataSize != bytesNeeded.value()) { + webgl->ErrorInvalidValue("%s: Provided buffer's size must match expected size." + " (needs %u, has %u)", + funcName, bytesNeeded.value(), dataSize); + return false; + } + + return true; +} + +static bool +DoChannelsMatchForCopyTexImage(const webgl::FormatInfo* srcFormat, + const webgl::FormatInfo* dstFormat) +{ + // GLES 3.0.4 p140 Table 3.16 "Valid CopyTexImage source framebuffer/destination + // texture base internal format combinations." + + switch (srcFormat->unsizedFormat) { + case webgl::UnsizedFormat::RGBA: + switch (dstFormat->unsizedFormat) { + case webgl::UnsizedFormat::A: + case webgl::UnsizedFormat::L: + case webgl::UnsizedFormat::LA: + case webgl::UnsizedFormat::R: + case webgl::UnsizedFormat::RG: + case webgl::UnsizedFormat::RGB: + case webgl::UnsizedFormat::RGBA: + return true; + default: + return false; + } + + case webgl::UnsizedFormat::RGB: + switch (dstFormat->unsizedFormat) { + case webgl::UnsizedFormat::L: + case webgl::UnsizedFormat::R: + case webgl::UnsizedFormat::RG: + case webgl::UnsizedFormat::RGB: + return true; + default: + return false; + } + + case webgl::UnsizedFormat::RG: + switch (dstFormat->unsizedFormat) { + case webgl::UnsizedFormat::L: + case webgl::UnsizedFormat::R: + case webgl::UnsizedFormat::RG: + return true; + default: + return false; + } + + case webgl::UnsizedFormat::R: + switch (dstFormat->unsizedFormat) { + case webgl::UnsizedFormat::L: + case webgl::UnsizedFormat::R: + return true; + default: + return false; + } + + default: + return false; + } +} + +static bool +EnsureImageDataInitializedForUpload(WebGLTexture* tex, const char* funcName, + TexImageTarget target, GLint level, GLint xOffset, + GLint yOffset, GLint zOffset, uint32_t width, + uint32_t height, uint32_t depth, + WebGLTexture::ImageInfo* imageInfo, + bool* const out_uploadWillInitialize) +{ + *out_uploadWillInitialize = false; + + if (!imageInfo->IsDataInitialized()) { + const bool isFullUpload = (!xOffset && !yOffset && !zOffset && + width == imageInfo->mWidth && + height == imageInfo->mHeight && + depth == imageInfo->mDepth); + if (isFullUpload) { + *out_uploadWillInitialize = true; + } else { + WebGLContext* webgl = tex->mContext; + webgl->GenerateWarning("%s: Texture has not been initialized prior to a" + " partial upload, forcing the browser to clear it." + " This may be slow.", + funcName); + if (!tex->InitializeImageData(funcName, target, level)) { + MOZ_ASSERT(false, "Unexpected failure to init image data."); + return false; + } + } + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// +// Actual calls + +static inline GLenum +DoTexStorage(gl::GLContext* gl, TexTarget target, GLsizei levels, GLenum sizedFormat, + GLsizei width, GLsizei height, GLsizei depth) +{ + gl::GLContext::LocalErrorScope errorScope(*gl); + + switch (target.get()) { + case LOCAL_GL_TEXTURE_2D: + case LOCAL_GL_TEXTURE_CUBE_MAP: + MOZ_ASSERT(depth == 1); + gl->fTexStorage2D(target.get(), levels, sizedFormat, width, height); + break; + + case LOCAL_GL_TEXTURE_3D: + case LOCAL_GL_TEXTURE_2D_ARRAY: + gl->fTexStorage3D(target.get(), levels, sizedFormat, width, height, depth); + break; + + default: + MOZ_CRASH("GFX: bad target"); + } + + return errorScope.GetError(); +} + +bool +IsTarget3D(TexImageTarget target) +{ + switch (target.get()) { + case LOCAL_GL_TEXTURE_2D: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + return false; + + case LOCAL_GL_TEXTURE_3D: + case LOCAL_GL_TEXTURE_2D_ARRAY: + return true; + + default: + MOZ_CRASH("GFX: bad target"); + } +} + +GLenum +DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level, + const webgl::DriverUnpackInfo* dui, GLsizei width, GLsizei height, + GLsizei depth, const void* data) +{ + const GLint border = 0; + + gl::GLContext::LocalErrorScope errorScope(*gl); + + if (IsTarget3D(target)) { + gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height, depth, + border, dui->unpackFormat, dui->unpackType, data); + } else { + MOZ_ASSERT(depth == 1); + gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height, border, + dui->unpackFormat, dui->unpackType, data); + } + + return errorScope.GetError(); +} + +GLenum +DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset, + GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, + const webgl::PackingInfo& pi, const void* data) +{ + gl::GLContext::LocalErrorScope errorScope(*gl); + + if (IsTarget3D(target)) { + gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width, height, + depth, pi.format, pi.type, data); + } else { + MOZ_ASSERT(zOffset == 0); + MOZ_ASSERT(depth == 1); + gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height, + pi.format, pi.type, data); + } + + return errorScope.GetError(); +} + +static inline GLenum +DoCompressedTexImage(gl::GLContext* gl, TexImageTarget target, GLint level, + GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, + GLsizei dataSize, const void* data) +{ + const GLint border = 0; + + gl::GLContext::LocalErrorScope errorScope(*gl); + + if (IsTarget3D(target)) { + gl->fCompressedTexImage3D(target.get(), level, internalFormat, width, height, + depth, border, dataSize, data); + } else { + MOZ_ASSERT(depth == 1); + gl->fCompressedTexImage2D(target.get(), level, internalFormat, width, height, + border, dataSize, data); + } + + return errorScope.GetError(); +} + +GLenum +DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, + GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, + GLsizei height, GLsizei depth, GLenum sizedUnpackFormat, + GLsizei dataSize, const void* data) +{ + gl::GLContext::LocalErrorScope errorScope(*gl); + + if (IsTarget3D(target)) { + gl->fCompressedTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, + width, height, depth, sizedUnpackFormat, dataSize, + data); + } else { + MOZ_ASSERT(zOffset == 0); + MOZ_ASSERT(depth == 1); + gl->fCompressedTexSubImage2D(target.get(), level, xOffset, yOffset, width, + height, sizedUnpackFormat, dataSize, data); + } + + return errorScope.GetError(); +} + +static inline GLenum +DoCopyTexImage2D(gl::GLContext* gl, TexImageTarget target, GLint level, + GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height) +{ + const GLint border = 0; + + gl::GLContext::LocalErrorScope errorScope(*gl); + + MOZ_ASSERT(!IsTarget3D(target)); + gl->fCopyTexImage2D(target.get(), level, internalFormat, x, y, width, height, + border); + + return errorScope.GetError(); +} + +static inline GLenum +DoCopyTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset, + GLint yOffset, GLint zOffset, GLint x, GLint y, GLsizei width, + GLsizei height) +{ + gl::GLContext::LocalErrorScope errorScope(*gl); + + if (IsTarget3D(target)) { + gl->fCopyTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, x, y, + width, height); + } else { + MOZ_ASSERT(zOffset == 0); + gl->fCopyTexSubImage2D(target.get(), level, xOffset, yOffset, x, y, width, + height); + } + + return errorScope.GetError(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// +// Actual (mostly generic) function implementations + +static bool +ValidateCompressedTexImageRestrictions(const char* funcName, WebGLContext* webgl, + TexImageTarget target, uint32_t level, + const webgl::FormatInfo* format, uint32_t width, + uint32_t height, uint32_t depth) +{ + const auto fnIsDimValid_S3TC = [level](uint32_t size, uint32_t blockSize) { + if (size % blockSize == 0) + return true; + + if (level == 0) + return false; + + return (size == 0 || size == 1 || size == 2); + }; + + switch (format->compression->family) { + case webgl::CompressionFamily::PVRTC: + if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height)) { + webgl->ErrorInvalidValue("%s: %s requires power-of-two width and height.", + funcName, format->name); + return false; + } + + break; + + case webgl::CompressionFamily::S3TC: + if (!fnIsDimValid_S3TC(width, format->compression->blockWidth) || + !fnIsDimValid_S3TC(height, format->compression->blockHeight)) + { + webgl->ErrorInvalidOperation("%s: %s requires that width and height are" + " block-aligned, or, if level>0, equal to 0, 1," + " or 2.", + funcName, format->name); + return false; + } + + break; + + // Default: There are no restrictions on CompressedTexImage. + default: // ATC, ETC1, ES3 + break; + } + + return true; +} + +static bool +ValidateTargetForFormat(const char* funcName, WebGLContext* webgl, TexImageTarget target, + const webgl::FormatInfo* format) +{ + // GLES 3.0.4 p127: + // "Textures with a base internal format of DEPTH_COMPONENT or DEPTH_STENCIL are + // supported by texture image specification commands only if `target` is TEXTURE_2D, + // TEXTURE_2D_ARRAY, or TEXTURE_CUBE_MAP. Using these formats in conjunction with any + // other `target` will result in an INVALID_OPERATION error." + + switch (format->effectiveFormat) { + // TEXTURE_2D_ARRAY but not TEXTURE_3D: + // D and DS formats + case webgl::EffectiveFormat::DEPTH_COMPONENT16: + case webgl::EffectiveFormat::DEPTH_COMPONENT24: + case webgl::EffectiveFormat::DEPTH_COMPONENT32F: + case webgl::EffectiveFormat::DEPTH24_STENCIL8: + case webgl::EffectiveFormat::DEPTH32F_STENCIL8: + // CompressionFamily::ES3 + case webgl::EffectiveFormat::COMPRESSED_R11_EAC: + case webgl::EffectiveFormat::COMPRESSED_SIGNED_R11_EAC: + case webgl::EffectiveFormat::COMPRESSED_RG11_EAC: + case webgl::EffectiveFormat::COMPRESSED_SIGNED_RG11_EAC: + case webgl::EffectiveFormat::COMPRESSED_RGB8_ETC2: + case webgl::EffectiveFormat::COMPRESSED_SRGB8_ETC2: + case webgl::EffectiveFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case webgl::EffectiveFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case webgl::EffectiveFormat::COMPRESSED_RGBA8_ETC2_EAC: + case webgl::EffectiveFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + // CompressionFamily::S3TC + case webgl::EffectiveFormat::COMPRESSED_RGB_S3TC_DXT1_EXT: + case webgl::EffectiveFormat::COMPRESSED_RGBA_S3TC_DXT1_EXT: + case webgl::EffectiveFormat::COMPRESSED_RGBA_S3TC_DXT3_EXT: + case webgl::EffectiveFormat::COMPRESSED_RGBA_S3TC_DXT5_EXT: + if (target == LOCAL_GL_TEXTURE_3D) { + webgl->ErrorInvalidOperation("%s: Format %s cannot be used with TEXTURE_3D.", + funcName, format->name); + return false; + } + break; + + // No 3D targets: + // CompressionFamily::ATC + case webgl::EffectiveFormat::ATC_RGB_AMD: + case webgl::EffectiveFormat::ATC_RGBA_EXPLICIT_ALPHA_AMD: + case webgl::EffectiveFormat::ATC_RGBA_INTERPOLATED_ALPHA_AMD: + // CompressionFamily::PVRTC + case webgl::EffectiveFormat::COMPRESSED_RGB_PVRTC_4BPPV1: + case webgl::EffectiveFormat::COMPRESSED_RGBA_PVRTC_4BPPV1: + case webgl::EffectiveFormat::COMPRESSED_RGB_PVRTC_2BPPV1: + case webgl::EffectiveFormat::COMPRESSED_RGBA_PVRTC_2BPPV1: + // CompressionFamily::ETC1 + case webgl::EffectiveFormat::ETC1_RGB8_OES: + if (target == LOCAL_GL_TEXTURE_3D || + target == LOCAL_GL_TEXTURE_2D_ARRAY) + { + webgl->ErrorInvalidOperation("%s: Format %s cannot be used with TEXTURE_3D or" + " TEXTURE_2D_ARRAY.", + funcName, format->name); + return false; + } + break; + + default: + break; + } + + return true; +} + +void +WebGLTexture::TexStorage(const char* funcName, TexTarget target, GLsizei levels, + GLenum sizedFormat, GLsizei width, GLsizei height, GLsizei depth) +{ + // Check levels + if (levels < 1) { + mContext->ErrorInvalidValue("%s: `levels` must be >= 1.", funcName); + return; + } + + if (!width || !height || !depth) { + mContext->ErrorInvalidValue("%s: Dimensions must be non-zero.", funcName); + return; + } + + const TexImageTarget testTarget = IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + : target.get(); + const GLint testLevel = 0; + + WebGLTexture::ImageInfo* testImageInfo; + if (!ValidateTexImageSpecification(funcName, testTarget, testLevel, width, height, + depth, &testImageInfo)) + { + return; + } + MOZ_ASSERT(testImageInfo); + mozilla::Unused << testImageInfo; + + auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat); + if (!dstUsage) { + mContext->ErrorInvalidEnum("%s: Invalid internalformat: 0x%04x", funcName, + sizedFormat); + return; + } + auto dstFormat = dstUsage->format; + + if (!ValidateTargetForFormat(funcName, mContext, testTarget, dstFormat)) + return; + + if (dstFormat->compression) { + if (!ValidateCompressedTexImageRestrictions(funcName, mContext, testTarget, + testLevel, dstFormat, width, height, + depth)) + { + return; + } + } + + //////////////////////////////////// + + const auto lastLevel = levels - 1; + MOZ_ASSERT(lastLevel <= 31, "Right-shift is only defined for bits-1."); + + const uint32_t lastLevelWidth = uint32_t(width) >> lastLevel; + const uint32_t lastLevelHeight = uint32_t(height) >> lastLevel; + const uint32_t lastLevelDepth = uint32_t(depth) >> lastLevel; + + // If these are all zero, then some earlier level was the final 1x1x1 level. + if (!lastLevelWidth && !lastLevelHeight && !lastLevelDepth) { + mContext->ErrorInvalidOperation("%s: Too many levels requested for the given" + " dimensions. (levels: %u, width: %u, height: %u," + " depth: %u)", + funcName, levels, width, height, depth); + return; + } + + //////////////////////////////////// + // Do the thing! + + mContext->gl->MakeCurrent(); + + GLenum error = DoTexStorage(mContext->gl, target.get(), levels, sizedFormat, width, + height, depth); + + if (error == LOCAL_GL_OUT_OF_MEMORY) { + mContext->ErrorOutOfMemory("%s: Ran out of memory during texture allocation.", + funcName); + return; + } + if (error) { + MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors."); + mContext->ErrorInvalidOperation("%s: Unexpected error during texture allocation.", + funcName); + return; + } + + //////////////////////////////////// + // Update our specification data. + + const bool isDataInitialized = false; + const WebGLTexture::ImageInfo newInfo(dstUsage, width, height, depth, + isDataInitialized); + SetImageInfosAtLevel(0, newInfo); + + PopulateMipChain(0, levels-1); + + mImmutable = true; + mImmutableLevelCount = levels; +} + +//////////////////////////////////////// +// Tex(Sub)Image + +void +WebGLTexture::TexImage(const char* funcName, TexImageTarget target, GLint level, + GLenum internalFormat, const webgl::PackingInfo& pi, + const webgl::TexUnpackBlob* blob) +{ + //////////////////////////////////// + // Get dest info + + WebGLTexture::ImageInfo* imageInfo; + if (!ValidateTexImageSpecification(funcName, target, level, blob->mWidth, + blob->mHeight, blob->mDepth, &imageInfo)) + { + return; + } + MOZ_ASSERT(imageInfo); + + const auto& fua = mContext->mFormatUsage; + if (!fua->IsInternalFormatEnumValid(internalFormat)) { + mContext->ErrorInvalidValue("%s: Invalid internalformat: 0x%04x", + funcName, internalFormat); + return; + } + + auto dstUsage = fua->GetSizedTexUsage(internalFormat); + if (!dstUsage) { + if (internalFormat != pi.format) { + /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification + * "Specifying a combination of values for format, type, and + * internalformat that is not listed as a valid combination + * in tables 3.2 or 3.3 generates the error INVALID_OPERATION." + */ + mContext->ErrorInvalidOperation("%s: Unsized internalFormat must match" + " unpack format.", + funcName); + return; + } + + dstUsage = fua->GetUnsizedTexUsage(pi); + } + + if (!dstUsage) { + mContext->ErrorInvalidOperation("%s: Invalid internalformat/format/type:" + " 0x%04x/0x%04x/0x%04x", + funcName, internalFormat, pi.format, pi.type); + return; + } + + const webgl::DriverUnpackInfo* driverUnpackInfo; + if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) { + mContext->ErrorInvalidOperation("%s: Mismatched internalFormat and format/type:" + " 0x%04x and 0x%04x/0x%04x", + funcName, internalFormat, pi.format, pi.type); + return; + } + + //////////////////////////////////// + // Check that source and dest info are compatible + auto dstFormat = dstUsage->format; + + if (!ValidateTargetForFormat(funcName, mContext, target, dstFormat)) + return; + + if (!mContext->IsWebGL2() && dstFormat->d) { + if (target != LOCAL_GL_TEXTURE_2D || + blob->HasData() || + level != 0) + { + mContext->ErrorInvalidOperation("%s: With format %s, this function may only" + " be called with target=TEXTURE_2D," + " data=null, and level=0.", + funcName, dstFormat->name); + return; + } + } + + //////////////////////////////////// + // Do the thing! + + MOZ_ALWAYS_TRUE( mContext->gl->MakeCurrent() ); + MOZ_ASSERT(mContext->gl->IsCurrent()); + + // It's tempting to do allocation first, and TexSubImage second, but this is generally + // slower. + + const ImageInfo newImageInfo(dstUsage, blob->mWidth, blob->mHeight, blob->mDepth, + blob->HasData()); + + const bool isSubImage = false; + const bool needsRespec = (imageInfo->mWidth != newImageInfo.mWidth || + imageInfo->mHeight != newImageInfo.mHeight || + imageInfo->mDepth != newImageInfo.mDepth || + imageInfo->mFormat != newImageInfo.mFormat); + const GLint xOffset = 0; + const GLint yOffset = 0; + const GLint zOffset = 0; + + GLenum glError; + if (!blob->TexOrSubImage(isSubImage, needsRespec, funcName, this, target, level, + driverUnpackInfo, xOffset, yOffset, zOffset, &glError)) + { + return; + } + + if (glError == LOCAL_GL_OUT_OF_MEMORY) { + mContext->ErrorOutOfMemory("%s: Driver ran out of memory during upload.", + funcName); + return; + } + + if (glError) { + mContext->ErrorInvalidOperation("%s: Unexpected error during upload: 0x%04x", + funcName, glError); + printf_stderr("%s: dui: %x/%x/%x\n", funcName, driverUnpackInfo->internalFormat, + driverUnpackInfo->unpackFormat, driverUnpackInfo->unpackType); + MOZ_ASSERT(false, "Unexpected GL error."); + return; + } + + //////////////////////////////////// + // Update our specification data. + + SetImageInfo(imageInfo, newImageInfo); +} + +void +WebGLTexture::TexSubImage(const char* funcName, TexImageTarget target, GLint level, + GLint xOffset, GLint yOffset, GLint zOffset, + const webgl::PackingInfo& pi, const webgl::TexUnpackBlob* blob) +{ + //////////////////////////////////// + // Get dest info + + WebGLTexture::ImageInfo* imageInfo; + if (!ValidateTexImageSelection(funcName, target, level, xOffset, yOffset, zOffset, + blob->mWidth, blob->mHeight, blob->mDepth, &imageInfo)) + { + return; + } + MOZ_ASSERT(imageInfo); + + auto dstUsage = imageInfo->mFormat; + auto dstFormat = dstUsage->format; + + if (dstFormat->compression) { + mContext->ErrorInvalidEnum("%s: Specified TexImage must not be compressed.", + funcName); + return; + } + + if (!mContext->IsWebGL2() && dstFormat->d) { + mContext->ErrorInvalidOperation("%s: Function may not be called on a texture of" + " format %s.", + funcName, dstFormat->name); + return; + } + + //////////////////////////////////// + // Get source info + + const webgl::DriverUnpackInfo* driverUnpackInfo; + if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) { + mContext->ErrorInvalidOperation("%s: Mismatched internalFormat and format/type:" + " %s and 0x%04x/0x%04x", + funcName, dstFormat->name, pi.format, pi.type); + return; + } + + //////////////////////////////////// + // Do the thing! + + mContext->gl->MakeCurrent(); + + bool uploadWillInitialize; + if (!EnsureImageDataInitializedForUpload(this, funcName, target, level, xOffset, + yOffset, zOffset, blob->mWidth, + blob->mHeight, blob->mDepth, imageInfo, + &uploadWillInitialize)) + { + return; + } + + const bool isSubImage = true; + const bool needsRespec = false; + + GLenum glError; + if (!blob->TexOrSubImage(isSubImage, needsRespec, funcName, this, target, level, + driverUnpackInfo, xOffset, yOffset, zOffset, &glError)) + { + return; + } + + if (glError == LOCAL_GL_OUT_OF_MEMORY) { + mContext->ErrorOutOfMemory("%s: Driver ran out of memory during upload.", + funcName); + return; + } + + if (glError) { + mContext->ErrorInvalidOperation("%s: Unexpected error during upload: 0x04x", + funcName, glError); + MOZ_ASSERT(false, "Unexpected GL error."); + return; + } + + //////////////////////////////////// + // Update our specification data? + + if (uploadWillInitialize) { + imageInfo->SetIsDataInitialized(true, this); + } +} + +//////////////////////////////////////// +// CompressedTex(Sub)Image + +UniquePtr<webgl::TexUnpackBytes> +WebGLContext::FromCompressed(const char* funcName, TexImageTarget target, + GLsizei rawWidth, GLsizei rawHeight, GLsizei rawDepth, + GLint border, const TexImageSource& src) +{ + uint32_t width, height, depth; + if (!ValidateExtents(this, funcName, rawWidth, rawHeight, rawDepth, border, &width, + &height, &depth)) + { + return nullptr; + } + + if (src.mPboOffset) { + return FromPboOffset(this, funcName, target, width, height, depth, + *(src.mPboOffset)); + } + + if (mBoundPixelUnpackBuffer) { + ErrorInvalidOperation("%s: PIXEL_UNPACK_BUFFER must be null.", funcName); + return nullptr; + } + + return FromView(this, funcName, target, width, height, depth, src.mView, + src.mViewElemOffset, src.mViewElemLengthOverride); +} + +void +WebGLTexture::CompressedTexImage(const char* funcName, TexImageTarget target, GLint level, + GLenum internalFormat, GLsizei rawWidth, + GLsizei rawHeight, GLsizei rawDepth, GLint border, + const TexImageSource& src) +{ + const auto blob = mContext->FromCompressed(funcName, target, rawWidth, rawHeight, + rawDepth, border, src); + if (!blob) + return; + + //////////////////////////////////// + // Get dest info + + WebGLTexture::ImageInfo* imageInfo; + if (!ValidateTexImageSpecification(funcName, target, level, blob->mWidth, + blob->mHeight, blob->mDepth, &imageInfo)) + { + return; + } + MOZ_ASSERT(imageInfo); + + auto usage = mContext->mFormatUsage->GetSizedTexUsage(internalFormat); + if (!usage) { + mContext->ErrorInvalidEnum("%s: Invalid internalFormat: 0x%04x", funcName, + internalFormat); + return; + } + + auto format = usage->format; + if (!format->compression) { + mContext->ErrorInvalidEnum("%s: Specified internalFormat must be compressed.", + funcName); + return; + } + + if (!ValidateTargetForFormat(funcName, mContext, target, format)) + return; + + //////////////////////////////////// + // Get source info + + if (!ValidateCompressedTexUnpack(mContext, funcName, blob->mWidth, blob->mHeight, + blob->mDepth, format, blob->mAvailBytes)) + { + return; + } + + //////////////////////////////////// + // Check that source is compatible with dest + + if (!ValidateCompressedTexImageRestrictions(funcName, mContext, target, level, format, + blob->mWidth, blob->mHeight, + blob->mDepth)) + { + return; + } + + //////////////////////////////////// + // Do the thing! + + mContext->gl->MakeCurrent(); + + // Warning: Possibly shared memory. See bug 1225033. + GLenum error = DoCompressedTexImage(mContext->gl, target, level, internalFormat, + blob->mWidth, blob->mHeight, blob->mDepth, + blob->mAvailBytes, blob->mPtr); + if (error == LOCAL_GL_OUT_OF_MEMORY) { + mContext->ErrorOutOfMemory("%s: Ran out of memory during upload.", funcName); + return; + } + if (error) { + MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors."); + mContext->GenerateWarning("%s: Unexpected error during texture upload. Context" + " lost.", + funcName); + mContext->ForceLoseContext(); + return; + } + + //////////////////////////////////// + // Update our specification data. + + const bool isDataInitialized = true; + const ImageInfo newImageInfo(usage, blob->mWidth, blob->mHeight, blob->mDepth, + isDataInitialized); + SetImageInfo(imageInfo, newImageInfo); +} + +static inline bool +IsSubImageBlockAligned(const webgl::CompressedFormatInfo* compression, + const WebGLTexture::ImageInfo* imageInfo, GLint xOffset, + GLint yOffset, uint32_t width, uint32_t height) +{ + if (xOffset % compression->blockWidth != 0 || + yOffset % compression->blockHeight != 0) + { + return false; + } + + if (width % compression->blockWidth != 0 && xOffset + width != imageInfo->mWidth) + return false; + + if (height % compression->blockHeight != 0 && yOffset + height != imageInfo->mHeight) + return false; + + return true; +} + +void +WebGLTexture::CompressedTexSubImage(const char* funcName, TexImageTarget target, + GLint level, GLint xOffset, GLint yOffset, + GLint zOffset, GLsizei rawWidth, GLsizei rawHeight, + GLsizei rawDepth, GLenum sizedUnpackFormat, + const TexImageSource& src) +{ + const GLint border = 0; + const auto blob = mContext->FromCompressed(funcName, target, rawWidth, rawHeight, + rawDepth, border, src); + if (!blob) + return; + + //////////////////////////////////// + // Get dest info + + WebGLTexture::ImageInfo* imageInfo; + if (!ValidateTexImageSelection(funcName, target, level, xOffset, yOffset, zOffset, + blob->mWidth, blob->mHeight, blob->mDepth, &imageInfo)) + { + return; + } + MOZ_ASSERT(imageInfo); + + auto dstUsage = imageInfo->mFormat; + auto dstFormat = dstUsage->format; + + //////////////////////////////////// + // Get source info + + auto srcUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedUnpackFormat); + if (!srcUsage->format->compression) { + mContext->ErrorInvalidEnum("%s: Specified format must be compressed.", funcName); + return; + } + + if (srcUsage != dstUsage) { + mContext->ErrorInvalidOperation("%s: `format` must match the format of the" + " existing texture image.", + funcName); + return; + } + + auto format = srcUsage->format; + MOZ_ASSERT(format == dstFormat); + if (!ValidateCompressedTexUnpack(mContext, funcName, blob->mWidth, blob->mHeight, + blob->mDepth, format, blob->mAvailBytes)) + { + return; + } + + //////////////////////////////////// + // Check that source is compatible with dest + + switch (format->compression->family) { + // Forbidden: + case webgl::CompressionFamily::ETC1: + case webgl::CompressionFamily::ATC: + mContext->ErrorInvalidOperation("%s: Format does not allow sub-image" + " updates.", funcName); + return; + + // Block-aligned: + case webgl::CompressionFamily::ES3: // Yes, the ES3 formats don't match the ES3 + case webgl::CompressionFamily::S3TC: // default behavior. + if (!IsSubImageBlockAligned(dstFormat->compression, imageInfo, xOffset, yOffset, + blob->mWidth, blob->mHeight)) + { + mContext->ErrorInvalidOperation("%s: Format requires block-aligned sub-image" + " updates.", + funcName); + return; + } + break; + + // Full-only: (The ES3 default) + default: // PVRTC + if (xOffset || yOffset || + blob->mWidth != imageInfo->mWidth || + blob->mHeight != imageInfo->mHeight) + { + mContext->ErrorInvalidOperation("%s: Format does not allow partial sub-image" + " updates.", + funcName); + return; + } + break; + } + + //////////////////////////////////// + // Do the thing! + + mContext->gl->MakeCurrent(); + + bool uploadWillInitialize; + if (!EnsureImageDataInitializedForUpload(this, funcName, target, level, xOffset, + yOffset, zOffset, blob->mWidth, + blob->mHeight, blob->mDepth, imageInfo, + &uploadWillInitialize)) + { + return; + } + + // Warning: Possibly shared memory. See bug 1225033. + GLenum error = DoCompressedTexSubImage(mContext->gl, target, level, xOffset, yOffset, + zOffset, blob->mWidth, blob->mHeight, + blob->mDepth, sizedUnpackFormat, + blob->mAvailBytes, blob->mPtr); + if (error == LOCAL_GL_OUT_OF_MEMORY) { + mContext->ErrorOutOfMemory("%s: Ran out of memory during upload.", funcName); + return; + } + if (error) { + MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors."); + mContext->GenerateWarning("%s: Unexpected error during texture upload. Context" + " lost.", + funcName); + mContext->ForceLoseContext(); + return; + } + + //////////////////////////////////// + // Update our specification data? + + if (uploadWillInitialize) { + imageInfo->SetIsDataInitialized(true, this); + } +} + +//////////////////////////////////////// +// CopyTex(Sub)Image + +static bool +ValidateCopyTexImageFormats(WebGLContext* webgl, const char* funcName, + const webgl::FormatInfo* srcFormat, + const webgl::FormatInfo* dstFormat) +{ + MOZ_ASSERT(!srcFormat->compression); + if (dstFormat->compression) { + webgl->ErrorInvalidEnum("%s: Specified destination must not have a compressed" + " format.", + funcName); + return false; + } + + if (dstFormat->effectiveFormat == webgl::EffectiveFormat::RGB9_E5) { + webgl->ErrorInvalidOperation("%s: RGB9_E5 is an invalid destination for" + " CopyTex(Sub)Image. (GLES 3.0.4 p145)", + funcName); + return false; + } + + if (!DoChannelsMatchForCopyTexImage(srcFormat, dstFormat)) { + webgl->ErrorInvalidOperation("%s: Destination channels must be compatible with" + " source channels. (GLES 3.0.4 p140 Table 3.16)", + funcName); + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +class ScopedCopyTexImageSource +{ + WebGLContext* const mWebGL; + GLuint mRB; + GLuint mFB; + +public: + ScopedCopyTexImageSource(WebGLContext* webgl, const char* funcName, uint32_t srcWidth, + uint32_t srcHeight, const webgl::FormatInfo* srcFormat, + const webgl::FormatUsageInfo* dstUsage); + ~ScopedCopyTexImageSource(); +}; + +ScopedCopyTexImageSource::ScopedCopyTexImageSource(WebGLContext* webgl, + const char* funcName, + uint32_t srcWidth, uint32_t srcHeight, + const webgl::FormatInfo* srcFormat, + const webgl::FormatUsageInfo* dstUsage) + : mWebGL(webgl) + , mRB(0) + , mFB(0) +{ + switch (dstUsage->format->unsizedFormat) { + case webgl::UnsizedFormat::L: + case webgl::UnsizedFormat::A: + case webgl::UnsizedFormat::LA: + webgl->GenerateWarning("%s: Copying to a LUMINANCE, ALPHA, or LUMINANCE_ALPHA" + " is deprecated, and has severely reduced performance" + " on some platforms.", + funcName); + break; + + default: + MOZ_ASSERT(!dstUsage->textureSwizzleRGBA); + return; + } + + if (!dstUsage->textureSwizzleRGBA) + return; + + gl::GLContext* gl = webgl->gl; + + GLenum sizedFormat; + + switch (srcFormat->componentType) { + case webgl::ComponentType::NormUInt: + sizedFormat = LOCAL_GL_RGBA8; + break; + + case webgl::ComponentType::Float: + if (webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float)) { + sizedFormat = LOCAL_GL_RGBA32F; + break; + } + + if (webgl->IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float)) { + sizedFormat = LOCAL_GL_RGBA16F; + break; + } + MOZ_CRASH("GFX: Should be able to request CopyTexImage from Float."); + + default: + MOZ_CRASH("GFX: Should be able to request CopyTexImage from this type."); + } + + gl::ScopedTexture scopedTex(gl); + gl::ScopedBindTexture scopedBindTex(gl, scopedTex.Texture(), LOCAL_GL_TEXTURE_2D); + + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST); + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST); + + GLint blitSwizzle[4] = {LOCAL_GL_ZERO}; + switch (dstUsage->format->unsizedFormat) { + case webgl::UnsizedFormat::L: + blitSwizzle[0] = LOCAL_GL_RED; + break; + + case webgl::UnsizedFormat::A: + blitSwizzle[0] = LOCAL_GL_ALPHA; + break; + + case webgl::UnsizedFormat::LA: + blitSwizzle[0] = LOCAL_GL_RED; + blitSwizzle[1] = LOCAL_GL_ALPHA; + break; + + default: + MOZ_CRASH("GFX: Unhandled unsizedFormat."); + } + + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R, blitSwizzle[0]); + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G, blitSwizzle[1]); + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B, blitSwizzle[2]); + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A, blitSwizzle[3]); + + gl->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, sizedFormat, 0, 0, srcWidth, + srcHeight, 0); + + // Now create the swizzled FB we'll be exposing. + + GLuint rgbaRB = 0; + gl->fGenRenderbuffers(1, &rgbaRB); + gl::ScopedBindRenderbuffer scopedRB(gl, rgbaRB); + gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, sizedFormat, srcWidth, srcHeight); + + GLuint rgbaFB = 0; + gl->fGenFramebuffers(1, &rgbaFB); + gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, rgbaFB); + gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, + LOCAL_GL_RENDERBUFFER, rgbaRB); + + const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + MOZ_CRASH("GFX: Temp framebuffer is not complete."); + } + + // Restore RB binding. + scopedRB.Unwrap(); // This function should really have a better name. + + // Draw-blit rgbaTex into rgbaFB. + const gfx::IntSize srcSize(srcWidth, srcHeight); + gl->BlitHelper()->DrawBlitTextureToFramebuffer(scopedTex.Texture(), rgbaFB, + srcSize, srcSize); + + // Restore Tex2D binding and destroy the temp tex. + scopedBindTex.Unwrap(); + scopedTex.Unwrap(); + + // Leave RB and FB alive, and FB bound. + mRB = rgbaRB; + mFB = rgbaFB; +} + +template<typename T> +static inline GLenum +ToGLHandle(const T& obj) +{ + return (obj ? obj->mGLName : 0); +} + +ScopedCopyTexImageSource::~ScopedCopyTexImageSource() +{ + if (!mFB) { + MOZ_ASSERT(!mRB); + return; + } + MOZ_ASSERT(mRB); + + gl::GLContext* gl = mWebGL->gl; + + // If we're swizzling, it's because we're on a GL core (3.2+) profile, which has + // split framebuffer support. + gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, + ToGLHandle(mWebGL->mBoundDrawFramebuffer)); + gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, + ToGLHandle(mWebGL->mBoundReadFramebuffer)); + + gl->fDeleteFramebuffers(1, &mFB); + gl->fDeleteRenderbuffers(1, &mRB); +} + +//////////////////////////////////////////////////////////////////////////////// + +static bool +GetUnsizedFormatForCopy(GLenum internalFormat, webgl::UnsizedFormat* const out) +{ + switch (internalFormat) { + case LOCAL_GL_RED: *out = webgl::UnsizedFormat::R; break; + case LOCAL_GL_RG: *out = webgl::UnsizedFormat::RG; break; + case LOCAL_GL_RGB: *out = webgl::UnsizedFormat::RGB; break; + case LOCAL_GL_RGBA: *out = webgl::UnsizedFormat::RGBA; break; + case LOCAL_GL_LUMINANCE: *out = webgl::UnsizedFormat::L; break; + case LOCAL_GL_ALPHA: *out = webgl::UnsizedFormat::A; break; + case LOCAL_GL_LUMINANCE_ALPHA: *out = webgl::UnsizedFormat::LA; break; + + default: + return false; + } + + return true; +} + +static const webgl::FormatUsageInfo* +ValidateCopyDestUsage(const char* funcName, WebGLContext* webgl, + const webgl::FormatInfo* srcFormat, GLenum internalFormat) +{ + const auto& fua = webgl->mFormatUsage; + + auto dstUsage = fua->GetSizedTexUsage(internalFormat); + if (!dstUsage) { + // Ok, maybe it's unsized. + webgl::UnsizedFormat unsizedFormat; + if (!GetUnsizedFormatForCopy(internalFormat, &unsizedFormat)) { + webgl->ErrorInvalidEnum("%s: Unrecongnized internalFormat 0x%04x.", funcName, + internalFormat); + return nullptr; + } + + const auto dstFormat = srcFormat->GetCopyDecayFormat(unsizedFormat); + if (dstFormat) { + dstUsage = fua->GetUsage(dstFormat->effectiveFormat); + } + if (!dstUsage) { + webgl->ErrorInvalidOperation("%s: 0x%04x is not a valid unsized format for" + " source format %s.", + funcName, internalFormat, srcFormat->name); + return nullptr; + } + + return dstUsage; + } + // Alright, it's sized. + + const auto dstFormat = dstUsage->format; + + if (dstFormat->componentType != srcFormat->componentType) { + webgl->ErrorInvalidOperation("%s: For sized internalFormats, source and dest" + " component types must match. (source: %s, dest:" + " %s)", + funcName, srcFormat->name, dstFormat->name); + return nullptr; + } + + bool componentSizesMatch = true; + if (dstFormat->r) { + componentSizesMatch &= (dstFormat->r == srcFormat->r); + } + if (dstFormat->g) { + componentSizesMatch &= (dstFormat->g == srcFormat->g); + } + if (dstFormat->b) { + componentSizesMatch &= (dstFormat->b == srcFormat->b); + } + if (dstFormat->a) { + componentSizesMatch &= (dstFormat->a == srcFormat->a); + } + + if (!componentSizesMatch) { + webgl->ErrorInvalidOperation("%s: For sized internalFormats, source and dest" + " component sizes must match exactly. (source: %s," + " dest: %s)", + funcName, srcFormat->name, dstFormat->name); + return nullptr; + } + + return dstUsage; +} + +bool +WebGLTexture::ValidateCopyTexImageForFeedback(const char* funcName, uint32_t level, GLint layer) const +{ + const auto& fb = mContext->mBoundReadFramebuffer; + if (fb) { + const auto& attach = fb->ColorReadBuffer(); + MOZ_ASSERT(attach); + + if (attach->Texture() == this && + attach->Layer() == layer && + uint32_t(attach->MipLevel()) == level) + { + // Note that the TexImageTargets *don't* have to match for this to be + // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL. + mContext->ErrorInvalidOperation("%s: Feedback loop detected, as this texture" + " is already attached to READ_FRAMEBUFFER's" + " READ_BUFFER-selected COLOR_ATTACHMENT%u.", + funcName, attach->mAttachmentPoint); + return false; + } + } + return true; +} + +static bool +DoCopyTexOrSubImage(WebGLContext* webgl, const char* funcName, bool isSubImage, + const WebGLTexture* tex, TexImageTarget target, GLint level, + GLint xWithinSrc, GLint yWithinSrc, + uint32_t srcTotalWidth, uint32_t srcTotalHeight, + const webgl::FormatUsageInfo* srcUsage, + GLint xOffset, GLint yOffset, GLint zOffset, + uint32_t dstWidth, uint32_t dstHeight, + const webgl::FormatUsageInfo* dstUsage) +{ + const auto& gl = webgl->gl; + + //// + + int32_t readX, readY; + int32_t writeX, writeY; + int32_t rwWidth, rwHeight; + if (!Intersect(srcTotalWidth, xWithinSrc, dstWidth, &readX, &writeX, &rwWidth) || + !Intersect(srcTotalHeight, yWithinSrc, dstHeight, &readY, &writeY, &rwHeight)) + { + webgl->ErrorOutOfMemory("%s: Bad subrect selection.", funcName); + return false; + } + + writeX += xOffset; + writeY += yOffset; + + //// + + GLenum error = 0; + do { + const auto& idealUnpack = dstUsage->idealUnpack; + if (!isSubImage) { + UniqueBuffer buffer; + + if (uint32_t(rwWidth) != dstWidth || uint32_t(rwHeight) != dstHeight) { + const auto& pi = idealUnpack->ToPacking(); + CheckedUint32 byteCount = BytesPerPixel(pi); + byteCount *= dstWidth; + byteCount *= dstHeight; + + if (byteCount.isValid()) { + buffer = calloc(1, byteCount.value()); + } + + if (!buffer.get()) { + webgl->ErrorOutOfMemory("%s: Ran out of memory allocating zeros.", + funcName); + return false; + } + } + + const ScopedUnpackReset unpackReset(webgl); + gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); + error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight, 1, + buffer.get()); + if (error) + break; + } + + if (!rwWidth || !rwHeight) { + // There aren't any pixels to copy, so we're 'done'. + return true; + } + + const auto& srcFormat = srcUsage->format; + ScopedCopyTexImageSource maybeSwizzle(webgl, funcName, srcTotalWidth, + srcTotalHeight, srcFormat, dstUsage); + + error = DoCopyTexSubImage(gl, target, level, writeX, writeY, zOffset, readX, + readY, rwWidth, rwHeight); + if (error) + break; + + return true; + } while (false); + + if (error == LOCAL_GL_OUT_OF_MEMORY) { + webgl->ErrorOutOfMemory("%s: Ran out of memory during texture copy.", funcName); + return false; + } + + if (gl->IsANGLE() && error == LOCAL_GL_INVALID_OPERATION) { + webgl->ErrorImplementationBug("%s: ANGLE is particular about CopyTexSubImage" + " formats matching exactly.", + funcName); + return false; + } + + MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors."); + webgl->GenerateWarning("%s: Unexpected error during texture copy. Context lost.", + funcName); + webgl->ForceLoseContext(); + return false; +} + +// There is no CopyTexImage3D. +void +WebGLTexture::CopyTexImage2D(TexImageTarget target, GLint level, GLenum internalFormat, + GLint x, GLint y, GLsizei rawWidth, GLsizei rawHeight, + GLint border) +{ + const char funcName[] = "copyTexImage2D"; + + //////////////////////////////////// + // Get dest info + + uint32_t width, height, depth; + if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, 1, border, &width, + &height, &depth)) + { + return; + } + + WebGLTexture::ImageInfo* imageInfo; + if (!ValidateTexImageSpecification(funcName, target, level, width, height, depth, + &imageInfo)) + { + return; + } + MOZ_ASSERT(imageInfo); + + //////////////////////////////////// + // Get source info + + const webgl::FormatUsageInfo* srcUsage; + uint32_t srcTotalWidth; + uint32_t srcTotalHeight; + if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcTotalWidth, + &srcTotalHeight)) + { + return; + } + + if (!ValidateCopyTexImageForFeedback(funcName, level)) + return; + + //////////////////////////////////// + // Check that source and dest info are compatible + + const auto& srcFormat = srcUsage->format; + const auto dstUsage = ValidateCopyDestUsage(funcName, mContext, srcFormat, + internalFormat); + if (!dstUsage) + return; + + const auto& dstFormat = dstUsage->format; + if (!ValidateTargetForFormat(funcName, mContext, target, dstFormat)) + return; + + if (!mContext->IsWebGL2() && dstFormat->d) { + mContext->ErrorInvalidOperation("%s: Function may not be called with format %s.", + funcName, dstFormat->name); + return; + } + + if (!ValidateCopyTexImageFormats(mContext, funcName, srcFormat, dstFormat)) + return; + + //////////////////////////////////// + // Do the thing! + + mContext->gl->MakeCurrent(); + mContext->OnBeforeReadCall(); + + const bool isSubImage = false; + if (!DoCopyTexOrSubImage(mContext, funcName, isSubImage, this, target, level, x, y, + srcTotalWidth, srcTotalHeight, srcUsage, 0, 0, 0, width, + height, dstUsage)) + { + return; + } + + //////////////////////////////////// + // Update our specification data. + + const bool isDataInitialized = true; + const ImageInfo newImageInfo(dstUsage, width, height, depth, isDataInitialized); + SetImageInfo(imageInfo, newImageInfo); +} + +void +WebGLTexture::CopyTexSubImage(const char* funcName, TexImageTarget target, GLint level, + GLint xOffset, GLint yOffset, GLint zOffset, GLint x, + GLint y, GLsizei rawWidth, GLsizei rawHeight) +{ + uint32_t width, height, depth; + if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, 1, 0, &width, + &height, &depth)) + { + return; + } + + //////////////////////////////////// + // Get dest info + + WebGLTexture::ImageInfo* imageInfo; + if (!ValidateTexImageSelection(funcName, target, level, xOffset, yOffset, zOffset, + width, height, depth, &imageInfo)) + { + return; + } + MOZ_ASSERT(imageInfo); + + auto dstUsage = imageInfo->mFormat; + MOZ_ASSERT(dstUsage); + + auto dstFormat = dstUsage->format; + if (!mContext->IsWebGL2() && dstFormat->d) { + mContext->ErrorInvalidOperation("%s: Function may not be called on a texture of" + " format %s.", + funcName, dstFormat->name); + return; + } + + //////////////////////////////////// + // Get source info + + const webgl::FormatUsageInfo* srcUsage; + uint32_t srcTotalWidth; + uint32_t srcTotalHeight; + if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcTotalWidth, + &srcTotalHeight)) + { + return; + } + + if (!ValidateCopyTexImageForFeedback(funcName, level, zOffset)) + return; + + //////////////////////////////////// + // Check that source and dest info are compatible + + auto srcFormat = srcUsage->format; + if (!ValidateCopyTexImageFormats(mContext, funcName, srcFormat, dstFormat)) + return; + + //////////////////////////////////// + // Do the thing! + + mContext->gl->MakeCurrent(); + mContext->OnBeforeReadCall(); + + bool uploadWillInitialize; + if (!EnsureImageDataInitializedForUpload(this, funcName, target, level, xOffset, + yOffset, zOffset, width, height, depth, + imageInfo, &uploadWillInitialize)) + { + return; + } + + const bool isSubImage = true; + if (!DoCopyTexOrSubImage(mContext, funcName, isSubImage, this, target, level, x, y, + srcTotalWidth, srcTotalHeight, srcUsage, xOffset, yOffset, + zOffset, width, height, dstUsage)) + { + return; + } + + //////////////////////////////////// + // Update our specification data? + + if (uploadWillInitialize) { + imageInfo->SetIsDataInitialized(true, this); + } +} + +} // namespace mozilla |