/*------------------------------------------------------------------------- * OpenGL Conformance Test Suite * ----------------------------- * * Copyright (c) 2017 The Khronos Group Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /*! * \file glcRobustnessTests.cpp * \brief Conformance tests for the Robustness feature functionality. */ /*-------------------------------------------------------------------*/ #include "glcRobustnessTests.hpp" #include "deSharedPtr.hpp" #include "glcRobustBufferAccessBehaviorTests.hpp" #include "gluContextInfo.hpp" #include "gluPlatform.hpp" #include "gluRenderContext.hpp" #include "glwEnums.hpp" #include "glwFunctions.hpp" #include "tcuCommandLine.hpp" #include "tcuTestLog.hpp" #include using namespace glw; using namespace glcts::RobustBufferAccessBehavior; namespace glcts { namespace ResetNotificationStrategy { class RobustnessBase : public tcu::TestCase { public: RobustnessBase(tcu::TestContext& testCtx, const char* name, const char* description, glu::ApiType apiType); glu::RenderContext* createRobustContext(glu::ResetNotificationStrategy reset); private: glu::ApiType m_ApiType; }; RobustnessBase::RobustnessBase(tcu::TestContext& testCtx, const char* name, const char* description, glu::ApiType apiType) : tcu::TestCase(testCtx, name, description), m_ApiType(apiType) { } glu::RenderContext* RobustnessBase::createRobustContext(glu::ResetNotificationStrategy reset) { /* Create test context to verify if GL_KHR_robustness extension is available */ { deqp::Context context(m_testCtx, glu::ContextType(m_ApiType)); if (!context.getContextInfo().isExtensionSupported("GL_KHR_robustness") && !contextSupports(context.getRenderContext().getType(), glu::ApiType::es(3, 2))) { m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "GL_KHR_robustness extension not supported"); return NULL; } } glu::RenderConfig renderCfg(glu::ContextType(m_ApiType, glu::CONTEXT_ROBUST)); const tcu::CommandLine& commandLine = m_testCtx.getCommandLine(); glu::parseRenderConfig(&renderCfg, commandLine); if (commandLine.getSurfaceType() == tcu::SURFACETYPE_WINDOW) renderCfg.resetNotificationStrategy = reset; else throw tcu::NotSupportedError("Test not supported in non-windowed context"); /* Try to create core/es robusness context */ return createRenderContext(m_testCtx.getPlatform(), commandLine, renderCfg); } class NoResetNotificationCase : public RobustnessBase { typedef glw::GLenum(GLW_APIENTRY* PFNGLGETGRAPHICSRESETSTATUS)(); public: NoResetNotificationCase(tcu::TestContext& testCtx, const char* name, const char* description, glu::ApiType apiType) : RobustnessBase(testCtx, name, description, apiType) { } virtual IterateResult iterate(void) { glu::ResetNotificationStrategy strategy = glu::RESET_NOTIFICATION_STRATEGY_NO_RESET_NOTIFICATION; de::SharedPtr robustContext(createRobustContext(strategy)); if (!robustContext.get()) return STOP; glw::GLint reset = 0; const glw::Functions& gl = robustContext->getFunctions(); gl.getIntegerv(GL_RESET_NOTIFICATION_STRATEGY, &reset); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv"); if (reset != GL_NO_RESET_NOTIFICATION) { m_testCtx.getLog() << tcu::TestLog::Message << "Test failed! glGet returned wrong value [" << reset << ", expected " << GL_NO_RESET_NOTIFICATION << "]." << tcu::TestLog::EndMessage; m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); return STOP; } glw::GLint status = gl.getGraphicsResetStatus(); if (status != GL_NO_ERROR) { m_testCtx.getLog() << tcu::TestLog::Message << "Test failed! glGetGraphicsResetStatus returned wrong value [" << status << ", expected " << GL_NO_ERROR << "]." << tcu::TestLog::EndMessage; m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); return STOP; } m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); return STOP; } }; class LoseContextOnResetCase : public RobustnessBase { public: LoseContextOnResetCase(tcu::TestContext& testCtx, const char* name, const char* description, glu::ApiType apiType) : RobustnessBase(testCtx, name, description, apiType) { } virtual IterateResult iterate(void) { glu::ResetNotificationStrategy strategy = glu::RESET_NOTIFICATION_STRATEGY_LOSE_CONTEXT_ON_RESET; de::SharedPtr robustContext(createRobustContext(strategy)); if (!robustContext.get()) return STOP; glw::GLint reset = 0; const glw::Functions& gl = robustContext->getFunctions(); gl.getIntegerv(GL_RESET_NOTIFICATION_STRATEGY, &reset); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv"); if (reset != GL_LOSE_CONTEXT_ON_RESET) { m_testCtx.getLog() << tcu::TestLog::Message << "Test failed! glGet returned wrong value [" << reset << ", expected " << GL_LOSE_CONTEXT_ON_RESET << "]." << tcu::TestLog::EndMessage; m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); return STOP; } m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); return STOP; } }; } // ResetNotificationStrategy namespace namespace RobustBufferAccessBehavior { static deqp::Context* createContext(tcu::TestContext& testCtx, glu::ApiType apiType) { deqp::Context* context = new deqp::Context(testCtx, glu::ContextType(apiType)); if (!context) { testCtx.setTestResult(QP_TEST_RESULT_INTERNAL_ERROR, "Pointer to context is NULL."); return DE_NULL; } if (!(contextSupports(context->getRenderContext().getType(), glu::ApiType::es(3, 2)) || (context->getContextInfo().isExtensionSupported("GL_KHR_robustness") && context->getContextInfo().isExtensionSupported("GL_KHR_robust_buffer_access_behavior")))) { testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not Supported"); delete context; return DE_NULL; } return context; } /** Implementation of test GetnUniformTest. Description follows: * * This test verifies if read uniform variables to the buffer with bufSize less than expected result with GL_INVALID_OPERATION error; **/ class GetnUniformTest : public tcu::TestCase { public: /* Public methods */ GetnUniformTest(tcu::TestContext& testCtx, glu::ApiType apiType); virtual ~GetnUniformTest() { } /* Public methods inherited from TestCase */ virtual tcu::TestNode::IterateResult iterate(void); private: /* Private methods */ std::string getComputeShader(bool glslES320); bool verifyResult(const void* inputData, const void* resultData, int size, const char* method); bool verifyError(glw::GLint error, glw::GLint expectedError, const char* method); glu::ApiType m_ApiType; }; /** Constructor * * @param context Test context **/ GetnUniformTest::GetnUniformTest(tcu::TestContext& testCtx, glu::ApiType apiType) : tcu::TestCase(testCtx, "getnuniform", "Verifies if read uniform variables to the buffer with bufSize less than " "expected result with GL_INVALID_OPERATION") , m_ApiType(apiType) { /* Nothing to be done here */ } /** Execute test * * @return tcu::TestNode::STOP **/ tcu::TestNode::IterateResult GetnUniformTest::iterate() { de::SharedPtr context(createContext(m_testCtx, m_ApiType)); if (!context.get()) return STOP; glu::RenderContext& renderContext = context->getRenderContext(); const Functions& gl = renderContext.getFunctions(); const GLfloat input4f[] = { 1.0f, 5.4f, 3.14159f, 1.28f }; const GLint input3i[] = { 10, -20, -30 }; const GLuint input4ui[] = { 10, 20, 30, 40 }; /* Test result indicator */ bool test_result = true; /* Iterate over all cases */ Program program(gl); /* Compute Shader */ bool glslES320 = contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)); const std::string& cs = getComputeShader(glslES320); /* Shaders initialization */ program.Init(cs /* cs */, "" /* fs */, "" /* gs */, "" /* tcs */, "" /* tes */, "" /* vs */); program.Use(); /* Initialize shader storage buffer */ GLuint buf; gl.genBuffers(1, &buf); GLU_EXPECT_NO_ERROR(gl.getError(), "GenBuffers"); gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buf); GLU_EXPECT_NO_ERROR(gl.getError(), "BindBufferBase"); gl.bufferData(GL_SHADER_STORAGE_BUFFER, 16, DE_NULL, GL_STREAM_DRAW); GLU_EXPECT_NO_ERROR(gl.getError(), "BufferData"); /* passing uniform values */ gl.programUniform4fv(program.m_id, 11, 1, input4f); GLU_EXPECT_NO_ERROR(gl.getError(), "ProgramUniform4fv"); gl.programUniform3iv(program.m_id, 12, 1, input3i); GLU_EXPECT_NO_ERROR(gl.getError(), "ProgramUniform3iv"); gl.programUniform4uiv(program.m_id, 13, 1, input4ui); GLU_EXPECT_NO_ERROR(gl.getError(), "ProgramUniform4uiv"); gl.dispatchCompute(1, 1, 1); GLU_EXPECT_NO_ERROR(gl.getError(), "DispatchCompute"); /* veryfing gfetnUniform error messages */ GLfloat result4f[4]; GLint result3i[3]; GLuint result4ui[4]; gl.getnUniformfv(program.m_id, 11, sizeof(GLfloat) * 4, result4f); test_result = test_result && verifyResult((void*)input4f, (void*)result4f, sizeof(GLfloat) * 4, "getnUniformfv [false negative]"); test_result = test_result && verifyError(gl.getError(), GL_NO_ERROR, "getnUniformfv [false negative]"); gl.getnUniformfv(program.m_id, 11, sizeof(GLfloat) * 3, result4f); test_result = test_result && verifyError(gl.getError(), GL_INVALID_OPERATION, "getnUniformfv [false positive]"); gl.getnUniformiv(program.m_id, 12, sizeof(GLint) * 3, result3i); test_result = test_result && verifyResult((void*)input3i, (void*)result3i, sizeof(GLint) * 3, "getnUniformiv [false negative]"); test_result = test_result && verifyError(gl.getError(), GL_NO_ERROR, "getnUniformiv [false negative]"); gl.getnUniformiv(program.m_id, 12, sizeof(GLint) * 2, result3i); test_result = test_result && verifyError(gl.getError(), GL_INVALID_OPERATION, "getnUniformiv [false positive]"); gl.getnUniformuiv(program.m_id, 13, sizeof(GLuint) * 4, result4ui); test_result = test_result && verifyResult((void*)input4ui, (void*)result4ui, sizeof(GLuint) * 4, "getnUniformuiv [false negative]"); test_result = test_result && verifyError(gl.getError(), GL_NO_ERROR, "getnUniformuiv [false negative]"); gl.getnUniformuiv(program.m_id, 13, sizeof(GLuint) * 3, result4ui); test_result = test_result && verifyError(gl.getError(), GL_INVALID_OPERATION, "getnUniformuiv [false positive]"); /* Set result */ if (true == test_result) { m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); } else { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); } gl.deleteBuffers(1, &buf); /* Done */ return tcu::TestNode::STOP; } std::string GetnUniformTest::getComputeShader(bool glslES320) { std::stringstream shader; shader << "#version " << (glslES320 ? "320 es\n" : "450\n"); shader << "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" "layout (location = 11) uniform vec4 inputf;\n" "layout (location = 12) uniform ivec3 inputi;\n" "layout (location = 13) uniform uvec4 inputu;\n" "layout (std140, binding = 0) buffer ssbo {\n" " float valuef;\n" " int valuei;\n" " uint valueu;\n" "};\n" "void main()\n" "{\n" " valuef = inputf.r + inputf.g + inputf.b + inputf.a;\n" " valuei = inputi.r + inputi.g + inputi.b;\n" " valueu = inputu.r + inputu.g + inputu.b + inputu.a;\n" "}\n"; return shader.str(); } bool GetnUniformTest::verifyResult(const void* inputData, const void* resultData, int size, const char* method) { int diff = memcmp(inputData, resultData, size); if (diff != 0) { m_testCtx.getLog() << tcu::TestLog::Message << "Test failed! " << method << " result is not as expected." << tcu::TestLog::EndMessage; return false; } return true; } bool GetnUniformTest::verifyError(GLint error, GLint expectedError, const char* method) { if (error != expectedError) { m_testCtx.getLog() << tcu::TestLog::Message << "Test failed! " << method << " throws unexpected error [" << error << "]." << tcu::TestLog::EndMessage; return false; } return true; } /** Implementation of test ReadnPixelsTest. Description follows: * * This test verifies if read pixels to the buffer with bufSize less than expected result with GL_INVALID_OPERATION error; **/ class ReadnPixelsTest : public tcu::TestCase { public: /* Public methods */ ReadnPixelsTest(tcu::TestContext& testCtx, glu::ApiType apiType); virtual ~ReadnPixelsTest() { } /* Public methods inherited from TestCase */ virtual tcu::TestNode::IterateResult iterate(void); private: /* Private methods */ void cleanTexture(deqp::Context& context, glw::GLuint texture_id); bool verifyResults(deqp::Context& context); bool verifyError(glw::GLint error, glw::GLint expectedError, const char* method); glu::ApiType m_ApiType; }; /** Constructor * * @param context Test context **/ ReadnPixelsTest::ReadnPixelsTest(tcu::TestContext& testCtx, glu::ApiType apiType) : tcu::TestCase(testCtx, "readnpixels", "Verifies if read pixels to the buffer with bufSize less than expected result " "with GL_INVALID_OPERATION error") , m_ApiType(apiType) { /* Nothing to be done here */ } /** Execute test * * @return tcu::TestNode::STOP **/ tcu::TestNode::IterateResult ReadnPixelsTest::iterate() { de::SharedPtr context(createContext(m_testCtx, m_ApiType)); if (!context.get()) return STOP; static const GLuint elements[] = { 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7, 0, 7, 8, 0, 8, 1, }; static const GLfloat vertices[] = { 0.0f, 0.0f, 0.0f, 1.0f, /* 0 */ -1.0f, 0.0f, 0.0f, 1.0f, /* 1 */ -1.0f, 1.0f, 0.0f, 1.0f, /* 2 */ 0.0f, 1.0f, 0.0f, 1.0f, /* 3 */ 1.0f, 1.0f, 0.0f, 1.0f, /* 4 */ 1.0f, 0.0f, 0.0f, 1.0f, /* 5 */ 1.0f, -1.0f, 0.0f, 1.0f, /* 6 */ 0.0f, -1.0f, 0.0f, 1.0f, /* 7 */ -1.0f, -1.0f, 0.0f, 1.0f, /* 8 */ }; bool glslES320 = contextSupports(context->getRenderContext().getType(), glu::ApiType::es(3, 2)); std::string fs("#version "); fs += (glslES320 ? "320 es\n" : "450\n"); fs += "layout (location = 0) out lowp uvec4 out_fs_color;\n" "\n" "void main()\n" "{\n" " out_fs_color = uvec4(1, 0, 0, 1);\n" "}\n" "\n"; std::string vs("#version "); vs += (glslES320 ? "320 es\n" : "450\n"); vs += "layout (location = 0) in vec4 in_vs_position;\n" "\n" "void main()\n" "{\n" " gl_Position = in_vs_position;\n" "}\n" "\n"; static const GLuint height = 8; static const GLuint width = 8; static const GLuint n_vertices = 24; /* GL entry points */ const Functions& gl = context->getRenderContext().getFunctions(); /* Test case objects */ Program program(gl); Texture texture(gl); Buffer elements_buffer(gl); Buffer vertices_buffer(gl); VertexArray vao(gl); /* Vertex array initialization */ VertexArray::Generate(gl, vao.m_id); VertexArray::Bind(gl, vao.m_id); /* Texture initialization */ Texture::Generate(gl, texture.m_id); Texture::Bind(gl, texture.m_id, GL_TEXTURE_2D); Texture::Storage(gl, GL_TEXTURE_2D, 1, GL_R8UI, width, height, 0); Texture::Bind(gl, 0, GL_TEXTURE_2D); /* Framebuffer initialization */ GLuint fbo; gl.genFramebuffers(1, &fbo); GLU_EXPECT_NO_ERROR(gl.getError(), "GenFramebuffers"); gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); GLU_EXPECT_NO_ERROR(gl.getError(), "BindFramebuffer"); gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.m_id, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "FramebufferTexture2D"); /* Buffers initialization */ elements_buffer.InitData(GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW, sizeof(elements), elements); vertices_buffer.InitData(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, sizeof(vertices), vertices); /* Shaders initialization */ program.Init("" /* cs */, fs, "" /* gs */, "" /* tcs */, "" /* tes */, vs); Program::Use(gl, program.m_id); /* Vertex buffer initialization */ vertices_buffer.Bind(); gl.bindVertexBuffer(0 /* bindindex = location */, vertices_buffer.m_id, 0 /* offset */, 16 /* stride */); gl.vertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 16, NULL); gl.enableVertexAttribArray(0 /* location */); /* Binding elements/indices buffer */ elements_buffer.Bind(); cleanTexture(*context, texture.m_id); gl.drawElements(GL_TRIANGLES, n_vertices, GL_UNSIGNED_INT, 0 /* indices */); GLU_EXPECT_NO_ERROR(gl.getError(), "DrawElements"); /* Set result */ if (verifyResults(*context)) { m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); } else { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail"); } /* Done */ return tcu::TestNode::STOP; } /** Fill texture with value 128 * * @param texture_id Id of texture **/ void ReadnPixelsTest::cleanTexture(deqp::Context& context, glw::GLuint texture_id) { static const GLuint height = 8; static const GLuint width = 8; const Functions& gl = context.getRenderContext().getFunctions(); GLubyte pixels[width * height]; for (GLuint i = 0; i < width * height; ++i) { pixels[i] = 64; } Texture::Bind(gl, texture_id, GL_TEXTURE_2D); Texture::SubImage(gl, GL_TEXTURE_2D, 0 /* level */, 0 /* x */, 0 /* y */, 0 /* z */, width, height, 0 /* depth */, GL_RED_INTEGER, GL_UNSIGNED_BYTE, pixels); /* Unbind */ Texture::Bind(gl, 0, GL_TEXTURE_2D); } /** Verifies glReadnPixels results * * @return true when glReadnPixels , false otherwise **/ bool ReadnPixelsTest::verifyResults(deqp::Context& context) { static const GLuint height = 8; static const GLuint width = 8; static const GLuint pixel_size = 4 * sizeof(GLuint); const Functions& gl = context.getRenderContext().getFunctions(); //Valid buffer size test const GLint bufSizeValid = width * height * pixel_size; GLubyte pixelsValid[bufSizeValid]; gl.readnPixels(0, 0, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_INT, bufSizeValid, pixelsValid); GLU_EXPECT_NO_ERROR(gl.getError(), "ReadnPixels"); //Verify glReadnPixels result for (unsigned int i = 0; i < width * height; ++i) { const size_t offset = i * pixel_size; const GLuint value = *(GLuint*)(pixelsValid + offset); if (value != 1) { context.getTestContext().getLog() << tcu::TestLog::Message << "Invalid pixel value: " << value << ". Offset: " << offset << tcu::TestLog::EndMessage; return false; } } //Invalid buffer size test const GLint bufSizeInvalid = width * height * pixel_size - 1; GLubyte pixelsInvalid[bufSizeInvalid]; gl.readnPixels(0, 0, width, height, GL_RGBA_INTEGER, GL_UNSIGNED_INT, bufSizeInvalid, pixelsInvalid); if (!verifyError(gl.getError(), GL_INVALID_OPERATION, "ReadnPixels [false positive]")) return false; return true; } /** Verify operation errors * * @param error OpenGL ES error code * @param expectedError Expected error code * @param method Method name marker * * @return true when error is as expected, false otherwise **/ bool ReadnPixelsTest::verifyError(GLint error, GLint expectedError, const char* method) { if (error != expectedError) { m_testCtx.getLog() << tcu::TestLog::Message << "Test failed! " << method << " throws unexpected error [" << error << "]." << tcu::TestLog::EndMessage; return false; } return true; } } // RobustBufferAccessBehavior namespace RobustnessTests::RobustnessTests(tcu::TestContext& testCtx, glu::ApiType apiType) : tcu::TestCaseGroup(testCtx, "robustness", "Verifies API coverage and functionality of GL_KHR_robustness extension.") , m_ApiType(apiType) { } void RobustnessTests::init(void) { tcu::TestCaseGroup::init(); try { addChild(new ResetNotificationStrategy::NoResetNotificationCase( m_testCtx, "no_reset_notification", "Verifies if NO_RESET_NOTIFICATION strategy works as expected.", m_ApiType)); addChild(new ResetNotificationStrategy::LoseContextOnResetCase( m_testCtx, "lose_context_on_reset", "Verifies if LOSE_CONTEXT_ON_RESET strategy works as expected.", m_ApiType)); addChild(new RobustBufferAccessBehavior::GetnUniformTest(m_testCtx, m_ApiType)); addChild(new RobustBufferAccessBehavior::ReadnPixelsTest(m_testCtx, m_ApiType)); } catch (...) { // Destroy context. tcu::TestCaseGroup::deinit(); throw; } } } // glcts namespace