/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES 3.0 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 Object lifetime tests. *//*--------------------------------------------------------------------*/ #include "es3fLifetimeTests.hpp" #include "deRandom.hpp" #include "deUniquePtr.hpp" #include "tcuRenderTarget.hpp" #include "tcuSurface.hpp" #include "gluDrawUtil.hpp" #include "gluObjectWrapper.hpp" #include "gluPixelTransfer.hpp" #include "gluShaderProgram.hpp" #include "glsLifetimeTests.hpp" #include "glwEnums.hpp" #include "glwFunctions.hpp" #include namespace deqp { namespace gles3 { namespace Functional { namespace { using de::MovePtr; using de::Random; using glu::Buffer; using glu::CallLogWrapper; using glu::ProgramSources; using glu::RenderContext; using glu::VertexArray; using std::vector; using tcu::RenderTarget; using tcu::Surface; using tcu::TestContext; using tcu::TestLog; namespace lt = gls::LifetimeTests; using namespace lt; using namespace glw; typedef TestCase::IterateResult IterateResult; enum { VIEWPORT_SIZE = 128 }; class ScaleProgram : public glu::ShaderProgram { public: ScaleProgram(lt::Context &ctx); void draw(GLuint vao, GLfloat scale, bool tf, Surface *dst); void setPos(GLuint buffer, GLuint vao); private: ProgramSources getSources(void); const RenderContext &m_renderCtx; GLint m_scaleLoc; GLint m_posLoc; }; enum { NUM_COMPONENTS = 4, NUM_VERTICES = 3 }; ScaleProgram::ScaleProgram(lt::Context &ctx) : glu::ShaderProgram(ctx.getRenderContext(), getSources()) , m_renderCtx(ctx.getRenderContext()) { const Functions &gl = m_renderCtx.getFunctions(); TCU_CHECK(isOk()); m_scaleLoc = gl.getUniformLocation(getProgram(), "scale"); m_posLoc = gl.getAttribLocation(getProgram(), "pos"); } #define GLSL(VERSION, BODY) ("#version " #VERSION "\n" #BODY "\n") static const char *const s_vertexShaderSrc = GLSL( 100, attribute vec4 pos; uniform float scale; void main() { gl_Position = vec4(scale * pos.xy, pos.zw); }); static const char *const s_fragmentShaderSrc = GLSL( 100, void main() { gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0); }); ProgramSources ScaleProgram::getSources(void) { using namespace glu; ProgramSources sources; sources << VertexSource(s_vertexShaderSrc) << FragmentSource(s_fragmentShaderSrc) << TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS) << TransformFeedbackVarying("gl_Position"); return sources; } void ScaleProgram::draw(GLuint vao, GLfloat scale, bool tf, Surface *dst) { const Functions &gl = m_renderCtx.getFunctions(); de::Random rnd(vao); Rectangle viewport = randomViewport(m_renderCtx, VIEWPORT_SIZE, VIEWPORT_SIZE, rnd); setViewport(m_renderCtx, viewport); gl.clearColor(0, 0, 0, 1); gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); gl.bindVertexArray(vao); gl.enableVertexAttribArray(m_posLoc); GLU_CHECK_CALL_ERROR(gl.useProgram(getProgram()), gl.getError()); gl.uniform1f(m_scaleLoc, scale); if (tf) { gl.beginTransformFeedback(GL_TRIANGLES); if (gl.getError() == GL_INVALID_OPERATION) return; } GLU_CHECK_CALL_ERROR(gl.drawArrays(GL_TRIANGLES, 0, 3), gl.getError()); if (tf) gl.endTransformFeedback(); if (dst != DE_NULL) readRectangle(m_renderCtx, viewport, *dst); gl.bindVertexArray(0); } void ScaleProgram::setPos(GLuint buffer, GLuint vao) { const Functions &gl = m_renderCtx.getFunctions(); gl.bindBuffer(GL_ARRAY_BUFFER, buffer); gl.bindVertexArray(vao); GLU_CHECK_CALL_ERROR(gl.vertexAttribPointer(m_posLoc, NUM_COMPONENTS, GL_FLOAT, false, 0, DE_NULL), gl.getError()); gl.bindVertexArray(0); gl.bindBuffer(GL_ARRAY_BUFFER, 0); GLU_CHECK_ERROR(gl.getError()); } class VertexArrayBinder : public SimpleBinder { public: VertexArrayBinder(lt::Context &ctx) : SimpleBinder(ctx, 0, GL_NONE, GL_VERTEX_ARRAY_BINDING, true) { } void bind(GLuint name) { glBindVertexArray(name); } }; class SamplerBinder : public Binder { public: SamplerBinder(lt::Context &ctx) : Binder(ctx) { } void bind(GLuint name) { glBindSampler(0, name); } GLuint getBinding(void) { GLint arr[32] = {}; glGetIntegerv(GL_SAMPLER_BINDING, arr); log() << TestLog::Message << "// First output integer: " << arr[0] << TestLog::EndMessage; return arr[0]; } bool genRequired(void) const { return true; } }; class QueryBinder : public Binder { public: QueryBinder(lt::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; } }; class BufferVAOAttacher : public Attacher { public: BufferVAOAttacher(lt::Context &ctx, Type &elementType, Type &varrType, ScaleProgram &program) : Attacher(ctx, elementType, varrType) , m_program(program) { } void initAttachment(GLuint seed, GLuint element); void attach(GLuint element, GLuint container); void detach(GLuint element, GLuint container); bool canAttachDeleted(void) const { return false; } ScaleProgram &getProgram(void) { return m_program; } GLuint getAttachment(GLuint container); private: ScaleProgram &m_program; }; static const GLfloat s_varrData[NUM_VERTICES * NUM_COMPONENTS] = {-1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0}; void initBuffer(const Functions &gl, GLuint seed, GLenum usage, GLuint buffer) { gl.bindBuffer(GL_ARRAY_BUFFER, buffer); if (seed == 0) gl.bufferData(GL_ARRAY_BUFFER, sizeof(s_varrData), s_varrData, usage); else { Random rnd(seed); GLfloat data[DE_LENGTH_OF_ARRAY(s_varrData)]; for (int ndx = 0; ndx < NUM_VERTICES; ndx++) { GLfloat *vertex = &data[ndx * NUM_COMPONENTS]; vertex[0] = 2.0f * (rnd.getFloat() - 0.5f); vertex[1] = 2.0f * (rnd.getFloat() - 0.5f); DE_STATIC_ASSERT(NUM_COMPONENTS == 4); vertex[2] = 0.0f; vertex[3] = 1.0f; } gl.bufferData(GL_ARRAY_BUFFER, sizeof(data), data, usage); } gl.bindBuffer(GL_ARRAY_BUFFER, 0); GLU_CHECK_ERROR(gl.getError()); } void BufferVAOAttacher::initAttachment(GLuint seed, GLuint buffer) { initBuffer(gl(), seed, GL_STATIC_DRAW, buffer); log() << TestLog::Message << "// Initialized buffer " << buffer << " from seed " << seed << TestLog::EndMessage; } void BufferVAOAttacher::attach(GLuint buffer, GLuint vao) { m_program.setPos(buffer, vao); log() << TestLog::Message << "// Set the `pos` attribute in VAO " << vao << " to buffer " << buffer << TestLog::EndMessage; } void BufferVAOAttacher::detach(GLuint buffer, GLuint varr) { DE_UNREF(buffer); attach(0, varr); } GLuint BufferVAOAttacher::getAttachment(GLuint varr) { GLint name = 0; gl().bindVertexArray(varr); gl().getVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &name); gl().bindVertexArray(0); GLU_CHECK_ERROR(gl().getError()); return GLuint(name); } class BufferVAOInputAttacher : public InputAttacher { public: BufferVAOInputAttacher(BufferVAOAttacher &attacher) : InputAttacher(attacher), m_program(attacher.getProgram()) { } void drawContainer(GLuint container, Surface &dst); private: ScaleProgram &m_program; }; void BufferVAOInputAttacher::drawContainer(GLuint vao, Surface &dst) { m_program.draw(vao, 1.0, false, &dst); log() << TestLog::Message << "// Drew an output image with VAO " << vao << TestLog::EndMessage; } class BufferTfAttacher : public Attacher { public: BufferTfAttacher(lt::Context &ctx, Type &bufferType, Type &tfType) : Attacher(ctx, bufferType, tfType) { } void initAttachment(GLuint seed, GLuint element); void attach(GLuint buffer, GLuint tf); void detach(GLuint buffer, GLuint tf); bool canAttachDeleted(void) const { return false; } GLuint getAttachment(GLuint tf); }; void BufferTfAttacher::initAttachment(GLuint seed, GLuint buffer) { initBuffer(gl(), seed, GL_DYNAMIC_READ, buffer); log() << TestLog::Message << "// Initialized buffer " << buffer << " from seed " << seed << TestLog::EndMessage; } void BufferTfAttacher::attach(GLuint buffer, GLuint tf) { glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffer); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); GLU_CHECK_ERROR(gl().getError()); } void BufferTfAttacher::detach(GLuint buffer, GLuint tf) { DE_UNREF(buffer); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); GLU_CHECK_ERROR(gl().getError()); } GLuint BufferTfAttacher::getAttachment(GLuint tf) { GLint ret = 0; gl().bindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf); gl().getIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, &ret); gl().bindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); GLU_CHECK_ERROR(gl().getError()); return GLuint(ret); } class BufferTfOutputAttacher : public OutputAttacher { public: BufferTfOutputAttacher(BufferTfAttacher &attacher, ScaleProgram &program) : OutputAttacher(attacher) , m_program(program) { } void setupContainer(GLuint seed, GLuint container); void drawAttachment(GLuint attachment, Surface &dst); private: ScaleProgram &m_program; }; void BufferTfOutputAttacher::drawAttachment(GLuint buffer, Surface &dst) { VertexArray vao(getRenderContext()); m_program.setPos(buffer, *vao); m_program.draw(*vao, 1.0, false, &dst); log() << TestLog::Message << "// Drew output image with vertices from buffer " << buffer << TestLog::EndMessage; GLU_CHECK_ERROR(gl().getError()); } void BufferTfOutputAttacher::setupContainer(GLuint seed, GLuint tf) { Buffer posBuf(getRenderContext()); VertexArray vao(getRenderContext()); initBuffer(gl(), seed, GL_STATIC_DRAW, *posBuf); m_program.setPos(*posBuf, *vao); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf); m_program.draw(*vao, -1.0, true, DE_NULL); log() << TestLog::Message << "// Drew an image with seed " << seed << " with transform feedback to " << tf << TestLog::EndMessage; glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); GLU_CHECK_ERROR(gl().getError()); } class ES3Types : public ES2Types { public: ES3Types(lt::Context &ctx); private: ScaleProgram m_program; QueryBinder m_queryBind; SimpleType m_queryType; SimpleBinder m_tfBind; SimpleType m_tfType; VertexArrayBinder m_varrBind; SimpleType m_varrType; SamplerBinder m_samplerBind; SimpleType m_samplerType; BufferVAOAttacher m_bufVarrAtt; BufferVAOInputAttacher m_bufVarrInAtt; BufferTfAttacher m_bufTfAtt; BufferTfOutputAttacher m_bufTfOutAtt; }; ES3Types::ES3Types(lt::Context &ctx) : ES2Types(ctx) , m_program(ctx) , m_queryBind(ctx) , m_queryType(ctx, "query", &CallLogWrapper::glGenQueries, &CallLogWrapper::glDeleteQueries, &CallLogWrapper::glIsQuery, &m_queryBind) , m_tfBind(ctx, &CallLogWrapper::glBindTransformFeedback, GL_TRANSFORM_FEEDBACK, GL_TRANSFORM_FEEDBACK_BINDING, true) , m_tfType(ctx, "transform_feedback", &CallLogWrapper::glGenTransformFeedbacks, &CallLogWrapper::glDeleteTransformFeedbacks, &CallLogWrapper::glIsTransformFeedback, &m_tfBind) , m_varrBind(ctx) , m_varrType(ctx, "vertex_array", &CallLogWrapper::glGenVertexArrays, &CallLogWrapper::glDeleteVertexArrays, &CallLogWrapper::glIsVertexArray, &m_varrBind) , m_samplerBind(ctx) , m_samplerType(ctx, "sampler", &CallLogWrapper::glGenSamplers, &CallLogWrapper::glDeleteSamplers, &CallLogWrapper::glIsSampler, &m_samplerBind, true) , m_bufVarrAtt(ctx, m_bufferType, m_varrType, m_program) , m_bufVarrInAtt(m_bufVarrAtt) , m_bufTfAtt(ctx, m_bufferType, m_tfType) , m_bufTfOutAtt(m_bufTfAtt, m_program) { Type *types[] = {&m_queryType, &m_tfType, &m_varrType, &m_samplerType}; m_types.insert(m_types.end(), DE_ARRAY_BEGIN(types), DE_ARRAY_END(types)); m_attachers.push_back(&m_bufVarrAtt); m_attachers.push_back(&m_bufTfAtt); m_inAttachers.push_back(&m_bufVarrInAtt); m_outAttachers.push_back(&m_bufTfOutAtt); } class TfDeleteActiveTest : public TestCase, private CallLogWrapper { public: TfDeleteActiveTest(gles3::Context &context, const char *name, const char *description); IterateResult iterate(void); }; TfDeleteActiveTest::TfDeleteActiveTest(gles3::Context &context, const char *name, const char *description) : TestCase(context, name, description) , CallLogWrapper(context.getRenderContext().getFunctions(), context.getTestContext().getLog()) { enableLogging(true); } class ScopedTransformFeedbackFeedback { public: ScopedTransformFeedbackFeedback(glu::CallLogWrapper &gl, GLenum type); ~ScopedTransformFeedbackFeedback(void); private: glu::CallLogWrapper &m_gl; }; ScopedTransformFeedbackFeedback::ScopedTransformFeedbackFeedback(glu::CallLogWrapper &gl, GLenum type) : m_gl(gl) { m_gl.glBeginTransformFeedback(type); GLU_EXPECT_NO_ERROR(m_gl.glGetError(), "glBeginTransformFeedback"); } ScopedTransformFeedbackFeedback::~ScopedTransformFeedbackFeedback(void) { m_gl.glEndTransformFeedback(); } IterateResult TfDeleteActiveTest::iterate(void) { static const char *const s_xfbVertexSource = "#version 300 es\n" "void main ()\n" "{\n" " gl_Position = vec4(float(gl_VertexID) / 2.0, float(gl_VertexID % 2) / 2.0, 0.0, 1.0);\n" "}\n"; static const char *const s_xfbFragmentSource = "#version 300 es\n" "layout(location=0) out mediump vec4 dEQP_FragColor;\n" "void main ()\n" "{\n" " dEQP_FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n" "}\n"; glu::Buffer buf(m_context.getRenderContext()); GLuint tf = 0; glu::ShaderProgram program(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_xfbVertexSource) << glu::FragmentSource(s_xfbFragmentSource) << glu::TransformFeedbackMode(GL_INTERLEAVED_ATTRIBS) << glu::TransformFeedbackVarying("gl_Position")); if (!program.isOk()) { m_testCtx.getLog() << program; throw tcu::TestError("failed to build program"); } try { GLU_CHECK_CALL(glUseProgram(program.getProgram())); GLU_CHECK_CALL(glGenTransformFeedbacks(1, &tf)); GLU_CHECK_CALL(glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tf)); GLU_CHECK_CALL(glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, *buf)); GLU_CHECK_CALL( glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 3 * sizeof(glw::GLfloat[4]), DE_NULL, GL_DYNAMIC_COPY)); { ScopedTransformFeedbackFeedback xfb(static_cast(*this), GL_TRIANGLES); glDeleteTransformFeedbacks(1, &tf); { GLenum err = glGetError(); if (err != GL_INVALID_OPERATION) getTestContext().setTestResult( QP_TEST_RESULT_FAIL, "Deleting active transform feedback did not produce GL_INVALID_OPERATION"); else getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Pass"); } } GLU_CHECK(); // ScopedTransformFeedbackFeedback::dtor might modify error state GLU_CHECK_CALL(glDeleteTransformFeedbacks(1, &tf)); } catch (const glu::Error &) { glDeleteTransformFeedbacks(1, &tf); throw; } return STOP; } class TestGroup : public TestCaseGroup { public: TestGroup(gles3::Context &context) : TestCaseGroup(context, "lifetime", "Object lifetime tests") { } void init(void); private: MovePtr m_types; }; void TestGroup::init(void) { gles3::Context &ctx = getContext(); lt::Context ltCtx(ctx.getRenderContext(), ctx.getTestContext()); m_types = MovePtr(new ES3Types(ltCtx)); addTestCases(*this, *m_types); TestCaseGroup *deleteActiveGroup = new TestCaseGroup(ctx, "delete_active", "Delete active object"); addChild(deleteActiveGroup); deleteActiveGroup->addChild(new TfDeleteActiveTest(ctx, "transform_feedback", "Transform Feedback")); } } // namespace TestCaseGroup *createLifetimeTests(Context &context) { return new TestGroup(context); } } // namespace Functional } // namespace gles3 } // namespace deqp