blob: 1a48d4749103d14051470e80beb1d086a32d5d66 [file] [log] [blame]
/*-------------------------------------------------------------------------
* 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 <vector>
#include <map>
#include <algorithm>
#include <sstream>
namespace deqp
{
namespace gls
{
namespace LifetimeTests
{
namespace details
{
using de::Random;
using std::map;
using std::ostringstream;
using std::string;
using tcu::RenderTarget;
using tcu::RGBA;
using tcu::StringTemplate;
using tcu::TestCase;
typedef TestCase::IterateResult IterateResult;
using glu::Framebuffer;
using glu::Program;
using glu::Shader;
using glu::SHADERTYPE_FRAGMENT;
using glu::SHADERTYPE_VERTEX;
using tcu::ScopedLogSection;
using tcu::TestLog;
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, nullptr);
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();
uint32_t curFbo = ~0u;
uint32_t curTex = ~0u;
uint32_t testFbo = 0u;
uint32_t testTex = 0u;
GLenum status = GL_NONE;
GLU_CHECK_GLW_CALL(gl, getIntegerv(GL_FRAMEBUFFER_BINDING, (int32_t *)&curFbo));
GLU_CHECK_GLW_CALL(gl, getIntegerv(GL_TEXTURE_BINDING_2D, (int32_t *)&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, nullptr));
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, nullptr),
gl().getError());
}
static bool isRenderbufferFormatColorRenderable(const glu::RenderContext &renderCtx, const uint32_t format)
{
const glw::Functions &gl = renderCtx.getFunctions();
uint32_t curFbo = ~0u;
uint32_t curRbo = ~0u;
uint32_t testFbo = 0u;
uint32_t testRbo = 0u;
GLenum status = GL_NONE;
GLU_CHECK_GLW_CALL(gl, getIntegerv(GL_FRAMEBUFFER_BINDING, (int32_t *)&curFbo));
GLU_CHECK_GLW_CALL(gl, getIntegerv(GL_RENDERBUFFER_BINDING, (int32_t *)&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 uint32_t getRenderableColorRenderbufferFormat(const glu::RenderContext &renderCtx)
{
if (glu::contextSupports(renderCtx.getType(), glu::ApiType::es(3, 0)))
return GL_RGBA4;
{
const uint32_t 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 uint32_t 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::floatToString;
using de::insert;
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, nullptr), 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 == nullptr || 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() != nullptr)
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));
}
}
}
}
} // namespace details
} // namespace LifetimeTests
} // namespace gls
} // namespace deqp