blob: 8951a5995ecbeb81057a70626eb65969de7cf6d8 [file] [log] [blame]
//
// Copyright 2013 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// validationES2.cpp: Validation functions for OpenGL ES 2.0 entry point parameters
#include "libANGLE/validationES2_autogen.h"
#include <cstdint>
#include "common/mathutil.h"
#include "common/string_utils.h"
#include "common/utilities.h"
#include "libANGLE/Context.h"
#include "libANGLE/ErrorStrings.h"
#include "libANGLE/Fence.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/FramebufferAttachment.h"
#include "libANGLE/Renderbuffer.h"
#include "libANGLE/Shader.h"
#include "libANGLE/Texture.h"
#include "libANGLE/Uniform.h"
#include "libANGLE/VertexArray.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/validationES.h"
#include "libANGLE/validationES2.h"
#include "libANGLE/validationES3_autogen.h"
namespace gl
{
using namespace err;
namespace
{
bool IsPartialBlit(gl::Context *context,
const FramebufferAttachment *readBuffer,
const FramebufferAttachment *writeBuffer,
GLint srcX0,
GLint srcY0,
GLint srcX1,
GLint srcY1,
GLint dstX0,
GLint dstY0,
GLint dstX1,
GLint dstY1)
{
const Extents &writeSize = writeBuffer->getSize();
const Extents &readSize = readBuffer->getSize();
if (srcX0 != 0 || srcY0 != 0 || dstX0 != 0 || dstY0 != 0 || dstX1 != writeSize.width ||
dstY1 != writeSize.height || srcX1 != readSize.width || srcY1 != readSize.height)
{
return true;
}
if (context->getState().isScissorTestEnabled())
{
const Rectangle &scissor = context->getState().getScissor();
return scissor.x > 0 || scissor.y > 0 || scissor.width < writeSize.width ||
scissor.height < writeSize.height;
}
return false;
}
template <typename T>
bool ValidatePathInstances(gl::Context *context,
GLsizei numPaths,
const void *paths,
PathID pathBase)
{
const auto *array = static_cast<const T *>(paths);
for (GLsizei i = 0; i < numPaths; ++i)
{
const GLuint pathName = array[i] + pathBase.value;
if (context->isPathGenerated({pathName}) && !context->isPath({pathName}))
{
context->validationError(GL_INVALID_OPERATION, kNoSuchPath);
return false;
}
}
return true;
}
bool ValidateInstancedPathParameters(gl::Context *context,
GLsizei numPaths,
GLenum pathNameType,
const void *paths,
PathID pathBase,
GLenum transformType,
const GLfloat *transformValues)
{
if (!context->getExtensions().pathRendering)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (paths == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidPathNameArray);
return false;
}
if (numPaths < 0)
{
context->validationError(GL_INVALID_VALUE, kInvalidPathNumPaths);
return false;
}
if (!angle::IsValueInRangeForNumericType<std::uint32_t>(numPaths))
{
context->validationError(GL_INVALID_OPERATION, kIntegerOverflow);
return false;
}
std::uint32_t pathNameTypeSize = 0;
std::uint32_t componentCount = 0;
switch (pathNameType)
{
case GL_UNSIGNED_BYTE:
pathNameTypeSize = sizeof(GLubyte);
if (!ValidatePathInstances<GLubyte>(context, numPaths, paths, pathBase))
return false;
break;
case GL_BYTE:
pathNameTypeSize = sizeof(GLbyte);
if (!ValidatePathInstances<GLbyte>(context, numPaths, paths, pathBase))
return false;
break;
case GL_UNSIGNED_SHORT:
pathNameTypeSize = sizeof(GLushort);
if (!ValidatePathInstances<GLushort>(context, numPaths, paths, pathBase))
return false;
break;
case GL_SHORT:
pathNameTypeSize = sizeof(GLshort);
if (!ValidatePathInstances<GLshort>(context, numPaths, paths, pathBase))
return false;
break;
case GL_UNSIGNED_INT:
pathNameTypeSize = sizeof(GLuint);
if (!ValidatePathInstances<GLuint>(context, numPaths, paths, pathBase))
return false;
break;
case GL_INT:
pathNameTypeSize = sizeof(GLint);
if (!ValidatePathInstances<GLint>(context, numPaths, paths, pathBase))
return false;
break;
default:
context->validationError(GL_INVALID_ENUM, kInvalidPathNameType);
return false;
}
switch (transformType)
{
case GL_NONE:
componentCount = 0;
break;
case GL_TRANSLATE_X_CHROMIUM:
case GL_TRANSLATE_Y_CHROMIUM:
componentCount = 1;
break;
case GL_TRANSLATE_2D_CHROMIUM:
componentCount = 2;
break;
case GL_TRANSLATE_3D_CHROMIUM:
componentCount = 3;
break;
case GL_AFFINE_2D_CHROMIUM:
case GL_TRANSPOSE_AFFINE_2D_CHROMIUM:
componentCount = 6;
break;
case GL_AFFINE_3D_CHROMIUM:
case GL_TRANSPOSE_AFFINE_3D_CHROMIUM:
componentCount = 12;
break;
default:
context->validationError(GL_INVALID_ENUM, kInvalidTransformation);
return false;
}
if (componentCount != 0 && transformValues == nullptr)
{
context->validationError(GL_INVALID_VALUE, kNoTransformArray);
return false;
}
angle::CheckedNumeric<std::uint32_t> checkedSize(0);
checkedSize += (numPaths * pathNameTypeSize);
checkedSize += (numPaths * sizeof(GLfloat) * componentCount);
if (!checkedSize.IsValid())
{
context->validationError(GL_INVALID_OPERATION, kIntegerOverflow);
return false;
}
return true;
}
bool IsValidCopyTextureSourceInternalFormatEnum(GLenum internalFormat)
{
// Table 1.1 from the CHROMIUM_copy_texture spec
switch (GetUnsizedFormat(internalFormat))
{
case GL_RED:
case GL_ALPHA:
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA:
case GL_RGB:
case GL_RGBA:
case GL_RGB8:
case GL_RGBA8:
case GL_BGRA_EXT:
case GL_BGRA8_EXT:
return true;
default:
return false;
}
}
bool IsValidCopySubTextureSourceInternalFormat(GLenum internalFormat)
{
return IsValidCopyTextureSourceInternalFormatEnum(internalFormat);
}
bool IsValidCopyTextureDestinationInternalFormatEnum(GLint internalFormat)
{
// Table 1.0 from the CHROMIUM_copy_texture spec
switch (internalFormat)
{
case GL_RGB:
case GL_RGBA:
case GL_RGB8:
case GL_RGBA8:
case GL_BGRA_EXT:
case GL_BGRA8_EXT:
case GL_SRGB_EXT:
case GL_SRGB_ALPHA_EXT:
case GL_R8:
case GL_R8UI:
case GL_RG8:
case GL_RG8UI:
case GL_SRGB8:
case GL_RGB565:
case GL_RGB8UI:
case GL_RGB10_A2:
case GL_SRGB8_ALPHA8:
case GL_RGB5_A1:
case GL_RGBA4:
case GL_RGBA8UI:
case GL_RGB9_E5:
case GL_R16F:
case GL_R32F:
case GL_RG16F:
case GL_RG32F:
case GL_RGB16F:
case GL_RGB32F:
case GL_RGBA16F:
case GL_RGBA32F:
case GL_R11F_G11F_B10F:
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA:
case GL_ALPHA:
return true;
default:
return false;
}
}
bool IsValidCopySubTextureDestionationInternalFormat(GLenum internalFormat)
{
return IsValidCopyTextureDestinationInternalFormatEnum(internalFormat);
}
bool IsValidCopyTextureDestinationFormatType(Context *context, GLint internalFormat, GLenum type)
{
if (!IsValidCopyTextureDestinationInternalFormatEnum(internalFormat))
{
context->validationError(GL_INVALID_OPERATION, kInvalidInternalFormat);
return false;
}
if (!ValidES3FormatCombination(GetUnsizedFormat(internalFormat), type, internalFormat))
{
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
const InternalFormat &internalFormatInfo = GetInternalFormatInfo(internalFormat, type);
if (!internalFormatInfo.textureSupport(context->getClientVersion(), context->getExtensions()))
{
context->validationError(GL_INVALID_OPERATION, kInvalidInternalFormat);
return false;
}
return true;
}
bool IsValidCopyTextureDestinationTargetEnum(Context *context, TextureTarget target)
{
switch (target)
{
case TextureTarget::_2D:
case TextureTarget::CubeMapNegativeX:
case TextureTarget::CubeMapNegativeY:
case TextureTarget::CubeMapNegativeZ:
case TextureTarget::CubeMapPositiveX:
case TextureTarget::CubeMapPositiveY:
case TextureTarget::CubeMapPositiveZ:
return true;
case TextureTarget::Rectangle:
return context->getExtensions().textureRectangle;
default:
return false;
}
}
bool IsValidCopyTextureDestinationTarget(Context *context,
TextureType textureType,
TextureTarget target)
{
return TextureTargetToType(target) == textureType;
}
bool IsValidCopyTextureSourceTarget(Context *context, TextureType type)
{
switch (type)
{
case TextureType::_2D:
return true;
case TextureType::Rectangle:
return context->getExtensions().textureRectangle;
case TextureType::External:
return context->getExtensions().eglImageExternal;
default:
return false;
}
}
bool IsValidCopyTextureSourceLevel(Context *context, TextureType type, GLint level)
{
if (!ValidMipLevel(context, type, level))
{
return false;
}
if (level > 0 && context->getClientVersion() < ES_3_0)
{
return false;
}
return true;
}
bool IsValidCopyTextureDestinationLevel(Context *context,
TextureType type,
GLint level,
GLsizei width,
GLsizei height,
bool isSubImage)
{
if (!ValidMipLevel(context, type, level))
{
return false;
}
if (!ValidImageSizeParameters(context, type, level, width, height, 1, isSubImage))
{
return false;
}
const Caps &caps = context->getCaps();
switch (type)
{
case TextureType::_2D:
return static_cast<GLuint>(width) <= (caps.max2DTextureSize >> level) &&
static_cast<GLuint>(height) <= (caps.max2DTextureSize >> level);
case TextureType::Rectangle:
ASSERT(level == 0);
return static_cast<GLuint>(width) <= (caps.max2DTextureSize >> level) &&
static_cast<GLuint>(height) <= (caps.max2DTextureSize >> level);
case TextureType::CubeMap:
return static_cast<GLuint>(width) <= (caps.maxCubeMapTextureSize >> level) &&
static_cast<GLuint>(height) <= (caps.maxCubeMapTextureSize >> level);
default:
return true;
}
}
bool IsValidStencilFunc(GLenum func)
{
switch (func)
{
case GL_NEVER:
case GL_ALWAYS:
case GL_LESS:
case GL_LEQUAL:
case GL_EQUAL:
case GL_GEQUAL:
case GL_GREATER:
case GL_NOTEQUAL:
return true;
default:
return false;
}
}
bool IsValidStencilFace(GLenum face)
{
switch (face)
{
case GL_FRONT:
case GL_BACK:
case GL_FRONT_AND_BACK:
return true;
default:
return false;
}
}
bool IsValidStencilOp(GLenum op)
{
switch (op)
{
case GL_ZERO:
case GL_KEEP:
case GL_REPLACE:
case GL_INCR:
case GL_DECR:
case GL_INVERT:
case GL_INCR_WRAP:
case GL_DECR_WRAP:
return true;
default:
return false;
}
}
bool ValidateES2CopyTexImageParameters(Context *context,
TextureTarget target,
GLint level,
GLenum internalformat,
bool isSubImage,
GLint xoffset,
GLint yoffset,
GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLint border)
{
if (!ValidTexture2DDestinationTarget(context, target))
{
context->validationError(GL_INVALID_ENUM, kInvalidTextureTarget);
return false;
}
TextureType texType = TextureTargetToType(target);
if (!ValidImageSizeParameters(context, texType, level, width, height, 1, isSubImage))
{
// Error is already handled.
return false;
}
Format textureFormat = Format::Invalid();
if (!ValidateCopyTexImageParametersBase(context, target, level, internalformat, isSubImage,
xoffset, yoffset, 0, x, y, width, height, border,
&textureFormat))
{
return false;
}
const gl::Framebuffer *framebuffer = context->getState().getReadFramebuffer();
GLenum colorbufferFormat =
framebuffer->getReadColorAttachment()->getFormat().info->sizedInternalFormat;
const auto &formatInfo = *textureFormat.info;
// [OpenGL ES 2.0.24] table 3.9
if (isSubImage)
{
switch (formatInfo.format)
{
case GL_ALPHA:
if (colorbufferFormat != GL_ALPHA8_EXT && colorbufferFormat != GL_RGBA4 &&
colorbufferFormat != GL_RGB5_A1 && colorbufferFormat != GL_RGBA8_OES &&
colorbufferFormat != GL_BGRA8_EXT && colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_LUMINANCE:
if (colorbufferFormat != GL_R8_EXT && colorbufferFormat != GL_RG8_EXT &&
colorbufferFormat != GL_RGB565 && colorbufferFormat != GL_RGB8_OES &&
colorbufferFormat != GL_RGBA4 && colorbufferFormat != GL_RGB5_A1 &&
colorbufferFormat != GL_RGBA8_OES && colorbufferFormat != GL_BGRA8_EXT &&
colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_RED_EXT:
if (colorbufferFormat != GL_R8_EXT && colorbufferFormat != GL_RG8_EXT &&
colorbufferFormat != GL_RGB565 && colorbufferFormat != GL_RGB8_OES &&
colorbufferFormat != GL_RGBA4 && colorbufferFormat != GL_RGB5_A1 &&
colorbufferFormat != GL_RGBA8_OES && colorbufferFormat != GL_R32F &&
colorbufferFormat != GL_RG32F && colorbufferFormat != GL_RGB32F &&
colorbufferFormat != GL_RGBA32F && colorbufferFormat != GL_BGRA8_EXT &&
colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_RG_EXT:
if (colorbufferFormat != GL_RG8_EXT && colorbufferFormat != GL_RGB565 &&
colorbufferFormat != GL_RGB8_OES && colorbufferFormat != GL_RGBA4 &&
colorbufferFormat != GL_RGB5_A1 && colorbufferFormat != GL_RGBA8_OES &&
colorbufferFormat != GL_RG32F && colorbufferFormat != GL_RGB32F &&
colorbufferFormat != GL_RGBA32F && colorbufferFormat != GL_BGRA8_EXT &&
colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_RGB:
if (colorbufferFormat != GL_RGB565 && colorbufferFormat != GL_RGB8_OES &&
colorbufferFormat != GL_RGBA4 && colorbufferFormat != GL_RGB5_A1 &&
colorbufferFormat != GL_RGBA8_OES && colorbufferFormat != GL_RGB32F &&
colorbufferFormat != GL_RGBA32F && colorbufferFormat != GL_BGRA8_EXT &&
colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_LUMINANCE_ALPHA:
case GL_RGBA:
if (colorbufferFormat != GL_RGBA4 && colorbufferFormat != GL_RGB5_A1 &&
colorbufferFormat != GL_RGBA8_OES && colorbufferFormat != GL_RGBA32F &&
colorbufferFormat != GL_BGRA8_EXT && colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE:
case GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE:
case GL_ETC1_RGB8_OES:
case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE:
case GL_COMPRESSED_RGB8_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_SRGB8_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_RGBA_BPTC_UNORM_EXT:
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT:
case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT:
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT:
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
case GL_DEPTH_COMPONENT:
case GL_DEPTH_STENCIL_OES:
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
default:
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
if (formatInfo.type == GL_FLOAT && !context->getExtensions().textureFloat)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
}
else
{
switch (internalformat)
{
case GL_ALPHA:
if (colorbufferFormat != GL_ALPHA8_EXT && colorbufferFormat != GL_RGBA4 &&
colorbufferFormat != GL_RGB5_A1 && colorbufferFormat != GL_BGRA8_EXT &&
colorbufferFormat != GL_RGBA8_OES && colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_LUMINANCE:
if (colorbufferFormat != GL_R8_EXT && colorbufferFormat != GL_RG8_EXT &&
colorbufferFormat != GL_RGB565 && colorbufferFormat != GL_RGB8_OES &&
colorbufferFormat != GL_RGBA4 && colorbufferFormat != GL_RGB5_A1 &&
colorbufferFormat != GL_BGRA8_EXT && colorbufferFormat != GL_RGBA8_OES &&
colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_RED_EXT:
if (colorbufferFormat != GL_R8_EXT && colorbufferFormat != GL_RG8_EXT &&
colorbufferFormat != GL_RGB565 && colorbufferFormat != GL_RGB8_OES &&
colorbufferFormat != GL_RGBA4 && colorbufferFormat != GL_RGB5_A1 &&
colorbufferFormat != GL_BGRA8_EXT && colorbufferFormat != GL_RGBA8_OES &&
colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_RG_EXT:
if (colorbufferFormat != GL_RG8_EXT && colorbufferFormat != GL_RGB565 &&
colorbufferFormat != GL_RGB8_OES && colorbufferFormat != GL_RGBA4 &&
colorbufferFormat != GL_RGB5_A1 && colorbufferFormat != GL_BGRA8_EXT &&
colorbufferFormat != GL_RGBA8_OES && colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_RGB:
if (colorbufferFormat != GL_RGB565 && colorbufferFormat != GL_RGB8_OES &&
colorbufferFormat != GL_RGBA4 && colorbufferFormat != GL_RGB5_A1 &&
colorbufferFormat != GL_BGRA8_EXT && colorbufferFormat != GL_RGBA8_OES &&
colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_LUMINANCE_ALPHA:
case GL_RGBA:
if (colorbufferFormat != GL_RGBA4 && colorbufferFormat != GL_RGB5_A1 &&
colorbufferFormat != GL_BGRA8_EXT && colorbufferFormat != GL_RGBA8_OES &&
colorbufferFormat != GL_BGR5_A1_ANGLEX)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
break;
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
if (context->getExtensions().textureCompressionDXT1)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE:
if (context->getExtensions().textureCompressionDXT3)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE:
if (context->getExtensions().textureCompressionDXT5)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_ETC1_RGB8_OES:
if (context->getExtensions().compressedETC1RGB8Texture)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE:
case GL_COMPRESSED_RGB8_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_SRGB8_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE:
if (context->getExtensions().lossyETCDecode)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
if (context->getExtensions().compressedTexturePVRTC)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:
case GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT:
if (context->getExtensions().compressedTexturePVRTCsRGB)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_DEPTH_COMPONENT:
case GL_DEPTH_COMPONENT16:
case GL_DEPTH_COMPONENT32_OES:
if (context->getExtensions().depthTextureAny())
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_DEPTH_STENCIL_OES:
case GL_DEPTH24_STENCIL8_OES:
if (context->getExtensions().depthTextureAny() ||
context->getExtensions().packedDepthStencil)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
default:
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
}
// If width or height is zero, it is a no-op. Return false without setting an error.
return (width > 0 && height > 0);
}
bool ValidCap(const Context *context, GLenum cap, bool queryOnly)
{
switch (cap)
{
// EXT_multisample_compatibility
case GL_MULTISAMPLE_EXT:
case GL_SAMPLE_ALPHA_TO_ONE_EXT:
return context->getExtensions().multisampleCompatibility;
case GL_CULL_FACE:
case GL_POLYGON_OFFSET_FILL:
case GL_SAMPLE_ALPHA_TO_COVERAGE:
case GL_SAMPLE_COVERAGE:
case GL_SCISSOR_TEST:
case GL_STENCIL_TEST:
case GL_DEPTH_TEST:
case GL_BLEND:
case GL_DITHER:
return true;
case GL_PRIMITIVE_RESTART_FIXED_INDEX:
case GL_RASTERIZER_DISCARD:
return (context->getClientMajorVersion() >= 3);
case GL_DEBUG_OUTPUT_SYNCHRONOUS:
case GL_DEBUG_OUTPUT:
return context->getExtensions().debug;
case GL_BIND_GENERATES_RESOURCE_CHROMIUM:
return queryOnly && context->getExtensions().bindGeneratesResource;
case GL_CLIENT_ARRAYS_ANGLE:
return queryOnly && context->getExtensions().clientArrays;
case GL_FRAMEBUFFER_SRGB_EXT:
return context->getExtensions().sRGBWriteControl;
case GL_SAMPLE_MASK:
return context->getClientVersion() >= Version(3, 1);
case GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE:
return queryOnly && context->getExtensions().robustResourceInitialization;
// GLES1 emulation: GLES1-specific caps
case GL_ALPHA_TEST:
case GL_VERTEX_ARRAY:
case GL_NORMAL_ARRAY:
case GL_COLOR_ARRAY:
case GL_TEXTURE_COORD_ARRAY:
case GL_TEXTURE_2D:
case GL_LIGHTING:
case GL_LIGHT0:
case GL_LIGHT1:
case GL_LIGHT2:
case GL_LIGHT3:
case GL_LIGHT4:
case GL_LIGHT5:
case GL_LIGHT6:
case GL_LIGHT7:
case GL_NORMALIZE:
case GL_RESCALE_NORMAL:
case GL_COLOR_MATERIAL:
case GL_CLIP_PLANE0:
case GL_CLIP_PLANE1:
case GL_CLIP_PLANE2:
case GL_CLIP_PLANE3:
case GL_CLIP_PLANE4:
case GL_CLIP_PLANE5:
case GL_FOG:
case GL_POINT_SMOOTH:
case GL_LINE_SMOOTH:
case GL_COLOR_LOGIC_OP:
return context->getClientVersion() < Version(2, 0);
case GL_POINT_SIZE_ARRAY_OES:
return context->getClientVersion() < Version(2, 0) &&
context->getExtensions().pointSizeArray;
case GL_TEXTURE_CUBE_MAP:
return context->getClientVersion() < Version(2, 0) &&
context->getExtensions().textureCubeMap;
case GL_POINT_SPRITE_OES:
return context->getClientVersion() < Version(2, 0) &&
context->getExtensions().pointSprite;
default:
return false;
}
}
// Return true if a character belongs to the ASCII subset as defined in GLSL ES 1.0 spec section
// 3.1.
bool IsValidESSLCharacter(unsigned char c)
{
// Printing characters are valid except " $ ` @ \ ' DEL.
if (c >= 32 && c <= 126 && c != '"' && c != '$' && c != '`' && c != '@' && c != '\\' &&
c != '\'')
{
return true;
}
// Horizontal tab, line feed, vertical tab, form feed, carriage return are also valid.
if (c >= 9 && c <= 13)
{
return true;
}
return false;
}
bool IsValidESSLString(const char *str, size_t len)
{
for (size_t i = 0; i < len; i++)
{
if (!IsValidESSLCharacter(str[i]))
{
return false;
}
}
return true;
}
bool IsValidESSLShaderSourceString(const char *str, size_t len, bool lineContinuationAllowed)
{
enum class ParseState
{
// Have not seen an ASCII non-whitespace character yet on
// this line. Possible that we might see a preprocessor
// directive.
BEGINING_OF_LINE,
// Have seen at least one ASCII non-whitespace character
// on this line.
MIDDLE_OF_LINE,
// Handling a preprocessor directive. Passes through all
// characters up to the end of the line. Disables comment
// processing.
IN_PREPROCESSOR_DIRECTIVE,
// Handling a single-line comment. The comment text is
// replaced with a single space.
IN_SINGLE_LINE_COMMENT,
// Handling a multi-line comment. Newlines are passed
// through to preserve line numbers.
IN_MULTI_LINE_COMMENT
};
ParseState state = ParseState::BEGINING_OF_LINE;
size_t pos = 0;
while (pos < len)
{
char c = str[pos];
char next = pos + 1 < len ? str[pos + 1] : 0;
// Check for newlines
if (c == '\n' || c == '\r')
{
if (state != ParseState::IN_MULTI_LINE_COMMENT)
{
state = ParseState::BEGINING_OF_LINE;
}
pos++;
continue;
}
switch (state)
{
case ParseState::BEGINING_OF_LINE:
if (c == ' ')
{
// Maintain the BEGINING_OF_LINE state until a non-space is seen
pos++;
}
else if (c == '#')
{
state = ParseState::IN_PREPROCESSOR_DIRECTIVE;
pos++;
}
else
{
// Don't advance, re-process this character with the MIDDLE_OF_LINE state
state = ParseState::MIDDLE_OF_LINE;
}
break;
case ParseState::MIDDLE_OF_LINE:
if (c == '/' && next == '/')
{
state = ParseState::IN_SINGLE_LINE_COMMENT;
pos++;
}
else if (c == '/' && next == '*')
{
state = ParseState::IN_MULTI_LINE_COMMENT;
pos++;
}
else if (lineContinuationAllowed && c == '\\' && (next == '\n' || next == '\r'))
{
// Skip line continuation characters
}
else if (!IsValidESSLCharacter(c))
{
return false;
}
pos++;
break;
case ParseState::IN_PREPROCESSOR_DIRECTIVE:
// Line-continuation characters may not be permitted.
// Otherwise, just pass it through. Do not parse comments in this state.
if (!lineContinuationAllowed && c == '\\')
{
return false;
}
pos++;
break;
case ParseState::IN_SINGLE_LINE_COMMENT:
// Line-continuation characters are processed before comment processing.
// Advance string if a new line character is immediately behind
// line-continuation character.
if (c == '\\' && (next == '\n' || next == '\r'))
{
pos++;
}
pos++;
break;
case ParseState::IN_MULTI_LINE_COMMENT:
if (c == '*' && next == '/')
{
state = ParseState::MIDDLE_OF_LINE;
pos++;
}
pos++;
break;
}
}
return true;
}
bool ValidateWebGLNamePrefix(Context *context, const GLchar *name)
{
ASSERT(context->isWebGL());
// WebGL 1.0 [Section 6.16] GLSL Constructs
// Identifiers starting with "webgl_" and "_webgl_" are reserved for use by WebGL.
if (strncmp(name, "webgl_", 6) == 0 || strncmp(name, "_webgl_", 7) == 0)
{
context->validationError(GL_INVALID_OPERATION, kWebglBindAttribLocationReservedPrefix);
return false;
}
return true;
}
bool ValidateWebGLNameLength(Context *context, size_t length)
{
ASSERT(context->isWebGL());
if (context->isWebGL1() && length > 256)
{
// WebGL 1.0 [Section 6.21] Maxmimum Uniform and Attribute Location Lengths
// WebGL imposes a limit of 256 characters on the lengths of uniform and attribute
// locations.
context->validationError(GL_INVALID_VALUE, kWebglNameLengthLimitExceeded);
return false;
}
else if (length > 1024)
{
// WebGL 2.0 [Section 4.3.2] WebGL 2.0 imposes a limit of 1024 characters on the lengths of
// uniform and attribute locations.
context->validationError(GL_INVALID_VALUE, kWebgl2NameLengthLimitExceeded);
return false;
}
return true;
}
bool ValidateMatrixMode(Context *context, GLenum matrixMode)
{
if (!context->getExtensions().pathRendering)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (matrixMode != GL_PATH_MODELVIEW_CHROMIUM && matrixMode != GL_PATH_PROJECTION_CHROMIUM)
{
context->validationError(GL_INVALID_ENUM, kInvalidMatrixMode);
return false;
}
return true;
}
bool ValidBlendFunc(const Context *context, GLenum val)
{
const gl::Extensions &ext = context->getExtensions();
// these are always valid for src and dst.
switch (val)
{
case GL_ZERO:
case GL_ONE:
case GL_SRC_COLOR:
case GL_ONE_MINUS_SRC_COLOR:
case GL_DST_COLOR:
case GL_ONE_MINUS_DST_COLOR:
case GL_SRC_ALPHA:
case GL_ONE_MINUS_SRC_ALPHA:
case GL_DST_ALPHA:
case GL_ONE_MINUS_DST_ALPHA:
case GL_CONSTANT_COLOR:
case GL_ONE_MINUS_CONSTANT_COLOR:
case GL_CONSTANT_ALPHA:
case GL_ONE_MINUS_CONSTANT_ALPHA:
return true;
// EXT_blend_func_extended.
case GL_SRC1_COLOR_EXT:
case GL_SRC1_ALPHA_EXT:
case GL_ONE_MINUS_SRC1_COLOR_EXT:
case GL_ONE_MINUS_SRC1_ALPHA_EXT:
case GL_SRC_ALPHA_SATURATE_EXT:
return ext.blendFuncExtended;
default:
return false;
}
}
bool ValidSrcBlendFunc(const Context *context, GLenum val)
{
if (ValidBlendFunc(context, val))
return true;
if (val == GL_SRC_ALPHA_SATURATE)
return true;
return false;
}
bool ValidDstBlendFunc(const Context *context, GLenum val)
{
if (ValidBlendFunc(context, val))
return true;
if (val == GL_SRC_ALPHA_SATURATE)
{
if (context->getClientMajorVersion() >= 3)
return true;
}
return false;
}
bool IsValidImageLayout(ImageLayout layout)
{
switch (layout)
{
case ImageLayout::Undefined:
case ImageLayout::General:
case ImageLayout::ColorAttachment:
case ImageLayout::DepthStencilAttachment:
case ImageLayout::DepthStencilReadOnlyAttachment:
case ImageLayout::ShaderReadOnly:
case ImageLayout::TransferSrc:
case ImageLayout::TransferDst:
case ImageLayout::DepthReadOnlyStencilAttachment:
case ImageLayout::DepthAttachmentStencilReadOnly:
return true;
default:
return false;
}
}
bool ValidateES2TexImageParameters(Context *context,
TextureTarget target,
GLint level,
GLenum internalformat,
bool isCompressed,
bool isSubImage,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
GLsizei imageSize,
const void *pixels)
{
if (!ValidTexture2DDestinationTarget(context, target))
{
context->validationError(GL_INVALID_ENUM, kInvalidTextureTarget);
return false;
}
return ValidateES2TexImageParametersBase(context, target, level, internalformat, isCompressed,
isSubImage, xoffset, yoffset, width, height, border,
format, type, imageSize, pixels);
}
} // anonymous namespace
bool ValidateES2TexImageParametersBase(Context *context,
TextureTarget target,
GLint level,
GLenum internalformat,
bool isCompressed,
bool isSubImage,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
GLsizei imageSize,
const void *pixels)
{
TextureType texType = TextureTargetToType(target);
if (!ValidImageSizeParameters(context, texType, level, width, height, 1, isSubImage))
{
// Error already handled.
return false;
}
if (!ValidMipLevel(context, texType, level))
{
context->validationError(GL_INVALID_VALUE, kInvalidMipLevel);
return false;
}
if (xoffset < 0 || std::numeric_limits<GLsizei>::max() - xoffset < width ||
std::numeric_limits<GLsizei>::max() - yoffset < height)
{
context->validationError(GL_INVALID_VALUE, kResourceMaxTextureSize);
return false;
}
const gl::Caps &caps = context->getCaps();
switch (texType)
{
case TextureType::_2D:
case TextureType::External:
if (static_cast<GLuint>(width) > (caps.max2DTextureSize >> level) ||
static_cast<GLuint>(height) > (caps.max2DTextureSize >> level))
{
context->validationError(GL_INVALID_VALUE, kResourceMaxTextureSize);
return false;
}
break;
case TextureType::Rectangle:
ASSERT(level == 0);
if (static_cast<GLuint>(width) > caps.maxRectangleTextureSize ||
static_cast<GLuint>(height) > caps.maxRectangleTextureSize)
{
context->validationError(GL_INVALID_VALUE, kResourceMaxTextureSize);
return false;
}
if (isCompressed)
{
context->validationError(GL_INVALID_ENUM, kRectangleTextureCompressed);
return false;
}
break;
case TextureType::CubeMap:
if (!isSubImage && width != height)
{
context->validationError(GL_INVALID_VALUE, kCubemapFacesEqualDimensions);
return false;
}
if (static_cast<GLuint>(width) > (caps.maxCubeMapTextureSize >> level) ||
static_cast<GLuint>(height) > (caps.maxCubeMapTextureSize >> level))
{
context->validationError(GL_INVALID_VALUE, kResourceMaxTextureSize);
return false;
}
break;
default:
context->validationError(GL_INVALID_ENUM, kInvalidTextureTarget);
return false;
}
gl::Texture *texture = context->getTextureByType(texType);
if (!texture)
{
context->validationError(GL_INVALID_OPERATION, kBufferNotBound);
return false;
}
// Verify zero border
if (border != 0)
{
context->validationError(GL_INVALID_VALUE, kInvalidBorder);
return false;
}
bool nonEqualFormatsAllowed = false;
if (isCompressed)
{
GLenum actualInternalFormat =
isSubImage ? texture->getFormat(target, level).info->sizedInternalFormat
: internalformat;
const InternalFormat &internalFormatInfo = GetSizedInternalFormatInfo(actualInternalFormat);
if (!internalFormatInfo.compressed)
{
context->validationError(GL_INVALID_ENUM, kInvalidInternalFormat);
return false;
}
if (!internalFormatInfo.textureSupport(context->getClientVersion(),
context->getExtensions()))
{
context->validationError(GL_INVALID_ENUM, kInvalidInternalFormat);
return false;
}
if (isSubImage)
{
// From the OES_compressed_ETC1_RGB8_texture spec:
// INVALID_OPERATION is generated by CompressedTexSubImage2D, TexSubImage2D, or
// CopyTexSubImage2D if the texture image <level> bound to <target> has internal format
// ETC1_RGB8_OES.
if (actualInternalFormat == GL_ETC1_RGB8_OES)
{
context->validationError(GL_INVALID_OPERATION, kInvalidInternalFormat);
return false;
}
if (!ValidCompressedSubImageSize(context, actualInternalFormat, xoffset, yoffset, 0,
width, height, 1, texture->getWidth(target, level),
texture->getHeight(target, level),
texture->getDepth(target, level)))
{
context->validationError(GL_INVALID_OPERATION, kInvalidCompressedImageSize);
return false;
}
if (format != actualInternalFormat)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
}
else
{
if (!ValidCompressedImageSize(context, actualInternalFormat, level, width, height, 1))
{
context->validationError(GL_INVALID_OPERATION, kInvalidCompressedImageSize);
return false;
}
}
}
else
{
// validate <type> by itself (used as secondary key below)
switch (type)
{
case GL_UNSIGNED_BYTE:
case GL_UNSIGNED_SHORT_5_6_5:
case GL_UNSIGNED_SHORT_4_4_4_4:
case GL_UNSIGNED_SHORT_5_5_5_1:
case GL_UNSIGNED_SHORT:
case GL_UNSIGNED_INT:
case GL_UNSIGNED_INT_24_8_OES:
case GL_HALF_FLOAT_OES:
case GL_FLOAT:
break;
case GL_UNSIGNED_INT_2_10_10_10_REV_EXT:
if (!context->getExtensions().textureFormat2101010REV)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
default:
context->validationError(GL_INVALID_ENUM, kInvalidType);
return false;
}
// validate <format> + <type> combinations
// - invalid <format> -> sets INVALID_ENUM
// - invalid <format>+<type> combination -> sets INVALID_OPERATION
switch (format)
{
case GL_ALPHA:
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA:
switch (type)
{
case GL_UNSIGNED_BYTE:
case GL_FLOAT:
case GL_HALF_FLOAT_OES:
break;
default:
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_RED:
case GL_RG:
if (!context->getExtensions().textureRG)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
switch (type)
{
case GL_UNSIGNED_BYTE:
break;
case GL_FLOAT:
case GL_HALF_FLOAT_OES:
if (!context->getExtensions().textureFloat)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_SHORT:
case GL_UNSIGNED_SHORT:
if (!context->getExtensions().textureNorm16)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
default:
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_RGB:
switch (type)
{
case GL_UNSIGNED_BYTE:
case GL_UNSIGNED_SHORT_5_6_5:
case GL_UNSIGNED_INT_2_10_10_10_REV_EXT:
case GL_FLOAT:
case GL_HALF_FLOAT_OES:
break;
case GL_SHORT:
case GL_UNSIGNED_SHORT:
if (!context->getExtensions().textureNorm16)
{
context->validationError(GL_INVALID_OPERATION,
kMismatchedTypeAndFormat);
return false;
}
break;
default:
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_RGBA:
switch (type)
{
case GL_UNSIGNED_BYTE:
case GL_UNSIGNED_SHORT_4_4_4_4:
case GL_UNSIGNED_SHORT_5_5_5_1:
case GL_FLOAT:
case GL_HALF_FLOAT_OES:
case GL_UNSIGNED_INT_2_10_10_10_REV_EXT:
break;
case GL_SHORT:
case GL_UNSIGNED_SHORT:
if (!context->getExtensions().textureNorm16)
{
context->validationError(GL_INVALID_OPERATION,
kMismatchedTypeAndFormat);
return false;
}
break;
default:
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_BGRA_EXT:
if (!context->getExtensions().textureFormatBGRA8888)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
switch (type)
{
case GL_UNSIGNED_BYTE:
break;
default:
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_SRGB_EXT:
case GL_SRGB_ALPHA_EXT:
if (!context->getExtensions().sRGB)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
switch (type)
{
case GL_UNSIGNED_BYTE:
break;
default:
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: // error cases for compressed textures are
// handled below
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE:
case GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE:
break;
case GL_DEPTH_COMPONENT:
switch (type)
{
case GL_UNSIGNED_SHORT:
case GL_UNSIGNED_INT:
break;
default:
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_DEPTH_STENCIL_OES:
switch (type)
{
case GL_UNSIGNED_INT_24_8_OES:
break;
default:
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
default:
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
switch (format)
{
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
if (context->getExtensions().textureCompressionDXT1)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE:
if (context->getExtensions().textureCompressionDXT3)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE:
if (context->getExtensions().textureCompressionDXT5)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_ETC1_RGB8_OES:
if (context->getExtensions().compressedETC1RGB8Texture)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE:
case GL_COMPRESSED_RGB8_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_SRGB8_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE:
if (context->getExtensions().lossyETCDecode)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
if (context->getExtensions().compressedTexturePVRTC)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:
case GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT:
if (context->getExtensions().compressedTexturePVRTCsRGB)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormat);
return false;
}
else
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_DEPTH_COMPONENT:
case GL_DEPTH_STENCIL_OES:
if (!context->getExtensions().depthTextureANGLE &&
!(context->getExtensions().packedDepthStencil &&
context->getExtensions().depthTextureOES))
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
if (target != TextureTarget::_2D)
{
context->validationError(GL_INVALID_OPERATION, kMismatchedTargetAndFormat);
return false;
}
// OES_depth_texture supports loading depth data and multiple levels,
// but ANGLE_depth_texture does not
if (!context->getExtensions().depthTextureOES)
{
if (pixels != nullptr)
{
context->validationError(GL_INVALID_OPERATION, kPixelDataNotNull);
return false;
}
if (level != 0)
{
context->validationError(GL_INVALID_OPERATION, kLevelNotZero);
return false;
}
}
break;
default:
break;
}
if (!isSubImage)
{
switch (internalformat)
{
// Core ES 2.0 formats
case GL_ALPHA:
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA:
case GL_RGB:
case GL_RGBA:
break;
case GL_RGBA32F:
if (!context->getExtensions().colorBufferFloatRGBA)
{
context->validationError(GL_INVALID_ENUM, kInvalidFormat);
return false;
}
nonEqualFormatsAllowed = true;
if (type != GL_FLOAT)
{
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
if (format != GL_RGBA)
{
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_RGB32F:
if (!context->getExtensions().colorBufferFloatRGB)
{
context->validationError(GL_INVALID_ENUM, kInvalidFormat);
return false;
}
nonEqualFormatsAllowed = true;
if (type != GL_FLOAT)
{
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
if (format != GL_RGB)
{
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
break;
case GL_BGRA_EXT:
if (!context->getExtensions().textureFormatBGRA8888)
{
context->validationError(GL_INVALID_ENUM, kInvalidFormat);
return false;
}
break;
case GL_DEPTH_COMPONENT:
if (!(context->getExtensions().depthTextureAny()))
{
context->validationError(GL_INVALID_ENUM, kInvalidFormat);
return false;
}
break;
case GL_DEPTH_STENCIL:
if (!(context->getExtensions().depthTextureANGLE ||
context->getExtensions().packedDepthStencil))
{
context->validationError(GL_INVALID_ENUM, kInvalidFormat);
return false;
}
break;
case GL_RED:
case GL_RG:
if (!context->getExtensions().textureRG)
{
context->validationError(GL_INVALID_ENUM, kInvalidFormat);
return false;
}
break;
case GL_SRGB_EXT:
case GL_SRGB_ALPHA_EXT:
if (!context->getExtensions().sRGB)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_RGB10_A2_EXT:
if (!context->getExtensions().textureFormat2101010REV)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
if (type != GL_UNSIGNED_INT_2_10_10_10_REV_EXT || format != GL_RGBA)
{
context->validationError(GL_INVALID_OPERATION, kMismatchedTypeAndFormat);
return false;
}
nonEqualFormatsAllowed = true;
break;
case GL_RGB5_A1:
if (context->getExtensions().textureFormat2101010REV &&
type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT && format == GL_RGBA)
{
nonEqualFormatsAllowed = true;
}
break;
case GL_R16_EXT:
case GL_RG16_EXT:
case GL_RGB16_EXT:
case GL_RGBA16_EXT:
case GL_R16_SNORM_EXT:
case GL_RG16_SNORM_EXT:
case GL_RGB16_SNORM_EXT:
case GL_RGBA16_SNORM_EXT:
if (!context->getExtensions().textureNorm16)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
default:
context->validationError(GL_INVALID_VALUE, kInvalidInternalFormat);
return false;
}
}
if (type == GL_FLOAT)
{
if (!context->getExtensions().textureFloat)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
}
else if (type == GL_HALF_FLOAT_OES)
{
if (!context->getExtensions().textureHalfFloat)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
}
}
if (isSubImage)
{
const InternalFormat &textureInternalFormat = *texture->getFormat(target, level).info;
if (textureInternalFormat.internalFormat == GL_NONE)
{
context->validationError(GL_INVALID_OPERATION, kInvalidTextureLevel);
return false;
}
if (format != textureInternalFormat.format)
{
context->validationError(GL_INVALID_OPERATION, err::kTextureFormatMismatch);
return false;
}
if (context->getExtensions().webglCompatibility)
{
if (GetInternalFormatInfo(format, type).sizedInternalFormat !=
textureInternalFormat.sizedInternalFormat)
{
context->validationError(GL_INVALID_OPERATION, kTextureTypeMismatch);
return false;
}
}
if (static_cast<size_t>(xoffset + width) > texture->getWidth(target, level) ||
static_cast<size_t>(yoffset + height) > texture->getHeight(target, level))
{
context->validationError(GL_INVALID_VALUE, kOffsetOverflow);
return false;
}
if (width > 0 && height > 0 && pixels == nullptr &&
context->getState().getTargetBuffer(BufferBinding::PixelUnpack) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kPixelDataNull);
return false;
}
}
else
{
if (texture->getImmutableFormat())
{
context->validationError(GL_INVALID_OPERATION, kTextureIsImmutable);
return false;
}
}
// From GL_CHROMIUM_color_buffer_float_rgb[a]:
// GL_RGB[A] / GL_RGB[A]32F becomes an allowable format / internalformat parameter pair for
// TexImage2D. The restriction in section 3.7.1 of the OpenGL ES 2.0 spec that the
// internalformat parameter and format parameter of TexImage2D must match is lifted for this
// case.
if (!isSubImage && !isCompressed && internalformat != format && !nonEqualFormatsAllowed)
{
context->validationError(GL_INVALID_OPERATION, kInvalidFormatCombination);
return false;
}
GLenum sizeCheckFormat = isSubImage ? format : internalformat;
return ValidImageDataSize(context, texType, width, height, 1, sizeCheckFormat, type, pixels,
imageSize);
}
bool ValidateES2TexStorageParameters(Context *context,
TextureType target,
GLsizei levels,
GLenum internalformat,
GLsizei width,
GLsizei height)
{
if (target != TextureType::_2D && target != TextureType::CubeMap &&
target != TextureType::Rectangle)
{
context->validationError(GL_INVALID_ENUM, kInvalidTextureTarget);
return false;
}
if (width < 1 || height < 1 || levels < 1)
{
context->validationError(GL_INVALID_VALUE, kTextureSizeTooSmall);
return false;
}
if (target == TextureType::CubeMap && width != height)
{
context->validationError(GL_INVALID_VALUE, kCubemapFacesEqualDimensions);
return false;
}
if (levels != 1 && levels != gl::log2(std::max(width, height)) + 1)
{
context->validationError(GL_INVALID_OPERATION, kInvalidMipLevels);
return false;
}
const gl::InternalFormat &formatInfo = gl::GetSizedInternalFormatInfo(internalformat);
if (formatInfo.format == GL_NONE || formatInfo.type == GL_NONE)
{
context->validationError(GL_INVALID_ENUM, kInvalidFormat);
return false;
}
const gl::Caps &caps = context->getCaps();
switch (target)
{
case TextureType::_2D:
if (static_cast<GLuint>(width) > caps.max2DTextureSize ||
static_cast<GLuint>(height) > caps.max2DTextureSize)
{
context->validationError(GL_INVALID_VALUE, kResourceMaxTextureSize);
return false;
}
break;
case TextureType::Rectangle:
if (levels != 1)
{
context->validationError(GL_INVALID_VALUE, kInvalidMipLevel);
return false;
}
if (static_cast<GLuint>(width) > caps.maxRectangleTextureSize ||
static_cast<GLuint>(height) > caps.maxRectangleTextureSize)
{
context->validationError(GL_INVALID_VALUE, kResourceMaxTextureSize);
return false;
}
if (formatInfo.compressed)
{
context->validationError(GL_INVALID_ENUM, kRectangleTextureCompressed);
return false;
}
break;
case TextureType::CubeMap:
if (static_cast<GLuint>(width) > caps.maxCubeMapTextureSize ||
static_cast<GLuint>(height) > caps.maxCubeMapTextureSize)
{
context->validationError(GL_INVALID_VALUE, kResourceMaxTextureSize);
return false;
}
break;
default:
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
if (levels != 1 && !context->getExtensions().textureNPOT)
{
if (!gl::isPow2(width) || !gl::isPow2(height))
{
context->validationError(GL_INVALID_OPERATION, kDimensionsMustBePow2);
return false;
}
}
switch (internalformat)
{
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
if (!context->getExtensions().textureCompressionDXT1)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGBA_S3TC_DXT3_ANGLE:
if (!context->getExtensions().textureCompressionDXT3)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGBA_S3TC_DXT5_ANGLE:
if (!context->getExtensions().textureCompressionDXT5)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_ETC1_RGB8_OES:
if (!context->getExtensions().compressedETC1RGB8Texture)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_ETC1_RGB8_LOSSY_DECODE_ANGLE:
case GL_COMPRESSED_RGB8_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_SRGB8_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE:
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_LOSSY_DECODE_ETC2_ANGLE:
if (!context->getExtensions().lossyETCDecode)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
if (!context->getExtensions().compressedTexturePVRTC)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:
case GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT:
if (!context->getExtensions().compressedTexturePVRTCsRGB)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_RGBA32F_EXT:
case GL_RGB32F_EXT:
case GL_ALPHA32F_EXT:
case GL_LUMINANCE32F_EXT:
case GL_LUMINANCE_ALPHA32F_EXT:
if (!context->getExtensions().textureFloat)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_RGBA16F_EXT:
case GL_RGB16F_EXT:
case GL_ALPHA16F_EXT:
case GL_LUMINANCE16F_EXT:
case GL_LUMINANCE_ALPHA16F_EXT:
if (!context->getExtensions().textureHalfFloat)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_R8_EXT:
case GL_RG8_EXT:
if (!context->getExtensions().textureRG)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_R16F_EXT:
case GL_RG16F_EXT:
if (!context->getExtensions().textureRG || !context->getExtensions().textureHalfFloat)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_R32F_EXT:
case GL_RG32F_EXT:
if (!context->getExtensions().textureRG || !context->getExtensions().textureFloat)
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
break;
case GL_DEPTH_COMPONENT16:
case GL_DEPTH_COMPONENT32_OES:
if (!(context->getExtensions().depthTextureAny()))
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
if (target != TextureType::_2D)
{
context->validationError(GL_INVALID_OPERATION, kInvalidTextureTarget);
return false;
}
// ANGLE_depth_texture only supports 1-level textures
if (!context->getExtensions().depthTextureOES)
{
if (levels != 1)
{
context->validationError(GL_INVALID_OPERATION, kInvalidMipLevels);
return false;
}
}
break;
case GL_DEPTH24_STENCIL8_OES:
if (!(context->getExtensions().depthTextureANGLE ||
(context->getExtensions().packedDepthStencil &&
context->getExtensions().textureStorage)))
{
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
if (target != TextureType::_2D)
{
context->validationError(GL_INVALID_OPERATION, kInvalidTextureTarget);
return false;
}
if (!context->getExtensions().packedDepthStencil)
{
// ANGLE_depth_texture only supports 1-level textures
if (levels != 1)
{
context->validationError(GL_INVALID_OPERATION, kInvalidMipLevels);
return false;
}
}
break;
default:
break;
}
gl::Texture *texture = context->getTextureByType(target);
if (!texture || texture->id().value == 0)
{
context->validationError(GL_INVALID_OPERATION, kMissingTexture);
return false;
}
if (texture->getImmutableFormat())
{
context->validationError(GL_INVALID_OPERATION, kTextureIsImmutable);
return false;
}
return true;
}
bool ValidateDiscardFramebufferEXT(Context *context,
GLenum target,
GLsizei numAttachments,
const GLenum *attachments)
{
if (!context->getExtensions().discardFramebuffer)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
bool defaultFramebuffer = false;
switch (target)
{
case GL_FRAMEBUFFER:
defaultFramebuffer =
(context->getState().getTargetFramebuffer(GL_FRAMEBUFFER)->isDefault());
break;
default:
context->validationError(GL_INVALID_ENUM, kInvalidFramebufferTarget);
return false;
}
return ValidateDiscardFramebufferBase(context, target, numAttachments, attachments,
defaultFramebuffer);
}
bool ValidateBindVertexArrayOES(Context *context, VertexArrayID array)
{
if (!context->getExtensions().vertexArrayObject)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
return ValidateBindVertexArrayBase(context, array);
}
bool ValidateDeleteVertexArraysOES(Context *context, GLsizei n, const VertexArrayID *arrays)
{
if (!context->getExtensions().vertexArrayObject)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
return ValidateGenOrDelete(context, n);
}
bool ValidateGenVertexArraysOES(Context *context, GLsizei n, VertexArrayID *arrays)
{
if (!context->getExtensions().vertexArrayObject)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
return ValidateGenOrDelete(context, n);
}
bool ValidateIsVertexArrayOES(Context *context, VertexArrayID array)
{
if (!context->getExtensions().vertexArrayObject)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
return true;
}
bool ValidateProgramBinaryOES(Context *context,
ShaderProgramID program,
GLenum binaryFormat,
const void *binary,
GLint length)
{
if (!context->getExtensions().getProgramBinary)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
return ValidateProgramBinaryBase(context, program, binaryFormat, binary, length);
}
bool ValidateGetProgramBinaryOES(Context *context,
ShaderProgramID program,
GLsizei bufSize,
GLsizei *length,
GLenum *binaryFormat,
void *binary)
{
if (!context->getExtensions().getProgramBinary)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
return ValidateGetProgramBinaryBase(context, program, bufSize, length, binaryFormat, binary);
}
static bool ValidDebugSource(GLenum source, bool mustBeThirdPartyOrApplication)
{
switch (source)
{
case GL_DEBUG_SOURCE_API:
case GL_DEBUG_SOURCE_SHADER_COMPILER:
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
case GL_DEBUG_SOURCE_OTHER:
// Only THIRD_PARTY and APPLICATION sources are allowed to be manually inserted
return !mustBeThirdPartyOrApplication;
case GL_DEBUG_SOURCE_THIRD_PARTY:
case GL_DEBUG_SOURCE_APPLICATION:
return true;
default:
return false;
}
}
static bool ValidDebugType(GLenum type)
{
switch (type)
{
case GL_DEBUG_TYPE_ERROR:
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
case GL_DEBUG_TYPE_PERFORMANCE:
case GL_DEBUG_TYPE_PORTABILITY:
case GL_DEBUG_TYPE_OTHER:
case GL_DEBUG_TYPE_MARKER:
case GL_DEBUG_TYPE_PUSH_GROUP:
case GL_DEBUG_TYPE_POP_GROUP:
return true;
default:
return false;
}
}
static bool ValidDebugSeverity(GLenum severity)
{
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH:
case GL_DEBUG_SEVERITY_MEDIUM:
case GL_DEBUG_SEVERITY_LOW:
case GL_DEBUG_SEVERITY_NOTIFICATION:
return true;
default:
return false;
}
}
bool ValidateDebugMessageControlKHR(Context *context,
GLenum source,
GLenum type,
GLenum severity,
GLsizei count,
const GLuint *ids,
GLboolean enabled)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (!ValidDebugSource(source, false) && source != GL_DONT_CARE)
{
context->validationError(GL_INVALID_ENUM, kInvalidDebugSource);
return false;
}
if (!ValidDebugType(type) && type != GL_DONT_CARE)
{
context->validationError(GL_INVALID_ENUM, kInvalidDebugType);
return false;
}
if (!ValidDebugSeverity(severity) && severity != GL_DONT_CARE)
{
context->validationError(GL_INVALID_ENUM, kInvalidDebugSeverity);
return false;
}
if (count > 0)
{
if (source == GL_DONT_CARE || type == GL_DONT_CARE)
{
context->validationError(GL_INVALID_OPERATION, kInvalidDebugSourceType);
return false;
}
if (severity != GL_DONT_CARE)
{
context->validationError(GL_INVALID_OPERATION, kInvalidDebugSeverity);
return false;
}
}
return true;
}
bool ValidateDebugMessageInsertKHR(Context *context,
GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *buf)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (!context->getState().getDebug().isOutputEnabled())
{
// If the DEBUG_OUTPUT state is disabled calls to DebugMessageInsert are discarded and do
// not generate an error.
return false;
}
if (!ValidDebugSeverity(severity))
{
context->validationError(GL_INVALID_ENUM, kInvalidDebugSource);
return false;
}
if (!ValidDebugType(type))
{
context->validationError(GL_INVALID_ENUM, kInvalidDebugType);
return false;
}
if (!ValidDebugSource(source, true))
{
context->validationError(GL_INVALID_ENUM, kInvalidDebugSource);
return false;
}
size_t messageLength = (length < 0) ? strlen(buf) : length;
if (messageLength > context->getExtensions().maxDebugMessageLength)
{
context->validationError(GL_INVALID_VALUE, kExceedsMaxDebugMessageLength);
return false;
}
return true;
}
bool ValidateDebugMessageCallbackKHR(Context *context,
GLDEBUGPROCKHR callback,
const void *userParam)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
return true;
}
bool ValidateGetDebugMessageLogKHR(Context *context,
GLuint count,
GLsizei bufSize,
GLenum *sources,
GLenum *types,
GLuint *ids,
GLenum *severities,
GLsizei *lengths,
GLchar *messageLog)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (bufSize < 0 && messageLog != nullptr)
{
context->validationError(GL_INVALID_VALUE, kNegativeBufferSize);
return false;
}
return true;
}
bool ValidatePushDebugGroupKHR(Context *context,
GLenum source,
GLuint id,
GLsizei length,
const GLchar *message)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (!ValidDebugSource(source, true))
{
context->validationError(GL_INVALID_ENUM, kInvalidDebugSource);
return false;
}
size_t messageLength = (length < 0) ? strlen(message) : length;
if (messageLength > context->getExtensions().maxDebugMessageLength)
{
context->validationError(GL_INVALID_VALUE, kExceedsMaxDebugMessageLength);
return false;
}
size_t currentStackSize = context->getState().getDebug().getGroupStackDepth();
if (currentStackSize >= context->getExtensions().maxDebugGroupStackDepth)
{
context->validationError(GL_STACK_OVERFLOW, kExceedsMaxDebugGroupStackDepth);
return false;
}
return true;
}
bool ValidatePopDebugGroupKHR(Context *context)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
size_t currentStackSize = context->getState().getDebug().getGroupStackDepth();
if (currentStackSize <= 1)
{
context->validationError(GL_STACK_UNDERFLOW, kCannotPopDefaultDebugGroup);
return false;
}
return true;
}
static bool ValidateObjectIdentifierAndName(Context *context, GLenum identifier, GLuint name)
{
switch (identifier)
{
case GL_BUFFER:
if (context->getBuffer({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidBufferName);
return false;
}
return true;
case GL_SHADER:
if (context->getShader({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidShaderName);
return false;
}
return true;
case GL_PROGRAM:
if (context->getProgramNoResolveLink({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidProgramName);
return false;
}
return true;
case GL_VERTEX_ARRAY:
if (context->getVertexArray({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidVertexArrayName);
return false;
}
return true;
case GL_QUERY:
if (context->getQuery({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidQueryName);
return false;
}
return true;
case GL_TRANSFORM_FEEDBACK:
if (context->getTransformFeedback({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidTransformFeedbackName);
return false;
}
return true;
case GL_SAMPLER:
if (context->getSampler({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidSamplerName);
return false;
}
return true;
case GL_TEXTURE:
if (context->getTexture({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidTextureName);
return false;
}
return true;
case GL_RENDERBUFFER:
if (!context->isRenderbuffer({name}))
{
context->validationError(GL_INVALID_VALUE, kInvalidRenderbufferName);
return false;
}
return true;
case GL_FRAMEBUFFER:
if (context->getFramebuffer({name}) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidFramebufferName);
return false;
}
return true;
default:
context->validationError(GL_INVALID_ENUM, kInvalidIndentifier);
return false;
}
}
static bool ValidateLabelLength(Context *context, GLsizei length, const GLchar *label)
{
size_t labelLength = 0;
if (length < 0)
{
if (label != nullptr)
{
labelLength = strlen(label);
}
}
else
{
labelLength = static_cast<size_t>(length);
}
if (labelLength > context->getExtensions().maxLabelLength)
{
context->validationError(GL_INVALID_VALUE, kExceedsMaxLabelLength);
return false;
}
return true;
}
bool ValidateObjectLabelKHR(Context *context,
GLenum identifier,
GLuint name,
GLsizei length,
const GLchar *label)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (!ValidateObjectIdentifierAndName(context, identifier, name))
{
return false;
}
if (!ValidateLabelLength(context, length, label))
{
return false;
}
return true;
}
bool ValidateGetObjectLabelKHR(Context *context,
GLenum identifier,
GLuint name,
GLsizei bufSize,
GLsizei *length,
GLchar *label)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (bufSize < 0)
{
context->validationError(GL_INVALID_VALUE, kNegativeBufferSize);
return false;
}
if (!ValidateObjectIdentifierAndName(context, identifier, name))
{
return false;
}
return true;
}
static bool ValidateObjectPtrName(Context *context, const void *ptr)
{
if (context->getSync(reinterpret_cast<GLsync>(const_cast<void *>(ptr))) == nullptr)
{
context->validationError(GL_INVALID_VALUE, kInvalidSyncPointer);
return false;
}
return true;
}
bool ValidateObjectPtrLabelKHR(Context *context,
const void *ptr,
GLsizei length,
const GLchar *label)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (!ValidateObjectPtrName(context, ptr))
{
return false;
}
if (!ValidateLabelLength(context, length, label))
{
return false;
}
return true;
}
bool ValidateGetObjectPtrLabelKHR(Context *context,
const void *ptr,
GLsizei bufSize,
GLsizei *length,
GLchar *label)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
if (bufSize < 0)
{
context->validationError(GL_INVALID_VALUE, kNegativeBufferSize);
return false;
}
if (!ValidateObjectPtrName(context, ptr))
{
return false;
}
return true;
}
bool ValidateGetPointervKHR(Context *context, GLenum pname, void **params)
{
if (!context->getExtensions().debug)
{
context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
return false;
}
// TODO: represent this in Context::getQueryParameterInfo.
switch (pname)
{
case GL_DEBUG_CALLBACK_FUNCTION:
case GL_DEBUG_CALLBACK_USER_PARAM</