Commit e79b1e14 by Jamie Madill

D3D11: Restrict use of MAX_UINT element indexes.

We need to block the app from using MAX_UINT on D3D11 because we can't disable primitive restart on this platform. Instead generate an INVALID_OPERATION error, which is spec-compliant in ES3 because the result is undefined behaviour. This is also compliant with WebGL which explicitly defines an error here. BUG=angleproject:597 Change-Id: I7ebc5371b63ff860dc6dddf79939e9629ebb2a3c Reviewed-on: https://chromium-review.googlesource.com/309638Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Tryjob-Request: Jamie Madill <jmadill@chromium.org> Tested-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 72028778
......@@ -41,6 +41,23 @@ const std::string &Error::getMessage() const
return *mMessage;
}
bool Error::operator==(const Error &other) const
{
if (mCode != other.mCode)
return false;
// TODO(jmadill): Compare extended error codes instead of strings.
if ((mMessage == nullptr || other.mMessage == nullptr) &&
((mMessage == nullptr) != (other.mMessage == nullptr)))
return false;
return (*mMessage == *other.mMessage);
}
bool Error::operator!=(const Error &other) const
{
return !(*this == other);
}
}
namespace egl
......
......@@ -35,6 +35,10 @@ class Error final
const std::string &getMessage() const;
// Useful for mocking and testing
bool operator==(const Error &other) const;
bool operator!=(const Error &other) const;
private:
void createMessageString() const;
......
......@@ -20,10 +20,28 @@ namespace gl
{
State::State()
: mMaxDrawBuffers(0),
mMaxCombinedTextureImageUnits(0),
mDepthClearValue(0),
mStencilClearValue(0),
mScissorTest(false),
mSampleCoverage(false),
mSampleCoverageValue(0),
mSampleCoverageInvert(false),
mStencilRef(0),
mStencilBackRef(0),
mLineWidth(0),
mGenerateMipmapHint(GL_NONE),
mFragmentShaderDerivativeHint(GL_NONE),
mNearZ(0),
mFarZ(0),
mReadFramebuffer(nullptr),
mDrawFramebuffer(nullptr),
mProgram(nullptr),
mVertexArray(nullptr),
mActiveSampler(0),
mPrimitiveRestart(false)
{
mMaxDrawBuffers = 0;
mMaxCombinedTextureImageUnits = 0;
// Initialize dirty bit masks
// TODO(jmadill): additional ES3 state
mUnpackStateBitMask.set(DIRTY_BIT_UNPACK_ALIGNMENT);
......
......@@ -12,9 +12,11 @@
#include "libANGLE/Data.h"
#include "libANGLE/State.h"
#include "libANGLE/Surface.h"
#include "libANGLE/renderer/FramebufferImpl.h"
#include "libANGLE/renderer/FramebufferImpl_mock.h"
#include "libANGLE/renderer/SurfaceImpl.h"
using namespace rx;
namespace
{
......@@ -41,49 +43,6 @@ class MockSurfaceImpl : public rx::SurfaceImpl
MOCK_METHOD0(destroy, void());
};
class MockFramebufferImpl : public rx::FramebufferImpl
{
public:
MockFramebufferImpl() : rx::FramebufferImpl(gl::Framebuffer::Data()) {}
virtual ~MockFramebufferImpl() { destroy(); }
MOCK_METHOD1(onUpdateColorAttachment, void(size_t));
MOCK_METHOD0(onUpdateDepthAttachment, void());
MOCK_METHOD0(onUpdateStencilAttachment, void());
MOCK_METHOD0(onUpdateDepthStencilAttachment, void());
MOCK_METHOD2(setDrawBuffers, void(size_t, const GLenum *));
MOCK_METHOD1(setReadBuffer, void(GLenum));
MOCK_METHOD2(discard, gl::Error(size_t, const GLenum *));
MOCK_METHOD2(invalidate, gl::Error(size_t, const GLenum *));
MOCK_METHOD3(invalidateSub, gl::Error(size_t, const GLenum *, const gl::Rectangle &));
MOCK_METHOD2(clear, gl::Error(const gl::Data &, GLbitfield));
MOCK_METHOD4(clearBufferfv, gl::Error(const gl::State &, GLenum, GLint, const GLfloat *));
MOCK_METHOD4(clearBufferuiv, gl::Error(const gl::State &, GLenum, GLint, const GLuint *));
MOCK_METHOD4(clearBufferiv, gl::Error(const gl::State &, GLenum, GLint, const GLint *));
MOCK_METHOD5(clearBufferfi, gl::Error(const gl::State &, GLenum, GLint, GLfloat, GLint));
MOCK_CONST_METHOD0(getImplementationColorReadFormat, GLenum());
MOCK_CONST_METHOD0(getImplementationColorReadType, GLenum());
MOCK_CONST_METHOD5(
readPixels,
gl::Error(const gl::State &, const gl::Rectangle &, GLenum, GLenum, GLvoid *));
MOCK_METHOD6(blit,
gl::Error(const gl::State &,
const gl::Rectangle &,
const gl::Rectangle &,
GLbitfield,
GLenum,
const gl::Framebuffer *));
MOCK_CONST_METHOD0(checkStatus, GLenum());
MOCK_METHOD0(destroy, void());
};
TEST(SurfaceTest, DestructionDeletesImpl)
{
MockFramebufferImpl *framebuffer = new MockFramebufferImpl;
......
......@@ -34,7 +34,6 @@ VertexArray::VertexArray(rx::ImplFactory *factory, GLuint id, size_t maxAttribs)
mVertexArray(factory->createVertexArray(mData)),
mData(maxAttribs)
{
ASSERT(mVertexArray != nullptr);
}
VertexArray::~VertexArray()
......
//
// Copyright 2015 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.
//
// FramebufferImpl_mock.h:
// Defines a mock of the FramebufferImpl class.
//
#ifndef LIBANGLE_RENDERER_FRAMEBUFFERIMPLMOCK_H_
#define LIBANGLE_RENDERER_FRAMEBUFFERIMPLMOCK_H_
#include "gmock/gmock.h"
#include "libANGLE/renderer/FramebufferImpl.h"
namespace rx
{
class MockFramebufferImpl : public rx::FramebufferImpl
{
public:
MockFramebufferImpl() : rx::FramebufferImpl(gl::Framebuffer::Data()) {}
virtual ~MockFramebufferImpl() { destroy(); }
MOCK_METHOD1(onUpdateColorAttachment, void(size_t));
MOCK_METHOD0(onUpdateDepthAttachment, void());
MOCK_METHOD0(onUpdateStencilAttachment, void());
MOCK_METHOD0(onUpdateDepthStencilAttachment, void());
MOCK_METHOD2(setDrawBuffers, void(size_t, const GLenum *));
MOCK_METHOD1(setReadBuffer, void(GLenum));
MOCK_METHOD2(discard, gl::Error(size_t, const GLenum *));
MOCK_METHOD2(invalidate, gl::Error(size_t, const GLenum *));
MOCK_METHOD3(invalidateSub, gl::Error(size_t, const GLenum *, const gl::Rectangle &));
MOCK_METHOD2(clear, gl::Error(const gl::Data &, GLbitfield));
MOCK_METHOD4(clearBufferfv, gl::Error(const gl::State &, GLenum, GLint, const GLfloat *));
MOCK_METHOD4(clearBufferuiv, gl::Error(const gl::State &, GLenum, GLint, const GLuint *));
MOCK_METHOD4(clearBufferiv, gl::Error(const gl::State &, GLenum, GLint, const GLint *));
MOCK_METHOD5(clearBufferfi, gl::Error(const gl::State &, GLenum, GLint, GLfloat, GLint));
MOCK_CONST_METHOD0(getImplementationColorReadFormat, GLenum());
MOCK_CONST_METHOD0(getImplementationColorReadType, GLenum());
MOCK_CONST_METHOD5(
readPixels,
gl::Error(const gl::State &, const gl::Rectangle &, GLenum, GLenum, GLvoid *));
MOCK_METHOD6(blit,
gl::Error(const gl::State &,
const gl::Rectangle &,
const gl::Rectangle &,
GLbitfield,
GLenum,
const gl::Framebuffer *));
MOCK_CONST_METHOD0(checkStatus, GLenum());
MOCK_METHOD0(destroy, void());
};
} // namespace rx
#endif // LIBANGLE_RENDERER_FRAMEBUFFERIMPLMOCK_H_
//
// Copyright 2015 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.
//
// ProgramImpl_mock.h:
// Defines a mock of the ProgramImpl class.
//
#ifndef LIBANGLE_RENDERER_PROGRAMIMPLMOCK_H_
#define LIBANGLE_RENDERER_PROGRAMIMPLMOCK_H_
#include "gmock/gmock.h"
#include "libANGLE/renderer/ProgramImpl.h"
namespace rx
{
class MockProgramImpl : public rx::ProgramImpl
{
public:
MockProgramImpl() : ProgramImpl(gl::Program::Data()) {}
virtual ~MockProgramImpl() { destroy(); }
MOCK_METHOD2(load, LinkResult(gl::InfoLog &, gl::BinaryInputStream *));
MOCK_METHOD1(save, gl::Error(gl::BinaryOutputStream *));
MOCK_METHOD2(link, LinkResult(const gl::Data &, gl::InfoLog &));
MOCK_METHOD2(validate, GLboolean(const gl::Caps &, gl::InfoLog *));
MOCK_METHOD3(setUniform1fv, void(GLint, GLsizei, const GLfloat *));
MOCK_METHOD3(setUniform2fv, void(GLint, GLsizei, const GLfloat *));
MOCK_METHOD3(setUniform3fv, void(GLint, GLsizei, const GLfloat *));
MOCK_METHOD3(setUniform4fv, void(GLint, GLsizei, const GLfloat *));
MOCK_METHOD3(setUniform1iv, void(GLint, GLsizei, const GLint *));
MOCK_METHOD3(setUniform2iv, void(GLint, GLsizei, const GLint *));
MOCK_METHOD3(setUniform3iv, void(GLint, GLsizei, const GLint *));
MOCK_METHOD3(setUniform4iv, void(GLint, GLsizei, const GLint *));
MOCK_METHOD3(setUniform1uiv, void(GLint, GLsizei, const GLuint *));
MOCK_METHOD3(setUniform2uiv, void(GLint, GLsizei, const GLuint *));
MOCK_METHOD3(setUniform3uiv, void(GLint, GLsizei, const GLuint *));
MOCK_METHOD3(setUniform4uiv, void(GLint, GLsizei, const GLuint *));
MOCK_METHOD4(setUniformMatrix2fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD4(setUniformMatrix3fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD4(setUniformMatrix4fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD4(setUniformMatrix2x3fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD4(setUniformMatrix3x2fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD4(setUniformMatrix2x4fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD4(setUniformMatrix4x2fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD4(setUniformMatrix3x4fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD4(setUniformMatrix4x3fv, void(GLint, GLsizei, GLboolean, const GLfloat *));
MOCK_METHOD2(setUniformBlockBinding, void(GLuint, GLuint));
MOCK_CONST_METHOD2(getUniformBlockSize, bool(const std::string &, size_t *));
MOCK_CONST_METHOD2(getUniformBlockMemberInfo, bool(const std::string &, sh::BlockMemberInfo *));
MOCK_METHOD0(destroy, void());
};
} // namespace rx
#endif // LIBANGLE_RENDERER_PROGRAMIMPLMOCK_H_
......@@ -1070,7 +1070,8 @@ void GenerateCaps(ID3D11Device *device, ID3D11DeviceContext *deviceContext, cons
}
// GL core feature limits
caps->maxElementIndex = static_cast<GLint64>(std::numeric_limits<unsigned int>::max());
// Reserve MAX_UINT for D3D11's primitive restart.
caps->maxElementIndex = static_cast<GLint64>(std::numeric_limits<unsigned int>::max() - 1);
caps->max3DTextureSize = static_cast<GLuint>(GetMaximum3DTextureSize(featureLevel));
caps->max2DTextureSize = static_cast<GLuint>(GetMaximum2DTextureSize(featureLevel));
caps->maxCubeMapTextureSize = static_cast<GLuint>(GetMaximumCubeMapTextureSize(featureLevel));
......
......@@ -27,6 +27,8 @@
namespace gl
{
const char *g_ExceedsMaxElementErrorMessage = "Element value exceeds maximum element index.";
namespace
{
bool ValidateDrawAttribs(ValidationContext *context, GLint primcount, GLint maxVertex)
......@@ -1674,7 +1676,7 @@ bool ValidateDrawElements(ValidationContext *context,
case GL_UNSIGNED_SHORT:
break;
case GL_UNSIGNED_INT:
if (!context->getExtensions().elementIndexUint)
if (context->getClientVersion() < 3 && !context->getExtensions().elementIndexUint)
{
context->recordError(Error(GL_INVALID_ENUM));
return false;
......@@ -1765,6 +1767,15 @@ bool ValidateDrawElements(ValidationContext *context,
*indexRangeOut = ComputeIndexRange(type, indices, count, state.isPrimitiveRestartEnabled());
}
// 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>(indexRangeOut->end) >= context->getCaps().maxElementIndex)
{
context->recordError(Error(GL_INVALID_OPERATION, g_ExceedsMaxElementErrorMessage));
return false;
}
if (!ValidateDrawAttribs(context, primcount, static_cast<GLsizei>(indexRangeOut->end)))
{
return false;
......
......@@ -146,6 +146,9 @@ bool ValidateEGLImageTargetRenderbufferStorageOES(Context *context,
bool ValidateBindVertexArrayBase(Context *context, GLuint array);
bool ValidateDeleteVertexArraysBase(Context *context, GLsizei n);
bool ValidateGenVertexArraysBase(Context *context, GLsizei n);
// Error messages shared here for use in testing.
extern const char *g_ExceedsMaxElementErrorMessage;
}
#endif // LIBANGLE_VALIDATION_ES_H_
//
// Copyright 2015 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 unit tests:
// Unit tests for general ES validation functions.
//
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libANGLE/Data.h"
#include "libANGLE/renderer/FramebufferImpl_mock.h"
#include "libANGLE/renderer/ProgramImpl_mock.h"
#include "libANGLE/renderer/TextureImpl_mock.h"
#include "libANGLE/validationES.h"
#include "tests/angle_unittests_utils.h"
using namespace gl;
using namespace rx;
using testing::_;
using testing::Return;
namespace
{
class MockFactory : public NullFactory
{
public:
MOCK_METHOD1(createFramebuffer, FramebufferImpl *(const gl::Framebuffer::Data &));
MOCK_METHOD1(createProgram, ProgramImpl *(const gl::Program::Data &));
MOCK_METHOD1(createVertexArray, VertexArrayImpl *(const gl::VertexArray::Data &));
};
class MockValidationContext : public ValidationContext
{
public:
MockValidationContext(GLint clientVersion,
const State &state,
const Caps &caps,
const TextureCapsMap &textureCaps,
const Extensions &extensions,
const ResourceManager *resourceManager,
const Limitations &limitations);
MOCK_METHOD1(recordError, void(const Error &));
};
MockValidationContext::MockValidationContext(GLint clientVersion,
const State &state,
const Caps &caps,
const TextureCapsMap &textureCaps,
const Extensions &extensions,
const ResourceManager *resourceManager,
const Limitations &limitations)
: ValidationContext(clientVersion,
state,
caps,
textureCaps,
extensions,
resourceManager,
limitations)
{
}
// Test that ANGLE generates an INVALID_OPERATION when validating index data that uses a value
// larger than MAX_ELEMENT_INDEX. Not specified in the GLES 3 spec, it's undefined behaviour,
// but we want a test to ensure we maintain this behaviour.
TEST(ValidationESTest, DrawElementsWithMaxIndexGivesError)
{
// TODO(jmadill): Generalize some of this code so we can re-use it for other tests.
MockFramebufferImpl *framebufferImpl = new MockFramebufferImpl();
EXPECT_CALL(*framebufferImpl, onUpdateColorAttachment(_)).Times(1).RetiresOnSaturation();
EXPECT_CALL(*framebufferImpl, checkStatus())
.Times(2)
.WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
.WillOnce(Return(GL_FRAMEBUFFER_COMPLETE));
EXPECT_CALL(*framebufferImpl, destroy()).Times(1).RetiresOnSaturation();
MockProgramImpl *programImpl = new MockProgramImpl();
EXPECT_CALL(*programImpl, destroy());
MockFactory mockFactory;
EXPECT_CALL(mockFactory, createFramebuffer(_)).WillOnce(Return(framebufferImpl));
EXPECT_CALL(mockFactory, createProgram(_)).WillOnce(Return(programImpl));
EXPECT_CALL(mockFactory, createVertexArray(_)).WillOnce(Return(nullptr));
State state;
Caps caps;
TextureCapsMap textureCaps;
Extensions extensions;
Limitations limitations;
// Set some basic caps.
caps.maxElementIndex = 100;
caps.maxDrawBuffers = 1;
caps.maxColorAttachments = 1;
state.initialize(caps, 3);
MockTextureImpl *textureImpl = new MockTextureImpl();
EXPECT_CALL(*textureImpl, setStorage(_, _, _, _)).WillOnce(Return(Error(GL_NO_ERROR)));
EXPECT_CALL(*textureImpl, destructor()).Times(1).RetiresOnSaturation();
Texture *texture = new Texture(textureImpl, 0, GL_TEXTURE_2D);
texture->addRef();
texture->setStorage(GL_TEXTURE_2D, 1, GL_RGBA8, Extents(1, 1, 0));
VertexArray *vertexArray = new VertexArray(&mockFactory, 0, 1);
Framebuffer *framebuffer = new Framebuffer(caps, &mockFactory, 1);
framebuffer->setAttachment(GL_FRAMEBUFFER_DEFAULT, GL_BACK, ImageIndex::Make2D(0), texture);
Program *program = new Program(&mockFactory, nullptr, 1);
state.setVertexArrayBinding(vertexArray);
state.setDrawFramebufferBinding(framebuffer);
state.setProgram(program);
MockValidationContext testContext(3, state, caps, textureCaps, extensions, nullptr,
limitations);
// Set the expectation for the validation error here.
Error expectedError(GL_INVALID_OPERATION, g_ExceedsMaxElementErrorMessage);
EXPECT_CALL(testContext, recordError(expectedError)).Times(1);
// Call once with maximum index, and once with an excessive index.
GLuint indexData[] = {0, 1, static_cast<GLuint>(caps.maxElementIndex - 1),
3, 4, static_cast<GLuint>(caps.maxElementIndex)};
IndexRange indexRange;
EXPECT_TRUE(ValidateDrawElements(&testContext, GL_TRIANGLES, 3, GL_UNSIGNED_INT, indexData, 1,
&indexRange));
EXPECT_FALSE(ValidateDrawElements(&testContext, GL_TRIANGLES, 6, GL_UNSIGNED_INT, indexData, 2,
&indexRange));
texture->release();
SafeDelete(vertexArray);
SafeDelete(framebuffer);
SafeDelete(program);
}
} // anonymous namespace
......@@ -29,7 +29,10 @@
'<(angle_path)/src/libANGLE/ResourceManager_unittest.cpp',
'<(angle_path)/src/libANGLE/Surface_unittest.cpp',
'<(angle_path)/src/libANGLE/TransformFeedback_unittest.cpp',
'<(angle_path)/src/libANGLE/validationES_unittest.cpp',
'<(angle_path)/src/libANGLE/renderer/BufferImpl_mock.h',
'<(angle_path)/src/libANGLE/renderer/FramebufferImpl_mock.h',
'<(angle_path)/src/libANGLE/renderer/ProgramImpl_mock.h',
'<(angle_path)/src/libANGLE/renderer/RenderbufferImpl_mock.h',
'<(angle_path)/src/libANGLE/renderer/ImageImpl_mock.h',
'<(angle_path)/src/libANGLE/renderer/TextureImpl_mock.h',
......
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