Add tests for EGL_EXT_buffer_age

Add basic functionality tests for EGL_EXT_buffer_age. Rendering using
single drawtype as well as combinations of two drawtypes is tested.
Introduce a new FrameSequence class to avoid the situation where edges
of different rectangles are too close to each other.

Change-Id: Id77b50ae962c9580a27d90807bcea4b5f785fadf
diff --git a/Android.mk b/Android.mk
index 9c27830..ef3ee8a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -245,6 +245,7 @@
 	framework/referencerenderer/rrVertexPacket.cpp \
 	modules/egl/teglAndroidUtil.cpp \
 	modules/egl/teglApiCase.cpp \
+	modules/egl/teglBufferAgeTests.cpp \
 	modules/egl/teglChooseConfigReference.cpp \
 	modules/egl/teglChooseConfigTests.cpp \
 	modules/egl/teglClientExtensionTests.cpp \
diff --git a/modules/egl/CMakeLists.txt b/modules/egl/CMakeLists.txt
index 37a06fd..070487f 100644
--- a/modules/egl/CMakeLists.txt
+++ b/modules/egl/CMakeLists.txt
@@ -5,6 +5,8 @@
 	teglAndroidUtil.hpp
 	teglApiCase.cpp
 	teglApiCase.hpp
+	teglBufferAgeTests.hpp
+	teglBufferAgeTests.cpp
 	teglChooseConfigReference.cpp
 	teglChooseConfigReference.hpp
 	teglChooseConfigTests.cpp
