/*------------------------------------------------------------------------- * 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 <vector> namespace deqp { namespace gles3 { namespace Functional { namespace { using std::vector; using de::MovePtr; using de::Random; using tcu::RenderTarget; using tcu::Surface; using tcu::TestContext; using tcu::TestLog; using glu::CallLogWrapper; using glu::RenderContext; using glu::ProgramSources; using glu::VertexArray; using glu::Buffer; 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); 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<glu::CallLogWrapper&>(*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<Types> m_types; }; void TestGroup::init (void) { gles3::Context& ctx = getContext(); lt::Context ltCtx (ctx.getRenderContext(), ctx.getTestContext()); m_types = MovePtr<Types>(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")); } } // anonymous TestCaseGroup* createLifetimeTests (Context& context) { return new TestGroup(context); } } // Functional } // gles3 } // deqp