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.
1413 lines
40 KiB
1413 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
|
|
|