/*-------------------------------------------------------------------------
* 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