blob: 6debd4f7bc380601c65fdb42236da7574bce9a57 [file] [log] [blame]
//
// Copyright (c) 2016 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.
//
// validationES31.cpp: Validation functions for OpenGL ES 3.1 entry point parameters
#include "libANGLE/validationES31.h"
#include "libANGLE/Context.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/VertexArray.h"
#include "libANGLE/validationES.h"
#include "libANGLE/validationES3.h"
#include "common/utilities.h"
using namespace angle;
namespace gl
{
namespace
{
bool ValidateNamedProgramInterface(GLenum programInterface)
{
switch (programInterface)
{
case GL_UNIFORM:
case GL_UNIFORM_BLOCK:
case GL_PROGRAM_INPUT:
case GL_PROGRAM_OUTPUT:
case GL_TRANSFORM_FEEDBACK_VARYING:
case GL_BUFFER_VARIABLE:
case GL_SHADER_STORAGE_BLOCK:
return true;
default:
return false;
}
}
bool ValidateProgramResourceIndex(const Program *programObject,
GLenum programInterface,
GLuint index)
{
switch (programInterface)
{
case GL_PROGRAM_INPUT:
return (index < static_cast<GLuint>(programObject->getActiveAttributeCount()));
case GL_PROGRAM_OUTPUT:
return (index < static_cast<GLuint>(programObject->getOutputResourceCount()));
// TODO(Jie): more interfaces.
case GL_UNIFORM:
case GL_UNIFORM_BLOCK:
case GL_TRANSFORM_FEEDBACK_VARYING:
case GL_BUFFER_VARIABLE:
case GL_SHADER_STORAGE_BLOCK:
UNIMPLEMENTED();
return false;
default:
UNREACHABLE();
return false;
}
}
} // anonymous namespace
bool ValidateGetBooleani_v(Context *context, GLenum target, GLuint index, GLboolean *data)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1"));
return false;
}
if (!ValidateIndexedStateQuery(context, target, index, nullptr))
{
return false;
}
return true;
}
bool ValidateGetBooleani_vRobustANGLE(Context *context,
GLenum target,
GLuint index,
GLsizei bufSize,
GLsizei *length,
GLboolean *data)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1"));
return false;
}
if (!ValidateRobustEntryPoint(context, bufSize))
{
return false;
}
if (!ValidateIndexedStateQuery(context, target, index, length))
{
return false;
}
if (!ValidateRobustBufferSize(context, bufSize, *length))
{
return false;
}
return true;
}
bool ValidateDrawIndirectBase(Context *context, GLenum mode, const void *indirect)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1"));
return false;
}
// Here the third parameter 1 is only to pass the count validation.
if (!ValidateDrawBase(context, mode, 1))
{
return false;
}
const State &state = context->getGLState();
// An INVALID_OPERATION error is generated if zero is bound to VERTEX_ARRAY_BINDING,
// DRAW_INDIRECT_BUFFER or to any enabled vertex array.
if (!state.getVertexArrayId())
{
context->handleError(Error(GL_INVALID_OPERATION, "zero is bound to VERTEX_ARRAY_BINDING"));
return false;
}
gl::Buffer *drawIndirectBuffer = state.getDrawIndirectBuffer();
if (!drawIndirectBuffer)
{
context->handleError(Error(GL_INVALID_OPERATION, "zero is bound to DRAW_INDIRECT_BUFFER"));
return false;
}
// An INVALID_VALUE error is generated if indirect is not a multiple of the size, in basic
// machine units, of uint.
GLint64 offset = reinterpret_cast<GLint64>(indirect);
if ((static_cast<GLuint>(offset) % sizeof(GLuint)) != 0)
{
context->handleError(
Error(GL_INVALID_VALUE,
"indirect is not a multiple of the size, in basic machine units, of uint"));
return false;
}
return true;
}
bool ValidateDrawArraysIndirect(Context *context, GLenum mode, const void *indirect)
{
const State &state = context->getGLState();
gl::TransformFeedback *curTransformFeedback = state.getCurrentTransformFeedback();
if (curTransformFeedback && curTransformFeedback->isActive() &&
!curTransformFeedback->isPaused())
{
// An INVALID_OPERATION error is generated if transform feedback is active and not paused.
context->handleError(
Error(GL_INVALID_OPERATION, "transform feedback is active and not paused."));
return false;
}
if (!ValidateDrawIndirectBase(context, mode, indirect))
return false;
gl::Buffer *drawIndirectBuffer = state.getDrawIndirectBuffer();
CheckedNumeric<size_t> checkedOffset(reinterpret_cast<size_t>(indirect));
// In OpenGL ES3.1 spec, session 10.5, it defines the struct of DrawArraysIndirectCommand
// which's size is 4 * sizeof(uint).
auto checkedSum = checkedOffset + 4 * sizeof(GLuint);
if (!checkedSum.IsValid() ||
checkedSum.ValueOrDie() > static_cast<size_t>(drawIndirectBuffer->getSize()))
{
context->handleError(
Error(GL_INVALID_OPERATION,
"the command would source data beyond the end of the buffer object."));
return false;
}
return true;
}
bool ValidateDrawElementsIndirect(Context *context, GLenum mode, GLenum type, const void *indirect)
{
if (!ValidateDrawElementsBase(context, type))
return false;
const State &state = context->getGLState();
const VertexArray *vao = state.getVertexArray();
gl::Buffer *elementArrayBuffer = vao->getElementArrayBuffer().get();
if (!elementArrayBuffer)
{
context->handleError(Error(GL_INVALID_OPERATION, "zero is bound to ELEMENT_ARRAY_BUFFER"));
return false;
}
if (!ValidateDrawIndirectBase(context, mode, indirect))
return false;
gl::Buffer *drawIndirectBuffer = state.getDrawIndirectBuffer();
CheckedNumeric<size_t> checkedOffset(reinterpret_cast<size_t>(indirect));
// In OpenGL ES3.1 spec, session 10.5, it defines the struct of DrawElementsIndirectCommand
// which's size is 5 * sizeof(uint).
auto checkedSum = checkedOffset + 5 * sizeof(GLuint);
if (!checkedSum.IsValid() ||
checkedSum.ValueOrDie() > static_cast<size_t>(drawIndirectBuffer->getSize()))
{
context->handleError(
Error(GL_INVALID_OPERATION,
"the command would source data beyond the end of the buffer object."));
return false;
}
return true;
}
bool ValidateGetTexLevelParameterBase(Context *context,
GLenum target,
GLint level,
GLenum pname,
GLsizei *length)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1"));
return false;
}
if (length)
{
*length = 0;
}
if (!ValidTexLevelDestinationTarget(context, target))
{
context->handleError(Error(GL_INVALID_ENUM, "Invalid texture target"));
return false;
}
if (context->getTargetTexture(IsCubeMapTextureTarget(target) ? GL_TEXTURE_CUBE_MAP : target) ==
nullptr)
{
context->handleError(Error(GL_INVALID_ENUM, "No texture bound."));
return false;
}
if (!ValidMipLevel(context, target, level))
{
context->handleError(Error(GL_INVALID_VALUE));
return false;
}
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:
break;
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:
break;
case GL_TEXTURE_INTERNAL_FORMAT:
case GL_TEXTURE_WIDTH:
case GL_TEXTURE_HEIGHT:
case GL_TEXTURE_DEPTH:
break;
case GL_TEXTURE_SAMPLES:
case GL_TEXTURE_FIXED_SAMPLE_LOCATIONS:
break;
case GL_TEXTURE_COMPRESSED:
break;
default:
context->handleError(Error(GL_INVALID_ENUM, "Unknown pname."));
return false;
}
if (length)
{
*length = 1;
}
return true;
}
bool ValidateGetTexLevelParameterfv(Context *context,
GLenum target,
GLint level,
GLenum pname,
GLfloat *params)
{
return ValidateGetTexLevelParameterBase(context, target, level, pname, nullptr);
}
bool ValidateGetTexLevelParameteriv(Context *context,
GLenum target,
GLint level,
GLenum pname,
GLint *params)
{
return ValidateGetTexLevelParameterBase(context, target, level, pname, nullptr);
}
bool ValidateTexStorage2DMultiSample(Context *context,
GLenum target,
GLsizei samples,
GLint internalFormat,
GLsizei width,
GLsizei height,
GLboolean fixedSampleLocations)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
if (target != GL_TEXTURE_2D_MULTISAMPLE)
{
context->handleError(Error(GL_INVALID_ENUM, "Target must be TEXTURE_2D_MULTISAMPLE."));
return false;
}
if (width < 1 || height < 1)
{
context->handleError(Error(GL_INVALID_VALUE, "Width and height must be positive."));
return false;
}
const Caps &caps = context->getCaps();
if (static_cast<GLuint>(width) > caps.max2DTextureSize ||
static_cast<GLuint>(height) > caps.max2DTextureSize)
{
context->handleError(
Error(GL_INVALID_VALUE,
"Width and height must be less than or equal to GL_MAX_TEXTURE_SIZE."));
return false;
}
if (samples == 0)
{
context->handleError(Error(GL_INVALID_VALUE, "Samples may not be zero."));
return false;
}
const TextureCaps &formatCaps = context->getTextureCaps().get(internalFormat);
if (!formatCaps.renderable)
{
context->handleError(
Error(GL_INVALID_ENUM,
"SizedInternalformat must be color-renderable, depth-renderable, "
"or stencil-renderable."));
return false;
}
// 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->handleError(
Error(GL_INVALID_ENUM,
"Internalformat is one of the unsupported unsized base internalformats."));
return false;
}
if (static_cast<GLuint>(samples) > formatCaps.getMaxSamples())
{
context->handleError(
Error(GL_INVALID_OPERATION,
"Samples must not be greater than maximum supported value for the format."));
return false;
}
Texture *texture = context->getTargetTexture(target);
if (!texture || texture->id() == 0)
{
context->handleError(Error(GL_INVALID_OPERATION, "Zero is bound to target."));
return false;
}
if (texture->getImmutableFormat())
{
context->handleError(
Error(GL_INVALID_OPERATION,
"The value of TEXTURE_IMMUTABLE_FORMAT for the texture "
"currently bound to target on the active texture unit is true."));
return false;
}
return true;
}
bool ValidateGetMultisamplefv(Context *context, GLenum pname, GLuint index, GLfloat *val)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
if (pname != GL_SAMPLE_POSITION)
{
context->handleError(Error(GL_INVALID_ENUM, "Pname must be SAMPLE_POSITION."));
return false;
}
GLint maxSamples = context->getCaps().maxSamples;
if (index >= static_cast<GLuint>(maxSamples))
{
context->handleError(
Error(GL_INVALID_VALUE, "Index must be less than the value of SAMPLES."));
return false;
}
return true;
}
bool ValidationFramebufferParameteri(Context *context, GLenum target, GLenum pname, GLint param)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
if (!ValidFramebufferTarget(target))
{
context->handleError(Error(GL_INVALID_ENUM, "Invalid framebuffer target."));
return false;
}
switch (pname)
{
case GL_FRAMEBUFFER_DEFAULT_WIDTH:
{
GLint maxWidth = context->getCaps().maxFramebufferWidth;
if (param < 0 || param > maxWidth)
{
context->handleError(
Error(GL_INVALID_VALUE,
"Params less than 0 or greater than GL_MAX_FRAMEBUFFER_WIDTH."));
return false;
}
break;
}
case GL_FRAMEBUFFER_DEFAULT_HEIGHT:
{
GLint maxHeight = context->getCaps().maxFramebufferHeight;
if (param < 0 || param > maxHeight)
{
context->handleError(
Error(GL_INVALID_VALUE,
"Params less than 0 or greater than GL_MAX_FRAMEBUFFER_HEIGHT."));
return false;
}
break;
}
case GL_FRAMEBUFFER_DEFAULT_SAMPLES:
{
GLint maxSamples = context->getCaps().maxFramebufferSamples;
if (param < 0 || param > maxSamples)
{
context->handleError(
Error(GL_INVALID_VALUE,
"Params less than 0 or greater than GL_MAX_FRAMEBUFFER_SAMPLES."));
return false;
}
break;
}
case GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:
{
break;
}
default:
{
context->handleError(Error(GL_INVALID_ENUM, "Invalid pname: 0x%X", pname));
return false;
}
}
const Framebuffer *framebuffer = context->getGLState().getTargetFramebuffer(target);
ASSERT(framebuffer);
if (framebuffer->id() == 0)
{
context->handleError(
Error(GL_INVALID_OPERATION, "Default framebuffer is bound to target."));
return false;
}
return true;
}
bool ValidationGetFramebufferParameteri(Context *context,
GLenum target,
GLenum pname,
GLint *params)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
if (!ValidFramebufferTarget(target))
{
context->handleError(Error(GL_INVALID_ENUM, "Invalid framebuffer target."));
return false;
}
switch (pname)
{
case GL_FRAMEBUFFER_DEFAULT_WIDTH:
case GL_FRAMEBUFFER_DEFAULT_HEIGHT:
case GL_FRAMEBUFFER_DEFAULT_SAMPLES:
case GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:
break;
default:
context->handleError(Error(GL_INVALID_ENUM, "Invalid pname: 0x%X", pname));
return false;
}
const Framebuffer *framebuffer = context->getGLState().getTargetFramebuffer(target);
ASSERT(framebuffer);
if (framebuffer->id() == 0)
{
context->handleError(
Error(GL_INVALID_OPERATION, "Default framebuffer is bound to target."));
return false;
}
return true;
}
bool ValidateGetProgramResourceIndex(Context *context,
GLuint program,
GLenum programInterface,
const GLchar *name)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES 3.1."));
return false;
}
Program *programObject = GetValidProgram(context, program);
if (programObject == nullptr)
{
return false;
}
if (!ValidateNamedProgramInterface(programInterface))
{
context->handleError(
Error(GL_INVALID_ENUM, "Invalid program interface: 0x%X", programInterface));
return false;
}
return true;
}
bool ValidateBindVertexBuffer(ValidationContext *context,
GLuint bindingIndex,
GLuint buffer,
GLintptr offset,
GLsizei stride)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
if (!context->isBufferGenerated(buffer))
{
context->handleError(Error(GL_INVALID_OPERATION, "Buffer is not generated."));
return false;
}
const Caps &caps = context->getCaps();
if (bindingIndex >= caps.maxVertexAttribBindings)
{
context->handleError(Error(
GL_INVALID_VALUE, "bindingindex must be smaller than MAX_VERTEX_ATTRIB_BINDINGS."));
return false;
}
if (offset < 0)
{
context->handleError(Error(GL_INVALID_VALUE, "offset cannot be negative."));
return false;
}
if (stride < 0 || stride > caps.maxVertexAttribStride)
{
context->handleError(
Error(GL_INVALID_VALUE, "stride must be between 0 and MAX_VERTEX_ATTRIB_STRIDE."));
return false;
}
// [OpenGL ES 3.1] Section 10.3.1 page 244:
// An INVALID_OPERATION error is generated if the default vertex array object is bound.
if (context->getGLState().getVertexArrayId() == 0)
{
context->handleError(Error(GL_INVALID_OPERATION, "Default vertex array buffer is bound."));
return false;
}
return true;
}
bool ValidateVertexBindingDivisor(ValidationContext *context, GLuint bindingIndex, GLuint divisor)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
const Caps &caps = context->getCaps();
if (bindingIndex >= caps.maxVertexAttribBindings)
{
context->handleError(Error(
GL_INVALID_VALUE, "bindingindex must be smaller than MAX_VERTEX_ATTRIB_BINDINGS."));
return false;
}
// [OpenGL ES 3.1] Section 10.3.1 page 243:
// An INVALID_OPERATION error is generated if the default vertex array object is bound.
if (context->getGLState().getVertexArrayId() == 0)
{
context->handleError(Error(GL_INVALID_OPERATION, "Default vertex array object is bound."));
return false;
}
return true;
}
bool ValidateVertexAttribFormat(ValidationContext *context,
GLuint attribIndex,
GLint size,
GLenum type,
GLuint relativeOffset,
GLboolean pureInteger)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
const Caps &caps = context->getCaps();
if (relativeOffset > static_cast<GLuint>(caps.maxVertexAttribRelativeOffset))
{
context->handleError(
Error(GL_INVALID_VALUE,
"relativeOffset cannot be greater than MAX_VERTEX_ATTRIB_RELATIVE_OFFSET."));
return false;
}
// [OpenGL ES 3.1] Section 10.3.1 page 243:
// An INVALID_OPERATION error is generated if the default vertex array object is bound.
if (context->getGLState().getVertexArrayId() == 0)
{
context->handleError(Error(GL_INVALID_OPERATION, "Default vertex array object is bound."));
return false;
}
return ValidateVertexFormatBase(context, attribIndex, size, type, pureInteger);
}
bool ValidateVertexAttribBinding(ValidationContext *context,
GLuint attribIndex,
GLuint bindingIndex)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
// [OpenGL ES 3.1] Section 10.3.1 page 243:
// An INVALID_OPERATION error is generated if the default vertex array object is bound.
if (context->getGLState().getVertexArrayId() == 0)
{
context->handleError(Error(GL_INVALID_OPERATION, "Default vertex array object is bound."));
return false;
}
const Caps &caps = context->getCaps();
if (attribIndex >= caps.maxVertexAttributes)
{
context->handleError(
Error(GL_INVALID_VALUE, "attribindex must be smaller than MAX_VERTEX_ATTRIBS."));
return false;
}
if (bindingIndex >= caps.maxVertexAttribBindings)
{
context->handleError(Error(GL_INVALID_VALUE,
"bindingindex must be smaller than MAX_VERTEX_ATTRIB_BINDINGS"));
return false;
}
return true;
}
bool ValidateGetProgramResourceName(Context *context,
GLuint program,
GLenum programInterface,
GLuint index,
GLsizei bufSize,
GLsizei *length,
GLchar *name)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
Program *programObject = GetValidProgram(context, program);
if (programObject == nullptr)
{
return false;
}
if (!ValidateNamedProgramInterface(programInterface))
{
context->handleError(
Error(GL_INVALID_ENUM, "Invalid program interface: 0x%X", programInterface));
return false;
}
if (!ValidateProgramResourceIndex(programObject, programInterface, index))
{
context->handleError(Error(GL_INVALID_VALUE, "Invalid index: %d", index));
return false;
}
if (bufSize < 0)
{
context->handleError(Error(GL_INVALID_VALUE, "Invalid bufSize: %d", bufSize));
return false;
}
return true;
}
bool ValidateDispatchCompute(Context *context,
GLuint numGroupsX,
GLuint numGroupsY,
GLuint numGroupsZ)
{
if (context->getClientVersion() < ES_3_1)
{
context->handleError(Error(GL_INVALID_OPERATION, "Context does not support GLES3.1."));
return false;
}
const State &state = context->getGLState();
Program *program = state.getProgram();
if (program == nullptr)
{
context->handleError(
Error(GL_INVALID_OPERATION, "No active program object for the compute shader stage."));
return false;
}
if (program->isLinked() == false || program->getAttachedComputeShader() == nullptr)
{
context->handleError(Error(
GL_INVALID_OPERATION,
"Program has not been successfully linked, or program contains no compute shaders."));
return false;
}
const Caps &caps = context->getCaps();
if (numGroupsX > caps.maxComputeWorkGroupCount[0])
{
context->handleError(
Error(GL_INVALID_VALUE,
"num_groups_x cannot be greater than MAX_COMPUTE_WORK_GROUP_COUNT[0](%u).",
caps.maxComputeWorkGroupCount[0]));
return false;
}
if (numGroupsY > caps.maxComputeWorkGroupCount[1])
{
context->handleError(
Error(GL_INVALID_VALUE,
"num_groups_y cannot be greater than MAX_COMPUTE_WORK_GROUP_COUNT[1](%u).",
caps.maxComputeWorkGroupCount[1]));
return false;
}
if (numGroupsZ > caps.maxComputeWorkGroupCount[2])
{
context->handleError(
Error(GL_INVALID_VALUE,
"num_groups_z cannot be greater than MAX_COMPUTE_WORK_GROUP_COUNT[2](%u).",
caps.maxComputeWorkGroupCount[2]));
return false;
}
return true;
}
} // namespace gl