static inline void writeUnorm8 (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val)
{
	deUint8* ptr = (deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*NumChannels;

	for (int c = 0; c < NumChannels; c++)
		ptr[c] = getChannel(val, c);
}
void ShaderMetamorphicVariant::render (const tcu::PixelBufferAccess& img, const std::string& vertexSrc, const std::string& fragmentSrc)
{
	TestLog&				log		= m_testCtx.getLog();
	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();

	// Positions, shared between shaders
	const float positions[] =
	{
		-1.0f,  1.0f,	// top-left
		-1.0f, -1.0f,	// bottom-left
		 1.0f, -1.0f,	// bottom-right
		 1.0f,  1.0f,	// top-right
	};

	const deUint16 indices[] =
	{
		0, 1, 2,	// bottom-left triangle
		0, 3, 2,	// top-right triangle
	};

	glu::VertexArrayBinding posBinding = glu::va::Float("coord2d", 2, 6, 0, &positions[0]);

	const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(vertexSrc, fragmentSrc));
	log << program;

	if (!program.isOk())
		throw tcu::TestError("Compile failed");

	// Set uniforms expected in GraphicsFuzz generated programs
	gl.useProgram(program.getProgram());
	// Uniform: injectionSwitch
	int uniformLoc = gl.getUniformLocation(program.getProgram(), "injectionSwitch");
	if (uniformLoc != -1)
		gl.uniform2f(uniformLoc, 0.0f, 1.0f);
	// Uniform: resolution
	uniformLoc = gl.getUniformLocation(program.getProgram(), "resolution");
	if (uniformLoc != -1)
		gl.uniform2f(uniformLoc, glw::GLfloat(img.getWidth()), glw::GLfloat(img.getHeight()));
	// Uniform: mouse
	uniformLoc = gl.getUniformLocation(program.getProgram(), "mouse");
	if (uniformLoc != -1)
		gl.uniform2f(uniformLoc, 0.0f, 0.0f);
	// Uniform: time
	uniformLoc = gl.getUniformLocation(program.getProgram(), "time");
	if (uniformLoc != -1)
		gl.uniform1f(uniformLoc, 0.0f);

	// Render two times to check nondeterministic renderings
	glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding, glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
	glu::readPixels(m_context.getRenderContext(), 0, 0, img);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
}
void resolveMultisampleColorBuffer (const tcu::PixelBufferAccess& dst, const MultisampleConstPixelBufferAccess& src)
{
	DE_ASSERT(dst.getWidth() == src.raw().getHeight());
	DE_ASSERT(dst.getHeight() == src.raw().getDepth());

	float numSamplesInv = 1.0f / (float)src.getNumSamples();

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			tcu::Vec4 sum;
			for (int s = 0; s < src.raw().getWidth(); s++)
				sum += src.raw().getPixel(s, x, y);

			dst.setPixel(sum*numSamplesInv, x, y);
		}
	}
}
tcu::PixelBufferAccess getMultisampleAccess(const tcu::PixelBufferAccess& original)
{
	return tcu::PixelBufferAccess(original.getFormat(),
								  1,
								  original.getWidth(),
								  original.getHeight(),
								  original.getFormat().getPixelSize(),
								  original.getRowPitch(),
								  original.getDataPtr());
}
inline void writeUnorm8<4> (const tcu::PixelBufferAccess& dst, int x, int y, deUint32 val)
{
	*(deUint32*)((deUint8*)dst.getDataPtr() + dst.getRowPitch()*y + x*4) = val;
}
GLW_APICALL void GLW_APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
{
	DE_UNREF(x);
	DE_UNREF(y);

	Context* const					ctx					= getCurrentContext();
	const tcu::Vec4					clearColor			(0.0f, 0.0f, 0.0f, 1.0f); // black
	const tcu::TextureFormat		transferFormat		= glu::mapGLTransferFormat(format, type);

	// invalid formats
	if (transferFormat.order == TextureFormat::CHANNELORDER_LAST || transferFormat.type == TextureFormat::CHANNELTYPE_LAST)
	{
		if (ctx->lastError == GL_NO_ERROR)
			ctx->lastError = GL_INVALID_ENUM;
		return;
	}

	// unsupported formats
	if (!(format == GL_RGBA			&& type == GL_UNSIGNED_BYTE)	&&
		!(format == GL_RGBA_INTEGER	&& type == GL_INT)				&&
		!(format == GL_RGBA_INTEGER	&& type == GL_UNSIGNED_INT)		&&
		!(format == GL_RGBA			&& type == GL_FLOAT))
	{
		if (ctx->lastError == GL_NO_ERROR)
			ctx->lastError = GL_INVALID_ENUM;
		return;
	}

	// invalid arguments
	if (width < 0 || height < 0)
	{
		if (ctx->lastError == GL_NO_ERROR)
			ctx->lastError = GL_INVALID_OPERATION;
		return;
	}

	// read to buffer
	if (ctx->pixelPackBufferBufferBinding)
		return;

	// read to use pointer
	{
		const int						targetRowLength		= (ctx->pixelPackRowLength != 0) ? (ctx->pixelPackRowLength) : (width);
		const int						targetSkipRows		= ctx->pixelPackSkipRows;
		const int						targetSkipPixels	= ctx->pixelPackSkipPixels;
		const int						infiniteHeight		= targetSkipRows + height; // as much as needed
		const int						targetRowPitch		= (ctx->pixelPackAlignment == 0) ? (targetRowLength * transferFormat.getPixelSize()) : (deAlign32(targetRowLength * transferFormat.getPixelSize(), ctx->pixelPackAlignment));

		// Create access to the whole copy target
		const tcu::PixelBufferAccess	targetAccess		(transferFormat, targetRowLength, infiniteHeight, 1, targetRowPitch, 0, pixels);

		// Select (skip_pixels, skip_rows, width, height) subregion from it. Clip to horizontal boundaries
		const tcu::PixelBufferAccess	targetRectAccess	= tcu::getSubregion(targetAccess,
																				de::clamp(targetSkipPixels, 0, targetAccess.getWidth()-1),
																				targetSkipRows,
																				de::clamp(width, 0, de::max(0, targetAccess.getWidth() - targetSkipPixels)),
																				height);

		tcu::clear(targetRectAccess, clearColor);
	}
}