| /*------------------------------------------------------------------------- |
| * drawElements Quality Program OpenGL (ES) Module |
| * ----------------------------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * 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 |
| * \brief Common object lifetime tests. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "glsLifetimeTests.hpp" |
| |
| #include "deString.h" |
| #include "deRandom.hpp" |
| #include "deSTLUtil.hpp" |
| #include "deStringUtil.hpp" |
| #include "tcuRGBA.hpp" |
| #include "tcuImageCompare.hpp" |
| #include "tcuRenderTarget.hpp" |
| #include "tcuStringTemplate.hpp" |
| #include "tcuTestLog.hpp" |
| #include "gluDrawUtil.hpp" |
| #include "gluObjectWrapper.hpp" |
| #include "gluPixelTransfer.hpp" |
| #include "gluShaderProgram.hpp" |
| #include "gluDefs.hpp" |
| #include "glwFunctions.hpp" |
| |
| #include <vector> |
| #include <map> |
| #include <algorithm> |
| #include <sstream> |
| |
| namespace deqp |
| { |
| namespace gls |
| { |
| namespace LifetimeTests |
| { |
| namespace details |
| { |
| |
| using std::map; |
| using std::string; |
| using std::ostringstream; |
| using de::Random; |
| using tcu::RenderTarget; |
| using tcu::RGBA; |
| using tcu::StringTemplate; |
| using tcu::TestCase; |
| typedef TestCase::IterateResult IterateResult; |
| using tcu::TestLog; |
| using tcu::ScopedLogSection; |
| using glu::Program; |
| using glu::Shader; |
| using glu::Framebuffer; |
| using glu::SHADERTYPE_VERTEX; |
| using glu::SHADERTYPE_FRAGMENT; |
| using namespace glw; |
| |
| enum { VIEWPORT_SIZE = 128, FRAMEBUFFER_SIZE = 128 }; |
| |
| GLint getInteger (ContextWrapper& gl, GLenum queryParam) |
| { |
| GLint ret = 0; |
| GLU_CHECK_CALL_ERROR( |
| gl.glGetIntegerv(queryParam, &ret), |
| gl.glGetError()); |
| gl.log() << TestLog::Message << "// Single integer output: " << ret << TestLog::EndMessage; |
| return ret; |
| } |
| |
| #define GLSL100_SRC(BODY) ("#version 100\n" #BODY "\n") |
| |
| static const char* const s_vertexShaderSrc = GLSL100_SRC( |
| attribute vec2 pos; |
| void main() |
| { |
| gl_Position = vec4(pos.xy, 0.0, 1.0); |
| } |
| ); |
| |
| static const char* const s_fragmentShaderSrc = GLSL100_SRC( |
| void main() |
| { |
| gl_FragColor = vec4(1.0); |
| } |
| ); |
| |
| class CheckedShader : public Shader |
| { |
| public: |
| CheckedShader (const RenderContext& renderCtx, glu::ShaderType type, const string& src) |
| : Shader (renderCtx, type) |
| { |
| const char* const srcStr = src.c_str(); |
| setSources(1, &srcStr, DE_NULL); |
| compile(); |
| TCU_CHECK(getCompileStatus()); |
| } |
| }; |
| |
| class CheckedProgram : public Program |
| { |
| public: |
| CheckedProgram (const RenderContext& renderCtx, GLuint vtxShader, GLuint fragShader) |
| : Program (renderCtx) |
| { |
| attachShader(vtxShader); |
| attachShader(fragShader); |
| link(); |
| TCU_CHECK(getLinkStatus()); |
| } |
| }; |
| |
| ContextWrapper::ContextWrapper (const Context& ctx) |
| : CallLogWrapper (ctx.gl(), ctx.log()) |
| , m_ctx (ctx) |
| { |
| enableLogging(true); |
| } |
| |
| void SimpleBinder::bind (GLuint name) |
| { |
| (this->*m_bindFunc)(m_bindTarget, name); |
| } |
| |
| GLuint SimpleBinder::getBinding (void) |
| { |
| return getInteger(*this, m_bindingParam); |
| } |
| |
| GLuint SimpleType::gen (void) |
| { |
| GLuint ret; |
| (this->*m_genFunc)(1, &ret); |
| return ret; |
| } |
| |
| class VertexArrayBinder : public SimpleBinder |
| { |
| public: |
| VertexArrayBinder (Context& ctx) |
| : SimpleBinder (ctx, 0, GL_NONE, GL_VERTEX_ARRAY_BINDING, true) {} |
| void bind (GLuint name) { glBindVertexArray(name); } |
| }; |
| |
| class QueryBinder : public Binder |
| { |
| public: |
| QueryBinder (Context& ctx) : Binder(ctx) {} |
| void bind (GLuint name) |
| { |
| if (name != 0) |
| glBeginQuery(GL_ANY_SAMPLES_PASSED, name); |
| else |
| glEndQuery(GL_ANY_SAMPLES_PASSED); |
| } |
| GLuint getBinding (void) { return 0; } |
| }; |
| |
| bool ProgramType::isDeleteFlagged (GLuint name) |
| { |
| GLint deleteFlagged = 0; |
| glGetProgramiv(name, GL_DELETE_STATUS, &deleteFlagged); |
| return deleteFlagged != 0; |
| } |
| |
| bool ShaderType::isDeleteFlagged (GLuint name) |
| { |
| GLint deleteFlagged = 0; |
| glGetShaderiv(name, GL_DELETE_STATUS, &deleteFlagged); |
| return deleteFlagged != 0; |
| } |
| |
| void setupFbo (const Context& ctx, GLuint seed, GLuint fbo) |
| { |
| const Functions& gl = ctx.getRenderContext().getFunctions(); |
| |
| GLU_CHECK_CALL_ERROR(gl.bindFramebuffer(GL_FRAMEBUFFER, fbo), |
| gl.getError()); |
| |
| if (seed == 0) |
| { |
| gl.clearColor(0.0, 0.0, 0.0, 1.0); |
| GLU_CHECK_CALL_ERROR(gl.clear(GL_COLOR_BUFFER_BIT), gl.getError()); |
| } |
| else |
| { |
| Random rnd (seed); |
| const GLsizei width = rnd.getInt(0, FRAMEBUFFER_SIZE); |
| const GLsizei height = rnd.getInt(0, FRAMEBUFFER_SIZE); |
| const GLint x = rnd.getInt(0, FRAMEBUFFER_SIZE - width); |
| const GLint y = rnd.getInt(0, FRAMEBUFFER_SIZE - height); |
| const GLfloat r1 = rnd.getFloat(); |
| const GLfloat g1 = rnd.getFloat(); |
| const GLfloat b1 = rnd.getFloat(); |
| const GLfloat a1 = rnd.getFloat(); |
| const GLfloat r2 = rnd.getFloat(); |
| const GLfloat g2 = rnd.getFloat(); |
| const GLfloat b2 = rnd.getFloat(); |
| const GLfloat a2 = rnd.getFloat(); |
| |
| GLU_CHECK_CALL_ERROR(gl.clearColor(r1, g1, b1, a1), gl.getError()); |
| GLU_CHECK_CALL_ERROR(gl.clear(GL_COLOR_BUFFER_BIT), gl.getError()); |
| gl.scissor(x, y, width, height); |
| gl.enable(GL_SCISSOR_TEST); |
| gl.clearColor(r2, g2, b2, a2); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| gl.disable(GL_SCISSOR_TEST); |
| } |
| |
| gl.bindFramebuffer(GL_FRAMEBUFFER, 0); |
| GLU_CHECK_ERROR(gl.getError()); |
| } |
| |
| void drawFbo (const Context& ctx, GLuint fbo, Surface& dst) |
| { |
| const RenderContext& renderCtx = ctx.getRenderContext(); |
| const Functions& gl = renderCtx.getFunctions(); |
| |
| GLU_CHECK_CALL_ERROR( |
| gl.bindFramebuffer(GL_FRAMEBUFFER, fbo), |
| gl.getError()); |
| |
| dst.setSize(FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE); |
| glu::readPixels(renderCtx, 0, 0, dst.getAccess()); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels from framebuffer"); |
| |
| GLU_CHECK_CALL_ERROR( |
| gl.bindFramebuffer(GL_FRAMEBUFFER, 0), |
| gl.getError()); |
| } |
| |
| GLuint getFboAttachment (const Functions& gl, GLuint fbo, GLenum requiredType) |
| { |
| GLint type = 0, name = 0; |
| gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); |
| GLU_CHECK_CALL_ERROR( |
| gl.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, |
| &type), |
| gl.getError()); |
| |
| if (GLenum(type) != requiredType || GLenum(type) == GL_NONE) |
| return 0; |
| |
| GLU_CHECK_CALL_ERROR( |
| gl.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, |
| &name), |
| gl.getError()); |
| gl.bindFramebuffer(GL_FRAMEBUFFER, 0); |
| GLU_CHECK_ERROR(gl.getError()); |
| |
| return name; |
| } |
| |
| void FboAttacher::initAttachment (GLuint seed, GLuint element) |
| { |
| Binder& binder = *getElementType().binder(); |
| Framebuffer fbo(getRenderContext()); |
| |
| enableLogging(false); |
| |
| binder.enableLogging(false); |
| binder.bind(element); |
| initStorage(); |
| binder.bind(0); |
| binder.enableLogging(true); |
| |
| attach(element, *fbo); |
| setupFbo(getContext(), seed, *fbo); |
| detach(element, *fbo); |
| |
| enableLogging(true); |
| |
| log() << TestLog::Message |
| << "// Drew to " << getElementType().getName() << " " << element |
| << " with seed " << seed << "." |
| << TestLog::EndMessage; |
| } |
| |
| void FboInputAttacher::drawContainer (GLuint fbo, Surface& dst) |
| { |
| drawFbo(getContext(), fbo, dst); |
| log() << TestLog::Message |
| << "// Read pixels from framebuffer " << fbo << " to output image." |
| << TestLog::EndMessage; |
| } |
| |
| void FboOutputAttacher::setupContainer (GLuint seed, GLuint fbo) |
| { |
| setupFbo(getContext(), seed, fbo); |
| log() << TestLog::Message |
| << "// Drew to framebuffer " << fbo << " with seed " << seed << "." |
| << TestLog::EndMessage; |
| } |
| |
| void FboOutputAttacher::drawAttachment (GLuint element, Surface& dst) |
| { |
| Framebuffer fbo(getRenderContext()); |
| m_attacher.enableLogging(false); |
| m_attacher.attach(element, *fbo); |
| drawFbo(getContext(), *fbo, dst); |
| m_attacher.detach(element, *fbo); |
| m_attacher.enableLogging(true); |
| log() << TestLog::Message |
| << "// Read pixels from " << m_attacher.getElementType().getName() << " " << element |
| << " to output image." |
| << TestLog::EndMessage; |
| GLU_CHECK_ERROR(gl().getError()); |
| } |
| |
| void TextureFboAttacher::attach (GLuint texture, GLuint fbo) |
| { |
| GLU_CHECK_CALL_ERROR( |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo), |
| gl().getError()); |
| GLU_CHECK_CALL_ERROR( |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_TEXTURE_2D, texture, 0), |
| gl().getError()); |
| GLU_CHECK_CALL_ERROR( |
| glBindFramebuffer(GL_FRAMEBUFFER, 0), |
| gl().getError()); |
| } |
| |
| void TextureFboAttacher::detach (GLuint texture, GLuint fbo) |
| { |
| DE_UNREF(texture); |
| GLU_CHECK_CALL_ERROR( |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo), |
| gl().getError()); |
| GLU_CHECK_CALL_ERROR( |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0), |
| gl().getError()); |
| GLU_CHECK_CALL_ERROR( |
| glBindFramebuffer(GL_FRAMEBUFFER, 0), |
| gl().getError()); |
| } |
| |
| GLuint TextureFboAttacher::getAttachment (GLuint fbo) |
| { |
| return getFboAttachment(gl(), fbo, GL_TEXTURE); |
| } |
| |
| void TextureFboAttacher::initStorage (void) |
| { |
| GLU_CHECK_CALL_ERROR( |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE, 0, |
| GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, DE_NULL), |
| gl().getError()); |
| } |
| |
| void RboFboAttacher::initStorage (void) |
| { |
| GLU_CHECK_CALL_ERROR( |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE), |
| gl().getError()); |
| } |
| |
| void RboFboAttacher::attach (GLuint rbo, GLuint fbo) |
| { |
| GLU_CHECK_CALL_ERROR( |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo), |
| gl().getError()); |
| GLU_CHECK_CALL_ERROR( |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo), |
| gl().getError()); |
| GLU_CHECK_CALL_ERROR( |
| glBindFramebuffer(GL_FRAMEBUFFER, 0), |
| gl().getError()); |
| } |
| |
| void RboFboAttacher::detach (GLuint rbo, GLuint fbo) |
| { |
| DE_UNREF(rbo); |
| GLU_CHECK_CALL_ERROR( |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo), |
| gl().getError()); |
| GLU_CHECK_CALL_ERROR( |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0), |
| gl().getError()); |
| GLU_CHECK_CALL_ERROR( |
| glBindFramebuffer(GL_FRAMEBUFFER, 0), |
| gl().getError()); |
| } |
| |
| GLuint RboFboAttacher::getAttachment (GLuint fbo) |
| { |
| return getFboAttachment(gl(), fbo, GL_RENDERBUFFER); |
| } |
| |
| static const char* const s_fragmentShaderTemplate = GLSL100_SRC( |
| void main() |
| { |
| gl_FragColor = vec4(${RED}, ${GREEN}, ${BLUE}, 1.0); |
| } |
| ); |
| |
| void ShaderProgramAttacher::initAttachment (GLuint seed, GLuint shader) |
| { |
| using de::insert; |
| using de::floatToString; |
| |
| Random rnd(seed); |
| map<string, string> params; |
| const StringTemplate sourceTmpl (s_fragmentShaderTemplate); |
| |
| insert(params, "RED", floatToString(rnd.getFloat(), 4)); |
| insert(params, "GREEN", floatToString(rnd.getFloat(), 4)); |
| insert(params, "BLUE", floatToString(rnd.getFloat(), 4)); |
| |
| { |
| const string source = sourceTmpl.specialize(params); |
| const char* const sourceStr = source.c_str(); |
| |
| GLU_CHECK_CALL_ERROR(glShaderSource(shader, 1, &sourceStr, DE_NULL), gl().getError()); |
| GLU_CHECK_CALL_ERROR(glCompileShader(shader), gl().getError()); |
| |
| { |
| GLint compileStatus = 0; |
| gl().getShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); |
| TCU_CHECK_MSG(compileStatus != 0, sourceStr); |
| } |
| } |
| } |
| |
| void ShaderProgramAttacher::attach (GLuint shader, GLuint program) |
| { |
| GLU_CHECK_CALL_ERROR( |
| glAttachShader(program, shader), |
| gl().getError()); |
| } |
| |
| void ShaderProgramAttacher::detach (GLuint shader, GLuint program) |
| { |
| GLU_CHECK_CALL_ERROR( |
| glDetachShader(program, shader), |
| gl().getError()); |
| } |
| |
| GLuint ShaderProgramAttacher::getAttachment (GLuint program) |
| { |
| GLuint shaders[2] = { 0, 0 }; |
| const GLsizei shadersLen = DE_LENGTH_OF_ARRAY(shaders); |
| GLsizei numShaders = 0; |
| GLuint ret = 0; |
| |
| gl().getAttachedShaders(program, shadersLen, &numShaders, shaders); |
| |
| // There should ever be at most one attached shader in normal use, but if |
| // something is wrong, the temporary vertex shader might not have been |
| // detached properly, so let's find the fragment shader explicitly. |
| for (int ndx = 0; ndx < de::min<GLsizei>(shadersLen, numShaders); ++ndx) |
| { |
| GLint shaderType = GL_NONE; |
| gl().getShaderiv(shaders[ndx], GL_SHADER_TYPE, &shaderType); |
| |
| if (shaderType == GL_FRAGMENT_SHADER) |
| { |
| ret = shaders[ndx]; |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| void setViewport (const RenderContext& renderCtx, const Rectangle& rect) |
| { |
| renderCtx.getFunctions().viewport(rect.x, rect.y, rect.width, rect.height); |
| } |
| |
| void readRectangle (const RenderContext& renderCtx, const Rectangle& rect, Surface& dst) |
| { |
| dst.setSize(rect.width, rect.height); |
| glu::readPixels(renderCtx, rect.x, rect.y, dst.getAccess()); |
| } |
| |
| Rectangle randomViewport (const RenderContext& ctx, GLint maxWidth, GLint maxHeight, |
| Random& rnd) |
| { |
| const RenderTarget& target = ctx.getRenderTarget(); |
| const GLint width = de::min(target.getWidth(), maxWidth); |
| const GLint xOff = rnd.getInt(0, target.getWidth() - width); |
| const GLint height = de::min(target.getHeight(), maxHeight); |
| const GLint yOff = rnd.getInt(0, target.getHeight() - height); |
| |
| return Rectangle(xOff, yOff, width, height); |
| } |
| |
| void ShaderProgramInputAttacher::drawContainer (GLuint program, Surface& dst) |
| { |
| static const float s_vertices[6] = { -1.0, 0.0, 1.0, 1.0, 0.0, -1.0 }; |
| Random rnd (program); |
| CheckedShader vtxShader (getRenderContext(), |
| SHADERTYPE_VERTEX, s_vertexShaderSrc); |
| const Rectangle viewport = randomViewport(getRenderContext(), |
| VIEWPORT_SIZE, VIEWPORT_SIZE, rnd); |
| |
| gl().attachShader(program, vtxShader.getShader()); |
| gl().linkProgram(program); |
| |
| { |
| GLint linkStatus = 0; |
| gl().getProgramiv(program, GL_LINK_STATUS, &linkStatus); |
| TCU_CHECK(linkStatus != 0); |
| } |
| |
| log() << TestLog::Message |
| << "// Attached a temporary vertex shader and linked program " << program |
| << TestLog::EndMessage; |
| |
| setViewport(getRenderContext(), viewport); |
| log() << TestLog::Message << "// Positioned viewport randomly" << TestLog::EndMessage; |
| |
| glUseProgram(program); |
| { |
| GLint posLoc = gl().getAttribLocation(program, "pos"); |
| TCU_CHECK(posLoc >= 0); |
| |
| gl().enableVertexAttribArray(posLoc); |
| |
| gl().clearColor(0, 0, 0, 1); |
| gl().clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| gl().vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, s_vertices); |
| gl().drawArrays(GL_TRIANGLES, 0, 3); |
| |
| gl().disableVertexAttribArray(posLoc); |
| log () << TestLog::Message << "// Drew a fixed triangle" << TestLog::EndMessage; |
| } |
| glUseProgram(0); |
| |
| readRectangle(getRenderContext(), viewport, dst); |
| log() << TestLog::Message << "// Copied viewport to output image" << TestLog::EndMessage; |
| |
| gl().detachShader(program, vtxShader.getShader()); |
| log() << TestLog::Message << "// Removed temporary vertex shader" << TestLog::EndMessage; |
| } |
| |
| ES2Types::ES2Types (const Context& ctx) |
| : Types (ctx) |
| , m_bufferBind (ctx, &CallLogWrapper::glBindBuffer, |
| GL_ARRAY_BUFFER, GL_ARRAY_BUFFER_BINDING) |
| , m_bufferType (ctx, "buffer", &CallLogWrapper::glGenBuffers, |
| &CallLogWrapper::glDeleteBuffers, |
| &CallLogWrapper::glIsBuffer, &m_bufferBind) |
| , m_textureBind (ctx, &CallLogWrapper::glBindTexture, GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D) |
| , m_textureType (ctx, "texture", &CallLogWrapper::glGenTextures, |
| &CallLogWrapper::glDeleteTextures, |
| &CallLogWrapper::glIsTexture, &m_textureBind) |
| , m_rboBind (ctx, &CallLogWrapper::glBindRenderbuffer, |
| GL_RENDERBUFFER, GL_RENDERBUFFER_BINDING) |
| , m_rboType (ctx, "renderbuffer", |
| &CallLogWrapper::glGenRenderbuffers, |
| &CallLogWrapper::glDeleteRenderbuffers, |
| &CallLogWrapper::glIsRenderbuffer, &m_rboBind) |
| , m_fboBind (ctx, &CallLogWrapper::glBindFramebuffer, |
| GL_FRAMEBUFFER, GL_FRAMEBUFFER_BINDING) |
| , m_fboType (ctx, "framebuffer", |
| &CallLogWrapper::glGenFramebuffers, |
| &CallLogWrapper::glDeleteFramebuffers, |
| &CallLogWrapper::glIsFramebuffer, &m_fboBind) |
| , m_shaderType (ctx) |
| , m_programType (ctx) |
| , m_texFboAtt (ctx, m_textureType, m_fboType) |
| , m_texFboInAtt (m_texFboAtt) |
| , m_texFboOutAtt(m_texFboAtt) |
| , m_rboFboAtt (ctx, m_rboType, m_fboType) |
| , m_rboFboInAtt (m_rboFboAtt) |
| , m_rboFboOutAtt(m_rboFboAtt) |
| , m_shaderAtt (ctx, m_shaderType, m_programType) |
| , m_shaderInAtt (m_shaderAtt) |
| { |
| Type* const types[] = |
| { |
| &m_bufferType, &m_textureType, &m_rboType, &m_fboType, &m_shaderType, &m_programType |
| }; |
| m_types.insert(m_types.end(), DE_ARRAY_BEGIN(types), DE_ARRAY_END(types)); |
| |
| m_attachers.push_back(&m_texFboAtt); |
| m_attachers.push_back(&m_rboFboAtt); |
| m_attachers.push_back(&m_shaderAtt); |
| |
| m_inAttachers.push_back(&m_texFboInAtt); |
| m_inAttachers.push_back(&m_rboFboInAtt); |
| m_inAttachers.push_back(&m_shaderInAtt); |
| |
| m_outAttachers.push_back(&m_texFboOutAtt); |
| m_outAttachers.push_back(&m_rboFboOutAtt); |
| } |
| |
| class Name |
| { |
| public: |
| Name (Type& type) : m_type(type), m_name(type.gen()) {} |
| Name (Type& type, GLuint name) : m_type(type), m_name(name) {} |
| ~Name (void) { m_type.release(m_name); } |
| GLuint operator* (void) const { return m_name; } |
| |
| private: |
| Type& m_type; |
| const GLuint m_name; |
| }; |
| |
| class ResultCollector |
| { |
| public: |
| ResultCollector (TestContext& testCtx); |
| bool check (bool cond, const char* msg); |
| void fail (const char* msg); |
| void warn (const char* msg); |
| ~ResultCollector (void); |
| |
| private: |
| void addResult (qpTestResult result, const char* msg); |
| |
| TestContext& m_testCtx; |
| TestLog& m_log; |
| qpTestResult m_result; |
| const char* m_message; |
| }; |
| |
| ResultCollector::ResultCollector (TestContext& testCtx) |
| : m_testCtx (testCtx) |
| , m_log (testCtx.getLog()) |
| , m_result (QP_TEST_RESULT_PASS) |
| , m_message ("Pass") |
| { |
| } |
| |
| bool ResultCollector::check (bool cond, const char* msg) |
| { |
| if (!cond) |
| fail(msg); |
| return cond; |
| } |
| |
| void ResultCollector::addResult (qpTestResult result, const char* msg) |
| { |
| m_log << TestLog::Message << "// Fail: " << msg << TestLog::EndMessage; |
| if (m_result == QP_TEST_RESULT_PASS) |
| { |
| m_result = result; |
| m_message = msg; |
| } |
| else |
| { |
| if (result == QP_TEST_RESULT_FAIL) |
| m_result = result; |
| m_message = "Multiple problems, see log for details"; |
| } |
| } |
| |
| void ResultCollector::fail (const char* msg) |
| { |
| addResult(QP_TEST_RESULT_FAIL, msg); |
| } |
| |
| void ResultCollector::warn (const char* msg) |
| { |
| addResult(QP_TEST_RESULT_QUALITY_WARNING, msg); |
| } |
| |
| ResultCollector::~ResultCollector (void) |
| { |
| m_testCtx.setTestResult(m_result, m_message); |
| } |
| |
| class TestBase : public TestCase, protected CallLogWrapper |
| { |
| protected: |
| TestBase (const char* name, |
| const char* description, |
| const Context& ctx); |
| |
| // Copy ContextWrapper since MI (except for CallLogWrapper) is a no-no. |
| const Context& getContext (void) const { return m_ctx; } |
| const RenderContext& getRenderContext (void) const { return m_ctx.getRenderContext(); } |
| const Functions& gl (void) const { return m_ctx.gl(); } |
| TestLog& log (void) const { return m_ctx.log(); } |
| void init (void); |
| |
| Context m_ctx; |
| Random m_rnd; |
| }; |
| |
| TestBase::TestBase (const char* name, const char* description, const Context& ctx) |
| : TestCase (ctx.getTestContext(), name, description) |
| , CallLogWrapper (ctx.gl(), ctx.log()) |
| , m_ctx (ctx) |
| , m_rnd (deStringHash(name)) |
| { |
| enableLogging(true); |
| } |
| |
| void TestBase::init (void) |
| { |
| m_rnd = Random(deStringHash(getName())); |
| } |
| |
| class LifeTest : public TestBase |
| { |
| public: |
| typedef void (LifeTest::*TestFunction) (void); |
| |
| LifeTest (const char* name, |
| const char* description, |
| Type& type, |
| TestFunction test) |
| : TestBase (name, description, type.getContext()) |
| , m_type (type) |
| , m_test (test) {} |
| |
| IterateResult iterate (void); |
| |
| void testGen (void); |
| void testDelete (void); |
| void testBind (void); |
| void testDeleteBound (void); |
| void testBindNoGen (void); |
| void testDeleteUsed (void); |
| |
| private: |
| Binder& binder (void) { return *m_type.binder(); } |
| |
| Type& m_type; |
| TestFunction m_test; |
| }; |
| |
| IterateResult LifeTest::iterate (void) |
| { |
| (this->*m_test)(); |
| return STOP; |
| } |
| |
| void LifeTest::testGen (void) |
| { |
| ResultCollector errors (getTestContext()); |
| Name name (m_type); |
| |
| if (m_type.genCreates()) |
| errors.check(m_type.exists(*name), "Gen* should have created an object, but didn't"); |
| else |
| errors.check(!m_type.exists(*name), "Gen* should not have created an object, but did"); |
| } |
| |
| void LifeTest::testDelete (void) |
| { |
| ResultCollector errors (getTestContext()); |
| GLuint name = m_type.gen(); |
| |
| m_type.release(name); |
| errors.check(!m_type.exists(name), "Object still exists after deletion"); |
| } |
| |
| void LifeTest::testBind (void) |
| { |
| ResultCollector errors (getTestContext()); |
| Name name (m_type); |
| |
| binder().bind(*name); |
| GLU_EXPECT_NO_ERROR(gl().getError(), "Bind failed"); |
| errors.check(m_type.exists(*name), "Object does not exist after binding"); |
| binder().bind(0); |
| } |
| |
| void LifeTest::testDeleteBound (void) |
| { |
| const GLuint id = m_type.gen(); |
| ResultCollector errors (getTestContext()); |
| |
| binder().bind(id); |
| m_type.release(id); |
| |
| if (m_type.nameLingers()) |
| { |
| errors.check(gl().getError() == GL_NO_ERROR, "Deleting bound object failed"); |
| errors.check(binder().getBinding() == id, |
| "Deleting bound object did not retain binding"); |
| errors.check(m_type.exists(id), |
| "Deleting bound object made its name invalid"); |
| errors.check(m_type.isDeleteFlagged(id), |
| "Deleting bound object did not flag the object for deletion"); |
| binder().bind(0); |
| } |
| else |
| { |
| errors.check(gl().getError() == GL_NO_ERROR, "Deleting bound object failed"); |
| errors.check(binder().getBinding() == 0, |
| "Deleting bound object did not remove binding"); |
| errors.check(!m_type.exists(id), |
| "Deleting bound object did not make its name invalid"); |
| binder().bind(0); |
| } |
| |
| errors.check(binder().getBinding() == 0, "Unbinding didn't remove binding"); |
| errors.check(!m_type.exists(id), "Name is still valid after deleting and unbinding"); |
| } |
| |
| void LifeTest::testBindNoGen (void) |
| { |
| ResultCollector errors (getTestContext()); |
| const GLuint id = m_rnd.getUint32(); |
| |
| if (!errors.check(!m_type.exists(id), "Randomly chosen identifier already exists")) |
| return; |
| |
| Name name (m_type, id); |
| binder().bind(*name); |
| |
| if (binder().genRequired()) |
| { |
| errors.check(glGetError() == GL_INVALID_OPERATION, |
| "Did not fail when binding a name not generated by Gen* call"); |
| errors.check(!m_type.exists(*name), |
| "Bind* created an object for a name not generated by a Gen* call"); |
| } |
| else |
| { |
| errors.check(glGetError() == GL_NO_ERROR, |
| "Failed when binding a name not generated by Gen* call"); |
| errors.check(m_type.exists(*name), |
| "Object was not created by the Bind* call"); |
| } |
| } |
| |
| void LifeTest::testDeleteUsed (void) |
| { |
| ResultCollector errors(getTestContext()); |
| GLuint programId = 0; |
| |
| { |
| CheckedShader vtxShader (getRenderContext(), |
| SHADERTYPE_VERTEX, s_vertexShaderSrc); |
| CheckedShader fragShader (getRenderContext(), |
| SHADERTYPE_FRAGMENT, s_fragmentShaderSrc); |
| CheckedProgram program (getRenderContext(), |
| vtxShader.getShader(), fragShader.getShader()); |
| |
| programId = program.getProgram(); |
| |
| log() << TestLog::Message << "// Created and linked program " << programId |
| << TestLog::EndMessage; |
| GLU_CHECK_CALL_ERROR(glUseProgram(programId), gl().getError()); |
| |
| log() << TestLog::Message << "// Deleted program " << programId |
| << TestLog::EndMessage; |
| } |
| TCU_CHECK(glIsProgram(programId)); |
| { |
| GLint deleteFlagged = 0; |
| glGetProgramiv(programId, GL_DELETE_STATUS, &deleteFlagged); |
| errors.check(deleteFlagged != 0, "Program object was not flagged as deleted"); |
| } |
| GLU_CHECK_CALL_ERROR(glUseProgram(0), gl().getError()); |
| errors.check(!gl().isProgram(programId), |
| "Deleted program name still valid after being made non-current"); |
| } |
| |
| class AttachmentTest : public TestBase |
| { |
| public: |
| typedef void (AttachmentTest::*TestFunction) (void); |
| AttachmentTest (const char* name, |
| const char* description, |
| Attacher& attacher, |
| TestFunction test) |
| : TestBase (name, description, attacher.getContext()) |
| , m_attacher (attacher) |
| , m_test (test) {} |
| IterateResult iterate (void); |
| |
| void testDeletedNames (void); |
| void testDeletedBinding (void); |
| void testDeletedReattach (void); |
| |
| private: |
| Attacher& m_attacher; |
| const TestFunction m_test; |
| }; |
| |
| IterateResult AttachmentTest::iterate (void) |
| { |
| (this->*m_test)(); |
| return STOP; |
| } |
| |
| GLuint getAttachment (Attacher& attacher, GLuint container) |
| { |
| const GLuint queriedAttachment = attacher.getAttachment(container); |
| attacher.log() << TestLog::Message |
| << "// Result of query for " << attacher.getElementType().getName() |
| << " attached to " << attacher.getContainerType().getName() << " " |
| << container << ": " << queriedAttachment << "." |
| << TestLog::EndMessage; |
| return queriedAttachment; |
| } |
| |
| void AttachmentTest::testDeletedNames (void) |
| { |
| Type& elemType = m_attacher.getElementType(); |
| Type& containerType = m_attacher.getContainerType(); |
| Name container (containerType); |
| ResultCollector errors (getTestContext()); |
| GLuint elementId = 0; |
| |
| { |
| Name element(elemType); |
| elementId = *element; |
| m_attacher.initAttachment(0, *element); |
| m_attacher.attach(*element, *container); |
| errors.check(getAttachment(m_attacher, *container) == elementId, |
| "Attachment name not returned by query even before deletion."); |
| } |
| |
| // "Such a container or other context may continue using the object, and |
| // may still contain state identifying its name as being currently bound" |
| // |
| // We here interpret "may" to mean that whenever the container has a |
| // deleted object attached to it, a query will return that object's former |
| // name. |
| errors.check(getAttachment(m_attacher, *container) == elementId, |
| "Attachment name not returned by query after attachment was deleted."); |
| |
| if (elemType.nameLingers()) |
| errors.check(elemType.exists(elementId), |
| "Attached object name no longer valid after deletion."); |
| else |
| errors.check(!elemType.exists(elementId), |
| "Attached object name still valid after deletion."); |
| |
| m_attacher.detach(elementId, *container); |
| errors.check(getAttachment(m_attacher, *container) == 0, |
| "Attachment name returned by query even after detachment."); |
| errors.check(!elemType.exists(elementId), |
| "Deleted attached object name still usable after detachment."); |
| }; |
| |
| class InputAttachmentTest : public TestBase |
| { |
| public: |
| InputAttachmentTest (const char* name, |
| const char* description, |
| InputAttacher& inputAttacher) |
| : TestBase (name, description, inputAttacher.getContext()) |
| , m_inputAttacher (inputAttacher) {} |
| |
| IterateResult iterate (void); |
| |
| private: |
| InputAttacher& m_inputAttacher; |
| }; |
| |
| GLuint replaceName (Type& type, GLuint oldName, TestLog& log) |
| { |
| const Binder* const binder = type.binder(); |
| const bool genRequired = binder == DE_NULL || binder->genRequired(); |
| |
| if (genRequired) |
| return type.gen(); |
| |
| log << TestLog::Message |
| << "// Type does not require Gen* for binding, reusing old id " << oldName << "." |
| << TestLog::EndMessage; |
| |
| return oldName; |
| } |
| |
| IterateResult InputAttachmentTest::iterate (void) |
| { |
| Attacher& attacher = m_inputAttacher.getAttacher(); |
| Type& containerType = attacher.getContainerType(); |
| Type& elementType = attacher.getElementType(); |
| Name container (containerType); |
| GLuint elementId = 0; |
| const GLuint refSeed = m_rnd.getUint32(); |
| const GLuint newSeed = m_rnd.getUint32(); |
| ResultCollector errors (getTestContext()); |
| |
| Surface refSurface; // Surface from drawing with refSeed-seeded attachment |
| Surface delSurface; // Surface from drawing with deleted refSeed attachment |
| Surface newSurface; // Surface from drawing with newSeed-seeded attachment |
| |
| log() << TestLog::Message |
| << "Testing if writing to a newly created object modifies a deleted attachment" |
| << TestLog::EndMessage; |
| |
| { |
| ScopedLogSection section (log(), |
| "Write to original", "Writing to an original attachment"); |
| const Name element (elementType); |
| |
| elementId = *element; |
| attacher.initAttachment(refSeed, elementId); |
| attacher.attach(elementId, *container); |
| m_inputAttacher.drawContainer(*container, refSurface); |
| // element gets deleted here |
| log() << TestLog::Message << "// Deleting attachment"; |
| } |
| { |
| ScopedLogSection section (log(), "Write to new", |
| "Writing to a new attachment after deleting the original"); |
| const GLuint newId = replaceName(elementType, elementId, log()); |
| const Name newElement (elementType, newId); |
| |
| attacher.initAttachment(newSeed, newId); |
| |
| m_inputAttacher.drawContainer(*container, delSurface); |
| attacher.detach(elementId, *container); |
| |
| attacher.attach(newId, *container); |
| m_inputAttacher.drawContainer(*container, newSurface); |
| attacher.detach(newId, *container); |
| } |
| { |
| const bool surfacesMatch = tcu::pixelThresholdCompare( |
| log(), "Reading from deleted", |
| "Comparison result from reading from a container with a deleted attachment " |
| "before and after writing to a fresh object.", |
| refSurface, delSurface, RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT); |
| |
| errors.check( |
| surfacesMatch, |
| "Writing to a fresh object modified the container with a deleted attachment."); |
| |
| if (!surfacesMatch) |
| log() << TestLog::Image("New attachment", |
| "Container state after attached to the fresh object", |
| newSurface); |
| } |
| |
| return STOP; |
| } |
| |
| class OutputAttachmentTest : public TestBase |
| { |
| public: |
| OutputAttachmentTest (const char* name, |
| const char* description, |
| OutputAttacher& outputAttacher) |
| : TestBase (name, description, |
| outputAttacher.getContext()) |
| , m_outputAttacher (outputAttacher) {} |
| IterateResult iterate (void); |
| |
| private: |
| OutputAttacher& m_outputAttacher; |
| }; |
| |
| IterateResult OutputAttachmentTest::iterate (void) |
| { |
| Attacher& attacher = m_outputAttacher.getAttacher(); |
| Type& containerType = attacher.getContainerType(); |
| Type& elementType = attacher.getElementType(); |
| Name container (containerType); |
| GLuint elementId = 0; |
| const GLuint refSeed = m_rnd.getUint32(); |
| const GLuint newSeed = m_rnd.getUint32(); |
| ResultCollector errors (getTestContext()); |
| Surface refSurface; // Surface drawn from attachment to refSeed container |
| Surface newSurface; // Surface drawn from attachment to newSeed container |
| Surface delSurface; // Like newSurface, after writing to a deleted attachment |
| |
| log() << TestLog::Message |
| << "Testing if writing to a container with a deleted attachment " |
| << "modifies a newly created object" |
| << TestLog::EndMessage; |
| |
| { |
| ScopedLogSection section (log(), "Write to existing", |
| "Writing to a container with an existing attachment"); |
| const Name element (elementType); |
| |
| elementId = *element; |
| attacher.initAttachment(0, elementId); |
| attacher.attach(elementId, *container); |
| |
| // For reference purposes, make note of what refSeed looks like. |
| m_outputAttacher.setupContainer(refSeed, *container); |
| m_outputAttacher.drawAttachment(elementId, refSurface); |
| } |
| { |
| ScopedLogSection section (log(), "Write to deleted", |
| "Writing to a container after deletion of attachment"); |
| const GLuint newId = replaceName(elementType, elementId, log()); |
| const Name newElement (elementType, newId); |
| |
| log() << TestLog::Message |
| << "Creating a new object " << newId |
| << TestLog::EndMessage; |
| |
| log() << TestLog::Message |
| << "Recording state of new object before writing to container" |
| << TestLog::EndMessage; |
| attacher.initAttachment(newSeed, newId); |
| m_outputAttacher.drawAttachment(newId, newSurface); |
| |
| log() << TestLog::Message |
| << "Writing to container" |
| << TestLog::EndMessage; |
| |
| // Now re-write refSeed to the container. |
| m_outputAttacher.setupContainer(refSeed, *container); |
| // Does it affect the newly created attachment object? |
| m_outputAttacher.drawAttachment(newId, delSurface); |
| } |
| attacher.detach(elementId, *container); |
| |
| const bool surfacesMatch = tcu::pixelThresholdCompare( |
| log(), "Writing to deleted", |
| "Comparison result from reading from a fresh object before and after " |
| "writing to a container with a deleted attachment", |
| newSurface, delSurface, RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT); |
| |
| errors.check(surfacesMatch, |
| "Writing to container with deleted attachment modified a new object."); |
| |
| if (!surfacesMatch) |
| log() << TestLog::Image( |
| "Original attachment", |
| "Result of container modification on original attachment before deletion.", |
| refSurface); |
| return STOP; |
| }; |
| |
| struct LifeTestSpec |
| { |
| const char* name; |
| LifeTest::TestFunction func; |
| bool needBind; |
| }; |
| |
| MovePtr<TestCaseGroup> createLifeTestGroup (TestContext& testCtx, |
| const LifeTestSpec& spec, |
| const vector<Type*>& types) |
| { |
| MovePtr<TestCaseGroup> group(new TestCaseGroup(testCtx, spec.name, spec.name)); |
| |
| for (vector<Type*>::const_iterator it = types.begin(); it != types.end(); ++it) |
| { |
| Type& type = **it; |
| const char* name = type.getName(); |
| if (!spec.needBind || type.binder() != DE_NULL) |
| group->addChild(new LifeTest(name, name, type, spec.func)); |
| } |
| |
| return group; |
| } |
| |
| static const LifeTestSpec s_lifeTests[] = |
| { |
| { "gen", &LifeTest::testGen, false }, |
| { "delete", &LifeTest::testDelete, false }, |
| { "bind", &LifeTest::testBind, true }, |
| { "delete_bound", &LifeTest::testDeleteBound, true }, |
| { "bind_no_gen", &LifeTest::testBindNoGen, true }, |
| }; |
| |
| string attacherName (Attacher& attacher) |
| { |
| ostringstream os; |
| os << attacher.getElementType().getName() << "_" << attacher.getContainerType().getName(); |
| return os.str(); |
| } |
| |
| void addTestCases (TestCaseGroup& group, Types& types) |
| { |
| TestContext& testCtx = types.getTestContext(); |
| |
| for (const LifeTestSpec* it = DE_ARRAY_BEGIN(s_lifeTests); |
| it != DE_ARRAY_END(s_lifeTests); ++it) |
| group.addChild(createLifeTestGroup(testCtx, *it, types.getTypes()).release()); |
| |
| { |
| TestCaseGroup* const delUsedGroup = |
| new TestCaseGroup(testCtx, "delete_used", "Delete current program"); |
| group.addChild(delUsedGroup); |
| |
| delUsedGroup->addChild( |
| new LifeTest("program", "program", types.getProgramType(), |
| &LifeTest::testDeleteUsed)); |
| } |
| |
| { |
| TestCaseGroup* const attGroup = new TestCaseGroup( |
| testCtx, "attach", "Attachment tests"); |
| group.addChild(attGroup); |
| |
| { |
| TestCaseGroup* const nameGroup = new TestCaseGroup( |
| testCtx, "deleted_name", "Name of deleted attachment"); |
| attGroup->addChild(nameGroup); |
| |
| const vector<Attacher*>& atts = types.getAttachers(); |
| for (vector<Attacher*>::const_iterator it = atts.begin(); it != atts.end(); ++it) |
| { |
| const string name = attacherName(**it); |
| nameGroup->addChild(new AttachmentTest(name.c_str(), name.c_str(), **it, |
| &AttachmentTest::testDeletedNames)); |
| } |
| } |
| { |
| TestCaseGroup* inputGroup = new TestCaseGroup( |
| testCtx, "deleted_input", "Input from deleted attachment"); |
| attGroup->addChild(inputGroup); |
| |
| const vector<InputAttacher*>& inAtts = types.getInputAttachers(); |
| for (vector<InputAttacher*>::const_iterator it = inAtts.begin(); |
| it != inAtts.end(); ++it) |
| { |
| const string name = attacherName((*it)->getAttacher()); |
| inputGroup->addChild(new InputAttachmentTest(name.c_str(), name.c_str(), **it)); |
| } |
| } |
| { |
| TestCaseGroup* outputGroup = new TestCaseGroup( |
| testCtx, "deleted_output", "Output to deleted attachment"); |
| attGroup->addChild(outputGroup); |
| |
| const vector<OutputAttacher*>& outAtts = types.getOutputAttachers(); |
| for (vector<OutputAttacher*>::const_iterator it = outAtts.begin(); |
| it != outAtts.end(); ++it) |
| { |
| string name = attacherName((*it)->getAttacher()); |
| outputGroup->addChild(new OutputAttachmentTest(name.c_str(), name.c_str(), |
| **it)); |
| } |
| } |
| } |
| } |
| |
| } // details |
| } // LifetimeTests |
| } // gls |
| } // deqp |