Commit 9fa54eab by Jamie Madill Committed by Commit Bot

Optimize and inline more validation.

Previously on each draw call we were checking both the instanced and non-instanced attributes for overflow. On on-instanced draw calls we don't need to do any special checks for instanced attributes. Also we can inline more into the headers. This improves draw call performance slightly. Bug: angleproject:2966 Change-Id: Idf5861d2d9daf9fffd5c84f6a1ea5b23ac8ab713 Reviewed-on: https://chromium-review.googlesource.com/c/1390357 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarMarkus Tavenrath <matavenrath@nvidia.com>
parent 4627b370
......@@ -18,7 +18,6 @@
#include "libANGLE/Query.h"
#include "libANGLE/Texture.h"
#include "libANGLE/TransformFeedback.h"
#include "libANGLE/VertexArray.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/queryconversions.h"
......@@ -85,40 +84,6 @@ bool DifferenceCanOverflow(GLint a, GLint b)
return !checkedA.IsValid();
}
bool ValidateDrawAttribsImpl(Context *context, GLint primcount, GLint maxVertex)
{
// If we're drawing zero vertices, we have enough data.
ASSERT(primcount > 0);
// 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(GL_INVALID_OPERATION, kIntegerOverflow);
return false;
}
// [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(GL_INVALID_OPERATION, kInsufficientVertexBufferSize);
return false;
}
ANGLE_INLINE bool ValidateDrawAttribs(Context *context, GLint primcount, GLint maxVertex)
{
if (maxVertex <= context->getStateCache().getNonInstancedVertexElementLimit() &&
(primcount - 1) <= context->getStateCache().getInstancedVertexElementLimit())
{
return true;
}
else
{
return ValidateDrawAttribsImpl(context, primcount, maxVertex);
}
}
bool ValidReadPixelsTypeEnum(Context *context, GLenum type)
{
switch (type)
......@@ -637,10 +602,16 @@ bool ValidateDrawElementsInstancedBase(Context *context,
const GLvoid *indices,
GLsizei primcount)
{
if (primcount < 0)
if (primcount <= 0)
{
context->validationError(GL_INVALID_VALUE, kNegativePrimcount);
return false;
if (primcount < 0)
{
context->validationError(GL_INVALID_VALUE, kNegativePrimcount);
return false;
}
// Early exit.
return ValidateDrawElementsCommon(context, mode, count, type, indices, primcount);
}
if (!ValidateDrawElementsCommon(context, mode, count, type, indices, primcount))
......@@ -648,7 +619,13 @@ bool ValidateDrawElementsInstancedBase(Context *context,
return false;
}
return true;
if (count == 0)
{
// Early exit.
return true;
}
return ValidateDrawInstancedAttribs(context, primcount);
}
bool ValidateDrawArraysInstancedBase(Context *context,
......@@ -657,10 +634,16 @@ bool ValidateDrawArraysInstancedBase(Context *context,
GLsizei count,
GLsizei primcount)
{
if (primcount < 0)
if (primcount <= 0)
{
context->validationError(GL_INVALID_VALUE, kNegativePrimcount);
return false;
if (primcount < 0)
{
context->validationError(GL_INVALID_VALUE, kNegativePrimcount);
return false;
}
// Early exit.
return ValidateDrawArraysCommon(context, mode, first, count, primcount);
}
if (!ValidateDrawArraysCommon(context, mode, first, count, primcount))
......@@ -668,7 +651,13 @@ bool ValidateDrawArraysInstancedBase(Context *context,
return false;
}
return true;
if (count == 0)
{
// Early exit.
return true;
}
return ValidateDrawInstancedAttribs(context, primcount);
}
bool ValidateDrawInstancedANGLE(Context *context)
......@@ -2774,7 +2763,7 @@ const char *ValidateDrawStates(Context *context)
return nullptr;
}
bool ValidateDrawMode(Context *context, PrimitiveMode mode)
void RecordDrawModeError(Context *context, PrimitiveMode mode)
{
const State &state = context->getState();
TransformFeedback *curTransformFeedback = state.getCurrentTransformFeedback();
......@@ -2785,7 +2774,7 @@ bool ValidateDrawMode(Context *context, PrimitiveMode mode)
curTransformFeedback->getPrimitiveMode(), mode))
{
context->validationError(GL_INVALID_OPERATION, kInvalidDrawModeTransformFeedback);
return false;
return;
}
}
......@@ -2809,12 +2798,12 @@ bool ValidateDrawMode(Context *context, PrimitiveMode mode)
if (!extensions.geometryShader)
{
context->validationError(GL_INVALID_ENUM, kGeometryShaderExtensionNotEnabled);
return false;
return;
}
break;
default:
context->validationError(GL_INVALID_ENUM, kInvalidDrawMode);
return false;
return;
}
// If we are running GLES1, there is no current program.
......@@ -2831,63 +2820,13 @@ bool ValidateDrawMode(Context *context, PrimitiveMode mode)
{
context->validationError(GL_INVALID_OPERATION,
kIncompatibleDrawModeAgainstGeometryShader);
return false;
return;
}
}
}
return true;
}
bool ValidateDrawArraysCommon(Context *context,
PrimitiveMode mode,
GLint first,
GLsizei count,
GLsizei primcount)
{
if (first < 0)
{
context->validationError(GL_INVALID_VALUE, kNegativeStart);
return false;
}
if (!ValidateDrawBase(context, mode, count))
{
return false;
}
if (context->getStateCache().isTransformFeedbackActiveUnpaused())
{
const State &state = context->getState();
TransformFeedback *curTransformFeedback = state.getCurrentTransformFeedback();
if (!curTransformFeedback->checkBufferSpaceForDraw(count, primcount))
{
context->validationError(GL_INVALID_OPERATION, kTransformFeedbackBufferTooSmall);
return false;
}
}
// Check the computation of maxVertex doesn't overflow.
// - first < 0 has been checked as an error condition.
// - if count < 0, skip validating no-op draw calls.
// From this we know maxVertex will be positive, and only need to check if it overflows GLint.
ASSERT(first >= 0);
if (count > 0 && primcount > 0)
{
int64_t maxVertex = static_cast<int64_t>(first) + static_cast<int64_t>(count) - 1;
if (maxVertex > static_cast<int64_t>(std::numeric_limits<GLint>::max()))
{
context->validationError(GL_INVALID_OPERATION, kIntegerOverflow);
return false;
}
if (!ValidateDrawAttribs(context, primcount, static_cast<GLint>(maxVertex)))
{
return false;
}
}
return true;
// An error should be recorded.
UNREACHABLE();
}
bool ValidateDrawArraysInstancedANGLE(Context *context,
......@@ -2910,34 +2849,6 @@ bool ValidateDrawArraysInstancedANGLE(Context *context,
return ValidateDrawInstancedANGLE(context);
}
bool ValidateDrawElementsBase(Context *context, PrimitiveMode mode, DrawElementsType type)
{
if (!context->getStateCache().isValidDrawElementsType(type))
{
if (type == DrawElementsType::UnsignedInt)
{
context->validationError(GL_INVALID_ENUM, kTypeNotUnsignedShortByte);
return false;
}
ASSERT(type == DrawElementsType::InvalidEnum);
context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
return false;
}
intptr_t drawElementsError = context->getStateCache().getBasicDrawElementsError(context);
if (drawElementsError)
{
// All errors from ValidateDrawElementsStates return INVALID_OPERATION.
const char *errorMessage = reinterpret_cast<const char *>(drawElementsError);
context->validationError(GL_INVALID_OPERATION, errorMessage);
return false;
}
// Note that we are missing overflow checks for active transform feedback buffers.
return true;
}
const char *ValidateDrawElementsStates(Context *context)
{
const State &state = context->getState();
......@@ -2990,127 +2901,6 @@ const char *ValidateDrawElementsStates(Context *context)
return nullptr;
}
bool ValidateDrawElementsCommon(Context *context,
PrimitiveMode mode,
GLsizei count,
DrawElementsType type,
const void *indices,
GLsizei primcount)
{
if (!ValidateDrawElementsBase(context, mode, type))
{
return false;
}
if (!ValidateDrawBase(context, mode, count))
{
return false;
}
ASSERT(isPow2(GetDrawElementsTypeSize(type)) && GetDrawElementsTypeSize(type) > 0);
if (context->getExtensions().webglCompatibility)
{
GLuint typeBytes = GetDrawElementsTypeSize(type);
if ((reinterpret_cast<uintptr_t>(indices) & static_cast<uintptr_t>(typeBytes - 1)) != 0)
{
// [WebGL 1.0] Section 6.4 Buffer Offset and Stride Requirements
// The offset arguments to drawElements and [...], must be a multiple of the size of the
// data type passed to the call, or an INVALID_OPERATION error is generated.
context->validationError(GL_INVALID_OPERATION, kOffsetMustBeMultipleOfType);
return false;
}
// [WebGL 1.0] Section 6.4 Buffer Offset and Stride Requirements
// In addition the offset argument to drawElements must be non-negative or an INVALID_VALUE
// error is generated.
if (reinterpret_cast<intptr_t>(indices) < 0)
{
context->validationError(GL_INVALID_VALUE, kNegativeOffset);
return false;
}
}
// Early exit.
if (count == 0)
{
return true;
}
const State &state = context->getState();
const VertexArray *vao = state.getVertexArray();
Buffer *elementArrayBuffer = vao->getElementArrayBuffer();
if (!elementArrayBuffer)
{
if (!indices)
{
// This is an application error that would normally result in a crash, but we catch
// it and return an error
context->validationError(GL_INVALID_OPERATION, kElementArrayNoBufferOrPointer);
return false;
}
}
else
{
// The max possible type size is 8 and count is on 32 bits so doing the multiplication
// in a 64 bit integer is safe. Also we are guaranteed that here count > 0.
static_assert(std::is_same<int, GLsizei>::value, "GLsizei isn't the expected type");
constexpr uint64_t kMaxTypeSize = 8;
constexpr uint64_t kIntMax = std::numeric_limits<int>::max();
constexpr uint64_t kUint64Max = std::numeric_limits<uint64_t>::max();
static_assert(kIntMax < kUint64Max / kMaxTypeSize, "");
uint64_t elementCount = static_cast<uint64_t>(count);
ASSERT(elementCount > 0 && GetDrawElementsTypeSize(type) <= kMaxTypeSize);
// Doing the multiplication here is overflow-safe
uint64_t elementDataSizeNoOffset = elementCount << GetDrawElementsTypeShift(type);
// The offset can be any value, check for overflows
uint64_t offset = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(indices));
uint64_t elementDataSizeWithOffset = elementDataSizeNoOffset + offset;
if (elementDataSizeWithOffset < elementDataSizeNoOffset)
{
context->validationError(GL_INVALID_OPERATION, kIntegerOverflow);
return false;
}
if (elementDataSizeWithOffset > static_cast<uint64_t>(elementArrayBuffer->getSize()))
{
context->validationError(GL_INVALID_OPERATION, kInsufficientBufferSize);
return false;
}
}
if (!context->getExtensions().robustBufferAccessBehavior && primcount > 0)
{
// Use the parameter buffer to retrieve and cache the index range.
IndexRange indexRange{IndexRange::Undefined()};
ANGLE_VALIDATION_TRY(vao->getIndexRange(context, type, count, indices, &indexRange));
// If we use an index greater than our maximum supported index range, return an error.
// The ES3 spec does not specify behaviour here, it is undefined, but ANGLE should
// always return an error if possible here.
if (static_cast<GLuint64>(indexRange.end) >= context->getCaps().maxElementIndex)
{
context->validationError(GL_INVALID_OPERATION, kExceedsMaxElement);
return false;
}
if (!ValidateDrawAttribs(context, primcount, static_cast<GLint>(indexRange.end)))
{
return false;
}
// No op if there are no real indices in the index data (all are primitive restart).
return (indexRange.vertexIndexCount > 0);
}
return true;
}
bool ValidateDrawElementsInstancedCommon(Context *context,
PrimitiveMode mode,
GLsizei count,
......@@ -6652,4 +6442,22 @@ bool ValidateSampleMaskiBase(Context *context, GLuint maskNumber, GLbitfield mas
return true;
}
void RecordDrawAttribsError(Context *context)
{
// 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(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(GL_INVALID_OPERATION, kInsufficientVertexBufferSize);
}
}
} // namespace gl
......@@ -15,6 +15,7 @@
#include "libANGLE/Context.h"
#include "libANGLE/ErrorStrings.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/VertexArray.h"
#include <GLES2/gl2.h>
#include <GLES3/gl3.h>
......@@ -286,19 +287,14 @@ bool ValidateCopyTexImageParametersBase(Context *context,
GLint border,
Format *textureFormatOut);
bool ValidateDrawMode(Context *context, PrimitiveMode mode);
void RecordDrawModeError(Context *context, PrimitiveMode mode);
const char *ValidateDrawElementsStates(Context *context);
ANGLE_INLINE bool ValidateDrawBase(Context *context, PrimitiveMode mode, GLsizei count)
ANGLE_INLINE bool ValidateDrawBase(Context *context, PrimitiveMode mode)
{
if (!context->getStateCache().isValidDrawMode(mode))
{
return ValidateDrawMode(context, mode);
}
if (count < 0)
{
context->validationError(GL_INVALID_VALUE, err::kNegativeCount);
RecordDrawModeError(context, mode);
return false;
}
......@@ -319,11 +315,6 @@ ANGLE_INLINE bool ValidateDrawBase(Context *context, PrimitiveMode mode, GLsizei
return true;
}
bool ValidateDrawArraysCommon(Context *context,
PrimitiveMode mode,
GLint first,
GLsizei count,
GLsizei primcount);
bool ValidateDrawArraysInstancedBase(Context *context,
PrimitiveMode mode,
GLint first,
......@@ -335,14 +326,6 @@ bool ValidateDrawArraysInstancedANGLE(Context *context,
GLsizei count,
GLsizei primcount);
bool ValidateDrawElementsBase(Context *context, PrimitiveMode mode, DrawElementsType type);
bool ValidateDrawElementsCommon(Context *context,
PrimitiveMode mode,
GLsizei count,
DrawElementsType type,
const void *indices,
GLsizei primcount);
bool ValidateDrawElementsInstancedCommon(Context *context,
PrimitiveMode mode,
GLsizei count,
......@@ -648,6 +631,248 @@ ANGLE_INLINE bool ValidateFramebufferComplete(Context *context, Framebuffer *fra
}
const char *ValidateDrawStates(Context *context);
void RecordDrawAttribsError(Context *context);
ANGLE_INLINE bool ValidateDrawAttribs(Context *context, int64_t maxVertex)
{
if (maxVertex > context->getStateCache().getNonInstancedVertexElementLimit())
{
RecordDrawAttribsError(context);
return false;
}
return true;
}
ANGLE_INLINE bool ValidateDrawArraysAttribs(Context *context, GLint first, GLsizei count)
{
// Check the computation of maxVertex doesn't overflow.
// - first < 0 has been checked as an error condition.
// - if count <= 0, skip validating no-op draw calls.
// From this we know maxVertex will be positive, and only need to check if it overflows GLint.
ASSERT(first >= 0);
ASSERT(count > 0);
int64_t maxVertex = static_cast<int64_t>(first) + static_cast<int64_t>(count) - 1;
if (maxVertex > static_cast<int64_t>(std::numeric_limits<GLint>::max()))
{
context->validationError(GL_INVALID_OPERATION, err::kIntegerOverflow);
return false;
}
return ValidateDrawAttribs(context, maxVertex);
}
ANGLE_INLINE bool ValidateDrawInstancedAttribs(Context *context, GLint primcount)
{
if ((primcount - 1) > context->getStateCache().getInstancedVertexElementLimit())
{
RecordDrawAttribsError(context);
return false;
}
return true;
}
ANGLE_INLINE bool ValidateDrawArraysCommon(Context *context,
PrimitiveMode mode,
GLint first,
GLsizei count,
GLsizei primcount)
{
if (first < 0)
{
context->validationError(GL_INVALID_VALUE, err::kNegativeStart);
return false;
}
if (count <= 0)
{
if (count < 0)
{
context->validationError(GL_INVALID_VALUE, err::kNegativeCount);
return false;
}
// Early exit.
return ValidateDrawBase(context, mode);
}
if (!ValidateDrawBase(context, mode))
{
return false;
}
if (context->getStateCache().isTransformFeedbackActiveUnpaused())
{
const State &state = context->getState();
TransformFeedback *curTransformFeedback = state.getCurrentTransformFeedback();
if (!curTransformFeedback->checkBufferSpaceForDraw(count, primcount))
{
context->validationError(GL_INVALID_OPERATION, err::kTransformFeedbackBufferTooSmall);
return false;
}
}
return ValidateDrawArraysAttribs(context, first, count);
}
ANGLE_INLINE bool ValidateDrawElementsBase(Context *context,
PrimitiveMode mode,
DrawElementsType type)
{
if (!context->getStateCache().isValidDrawElementsType(type))
{
if (type == DrawElementsType::UnsignedInt)
{
context->validationError(GL_INVALID_ENUM, err::kTypeNotUnsignedShortByte);
return false;
}
ASSERT(type == DrawElementsType::InvalidEnum);
context->validationError(GL_INVALID_ENUM, err::kEnumNotSupported);
return false;
}
intptr_t drawElementsError = context->getStateCache().getBasicDrawElementsError(context);
if (drawElementsError)
{
// All errors from ValidateDrawElementsStates return INVALID_OPERATION.
const char *errorMessage = reinterpret_cast<const char *>(drawElementsError);
context->validationError(GL_INVALID_OPERATION, errorMessage);
return false;
}
// Note that we are missing overflow checks for active transform feedback buffers.
return true;
}
ANGLE_INLINE bool ValidateDrawElementsCommon(Context *context,
PrimitiveMode mode,
GLsizei count,
DrawElementsType type,
const void *indices,
GLsizei primcount)
{
if (!ValidateDrawElementsBase(context, mode, type))
{
return false;
}
ASSERT(isPow2(GetDrawElementsTypeSize(type)) && GetDrawElementsTypeSize(type) > 0);
if (context->getExtensions().webglCompatibility)
{
GLuint typeBytes = GetDrawElementsTypeSize(type);
if ((reinterpret_cast<uintptr_t>(indices) & static_cast<uintptr_t>(typeBytes - 1)) != 0)
{
// [WebGL 1.0] Section 6.4 Buffer Offset and Stride Requirements
// The offset arguments to drawElements and [...], must be a multiple of the size of the
// data type passed to the call, or an INVALID_OPERATION error is generated.
context->validationError(GL_INVALID_OPERATION, err::kOffsetMustBeMultipleOfType);
return false;
}
// [WebGL 1.0] Section 6.4 Buffer Offset and Stride Requirements
// In addition the offset argument to drawElements must be non-negative or an INVALID_VALUE
// error is generated.
if (reinterpret_cast<intptr_t>(indices) < 0)
{
context->validationError(GL_INVALID_VALUE, err::kNegativeOffset);
return false;
}
}
if (count <= 0)
{
if (count < 0)
{
context->validationError(GL_INVALID_VALUE, err::kNegativeCount);
return false;
}
// Early exit.
return ValidateDrawBase(context, mode);
}
if (!ValidateDrawBase(context, mode))
{
return false;
}
const State &state = context->getState();
const VertexArray *vao = state.getVertexArray();
Buffer *elementArrayBuffer = vao->getElementArrayBuffer();
if (!elementArrayBuffer)
{
if (!indices)
{
// This is an application error that would normally result in a crash, but we catch
// it and return an error
context->validationError(GL_INVALID_OPERATION, err::kElementArrayNoBufferOrPointer);
return false;
}
}
else
{
// The max possible type size is 8 and count is on 32 bits so doing the multiplication
// in a 64 bit integer is safe. Also we are guaranteed that here count > 0.
static_assert(std::is_same<int, GLsizei>::value, "GLsizei isn't the expected type");
constexpr uint64_t kMaxTypeSize = 8;
constexpr uint64_t kIntMax = std::numeric_limits<int>::max();
constexpr uint64_t kUint64Max = std::numeric_limits<uint64_t>::max();
static_assert(kIntMax < kUint64Max / kMaxTypeSize, "");
uint64_t elementCount = static_cast<uint64_t>(count);
ASSERT(elementCount > 0 && GetDrawElementsTypeSize(type) <= kMaxTypeSize);
// Doing the multiplication here is overflow-safe
uint64_t elementDataSizeNoOffset = elementCount << GetDrawElementsTypeShift(type);
// The offset can be any value, check for overflows
uint64_t offset = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(indices));
uint64_t elementDataSizeWithOffset = elementDataSizeNoOffset + offset;
if (elementDataSizeWithOffset < elementDataSizeNoOffset)
{
context->validationError(GL_INVALID_OPERATION, err::kIntegerOverflow);
return false;
}
if (elementDataSizeWithOffset > static_cast<uint64_t>(elementArrayBuffer->getSize()))
{
context->validationError(GL_INVALID_OPERATION, err::kInsufficientBufferSize);
return false;
}
}
if (!context->getExtensions().robustBufferAccessBehavior && primcount > 0)
{
// Use the parameter buffer to retrieve and cache the index range.
IndexRange indexRange{IndexRange::Undefined()};
ANGLE_VALIDATION_TRY(vao->getIndexRange(context, type, count, indices, &indexRange));
// If we use an index greater than our maximum supported index range, return an error.
// The ES3 spec does not specify behaviour here, it is undefined, but ANGLE should
// always return an error if possible here.
if (static_cast<GLuint64>(indexRange.end) >= context->getCaps().maxElementIndex)
{
context->validationError(GL_INVALID_OPERATION, err::kExceedsMaxElement);
return false;
}
if (!ValidateDrawAttribs(context, static_cast<GLint>(indexRange.end)))
{
return false;
}
// No op if there are no real indices in the index data (all are primitive restart).
return (indexRange.vertexIndexCount > 0);
}
return true;
}
} // namespace gl
#endif // LIBANGLE_VALIDATION_ES_H_
......@@ -427,7 +427,7 @@ bool ValidateDrawIndirectBase(Context *context, PrimitiveMode mode, const void *
}
// Here the third parameter 1 is only to pass the count validation.
if (!ValidateDrawBase(context, mode, 1))
if (!ValidateDrawBase(context, mode))
{
return false;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment