/*------------------------------------------------------------------------- * 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 "gluTextureUtil.hpp" #include "gluStrUtil.hpp" #include "glwFunctions.hpp" #include #include #include #include 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); } static bool isTextureFormatColorRenderable (const glu::RenderContext& renderCtx, const glu::TransferFormat& format) { const glw::Functions& gl = renderCtx.getFunctions(); deUint32 curFbo = ~0u; deUint32 curTex = ~0u; deUint32 testFbo = 0u; deUint32 testTex = 0u; GLenum status = GL_NONE; GLU_CHECK_GLW_CALL(gl, getIntegerv(GL_FRAMEBUFFER_BINDING, (deInt32*)&curFbo)); GLU_CHECK_GLW_CALL(gl, getIntegerv(GL_TEXTURE_BINDING_2D, (deInt32*)&curTex)); try { GLU_CHECK_GLW_CALL(gl, genTextures(1, &testTex)); GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D, testTex)); GLU_CHECK_GLW_CALL(gl, texImage2D(GL_TEXTURE_2D, 0, format.format, FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE, 0, format.format, format.dataType, DE_NULL)); GLU_CHECK_GLW_CALL(gl, genFramebuffers(1, &testFbo)); GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, testFbo)); GLU_CHECK_GLW_CALL(gl, framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, testTex, 0)); status = gl.checkFramebufferStatus(GL_FRAMEBUFFER); GLU_CHECK_GLW_MSG(gl, "glCheckFramebufferStatus(GL_FRAMEBUFFER)"); GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D, curTex)); GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, curFbo)); GLU_CHECK_GLW_CALL(gl, deleteTextures(1, &testTex)); GLU_CHECK_GLW_CALL(gl, deleteFramebuffers(1, &testFbo)); } catch (...) { if (testTex != 0) gl.deleteTextures(1, &testTex); if (testFbo != 0) gl.deleteFramebuffers(1, &testFbo); throw; } if (status == GL_FRAMEBUFFER_COMPLETE) return true; else if (status == GL_FRAMEBUFFER_UNSUPPORTED) return false; else TCU_THROW(TestError, (std::string("glCheckFramebufferStatus() returned invalid result code ") + de::toString(glu::getFramebufferStatusStr(status))).c_str()); } static glu::TransferFormat getRenderableColorTextureFormat (const glu::RenderContext& renderCtx) { if (glu::contextSupports(renderCtx.getType(), glu::ApiType::es(3,0))) return glu::TransferFormat(GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4); { const glu::TransferFormat candidates[] = { glu::TransferFormat(GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4), glu::TransferFormat(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1), glu::TransferFormat(GL_RGB, GL_UNSIGNED_SHORT_5_6_5), glu::TransferFormat(GL_RGBA, GL_UNSIGNED_BYTE), }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(candidates); ++ndx) { if (isTextureFormatColorRenderable(renderCtx, candidates[ndx])) return candidates[ndx]; } } return glu::TransferFormat(GL_NONE, GL_NONE); } void TextureFboAttacher::initStorage (void) { const glu::TransferFormat format = getRenderableColorTextureFormat(getRenderContext()); if (format.format == GL_NONE) TCU_THROW(NotSupportedError, "No renderable texture format found"); GLU_CHECK_CALL_ERROR( glTexImage2D(GL_TEXTURE_2D, 0, format.format, FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE, 0, format.format, format.dataType, DE_NULL), gl().getError()); } static bool isRenderbufferFormatColorRenderable (const glu::RenderContext& renderCtx, const deUint32 format) { const glw::Functions& gl = renderCtx.getFunctions(); deUint32 curFbo = ~0u; deUint32 curRbo = ~0u; deUint32 testFbo = 0u; deUint32 testRbo = 0u; GLenum status = GL_NONE; GLU_CHECK_GLW_CALL(gl, getIntegerv(GL_FRAMEBUFFER_BINDING, (deInt32*)&curFbo)); GLU_CHECK_GLW_CALL(gl, getIntegerv(GL_RENDERBUFFER_BINDING, (deInt32*)&curRbo)); try { GLU_CHECK_GLW_CALL(gl, genRenderbuffers(1, &testRbo)); GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, testRbo)); GLU_CHECK_GLW_CALL(gl, renderbufferStorage(GL_RENDERBUFFER, format, FRAMEBUFFER_SIZE, FRAMEBUFFER_SIZE)); GLU_CHECK_GLW_CALL(gl, genFramebuffers(1, &testFbo)); GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, testFbo)); GLU_CHECK_GLW_CALL(gl, framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, testRbo)); status = gl.checkFramebufferStatus(GL_FRAMEBUFFER); GLU_CHECK_GLW_MSG(gl, "glCheckFramebufferStatus(GL_FRAMEBUFFER)"); GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, curRbo)); GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, curFbo)); GLU_CHECK_GLW_CALL(gl, deleteRenderbuffers(1, &testRbo)); GLU_CHECK_GLW_CALL(gl, deleteFramebuffers(1, &testFbo)); } catch (...) { if (testRbo != 0) gl.deleteRenderbuffers(1, &testRbo); if (testFbo != 0) gl.deleteFramebuffers(1, &testFbo); throw; } if (status == GL_FRAMEBUFFER_COMPLETE) return true; else if (status == GL_FRAMEBUFFER_UNSUPPORTED) return false; else TCU_THROW(TestError, (std::string("glCheckFramebufferStatus() returned invalid result code ") + de::toString(glu::getFramebufferStatusStr(status))).c_str()); } static deUint32 getRenderableColorRenderbufferFormat (const glu::RenderContext& renderCtx) { if (glu::contextSupports(renderCtx.getType(), glu::ApiType::es(3,0))) return GL_RGBA4; { const deUint32 candidates[] = { GL_RGBA4, GL_RGB5_A1, GL_RGB565, }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(candidates); ++ndx) { if (isRenderbufferFormatColorRenderable(renderCtx, candidates[ndx])) return candidates[ndx]; } } return GL_NONE; } void RboFboAttacher::initStorage (void) { const deUint32 format = getRenderableColorRenderbufferFormat(getRenderContext()); if (format == GL_NONE) TCU_THROW(TestError, "No color-renderable renderbuffer format found"); GLU_CHECK_CALL_ERROR( glRenderbufferStorage(GL_RENDERBUFFER, format, 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 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(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 createLifeTestGroup (TestContext& testCtx, const LifeTestSpec& spec, const vector& types) { MovePtr group(new TestCaseGroup(testCtx, spec.name, spec.name)); for (vector::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& atts = types.getAttachers(); for (vector::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& inAtts = types.getInputAttachers(); for (vector::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& outAtts = types.getOutputAttachers(); for (vector::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