You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1414 lines
40 KiB
1414 lines
40 KiB
/*-------------------------------------------------------------------------
|
|
* 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 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<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, 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<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 == 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<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() != 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<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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // details
|
|
} // LifetimeTests
|
|
} // gls
|
|
} // deqp
|