diff -r 000000000000 -r 6474c204b198 content/canvas/src/WebGLContextValidate.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/canvas/src/WebGLContextValidate.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1866 @@ +/* -*- 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 "WebGLContext.h" +#include "WebGLBuffer.h" +#include "WebGLVertexAttribData.h" +#include "WebGLShader.h" +#include "WebGLProgram.h" +#include "WebGLUniformLocation.h" +#include "WebGLFramebuffer.h" +#include "WebGLRenderbuffer.h" +#include "WebGLTexture.h" +#include "WebGLVertexArray.h" +#include "GLContext.h" +#include "CanvasUtils.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" + +#include "jsfriendapi.h" + +#include "angle/ShaderLang.h" + +#include + +#include "mozilla/Services.h" +#include "nsIObserverService.h" + +using namespace mozilla; + +/** + * Return the block size for format. + */ +static void +BlockSizeFor(GLenum format, GLint* blockWidth, GLint* blockHeight) +{ + MOZ_ASSERT(blockWidth && blockHeight); + + switch (format) { + case LOCAL_GL_ATC_RGB: + case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA: + case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA: + case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + if (blockWidth) + *blockWidth = 4; + if (blockHeight) + *blockHeight = 4; + break; + + case LOCAL_GL_ETC1_RGB8_OES: + // 4x4 blocks, but no 4-multiple requirement. + default: + break; + } +} + +/** + * Return the displayable name for the texture function that is the + * source for validation. + */ +static const char* +InfoFrom(WebGLTexImageFunc func) +{ + // TODO: Account for dimensions (WebGL 2) + switch (func) { + case WebGLTexImageFunc::TexImage: return "texImage2D"; + case WebGLTexImageFunc::TexSubImage: return "texSubImage2D"; + case WebGLTexImageFunc::CopyTexImage: return "copyTexImage2D"; + case WebGLTexImageFunc::CopyTexSubImage: return "copyTexSubImage2D"; + case WebGLTexImageFunc::CompTexImage: return "compressedTexImage2D"; + case WebGLTexImageFunc::CompTexSubImage: return "compressedTexSubImage2D"; + default: + MOZ_ASSERT(false, "Missing case for WebGLTexImageSource"); + return "(error)"; + } +} + +/** + * Return displayable name for GLenum. + * This version is like gl::GLenumToStr but with out the GL_ prefix to + * keep consistency with how errors are reported from WebGL. + */ +static const char* +NameFrom(GLenum glenum) +{ + switch (glenum) { +#define XX(x) case LOCAL_GL_##x: return #x + XX(ALPHA); + XX(ATC_RGB); + XX(ATC_RGBA_EXPLICIT_ALPHA); + XX(ATC_RGBA_INTERPOLATED_ALPHA); + XX(COMPRESSED_RGBA_PVRTC_2BPPV1); + XX(COMPRESSED_RGBA_PVRTC_4BPPV1); + XX(COMPRESSED_RGBA_S3TC_DXT1_EXT); + XX(COMPRESSED_RGBA_S3TC_DXT3_EXT); + XX(COMPRESSED_RGBA_S3TC_DXT5_EXT); + XX(COMPRESSED_RGB_PVRTC_2BPPV1); + XX(COMPRESSED_RGB_PVRTC_4BPPV1); + XX(COMPRESSED_RGB_S3TC_DXT1_EXT); + XX(DEPTH_COMPONENT); + XX(DEPTH_COMPONENT16); + XX(DEPTH_COMPONENT32); + XX(DEPTH_STENCIL); + XX(DEPTH24_STENCIL8); + XX(ETC1_RGB8_OES); + XX(FLOAT); + XX(HALF_FLOAT); + XX(LUMINANCE); + XX(LUMINANCE_ALPHA); + XX(RGB); + XX(RGB16F); + XX(RGB32F); + XX(RGBA); + XX(RGBA16F); + XX(RGBA32F); + XX(SRGB); + XX(SRGB_ALPHA); + XX(TEXTURE_2D); + XX(TEXTURE_3D); + XX(TEXTURE_CUBE_MAP); + XX(TEXTURE_CUBE_MAP_NEGATIVE_X); + XX(TEXTURE_CUBE_MAP_NEGATIVE_Y); + XX(TEXTURE_CUBE_MAP_NEGATIVE_Z); + XX(TEXTURE_CUBE_MAP_POSITIVE_X); + XX(TEXTURE_CUBE_MAP_POSITIVE_Y); + XX(TEXTURE_CUBE_MAP_POSITIVE_Z); + XX(UNSIGNED_BYTE); + XX(UNSIGNED_INT); + XX(UNSIGNED_INT_24_8); + XX(UNSIGNED_SHORT); + XX(UNSIGNED_SHORT_4_4_4_4); + XX(UNSIGNED_SHORT_5_5_5_1); + XX(UNSIGNED_SHORT_5_6_5); +#undef XX + } + + return nullptr; +} + +/** + * Same as ErrorInvalidEnum but uses NameFrom to print displayable + * name for \a glenum. + */ +static void +ErrorInvalidEnumWithName(WebGLContext* ctx, const char* msg, GLenum glenum, WebGLTexImageFunc func) +{ + const char* name = NameFrom(glenum); + if (name) + ctx->ErrorInvalidEnum("%s: %s %s", InfoFrom(func), msg, name); + else + ctx->ErrorInvalidEnum("%s: %s 0x%04X", InfoFrom(func), msg, glenum); +} + +/** + * Same as ErrorInvalidOperation but uses NameFrom to print displayable + * name for \a glenum. + */ +static void +ErrorInvalidOperationWithName(WebGLContext* ctx, const char* msg, GLenum glenum, + WebGLTexImageFunc func) +{ + const char* name = NameFrom(glenum); + if (name) + ctx->ErrorInvalidOperation("%s: %s %s", InfoFrom(func), msg, name); + else + ctx->ErrorInvalidOperation("%s: %s 0x%04X", InfoFrom(func), msg, glenum); +} + +/** + * Return true if the format is valid for source calls. + */ +static bool +IsAllowedFromSource(GLenum format, WebGLTexImageFunc func) +{ + switch (format) { + case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1: + case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1: + return (func == WebGLTexImageFunc::CompTexImage || + func == WebGLTexImageFunc::CompTexSubImage); + + case LOCAL_GL_ATC_RGB: + case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA: + case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA: + case LOCAL_GL_ETC1_RGB8_OES: + return func == WebGLTexImageFunc::CompTexImage; + } + + return true; +} + +/** + * Returns true if func is a CopyTexImage variant. + */ +static bool +IsCopyFunc(WebGLTexImageFunc func) +{ + return (func == WebGLTexImageFunc::CopyTexImage || + func == WebGLTexImageFunc::CopyTexSubImage); +} + +/** + * Returns true if func is a SubImage variant. + */ +static bool +IsSubFunc(WebGLTexImageFunc func) +{ + return (func == WebGLTexImageFunc::TexSubImage || + func == WebGLTexImageFunc::CopyTexSubImage || + func == WebGLTexImageFunc::CompTexSubImage); +} + +/** + * returns true is target is a texture cube map target. + */ +static bool +IsTexImageCubemapTarget(GLenum target) +{ + return (target >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X && + target <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z); +} + +/* + * Pull data out of the program, post-linking + */ +bool +WebGLProgram::UpdateInfo() +{ + mIdentifierMap = nullptr; + mIdentifierReverseMap = nullptr; + mUniformInfoMap = nullptr; + + mAttribMaxNameLength = 0; + + for (size_t i = 0; i < mAttachedShaders.Length(); i++) + mAttribMaxNameLength = std::max(mAttribMaxNameLength, mAttachedShaders[i]->mAttribMaxNameLength); + + GLint attribCount; + mContext->gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES, &attribCount); + + if (!mAttribsInUse.SetLength(mContext->mGLMaxVertexAttribs)) { + mContext->ErrorOutOfMemory("updateInfo: out of memory to allocate %d attribs", mContext->mGLMaxVertexAttribs); + return false; + } + + for (size_t i = 0; i < mAttribsInUse.Length(); i++) + mAttribsInUse[i] = false; + + nsAutoArrayPtr nameBuf(new char[mAttribMaxNameLength]); + + for (int i = 0; i < attribCount; ++i) { + GLint attrnamelen; + GLint attrsize; + GLenum attrtype; + mContext->gl->fGetActiveAttrib(mGLName, i, mAttribMaxNameLength, &attrnamelen, &attrsize, &attrtype, nameBuf); + if (attrnamelen > 0) { + GLint loc = mContext->gl->fGetAttribLocation(mGLName, nameBuf); + MOZ_ASSERT(loc >= 0, "major oops in managing the attributes of a WebGL program"); + if (loc < mContext->mGLMaxVertexAttribs) { + mAttribsInUse[loc] = true; + } else { + mContext->GenerateWarning("program exceeds MAX_VERTEX_ATTRIBS"); + return false; + } + } + } + + if (!mUniformInfoMap) { + mUniformInfoMap = new CStringToUniformInfoMap; + for (size_t i = 0; i < mAttachedShaders.Length(); i++) { + for (size_t j = 0; j < mAttachedShaders[i]->mUniforms.Length(); j++) { + const WebGLMappedIdentifier& uniform = mAttachedShaders[i]->mUniforms[j]; + const WebGLUniformInfo& info = mAttachedShaders[i]->mUniformInfos[j]; + mUniformInfoMap->Put(uniform.mapped, info); + } + } + } + + mActiveAttribMap.clear(); + + GLint numActiveAttrs = 0; + mContext->gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES, &numActiveAttrs); + + // Spec says the maximum attrib name length is 256 chars, so this is + // sufficient to hold any attrib name. + char attrName[257]; + + GLint dummySize; + GLenum dummyType; + for (GLint i = 0; i < numActiveAttrs; i++) { + mContext->gl->fGetActiveAttrib(mGLName, i, 257, nullptr, &dummySize, + &dummyType, attrName); + GLint attrLoc = mContext->gl->fGetAttribLocation(mGLName, attrName); + MOZ_ASSERT(attrLoc >= 0); + mActiveAttribMap.insert(std::make_pair(attrLoc, nsCString(attrName))); + } + + return true; +} + +/** + * Return the simple base format for a given internal format. + * + * \return the corresponding \u base internal format (GL_ALPHA, GL_LUMINANCE, + * GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA), or GL_NONE if invalid enum. + */ +GLenum +WebGLContext::BaseTexFormat(GLenum internalFormat) const +{ + if (internalFormat == LOCAL_GL_ALPHA || + internalFormat == LOCAL_GL_LUMINANCE || + internalFormat == LOCAL_GL_LUMINANCE_ALPHA || + internalFormat == LOCAL_GL_RGB || + internalFormat == LOCAL_GL_RGBA) + { + return internalFormat; + } + + if (IsExtensionEnabled(WebGLExtensionID::EXT_sRGB)) { + if (internalFormat == LOCAL_GL_SRGB) + return LOCAL_GL_RGB; + + if (internalFormat == LOCAL_GL_SRGB_ALPHA) + return LOCAL_GL_RGBA; + } + + if (IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_atc)) { + if (internalFormat == LOCAL_GL_ATC_RGB) + return LOCAL_GL_RGB; + + if (internalFormat == LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA || + internalFormat == LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA) + { + return LOCAL_GL_RGBA; + } + } + + if (IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_etc1)) { + if (internalFormat == LOCAL_GL_ETC1_RGB8_OES) + return LOCAL_GL_RGB; + } + + if (IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_pvrtc)) { + if (internalFormat == LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1 || + internalFormat == LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1) + { + return LOCAL_GL_RGB; + } + + if (internalFormat == LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1 || + internalFormat == LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1) + { + return LOCAL_GL_RGBA; + } + } + + if (IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_s3tc)) { + if (internalFormat == LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT) + return LOCAL_GL_RGB; + + if (internalFormat == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || + internalFormat == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT || + internalFormat == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + { + return LOCAL_GL_RGBA; + } + } + + if (IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture)) { + if (internalFormat == LOCAL_GL_DEPTH_COMPONENT || + internalFormat == LOCAL_GL_DEPTH_COMPONENT16 || + internalFormat == LOCAL_GL_DEPTH_COMPONENT32) + { + return LOCAL_GL_DEPTH_COMPONENT; + } + + if (internalFormat == LOCAL_GL_DEPTH_STENCIL || + internalFormat == LOCAL_GL_DEPTH24_STENCIL8) + { + return LOCAL_GL_DEPTH_STENCIL; + } + } + + MOZ_ASSERT(false, "Unhandled internalFormat"); + return LOCAL_GL_NONE; +} + +bool WebGLContext::ValidateBlendEquationEnum(GLenum mode, const char *info) +{ + switch (mode) { + case LOCAL_GL_FUNC_ADD: + case LOCAL_GL_FUNC_SUBTRACT: + case LOCAL_GL_FUNC_REVERSE_SUBTRACT: + return true; + case LOCAL_GL_MIN: + case LOCAL_GL_MAX: + if (IsWebGL2()) { + // http://www.opengl.org/registry/specs/EXT/blend_minmax.txt + return true; + } + break; + default: + break; + } + + ErrorInvalidEnumInfo(info, mode); + return false; +} + +bool WebGLContext::ValidateBlendFuncDstEnum(GLenum factor, const char *info) +{ + switch (factor) { + case LOCAL_GL_ZERO: + case LOCAL_GL_ONE: + case LOCAL_GL_SRC_COLOR: + case LOCAL_GL_ONE_MINUS_SRC_COLOR: + case LOCAL_GL_DST_COLOR: + case LOCAL_GL_ONE_MINUS_DST_COLOR: + case LOCAL_GL_SRC_ALPHA: + case LOCAL_GL_ONE_MINUS_SRC_ALPHA: + case LOCAL_GL_DST_ALPHA: + case LOCAL_GL_ONE_MINUS_DST_ALPHA: + case LOCAL_GL_CONSTANT_COLOR: + case LOCAL_GL_ONE_MINUS_CONSTANT_COLOR: + case LOCAL_GL_CONSTANT_ALPHA: + case LOCAL_GL_ONE_MINUS_CONSTANT_ALPHA: + return true; + default: + ErrorInvalidEnumInfo(info, factor); + return false; + } +} + +bool WebGLContext::ValidateBlendFuncSrcEnum(GLenum factor, const char *info) +{ + if (factor == LOCAL_GL_SRC_ALPHA_SATURATE) + return true; + else + return ValidateBlendFuncDstEnum(factor, info); +} + +bool WebGLContext::ValidateBlendFuncEnumsCompatibility(GLenum sfactor, GLenum dfactor, const char *info) +{ + bool sfactorIsConstantColor = sfactor == LOCAL_GL_CONSTANT_COLOR || + sfactor == LOCAL_GL_ONE_MINUS_CONSTANT_COLOR; + bool sfactorIsConstantAlpha = sfactor == LOCAL_GL_CONSTANT_ALPHA || + sfactor == LOCAL_GL_ONE_MINUS_CONSTANT_ALPHA; + bool dfactorIsConstantColor = dfactor == LOCAL_GL_CONSTANT_COLOR || + dfactor == LOCAL_GL_ONE_MINUS_CONSTANT_COLOR; + bool dfactorIsConstantAlpha = dfactor == LOCAL_GL_CONSTANT_ALPHA || + dfactor == LOCAL_GL_ONE_MINUS_CONSTANT_ALPHA; + if ( (sfactorIsConstantColor && dfactorIsConstantAlpha) || + (dfactorIsConstantColor && sfactorIsConstantAlpha) ) { + ErrorInvalidOperation("%s are mutually incompatible, see section 6.8 in the WebGL 1.0 spec", info); + return false; + } else { + return true; + } +} + +bool WebGLContext::ValidateTextureTargetEnum(GLenum target, const char *info) +{ + switch (target) { + case LOCAL_GL_TEXTURE_2D: + case LOCAL_GL_TEXTURE_CUBE_MAP: + return true; + default: + ErrorInvalidEnumInfo(info, target); + return false; + } +} + +bool WebGLContext::ValidateComparisonEnum(GLenum target, const char *info) +{ + switch (target) { + case LOCAL_GL_NEVER: + case LOCAL_GL_LESS: + case LOCAL_GL_LEQUAL: + case LOCAL_GL_GREATER: + case LOCAL_GL_GEQUAL: + case LOCAL_GL_EQUAL: + case LOCAL_GL_NOTEQUAL: + case LOCAL_GL_ALWAYS: + return true; + default: + ErrorInvalidEnumInfo(info, target); + return false; + } +} + +bool WebGLContext::ValidateStencilOpEnum(GLenum action, const char *info) +{ + switch (action) { + case LOCAL_GL_KEEP: + case LOCAL_GL_ZERO: + case LOCAL_GL_REPLACE: + case LOCAL_GL_INCR: + case LOCAL_GL_INCR_WRAP: + case LOCAL_GL_DECR: + case LOCAL_GL_DECR_WRAP: + case LOCAL_GL_INVERT: + return true; + default: + ErrorInvalidEnumInfo(info, action); + return false; + } +} + +bool WebGLContext::ValidateFaceEnum(GLenum face, const char *info) +{ + switch (face) { + case LOCAL_GL_FRONT: + case LOCAL_GL_BACK: + case LOCAL_GL_FRONT_AND_BACK: + return true; + default: + ErrorInvalidEnumInfo(info, face); + return false; + } +} + +bool WebGLContext::ValidateDrawModeEnum(GLenum mode, const char *info) +{ + switch (mode) { + case LOCAL_GL_TRIANGLES: + case LOCAL_GL_TRIANGLE_STRIP: + case LOCAL_GL_TRIANGLE_FAN: + case LOCAL_GL_POINTS: + case LOCAL_GL_LINE_STRIP: + case LOCAL_GL_LINE_LOOP: + case LOCAL_GL_LINES: + return true; + default: + ErrorInvalidEnumInfo(info, mode); + return false; + } +} + +bool WebGLContext::ValidateGLSLVariableName(const nsAString& name, const char *info) +{ + if (name.IsEmpty()) + return false; + + const uint32_t maxSize = 256; + if (name.Length() > maxSize) { + ErrorInvalidValue("%s: identifier is %d characters long, exceeds the maximum allowed length of %d characters", + info, name.Length(), maxSize); + return false; + } + + if (!ValidateGLSLString(name, info)) { + return false; + } + + nsString prefix1 = NS_LITERAL_STRING("webgl_"); + nsString prefix2 = NS_LITERAL_STRING("_webgl_"); + + if (Substring(name, 0, prefix1.Length()).Equals(prefix1) || + Substring(name, 0, prefix2.Length()).Equals(prefix2)) + { + ErrorInvalidOperation("%s: string contains a reserved GLSL prefix", info); + return false; + } + + return true; +} + +bool WebGLContext::ValidateGLSLString(const nsAString& string, const char *info) +{ + for (uint32_t i = 0; i < string.Length(); ++i) { + if (!ValidateGLSLCharacter(string.CharAt(i))) { + ErrorInvalidValue("%s: string contains the illegal character '%d'", info, string.CharAt(i)); + return false; + } + } + + return true; +} + +/** + * Return true if format is a valid texture image format for source, + * taking into account enabled WebGL extensions. + */ +bool +WebGLContext::ValidateTexImageFormat(GLenum format, WebGLTexImageFunc func) +{ + /* Core WebGL texture formats */ + if (format == LOCAL_GL_ALPHA || + format == LOCAL_GL_RGB || + format == LOCAL_GL_RGBA || + format == LOCAL_GL_LUMINANCE || + format == LOCAL_GL_LUMINANCE_ALPHA) + { + return true; + } + + /* Only core formats are valid for CopyTex(Sub)?Image */ + // TODO: Revisit this once color_buffer_(half_)?float lands + if (IsCopyFunc(func)) { + ErrorInvalidEnumWithName(this, "invalid format", format, func); + return false; + } + + /* WEBGL_depth_texture added formats */ + if (format == LOCAL_GL_DEPTH_COMPONENT || + format == LOCAL_GL_DEPTH_STENCIL) + { + bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture); + if (!validFormat) + ErrorInvalidEnum("%s: invalid format %s: need WEBGL_depth_texture enabled", + InfoFrom(func), NameFrom(format)); + return validFormat; + } + + /* EXT_sRGB added formats */ + if (format == LOCAL_GL_SRGB || + format == LOCAL_GL_SRGB_ALPHA) + { + bool validFormat = IsExtensionEnabled(WebGLExtensionID::EXT_sRGB); + if (!validFormat) + ErrorInvalidEnum("%s: invalid format %s: need EXT_sRGB enabled", + InfoFrom(func), NameFrom(format)); + return validFormat; + } + + /* WEBGL_compressed_texture_atc added formats */ + if (format == LOCAL_GL_ATC_RGB || + format == LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA || + format == LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA) + { + bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_atc); + if (!validFormat) + ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_atc enabled", + InfoFrom(func), NameFrom(format)); + return validFormat; + } + + // WEBGL_compressed_texture_etc1 + if (format == LOCAL_GL_ETC1_RGB8_OES) { + bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_etc1); + if (!validFormat) + ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_etc1 enabled", + InfoFrom(func), NameFrom(format)); + return validFormat; + } + + + if (format == LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1 || + format == LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1 || + format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1 || + format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1) + { + bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_pvrtc); + if (!validFormat) + ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_pvrtc enabled", + InfoFrom(func), NameFrom(format)); + return validFormat; + } + + + if (format == LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT || + format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || + format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT || + format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + { + bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_s3tc); + if (!validFormat) + ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_s3tc enabled", + InfoFrom(func), NameFrom(format)); + return validFormat; + } + + ErrorInvalidEnumWithName(this, "invalid format", format, func); + + return false; +} + +/** + * Check if the given texture target is valid for TexImage. + */ +bool +WebGLContext::ValidateTexImageTarget(GLuint dims, GLenum target, WebGLTexImageFunc func) +{ + switch (dims) { + case 2: + if (target == LOCAL_GL_TEXTURE_2D || + IsTexImageCubemapTarget(target)) + { + return true; + } + + ErrorInvalidEnumWithName(this, "invalid target", target, func); + return false; + + default: + MOZ_ASSERT(false, "ValidateTexImageTarget: Invalid dims"); + } + + return false; +} + +/** + * Return true if type is a valid texture image type for source, + * taking into account enabled WebGL extensions. + */ +bool +WebGLContext::ValidateTexImageType(GLenum type, WebGLTexImageFunc func) +{ + /* Core WebGL texture types */ + if (type == LOCAL_GL_UNSIGNED_BYTE || + type == LOCAL_GL_UNSIGNED_SHORT_5_6_5 || + type == LOCAL_GL_UNSIGNED_SHORT_4_4_4_4 || + type == LOCAL_GL_UNSIGNED_SHORT_5_5_5_1) + { + return true; + } + + /* OES_texture_float added types */ + if (type == LOCAL_GL_FLOAT) { + bool validType = IsExtensionEnabled(WebGLExtensionID::OES_texture_float); + if (!validType) + ErrorInvalidEnum("%s: invalid type %s: need OES_texture_float enabled", + InfoFrom(func), NameFrom(type)); + return validType; + } + + /* OES_texture_half_float add types */ + if (type == LOCAL_GL_HALF_FLOAT_OES) { + bool validType = IsExtensionEnabled(WebGLExtensionID::OES_texture_half_float); + if (!validType) + ErrorInvalidEnum("%s: invalid type %s: need OES_texture_half_float enabled", + InfoFrom(func), NameFrom(type)); + return validType; + } + + /* WEBGL_depth_texture added types */ + if (type == LOCAL_GL_UNSIGNED_SHORT || + type == LOCAL_GL_UNSIGNED_INT || + type == LOCAL_GL_UNSIGNED_INT_24_8) + { + bool validType = IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture); + if (!validType) + ErrorInvalidEnum("%s: invalid type %s: need WEBGL_depth_texture enabled", + InfoFrom(func), NameFrom(type)); + return validType; + } + + ErrorInvalidEnumWithName(this, "invalid type", type, func); + return false; +} + +/** + * Validate texture image sizing extra constraints for + * CompressedTex(Sub)?Image. + */ +// TODO: WebGL 2 +bool +WebGLContext::ValidateCompTexImageSize(GLenum target, GLint level, GLenum format, + GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, + GLsizei levelWidth, GLsizei levelHeight, + WebGLTexImageFunc func) +{ + // Negative parameters must already have been handled above + MOZ_ASSERT(xoffset >= 0 && yoffset >= 0 && + width >= 0 && height >= 0); + + if (xoffset + width > (GLint) levelWidth) { + ErrorInvalidValue("%s: xoffset + width must be <= levelWidth", InfoFrom(func)); + return false; + } + + if (yoffset + height > (GLint) levelHeight) { + ErrorInvalidValue("%s: yoffset + height must be <= levelHeight", InfoFrom(func)); + return false; + } + + GLint blockWidth = 1; + GLint blockHeight = 1; + BlockSizeFor(format, &blockWidth, &blockHeight); + + /* If blockWidth || blockHeight != 1, then the compressed format + * had block-based constraints to be checked. (For example, PVRTC is compressed but + * isn't a block-based format) + */ + if (blockWidth != 1 || blockHeight != 1) { + /* offsets must be multiple of block size */ + if (xoffset % blockWidth != 0) { + ErrorInvalidOperation("%s: xoffset must be multiple of %d", + InfoFrom(func), blockWidth); + return false; + } + + if (yoffset % blockHeight != 0) { + ErrorInvalidOperation("%s: yoffset must be multiple of %d", + InfoFrom(func), blockHeight); + return false; + } + + /* The size must be a multiple of blockWidth and blockHeight, + * or must be using offset+size that exactly hits the edge. + * Important for small mipmap levels. + */ + /* https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ + * "When level equals zero width and height must be a multiple of 4. When + * level is greater than 0 width and height must be 0, 1, 2 or a multiple of 4. + * If they are not an INVALID_OPERATION error is generated." + */ + if (level == 0) { + if (width % blockWidth != 0) { + ErrorInvalidOperation("%s: width of level 0 must be multple of %d", + InfoFrom(func), blockWidth); + return false; + } + + if (height % blockHeight != 0) { + ErrorInvalidOperation("%s: height of level 0 must be multipel of %d", + InfoFrom(func), blockHeight); + return false; + } + } + else if (level > 0) { + if (width % blockWidth != 0 && width > 2) { + ErrorInvalidOperation("%s: width of level %d must be multiple" + " of %d or 0, 1, 2", + InfoFrom(func), level, blockWidth); + return false; + } + + if (height % blockHeight != 0 && height > 2) { + ErrorInvalidOperation("%s: height of level %d must be multiple" + " of %d or 0, 1, 2", + InfoFrom(func), level, blockHeight); + return false; + } + } + + if (IsSubFunc(func)) { + if ((xoffset % blockWidth) != 0) { + ErrorInvalidOperation("%s: xoffset must be multiple of %d", + InfoFrom(func), blockWidth); + return false; + } + + if (yoffset % blockHeight != 0) { + ErrorInvalidOperation("%s: yoffset must be multiple of %d", + InfoFrom(func), blockHeight); + return false; + } + } + } + + switch (format) { + case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1: + case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1: + if (!is_pot_assuming_nonnegative(width) || + !is_pot_assuming_nonnegative(height)) + { + ErrorInvalidValue("%s: width and height must be powers of two", + InfoFrom(func)); + return false; + } + } + + return true; +} + +/** + * Return true if the enough data is present to satisfy compressed + * texture format constraints. + */ +bool +WebGLContext::ValidateCompTexImageDataSize(GLint level, GLenum format, + GLsizei width, GLsizei height, + uint32_t byteLength, WebGLTexImageFunc func) +{ + // negative width and height must already have been handled above + MOZ_ASSERT(width >= 0 && height >= 0); + + CheckedUint32 required_byteLength = 0; + + switch (format) { + case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case LOCAL_GL_ATC_RGB: + case LOCAL_GL_ETC1_RGB8_OES: + { + required_byteLength = ((CheckedUint32(width) + 3) / 4) * ((CheckedUint32(height) + 3) / 4) * 8; + break; + } + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA: + case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA: + { + required_byteLength = ((CheckedUint32(width) + 3) / 4) * ((CheckedUint32(height) + 3) / 4) * 16; + break; + } + case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1: + { + required_byteLength = CheckedUint32(std::max(width, 8)) * CheckedUint32(std::max(height, 8)) / 2; + break; + } + case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1: + { + required_byteLength = CheckedUint32(std::max(width, 16)) * CheckedUint32(std::max(height, 8)) / 4; + break; + } + } + + if (!required_byteLength.isValid() || required_byteLength.value() != byteLength) { + ErrorInvalidValue("%s: data size does not match dimensions", InfoFrom(func)); + return false; + } + + return true; +} + +/** + * Validate the width, height, and depth of a texture image, \return + * true is valid, false otherwise. + * Used by all the (Compressed|Copy)?Tex(Sub)?Image functions. + * Target and level must have been validated before calling. + */ +bool +WebGLContext::ValidateTexImageSize(GLenum target, GLint level, + GLint width, GLint height, GLint depth, + WebGLTexImageFunc func) +{ + MOZ_ASSERT(level >= 0, "level should already be validated"); + + /* Bug 966630: maxTextureSize >> level runs into "undefined" + * behaviour depending on ISA. For example, on Intel shifts + * amounts are mod 64 (in 64-bit mode on 64-bit dest) and mod 32 + * otherwise. This means 16384 >> 0x10000001 == 8192 which isn't + * what would be expected. Make the required behaviour explicit by + * clamping to a shift of 31 bits if level is greater than that + * ammount. This will give 0 that if (!maxAllowedSize) is + * expecting. + */ + + if (level > 31) + level = 31; + + const GLuint maxTexImageSize = MaxTextureSizeForTarget(target) >> level; + const bool isCubemapTarget = IsTexImageCubemapTarget(target); + const bool isSub = IsSubFunc(func); + + if (!isSub && isCubemapTarget && (width != height)) { + /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification + * "When the target parameter to TexImage2D is one of the + * six cube map two-dimensional image targets, the error + * INVALID_VALUE is generated if the width and height + * parameters are not equal." + */ + ErrorInvalidValue("%s: for cube map, width must equal height", InfoFrom(func)); + return false; + } + + if (target == LOCAL_GL_TEXTURE_2D || isCubemapTarget) + { + /* 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." + */ + if (width < 0) { + ErrorInvalidValue("%s: width must be >= 0", InfoFrom(func)); + return false; + } + + if (height < 0) { + ErrorInvalidValue("%s: height must be >= 0", InfoFrom(func)); + return false; + } + + /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification + * "The maximum allowable width and height of a + * two-dimensional texture image must be at least 2**(k−lod) + * for image arrays of level zero through k, where k is the + * log base 2 of MAX_TEXTURE_SIZE. and lod is the + * level-of-detail of the image array. It may be zero for + * image arrays of any level-of-detail greater than k. The + * error INVALID_VALUE is generated if the specified image + * is too large to be stored under any conditions. + */ + if (width > (int) maxTexImageSize) { + ErrorInvalidValue("%s: the maximum width for level %d is %u", + InfoFrom(func), level, maxTexImageSize); + return false; + } + + if (height > (int) maxTexImageSize) { + ErrorInvalidValue("%s: tex maximum height for level %d is %u", + InfoFrom(func), level, maxTexImageSize); + 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." + */ + if (level > 0) { + if (!is_pot_assuming_nonnegative(width)) { + ErrorInvalidValue("%s: level >= 0, width of %d must be a power of two.", + InfoFrom(func), width); + return false; + } + + if (!is_pot_assuming_nonnegative(height)) { + ErrorInvalidValue("%s: level >= 0, height of %d must be a power of two.", + InfoFrom(func), height); + return false; + } + } + } + + // TODO: WebGL 2 + if (target == LOCAL_GL_TEXTURE_3D) { + if (depth < 0) { + ErrorInvalidValue("%s: depth must be >= 0", InfoFrom(func)); + return false; + } + + if (!is_pot_assuming_nonnegative(depth)) { + ErrorInvalidValue("%s: level >= 0, depth of %d must be a power of two.", + InfoFrom(func), depth); + return false; + } + } + + return true; +} + +/** + * Validate texture image sizing for Tex(Sub)?Image variants. + */ +// TODO: WebGL 2. Update this to handle 3D textures. +bool +WebGLContext::ValidateTexSubImageSize(GLint xoffset, GLint yoffset, GLint /*zoffset*/, + GLsizei width, GLsizei height, GLsizei /*depth*/, + GLsizei baseWidth, GLsizei baseHeight, GLsizei /*baseDepth*/, + WebGLTexImageFunc func) +{ + /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification + * "Taking wt and ht to be the specified width and height of the + * texture array, and taking x, y, w, and h to be the xoffset, + * yoffset, width, and height argument values, any of the + * following relationships generates the error INVALID_VALUE: + * x < 0 + * x + w > wt + * y < 0 + * y + h > ht" + */ + + if (xoffset < 0) { + ErrorInvalidValue("%s: xoffset must be >= 0", InfoFrom(func)); + return false; + } + + if (yoffset < 0) { + ErrorInvalidValue("%s: yoffset must be >= 0", InfoFrom(func)); + return false; + } + + if (!CanvasUtils::CheckSaneSubrectSize(xoffset, yoffset, width, height, baseWidth, baseHeight)) { + ErrorInvalidValue("%s: subtexture rectangle out-of-bounds", InfoFrom(func)); + return false; + } + + return true; +} + +/** + * Return the bits per texel for format & type combination. + * Assumes that format & type are a valid combination as checked with + * ValidateTexImageFormatAndType(). + */ +uint32_t +WebGLContext::GetBitsPerTexel(GLenum format, GLenum type) +{ + // If there is no defined format or type, we're not taking up any memory + if (!format || !type) { + return 0; + } + + /* Known fixed-sized types */ + if (type == LOCAL_GL_UNSIGNED_SHORT_4_4_4_4 || + type == LOCAL_GL_UNSIGNED_SHORT_5_5_5_1 || + type == LOCAL_GL_UNSIGNED_SHORT_5_6_5) + { + return 16; + } + + if (type == LOCAL_GL_UNSIGNED_INT_24_8) + return 32; + + int bitsPerComponent = 0; + switch (type) { + case LOCAL_GL_UNSIGNED_BYTE: + bitsPerComponent = 8; + break; + + case LOCAL_GL_HALF_FLOAT: + case LOCAL_GL_HALF_FLOAT_OES: + case LOCAL_GL_UNSIGNED_SHORT: + bitsPerComponent = 16; + break; + + case LOCAL_GL_FLOAT: + case LOCAL_GL_UNSIGNED_INT: + bitsPerComponent = 32; + break; + + default: + MOZ_ASSERT(false, "Unhandled type."); + break; + } + + switch (format) { + // Uncompressed formats + case LOCAL_GL_ALPHA: + case LOCAL_GL_LUMINANCE: + case LOCAL_GL_DEPTH_COMPONENT: + case LOCAL_GL_DEPTH_STENCIL: + return 1 * bitsPerComponent; + + case LOCAL_GL_LUMINANCE_ALPHA: + return 2 * bitsPerComponent; + + case LOCAL_GL_RGB: + case LOCAL_GL_RGB32F: + case LOCAL_GL_SRGB_EXT: + return 3 * bitsPerComponent; + + case LOCAL_GL_RGBA: + case LOCAL_GL_RGBA32F: + case LOCAL_GL_SRGB_ALPHA_EXT: + return 4 * bitsPerComponent; + + // Compressed formats + case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1: + return 2; + + case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case LOCAL_GL_ATC_RGB: + case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1: + case LOCAL_GL_ETC1_RGB8_OES: + return 4; + + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA: + case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA: + return 8; + + default: + break; + } + + MOZ_ASSERT(false, "Unhandled format+type combo."); + return 0; +} + +/** + * Perform validation of format/type combinations for TexImage variants. + * Returns true if the format/type is a valid combination, false otherwise. + */ +bool +WebGLContext::ValidateTexImageFormatAndType(GLenum format, GLenum type, WebGLTexImageFunc func) +{ + if (!ValidateTexImageFormat(format, func) || + !ValidateTexImageType(type, func)) + { + return false; + } + + bool validCombo = false; + + switch (format) { + case LOCAL_GL_ALPHA: + case LOCAL_GL_LUMINANCE: + case LOCAL_GL_LUMINANCE_ALPHA: + validCombo = (type == LOCAL_GL_UNSIGNED_BYTE || + type == LOCAL_GL_HALF_FLOAT || + type == LOCAL_GL_HALF_FLOAT_OES || + type == LOCAL_GL_FLOAT); + break; + + case LOCAL_GL_RGB: + case LOCAL_GL_SRGB: + validCombo = (type == LOCAL_GL_UNSIGNED_BYTE || + type == LOCAL_GL_UNSIGNED_SHORT_5_6_5 || + type == LOCAL_GL_HALF_FLOAT || + type == LOCAL_GL_HALF_FLOAT_OES || + type == LOCAL_GL_FLOAT); + break; + + case LOCAL_GL_RGBA: + case LOCAL_GL_SRGB_ALPHA: + validCombo = (type == LOCAL_GL_UNSIGNED_BYTE || + type == LOCAL_GL_UNSIGNED_SHORT_4_4_4_4 || + type == LOCAL_GL_UNSIGNED_SHORT_5_5_5_1 || + type == LOCAL_GL_HALF_FLOAT || + type == LOCAL_GL_HALF_FLOAT_OES || + type == LOCAL_GL_FLOAT); + break; + + case LOCAL_GL_DEPTH_COMPONENT: + validCombo = (type == LOCAL_GL_UNSIGNED_SHORT || + type == LOCAL_GL_UNSIGNED_INT); + break; + + case LOCAL_GL_DEPTH_STENCIL: + validCombo = (type == LOCAL_GL_UNSIGNED_INT_24_8); + break; + + case LOCAL_GL_ATC_RGB: + case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA: + case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA: + case LOCAL_GL_ETC1_RGB8_OES: + case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1: + case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1: + case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1: + case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + validCombo = (type == LOCAL_GL_UNSIGNED_BYTE); + break; + + default: + // Only valid formats should be passed to the switch stmt. + MOZ_ASSERT(false, "Unexpected format and type combo. How'd this happen?"); + validCombo = false; + // Fall through to return an InvalidOperations. This will alert us to the + // unexpected case that needs fixing in builds without asserts. + } + + if (!validCombo) + ErrorInvalidOperation("%s: invalid combination of format %s and type %s", + InfoFrom(func), NameFrom(format), NameFrom(type)); + + return validCombo; +} + +/** + * Return true if format, type and jsArrayType are a valid combination. + * Also returns the size for texel of format and type (in bytes) via + * \a texelSize. + * + * It is assumed that type has previously been validated. + */ +bool +WebGLContext::ValidateTexInputData(GLenum type, int jsArrayType, WebGLTexImageFunc func) +{ + bool validInput = false; + const char invalidTypedArray[] = "%s: invalid typed array type for given texture data type"; + + // First, we check for packed types + switch (type) { + case LOCAL_GL_UNSIGNED_BYTE: + validInput = (jsArrayType == -1 || jsArrayType == js::ArrayBufferView::TYPE_UINT8); + break; + + // TODO: WebGL spec doesn't allow half floats to specified as UInt16. + case LOCAL_GL_HALF_FLOAT: + case LOCAL_GL_HALF_FLOAT_OES: + validInput = (jsArrayType == -1); + break; + + 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: + validInput = (jsArrayType == -1 || jsArrayType == js::ArrayBufferView::TYPE_UINT16); + break; + + case LOCAL_GL_UNSIGNED_INT: + case LOCAL_GL_UNSIGNED_INT_24_8: + validInput = (jsArrayType == -1 || jsArrayType == js::ArrayBufferView::TYPE_UINT32); + break; + + case LOCAL_GL_FLOAT: + validInput = (jsArrayType == -1 || jsArrayType == js::ArrayBufferView::TYPE_FLOAT32); + break; + + default: + break; + } + + if (!validInput) + ErrorInvalidOperation(invalidTypedArray, InfoFrom(func)); + + return validInput; +} + +/** + * Test the gl(Copy|Compressed)?Tex[Sub]?Image[23]() parameters for errors. + * Verifies each of the parameters against the WebGL standard and enabled extensions. + */ +// TODO: Texture dims is here for future expansion in WebGL 2.0 +bool +WebGLContext::ValidateTexImage(GLuint dims, GLenum target, + GLint level, GLint internalFormat, + GLint xoffset, GLint yoffset, GLint zoffset, + GLint width, GLint height, GLint depth, + GLint border, GLenum format, GLenum type, + WebGLTexImageFunc func) +{ + const char* info = InfoFrom(func); + + /* Check target */ + if (!ValidateTexImageTarget(dims, target, func)) + return false; + + /* Check level */ + if (level < 0) { + ErrorInvalidValue("%s: level must be >= 0", info); + return false; + } + + /* Check border */ + if (border != 0) { + ErrorInvalidValue("%s: border must be 0", info); + return false; + } + + /* Check incoming image format and type */ + if (!ValidateTexImageFormatAndType(format, type, func)) + return false; + + /* WebGL and OpenGL ES 2.0 impose additional restrictions on the + * combinations of format, internalFormat, and type that can be + * used. Formats and types that require additional extensions + * (e.g., GL_FLOAT requires GL_OES_texture_float) are filtered + * elsewhere. + */ + if ((GLint) format != internalFormat) { + ErrorInvalidOperation("%s: format does not match internalformat", info); + return false; + } + + /* check internalFormat */ + // TODO: Not sure if this is a bit of over kill. + if (BaseTexFormat(internalFormat) == LOCAL_GL_NONE) { + MOZ_ASSERT(false); + ErrorInvalidValue("%s:", info); + return false; + } + + /* Check texture image size */ + if (!ValidateTexImageSize(target, level, width, height, 0, func)) + return false; + + /* 5.14.8 Texture objects - WebGL Spec. + * "If an attempt is made to call these functions with no + * WebGLTexture bound (see above), an INVALID_OPERATION error + * is generated." + */ + WebGLTexture* tex = activeBoundTextureForTarget(target); + if (!tex) { + ErrorInvalidOperation("%s: no texture is bound to target %s", + info, NameFrom(target)); + return false; + } + + if (IsSubFunc(func)) { + if (!tex->HasImageInfoAt(target, level)) { + ErrorInvalidOperation("%s: no texture image previously defined for target %s at level %d", + info, NameFrom(target), level); + return false; + } + + const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(target, level); + if (!ValidateTexSubImageSize(xoffset, yoffset, zoffset, + width, height, depth, + imageInfo.Width(), imageInfo.Height(), 0, + func)) + { + return false; + } + + /* Require the format and type to match that of the existing + * texture as created + */ + if (imageInfo.WebGLFormat() != format || + imageInfo.WebGLType() != type) + { + ErrorInvalidOperation("%s: format or type doesn't match the existing texture", + info); + return false; + } + } + + /* Additional checks for depth textures */ + if (format == LOCAL_GL_DEPTH_COMPONENT || + format == LOCAL_GL_DEPTH_STENCIL) + { + if (func == WebGLTexImageFunc::TexSubImage || IsCopyFunc(func)) { + ErrorInvalidOperationWithName(this, "called with format/internalformat", + format, func); + return false; + } + + if (func == WebGLTexImageFunc::TexImage && + target != LOCAL_GL_TEXTURE_2D) + { + ErrorInvalidOperation("%s: with format of %s target must be TEXTURE_2D", + info, NameFrom(format)); + return false; + } + + if (func == WebGLTexImageFunc::TexImage && level != 0) { + ErrorInvalidOperation("%s: with format of %s target, level must be 0", + info, NameFrom(format)); + return false; + } + } + + /* Additional checks for compressed textures */ + if (!IsAllowedFromSource(format, func)) { + ErrorInvalidOperation("%s: Invalid format %s for this operation", + info, NameFrom(format)); + return false; + } + + /* Parameters are OK */ + return true; +} + +bool +WebGLContext::ValidateUniformLocation(const char* info, WebGLUniformLocation *location_object) +{ + if (!ValidateObjectAllowNull(info, location_object)) + return false; + if (!location_object) + return false; + /* the need to check specifically for !mCurrentProgram here is explained in bug 657556 */ + if (!mCurrentProgram) { + ErrorInvalidOperation("%s: no program is currently bound", info); + return false; + } + if (mCurrentProgram != location_object->Program()) { + ErrorInvalidOperation("%s: this uniform location doesn't correspond to the current program", info); + return false; + } + if (mCurrentProgram->Generation() != location_object->ProgramGeneration()) { + ErrorInvalidOperation("%s: This uniform location is obsolete since the program has been relinked", info); + return false; + } + return true; +} + +bool +WebGLContext::ValidateSamplerUniformSetter(const char* info, WebGLUniformLocation *location, GLint value) +{ + if (location->Info().type != SH_SAMPLER_2D && + location->Info().type != SH_SAMPLER_CUBE) + { + return true; + } + + if (value >= 0 && value < mGLMaxTextureUnits) + return true; + + ErrorInvalidValue("%s: this uniform location is a sampler, but %d is not a valid texture unit", + info, value); + return false; +} + +bool +WebGLContext::ValidateAttribArraySetter(const char* name, uint32_t cnt, uint32_t arrayLength) +{ + if (IsContextLost()) { + return false; + } + if (arrayLength < cnt) { + ErrorInvalidOperation("%s: array must be >= %d elements", name, cnt); + return false; + } + return true; +} + +bool +WebGLContext::ValidateUniformArraySetter(const char* name, uint32_t expectedElemSize, WebGLUniformLocation *location_object, + GLint& location, uint32_t& numElementsToUpload, uint32_t arrayLength) +{ + if (IsContextLost()) + return false; + if (!ValidateUniformLocation(name, location_object)) + return false; + location = location_object->Location(); + uint32_t uniformElemSize = location_object->ElementSize(); + if (expectedElemSize != uniformElemSize) { + ErrorInvalidOperation("%s: this function expected a uniform of element size %d," + " got a uniform of element size %d", name, + expectedElemSize, + uniformElemSize); + return false; + } + if (arrayLength == 0 || + arrayLength % expectedElemSize) + { + ErrorInvalidValue("%s: expected an array of length a multiple" + " of %d, got an array of length %d", name, + expectedElemSize, + arrayLength); + return false; + } + const WebGLUniformInfo& info = location_object->Info(); + if (!info.isArray && + arrayLength != expectedElemSize) { + ErrorInvalidOperation("%s: expected an array of length exactly" + " %d (since this uniform is not an array" + " uniform), got an array of length %d", name, + expectedElemSize, + arrayLength); + return false; + } + numElementsToUpload = + std::min(info.arraySize, arrayLength / expectedElemSize); + return true; +} + +bool +WebGLContext::ValidateUniformMatrixArraySetter(const char* name, int dim, WebGLUniformLocation *location_object, + GLint& location, uint32_t& numElementsToUpload, uint32_t arrayLength, + WebGLboolean aTranspose) +{ + uint32_t expectedElemSize = (dim)*(dim); + if (IsContextLost()) + return false; + if (!ValidateUniformLocation(name, location_object)) + return false; + location = location_object->Location(); + uint32_t uniformElemSize = location_object->ElementSize(); + if (expectedElemSize != uniformElemSize) { + ErrorInvalidOperation("%s: this function expected a uniform of element size %d," + " got a uniform of element size %d", name, + expectedElemSize, + uniformElemSize); + return false; + } + if (arrayLength == 0 || + arrayLength % expectedElemSize) + { + ErrorInvalidValue("%s: expected an array of length a multiple" + " of %d, got an array of length %d", name, + expectedElemSize, + arrayLength); + return false; + } + const WebGLUniformInfo& info = location_object->Info(); + if (!info.isArray && + arrayLength != expectedElemSize) { + ErrorInvalidOperation("%s: expected an array of length exactly" + " %d (since this uniform is not an array" + " uniform), got an array of length %d", name, + expectedElemSize, + arrayLength); + return false; + } + if (aTranspose) { + ErrorInvalidValue("%s: transpose must be FALSE as per the " + "OpenGL ES 2.0 spec", name); + return false; + } + numElementsToUpload = + std::min(info.arraySize, arrayLength / (expectedElemSize)); + return true; +} + +bool +WebGLContext::ValidateUniformSetter(const char* name, WebGLUniformLocation *location_object, GLint& location) +{ + if (IsContextLost()) + return false; + if (!ValidateUniformLocation(name, location_object)) + return false; + location = location_object->Location(); + return true; +} + +bool WebGLContext::ValidateAttribIndex(GLuint index, const char *info) +{ + return mBoundVertexArray->EnsureAttrib(index, info); +} + +bool WebGLContext::ValidateStencilParamsForDrawCall() +{ + const char *msg = "%s set different front and back stencil %s. Drawing in this configuration is not allowed."; + if (mStencilRefFront != mStencilRefBack) { + ErrorInvalidOperation(msg, "stencilFuncSeparate", "reference values"); + return false; + } + if (mStencilValueMaskFront != mStencilValueMaskBack) { + ErrorInvalidOperation(msg, "stencilFuncSeparate", "value masks"); + return false; + } + if (mStencilWriteMaskFront != mStencilWriteMaskBack) { + ErrorInvalidOperation(msg, "stencilMaskSeparate", "write masks"); + return false; + } + return true; +} + +static inline int32_t floorPOT(int32_t x) +{ + MOZ_ASSERT(x > 0); + int32_t pot = 1; + while (pot < 0x40000000) { + if (x < pot*2) + break; + pot *= 2; + } + return pot; +} + +bool +WebGLContext::InitAndValidateGL() +{ + if (!gl) return false; + + GLenum error = gl->fGetError(); + if (error != LOCAL_GL_NO_ERROR) { + GenerateWarning("GL error 0x%x occurred during OpenGL context initialization, before WebGL initialization!", error); + return false; + } + + mMinCapability = Preferences::GetBool("webgl.min_capability_mode", false); + mDisableExtensions = Preferences::GetBool("webgl.disable-extensions", false); + mLoseContextOnHeapMinimize = Preferences::GetBool("webgl.lose-context-on-heap-minimize", false); + mCanLoseContextInForeground = Preferences::GetBool("webgl.can-lose-context-in-foreground", true); + + if (MinCapabilityMode()) { + mDisableFragHighP = true; + } + + // These are the default values, see 6.2 State tables in the + // OpenGL ES 2.0.25 spec. + mColorWriteMask[0] = 1; + mColorWriteMask[1] = 1; + mColorWriteMask[2] = 1; + mColorWriteMask[3] = 1; + mDepthWriteMask = 1; + mColorClearValue[0] = 0.f; + mColorClearValue[1] = 0.f; + mColorClearValue[2] = 0.f; + mColorClearValue[3] = 0.f; + mDepthClearValue = 1.f; + mStencilClearValue = 0; + mStencilRefFront = 0; + mStencilRefBack = 0; + mStencilValueMaskFront = 0xffffffff; + mStencilValueMaskBack = 0xffffffff; + mStencilWriteMaskFront = 0xffffffff; + mStencilWriteMaskBack = 0xffffffff; + + // Bindings, etc. + mActiveTexture = 0; + mEmitContextLostErrorOnce = true; + mWebGLError = LOCAL_GL_NO_ERROR; + mUnderlyingGLError = LOCAL_GL_NO_ERROR; + + mBound2DTextures.Clear(); + mBoundCubeMapTextures.Clear(); + + mBoundArrayBuffer = nullptr; + mBoundTransformFeedbackBuffer = nullptr; + mCurrentProgram = nullptr; + + mBoundFramebuffer = nullptr; + mBoundRenderbuffer = nullptr; + + MakeContextCurrent(); + + // on desktop OpenGL, we always keep vertex attrib 0 array enabled + if (!gl->IsGLES()) { + gl->fEnableVertexAttribArray(0); + } + + if (MinCapabilityMode()) { + mGLMaxVertexAttribs = MINVALUE_GL_MAX_VERTEX_ATTRIBS; + } else { + gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_ATTRIBS, &mGLMaxVertexAttribs); + } + if (mGLMaxVertexAttribs < 8) { + GenerateWarning("GL_MAX_VERTEX_ATTRIBS: %d is < 8!", mGLMaxVertexAttribs); + return false; + } + + // Note: GL_MAX_TEXTURE_UNITS is fixed at 4 for most desktop hardware, + // even though the hardware supports much more. The + // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS value is the accurate value. + if (MinCapabilityMode()) { + mGLMaxTextureUnits = MINVALUE_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS; + } else { + gl->fGetIntegerv(LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mGLMaxTextureUnits); + } + if (mGLMaxTextureUnits < 8) { + GenerateWarning("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d is < 8!", mGLMaxTextureUnits); + return false; + } + + mBound2DTextures.SetLength(mGLMaxTextureUnits); + mBoundCubeMapTextures.SetLength(mGLMaxTextureUnits); + + if (MinCapabilityMode()) { + mGLMaxTextureSize = MINVALUE_GL_MAX_TEXTURE_SIZE; + mGLMaxCubeMapTextureSize = MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE; + mGLMaxRenderbufferSize = MINVALUE_GL_MAX_RENDERBUFFER_SIZE; + mGLMaxTextureImageUnits = MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS; + mGLMaxVertexTextureImageUnits = MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS; + } else { + gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mGLMaxTextureSize); + gl->fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mGLMaxCubeMapTextureSize); + gl->fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mGLMaxRenderbufferSize); + gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS, &mGLMaxTextureImageUnits); + gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mGLMaxVertexTextureImageUnits); + } + + mGLMaxTextureSize = floorPOT(mGLMaxTextureSize); + mGLMaxRenderbufferSize = floorPOT(mGLMaxRenderbufferSize); + + if (MinCapabilityMode()) { + mGLMaxFragmentUniformVectors = MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS; + mGLMaxVertexUniformVectors = MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS; + mGLMaxVaryingVectors = MINVALUE_GL_MAX_VARYING_VECTORS; + } else { + if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) { + gl->fGetIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_VECTORS, &mGLMaxFragmentUniformVectors); + gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_UNIFORM_VECTORS, &mGLMaxVertexUniformVectors); + gl->fGetIntegerv(LOCAL_GL_MAX_VARYING_VECTORS, &mGLMaxVaryingVectors); + } else { + gl->fGetIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &mGLMaxFragmentUniformVectors); + mGLMaxFragmentUniformVectors /= 4; + gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_UNIFORM_COMPONENTS, &mGLMaxVertexUniformVectors); + mGLMaxVertexUniformVectors /= 4; + + // we are now going to try to read GL_MAX_VERTEX_OUTPUT_COMPONENTS and GL_MAX_FRAGMENT_INPUT_COMPONENTS, + // however these constants only entered the OpenGL standard at OpenGL 3.2. So we will try reading, + // and check OpenGL error for INVALID_ENUM. + + // before we start, we check that no error already occurred, to prevent hiding it in our subsequent error handling + error = gl->GetAndClearError(); + if (error != LOCAL_GL_NO_ERROR) { + GenerateWarning("GL error 0x%x occurred during WebGL context initialization!", error); + return false; + } + + // On the public_webgl list, "problematic GetParameter pnames" thread, the following formula was given: + // mGLMaxVaryingVectors = min (GL_MAX_VERTEX_OUTPUT_COMPONENTS, GL_MAX_FRAGMENT_INPUT_COMPONENTS) / 4 + GLint maxVertexOutputComponents, + minFragmentInputComponents; + gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_OUTPUT_COMPONENTS, &maxVertexOutputComponents); + gl->fGetIntegerv(LOCAL_GL_MAX_FRAGMENT_INPUT_COMPONENTS, &minFragmentInputComponents); + + error = gl->GetAndClearError(); + switch (error) { + case LOCAL_GL_NO_ERROR: + mGLMaxVaryingVectors = std::min(maxVertexOutputComponents, minFragmentInputComponents) / 4; + break; + case LOCAL_GL_INVALID_ENUM: + mGLMaxVaryingVectors = 16; // = 64/4, 64 is the min value for maxVertexOutputComponents in OpenGL 3.2 spec + break; + default: + GenerateWarning("GL error 0x%x occurred during WebGL context initialization!", error); + return false; + } + } + } + + // Always 1 for GLES2 + mMaxFramebufferColorAttachments = 1; + + if (!gl->IsGLES()) { + // gl_PointSize is always available in ES2 GLSL, but has to be + // specifically enabled on desktop GLSL. + gl->fEnable(LOCAL_GL_VERTEX_PROGRAM_POINT_SIZE); + + // gl_PointCoord is always available in ES2 GLSL and in newer desktop GLSL versions, but apparently + // not in OpenGL 2 and apparently not (due to a driver bug) on certain NVIDIA setups. See: + // http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=261472 + // Note that this used to cause crashes on old ATI drivers... hopefully not a significant + // problem anymore. See bug 602183. + gl->fEnable(LOCAL_GL_POINT_SPRITE); + } + +#ifdef XP_MACOSX + if (gl->WorkAroundDriverBugs() && + gl->Vendor() == gl::GLVendor::ATI) { + // The Mac ATI driver, in all known OSX version up to and including 10.8, + // renders points sprites upside-down. Apple bug 11778921 + gl->fPointParameterf(LOCAL_GL_POINT_SPRITE_COORD_ORIGIN, LOCAL_GL_LOWER_LEFT); + } +#endif + + // Check the shader validator pref + NS_ENSURE_TRUE(Preferences::GetRootBranch(), false); + + mShaderValidation = + Preferences::GetBool("webgl.shader_validator", mShaderValidation); + + // initialize shader translator + if (mShaderValidation) { + if (!ShInitialize()) { + GenerateWarning("GLSL translator initialization failed!"); + return false; + } + } + + // Mesa can only be detected with the GL_VERSION string, of the form "2.1 Mesa 7.11.0" + mIsMesa = strstr((const char *)(gl->fGetString(LOCAL_GL_VERSION)), "Mesa"); + + // notice that the point of calling GetAndClearError here is not only to check for error, + // it is also to reset the error flags so that a subsequent WebGL getError call will give the correct result. + error = gl->GetAndClearError(); + if (error != LOCAL_GL_NO_ERROR) { + GenerateWarning("GL error 0x%x occurred during WebGL context initialization!", error); + return false; + } + + if (IsWebGL2() && + !InitWebGL2()) + { + // Todo: Bug 898404: Only allow WebGL2 on GL>=3.0 on desktop GL. + return false; + } + + mMemoryPressureObserver + = new WebGLMemoryPressureObserver(this); + nsCOMPtr observerService + = mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(mMemoryPressureObserver, + "memory-pressure", + false); + } + + mDefaultVertexArray = new WebGLVertexArray(this); + mDefaultVertexArray->mAttribs.SetLength(mGLMaxVertexAttribs); + mBoundVertexArray = mDefaultVertexArray; + + return true; +}