diff --git a/modules/egl/teglBufferAgeTests.cpp b/modules/egl/teglBufferAgeTests.cpp
new file mode 100644
index 0000000..57feb50
--- /dev/null
+++ b/modules/egl/teglBufferAgeTests.cpp
@@ -0,0 +1,692 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2015 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 Test EXT_buffer_age
+ *//*--------------------------------------------------------------------*/
+
+#include "teglBufferAgeTests.hpp"
+
+#include "tcuImageCompare.hpp"
+#include "tcuTestLog.hpp"
+#include "tcuSurface.hpp"
+#include "tcuTextureUtil.hpp"
+
+#include "egluNativeWindow.hpp"
+#include "egluUtil.hpp"
+#include "egluConfigFilter.hpp"
+
+#include "eglwLibrary.hpp"
+#include "eglwEnums.hpp"
+
+#include "gluDefs.hpp"
+#include "gluRenderContext.hpp"
+#include "gluShaderProgram.hpp"
+
+#include "glwDefs.hpp"
+#include "glwEnums.hpp"
+#include "glwFunctions.hpp"
+
+#include "deRandom.hpp"
+#include "deString.h"
+
+#include <string>
+#include <vector>
+#include <sstream>
+
+using std::string;
+using std::vector;
+using glw::GLubyte;
+using tcu::IVec2;
+
+using namespace eglw;
+
+namespace deqp
+{
+namespace egl
+{
+namespace
+{
+
+typedef	tcu::Vector<GLubyte, 3> Color;
+
+class GLES2Renderer;
+
+class ReferenceRenderer;
+
+class BufferAgeTest : public TestCase
+{
+public:
+	enum DrawType
+	{
+		DRAWTYPE_GLES2_CLEAR,
+		DRAWTYPE_GLES2_RENDER
+	};
+
+								BufferAgeTest	(EglTestContext& eglTestCtx, bool preserveColorBuffer, const vector<DrawType>& oddFrameDrawType,
+												 const vector<DrawType>& evenFrameDrawType, const char* name, const char* description);
+								~BufferAgeTest	(void);
+
+	void						init			(void);
+	void						deinit			(void);
+	IterateResult				iterate			(void);
+
+private:
+	void						initEGLSurface (EGLConfig config);
+	void						initEGLContext (EGLConfig config);
+
+	const int					m_seed;
+	const bool					m_preserveColorBuffer;
+	const vector<DrawType>		m_oddFrameDrawType;
+	const vector<DrawType>		m_evenFrameDrawType;
+
+	EGLDisplay					m_eglDisplay;
+	eglu::NativeWindow*			m_window;
+	EGLSurface					m_eglSurface;
+	EGLConfig					m_eglConfig;
+	EGLContext					m_eglContext;
+	glw::Functions				m_gl;
+
+	GLES2Renderer*				m_gles2Renderer;
+	ReferenceRenderer*			m_refRenderer;
+
+};
+
+struct ColoredRect
+{
+public:
+							ColoredRect (const IVec2& bottomLeft_, const IVec2& topRight_, const Color& color_);
+	IVec2	 				bottomLeft;
+	IVec2 					topRight;
+	Color 					color;
+};
+
+ColoredRect::ColoredRect (const IVec2& bottomLeft_, const IVec2& topRight_, const Color& color_)
+	: bottomLeft(bottomLeft_)
+	, topRight	(topRight_)
+	, color		(color_)
+{
+}
+
+struct DrawCommand
+{
+							DrawCommand (const BufferAgeTest::DrawType drawType_, const ColoredRect& rect_);
+	BufferAgeTest::DrawType drawType;
+	ColoredRect				rect;
+};
+
+DrawCommand::DrawCommand (const BufferAgeTest::DrawType drawType_, const ColoredRect& rect_)
+	: drawType(drawType_)
+	, rect    (rect_)
+{
+}
+
+struct Frame
+{
+						Frame (int width_, int height_);
+	int 				width;
+	int					height;
+	vector<DrawCommand> draws;
+};
+
+Frame::Frame (int width_, int height_)
+	: width(width_)
+	, height(height_)
+{
+}
+
+
+// (x1,y1) lie in the lower-left quadrant while (x2,y2) lie in the upper-right.
+// the coords are multiplied by 4 to amplify the minimial difference between coords to 4 (if not zero)
+// to avoid the situation where two edges are too close to each other which makes the rounding error
+// intoleratable by compareToReference()
+void generateRandomFrame (Frame* dst, const vector<BufferAgeTest::DrawType>& drawTypes, de::Random& rnd)
+{
+	for (size_t ndx = 0; ndx < drawTypes.size(); ndx++)
+	{
+		const int			x1			= rnd.getInt(0, (dst->width-1)/8) * 4;
+		const int			y1			= rnd.getInt(0, (dst->height-1)/8) * 4;
+		const int			x2			= rnd.getInt((dst->width-1)/8, (dst->width-1)/4) * 4;
+		const int			y2			= rnd.getInt((dst->height-1)/8, (dst->height-1)/4) * 4;
+		const GLubyte		r			= rnd.getUint8();
+		const GLubyte		g			= rnd.getUint8();
+		const GLubyte		b			= rnd.getUint8();
+		const ColoredRect	coloredRect	(IVec2(x1, y1), IVec2(x2, y2), Color(r, g, b));
+		const DrawCommand	drawCommand (drawTypes[ndx], coloredRect);
+		(*dst).draws.push_back(drawCommand);
+	}
+}
+
+typedef vector<Frame> FrameSequence;
+
+//helper function declaration
+EGLConfig		getEGLConfig					(const Library& egl, EGLDisplay eglDisplay, bool preserveColorBuffer);
+void			clearColorScreen				(const glw::Functions& gl, const tcu::Vec4& clearColor);
+void			clearColorReference				(tcu::Surface* ref, const tcu::Vec4& clearColor);
+void			readPixels						(const glw::Functions& gl, tcu::Surface* screen);
+float			windowToDeviceCoordinates		(int x, int length);
+bool			compareToReference				(tcu::TestLog& log, const tcu::Surface& reference, const tcu::Surface& buffer, int frameNdx, int bufferNum);
+vector<int> 	getFramesOnBuffer 				(const vector<int>& bufferAges, int frameNdx);
+
+class GLES2Renderer
+{
+public:
+							GLES2Renderer		(const glw::Functions& gl);
+							~GLES2Renderer		(void);
+	void					render				(int width, int height, const Frame& frame) const;
+
+private:
+							GLES2Renderer		(const GLES2Renderer&);
+	GLES2Renderer&			operator=			(const GLES2Renderer&);
+
+	const glw::Functions&	m_gl;
+	glu::ShaderProgram		m_glProgram;
+	glw::GLuint				m_coordLoc;
+	glw::GLuint				m_colorLoc;
+};
+
+// generate sources for vertex and fragment buffer
+glu::ProgramSources getSources (void)
+{
+	const char* const vertexShaderSource =
+		"attribute mediump vec4 a_pos;\n"
+		"attribute mediump vec4 a_color;\n"
+		"varying mediump vec4 v_color;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tv_color = a_color;\n"
+		"\tgl_Position = a_pos;\n"
+		"}";
+
+	const char* const fragmentShaderSource =
+		"varying mediump vec4 v_color;\n"
+		"void main(void)\n"
+		"{\n"
+		"\tgl_FragColor = v_color;\n"
+		"}";
+
+	return glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource);
+}
+
+GLES2Renderer::GLES2Renderer (const glw::Functions& gl)
+	: m_gl		  (gl)
+	, m_glProgram (gl, getSources())
+	, m_coordLoc  ((glw::GLuint)-1)
+	, m_colorLoc  ((glw::GLuint)-1)
+{
+	m_colorLoc = m_gl.getAttribLocation(m_glProgram.getProgram(), "a_color");
+	m_coordLoc = m_gl.getAttribLocation(m_glProgram.getProgram(), "a_pos");
+	GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to get attribute locations");
+}
+
+GLES2Renderer::~GLES2Renderer (void)
+{
+}
+
+void GLES2Renderer::render (int width, int height, const Frame& frame) const
+{
+	for (size_t drawNdx = 0; drawNdx < frame.draws.size(); drawNdx++)
+	{
+		const ColoredRect& coloredRect = frame.draws[drawNdx].rect;
+		if (frame.draws[drawNdx].drawType == BufferAgeTest::DRAWTYPE_GLES2_RENDER)
+		{
+			float x1 = windowToDeviceCoordinates(coloredRect.bottomLeft.x(), width);
+			float y1 = windowToDeviceCoordinates(coloredRect.bottomLeft.y(), height);
+			float x2 = windowToDeviceCoordinates(coloredRect.topRight.x(), width);
+			float y2 = windowToDeviceCoordinates(coloredRect.topRight.y(), height);
+
+			const glw::GLfloat coords[] =
+			{
+				x1, y1, 0.0f, 1.0f,
+				x1, y2, 0.0f, 1.0f,
+				x2, y2, 0.0f, 1.0f,
+
+				x2, y2, 0.0f, 1.0f,
+				x2, y1, 0.0f, 1.0f,
+				x1, y1, 0.0f, 1.0f
+			};
+
+			const glw::GLubyte colors[] =
+			{
+				coloredRect.color.x(), coloredRect.color.y(), coloredRect.color.z(), 255,
+				coloredRect.color.x(), coloredRect.color.y(), coloredRect.color.z(), 255,
+				coloredRect.color.x(), coloredRect.color.y(), coloredRect.color.z(), 255,
+
+				coloredRect.color.x(), coloredRect.color.y(), coloredRect.color.z(), 255,
+				coloredRect.color.x(), coloredRect.color.y(), coloredRect.color.z(), 255,
+				coloredRect.color.x(), coloredRect.color.y(), coloredRect.color.z(), 255,
+			};
+
+			m_gl.useProgram(m_glProgram.getProgram());
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgram() failed");
+
+			m_gl.enableVertexAttribArray(m_coordLoc);
+			m_gl.enableVertexAttribArray(m_colorLoc);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to enable attributes");
+
+			m_gl.vertexAttribPointer(m_coordLoc, 4, GL_FLOAT, GL_FALSE, 0, coords);
+			m_gl.vertexAttribPointer(m_colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, colors);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to set attribute pointers");
+
+			m_gl.drawArrays(GL_TRIANGLES, 0, DE_LENGTH_OF_ARRAY(coords)/4);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "glDrawArrays(), failed");
+
+			m_gl.disableVertexAttribArray(m_coordLoc);
+			m_gl.disableVertexAttribArray(m_colorLoc);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "Failed to disable attributes");
+
+			m_gl.useProgram(0);
+			GLU_EXPECT_NO_ERROR(m_gl.getError(), "glUseProgram() failed");
+		}
+		else if (frame.draws[drawNdx].drawType == BufferAgeTest::DRAWTYPE_GLES2_CLEAR)
+		{
+			m_gl.enable(GL_SCISSOR_TEST);
+			m_gl.scissor(coloredRect.bottomLeft.x(), coloredRect.bottomLeft.y(),
+						 coloredRect.topRight.x()-coloredRect.bottomLeft.x(), coloredRect.topRight.y()-coloredRect.bottomLeft.y());
+			m_gl.clearColor(coloredRect.color.x()/255.0f, coloredRect.color.y()/255.0f, coloredRect.color.z()/255.0f, 1.0f);
+			m_gl.clear(GL_COLOR_BUFFER_BIT);
+			m_gl.disable(GL_SCISSOR_TEST);
+		}
+		else
+			DE_ASSERT(false);
+	}
+}
+
+class ReferenceRenderer
+{
+public:
+						ReferenceRenderer 	(void);
+	void				render			  	(tcu::Surface* target, const Frame& frame) const;
+private:
+						ReferenceRenderer	(const ReferenceRenderer&);
+	ReferenceRenderer&	operator=		  	(const ReferenceRenderer&);
+};
+
+ReferenceRenderer::ReferenceRenderer(void)
+{
+}
+
+void ReferenceRenderer::render (tcu::Surface* target, const Frame& frame) const
+{
+	for (size_t drawNdx = 0; drawNdx < frame.draws.size(); drawNdx++)
+	{
+		const ColoredRect& coloredRect = frame.draws[drawNdx].rect;
+		if (frame.draws[drawNdx].drawType == BufferAgeTest::DRAWTYPE_GLES2_RENDER || frame.draws[drawNdx].drawType == BufferAgeTest::DRAWTYPE_GLES2_CLEAR)
+		{
+			const tcu::UVec4 color(coloredRect.color.x(), coloredRect.color.y(), coloredRect.color.z(), 255);
+			tcu::clear(tcu::getSubregion(target->getAccess(), coloredRect.bottomLeft.x(), coloredRect.bottomLeft.y(),
+										 coloredRect.topRight.x()-coloredRect.bottomLeft.x(), coloredRect.topRight.y()-coloredRect.bottomLeft.y()), color);
+		}
+		else
+			DE_ASSERT(false);
+	}
+}
+
+BufferAgeTest::BufferAgeTest (EglTestContext& eglTestCtx, bool preserveColorBuffer, const vector<DrawType>& oddFrameDrawType, const vector<DrawType>& evenFrameDrawType,
+							  const char* name, const char* description)
+	: TestCase				(eglTestCtx, name, description)
+	, m_seed				(deStringHash(name))
+	, m_preserveColorBuffer (preserveColorBuffer)
+	, m_oddFrameDrawType	(oddFrameDrawType)
+	, m_evenFrameDrawType	(evenFrameDrawType)
+	, m_eglDisplay			(EGL_NO_DISPLAY)
+	, m_window				(DE_NULL)
+	, m_eglSurface			(EGL_NO_SURFACE)
+	, m_eglContext			(EGL_NO_CONTEXT)
+	, m_gles2Renderer		(DE_NULL)
+	, m_refRenderer			(DE_NULL)
+{
+}
+
+BufferAgeTest::~BufferAgeTest (void)
+{
+	deinit();
+}
+
+void BufferAgeTest::init (void)
+{
+	const Library&	egl	= m_eglTestCtx.getLibrary();
+
+	m_eglDisplay = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());
+	m_eglConfig	 = getEGLConfig(m_eglTestCtx.getLibrary(), m_eglDisplay, m_preserveColorBuffer);
+
+	if (m_eglConfig == DE_NULL)
+		TCU_THROW(NotSupportedError, "No supported config found");
+
+	//create surface and context and make them current
+	initEGLSurface(m_eglConfig);
+	initEGLContext(m_eglConfig);
+
+	m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0));
+
+	if (eglu::hasExtension(egl, m_eglDisplay, "EGL_EXT_buffer_age") == false)
+		TCU_THROW(NotSupportedError, "EGL_EXT_buffer_age is not supported");
+
+	m_gles2Renderer = new GLES2Renderer(m_gl);
+	m_refRenderer   = new ReferenceRenderer();
+}
+
+void BufferAgeTest::deinit (void)
+{
+	const Library& egl = m_eglTestCtx.getLibrary();
+
+	delete m_refRenderer;
+	m_refRenderer = DE_NULL;
+
+	delete m_gles2Renderer;
+	m_gles2Renderer = DE_NULL;
+
+	if (m_eglContext != EGL_NO_CONTEXT)
+	{
+		egl.makeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+		egl.destroyContext(m_eglDisplay, m_eglContext);
+		m_eglContext = EGL_NO_CONTEXT;
+	}
+
+	if (m_eglSurface != EGL_NO_SURFACE)
+	{
+		egl.destroySurface(m_eglDisplay, m_eglSurface);
+		m_eglSurface = EGL_NO_SURFACE;
+	}
+
+	if (m_eglDisplay != EGL_NO_DISPLAY)
+	{
+		egl.terminate(m_eglDisplay);
+		m_eglDisplay = EGL_NO_DISPLAY;
+	}
+
+	delete m_window;
+	m_window = DE_NULL;
+}
+
+void BufferAgeTest::initEGLSurface (EGLConfig config)
+{
+	const eglu::NativeWindowFactory& factory = eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());
+	m_window = factory.createWindow(&m_eglTestCtx.getNativeDisplay(), m_eglDisplay, config, DE_NULL,
+									eglu::WindowParams(480, 480, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
+	m_eglSurface = eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *m_window, m_eglDisplay, config, DE_NULL);
+}
+
+void BufferAgeTest::initEGLContext (EGLConfig config)
+{
+	const Library& 	egl 		 = m_eglTestCtx.getLibrary();
+	const EGLint 	attribList[] =
+	{
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	egl.bindAPI(EGL_OPENGL_ES_API);
+	m_eglContext = egl.createContext(m_eglDisplay, config, EGL_NO_CONTEXT, attribList);
+	EGLU_CHECK_MSG(egl, "eglCreateContext");
+	DE_ASSERT(m_eglSurface != EGL_NO_SURFACE);
+	egl.makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
+	EGLU_CHECK_MSG(egl, "eglMakeCurrent");
+}
+
+// return indices of frames that have been written to the given buffer
+vector<int> getFramesOnBuffer (const vector<int>& bufferAges, int frameNdx)
+{
+	DE_ASSERT(frameNdx < (int)bufferAges.size());
+	vector<int> frameOnBuffer;
+	int 		age = bufferAges[frameNdx];
+	while (age != 0)
+	{
+		frameNdx = frameNdx - age;
+		DE_ASSERT(frameNdx >= 0);
+		frameOnBuffer.push_back(frameNdx);
+		age = bufferAges[frameNdx];
+	}
+
+	reverse(frameOnBuffer.begin(), frameOnBuffer.end());
+	return frameOnBuffer;
+}
+
+TestCase::IterateResult BufferAgeTest::iterate (void)
+{
+	de::Random 		rnd					(m_seed);
+	const Library&	egl					= m_eglTestCtx.getLibrary();
+	tcu::TestLog& 	log					= m_testCtx.getLog();
+	const int 		width				= eglu::querySurfaceInt(egl, m_eglDisplay, m_eglSurface, EGL_WIDTH);
+	const int 		height				= eglu::querySurfaceInt(egl, m_eglDisplay, m_eglSurface, EGL_HEIGHT);
+	const float 	clearRed			= rnd.getFloat();
+	const float 	clearGreen			= rnd.getFloat();
+	const float 	clearBlue			= rnd.getFloat();
+	const tcu::Vec4	clearColor			(clearRed, clearGreen, clearBlue, 1.0f);
+	const int 		numFrames			= 20;
+	FrameSequence 	frameSequence;
+	vector<int> 	bufferAges;
+
+	if (m_preserveColorBuffer)
+		EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));
+
+	for (int frameNdx = 0; frameNdx < numFrames; frameNdx++)
+	{
+		tcu::Surface					currentBuffer			(width, height);
+		tcu::Surface					refBuffer				(width, height);
+		Frame			   				newFrame				(width, height);
+		EGLint							currentBufferAge		= -1;
+
+		if (frameNdx % 2 == 0)
+			generateRandomFrame(&newFrame, m_evenFrameDrawType, rnd);
+		else
+			generateRandomFrame(&newFrame, m_oddFrameDrawType, rnd);
+
+		frameSequence.push_back(newFrame);
+
+		EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_BUFFER_AGE_EXT, &currentBufferAge));
+
+		if (currentBufferAge > frameNdx || currentBufferAge < 0) // invalid buffer age
+		{
+			std::ostringstream stream;
+			stream << "Fail, the age is invalid. Age: " << currentBufferAge << ", frameNdx: " << frameNdx;
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, stream.str().c_str());
+			return STOP;
+		}
+
+		if (frameNdx > 0 && m_preserveColorBuffer && currentBufferAge != 1)
+		{
+			std::ostringstream stream;
+			stream << "Fail, EGL_BUFFER_PRESERVED is set to true, but buffer age is: " << currentBufferAge << " (should be 1)";
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, stream.str().c_str());
+			return STOP;
+		}
+
+		bufferAges.push_back(currentBufferAge);
+		DE_ASSERT((int)bufferAges.size() == frameNdx+1);
+
+		// during first half, just keep rendering without reading pixel back to mimic ordinary use case
+		if (frameNdx < numFrames/2)
+		{
+			if (currentBufferAge == 0)
+				clearColorScreen(m_gl, clearColor);
+
+			m_gles2Renderer->render(width, height, newFrame);
+			EGLU_CHECK_CALL(egl, swapBuffers(m_eglDisplay, m_eglSurface));
+			continue;
+		}
+
+		// do verification in the second half
+		if (currentBufferAge > 0) //buffer contain previous content, need to verify
+		{
+			const vector<int> framesOnBuffer = getFramesOnBuffer(bufferAges, frameNdx);
+			readPixels(m_gl, &currentBuffer);
+			clearColorReference(&refBuffer, clearColor);
+
+			for (vector<int>::const_iterator it = framesOnBuffer.begin(); it != framesOnBuffer.end(); it++)
+				m_refRenderer->render(&refBuffer, frameSequence[*it]);
+
+			if (compareToReference(log, refBuffer, currentBuffer, frameNdx, frameNdx-currentBufferAge) == false)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail, buffer content is not well preserved when age > 0");
+				return STOP;
+			}
+		}
+		else // currentBufferAge == 0, content is undefined, clear the buffer, currentBufferAge < 0 is ruled out at the beginning
+		{
+			clearColorScreen(m_gl, clearColor);
+			clearColorReference(&refBuffer, clearColor);
+		}
+
+		m_gles2Renderer->render(width, height, newFrame);
+		m_refRenderer->render(&refBuffer, newFrame);
+
+		readPixels(m_gl, &currentBuffer);
+
+		if (compareToReference(log, refBuffer, currentBuffer, frameNdx, frameNdx) == false)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail, render result is wrong");
+			return STOP;
+		}
+
+		EGLU_CHECK_CALL(egl, swapBuffers(m_eglDisplay, m_eglSurface));
+	}
+
+	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+	return STOP;
+}
+
+string generateDrawTypeName (const vector<BufferAgeTest::DrawType>& drawTypes)
+{
+	std::ostringstream stream;
+	if (drawTypes.size() == 0)
+		return string("_none");
+
+	for (size_t ndx = 0; ndx < drawTypes.size(); ndx++)
+	{
+		if (drawTypes[ndx] == BufferAgeTest::DRAWTYPE_GLES2_RENDER)
+			stream << "_render";
+		else if (drawTypes[ndx] == BufferAgeTest::DRAWTYPE_GLES2_CLEAR)
+			stream << "_clear";
+		else
+			DE_ASSERT(false);
+	}
+	return stream.str();
+}
+
+string generateTestName (const vector<BufferAgeTest::DrawType>& oddFrameDrawType, const vector<BufferAgeTest::DrawType>& evenFrameDrawType)
+{
+	return "odd" + generateDrawTypeName(oddFrameDrawType) + "_even" + generateDrawTypeName(evenFrameDrawType);
+}
+
+bool isWindow (const eglu::CandidateConfig& c)
+{
+	return (c.surfaceType() & EGL_WINDOW_BIT) == EGL_WINDOW_BIT;
+}
+
+bool isES2Renderable (const eglu::CandidateConfig& c)
+{
+	return (c.get(EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES2_BIT) == EGL_OPENGL_ES2_BIT;
+}
+
+bool hasPreserveSwap (const eglu::CandidateConfig& c)
+{
+	return (c.surfaceType() & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) == EGL_SWAP_BEHAVIOR_PRESERVED_BIT;
+}
+
+EGLConfig getEGLConfig (const Library& egl, EGLDisplay eglDisplay, bool preserveColorBuffer)
+{
+	eglu::FilterList filters;
+ 	filters << isWindow << isES2Renderable;
+ 	if (preserveColorBuffer)
+ 		filters << hasPreserveSwap;
+ 	return eglu::chooseSingleConfig(egl, eglDisplay, filters);
+}
+
+void clearColorScreen (const glw::Functions& gl, const tcu::Vec4& clearColor)
+{
+	gl.clearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
+	gl.clear(GL_COLOR_BUFFER_BIT);
+}
+
+void clearColorReference (tcu::Surface* ref, const tcu::Vec4& clearColor)
+{
+	tcu::clear(ref->getAccess(), clearColor);
+}
+
+void readPixels (const glw::Functions& gl, tcu::Surface* screen)
+{
+	gl.readPixels(0, 0, screen->getWidth(), screen->getHeight(),  GL_RGBA, GL_UNSIGNED_BYTE, screen->getAccess().getDataPtr());
+}
+
+float windowToDeviceCoordinates (int x, int length)
+{
+	return (2.0f * float(x) / float(length)) - 1.0f;
+}
+
+bool compareToReference (tcu::TestLog& log,	 const tcu::Surface& reference, const tcu::Surface& buffer, int frameNdx, int bufferNum)
+{
+	std::ostringstream stream;
+	stream << "FrameNdx = " << frameNdx << ", compare current buffer (numbered: " << bufferNum << ") to reference";
+	return tcu::intThresholdPositionDeviationCompare(log, "buffer age test", stream.str().c_str(), reference.getAccess(), buffer.getAccess(),
+													 tcu::UVec4(8, 8, 8, 0), tcu::IVec3(2,2,0), true, tcu::COMPARE_LOG_RESULT);
+}
+
+} // anonymous
+
+BufferAgeTests::BufferAgeTests (EglTestContext& eglTestCtx)
+	: TestCaseGroup(eglTestCtx, "buffer_age", "Color buffer age tests")
+{
+}
+
+void BufferAgeTests::init (void)
+{
+	const BufferAgeTest::DrawType clearRender[2] =
+	{
+		BufferAgeTest::DRAWTYPE_GLES2_CLEAR,
+		BufferAgeTest::DRAWTYPE_GLES2_RENDER
+	};
+
+	const BufferAgeTest::DrawType renderClear[2] =
+	{
+		BufferAgeTest::DRAWTYPE_GLES2_RENDER,
+		BufferAgeTest::DRAWTYPE_GLES2_CLEAR
+	};
+
+	vector< vector<BufferAgeTest::DrawType> > frameDrawTypes;
+	frameDrawTypes.push_back(vector<BufferAgeTest::DrawType> ());
+	frameDrawTypes.push_back(vector<BufferAgeTest::DrawType> (1, BufferAgeTest::DRAWTYPE_GLES2_CLEAR));
+	frameDrawTypes.push_back(vector<BufferAgeTest::DrawType> (1, BufferAgeTest::DRAWTYPE_GLES2_RENDER));
+	frameDrawTypes.push_back(vector<BufferAgeTest::DrawType> (2, BufferAgeTest::DRAWTYPE_GLES2_CLEAR));
+	frameDrawTypes.push_back(vector<BufferAgeTest::DrawType> (2, BufferAgeTest::DRAWTYPE_GLES2_RENDER));
+	frameDrawTypes.push_back(vector<BufferAgeTest::DrawType> (DE_ARRAY_BEGIN(clearRender), DE_ARRAY_END(clearRender)));
+	frameDrawTypes.push_back(vector<BufferAgeTest::DrawType> (DE_ARRAY_BEGIN(renderClear), DE_ARRAY_END(renderClear)));
+
+	for (int preserveNdx = 0; preserveNdx < 2; preserveNdx++)
+	{
+		const bool			 preserve 		= (preserveNdx == 0);
+		TestCaseGroup* const preserveGroup 	= new TestCaseGroup(m_eglTestCtx, (preserve ? "preserve" : "no_preserve"), "");
+
+		for (size_t evenNdx = 0; evenNdx < frameDrawTypes.size(); evenNdx++)
+		{
+			const vector<BufferAgeTest::DrawType>& evenFrameDrawType = frameDrawTypes[evenNdx];
+
+			for (size_t oddNdx = evenNdx; oddNdx < frameDrawTypes.size(); oddNdx++)
+			{
+				const vector<BufferAgeTest::DrawType>& 	oddFrameDrawType = frameDrawTypes[oddNdx];
+				const std::string 						name 			 = generateTestName(oddFrameDrawType, evenFrameDrawType);
+				preserveGroup->addChild(new BufferAgeTest(m_eglTestCtx, preserve, oddFrameDrawType, evenFrameDrawType, name.c_str(), ""));
+			}
+		}
+		addChild(preserveGroup);
+	}
+}
+
+} // egl
+} // deqp
diff --git a/modules/egl/teglBufferAgeTests.hpp b/modules/egl/teglBufferAgeTests.hpp
new file mode 100644
index 0000000..fadf31a
--- /dev/null
+++ b/modules/egl/teglBufferAgeTests.hpp
@@ -0,0 +1,48 @@
+#ifndef _TEGLBUFFERAGETESTS_HPP
+#define _TEGLBUFFERAGETESTS_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program EGL Module
+ * ---------------------------------------
+ *
+ * Copyright 2015 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 Test EXT_buffer_age
+ *//*--------------------------------------------------------------------*/
+
+#include "tcuDefs.hpp"
+#include "teglTestCase.hpp"
+
+namespace deqp
+{
+namespace egl
+{
+
+class BufferAgeTests : public TestCaseGroup
+{
+public:
+						BufferAgeTests	(EglTestContext& eglTestCtx);
+	void				init			(void);
+
+private:
+						BufferAgeTests  (const BufferAgeTests&);
+	BufferAgeTests&		operator=		(const BufferAgeTests&);
+};
+
+} // egl
+} // deqp
+
+#endif // _TEGLBUFFERAGETESTS_HPP
diff --git a/modules/egl/teglTestPackage.cpp b/modules/egl/teglTestPackage.cpp
index 1336e14..a31eb60 100644
--- a/modules/egl/teglTestPackage.cpp
+++ b/modules/egl/teglTestPackage.cpp
@@ -56,6 +56,7 @@
 #include "teglNativeColorMappingTests.hpp"
 #include "teglNativeCoordMappingTests.hpp"
 #include "teglResizeTests.hpp"
+#include "teglBufferAgeTests.hpp"
 
 namespace deqp
 {
@@ -124,6 +125,7 @@
 		addChild(new NativeCoordMappingTests	(m_eglTestCtx));
 		addChild(new ReusableSyncTests			(m_eglTestCtx));
 		addChild(new ResizeTests				(m_eglTestCtx));
+		addChild(new BufferAgeTests				(m_eglTestCtx));
 	}
 };