// // 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. //
// validationES.h: Validation functions for generic OpenGL ES entry point parameters
namespace
{ bool CompressedTextureFormatRequiresExactSize(GLenum internalFormat)
{ // List of compressed format that require that the texture size is smaller than or a multiple of // the compressed block size. switch (internalFormat)
{ 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_COMPRESSED_SRGB_S3TC_DXT1_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: 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_RGBA8_LOSSY_DECODE_ETC2_EAC_ANGLE: case GL_COMPRESSED_SRGB8_ALPHA8_LOSSY_DECODE_ETC2_EAC_ANGLE: case GL_COMPRESSED_RED_RGTC1_EXT: case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: 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: returntrue;
default: returnfalse;
}
} bool CompressedSubTextureFormatRequiresExactSize(GLenum internalFormat)
{ // Compressed sub textures have additional formats that requires exact size. // ES 3.1, Section 8.7, Page 171 return CompressedTextureFormatRequiresExactSize(internalFormat) ||
IsETC2EACFormat(internalFormat) || IsASTC2DFormat(internalFormat);
}
bool DifferenceCanOverflow(GLint a, GLint b)
{
CheckedNumeric<GLint> checkedA(a);
checkedA -= b; // Use negation to make sure that the difference can't overflow regardless of the order.
checkedA = -checkedA; return !checkedA.IsValid();
}
bool ValidReadPixelsTypeEnum(const Context *context, GLenum type)
{ switch (type)
{ // Types referenced in Table 3.4 of the ES 2.0.25 spec case GL_UNSIGNED_BYTE: case GL_UNSIGNED_SHORT_4_4_4_4: case GL_UNSIGNED_SHORT_5_5_5_1: case GL_UNSIGNED_SHORT_5_6_5: return context->getClientVersion() >= ES_2_0;
// Types referenced in Table 3.2 of the ES 3.0.5 spec (Except depth stencil) case GL_BYTE: case GL_INT: case GL_SHORT: case GL_UNSIGNED_INT: case GL_UNSIGNED_INT_10F_11F_11F_REV: case GL_UNSIGNED_INT_2_10_10_10_REV: case GL_UNSIGNED_INT_5_9_9_9_REV: case GL_UNSIGNED_SHORT: case GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT: case GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT: return context->getClientVersion() >= ES_3_0;
case GL_FLOAT: return context->getClientVersion() >= ES_3_0 ||
context->getExtensions().textureFloatOES ||
context->getExtensions().colorBufferHalfFloatEXT;
case GL_HALF_FLOAT: return context->getClientVersion() >= ES_3_0 ||
context->getExtensions().textureHalfFloatOES;
case GL_HALF_FLOAT_OES: return context->getExtensions().colorBufferHalfFloatEXT;
default: returnfalse;
}
}
bool ValidReadPixelsFormatEnum(const Context *context, GLenum format)
{ switch (format)
{ // Formats referenced in Table 3.4 of the ES 2.0.25 spec (Except luminance) case GL_RGBA: case GL_RGB: case GL_ALPHA: return context->getClientVersion() >= ES_2_0;
// Formats referenced in Table 3.2 of the ES 3.0.5 spec case GL_RG: case GL_RED: case GL_RGBA_INTEGER: case GL_RGB_INTEGER: case GL_RG_INTEGER: case GL_RED_INTEGER: return context->getClientVersion() >= ES_3_0;
case GL_SRGB_ALPHA_EXT: case GL_SRGB_EXT: return context->getExtensions().sRGBEXT;
case GL_BGRA_EXT: return context->getExtensions().readFormatBgraEXT;
case GL_RGBX8_ANGLE: return context->getExtensions().rgbxInternalFormatANGLE;
case GL_CLAMP_TO_BORDER: if (!context->getExtensions().textureBorderClampAny() &&
context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} break;
case GL_REPEAT: case GL_MIRRORED_REPEAT: if (restrictedWrapModes)
{ // OES_EGL_image_external and ANGLE_texture_rectangle specifies this error.
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidWrapModeTexture); returnfalse;
} break;
case GL_NEAREST_MIPMAP_NEAREST: case GL_LINEAR_MIPMAP_NEAREST: case GL_NEAREST_MIPMAP_LINEAR: case GL_LINEAR_MIPMAP_LINEAR: if (restrictedMinFilter)
{ // OES_EGL_image_external specifies this error.
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidFilterTexture); returnfalse;
} break;
template <typename ParamType> bool ValidateTextureCompareFuncValue(const Context *context,
angle::EntryPoint entryPoint, const ParamType *params)
{ // Acceptable function parameters from GLES 3.0.2 spec, table 3.17 switch (ConvertToGLenum(params[0]))
{ case GL_LEQUAL: case GL_GEQUAL: case GL_LESS: case GL_GREATER: case GL_EQUAL: case GL_NOTEQUAL: case GL_ALWAYS: case GL_NEVER: break;
auto drawBufferMask =
framebuffer->getDrawBufferMask() & glState.getBlendStateExt().compareColorMask(0); auto fragmentOutputMask = program->getExecutable().getActiveOutputVariablesMask();
bool IsCompatibleDrawModeWithGeometryShader(PrimitiveMode drawMode,
PrimitiveMode geometryShaderInputPrimitiveType)
{ // [EXT_geometry_shader] Section 11.1gs.1, Geometry Shader Input Primitives switch (drawMode)
{ case PrimitiveMode::Points: return geometryShaderInputPrimitiveType == PrimitiveMode::Points; case PrimitiveMode::Lines: case PrimitiveMode::LineStrip: case PrimitiveMode::LineLoop: return geometryShaderInputPrimitiveType == PrimitiveMode::Lines; case PrimitiveMode::LinesAdjacency: case PrimitiveMode::LineStripAdjacency: return geometryShaderInputPrimitiveType == PrimitiveMode::LinesAdjacency; case PrimitiveMode::Triangles: case PrimitiveMode::TriangleFan: case PrimitiveMode::TriangleStrip: return geometryShaderInputPrimitiveType == PrimitiveMode::Triangles; case PrimitiveMode::TrianglesAdjacency: case PrimitiveMode::TriangleStripAdjacency: return geometryShaderInputPrimitiveType == PrimitiveMode::TrianglesAdjacency; default:
UNREACHABLE(); returnfalse;
}
}
// GLES1 texture parameters are a small subset of the others bool IsValidGLES1TextureParameter(GLenum pname)
{ switch (pname)
{ case GL_TEXTURE_MAG_FILTER: case GL_TEXTURE_MIN_FILTER: case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: case GL_TEXTURE_WRAP_R: case GL_GENERATE_MIPMAP: case GL_TEXTURE_CROP_RECT_OES: returntrue; default: returnfalse;
}
}
if (extensions.blendEquationAdvancedKHR)
{
errorString = ValidateProgramDrawAdvancedBlendState(context, program);
}
return errorString;
}
} // anonymous namespace
void SetRobustLengthParam(const GLsizei *length, GLsizei value)
{ if (length)
{ // Currently we modify robust length parameters in the validation layer. We should be only // doing this in the Context instead. // TODO(http://anglebug.com/4406): Remove when possible.
*const_cast<GLsizei *>(length) = value;
}
}
bool ValidTextureTarget(const Context *context, TextureType type)
{ switch (type)
{ case TextureType::_2D: case TextureType::CubeMap: returntrue;
case TextureType::Rectangle: return context->getExtensions().textureRectangleANGLE;
case TextureType::_3D: return ((context->getClientMajorVersion() >= 3) ||
context->getExtensions().texture3DOES);
case TextureType::_2DArray: return (context->getClientMajorVersion() >= 3);
case TextureType::_2DMultisample: return (context->getClientVersion() >= Version(3, 1) ||
context->getExtensions().textureMultisampleANGLE); case TextureType::_2DMultisampleArray: return context->getExtensions().textureStorageMultisample2dArrayOES;
case TextureType::CubeMapArray: return (context->getClientVersion() >= Version(3, 2) ||
context->getExtensions().textureCubeMapArrayAny());
case TextureType::VideoImage: return context->getExtensions().videoTextureWEBGL;
case TextureType::Buffer: return (context->getClientVersion() >= Version(3, 2) ||
context->getExtensions().textureBufferAny());
default: returnfalse;
}
}
bool ValidTexture2DTarget(const Context *context, TextureType type)
{ switch (type)
{ case TextureType::_2D: case TextureType::CubeMap: returntrue;
case TextureType::Rectangle: return context->getExtensions().textureRectangleANGLE;
default: returnfalse;
}
}
bool ValidTexture3DTarget(const Context *context, TextureType target)
{ switch (target)
{ case TextureType::_3D: case TextureType::_2DArray: return (context->getClientMajorVersion() >= 3);
case TextureType::CubeMapArray: return (context->getClientVersion() >= Version(3, 2) ||
context->getExtensions().textureCubeMapArrayAny());
default: returnfalse;
}
}
// Most texture GL calls are not compatible with external textures, so we have a separate validation // function for use in the GL calls that do bool ValidTextureExternalTarget(const Context *context, TextureType target)
{ return (target == TextureType::External) &&
(context->getExtensions().EGLImageExternalOES ||
context->getExtensions().EGLStreamConsumerExternalNV);
}
// This function differs from ValidTextureTarget in that the target must be // usable as the destination of a 2D operation-- so a cube face is valid, but // GL_TEXTURE_CUBE_MAP is not. // Note: duplicate of IsInternalTextureTarget bool ValidTexture2DDestinationTarget(const 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: returntrue; case TextureTarget::Rectangle: return context->getExtensions().textureRectangleANGLE; case TextureTarget::VideoImage: return context->getExtensions().videoTextureWEBGL; default: returnfalse;
}
}
if ((!context->getExtensions().geometryShaderAny() ||
!context->getExtensions().tessellationShaderEXT) &&
context->getClientVersion() < ES_3_2)
{ // It is an invalid operation to call DrawArrays or DrawArraysInstanced with a draw mode // that does not match the current transform feedback object's draw mode (if transform // feedback is active), (3.0.2, section 2.14, pg 86) return transformFeedbackPrimitiveMode == renderPrimitiveMode;
}
const ProgramExecutable *executable = context->getState().getLinkedProgramExecutable(context);
ASSERT(executable); if (executable->hasLinkedShaderStage(ShaderType::Geometry))
{ // If geometry shader is active, transform feedback mode must match what is output from this // stage.
renderPrimitiveMode = executable->getGeometryShaderOutputPrimitiveType();
} elseif (executable->hasLinkedShaderStage(ShaderType::TessEvaluation))
{ // Similarly with tessellation shaders, but only if no geometry shader is present. With // tessellation shaders, only triangles are possibly output. return transformFeedbackPrimitiveMode == PrimitiveMode::Triangles &&
executable->getTessGenMode() == GL_TRIANGLES;
}
// [GL_EXT_geometry_shader] Table 12.1gs switch (renderPrimitiveMode)
{ case PrimitiveMode::Points: return transformFeedbackPrimitiveMode == PrimitiveMode::Points; case PrimitiveMode::Lines: case PrimitiveMode::LineStrip: case PrimitiveMode::LineLoop: return transformFeedbackPrimitiveMode == PrimitiveMode::Lines; case PrimitiveMode::Triangles: case PrimitiveMode::TriangleFan: case PrimitiveMode::TriangleStrip: return transformFeedbackPrimitiveMode == PrimitiveMode::Triangles; case PrimitiveMode::Patches: return transformFeedbackPrimitiveMode == PrimitiveMode::Patches; default:
UNREACHABLE(); returnfalse;
}
}
bool ValidateDrawInstancedANGLE(const Context *context, angle::EntryPoint entryPoint)
{ // Verify there is at least one active attribute with a divisor of zero const State &state = context->getState(); const ProgramExecutable *executable = state.getLinkedProgramExecutable(context);
if (!executable)
{ // No executable means there is no Program/PPO bound, which is undefined behavior, but isn't // an error.
context->getState().getDebug().insertMessage(
GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, 0, GL_DEBUG_SEVERITY_HIGH,
std::string("Attempting to draw without a program"), gl::LOG_WARN, entryPoint); returntrue;
}
switch (target)
{ case GL_FRAMEBUFFER: returntrue;
case GL_READ_FRAMEBUFFER: case GL_DRAW_FRAMEBUFFER: return (context->getExtensions().framebufferBlitAny() ||
context->getClientMajorVersion() >= 3);
default: returnfalse;
}
}
bool ValidMipLevel(const Context *context, TextureType type, GLint level)
{ constauto &caps = context->getCaps(); int maxDimension = 0; switch (type)
{ case TextureType::_2D: case TextureType::_2DArray: case TextureType::_2DMultisample: case TextureType::_2DMultisampleArray: // TODO(http://anglebug.com/2775): It's a bit unclear what the "maximum allowable // level-of-detail" for multisample textures should be. Could maybe make it zero.
maxDimension = caps.max2DTextureSize; break;
case TextureType::CubeMap: case TextureType::CubeMapArray:
maxDimension = caps.maxCubeMapTextureSize; break;
case TextureType::External: case TextureType::Rectangle: case TextureType::VideoImage: case TextureType::Buffer: return level == 0;
case TextureType::_3D:
maxDimension = caps.max3DTextureSize; break;
bool ValidImageSizeParameters(const Context *context,
angle::EntryPoint entryPoint,
TextureType target,
GLint level,
GLsizei width,
GLsizei height,
GLsizei depth, bool isSubImage)
{ if (width < 0 || height < 0 || depth < 0)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kNegativeSize); returnfalse;
} // TexSubImage parameters can be NPOT without textureNPOT extension, // as long as the destination texture is POT. bool hasNPOTSupport =
context->getExtensions().textureNpotOES || context->getClientVersion() >= Version(3, 0); if (!isSubImage && !hasNPOTSupport &&
(level != 0 && (!isPow2(width) || !isPow2(height) || !isPow2(depth))))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kTextureNotPow2); returnfalse;
}
if (!ValidMipLevel(context, target, level))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidMipLevel); returnfalse;
}
returntrue;
}
bool ValidCompressedBaseLevel(GLsizei size, GLuint blockSize, GLint level)
{ // Already checked in ValidMipLevel.
ASSERT(level < 32); // This function is used only for 4x4 BC formats.
ASSERT(blockSize == 4); // Use the constant value to avoid division. return ((size << level) % 4) == 0;
}
if (!formatInfo.compressed && !formatInfo.paletted)
{ returnfalse;
}
// A texture format can not be both block-compressed and paletted
ASSERT(!(formatInfo.compressed && formatInfo.paletted));
if (formatInfo.compressed)
{ // Only PVRTC1 requires dimensions to be powers of two if (IsPVRTC1Format(internalFormat))
{ if (!isPow2(width) || !isPow2(height))
{ returnfalse;
}
if (context->getLimitations().squarePvrtc1)
{ if (width != height)
{ returnfalse;
}
}
}
if (CompressedTextureFormatRequiresExactSize(internalFormat))
{ // In WebGL compatibility mode and D3D, enforce that the base level implied // by the compressed texture's mip level would conform to the block // size. if (context->isWebGL() ||
context->getLimitations().compressedBaseMipLevelMultipleOfFour)
{ // This check is performed only for BC formats.
ASSERT(formatInfo.compressedBlockDepth == 1); if (!ValidCompressedBaseLevel(width, formatInfo.compressedBlockWidth, level) ||
!ValidCompressedBaseLevel(height, formatInfo.compressedBlockHeight, level))
{ returnfalse;
}
} // non-WebGL and non-D3D check is not necessary for the following formats // From EXT_texture_compression_s3tc specification: // If the width or height is not a multiple of four, there will be 4x4 blocks at the // edge of the image that contain "extra" texels that are not part of the image. From // EXT_texture_compression_bptc & EXT_texture_compression_rgtc specification: If an // RGTC/BPTC image has a width or height that is not a multiple of four, the data // corresponding to texels outside the image are irrelevant and undefined.
}
}
if (formatInfo.paletted)
{ // TODO(http://anglebug.com/7688): multi-level paletted images if (level != 0)
{ returnfalse;
}
if (!isPow2(width) || !isPow2(height))
{ returnfalse;
}
}
// ANGLE does not support compressed 3D blocks (provided exclusively by ASTC 3D formats), so // there is no need to check the depth here. Only width and height determine whether a 2D array // element or a 2D slice of a sliced 3D texture fill the entire level. bool fillsEntireMip = xoffset == 0 && yoffset == 0 && static_cast<size_t>(width) == textureWidth && static_cast<size_t>(height) == textureHeight;
if (CompressedFormatRequiresWholeImage(internalFormat))
{ return fillsEntireMip;
}
// Allowed to either have data that is a multiple of block size or is smaller than the block // size but fills the entire mip bool sizeMultipleOfBlockSize = (width % formatInfo.compressedBlockWidth) == 0 &&
(height % formatInfo.compressedBlockHeight) == 0 &&
(depth % formatInfo.compressedBlockDepth) == 0; if (!sizeMultipleOfBlockSize && !fillsEntireMip)
{ returnfalse;
}
}
// ...the data would be unpacked from the buffer object such that the memory reads required // would exceed the data store size. const InternalFormat &formatInfo = GetInternalFormatInfo(format, type);
ASSERT(formatInfo.internalFormat != GL_NONE); const Extents size(width, height, depth); constauto &unpack = context->getState().getUnpackState();
bool ValidQueryType(const Context *context, QueryType queryType)
{ switch (queryType)
{ case QueryType::AnySamples: case QueryType::AnySamplesConservative: return context->getClientMajorVersion() >= 3 ||
context->getExtensions().occlusionQueryBooleanEXT; case QueryType::TransformFeedbackPrimitivesWritten: return (context->getClientMajorVersion() >= 3); case QueryType::TimeElapsed: return context->getExtensions().disjointTimerQueryEXT; case QueryType::CommandsCompleted: return context->getExtensions().syncQueryCHROMIUM; case QueryType::PrimitivesGenerated: return context->getClientVersion() >= ES_3_2 ||
context->getExtensions().geometryShaderAny(); default: returnfalse;
}
}
bool ValidateWebGLVertexAttribPointer(const Context *context,
angle::EntryPoint entryPoint,
VertexAttribType type,
GLboolean normalized,
GLsizei stride, constvoid *ptr, bool pureInteger)
{
ASSERT(context->isWebGL()); // WebGL 1.0 [Section 6.11] Vertex Attribute Data Stride // The WebGL API supports vertex attribute data strides up to 255 bytes. A call to // vertexAttribPointer will generate an INVALID_VALUE error if the value for the stride // parameter exceeds 255.
constexpr GLsizei kMaxWebGLStride = 255; if (stride > kMaxWebGLStride)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kStrideExceedsWebGLLimit); returnfalse;
}
// WebGL 1.0 [Section 6.4] Buffer Offset and Stride Requirements // The offset arguments to drawElements and vertexAttribPointer, and the stride argument to // vertexAttribPointer, must be a multiple of the size of the data type passed to the call, // or an INVALID_OPERATION error is generated.
angle::FormatID internalType = GetVertexFormatID(type, normalized, 1, pureInteger);
size_t typeSize = GetVertexFormatSize(internalType);
Program *GetValidProgramNoResolve(const Context *context,
angle::EntryPoint entryPoint,
ShaderProgramID id)
{ // ES3 spec (section 2.11.1) -- "Commands that accept shader or program object names will // generate the error INVALID_VALUE if the provided name is not the name of either a shader // or program object and INVALID_OPERATION if the provided name identifies an object // that is not the expected type."
Program *validProgram = context->getProgramNoResolveLink(id);
if (!validProgram)
{ if (context->getShader(id))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kExpectedProgramName);
} else
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidProgramName);
}
}
return validProgram;
}
Program *GetValidProgram(const Context *context, angle::EntryPoint entryPoint, ShaderProgramID id)
{
Program *program = GetValidProgramNoResolve(context, entryPoint, id); if (program)
{
program->resolveLink(context);
} return program;
}
Shader *GetValidShader(const Context *context, angle::EntryPoint entryPoint, ShaderProgramID id)
{ // See ValidProgram for spec details.
Shader *validShader = context->getShader(id);
if (!validShader)
{ if (context->getProgramNoResolveLink(id))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kExpectedShaderName);
} else
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidShaderName);
}
}
// Color attachment 0 is validated below because it is always valid constint colorAttachment = (attachment - GL_COLOR_ATTACHMENT0_EXT); if (colorAttachment >= context->getCaps().maxColorAttachments)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidAttachment); returnfalse;
}
} else
{ switch (attachment)
{ case GL_COLOR_ATTACHMENT0: case GL_DEPTH_ATTACHMENT: case GL_STENCIL_ATTACHMENT: break;
case GL_DEPTH_STENCIL_ATTACHMENT: if (!context->isWebGL() && context->getClientMajorVersion() < 3)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidAttachment); returnfalse;
} break;
// Hack for the special WebGL 1 "DEPTH_STENCIL" internal format.
GLenum convertedInternalFormat = context->getConvertedRenderbufferFormat(internalformat);
// ANGLE_framebuffer_multisample does not explicitly state that the internal format must be // sized but it does state that the format must be in the ES2.0 spec table 4.5 which contains // only sized internal formats. const InternalFormat &formatInfo = GetSizedInternalFormatInfo(convertedInternalFormat); if (formatInfo.internalFormat == GL_NONE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidRenderbufferInternalFormat); returnfalse;
}
// ES3.0 spec, section 4.3.2 states that linear filtering is only available for the // color buffer, leaving only nearest being unfiltered from above if ((mask & ~GL_COLOR_BUFFER_BIT) != 0 && filter != GL_NEAREST)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kBlitOnlyNearestForNonColor); returnfalse;
}
if (!readFramebuffer || !drawFramebuffer)
{
context->validationError(entryPoint, GL_INVALID_FRAMEBUFFER_OPERATION,
kBlitFramebufferMissing); returnfalse;
}
if (!ValidateFramebufferComplete(context, entryPoint, readFramebuffer))
{ returnfalse;
}
if (!ValidateFramebufferComplete(context, entryPoint, drawFramebuffer))
{ returnfalse;
}
// EXT_YUV_target disallows blitting to or from a YUV framebuffer if ((mask & GL_COLOR_BUFFER_BIT) != 0 &&
(readFramebuffer->hasYUVAttachment() || drawFramebuffer->hasYUVAttachment()))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kBlitYUVFramebuffer); returnfalse;
}
// The draw and read framebuffers can only match if: // - They are the default framebuffer AND // - The read/draw surfaces are different if ((readFramebuffer->id() == drawFramebuffer->id()) &&
((drawFramebuffer->id() != Framebuffer::kDefaultDrawFramebufferHandle) ||
(context->getCurrentDrawSurface() == context->getCurrentReadSurface())))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kBlitFeedbackLoop); returnfalse;
}
// Not allow blitting to MS buffers, therefore if renderToTextureSamples exist, // consider it MS. checkReadBufferResourceSamples = false if (!ValidateFramebufferNotMultisampled(context, entryPoint, drawFramebuffer, false))
{ returnfalse;
}
// This validation is specified in the WebGL 2.0 spec and not in the GLES 3.0.5 spec, but we // always run it in order to avoid triggering driver bugs. if (DifferenceCanOverflow(srcX0, srcX1) || DifferenceCanOverflow(srcY0, srcY1) ||
DifferenceCanOverflow(dstX0, dstX1) || DifferenceCanOverflow(dstY0, dstY1))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kBlitDimensionsOutOfRange); returnfalse;
}
if (readColorBuffer)
{ const Format &readFormat = readColorBuffer->getFormat();
for (size_t drawbufferIdx = 0;
drawbufferIdx < drawFramebuffer->getDrawbufferStateCount(); ++drawbufferIdx)
{ const FramebufferAttachment *attachment =
drawFramebuffer->getDrawBuffer(drawbufferIdx); if (attachment)
{ const Format &drawFormat = attachment->getFormat();
// The GL ES 3.0.2 spec (pg 193) states that: // 1) If the read buffer is fixed point format, the draw buffer must be as well // 2) If the read buffer is an unsigned integer format, the draw buffer must be // as well // 3) If the read buffer is a signed integer format, the draw buffer must be as // well // Changes with EXT_color_buffer_float: // Case 1) is changed to fixed point OR floating point
GLenum readComponentType = readFormat.info->componentType;
GLenum drawComponentType = drawFormat.info->componentType; bool readFixedPoint = (readComponentType == GL_UNSIGNED_NORMALIZED ||
readComponentType == GL_SIGNED_NORMALIZED); bool drawFixedPoint = (drawComponentType == GL_UNSIGNED_NORMALIZED ||
drawComponentType == GL_SIGNED_NORMALIZED);
if (readFormat.info->isInt() && filter == GL_LINEAR)
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kBlitIntegerWithLinearFilter); returnfalse;
}
} // WebGL 2.0 BlitFramebuffer when blitting from a missing attachment // In OpenGL ES it is undefined what happens when an operation tries to blit from a missing // attachment and WebGL defines it to be an error. We do the check unconditionally as the // situation is an application error that would lead to a crash in ANGLE. elseif (drawFramebuffer->hasEnabledDrawBuffer())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kBlitMissingColor); returnfalse;
}
}
GLenum masks[] = {GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT};
GLenum attachments[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}; for (size_t i = 0; i < 2; i++)
{ if (mask & masks[i])
{ const FramebufferAttachment *readBuffer =
readFramebuffer->getAttachment(context, attachments[i]); const FramebufferAttachment *drawBuffer =
drawFramebuffer->getAttachment(context, attachments[i]);
if (readBuffer && drawBuffer)
{ if (!Format::EquivalentForBlit(readBuffer->getFormat(), drawBuffer->getFormat()))
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kBlitDepthOrStencilFormatMismatch); returnfalse;
}
if (context->isWebGL() && *readBuffer == *drawBuffer)
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kBlitSameImageDepthOrStencil); returnfalse;
}
} // WebGL 2.0 BlitFramebuffer when blitting from a missing attachment elseif (drawBuffer)
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kBlitMissingDepthOrStencil); returnfalse;
}
}
}
// OVR_multiview2: // Calling BlitFramebuffer will result in an INVALID_FRAMEBUFFER_OPERATION error if the // current draw framebuffer isMultiview() or the number of // views in the current read framebuffer is more than one. if (readFramebuffer->readDisallowedByMultiview())
{
context->validationError(entryPoint, GL_INVALID_FRAMEBUFFER_OPERATION, kBlitFromMultiview); returnfalse;
} if (drawFramebuffer->isMultiview())
{
context->validationError(entryPoint, GL_INVALID_FRAMEBUFFER_OPERATION, kBlitToMultiview); returnfalse;
}
ASSERT(framebuffer); if (framebuffer->isDefault())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kDefaultFramebufferTarget); returnfalse;
}
if (!ValidateAttachmentTarget(context, entryPoint, attachment))
{ returnfalse;
}
// [OpenGL ES 2.0.25] Section 4.4.3 page 112 // [OpenGL ES 3.0.2] Section 4.4.2 page 201 // 'renderbuffer' must be either zero or the name of an existing renderbuffer object of // type 'renderbuffertarget', otherwise an INVALID_OPERATION error is generated. if (renderbuffer.value != 0)
{ if (!context->getRenderbuffer(renderbuffer))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidRenderbufferTarget); returnfalse;
}
}
if (!ValidateAttachmentTarget(context, entryPoint, attachment))
{ returnfalse;
}
if (texture.value != 0)
{
Texture *tex = context->getTexture(texture);
if (tex == nullptr)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kMissingTexture); returnfalse;
}
if (level < 0)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidMipLevel); returnfalse;
}
// GLES spec 3.1, Section 9.2.8 "Attaching Texture Images to a Framebuffer" // An INVALID_VALUE error is generated if texture is not zero and level is // not a supported texture level for textarget
// Common criteria for not supported texture levels(other criteria are handled case by case // in non base functions): If texture refers to an immutable-format texture, level must be // greater than or equal to zero and smaller than the value of TEXTURE_IMMUTABLE_LEVELS for // texture. if (tex->getImmutableFormat() && context->getClientVersion() >= ES_3_1)
{ if (level >= static_cast<GLint>(tex->getImmutableLevels()))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidMipLevel); returnfalse;
}
}
// GLES spec 3.2, Section 9.2.8 "Attaching Texture Images to a Framebuffer" // An INVALID_OPERATION error is generated if <texture> is the name of a buffer texture. if ((context->getClientVersion() >= ES_3_2 ||
context->getExtensions().textureBufferAny()) &&
tex->getType() == TextureType::Buffer)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidTextureTarget); returnfalse;
}
if (tex->getState().hasProtectedContent() != context->getState().hasProtectedContent())
{
context->validationError(
entryPoint, GL_INVALID_OPERATION, "Mismatch between Texture and Context Protected Content state"); returnfalse;
}
}
// This error isn't spelled out in the spec in a very explicit way, but we interpret the spec so // that out-of-range base level has a non-color-renderable / non-texture-filterable format. if (effectiveBaseLevel >= IMPLEMENTATION_MAX_TEXTURE_LEVELS)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kBaseLevelOutOfRange); returnfalse;
}
// GenerateMipmap accepts formats that are unsized or both color renderable and filterable. bool formatUnsized = !format.sized; bool formatColorRenderableAndFilterable =
format.filterSupport(context->getClientVersion(), context->getExtensions()) &&
format.textureAttachmentSupport(context->getClientVersion(), context->getExtensions()); if (!formatUnsized && !formatColorRenderableAndFilterable)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kGenerateMipmapNotAllowed); returnfalse;
}
// GL_EXT_sRGB adds an unsized SRGB (no alpha) format which has explicitly disabled mipmap // generation if (format.colorEncoding == GL_SRGB && format.format == GL_RGB)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kGenerateMipmapNotAllowed); returnfalse;
}
// According to the OpenGL extension spec EXT_sRGB.txt, EXT_SRGB is based on ES 2.0 and // generateMipmap is not allowed if texture format is SRGB_EXT or SRGB_ALPHA_EXT. if (context->getClientVersion() < Version(3, 0) && format.colorEncoding == GL_SRGB)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kGenerateMipmapNotAllowed); returnfalse;
}
if (id.value == 0)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidQueryId); returnfalse;
}
// From EXT_occlusion_query_boolean: If BeginQueryEXT is called with an <id> // of zero, if the active query object name for <target> is non-zero (for the // targets ANY_SAMPLES_PASSED_EXT and ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, if // the active query for either target is non-zero), if <id> is the name of an // existing query object whose type does not match <target>, or if <id> is the // active query object name for any query type, the error INVALID_OPERATION is // generated.
// Ensure no other queries are active // NOTE: If other queries than occlusion are supported, we will need to check // separately that: // a) The query ID passed is not the current active query for any target/type // b) There are no active queries for the requested target (and in the case // of GL_ANY_SAMPLES_PASSED_EXT and GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, // no query may be active for either if glBeginQuery targets either.
if (context->getState().isQueryActive(target))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kOtherQueryActive); returnfalse;
}
// check that name was obtained with glGenQueries if (!context->isQueryGenerated(id))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidQueryId); returnfalse;
}
// Check for type mismatch. If query is not yet started we're good to go.
Query *queryObject = context->getQuery(id); if (queryObject && queryObject->getType() != target)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kQueryTargetMismatch); returnfalse;
}
if (context->isContextLost())
{
context->validationError(entryPoint, GL_CONTEXT_LOST, kContextLost);
if (pname == GL_QUERY_RESULT_AVAILABLE_EXT)
{ // Generate an error but still return true, the context still needs to return a // value in this case. returntrue;
} else
{ returnfalse;
}
}
Query *queryObject = context->getQuery(id);
if (!queryObject)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidQueryId); returnfalse;
}
if (context->getState().isQueryActive(queryObject))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kQueryActive); returnfalse;
}
switch (pname)
{ case GL_QUERY_RESULT_EXT: case GL_QUERY_RESULT_AVAILABLE_EXT: break;
// attempting to write an array to a non-array uniform is an INVALID_OPERATION if (count > 1 && !uniform.isArray())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidUniformCount); returnfalse;
}
*uniformOut = &uniform; returntrue;
}
bool ValidateUniform1ivValue(const Context *context,
angle::EntryPoint entryPoint,
GLenum uniformType,
GLsizei count, const GLint *value)
{ // Value type is GL_INT, because we only get here from glUniform1i{v}. // It is compatible with INT or BOOL. // Do these cheap tests first, for a little extra speed. if (GL_INT == uniformType || GL_BOOL == uniformType)
{ returntrue;
}
if (IsSamplerType(uniformType))
{ // Check that the values are in range. const GLint max = context->getCaps().maxCombinedTextureImageUnits; for (GLsizei i = 0; i < count; ++i)
{ if (value[i] < 0 || value[i] >= max)
{
context->validationError(entryPoint, GL_INVALID_VALUE,
kSamplerUniformValueOutOfRange); returnfalse;
}
} returntrue;
}
bool ValidateUniformMatrixValue(const Context *context,
angle::EntryPoint entryPoint,
GLenum valueType,
GLenum uniformType)
{ // Check that the value type is compatible with uniform type. if (valueType == uniformType)
{ returntrue;
}
switch (pname)
{ case GL_TEXTURE_BINDING_2D: case GL_TEXTURE_BINDING_CUBE_MAP: case GL_TEXTURE_BINDING_3D: case GL_TEXTURE_BINDING_2D_ARRAY: case GL_TEXTURE_BINDING_2D_MULTISAMPLE: break; case GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY: if (!context->getExtensions().textureStorageMultisample2dArrayOES)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kMultisampleArrayExtensionRequired); returnfalse;
} break; case GL_TEXTURE_BINDING_RECTANGLE_ANGLE: if (!context->getExtensions().textureRectangleANGLE)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break; case GL_TEXTURE_BINDING_EXTERNAL_OES: if (!context->getExtensions().EGLStreamConsumerExternalNV &&
!context->getExtensions().EGLImageExternalOES)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break; case GL_TEXTURE_BUFFER_BINDING: case GL_TEXTURE_BINDING_BUFFER: case GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT: case GL_MAX_TEXTURE_BUFFER_SIZE: if (context->getClientVersion() < Version(3, 2) &&
!context->getExtensions().textureBufferAny())
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kTextureBufferExtensionNotAvailable); returnfalse;
} break;
case GL_IMPLEMENTATION_COLOR_READ_TYPE: case GL_IMPLEMENTATION_COLOR_READ_FORMAT:
{
Framebuffer *readFramebuffer = context->getState().getReadFramebuffer();
ASSERT(readFramebuffer);
if (!ValidateFramebufferComplete<GL_INVALID_OPERATION>(context, entryPoint,
readFramebuffer))
{ returnfalse;
}
if (readFramebuffer->getReadBufferState() == GL_NONE)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kReadBufferNone); returnfalse;
}
if (!ValidateStateQuery(context, entryPoint, pname, nativeType, numParams))
{ returnfalse;
}
if (!ValidateRobustBufferSize(context, entryPoint, bufSize, *numParams))
{ returnfalse;
}
returntrue;
}
bool ValidateCopyImageSubDataTarget(const Context *context,
angle::EntryPoint entryPoint,
GLuint name,
GLenum target)
{ // From EXT_copy_image: INVALID_ENUM is generated if either <srcTarget> or <dstTarget> is not // RENDERBUFFER or a valid non - proxy texture target, is TEXTURE_BUFFER, or is one of the // cubemap face selectors described in table 3.17, or if the target does not match the type of // the object. INVALID_VALUE is generated if either <srcName> or <dstName> does not correspond // to a valid renderbuffer or texture object according to the corresponding target parameter. switch (target)
{ case GL_RENDERBUFFER:
{
RenderbufferID renderbuffer = PackParam<RenderbufferID>(name); if (!context->isRenderbuffer(renderbuffer))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidRenderbufferName); returnfalse;
} break;
} case GL_TEXTURE_2D: case GL_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: case GL_TEXTURE_CUBE_MAP: case GL_TEXTURE_CUBE_MAP_ARRAY_EXT:
{
TextureID texture = PackParam<TextureID>(name); if (!context->isTexture(texture))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidTextureName); returnfalse;
}
bool ValidateCopyImageSubDataLevel(const Context *context,
angle::EntryPoint entryPoint,
GLenum target,
GLint level)
{ switch (target)
{ case GL_RENDERBUFFER:
{ if (level != 0)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidMipLevel); returnfalse;
} break;
} case GL_TEXTURE_2D: case GL_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: case GL_TEXTURE_CUBE_MAP: case GL_TEXTURE_CUBE_MAP_ARRAY_EXT:
{ if (!ValidMipLevel(context, PackParam<TextureType>(target), level))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidMipLevel); returnfalse;
} break;
} default:
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidTarget); returnfalse;
}
returntrue;
}
bool ValidateCopyImageSubDataTargetRegion(const Context *context,
angle::EntryPoint entryPoint,
GLuint name,
GLenum target,
GLint level,
GLint offsetX,
GLint offsetY,
GLint offsetZ,
GLsizei width,
GLsizei height,
GLsizei *samples)
{ // INVALID_VALUE is generated if the dimensions of the either subregion exceeds the boundaries // of the corresponding image object. if (offsetX < 0 || offsetY < 0 || offsetZ < 0)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kNegativeOffset); returnfalse;
}
if (target == GL_RENDERBUFFER)
{ // INVALID_VALUE is generated if the dimensions of the either subregion exceeds the // boundaries of the corresponding image object
Renderbuffer *buffer = context->getRenderbuffer(PackParam<RenderbufferID>(name)); if ((buffer->getWidth() - offsetX < width) || (buffer->getHeight() - offsetY < height))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kSourceTextureTooSmall); returnfalse;
}
} else
{
Texture *texture = context->getTexture(PackParam<TextureID>(name));
// INVALID_OPERATION is generated if either object is a texture and the texture is not // complete // This will handle the texture completeness check. Note that this ignores format-based // compleness rules. if (!texture->isSamplerCompleteForCopyImage(context, nullptr))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kNotTextureComplete); returnfalse;
}
GLenum textureTargetToUse = target; if (target == GL_TEXTURE_CUBE_MAP)
{ // Use GL_TEXTURE_CUBE_MAP_POSITIVE_X to properly gather the textureWidth/textureHeight
textureTargetToUse = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
}
// INVALID_VALUE is generated if the dimensions of the either subregion exceeds the // boundaries of the corresponding image object if ((textureWidth - offsetX < width) || (textureHeight - offsetY < height))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kSourceTextureTooSmall); returnfalse;
}
// INVALID_VALUE is generated if the image format is compressed and the dimensions of the // subregion fail to meet the alignment constraints of the format. if ((width % formatInfo.compressedBlockWidth != 0) ||
(height % formatInfo.compressedBlockHeight != 0))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidCompressedRegionSize); returnfalse;
}
switch (target)
{ case GL_RENDERBUFFER:
{
Renderbuffer *buffer = context->getRenderbuffer(PackParam<RenderbufferID>(name)); return *buffer->getFormat().info;
} case GL_TEXTURE_2D: case GL_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: case GL_TEXTURE_CUBE_MAP: case GL_TEXTURE_CUBE_MAP_ARRAY_EXT:
{
Texture *texture = context->getTexture(PackParam<TextureID>(name));
GLenum textureTargetToUse = target;
if (target == GL_TEXTURE_CUBE_MAP)
{ // Use GL_TEXTURE_CUBE_MAP_POSITIVE_X to properly gather the // textureWidth/textureHeight
textureTargetToUse = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
} return *texture->getFormat(PackParam<TextureTarget>(textureTargetToUse), level).info;
} default:
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidTarget); return defaultInternalFormat;
}
}
bool ValidateCopyMixedFormatCompatible(GLenum uncompressedFormat, GLenum compressedFormat)
{ // Validates mixed format compatibility (uncompressed and compressed) from Table 4.X.1 of the // EXT_copy_image spec. switch (compressedFormat)
{ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: 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: case GL_COMPRESSED_RGBA8_ETC2_EAC: case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: case GL_COMPRESSED_RG11_EAC: case GL_COMPRESSED_SIGNED_RG11_EAC: case GL_COMPRESSED_RGBA_ASTC_4x4_KHR: case GL_COMPRESSED_RGBA_ASTC_5x4_KHR: case GL_COMPRESSED_RGBA_ASTC_5x5_KHR: case GL_COMPRESSED_RGBA_ASTC_6x5_KHR: case GL_COMPRESSED_RGBA_ASTC_6x6_KHR: case GL_COMPRESSED_RGBA_ASTC_8x5_KHR: case GL_COMPRESSED_RGBA_ASTC_8x6_KHR: case GL_COMPRESSED_RGBA_ASTC_8x8_KHR: case GL_COMPRESSED_RGBA_ASTC_10x5_KHR: case GL_COMPRESSED_RGBA_ASTC_10x6_KHR: case GL_COMPRESSED_RGBA_ASTC_10x8_KHR: case GL_COMPRESSED_RGBA_ASTC_10x10_KHR: case GL_COMPRESSED_RGBA_ASTC_12x10_KHR: case GL_COMPRESSED_RGBA_ASTC_12x12_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR: case GL_COMPRESSED_RGBA_ASTC_3x3x3_OES: case GL_COMPRESSED_RGBA_ASTC_4x3x3_OES: case GL_COMPRESSED_RGBA_ASTC_4x4x3_OES: case GL_COMPRESSED_RGBA_ASTC_4x4x4_OES: case GL_COMPRESSED_RGBA_ASTC_5x4x4_OES: case GL_COMPRESSED_RGBA_ASTC_5x5x4_OES: case GL_COMPRESSED_RGBA_ASTC_5x5x5_OES: case GL_COMPRESSED_RGBA_ASTC_6x5x5_OES: case GL_COMPRESSED_RGBA_ASTC_6x6x5_OES: case GL_COMPRESSED_RGBA_ASTC_6x6x6_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES: case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES:
{ switch (uncompressedFormat)
{ case GL_RGBA32UI: case GL_RGBA32I: case GL_RGBA32F: returntrue; default: returnfalse;
}
} case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: case GL_COMPRESSED_RED_RGTC1_EXT: case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: case GL_COMPRESSED_RGB8_ETC2: case GL_COMPRESSED_SRGB8_ETC2: case GL_COMPRESSED_R11_EAC: case GL_COMPRESSED_SIGNED_R11_EAC: case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
{ switch (uncompressedFormat)
{ case GL_RGBA16UI: case GL_RGBA16I: case GL_RGBA16F: case GL_RG32UI: case GL_RG32I: case GL_RG32F: returntrue; default: returnfalse;
}
} default: break;
}
returnfalse;
}
bool ValidateCopyCompressedFormatCompatible(const InternalFormat &srcFormatInfo, const InternalFormat &dstFormatInfo)
{ // Validates compressed format compatibility from Table 4.X.2 of the EXT_copy_image spec.
switch (srcFormat)
{ case GL_COMPRESSED_RED_RGTC1_EXT: return (dstFormat == GL_COMPRESSED_SIGNED_RED_RGTC1_EXT); case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: return (dstFormat == GL_COMPRESSED_RED_RGTC1_EXT); case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: return (dstFormat == GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT); case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: return (dstFormat == GL_COMPRESSED_RED_GREEN_RGTC2_EXT); case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT: return (dstFormat == GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT); case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT: return (dstFormat == GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT); case GL_COMPRESSED_R11_EAC: return (dstFormat == GL_COMPRESSED_SIGNED_R11_EAC); case GL_COMPRESSED_SIGNED_R11_EAC: return (dstFormat == GL_COMPRESSED_R11_EAC); case GL_COMPRESSED_RG11_EAC: return (dstFormat == GL_COMPRESSED_SIGNED_RG11_EAC); case GL_COMPRESSED_SIGNED_RG11_EAC: return (dstFormat == GL_COMPRESSED_RG11_EAC); default: break;
}
// Since they can't be the same format and are both compressed formats, one must be linear and // the other nonlinear. if (srcFormatInfo.colorEncoding == dstFormatInfo.colorEncoding)
{ returnfalse;
}
// When copying from a compressed image to an uncompressed image the image texel dimensions // written to the uncompressed image will be source extent divided by the compressed texel block // dimensions. if ((srcFormatInfo.compressed) && (!dstFormatInfo.compressed))
{
ASSERT(srcFormatInfo.compressedBlockWidth != 0);
ASSERT(srcFormatInfo.compressedBlockHeight != 0);
dstWidth /= srcFormatInfo.compressedBlockWidth;
dstHeight /= srcFormatInfo.compressedBlockHeight;
} // When copying from an uncompressed image to a compressed image the image texel dimensions // written to the compressed image will be the source extent multiplied by the compressed texel // block dimensions. elseif ((!srcFormatInfo.compressed) && (dstFormatInfo.compressed))
{
dstWidth *= dstFormatInfo.compressedBlockWidth;
dstHeight *= dstFormatInfo.compressedBlockHeight;
}
// From EXT_copy_image: INVALID_OPERATION is generated if the source and destination formats // are not compatible, if one image is compressed and the other is uncompressed and the block // size of compressed image is not equal to the texel size of the compressed image. if (!ValidateCopyFormatCompatible(srcFormatInfo, dstFormatInfo))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kIncompatibleTextures); returnfalse;
}
// INVALID_OPERATION is generated if the source and destination number of samples do not match if (srcSamples != dstSamples)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kSamplesOutOfRange); returnfalse;
}
if (std::numeric_limits<GLint>::max() - width < x ||
std::numeric_limits<GLint>::max() - height < y)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kIntegerOverflow); returnfalse;
}
if (border != 0)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidBorder); returnfalse;
}
if (!ValidMipLevel(context, texType, level))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidMipLevel); returnfalse;
}
const State &state = context->getState();
Framebuffer *readFramebuffer = state.getReadFramebuffer(); if (!ValidateFramebufferComplete(context, entryPoint, readFramebuffer))
{ returnfalse;
}
// checkReadBufferResourceSamples = true. Treat renderToTexture textures as single sample since // they will be resolved before copying. if (!readFramebuffer->isDefault() &&
!ValidateFramebufferNotMultisampled(context, entryPoint, readFramebuffer, true))
{ returnfalse;
}
if (readFramebuffer->getReadBufferState() == GL_NONE)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kReadBufferNone); returnfalse;
}
// WebGL 1.0 [Section 6.26] Reading From a Missing Attachment // In OpenGL ES it is undefined what happens when an operation tries to read from a missing // attachment and WebGL defines it to be an error. We do the check unconditionally as the // situation is an application error that would lead to a crash in ANGLE. const FramebufferAttachment *source = readFramebuffer->getReadColorAttachment(); if (source == nullptr)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kMissingReadAttachment); returnfalse;
}
if (source->isYUV())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kCopyFromYUVFramebuffer); returnfalse;
}
// ANGLE_multiview spec, Revision 1: // Calling CopyTexSubImage3D, CopyTexImage2D, or CopyTexSubImage2D will result in an // INVALID_FRAMEBUFFER_OPERATION error if the multi-view layout of the current read framebuffer // is FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE or the number of views in the current read // framebuffer is more than one. if (readFramebuffer->readDisallowedByMultiview())
{
context->validationError(entryPoint, GL_INVALID_FRAMEBUFFER_OPERATION,
kMultiviewReadFramebuffer); returnfalse;
}
// Do not leak the previous texture format for non-subImage case. if (textureFormatOut && isSubImage)
{
*textureFormatOut = texture->getFormat(target, level);
}
// Detect texture copying feedback loops for WebGL. if (context->isWebGL())
{ if (readFramebuffer->formsCopyingFeedbackLoopWith(texture->id(), level, zoffset))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kFeedbackLoop); returnfalse;
}
}
returntrue;
}
constchar *ValidateProgramPipelineDrawStates(const Context *context, const Extensions &extensions,
ProgramPipeline *programPipeline)
{ for (const ShaderType shaderType : gl::AllShaderTypes())
{
Program *program = programPipeline->getShaderProgram(shaderType); if (program)
{ constchar *errorMsg = ValidateProgramDrawStates(context, extensions, program); if (errorMsg)
{ return errorMsg;
}
}
}
return nullptr;
}
constchar *ValidateProgramPipelineAttachedPrograms(ProgramPipeline *programPipeline)
{ // An INVALID_OPERATION error is generated by any command that transfers vertices to the // GL or launches compute work if the current set of active // program objects cannot be executed, for reasons including: // - There is no current program object specified by UseProgram, there is a current program // pipeline object, and that object is empty (no executable code is installed for any stage). // - A program object is active for at least one, but not all of the shader // stages that were present when the program was linked. if (!programPipeline->getExecutable().getLinkedShaderStages().any())
{ return gl::err::kNoExecutableCodeInstalled;
} for (const ShaderType shaderType : gl::AllShaderTypes())
{
Program *shaderProgram = programPipeline->getShaderProgram(shaderType); if (shaderProgram)
{
ProgramExecutable &executable = shaderProgram->getExecutable(); for (const ShaderType programShaderType : executable.getLinkedShaderStages())
{ if (shaderProgram != programPipeline->getShaderProgram(programShaderType))
{ return gl::err::kNotAllStagesOfSeparableProgramUsed;
}
}
}
}
// [EXT_geometry_shader] Section 11.1.gs Geometry Shaders // A non-separable program object or program pipeline object that includes // a geometry shader must also include a vertex shader. // An INVALID_OPERATION error is generated by any command that transfers // vertices to the GL if the current program state has a geometry shader // but no vertex shader. if (!programPipeline->getShaderProgram(ShaderType::Vertex) &&
programPipeline->getShaderProgram(ShaderType::Geometry))
{ return gl::err::kNoActiveGraphicsShaderStage;
}
return nullptr;
}
// Note all errors returned from this function are INVALID_OPERATION except for the draw framebuffer // completeness check. constchar *ValidateDrawStates(const Context *context)
{ const Extensions &extensions = context->getExtensions(); const State &state = context->getState();
// WebGL buffers cannot be mapped/unmapped because the MapBufferRange, FlushMappedBufferRange, // and UnmapBuffer entry points are removed from the WebGL 2.0 API. // https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.14
VertexArray *vertexArray = state.getVertexArray();
ASSERT(vertexArray);
if (!extensions.webglCompatibilityANGLE && vertexArray->hasInvalidMappedArrayBuffer())
{ return kBufferMapped;
}
// Note: these separate values are not supported in WebGL, due to D3D's limitations. See // Section 6.10 of the WebGL 1.0 spec.
Framebuffer *framebuffer = state.getDrawFramebuffer();
ASSERT(framebuffer);
if (differentRefs || differentWritemasks || differentMasks)
{ if (!extensions.webglCompatibilityANGLE)
{
WARN() << "This ANGLE implementation does not support separate front/back " "stencil writemasks, reference values, or stencil mask values.";
} return kStencilReferenceMaskOrMismatch;
}
}
}
if (!extensions.floatBlendEXT)
{ const DrawBufferMask blendEnabledActiveFloat32ColorAttachmentDrawBufferMask =
state.getBlendEnabledDrawBufferMask() &
framebuffer->getActiveFloat32ColorAttachmentDrawBufferMask(); if (blendEnabledActiveFloat32ColorAttachmentDrawBufferMask.any())
{ return kUnsupportedFloatBlending;
}
}
if (context->getLimitations().noSimultaneousConstantColorAndAlphaBlendFunc ||
extensions.webglCompatibilityANGLE)
{ if (state.hasSimultaneousConstantColorAndAlphaBlendFunc())
{ if (extensions.webglCompatibilityANGLE)
{ return kInvalidConstantColor;
}
if (!framebuffer->isComplete(context))
{ // Note: this error should be generated as INVALID_FRAMEBUFFER_OPERATION. return kDrawFramebufferIncomplete;
}
bool framebufferIsYUV = framebuffer->hasYUVAttachment(); if (framebufferIsYUV)
{ const BlendState &blendState = state.getBlendState(); if (!blendState.colorMaskRed || !blendState.colorMaskGreen || !blendState.colorMaskBlue)
{ // When rendering into a YUV framebuffer, the color mask must have r g and b set to // true. return kInvalidColorMaskForYUV;
}
if (blendState.blend)
{ // When rendering into a YUV framebuffer, blending must be disabled. return kInvalidBlendStateForYUV;
}
} else
{ if (framebuffer->hasExternalTextureAttachment())
{ // It is an error to render into an external texture that is not YUV. return kExternalTextureAttachmentNotYUV;
}
}
// Advanced blend equation can only be enabled for a single render target. const BlendStateExt &blendStateExt = state.getBlendStateExt(); if (blendStateExt.getUsesAdvancedBlendEquationMask().any())
{ const size_t drawBufferCount = framebuffer->getDrawbufferStateCount();
uint32_t advancedBlendRenderTargetCount = 0;
if (advancedBlendRenderTargetCount > 1)
{ return kAdvancedBlendEquationWithMRT;
}
}
if (context->getStateCache().hasAnyEnabledClientAttrib())
{ if (extensions.webglCompatibilityANGLE || !state.areClientArraysEnabled())
{ // [WebGL 1.0] Section 6.5 Enabled Vertex Attributes and Range Checking // If a vertex attribute is enabled as an array via enableVertexAttribArray but no // buffer is bound to that attribute via bindBuffer and vertexAttribPointer, then calls // to drawArrays or drawElements will generate an INVALID_OPERATION error. return kVertexArrayNoBuffer;
}
if (state.getVertexArray()->hasEnabledNullPointerClientArray())
{ // This is an application error that would normally result in a crash, but we catch it // and return an error return kVertexArrayNoBufferPointer;
}
}
// If we are running GLES1, there is no current program. if (context->getClientVersion() >= Version(2, 0))
{
Program *program = state.getLinkedProgram(context);
ProgramPipeline *programPipeline = state.getLinkedProgramPipeline(context); const ProgramExecutable *executable = state.getProgramExecutable();
bool programIsYUVOutput = false;
if (program)
{ constchar *errorMsg = ValidateProgramDrawStates(context, extensions, program); if (errorMsg)
{ return errorMsg;
}
if (!programPipeline->isLinked())
{ return kProgramPipelineLinkFailed;
}
programIsYUVOutput = executable->isYUVOutput();
}
if (executable)
{ if (!executable->validateSamplers(nullptr, context->getCaps()))
{ return kTextureTypeConflict;
}
if (executable->hasLinkedTessellationShader())
{ if (!executable->hasLinkedShaderStage(ShaderType::Vertex))
{ return kTessellationShaderRequiresVertexShader;
}
if (!executable->hasLinkedShaderStage(ShaderType::TessControl) ||
!executable->hasLinkedShaderStage(ShaderType::TessEvaluation))
{ return kTessellationShaderRequiresBothControlAndEvaluation;
}
}
if (state.isTransformFeedbackActive())
{ if (!ValidateProgramExecutableXFBBuffersPresent(context, executable))
{ return kTransformFeedbackBufferMissing;
}
}
}
if (programIsYUVOutput != framebufferIsYUV)
{ // Both the program and framebuffer must match in YUV output state. return kYUVOutputMissmatch;
}
if (!state.validateSamplerFormats())
{ return kSamplerFormatMismatch;
}
// Do some additional WebGL-specific validation if (extensions.webglCompatibilityANGLE)
{ const TransformFeedback *transformFeedbackObject = state.getCurrentTransformFeedback(); if (state.isTransformFeedbackActive() &&
transformFeedbackObject->buffersBoundForOtherUseInWebGL())
{ return kTransformFeedbackBufferDoubleBound;
}
// Detect rendering feedback loops for WebGL. if (framebuffer->formsRenderingFeedbackLoopWith(context))
{ return kFeedbackLoop;
}
// Detect that the vertex shader input types match the attribute types if (!ValidateVertexShaderAttributeTypeMatch(context))
{ return kVertexShaderTypeMismatch;
}
if (!context->getState().getRasterizerState().rasterizerDiscard &&
!context->getState().allActiveDrawBufferChannelsMasked())
{ // Detect that if there's active color buffer without fragment shader output if (!ValidateFragmentShaderColorBufferMaskMatch(context))
{ return kDrawBufferMaskMismatch;
}
// Detect that the color buffer types match the fragment shader output types if (!ValidateFragmentShaderColorBufferTypeMatch(context))
{ return kDrawBufferTypeMismatch;
}
}
// Validate that we are rendering with a linked program. if (!program->isLinked())
{ return kProgramNotLinked;
}
}
}
return nullptr;
}
constchar *ValidateProgramPipeline(const Context *context)
{ const State &state = context->getState(); // If we are running GLES1, there is no current program. if (context->getClientVersion() >= Version(2, 0))
{
ProgramPipeline *programPipeline = state.getProgramPipeline(); if (programPipeline)
{ constchar *errorMsg = ValidateProgramPipelineAttachedPrograms(programPipeline); if (errorMsg)
{ return errorMsg;
}
}
} return nullptr;
}
switch (mode)
{ case PrimitiveMode::Points: case PrimitiveMode::Lines: case PrimitiveMode::LineLoop: case PrimitiveMode::LineStrip: case PrimitiveMode::Triangles: case PrimitiveMode::TriangleStrip: case PrimitiveMode::TriangleFan: break;
case PrimitiveMode::LinesAdjacency: case PrimitiveMode::LineStripAdjacency: case PrimitiveMode::TrianglesAdjacency: case PrimitiveMode::TriangleStripAdjacency: if (!extensions.geometryShaderAny() && context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kGeometryShaderExtensionNotEnabled); return;
} break;
case PrimitiveMode::Patches: if (!extensions.tessellationShaderEXT && context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kTessellationShaderExtensionNotEnabled); return;
} break;
// If we are running GLES1, there is no current program. if (context->getClientVersion() >= Version(2, 0))
{ const ProgramExecutable *executable = state.getProgramExecutable();
ASSERT(executable);
// Do geometry shader specific validations if (executable->hasLinkedShaderStage(ShaderType::Geometry))
{ if (!IsCompatibleDrawModeWithGeometryShader(
mode, executable->getGeometryShaderInputPrimitiveType()))
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kIncompatibleDrawModeAgainstGeometryShader); return;
}
}
if (!ValidateDrawArraysInstancedBase(context, entryPoint, mode, first, count, primcount))
{ returnfalse;
}
returntrue;
}
constchar *ValidateDrawElementsStates(const Context *context)
{ const State &state = context->getState();
if (context->getStateCache().isTransformFeedbackActiveUnpaused())
{ // EXT_geometry_shader allows transform feedback to work with all draw commands. // [EXT_geometry_shader] Section 12.1, "Transform Feedback" if (!context->getExtensions().geometryShaderAny() && context->getClientVersion() < ES_3_2)
{ // It is an invalid operation to call DrawElements, DrawRangeElements or // DrawElementsInstanced while transform feedback is active, (3.0.2, section 2.14, pg // 86) return kUnsupportedDrawModeForTransformFeedback;
}
}
if (elementArrayBuffer)
{ if (elementArrayBuffer->hasWebGLXFBBindingConflict(context->isWebGL()))
{ return kElementArrayBufferBoundForTransformFeedback;
} if (elementArrayBuffer->isMapped() &&
(!elementArrayBuffer->isImmutable() ||
(elementArrayBuffer->getAccessFlags() & GL_MAP_PERSISTENT_BIT_EXT) == 0))
{ return kBufferMapped;
}
} else
{ // [WebGL 1.0] Section 6.2 No Client Side Arrays // If an indexed draw command (drawElements) is called and no WebGLBuffer is bound to // the ELEMENT_ARRAY_BUFFER binding point, an INVALID_OPERATION error is generated. if (!context->getState().areClientArraysEnabled() || context->isWebGL())
{ return kMustHaveElementArrayBinding;
}
}
for (GLsizei i = 0; i < numAttachments; ++i)
{ if (attachments[i] >= GL_COLOR_ATTACHMENT0 && attachments[i] <= GL_COLOR_ATTACHMENT31)
{ if (defaultFramebuffer)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kDefaultFramebufferInvalidAttachment); returnfalse;
}
if (attachments[i] >=
GL_COLOR_ATTACHMENT0 + static_cast<GLuint>(context->getCaps().maxColorAttachments))
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kExceedsMaxColorAttachments); returnfalse;
}
} else
{ switch (attachments[i])
{ case GL_DEPTH_ATTACHMENT: case GL_STENCIL_ATTACHMENT: case GL_DEPTH_STENCIL_ATTACHMENT: if (defaultFramebuffer)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kDefaultFramebufferInvalidAttachment); returnfalse;
} break; case GL_COLOR: case GL_DEPTH: case GL_STENCIL: if (!defaultFramebuffer)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kDefaultFramebufferAttachmentOnUserFBO); returnfalse;
} break; default:
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidAttachment); returnfalse;
}
}
}
returntrue;
}
bool ValidateInsertEventMarkerEXT(const Context *context,
angle::EntryPoint entryPoint,
GLsizei length, constchar *marker)
{ if (!context->getExtensions().debugMarkerEXT)
{ // The debug marker calls should not set error state // However, it seems reasonable to set an error state if the extension is not enabled
context->validationError(entryPoint, GL_INVALID_OPERATION, kExtensionNotEnabled); returnfalse;
}
// Note that debug marker calls must not set error state if (length < 0)
{ returnfalse;
}
if (marker == nullptr)
{ returnfalse;
}
returntrue;
}
bool ValidatePushGroupMarkerEXT(const Context *context,
angle::EntryPoint entryPoint,
GLsizei length, constchar *marker)
{ if (!context->getExtensions().debugMarkerEXT)
{ // The debug marker calls should not set error state // However, it seems reasonable to set an error state if the extension is not enabled
context->validationError(entryPoint, GL_INVALID_OPERATION, kExtensionNotEnabled); returnfalse;
}
// Note that debug marker calls must not set error state if (length < 0)
{ returnfalse;
}
ASSERT(context->getDisplay()); if (!context->getDisplay()->isValidImage(imageObject))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidEGLImage); returnfalse;
}
if (imageObject->getSamples() > 0)
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kEGLImageCannotCreate2DMultisampled); returnfalse;
}
if (!imageObject->isTexturable(context))
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kEGLImageTextureFormatNotSupported); returnfalse;
}
// Validate source egl image and target texture are compatible
size_t depth = static_cast<size_t>(imageObject->getExtents().depth); if (imageObject->isYUV() && type != TextureType::External)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, "Image is YUV, target must be TEXTURE_EXTERNAL_OES"); returnfalse;
}
if (depth > 1 && type != TextureType::_2DArray && type != TextureType::CubeMap &&
type != TextureType::CubeMapArray && type != TextureType::_3D)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kEGLImageTextureTargetMismatch); returnfalse;
}
if (imageObject->isCubeMap() && type != TextureType::CubeMapArray &&
(type != TextureType::CubeMap || depth > gl::kCubeFaceCount))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kEGLImageTextureTargetMismatch); returnfalse;
}
if (imageObject->getLevelCount() > 1 && type == TextureType::External)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kEGLImageTextureTargetMismatch); returnfalse;
}
// 3d EGLImages are currently not supported if (type == TextureType::_3D)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kEGLImageTextureTargetMismatch); returnfalse;
}
if (imageObject->hasProtectedContent() && !context->getState().hasProtectedContent())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, "Mismatch between Image and Context Protected Content state"); returnfalse;
}
if (!programObject->isLinked())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kProgramNotLinked); returnfalse;
}
if (context->getCaps().programBinaryFormats.empty())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kNoProgramBinaryFormats); returnfalse;
}
returntrue;
}
bool ValidateDrawBuffersBase(const Context *context,
angle::EntryPoint entryPoint,
GLsizei n, const GLenum *bufs)
{ // INVALID_VALUE is generated if n is negative or greater than value of MAX_DRAW_BUFFERS if (n < 0)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kNegativeCount); returnfalse;
} if (n > context->getCaps().maxDrawBuffers)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kIndexExceedsMaxDrawBuffer); returnfalse;
}
// This should come first before the check for the default frame buffer // because when we switch to ES3.1+, invalid enums will return INVALID_ENUM // rather than INVALID_OPERATION for (int colorAttachment = 0; colorAttachment < n; colorAttachment++)
{ const GLenum attachment = GL_COLOR_ATTACHMENT0_EXT + colorAttachment;
if (bufs[colorAttachment] != GL_NONE && bufs[colorAttachment] != GL_BACK &&
(bufs[colorAttachment] < GL_COLOR_ATTACHMENT0 ||
bufs[colorAttachment] > GL_COLOR_ATTACHMENT31))
{ // Value in bufs is not NONE, BACK, or GL_COLOR_ATTACHMENTi // The 3.0.4 spec says to generate GL_INVALID_OPERATION here, but this // was changed to GL_INVALID_ENUM in 3.1, which dEQP also expects. // 3.1 is still a bit ambiguous about the error, but future specs are // expected to clarify that GL_INVALID_ENUM is the correct error.
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidDrawBuffer); returnfalse;
} elseif (bufs[colorAttachment] >= maxColorAttachment)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kExceedsMaxColorAttachments); returnfalse;
} elseif (bufs[colorAttachment] != GL_NONE && bufs[colorAttachment] != attachment &&
frameBufferId.value != 0)
{ // INVALID_OPERATION-GL is bound to buffer and ith argument // is not COLOR_ATTACHMENTi or NONE
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidDrawBufferValue); returnfalse;
}
}
// INVALID_OPERATION is generated if GL is bound to the default framebuffer // and n is not 1 or bufs is bound to value other than BACK and NONE if (frameBufferId.value == 0)
{ if (n != 1)
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kInvalidDrawBufferCountForDefault); returnfalse;
}
// GLES 3.0 section 2.10.1: "Attempts to attempts to modify or query buffer object state for a // target bound to zero generate an INVALID_OPERATION error." // GLES 3.1 section 6.6 explicitly specifies this error. if (context->getState().getTargetBuffer(target) == nullptr)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kBufferPointerNotAvailable); returnfalse;
}
// It is invalid if any of bufferStorageMatchedAccessBits bits are included in access, // but the same bits are not included in the buffer's storage flags
constexpr GLbitfield kBufferStorageMatchedAccessBits = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT_EXT |
GL_MAP_COHERENT_BIT_EXT;
GLbitfield accessFlags = access & kBufferStorageMatchedAccessBits; if ((accessFlags & buffer->getStorageExtUsageFlags()) != accessFlags)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidAccessBits); returnfalse;
}
} elseif ((access & ~kAllAccessBits) != 0)
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidAccessBits); returnfalse;
}
if (length == 0)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kLengthZero); returnfalse;
}
if (buffer->isMapped())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kBufferAlreadyMapped); returnfalse;
}
// Check for invalid bit combinations if ((access & (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT)) == 0)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidAccessBitsReadWrite); returnfalse;
}
int clientVersion = context->getClientMajorVersion();
switch (pname)
{ case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL: case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: break;
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR: case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR: if (clientVersion < 3 ||
!(context->getExtensions().multiviewOVR || context->getExtensions().multiview2OVR))
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT: if (!context->getExtensions().multisampledRenderToTextureEXT)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: if (clientVersion < 3 && !context->getExtensions().sRGBEXT)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE: case GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: case GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE: case GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: case GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: case GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: case GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER: if (clientVersion < 3)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kES3Required); returnfalse;
} break;
case GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT: if (!context->getExtensions().geometryShaderAny() &&
context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kGeometryShaderExtensionNotEnabled); returnfalse;
} break;
// Determine if the attachment is a valid enum switch (attachment)
{ case GL_BACK: case GL_DEPTH: case GL_STENCIL: if (clientVersion < 3)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidAttachment); returnfalse;
} break;
case GL_DEPTH_STENCIL_ATTACHMENT: if (clientVersion < 3 && !context->isWebGL1())
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidAttachment); returnfalse;
} break;
case GL_COLOR_ATTACHMENT0: case GL_DEPTH_ATTACHMENT: case GL_STENCIL_ATTACHMENT: break;
switch (pname)
{ case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: if (attachmentObject->type() != GL_RENDERBUFFER &&
attachmentObject->type() != GL_TEXTURE)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kFramebufferIncompleteAttachment); returnfalse;
} break;
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL: if (attachmentObject->type() != GL_TEXTURE)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kFramebufferIncompleteAttachment); returnfalse;
} break;
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: if (attachmentObject->type() != GL_TEXTURE)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kFramebufferIncompleteAttachment); returnfalse;
} break;
case GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: if (attachment == GL_DEPTH_STENCIL_ATTACHMENT)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidAttachment); returnfalse;
} break;
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER: if (attachmentObject->type() != GL_TEXTURE)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kFramebufferIncompleteAttachment); returnfalse;
} break;
default: break;
}
} else
{ // ES 2.0.25 spec pg 127 states that if the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE // is NONE, then querying any other pname will generate INVALID_ENUM.
// ES 3.0.2 spec pg 235 states that if the attachment type is none, // GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME will return zero and be an // INVALID_OPERATION for all other pnames
switch (pname)
{ case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: break;
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: if (clientVersion < 3)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kInvalidFramebufferAttachmentParameter); returnfalse;
} break;
if (context->isContextLost())
{
context->validationError(entryPoint, GL_CONTEXT_LOST, kContextLost);
if (context->getExtensions().parallelShaderCompileKHR && pname == GL_COMPLETION_STATUS_KHR)
{ // Generate an error but still return true, the context still needs to return a // value in this case. returntrue;
} else
{ returnfalse;
}
}
// Special case for GL_COMPLETION_STATUS_KHR: don't resolve the link. Otherwise resolve it now.
Program *programObject = (pname == GL_COMPLETION_STATUS_KHR)
? GetValidProgramNoResolve(context, entryPoint, program)
: GetValidProgram(context, entryPoint, program); if (!programObject)
{ returnfalse;
}
switch (pname)
{ case GL_DELETE_STATUS: case GL_LINK_STATUS: case GL_VALIDATE_STATUS: case GL_INFO_LOG_LENGTH: case GL_ATTACHED_SHADERS: case GL_ACTIVE_ATTRIBUTES: case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH: case GL_ACTIVE_UNIFORMS: case GL_ACTIVE_UNIFORM_MAX_LENGTH: break;
case GL_PROGRAM_BINARY_LENGTH: if (context->getClientMajorVersion() < 3 &&
!context->getExtensions().getProgramBinaryOES)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_ACTIVE_UNIFORM_BLOCKS: case GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH: case GL_TRANSFORM_FEEDBACK_BUFFER_MODE: case GL_TRANSFORM_FEEDBACK_VARYINGS: case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH: case GL_PROGRAM_BINARY_RETRIEVABLE_HINT: if (context->getClientMajorVersion() < 3)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kEnumRequiresGLES30); returnfalse;
} break;
case GL_PROGRAM_SEPARABLE: case GL_ACTIVE_ATOMIC_COUNTER_BUFFERS: if (context->getClientVersion() < Version(3, 1))
{
context->validationError(entryPoint, GL_INVALID_ENUM, kEnumRequiresGLES31); returnfalse;
} break;
case GL_COMPUTE_WORK_GROUP_SIZE: if (context->getClientVersion() < Version(3, 1))
{
context->validationError(entryPoint, GL_INVALID_ENUM, kEnumRequiresGLES31); returnfalse;
}
// [OpenGL ES 3.1] Chapter 7.12 Page 122 // An INVALID_OPERATION error is generated if COMPUTE_WORK_GROUP_SIZE is queried for a // program which has not been linked successfully, or which does not contain objects to // form a compute shader. if (!programObject->isLinked())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kProgramNotLinked); returnfalse;
} if (!programObject->getExecutable().hasLinkedShaderStage(ShaderType::Compute))
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kNoActiveComputeShaderStage); returnfalse;
} break;
case GL_GEOMETRY_LINKED_INPUT_TYPE_EXT: case GL_GEOMETRY_LINKED_OUTPUT_TYPE_EXT: case GL_GEOMETRY_LINKED_VERTICES_OUT_EXT: case GL_GEOMETRY_SHADER_INVOCATIONS_EXT: if (!context->getExtensions().geometryShaderAny() &&
context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kGeometryShaderExtensionNotEnabled); returnfalse;
}
// [EXT_geometry_shader] Chapter 7.12 // An INVALID_OPERATION error is generated if GEOMETRY_LINKED_VERTICES_OUT_EXT, // GEOMETRY_LINKED_INPUT_TYPE_EXT, GEOMETRY_LINKED_OUTPUT_TYPE_EXT, or // GEOMETRY_SHADER_INVOCATIONS_EXT are queried for a program which has not been linked // successfully, or which does not contain objects to form a geometry shader. if (!programObject->isLinked())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kProgramNotLinked); returnfalse;
} if (!programObject->getExecutable().hasLinkedShaderStage(ShaderType::Geometry))
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kNoActiveGeometryShaderStage); returnfalse;
} break;
case GL_COMPLETION_STATUS_KHR: if (!context->getExtensions().parallelShaderCompileKHR)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kExtensionNotEnabled); returnfalse;
} break; case GL_TESS_CONTROL_OUTPUT_VERTICES_EXT: case GL_TESS_GEN_MODE_EXT: case GL_TESS_GEN_SPACING_EXT: case GL_TESS_GEN_VERTEX_ORDER_EXT: case GL_TESS_GEN_POINT_MODE_EXT: if (!context->getExtensions().tessellationShaderEXT &&
context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kTessellationShaderExtensionNotEnabled); returnfalse;
} if (!programObject->isLinked())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kProgramNotLinked); returnfalse;
} break; default:
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
}
if (!ValidateRobustBufferSize(context, entryPoint, bufSize, numParams))
{ returnfalse;
}
SetRobustLengthParam(length, numParams);
returntrue;
}
// Perform validation from WebGL 2 section 5.10 "Invalid Clears": // In the WebGL 2 API, trying to perform a clear when there is a mismatch between the type of the // specified clear value and the type of a buffer that is being cleared generates an // INVALID_OPERATION error instead of producing undefined results bool ValidateWebGLFramebufferAttachmentClearType(const Context *context,
angle::EntryPoint entryPoint,
GLint drawbuffer, const GLenum *validComponentTypes,
size_t validComponentTypeCount)
{ const FramebufferAttachment *attachment =
context->getState().getDrawFramebuffer()->getDrawBuffer(drawbuffer); if (attachment)
{
GLenum componentType = attachment->getFormat().info->componentType; const GLenum *end = validComponentTypes + validComponentTypeCount; if (std::find(validComponentTypes, end, componentType) == end)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kNoDefinedClearConversion); returnfalse;
}
}
if (!context->isValidBufferBinding(target))
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidBufferTypes); returnfalse;
}
const Buffer *buffer = context->getState().getTargetBuffer(target); if (!buffer)
{ // A null buffer means that "0" is bound to the requested buffer target
context->validationError(entryPoint, GL_INVALID_OPERATION, kBufferNotBound); returnfalse;
}
switch (pname)
{ case GL_BUFFER_USAGE: case GL_BUFFER_SIZE: break;
case GL_BUFFER_ACCESS_OES: if (!extensions.mapbufferOES)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_BUFFER_MAPPED:
static_assert(GL_BUFFER_MAPPED == GL_BUFFER_MAPPED_OES, "GL enums should be equal."); if (context->getClientMajorVersion() < 3 && !extensions.mapbufferOES &&
!extensions.mapBufferRangeEXT)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_BUFFER_MAP_POINTER: if (!pointerVersion)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidMapPointerQuery); returnfalse;
} break;
case GL_BUFFER_ACCESS_FLAGS: case GL_BUFFER_MAP_OFFSET: case GL_BUFFER_MAP_LENGTH: if (context->getClientMajorVersion() < 3 && !extensions.mapBufferRangeEXT)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_MEMORY_SIZE_ANGLE: if (!context->getExtensions().memorySizeANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} break;
case GL_RESOURCE_INITIALIZED_ANGLE: if (!context->getExtensions().robustResourceInitializationANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kRobustResourceInitializationExtensionRequired); returnfalse;
} break;
switch (pname)
{ case GL_RENDERBUFFER_WIDTH: case GL_RENDERBUFFER_HEIGHT: case GL_RENDERBUFFER_INTERNAL_FORMAT: case GL_RENDERBUFFER_RED_SIZE: case GL_RENDERBUFFER_GREEN_SIZE: case GL_RENDERBUFFER_BLUE_SIZE: case GL_RENDERBUFFER_ALPHA_SIZE: case GL_RENDERBUFFER_DEPTH_SIZE: case GL_RENDERBUFFER_STENCIL_SIZE: break;
case GL_RENDERBUFFER_SAMPLES_ANGLE: if (context->getClientMajorVersion() < 3 &&
!context->getExtensions().framebufferMultisampleANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} break;
case GL_MEMORY_SIZE_ANGLE: if (!context->getExtensions().memorySizeANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} break;
case GL_IMPLEMENTATION_COLOR_READ_FORMAT: case GL_IMPLEMENTATION_COLOR_READ_TYPE: if (!context->getExtensions().getImageANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kGetImageExtensionNotEnabled); returnfalse;
} break;
case GL_RESOURCE_INITIALIZED_ANGLE: if (!context->getExtensions().robustResourceInitializationANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kRobustResourceInitializationExtensionRequired); returnfalse;
} break;
if (context->isContextLost())
{
context->validationError(entryPoint, GL_CONTEXT_LOST, kContextLost);
if (context->getExtensions().parallelShaderCompileKHR && pname == GL_COMPLETION_STATUS_KHR)
{ // Generate an error but still return true, the context still needs to return a // value in this case. returntrue;
} else
{ returnfalse;
}
}
if (GetValidShader(context, entryPoint, shader) == nullptr)
{ returnfalse;
}
switch (pname)
{ case GL_SHADER_TYPE: case GL_DELETE_STATUS: case GL_COMPILE_STATUS: case GL_INFO_LOG_LENGTH: case GL_SHADER_SOURCE_LENGTH: break;
case GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE: if (!context->getExtensions().translatedShaderSourceANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} break;
case GL_COMPLETION_STATUS_KHR: if (!context->getExtensions().parallelShaderCompileKHR)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kExtensionNotEnabled); returnfalse;
} break;
if (context->getTextureByType(target) == nullptr)
{ // Should only be possible for external textures
context->validationError(entryPoint, GL_INVALID_ENUM, kTextureNotBound); returnfalse;
}
switch (pname)
{ case GL_TEXTURE_MAG_FILTER: case GL_TEXTURE_MIN_FILTER: case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: break;
case GL_TEXTURE_USAGE_ANGLE: if (!context->getExtensions().textureUsageANGLE)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT: if (!ValidateTextureMaxAnisotropyExtensionEnabled(context, entryPoint))
{ returnfalse;
} break;
case GL_TEXTURE_IMMUTABLE_FORMAT: if (context->getClientMajorVersion() < 3 && !context->getExtensions().textureStorageEXT)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_TEXTURE_WRAP_R: case GL_TEXTURE_IMMUTABLE_LEVELS: case GL_TEXTURE_SWIZZLE_R: case GL_TEXTURE_SWIZZLE_G: case GL_TEXTURE_SWIZZLE_B: case GL_TEXTURE_SWIZZLE_A: case GL_TEXTURE_BASE_LEVEL: case GL_TEXTURE_MAX_LEVEL: case GL_TEXTURE_MIN_LOD: case GL_TEXTURE_MAX_LOD: if (context->getClientMajorVersion() < 3)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kEnumRequiresGLES30); returnfalse;
} break;
case GL_TEXTURE_COMPARE_MODE: case GL_TEXTURE_COMPARE_FUNC: if (context->getClientMajorVersion() < 3 && !context->getExtensions().shadowSamplersEXT)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_TEXTURE_SRGB_DECODE_EXT: if (!context->getExtensions().textureSRGBDecodeEXT)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_DEPTH_STENCIL_TEXTURE_MODE: case GL_IMAGE_FORMAT_COMPATIBILITY_TYPE: if (context->getClientVersion() < ES_3_1)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kEnumRequiresGLES31); returnfalse;
} break;
case GL_GENERATE_MIPMAP: case GL_TEXTURE_CROP_RECT_OES: // TODO(lfy@google.com): Restrict to GL_OES_draw_texture // after GL_OES_draw_texture functionality implemented if (context->getClientMajorVersion() > 1)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kGLES1Only); returnfalse;
} break;
case GL_MEMORY_SIZE_ANGLE: if (!context->getExtensions().memorySizeANGLE)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_TEXTURE_BORDER_COLOR: if (!context->getExtensions().textureBorderClampOES &&
context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} break;
case GL_TEXTURE_NATIVE_ID_ANGLE: if (!context->getExtensions().textureExternalUpdateANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} break;
case GL_IMPLEMENTATION_COLOR_READ_FORMAT: case GL_IMPLEMENTATION_COLOR_READ_TYPE: if (!context->getExtensions().getImageANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kGetImageExtensionNotEnabled); returnfalse;
} break;
case GL_RESOURCE_INITIALIZED_ANGLE: if (!context->getExtensions().robustResourceInitializationANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kRobustResourceInitializationExtensionRequired); returnfalse;
} break;
case GL_TEXTURE_PROTECTED_EXT: if (!context->getExtensions().protectedTexturesEXT)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kProtectedTexturesExtensionRequired); returnfalse;
} break;
if (index >= static_cast<GLuint>(context->getCaps().maxVertexAttributes))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kIndexExceedsMaxVertexAttribute); returnfalse;
}
if (pointer)
{ if (pname != GL_VERTEX_ATTRIB_ARRAY_POINTER)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
}
} else
{ switch (pname)
{ case GL_VERTEX_ATTRIB_ARRAY_ENABLED: case GL_VERTEX_ATTRIB_ARRAY_SIZE: case GL_VERTEX_ATTRIB_ARRAY_STRIDE: case GL_VERTEX_ATTRIB_ARRAY_TYPE: case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED: case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: case GL_CURRENT_VERTEX_ATTRIB: break;
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
static_assert(
GL_VERTEX_ATTRIB_ARRAY_DIVISOR == GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE, "ANGLE extension enums not equal to GL enums."); if (context->getClientMajorVersion() < 3 &&
!context->getExtensions().instancedArraysAny())
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported,
pname); returnfalse;
} break;
case GL_VERTEX_ATTRIB_ARRAY_INTEGER: if (context->getClientMajorVersion() < 3)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported,
pname); returnfalse;
} break;
case GL_VERTEX_ATTRIB_BINDING: case GL_VERTEX_ATTRIB_RELATIVE_OFFSET: if (context->getClientVersion() < ES_3_1)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kEnumRequiresGLES31); returnfalse;
} break;
if (length)
{ if (pname == GL_CURRENT_VERTEX_ATTRIB)
{
*length = 4;
} else
{
*length = 1;
}
}
returntrue;
}
bool ValidatePixelPack(const Context *context,
angle::EntryPoint entryPoint,
GLenum format,
GLenum type,
GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLsizei bufSize,
GLsizei *length, constvoid *pixels)
{ // Check for pixel pack buffer related API errors
Buffer *pixelPackBuffer = context->getState().getTargetBuffer(BufferBinding::PixelPack); if (pixelPackBuffer != nullptr && pixelPackBuffer->isMapped())
{ // ...the buffer object's data store is currently mapped.
context->validationError(entryPoint, GL_INVALID_OPERATION, kBufferMapped); returnfalse;
} if (pixelPackBuffer != nullptr &&
pixelPackBuffer->hasWebGLXFBBindingConflict(context->isWebGL()))
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kPixelPackBufferBoundForTransformFeedback); returnfalse;
}
// .. the data would be packed to the buffer object such that the memory writes required // would exceed the data store size. const InternalFormat &formatInfo = GetInternalFormatInfo(format, type); const Extents size(width, height, 1); constauto &pack = context->getState().getPackState();
if (checkedEndByte.ValueOrDie() > static_cast<size_t>(pixelPackBuffer->getSize()))
{ // Overflow past the end of the buffer
context->validationError(entryPoint, GL_INVALID_OPERATION, kParamOverflow); returnfalse;
}
}
if (pixelPackBuffer == nullptr && length != nullptr)
{ if (endByte > static_cast<size_t>(std::numeric_limits<GLsizei>::max()))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kIntegerOverflow); returnfalse;
}
*length = static_cast<GLsizei>(endByte);
}
if (context->isWebGL())
{ // WebGL 2.0 disallows the scenario: // GL_PACK_SKIP_PIXELS + width > DataStoreWidth // where: // DataStoreWidth = (GL_PACK_ROW_LENGTH ? GL_PACK_ROW_LENGTH : width) // Since these two pack parameters can only be set to non-zero values // on WebGL 2.0 contexts, verify them for all WebGL contexts.
GLint dataStoreWidth = pack.rowLength ? pack.rowLength : width; if (pack.skipPixels + width > dataStoreWidth)
{
context->validationError(entryPoint, GL_INVALID_OPERATION,
kInvalidPackParametersForWebGL); returnfalse;
}
}
if (!ValidateFramebufferComplete(context, entryPoint, readFramebuffer))
{ returnfalse;
}
// needIntrinsic = true. Treat renderToTexture textures as single sample since they will be // resolved before reading. if (!readFramebuffer->isDefault() &&
!ValidateFramebufferNotMultisampled(context, entryPoint, readFramebuffer, true))
{ returnfalse;
}
if (readFramebuffer->getReadBufferState() == GL_NONE)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kReadBufferNone); returnfalse;
}
const FramebufferAttachment *readBuffer = nullptr; switch (format)
{ case GL_DEPTH_COMPONENT:
readBuffer = readFramebuffer->getDepthAttachment(); break; case GL_STENCIL_INDEX_OES: case GL_DEPTH_STENCIL_OES:
readBuffer = readFramebuffer->getStencilOrDepthStencilAttachment(); break; default:
readBuffer = readFramebuffer->getReadColorAttachment(); break;
}
// OVR_multiview2, Revision 1: // ReadPixels generates an INVALID_FRAMEBUFFER_OPERATION error if // the number of views in the current read framebuffer is more than one. if (readFramebuffer->readDisallowedByMultiview())
{
context->validationError(entryPoint, GL_INVALID_FRAMEBUFFER_OPERATION,
kMultiviewReadFramebuffer); returnfalse;
}
if (context->isWebGL())
{ // The ES 2.0 spec states that the format must be "among those defined in table 3.4, // excluding formats LUMINANCE and LUMINANCE_ALPHA.". This requires validating the format // and type before validating the combination of format and type. However, the // dEQP-GLES3.functional.negative_api.buffer.read_pixels passes GL_LUMINANCE as a format and // verifies that GL_INVALID_OPERATION is generated. // TODO(geofflang): Update this check to be done in all/no cases once this is resolved in // dEQP/WebGL. if (!ValidReadPixelsFormatEnum(context, format))
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidFormat); returnfalse;
}
if (!ValidReadPixelsTypeEnum(context, type))
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidType); returnfalse;
}
}
// WebGL 1.0 [Section 6.26] Reading From a Missing Attachment // In OpenGL ES it is undefined what happens when an operation tries to read from a missing // attachment and WebGL defines it to be an error. We do the check unconditionally as the // situation is an application error that would lead to a crash in ANGLE. if (readBuffer == nullptr)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kMissingReadAttachment); returnfalse;
}
switch (format)
{ case GL_DEPTH_COMPONENT: case GL_STENCIL_INDEX_OES: case GL_DEPTH_STENCIL_OES: // Only rely on ValidReadPixelsFormatType for depth/stencil formats break; default:
currentFormat = readFramebuffer->getImplementationColorReadFormat(context);
currentType = readFramebuffer->getImplementationColorReadType(context); break;
}
bool validFormatTypeCombination =
ValidReadPixelsFormatType(context, readBuffer->getFormat().info, format, type);
if (!(currentFormat == format && currentType == type) && !validFormatTypeCombination)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kMismatchedTypeAndFormat); returnfalse;
}
if (!ValidatePixelPack(context, entryPoint, format, type, x, y, width, height, bufSize, length,
pixels))
{ returnfalse;
}
auto getClippedExtent = [](GLint start, GLsizei length, int bufferSize, GLsizei *outExtent) {
angle::CheckedNumeric<int> clippedExtent(length); if (start < 0)
{ // "subtract" the area that is less than 0
clippedExtent += start;
}
if (context->getTextureByType(target) == nullptr)
{ // Should only be possible for external textures
context->validationError(entryPoint, GL_INVALID_ENUM, kTextureNotBound); returnfalse;
}
switch (pname)
{ case GL_TEXTURE_WRAP_R: case GL_TEXTURE_SWIZZLE_R: case GL_TEXTURE_SWIZZLE_G: case GL_TEXTURE_SWIZZLE_B: case GL_TEXTURE_SWIZZLE_A: case GL_TEXTURE_BASE_LEVEL: case GL_TEXTURE_MAX_LEVEL: case GL_TEXTURE_COMPARE_MODE: case GL_TEXTURE_COMPARE_FUNC: case GL_TEXTURE_MIN_LOD: case GL_TEXTURE_MAX_LOD: if (context->getClientMajorVersion() < 3 &&
!(pname == GL_TEXTURE_WRAP_R && context->getExtensions().texture3DOES))
{
context->validationError(entryPoint, GL_INVALID_ENUM, kES3Required); returnfalse;
} if (target == TextureType::External &&
!context->getExtensions().EGLImageExternalEssl3OES)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} if (target == TextureType::VideoImage && !context->getExtensions().videoTextureWEBGL)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname);
} break;
case GL_GENERATE_MIPMAP: case GL_TEXTURE_CROP_RECT_OES: if (context->getClientMajorVersion() > 1)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kGLES1Only); returnfalse;
} break;
default: break;
}
if (target == TextureType::_2DMultisample || target == TextureType::_2DMultisampleArray)
{ switch (pname)
{ case GL_TEXTURE_MIN_FILTER: case GL_TEXTURE_MAG_FILTER: case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: case GL_TEXTURE_WRAP_R: case GL_TEXTURE_MIN_LOD: case GL_TEXTURE_MAX_LOD: case GL_TEXTURE_COMPARE_MODE: case GL_TEXTURE_COMPARE_FUNC: case GL_TEXTURE_BORDER_COLOR:
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidPname); returnfalse;
}
}
switch (pname)
{ case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: case GL_TEXTURE_WRAP_R:
{ bool restrictedWrapModes = ((target == TextureType::External &&
!context->getExtensions().EGLImageExternalWrapModesEXT) ||
target == TextureType::Rectangle); if (!ValidateTextureWrapModeValue(context, entryPoint, params, restrictedWrapModes))
{ returnfalse;
}
} break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
{
GLfloat paramValue = static_cast<GLfloat>(params[0]); if (!ValidateTextureMaxAnisotropyValue(context, entryPoint, paramValue))
{ returnfalse;
}
ASSERT(static_cast<ParamType>(paramValue) == params[0]);
} break;
case GL_TEXTURE_MIN_LOD: case GL_TEXTURE_MAX_LOD: // any value is permissible break;
case GL_TEXTURE_COMPARE_MODE: if (!ValidateTextureCompareModeValue(context, entryPoint, params))
{ returnfalse;
} break;
case GL_TEXTURE_COMPARE_FUNC: if (!ValidateTextureCompareFuncValue(context, entryPoint, params))
{ returnfalse;
} break;
case GL_TEXTURE_SWIZZLE_R: case GL_TEXTURE_SWIZZLE_G: case GL_TEXTURE_SWIZZLE_B: case GL_TEXTURE_SWIZZLE_A: switch (ConvertToGLenum(params[0]))
{ case GL_RED: case GL_GREEN: case GL_BLUE: case GL_ALPHA: case GL_ZERO: case GL_ONE: break;
if (context->getClientMajorVersion() < 3)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kES3Required); returnfalse;
}
Program *programObject = GetValidProgram(context, entryPoint, program); if (!programObject)
{ returnfalse;
}
if (uniformBlockIndex.value >= programObject->getActiveUniformBlockCount())
{
context->validationError(entryPoint, GL_INVALID_VALUE,
kIndexExceedsActiveUniformBlockCount); returnfalse;
}
switch (pname)
{ case GL_UNIFORM_BLOCK_BINDING: case GL_UNIFORM_BLOCK_DATA_SIZE: case GL_UNIFORM_BLOCK_NAME_LENGTH: case GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: case GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: case GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER: case GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER: break;
switch (pname)
{ case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: case GL_TEXTURE_WRAP_R: if (!ValidateTextureWrapModeValue(context, entryPoint, params, false))
{ returnfalse;
} break;
case GL_TEXTURE_MIN_FILTER: if (!ValidateTextureMinFilterValue(context, entryPoint, params, false))
{ returnfalse;
} break;
case GL_TEXTURE_MAG_FILTER: if (!ValidateTextureMagFilterValue(context, entryPoint, params))
{ returnfalse;
} break;
case GL_TEXTURE_MIN_LOD: case GL_TEXTURE_MAX_LOD: // any value is permissible break;
case GL_TEXTURE_COMPARE_MODE: if (!ValidateTextureCompareModeValue(context, entryPoint, params))
{ returnfalse;
} break;
case GL_TEXTURE_COMPARE_FUNC: if (!ValidateTextureCompareFuncValue(context, entryPoint, params))
{ returnfalse;
} break;
case GL_TEXTURE_SRGB_DECODE_EXT: if (!ValidateTextureSRGBDecodeValue(context, entryPoint, params))
{ returnfalse;
} break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
{
GLfloat paramValue = static_cast<GLfloat>(params[0]); if (!ValidateTextureMaxAnisotropyValue(context, entryPoint, paramValue))
{ returnfalse;
}
} break;
case GL_TEXTURE_BORDER_COLOR: if (!context->getExtensions().textureBorderClampOES &&
context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} if (!vectorParams)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInsufficientBufferSize); returnfalse;
} break;
if (context->getClientMajorVersion() < 3)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kES3Required); returnfalse;
}
if (!context->isSampler(sampler))
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kInvalidSampler); returnfalse;
}
switch (pname)
{ case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: case GL_TEXTURE_WRAP_R: case GL_TEXTURE_MIN_FILTER: case GL_TEXTURE_MAG_FILTER: case GL_TEXTURE_MIN_LOD: case GL_TEXTURE_MAX_LOD: case GL_TEXTURE_COMPARE_MODE: case GL_TEXTURE_COMPARE_FUNC: break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT: if (!ValidateTextureMaxAnisotropyExtensionEnabled(context, entryPoint))
{ returnfalse;
} break;
case GL_TEXTURE_SRGB_DECODE_EXT: if (!context->getExtensions().textureSRGBDecodeEXT)
{
context->validationErrorF(entryPoint, GL_INVALID_ENUM, kEnumNotSupported, pname); returnfalse;
} break;
case GL_TEXTURE_BORDER_COLOR: if (!context->getExtensions().textureBorderClampOES &&
context->getClientVersion() < ES_3_2)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kExtensionNotEnabled); returnfalse;
} break;
// The ES3.1 spec(section 8.8) states that an INVALID_ENUM error is generated if internalformat // is one of the unsized base internalformats listed in table 8.11. const InternalFormat &formatInfo = GetSizedInternalFormatInfo(internalFormat); if (formatInfo.internalFormat == GL_NONE)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kUnsizedInternalFormatUnsupported); returnfalse;
}
if (static_cast<GLuint>(samples) > formatCaps.getMaxSamples())
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kSamplesOutOfRange); returnfalse;
}
if (!ValidTexLevelDestinationTarget(context, type))
{
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidTextureTarget); returnfalse;
}
if (context->getTextureByType(type) == nullptr)
{
context->validationError(entryPoint, GL_INVALID_ENUM, kTextureNotBound); returnfalse;
}
if (!ValidMipLevel(context, type, level))
{
context->validationError(entryPoint, GL_INVALID_VALUE, kInvalidMipLevel); returnfalse;
}
switch (pname)
{ case GL_TEXTURE_RED_TYPE: case GL_TEXTURE_GREEN_TYPE: case GL_TEXTURE_BLUE_TYPE: case GL_TEXTURE_ALPHA_TYPE: case GL_TEXTURE_DEPTH_TYPE: case GL_TEXTURE_RED_SIZE: case GL_TEXTURE_GREEN_SIZE: case GL_TEXTURE_BLUE_SIZE: case GL_TEXTURE_ALPHA_SIZE: case GL_TEXTURE_DEPTH_SIZE: case GL_TEXTURE_STENCIL_SIZE: case GL_TEXTURE_SHARED_SIZE: case GL_TEXTURE_INTERNAL_FORMAT: case GL_TEXTURE_WIDTH: case GL_TEXTURE_HEIGHT: case GL_TEXTURE_DEPTH: case GL_TEXTURE_SAMPLES: case GL_TEXTURE_FIXED_SAMPLE_LOCATIONS: case GL_TEXTURE_COMPRESSED: break;
case GL_RESOURCE_INITIALIZED_ANGLE: if (!context->getExtensions().robustResourceInitializationANGLE)
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kRobustResourceInitializationExtensionRequired); returnfalse;
} break;
case GL_TEXTURE_BUFFER_DATA_STORE_BINDING: case GL_TEXTURE_BUFFER_OFFSET: case GL_TEXTURE_BUFFER_SIZE: if (context->getClientVersion() < Version(3, 2) &&
!context->getExtensions().textureBufferAny())
{
context->validationError(entryPoint, GL_INVALID_ENUM,
kTextureBufferExtensionNotAvailable); returnfalse;
} break;
void RecordDrawAttribsError(const Context *context, angle::EntryPoint entryPoint)
{ // An overflow can happen when adding the offset. Check against a special constant. if (context->getStateCache().getNonInstancedVertexElementLimit() ==
VertexAttribute::kIntegerOverflow ||
context->getStateCache().getInstancedVertexElementLimit() ==
VertexAttribute::kIntegerOverflow)
{
context->validationError(entryPoint, GL_INVALID_OPERATION, kIntegerOverflow);
} else
{ // [OpenGL ES 3.0.2] section 2.9.4 page 40: // We can return INVALID_OPERATION if our buffer does not have enough backing data.
context->validationError(entryPoint, GL_INVALID_OPERATION, kInsufficientVertexBufferSize);
}
}
switch (current)
{ case GraphicsResetStatus::GuiltyContextReset: case GraphicsResetStatus::InnocentContextReset: case GraphicsResetStatus::UnknownContextReset: break;
switch (other)
{ case GraphicsResetStatus::GuiltyContextReset: case GraphicsResetStatus::InnocentContextReset: case GraphicsResetStatus::UnknownContextReset: break;
bool ValidateLogicOpCommon(const Context *context,
angle::EntryPoint entryPoint,
LogicalOperation opcodePacked)
{ switch (opcodePacked)
{ case LogicalOperation::And: case LogicalOperation::AndInverted: case LogicalOperation::AndReverse: case LogicalOperation::Clear: case LogicalOperation::Copy: case LogicalOperation::CopyInverted: case LogicalOperation::Equiv: case LogicalOperation::Invert: case LogicalOperation::Nand: case LogicalOperation::Noop: case LogicalOperation::Nor: case LogicalOperation::Or: case LogicalOperation::OrInverted: case LogicalOperation::OrReverse: case LogicalOperation::Set: case LogicalOperation::Xor: returntrue; default:
context->validationError(entryPoint, GL_INVALID_ENUM, kInvalidLogicOp); returnfalse;
}
}
} // namespace gl
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.291Bemerkung:
(vorverarbeitet am 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.