/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES Utilities
 * ------------------------------------------------
 *
 * 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 Reference Rendering Context.
 *//*--------------------------------------------------------------------*/

#include "sglrReferenceContext.hpp"
#include "sglrReferenceUtils.hpp"
#include "sglrShaderProgram.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuMatrix.hpp"
#include "tcuMatrixUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "gluDefs.hpp"
#include "gluTextureUtil.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deMemory.h"
#include "rrFragmentOperations.hpp"
#include "rrRenderer.hpp"

namespace sglr
{

using std::vector;
using std::map;

using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec4;
using tcu::RGBA;

// Reference context implementation
using namespace rc;

using tcu::TextureFormat;
using tcu::PixelBufferAccess;
using tcu::ConstPixelBufferAccess;

// Utilities for ReferenceContext
#define RC_RET_VOID

#define RC_ERROR_RET(ERR, RET)			\
do {									\
	setError(ERR);						\
	return RET;							\
} while (deGetFalse())

#define RC_IF_ERROR(COND, ERR, RET)		\
do {									\
	if (COND)							\
		RC_ERROR_RET(ERR, RET);			\
} while (deGetFalse())

static inline tcu::PixelBufferAccess nullAccess (void)
{
	return tcu::PixelBufferAccess(TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT8), 0, 0, 0, DE_NULL);
}

static inline bool isEmpty (const tcu::ConstPixelBufferAccess& access)
{
	return access.getWidth() == 0 || access.getHeight() == 0 || access.getDepth() == 0;
}

static inline bool isEmpty (const rr::MultisampleConstPixelBufferAccess& access)
{
	return access.raw().getWidth() == 0 || access.raw().getHeight() == 0 || access.raw().getDepth() == 0;
}

static inline bool isEmpty (const IVec4& rect)
{
	return rect.z() == 0 || rect.w() == 0;
}

inline int getNumMipLevels1D (int size)
{
	return deLog2Floor32(size)+1;
}

inline int getNumMipLevels2D (int width, int height)
{
	return deLog2Floor32(de::max(width, height))+1;
}

inline int getNumMipLevels3D (int width, int height, int depth)
{
	return deLog2Floor32(de::max(width, de::max(height, depth)))+1;
}

inline int getMipLevelSize (int baseLevelSize, int levelNdx)
{
	return de::max(baseLevelSize >> levelNdx, 1);
}

inline bool isMipmapFilter (const tcu::Sampler::FilterMode mode)
{
	return mode != tcu::Sampler::NEAREST && mode != tcu::Sampler::LINEAR;
}

static tcu::CubeFace texTargetToFace (Framebuffer::TexTarget target)
{
	switch (target)
	{
		case Framebuffer::TEXTARGET_CUBE_MAP_NEGATIVE_X:	return tcu::CUBEFACE_NEGATIVE_X;
		case Framebuffer::TEXTARGET_CUBE_MAP_POSITIVE_X:	return tcu::CUBEFACE_POSITIVE_X;
		case Framebuffer::TEXTARGET_CUBE_MAP_NEGATIVE_Y:	return tcu::CUBEFACE_NEGATIVE_Y;
		case Framebuffer::TEXTARGET_CUBE_MAP_POSITIVE_Y:	return tcu::CUBEFACE_POSITIVE_Y;
		case Framebuffer::TEXTARGET_CUBE_MAP_NEGATIVE_Z:	return tcu::CUBEFACE_NEGATIVE_Z;
		case Framebuffer::TEXTARGET_CUBE_MAP_POSITIVE_Z:	return tcu::CUBEFACE_POSITIVE_Z;
		default:											return tcu::CUBEFACE_LAST;
	}
}

static Framebuffer::TexTarget texLayeredTypeToTarget (Texture::Type type)
{
	switch (type)
	{
		case Texture::TYPE_2D_ARRAY:		return Framebuffer::TEXTARGET_2D_ARRAY;
		case Texture::TYPE_3D:				return Framebuffer::TEXTARGET_3D;
		case Texture::TYPE_CUBE_MAP_ARRAY:	return Framebuffer::TEXTARGET_CUBE_MAP_ARRAY;
		default:							return Framebuffer::TEXTARGET_LAST;
	}
}

static tcu::CubeFace mapGLCubeFace (deUint32 face)
{
	switch (face)
	{
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:	return tcu::CUBEFACE_NEGATIVE_X;
		case GL_TEXTURE_CUBE_MAP_POSITIVE_X:	return tcu::CUBEFACE_POSITIVE_X;
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:	return tcu::CUBEFACE_NEGATIVE_Y;
		case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:	return tcu::CUBEFACE_POSITIVE_Y;
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:	return tcu::CUBEFACE_NEGATIVE_Z;
		case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:	return tcu::CUBEFACE_POSITIVE_Z;
		default:								return tcu::CUBEFACE_LAST;
	}
}

tcu::TextureFormat toTextureFormat (const tcu::PixelFormat& pixelFmt)
{
	static const struct
	{
		tcu::PixelFormat	pixelFmt;
		tcu::TextureFormat	texFmt;
	} pixelFormatMap[] =
	{
		{ tcu::PixelFormat(8,8,8,8),	tcu::TextureFormat(tcu::TextureFormat::RGBA,	tcu::TextureFormat::UNORM_INT8)			},
		{ tcu::PixelFormat(8,8,8,0),	tcu::TextureFormat(tcu::TextureFormat::RGB,		tcu::TextureFormat::UNORM_INT8)			},
		{ tcu::PixelFormat(4,4,4,4),	tcu::TextureFormat(tcu::TextureFormat::RGBA,	tcu::TextureFormat::UNORM_SHORT_4444)	},
		{ tcu::PixelFormat(5,5,5,1),	tcu::TextureFormat(tcu::TextureFormat::RGBA,	tcu::TextureFormat::UNORM_SHORT_5551)	},
		{ tcu::PixelFormat(5,6,5,0),	tcu::TextureFormat(tcu::TextureFormat::RGB,		tcu::TextureFormat::UNORM_SHORT_565)	}
	};

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pixelFormatMap); ndx++)
	{
		if (pixelFormatMap[ndx].pixelFmt == pixelFmt)
			return pixelFormatMap[ndx].texFmt;
	}

	TCU_FAIL("Can't map pixel format to texture format");
}

tcu::TextureFormat toNonSRGBFormat (const tcu::TextureFormat& fmt)
{
	switch (fmt.order)
	{
		case tcu::TextureFormat::sRGB:
			return tcu::TextureFormat(tcu::TextureFormat::RGB,	fmt.type);
		case tcu::TextureFormat::sRGBA:
			return tcu::TextureFormat(tcu::TextureFormat::RGBA,	fmt.type);
		default:
			return fmt;
	}
}

tcu::TextureFormat getDepthFormat (int depthBits)
{
	switch (depthBits)
	{
		case 8:		return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT8);
		case 16:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16);
		case 24:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNSIGNED_INT_24_8);
		case 32:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT);
		default:
			TCU_FAIL("Can't map depth buffer format");
	}
}

tcu::TextureFormat getStencilFormat (int stencilBits)
{
	switch (stencilBits)
	{
		case 8:		return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT8);
		case 16:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT16);
		case 24:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT_24_8);
		case 32:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT32);
		default:
			TCU_FAIL("Can't map depth buffer format");
	}
}

static inline tcu::IVec4 intersect (const tcu::IVec4& a, const tcu::IVec4& b)
{
	int		x0	= de::max(a.x(), b.x());
	int		y0	= de::max(a.y(), b.y());
	int		x1	= de::min(a.x()+a.z(), b.x()+b.z());
	int		y1	= de::min(a.y()+a.w(), b.y()+b.w());
	int		w	= de::max(0, x1-x0);
	int		h	= de::max(0, y1-y0);

	return tcu::IVec4(x0, y0, w, h);
}

static inline tcu::IVec4 getBufferRect (const rr::MultisampleConstPixelBufferAccess& access)
{
	return tcu::IVec4(0, 0, access.raw().getHeight(), access.raw().getDepth());
}

ReferenceContextLimits::ReferenceContextLimits (const glu::RenderContext& renderCtx)
	: contextType				(renderCtx.getType())
	, maxTextureImageUnits		(0)
	, maxTexture2DSize			(0)
	, maxTextureCubeSize		(0)
	, maxTexture2DArrayLayers	(0)
	, maxTexture3DSize			(0)
	, maxRenderbufferSize		(0)
	, maxVertexAttribs			(0)
{
	const glw::Functions& gl = renderCtx.getFunctions();

	gl.getIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS,		&maxTextureImageUnits);
	gl.getIntegerv(GL_MAX_TEXTURE_SIZE,				&maxTexture2DSize);
	gl.getIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE,	&maxTextureCubeSize);
	gl.getIntegerv(GL_MAX_RENDERBUFFER_SIZE,		&maxRenderbufferSize);
	gl.getIntegerv(GL_MAX_VERTEX_ATTRIBS,			&maxVertexAttribs);

	if (contextSupports(contextType, glu::ApiType::es(3,0)) || glu::isContextTypeGLCore(contextType))
	{
		gl.getIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS,	&maxTexture2DArrayLayers);
		gl.getIntegerv(GL_MAX_3D_TEXTURE_SIZE,		&maxTexture3DSize);
	}

	// Limit texture sizes to supported values
	maxTexture2DSize	= de::min(maxTexture2DSize,		(int)MAX_TEXTURE_SIZE);
	maxTextureCubeSize	= de::min(maxTextureCubeSize,	(int)MAX_TEXTURE_SIZE);
	maxTexture3DSize	= de::min(maxTexture3DSize,		(int)MAX_TEXTURE_SIZE);

	GLU_EXPECT_NO_ERROR(gl.getError(), GL_NO_ERROR);

	// \todo [pyry] Figure out following things:
	// + supported fbo configurations
	// ...

	// \todo [2013-08-01 pyry] Do we want to make these conditional based on renderCtx?
	addExtension("GL_EXT_color_buffer_half_float");
	addExtension("GL_EXT_color_buffer_float");

	if (contextSupports(contextType, glu::ApiType::es(3,1)))
		addExtension("GL_EXT_texture_cube_map_array");
}

void ReferenceContextLimits::addExtension (const char* extension)
{
	extensionList.push_back(extension);

	if (!extensionStr.empty())
		extensionStr += " ";
	extensionStr += extension;
}

ReferenceContextBuffers::ReferenceContextBuffers (const tcu::PixelFormat& colorBits, int depthBits, int stencilBits, int width, int height, int samples)
{
	m_colorbuffer.setStorage(toTextureFormat(colorBits), samples, width, height);

	if (depthBits > 0)
		m_depthbuffer.setStorage(getDepthFormat(depthBits), samples, width, height);

	if (stencilBits > 0)
		m_stencilbuffer.setStorage(getStencilFormat(stencilBits), samples, width, height);
}

ReferenceContext::StencilState::StencilState (void)
	: func				(GL_ALWAYS)
	, ref				(0)
	, opMask			(~0u)
	, opStencilFail		(GL_KEEP)
	, opDepthFail		(GL_KEEP)
	, opDepthPass		(GL_KEEP)
	, writeMask			(~0u)
{
}

ReferenceContext::ReferenceContext (const ReferenceContextLimits& limits, const rr::MultisamplePixelBufferAccess& colorbuffer, const rr::MultisamplePixelBufferAccess& depthbuffer, const rr::MultisamplePixelBufferAccess& stencilbuffer)
	: Context							(limits.contextType)
	, m_limits							(limits)
	, m_defaultColorbuffer				(colorbuffer)
	, m_defaultDepthbuffer				(depthbuffer)
	, m_defaultStencilbuffer			(stencilbuffer)
	, m_clientVertexArray				(0, m_limits.maxVertexAttribs)

	, m_viewport						(0, 0, colorbuffer.raw().getHeight(), colorbuffer.raw().getDepth())

	, m_activeTexture					(0)
	, m_textureUnits					(m_limits.maxTextureImageUnits)
	, m_emptyTex1D						()
	, m_emptyTex2D						()
	, m_emptyTexCube					()
	, m_emptyTex2DArray					()
	, m_emptyTex3D						()
	, m_emptyTexCubeArray				()

	, m_pixelUnpackRowLength			(0)
	, m_pixelUnpackSkipRows				(0)
	, m_pixelUnpackSkipPixels			(0)
	, m_pixelUnpackImageHeight			(0)
	, m_pixelUnpackSkipImages			(0)
	, m_pixelUnpackAlignment			(4)
	, m_pixelPackAlignment				(4)

	, m_readFramebufferBinding			(DE_NULL)
	, m_drawFramebufferBinding			(DE_NULL)
	, m_renderbufferBinding				(DE_NULL)
	, m_vertexArrayBinding				(DE_NULL)
	, m_currentProgram					(DE_NULL)

	, m_arrayBufferBinding				(DE_NULL)
	, m_pixelPackBufferBinding			(DE_NULL)
	, m_pixelUnpackBufferBinding		(DE_NULL)
	, m_transformFeedbackBufferBinding	(DE_NULL)
	, m_uniformBufferBinding			(DE_NULL)
	, m_copyReadBufferBinding			(DE_NULL)
	, m_copyWriteBufferBinding			(DE_NULL)
	, m_drawIndirectBufferBinding		(DE_NULL)

	, m_clearColor						(0.0f, 0.0f, 0.0f, 0.0f)
	, m_clearDepth						(1.0f)
	, m_clearStencil					(0)
	, m_scissorEnabled					(false)
	, m_scissorBox						(m_viewport)
	, m_stencilTestEnabled				(false)
	, m_depthTestEnabled				(false)
	, m_depthFunc						(GL_LESS)
	, m_depthRangeNear					(0.0f)
	, m_depthRangeFar					(1.0f)
	, m_polygonOffsetFactor				(0.0f)
	, m_polygonOffsetUnits				(0.0f)
	, m_polygonOffsetFillEnabled		(false)
	, m_provokingFirstVertexConvention	(false)
	, m_blendEnabled					(false)
	, m_blendModeRGB					(GL_FUNC_ADD)
	, m_blendModeAlpha					(GL_FUNC_ADD)
	, m_blendFactorSrcRGB				(GL_ONE)
	, m_blendFactorDstRGB				(GL_ZERO)
	, m_blendFactorSrcAlpha				(GL_ONE)
	, m_blendFactorDstAlpha				(GL_ZERO)
	, m_blendColor						(0.0f, 0.0f, 0.0f, 0.0f)
	, m_sRGBUpdateEnabled				(true)
	, m_depthClampEnabled				(false)
	, m_colorMask						(true, true, true, true)
	, m_depthMask						(true)
	, m_currentAttribs					(m_limits.maxVertexAttribs, rr::GenericVec4(tcu::Vec4(0, 0, 0, 1)))
	, m_lineWidth						(1.0f)
	, m_primitiveRestartFixedIndex		(false)
	, m_primitiveRestartSettableIndex	(false)
	, m_primitiveRestartIndex			(0)

	, m_lastError						(GL_NO_ERROR)
{
	// Create empty textures to be used when texture objects are incomplete.
	m_emptyTex1D.getSampler().wrapS		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex1D.getSampler().wrapT		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex1D.getSampler().minFilter	= tcu::Sampler::NEAREST;
	m_emptyTex1D.getSampler().magFilter	= tcu::Sampler::NEAREST;
	m_emptyTex1D.allocLevel(0, tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1);
	m_emptyTex1D.getLevel(0).setPixel(Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0);
	m_emptyTex1D.updateView(tcu::Sampler::MODE_LAST);

	m_emptyTex2D.getSampler().wrapS		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex2D.getSampler().wrapT		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex2D.getSampler().minFilter	= tcu::Sampler::NEAREST;
	m_emptyTex2D.getSampler().magFilter	= tcu::Sampler::NEAREST;
	m_emptyTex2D.allocLevel(0, tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1);
	m_emptyTex2D.getLevel(0).setPixel(Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0);
	m_emptyTex2D.updateView(tcu::Sampler::MODE_LAST);

	m_emptyTexCube.getSampler().wrapS		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTexCube.getSampler().wrapT		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTexCube.getSampler().minFilter	= tcu::Sampler::NEAREST;
	m_emptyTexCube.getSampler().magFilter	= tcu::Sampler::NEAREST;
	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
	{
		m_emptyTexCube.allocFace(0, (tcu::CubeFace)face, tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1);
		m_emptyTexCube.getFace(0, (tcu::CubeFace)face).setPixel(Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0);
	}
	m_emptyTexCube.updateView(tcu::Sampler::MODE_LAST);

	m_emptyTex2DArray.getSampler().wrapS		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex2DArray.getSampler().wrapT		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex2DArray.getSampler().minFilter	= tcu::Sampler::NEAREST;
	m_emptyTex2DArray.getSampler().magFilter	= tcu::Sampler::NEAREST;
	m_emptyTex2DArray.allocLevel(0, tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1, 1);
	m_emptyTex2DArray.getLevel(0).setPixel(Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0);
	m_emptyTex2DArray.updateView(tcu::Sampler::MODE_LAST);

	m_emptyTex3D.getSampler().wrapS		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex3D.getSampler().wrapT		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex3D.getSampler().wrapR		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTex3D.getSampler().minFilter	= tcu::Sampler::NEAREST;
	m_emptyTex3D.getSampler().magFilter	= tcu::Sampler::NEAREST;
	m_emptyTex3D.allocLevel(0, tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1, 1);
	m_emptyTex3D.getLevel(0).setPixel(Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0);
	m_emptyTex3D.updateView(tcu::Sampler::MODE_LAST);

	m_emptyTexCubeArray.getSampler().wrapS		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTexCubeArray.getSampler().wrapT		= tcu::Sampler::CLAMP_TO_EDGE;
	m_emptyTexCubeArray.getSampler().minFilter	= tcu::Sampler::NEAREST;
	m_emptyTexCubeArray.getSampler().magFilter	= tcu::Sampler::NEAREST;
	m_emptyTexCubeArray.allocLevel(0, tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1, 6);
	for (int faceNdx = 0; faceNdx < 6; faceNdx++)
		m_emptyTexCubeArray.getLevel(0).setPixel(Vec4(0.0f, 0.0f, 0.0f, 1.0f), 0, 0, faceNdx);
	m_emptyTexCubeArray.updateView(tcu::Sampler::MODE_LAST);

	if (glu::isContextTypeGLCore(getType()))
		m_sRGBUpdateEnabled = false;
}

ReferenceContext::~ReferenceContext (void)
{
	// Destroy all objects -- verifies that ref counting works
	{
		vector<VertexArray*> vertexArrays;
		m_vertexArrays.getAll(vertexArrays);
		for (vector<VertexArray*>::iterator i = vertexArrays.begin(); i != vertexArrays.end(); i++)
			deleteVertexArray(*i);

		DE_ASSERT(m_clientVertexArray.getRefCount() == 1);
	}

	{
		vector<Texture*> textures;
		m_textures.getAll(textures);
		for (vector<Texture*>::iterator i = textures.begin(); i != textures.end(); i++)
			deleteTexture(*i);
	}

	{
		vector<Framebuffer*> framebuffers;
		m_framebuffers.getAll(framebuffers);
		for (vector<Framebuffer*>::iterator i = framebuffers.begin(); i != framebuffers.end(); i++)
			deleteFramebuffer(*i);
	}

	{
		vector<Renderbuffer*> renderbuffers;
		m_renderbuffers.getAll(renderbuffers);
		for (vector<Renderbuffer*>::iterator i = renderbuffers.begin(); i != renderbuffers.end(); i++)
			deleteRenderbuffer(*i);
	}

	{
		vector<DataBuffer*> buffers;
		m_buffers.getAll(buffers);
		for (vector<DataBuffer*>::iterator i = buffers.begin(); i != buffers.end(); i++)
			deleteBuffer(*i);
	}

	{
		vector<ShaderProgramObjectContainer*> programs;
		m_programs.getAll(programs);
		for (vector<ShaderProgramObjectContainer*>::iterator i = programs.begin(); i != programs.end(); i++)
			deleteProgramObject(*i);
	}
}

void ReferenceContext::activeTexture (deUint32 texture)
{
	if (deInBounds32(texture, GL_TEXTURE0, GL_TEXTURE0 + (deUint32)m_textureUnits.size()))
		m_activeTexture = texture - GL_TEXTURE0;
	else
		setError(GL_INVALID_ENUM);
}

void ReferenceContext::setTex1DBinding (int unitNdx, Texture1D* texture)
{
	if (m_textureUnits[unitNdx].tex1DBinding)
	{
		m_textures.releaseReference(m_textureUnits[unitNdx].tex1DBinding);
		m_textureUnits[unitNdx].tex1DBinding = DE_NULL;
	}

	if (texture)
	{
		m_textures.acquireReference(texture);
		m_textureUnits[unitNdx].tex1DBinding = texture;
	}
}

void ReferenceContext::setTex2DBinding (int unitNdx, Texture2D* texture)
{
	if (m_textureUnits[unitNdx].tex2DBinding)
	{
		m_textures.releaseReference(m_textureUnits[unitNdx].tex2DBinding);
		m_textureUnits[unitNdx].tex2DBinding = DE_NULL;
	}

	if (texture)
	{
		m_textures.acquireReference(texture);
		m_textureUnits[unitNdx].tex2DBinding = texture;
	}
}

void ReferenceContext::setTexCubeBinding (int unitNdx, TextureCube* texture)
{
	if (m_textureUnits[unitNdx].texCubeBinding)
	{
		m_textures.releaseReference(m_textureUnits[unitNdx].texCubeBinding);
		m_textureUnits[unitNdx].texCubeBinding = DE_NULL;
	}

	if (texture)
	{
		m_textures.acquireReference(texture);
		m_textureUnits[unitNdx].texCubeBinding = texture;
	}
}

void ReferenceContext::setTex2DArrayBinding (int unitNdx, Texture2DArray* texture)
{
	if (m_textureUnits[unitNdx].tex2DArrayBinding)
	{
		m_textures.releaseReference(m_textureUnits[unitNdx].tex2DArrayBinding);
		m_textureUnits[unitNdx].tex2DArrayBinding = DE_NULL;
	}

	if (texture)
	{
		m_textures.acquireReference(texture);
		m_textureUnits[unitNdx].tex2DArrayBinding = texture;
	}
}

void ReferenceContext::setTex3DBinding (int unitNdx, Texture3D* texture)
{
	if (m_textureUnits[unitNdx].tex3DBinding)
	{
		m_textures.releaseReference(m_textureUnits[unitNdx].tex3DBinding);
		m_textureUnits[unitNdx].tex3DBinding = DE_NULL;
	}

	if (texture)
	{
		m_textures.acquireReference(texture);
		m_textureUnits[unitNdx].tex3DBinding = texture;
	}
}

void ReferenceContext::setTexCubeArrayBinding (int unitNdx, TextureCubeArray* texture)
{
	if (m_textureUnits[unitNdx].texCubeArrayBinding)
	{
		m_textures.releaseReference(m_textureUnits[unitNdx].texCubeArrayBinding);
		m_textureUnits[unitNdx].texCubeArrayBinding = DE_NULL;
	}

	if (texture)
	{
		m_textures.acquireReference(texture);
		m_textureUnits[unitNdx].texCubeArrayBinding = texture;
	}
}

void ReferenceContext::bindTexture (deUint32 target, deUint32 texture)
{
	int unitNdx = m_activeTexture;

	RC_IF_ERROR(target != GL_TEXTURE_1D				&&
				target != GL_TEXTURE_2D				&&
				target != GL_TEXTURE_CUBE_MAP		&&
				target != GL_TEXTURE_2D_ARRAY		&&
				target != GL_TEXTURE_3D				&&
				target != GL_TEXTURE_CUBE_MAP_ARRAY,
				GL_INVALID_ENUM, RC_RET_VOID);

	RC_IF_ERROR(glu::isContextTypeES(m_limits.contextType) && (target == GL_TEXTURE_1D), GL_INVALID_ENUM, RC_RET_VOID);

	if (texture == 0)
	{
		// Clear binding.
		switch (target)
		{
			case GL_TEXTURE_1D:				setTex1DBinding			(unitNdx, DE_NULL);	break;
			case GL_TEXTURE_2D:				setTex2DBinding			(unitNdx, DE_NULL);	break;
			case GL_TEXTURE_CUBE_MAP:		setTexCubeBinding		(unitNdx, DE_NULL);	break;
			case GL_TEXTURE_2D_ARRAY:		setTex2DArrayBinding	(unitNdx, DE_NULL);	break;
			case GL_TEXTURE_3D:				setTex3DBinding			(unitNdx, DE_NULL);	break;
			case GL_TEXTURE_CUBE_MAP_ARRAY:	setTexCubeArrayBinding	(unitNdx, DE_NULL);	break;
			default:
				DE_ASSERT(false);
		}
	}
	else
	{
		Texture* texObj = m_textures.find(texture);

		if (texObj)
		{
			// Validate type.
			Texture::Type expectedType = Texture::TYPE_LAST;
			switch (target)
			{
				case GL_TEXTURE_1D:				expectedType = Texture::TYPE_1D;				break;
				case GL_TEXTURE_2D:				expectedType = Texture::TYPE_2D;				break;
				case GL_TEXTURE_CUBE_MAP:		expectedType = Texture::TYPE_CUBE_MAP;			break;
				case GL_TEXTURE_2D_ARRAY:		expectedType = Texture::TYPE_2D_ARRAY;			break;
				case GL_TEXTURE_3D:				expectedType = Texture::TYPE_3D;				break;
				case GL_TEXTURE_CUBE_MAP_ARRAY:	expectedType = Texture::TYPE_CUBE_MAP_ARRAY;	break;
				default:
					DE_ASSERT(false);
			}
			RC_IF_ERROR(texObj->getType() != expectedType, GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
		{
			// New texture object.
			switch (target)
			{
				case GL_TEXTURE_1D:				texObj = new Texture1D			(texture);	break;
				case GL_TEXTURE_2D:				texObj = new Texture2D			(texture);	break;
				case GL_TEXTURE_CUBE_MAP:		texObj = new TextureCube		(texture);	break;
				case GL_TEXTURE_2D_ARRAY:		texObj = new Texture2DArray		(texture);	break;
				case GL_TEXTURE_3D:				texObj = new Texture3D			(texture);	break;
				case GL_TEXTURE_CUBE_MAP_ARRAY:	texObj = new TextureCubeArray	(texture);	break;
				default:
					DE_ASSERT(false);
			}

			m_textures.insert(texObj);
		}

		switch (target)
		{
			case GL_TEXTURE_1D:				setTex1DBinding			(unitNdx, static_cast<Texture1D*>			(texObj));	break;
			case GL_TEXTURE_2D:				setTex2DBinding			(unitNdx, static_cast<Texture2D*>			(texObj));	break;
			case GL_TEXTURE_CUBE_MAP:		setTexCubeBinding		(unitNdx, static_cast<TextureCube*>			(texObj));	break;
			case GL_TEXTURE_2D_ARRAY:		setTex2DArrayBinding	(unitNdx, static_cast<Texture2DArray*>		(texObj));	break;
			case GL_TEXTURE_3D:				setTex3DBinding			(unitNdx, static_cast<Texture3D*>			(texObj));	break;
			case GL_TEXTURE_CUBE_MAP_ARRAY:	setTexCubeArrayBinding	(unitNdx, static_cast<TextureCubeArray*>	(texObj));	break;
			default:
				DE_ASSERT(false);
		}
	}
}

void ReferenceContext::genTextures (int numTextures, deUint32* textures)
{
	while (numTextures--)
		*textures++ = m_textures.allocateName();
}

void ReferenceContext::deleteTextures (int numTextures, const deUint32* textures)
{
	for (int i = 0; i < numTextures; i++)
	{
		deUint32	name		= textures[i];
		Texture*	texture		= name ? m_textures.find(name) : DE_NULL;

		if (texture)
			deleteTexture(texture);
	}
}

void ReferenceContext::deleteTexture (Texture* texture)
{
	// Unbind from context
	for (int unitNdx = 0; unitNdx < (int)m_textureUnits.size(); unitNdx++)
	{
		if (m_textureUnits[unitNdx].tex1DBinding				== texture)	setTex1DBinding			(unitNdx, DE_NULL);
		else if (m_textureUnits[unitNdx].tex2DBinding			== texture)	setTex2DBinding			(unitNdx, DE_NULL);
		else if (m_textureUnits[unitNdx].texCubeBinding			== texture)	setTexCubeBinding		(unitNdx, DE_NULL);
		else if (m_textureUnits[unitNdx].tex2DArrayBinding		== texture)	setTex2DArrayBinding	(unitNdx, DE_NULL);
		else if (m_textureUnits[unitNdx].tex3DBinding			== texture)	setTex3DBinding			(unitNdx, DE_NULL);
		else if (m_textureUnits[unitNdx].texCubeArrayBinding	== texture)	setTexCubeArrayBinding	(unitNdx, DE_NULL);
	}

	// Unbind from currently bound framebuffers
	for (int ndx = 0; ndx < 2; ndx++)
	{
		rc::Framebuffer* framebufferBinding = ndx ? m_drawFramebufferBinding : m_readFramebufferBinding;
		if (framebufferBinding)
		{
			int releaseRefCount = (framebufferBinding == m_drawFramebufferBinding ? 1 : 0)
								+ (framebufferBinding == m_readFramebufferBinding ? 1 : 0);

			for (int point = 0; point < Framebuffer::ATTACHMENTPOINT_LAST; point++)
			{
				Framebuffer::Attachment& attachment = framebufferBinding->getAttachment((Framebuffer::AttachmentPoint)point);
				if (attachment.name == texture->getName())
				{
					for (int refNdx = 0; refNdx < releaseRefCount; refNdx++)
						releaseFboAttachmentReference(attachment);
					attachment = Framebuffer::Attachment();
				}
			}
		}
	}

	DE_ASSERT(texture->getRefCount() == 1);
	m_textures.releaseReference(texture);
}

void ReferenceContext::bindFramebuffer (deUint32 target, deUint32 name)
{
	Framebuffer* fbo = DE_NULL;

	RC_IF_ERROR(target != GL_FRAMEBUFFER		&&
				target != GL_DRAW_FRAMEBUFFER	&&
				target != GL_READ_FRAMEBUFFER, GL_INVALID_ENUM, RC_RET_VOID);

	if (name != 0)
	{
		// Find or create framebuffer object.
		fbo = m_framebuffers.find(name);
		if (!fbo)
		{
			fbo = new Framebuffer(name);
			m_framebuffers.insert(fbo);
		}
	}

	for (int ndx = 0; ndx < 2; ndx++)
	{
		deUint32			bindingTarget	= ndx ? GL_DRAW_FRAMEBUFFER			: GL_READ_FRAMEBUFFER;
		rc::Framebuffer*&	binding			= ndx ? m_drawFramebufferBinding	: m_readFramebufferBinding;

		if (target != GL_FRAMEBUFFER && target != bindingTarget)
			continue; // Doesn't match this target.

		// Remove old references
		if (binding)
		{
			// Clear all attachment point references
			for (int point = 0; point < Framebuffer::ATTACHMENTPOINT_LAST; point++)
				releaseFboAttachmentReference(binding->getAttachment((Framebuffer::AttachmentPoint)point));

			m_framebuffers.releaseReference(binding);
		}

		// Create new references
		if (fbo)
		{
			m_framebuffers.acquireReference(fbo);

			for (int point = 0; point < Framebuffer::ATTACHMENTPOINT_LAST; point++)
				acquireFboAttachmentReference(fbo->getAttachment((Framebuffer::AttachmentPoint)point));
		}

		binding = fbo;
	}
}

void ReferenceContext::genFramebuffers (int numFramebuffers, deUint32* framebuffers)
{
	while (numFramebuffers--)
		*framebuffers++ = m_framebuffers.allocateName();
}

void ReferenceContext::deleteFramebuffer (Framebuffer* framebuffer)
{
	// Remove bindings.
	if (m_drawFramebufferBinding == framebuffer) bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	if (m_readFramebufferBinding == framebuffer) bindFramebuffer(GL_READ_FRAMEBUFFER, 0);

	DE_ASSERT(framebuffer->getRefCount() == 1);
	m_framebuffers.releaseReference(framebuffer);
}

void ReferenceContext::deleteFramebuffers (int numFramebuffers, const deUint32* framebuffers)
{
	for (int i = 0; i < numFramebuffers; i++)
	{
		deUint32		name		= framebuffers[i];
		Framebuffer*	framebuffer	= name ? m_framebuffers.find(name) : DE_NULL;

		if (framebuffer)
			deleteFramebuffer(framebuffer);
	}
}

void ReferenceContext::bindRenderbuffer (deUint32 target, deUint32 name)
{
	Renderbuffer* rbo = DE_NULL;

	RC_IF_ERROR(target != GL_RENDERBUFFER, GL_INVALID_ENUM, RC_RET_VOID);

	if (name != 0)
	{
		rbo = m_renderbuffers.find(name);
		if (!rbo)
		{
			rbo = new Renderbuffer(name);
			m_renderbuffers.insert(rbo);
		}
	}

	// Remove old reference
	if (m_renderbufferBinding)
		m_renderbuffers.releaseReference(m_renderbufferBinding);

	// Create new reference
	if (rbo)
		m_renderbuffers.acquireReference(rbo);

	m_renderbufferBinding = rbo;
}

void ReferenceContext::genRenderbuffers (int numRenderbuffers, deUint32* renderbuffers)
{
	while (numRenderbuffers--)
		*renderbuffers++ = m_renderbuffers.allocateName();
}

void ReferenceContext::deleteRenderbuffer (Renderbuffer* renderbuffer)
{
	if (m_renderbufferBinding == renderbuffer)
		bindRenderbuffer(GL_RENDERBUFFER, 0);

	// Unbind from currently bound framebuffers
	for (int ndx = 0; ndx < 2; ndx++)
	{
		rc::Framebuffer* framebufferBinding = ndx ? m_drawFramebufferBinding : m_readFramebufferBinding;
		if (framebufferBinding)
		{
			int releaseRefCount = (framebufferBinding == m_drawFramebufferBinding ? 1 : 0)
								+ (framebufferBinding == m_readFramebufferBinding ? 1 : 0);

			for (int point = 0; point < Framebuffer::ATTACHMENTPOINT_LAST; point++)
			{
				Framebuffer::Attachment& attachment = framebufferBinding->getAttachment((Framebuffer::AttachmentPoint)point);
				if (attachment.name == renderbuffer->getName())
				{
					for (int refNdx = 0; refNdx < releaseRefCount; refNdx++)
						releaseFboAttachmentReference(attachment);
					attachment = Framebuffer::Attachment();
				}
			}
		}
	}

	DE_ASSERT(renderbuffer->getRefCount() == 1);
	m_renderbuffers.releaseReference(renderbuffer);
}

void ReferenceContext::deleteRenderbuffers (int numRenderbuffers, const deUint32* renderbuffers)
{
	for (int i = 0; i < numRenderbuffers; i++)
	{
		deUint32		name			= renderbuffers[i];
		Renderbuffer*	renderbuffer	= name ? m_renderbuffers.find(name) : DE_NULL;

		if (renderbuffer)
			deleteRenderbuffer(renderbuffer);
	}
}

void ReferenceContext::pixelStorei (deUint32 pname, int param)
{
	switch (pname)
	{
		case GL_UNPACK_ALIGNMENT:
			RC_IF_ERROR(param != 1 && param != 2 && param != 4 && param != 8, GL_INVALID_VALUE, RC_RET_VOID);
			m_pixelUnpackAlignment = param;
			break;

		case GL_PACK_ALIGNMENT:
			RC_IF_ERROR(param != 1 && param != 2 && param != 4 && param != 8, GL_INVALID_VALUE, RC_RET_VOID);
			m_pixelPackAlignment = param;
			break;

		case GL_UNPACK_ROW_LENGTH:
			RC_IF_ERROR(param < 0, GL_INVALID_VALUE, RC_RET_VOID);
			m_pixelUnpackRowLength = param;
			break;

		case GL_UNPACK_SKIP_ROWS:
			RC_IF_ERROR(param < 0, GL_INVALID_VALUE, RC_RET_VOID);
			m_pixelUnpackSkipRows = param;
			break;

		case GL_UNPACK_SKIP_PIXELS:
			RC_IF_ERROR(param < 0, GL_INVALID_VALUE, RC_RET_VOID);
			m_pixelUnpackSkipPixels = param;
			break;

		case GL_UNPACK_IMAGE_HEIGHT:
			RC_IF_ERROR(param < 0, GL_INVALID_VALUE, RC_RET_VOID);
			m_pixelUnpackImageHeight = param;
			break;

		case GL_UNPACK_SKIP_IMAGES:
			RC_IF_ERROR(param < 0, GL_INVALID_VALUE, RC_RET_VOID);
			m_pixelUnpackSkipImages = param;
			break;

		default:
			setError(GL_INVALID_ENUM);
	}
}

tcu::ConstPixelBufferAccess ReferenceContext::getUnpack2DAccess (const tcu::TextureFormat& format, int width, int height, const void* data)
{
	int				pixelSize	= format.getPixelSize();
	int				rowLen		= m_pixelUnpackRowLength > 0 ? m_pixelUnpackRowLength : width;
	int				rowPitch	= deAlign32(rowLen*pixelSize, m_pixelUnpackAlignment);
	const deUint8*	ptr			= (const deUint8*)data + m_pixelUnpackSkipRows*rowPitch + m_pixelUnpackSkipPixels*pixelSize;

	return tcu::ConstPixelBufferAccess(format, width, height, 1, rowPitch, 0, ptr);
}

tcu::ConstPixelBufferAccess ReferenceContext::getUnpack3DAccess (const tcu::TextureFormat& format, int width, int height, int depth, const void* data)
{
	int				pixelSize	= format.getPixelSize();
	int				rowLen		= m_pixelUnpackRowLength	> 0 ? m_pixelUnpackRowLength	: width;
	int				imageHeight	= m_pixelUnpackImageHeight	> 0 ? m_pixelUnpackImageHeight	: height;
	int				rowPitch	= deAlign32(rowLen*pixelSize, m_pixelUnpackAlignment);
	int				slicePitch	= imageHeight*rowPitch;
	const deUint8*	ptr			= (const deUint8*)data + m_pixelUnpackSkipImages*slicePitch + m_pixelUnpackSkipRows*rowPitch + m_pixelUnpackSkipPixels*pixelSize;

	return tcu::ConstPixelBufferAccess(format, width, height, depth, rowPitch, slicePitch, ptr);
}

static tcu::TextureFormat mapInternalFormat (deUint32 internalFormat)
{
	switch (internalFormat)
	{
		case GL_ALPHA:				return TextureFormat(TextureFormat::A,		TextureFormat::UNORM_INT8);
		case GL_LUMINANCE:			return TextureFormat(TextureFormat::L,		TextureFormat::UNORM_INT8);
		case GL_LUMINANCE_ALPHA:	return TextureFormat(TextureFormat::LA,		TextureFormat::UNORM_INT8);
		case GL_RGB:				return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_INT8);
		case GL_RGBA:				return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);

		default:
			return glu::mapGLInternalFormat(internalFormat);
	}
}

static void depthValueFloatClampCopy (const PixelBufferAccess& dst, const ConstPixelBufferAccess& src)
{
	int width	= dst.getWidth();
	int height	= dst.getHeight();
	int depth	= dst.getDepth();

	DE_ASSERT(src.getWidth() == width && src.getHeight() == height && src.getDepth() == depth);

	// clamping copy

	if (src.getFormat().order == tcu::TextureFormat::DS && dst.getFormat().order == tcu::TextureFormat::DS)
	{
		// copy only depth and stencil
		for (int z = 0; z < depth; z++)
		for (int y = 0; y < height; y++)
		for (int x = 0; x < width; x++)
		{
			dst.setPixDepth(de::clamp(src.getPixDepth(x, y, z), 0.0f, 1.0f), x, y, z);
			dst.setPixStencil(src.getPixStencil(x, y, z), x, y, z);
		}
	}
	else
	{
		// copy only depth
		for (int z = 0; z < depth; z++)
		for (int y = 0; y < height; y++)
		for (int x = 0; x < width; x++)
			dst.setPixDepth(de::clamp(src.getPixDepth(x, y, z), 0.0f, 1.0f), x, y, z);
	}
}

void ReferenceContext::texImage1D (deUint32 target, int level, deUint32 internalFormat, int width, int border, deUint32 format, deUint32 type, const void* data)
{
	texImage2D(target, level, internalFormat, width, 1, border, format, type, data);
}

void ReferenceContext::texImage2D (deUint32 target, int level, deUint32 internalFormat, int width, int height, int border, deUint32 format, deUint32 type, const void* data)
{
	texImage3D(target, level, internalFormat, width, height, 1, border, format, type, data);
}

static void clearToTextureInitialValue (PixelBufferAccess access)
{
	const bool hasDepth		= access.getFormat().order == tcu::TextureFormat::D || access.getFormat().order == tcu::TextureFormat::DS;
	const bool hasStencil	= access.getFormat().order == tcu::TextureFormat::S || access.getFormat().order == tcu::TextureFormat::DS;
	const bool hasColor		= !hasDepth && !hasStencil;

	if (hasDepth)
		tcu::clearDepth(access, 0.0f);
	if (hasStencil)
		tcu::clearStencil(access, 0u);
	if (hasColor)
		tcu::clear(access, Vec4(0.0f, 0.0f, 0.0f, 1.0f));
}

void ReferenceContext::texImage3D (deUint32 target, int level, deUint32 internalFormat, int width, int height, int depth, int border, deUint32 format, deUint32 type, const void* data)
{
	TextureUnit&		unit					= m_textureUnits[m_activeTexture];
	const void*			unpackPtr				= getPixelUnpackPtr(data);
	const bool			isDstFloatDepthFormat	= (internalFormat == GL_DEPTH_COMPONENT32F || internalFormat == GL_DEPTH32F_STENCIL8); // depth components are limited to [0,1] range
	TextureFormat		storageFmt;
	TextureFormat		transferFmt;

	RC_IF_ERROR(border != 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(width < 0 || height < 0 || depth < 0 || level < 0, GL_INVALID_VALUE, RC_RET_VOID);

	// Map storage format.
	storageFmt = mapInternalFormat(internalFormat);
	RC_IF_ERROR(storageFmt.order	== TextureFormat::CHANNELORDER_LAST ||
				storageFmt.type		== TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	// Map transfer format.
	transferFmt = glu::mapGLTransferFormat(format, type);
	RC_IF_ERROR(transferFmt.order	== TextureFormat::CHANNELORDER_LAST ||
				transferFmt.type	== TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	if (target == GL_TEXTURE_1D && glu::isContextTypeGLCore(m_limits.contextType))
	{
		// Validate size and level.
		RC_IF_ERROR(width > m_limits.maxTexture2DSize || height != 1 || depth != 1, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTexture2DSize), GL_INVALID_VALUE, RC_RET_VOID);

		Texture1D* texture = unit.tex1DBinding ? unit.tex1DBinding : &unit.default1DTex;

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasLevel(level), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getLevel(level));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocLevel(level, storageFmt, width);

		if (unpackPtr)
		{
			ConstPixelBufferAccess	src		= getUnpack2DAccess(transferFmt, width, 1, unpackPtr);
			PixelBufferAccess		dst		(texture->getLevel(level));

			if (isDstFloatDepthFormat)
				depthValueFloatClampCopy(dst, src);
			else
				tcu::copy(dst, src);
		}
		else
		{
			// No data supplied, clear to initial
			clearToTextureInitialValue(texture->getLevel(level));
		}
	}
	else if (target == GL_TEXTURE_2D)
	{
		// Validate size and level.
		RC_IF_ERROR(width > m_limits.maxTexture2DSize || height > m_limits.maxTexture2DSize || depth != 1, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTexture2DSize), GL_INVALID_VALUE, RC_RET_VOID);

		Texture2D* texture = unit.tex2DBinding ? unit.tex2DBinding : &unit.default2DTex;

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasLevel(level), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getLevel(level));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth()	||
						height		!= dst.getHeight(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocLevel(level, storageFmt, width, height);

		if (unpackPtr)
		{
			ConstPixelBufferAccess	src		= getUnpack2DAccess(transferFmt, width, height, unpackPtr);
			PixelBufferAccess		dst		(texture->getLevel(level));

			if (isDstFloatDepthFormat)
				depthValueFloatClampCopy(dst, src);
			else
				tcu::copy(dst, src);
		}
		else
		{
			// No data supplied, clear to initial
			clearToTextureInitialValue(texture->getLevel(level));
		}
	}
	else if (target == GL_TEXTURE_CUBE_MAP_NEGATIVE_X ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
			 target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_Y ||
			 target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_Z)
	{
		// Validate size and level.
		RC_IF_ERROR(width != height || width > m_limits.maxTextureCubeSize || depth != 1, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTextureCubeSize), GL_INVALID_VALUE, RC_RET_VOID);

		TextureCube*	texture	= unit.texCubeBinding ? unit.texCubeBinding : &unit.defaultCubeTex;
		tcu::CubeFace	face	= mapGLCubeFace(target);

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasFace(level, face), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getFace(level, face));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth()	||
						height		!= dst.getHeight(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocFace(level, face, storageFmt, width, height);

		if (unpackPtr)
		{
			ConstPixelBufferAccess	src		= getUnpack2DAccess(transferFmt, width, height, unpackPtr);
			PixelBufferAccess		dst		(texture->getFace(level, face));

			if (isDstFloatDepthFormat)
				depthValueFloatClampCopy(dst, src);
			else
				tcu::copy(dst, src);
		}
		else
		{
			// No data supplied, clear to initial
			clearToTextureInitialValue(texture->getFace(level, face));
		}
	}
	else if (target == GL_TEXTURE_2D_ARRAY)
	{
		// Validate size and level.
		RC_IF_ERROR(width	> m_limits.maxTexture2DSize ||
					height	> m_limits.maxTexture2DSize ||
					depth	> m_limits.maxTexture2DArrayLayers, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTexture2DSize), GL_INVALID_VALUE, RC_RET_VOID);

		Texture2DArray* texture = unit.tex2DArrayBinding ? unit.tex2DArrayBinding : &unit.default2DArrayTex;

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasLevel(level), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getLevel(level));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth()	||
						height		!= dst.getHeight()	||
						depth		!= dst.getDepth(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocLevel(level, storageFmt, width, height, depth);

		if (unpackPtr)
		{
			ConstPixelBufferAccess	src		= getUnpack3DAccess(transferFmt, width, height, depth, unpackPtr);
			PixelBufferAccess		dst		(texture->getLevel(level));

			if (isDstFloatDepthFormat)
				depthValueFloatClampCopy(dst, src);
			else
				tcu::copy(dst, src);
		}
		else
		{
			// No data supplied, clear to initial
			clearToTextureInitialValue(texture->getLevel(level));
		}
	}
	else if (target == GL_TEXTURE_3D)
	{
		// Validate size and level.
		RC_IF_ERROR(width	> m_limits.maxTexture3DSize ||
					height	> m_limits.maxTexture3DSize ||
					depth	> m_limits.maxTexture3DSize, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTexture3DSize), GL_INVALID_VALUE, RC_RET_VOID);

		Texture3D* texture = unit.tex3DBinding ? unit.tex3DBinding : &unit.default3DTex;

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasLevel(level), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getLevel(level));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth()	||
						height		!= dst.getHeight()	||
						depth		!= dst.getDepth(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocLevel(level, storageFmt, width, height, depth);

		if (unpackPtr)
		{
			ConstPixelBufferAccess	src		= getUnpack3DAccess(transferFmt, width, height, depth, unpackPtr);
			PixelBufferAccess		dst		(texture->getLevel(level));

			if (isDstFloatDepthFormat)
				depthValueFloatClampCopy(dst, src);
			else
				tcu::copy(dst, src);
		}
		else
		{
			// No data supplied, clear to initial
			clearToTextureInitialValue(texture->getLevel(level));
		}
	}
	else if (target == GL_TEXTURE_CUBE_MAP_ARRAY)
	{
		// Validate size and level.
		RC_IF_ERROR(width		!= height						||
					width		 > m_limits.maxTexture2DSize	||
					depth % 6	!= 0							||
					depth		 > m_limits.maxTexture2DArrayLayers, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTexture2DSize), GL_INVALID_VALUE, RC_RET_VOID);

		TextureCubeArray* texture = unit.texCubeArrayBinding ? unit.texCubeArrayBinding : &unit.defaultCubeArrayTex;

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasLevel(level), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getLevel(level));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth()	||
						height		!= dst.getHeight()	||
						depth		!= dst.getDepth(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocLevel(level, storageFmt, width, height, depth);

		if (unpackPtr)
		{
			ConstPixelBufferAccess	src		= getUnpack3DAccess(transferFmt, width, height, depth, unpackPtr);
			PixelBufferAccess		dst		(texture->getLevel(level));

			if (isDstFloatDepthFormat)
				depthValueFloatClampCopy(dst, src);
			else
				tcu::copy(dst, src);
		}
		else
		{
			// No data supplied, clear to initial
			clearToTextureInitialValue(texture->getLevel(level));
		}
	}
	else
		RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
}

void ReferenceContext::texSubImage1D (deUint32 target, int level, int xoffset, int width, deUint32 format, deUint32 type, const void* data)
{
	texSubImage2D(target, level, xoffset, 0, width, 1, format, type, data);
}

void ReferenceContext::texSubImage2D (deUint32 target, int level, int xoffset, int yoffset, int width, int height, deUint32 format, deUint32 type, const void* data)
{
	texSubImage3D(target, level, xoffset, yoffset, 0, width, height, 1, format, type, data);
}

void ReferenceContext::texSubImage3D (deUint32 target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, deUint32 format, deUint32 type, const void* data)
{
	TextureUnit& unit = m_textureUnits[m_activeTexture];

	RC_IF_ERROR(xoffset < 0 || yoffset < 0 || zoffset < 0,	GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(width < 0 || height < 0 || depth < 0,		GL_INVALID_VALUE, RC_RET_VOID);

	TextureFormat transferFmt = glu::mapGLTransferFormat(format, type);
	RC_IF_ERROR(transferFmt.order	== TextureFormat::CHANNELORDER_LAST ||
				transferFmt.type	== TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	ConstPixelBufferAccess src = getUnpack3DAccess(transferFmt, width, height, depth, getPixelUnpackPtr(data));

	if (target == GL_TEXTURE_1D && glu::isContextTypeGLCore(m_limits.contextType))
	{
		Texture1D& texture = unit.tex1DBinding ? *unit.tex1DBinding : unit.default1DTex;

		RC_IF_ERROR(!texture.hasLevel(level), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getLevel(level);

		RC_IF_ERROR(xoffset + width		> dst.getWidth()	||
					yoffset + height	> dst.getHeight()	||
					zoffset + depth		> dst.getDepth(),
					GL_INVALID_VALUE, RC_RET_VOID);

		// depth components are limited to [0,1] range
		if (dst.getFormat().order == tcu::TextureFormat::D || dst.getFormat().order == tcu::TextureFormat::DS)
			depthValueFloatClampCopy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
		else
			tcu::copy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
	}
	else if (target == GL_TEXTURE_2D)
	{
		Texture2D& texture = unit.tex2DBinding ? *unit.tex2DBinding : unit.default2DTex;

		RC_IF_ERROR(!texture.hasLevel(level), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getLevel(level);

		RC_IF_ERROR(xoffset + width		> dst.getWidth()	||
					yoffset + height	> dst.getHeight()	||
					zoffset + depth		> dst.getDepth(),
					GL_INVALID_VALUE, RC_RET_VOID);

		// depth components are limited to [0,1] range
		if (dst.getFormat().order == tcu::TextureFormat::D || dst.getFormat().order == tcu::TextureFormat::DS)
			depthValueFloatClampCopy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
		else
			tcu::copy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
	}
	else if (target == GL_TEXTURE_CUBE_MAP_NEGATIVE_X ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
			 target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_Y ||
			 target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_Z)
	{
		TextureCube&	texture		= unit.texCubeBinding ? *unit.texCubeBinding : unit.defaultCubeTex;
		tcu::CubeFace	face		= mapGLCubeFace(target);

		RC_IF_ERROR(!texture.hasFace(level, face), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getFace(level, face);

		RC_IF_ERROR(xoffset + width		> dst.getWidth()	||
					yoffset + height	> dst.getHeight()	||
					zoffset + depth		> dst.getDepth(),
					GL_INVALID_VALUE, RC_RET_VOID);

		// depth components are limited to [0,1] range
		if (dst.getFormat().order == tcu::TextureFormat::D || dst.getFormat().order == tcu::TextureFormat::DS)
			depthValueFloatClampCopy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
		else
			tcu::copy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
	}
	else if (target == GL_TEXTURE_3D)
	{
		Texture3D& texture = unit.tex3DBinding ? *unit.tex3DBinding : unit.default3DTex;

		RC_IF_ERROR(!texture.hasLevel(level), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getLevel(level);

		RC_IF_ERROR(xoffset + width		> dst.getWidth()	||
					yoffset + height	> dst.getHeight()	||
					zoffset + depth		> dst.getDepth(),
					GL_INVALID_VALUE, RC_RET_VOID);

		// depth components are limited to [0,1] range
		if (dst.getFormat().order == tcu::TextureFormat::D || dst.getFormat().order == tcu::TextureFormat::DS)
			depthValueFloatClampCopy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
		else
			tcu::copy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
	}
	else if (target == GL_TEXTURE_2D_ARRAY)
	{
		Texture2DArray& texture = unit.tex2DArrayBinding ? *unit.tex2DArrayBinding : unit.default2DArrayTex;

		RC_IF_ERROR(!texture.hasLevel(level), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getLevel(level);

		RC_IF_ERROR(xoffset + width		> dst.getWidth()	||
					yoffset + height	> dst.getHeight()	||
					zoffset + depth		> dst.getDepth(),
					GL_INVALID_VALUE, RC_RET_VOID);

		// depth components are limited to [0,1] range
		if (dst.getFormat().order == tcu::TextureFormat::D || dst.getFormat().order == tcu::TextureFormat::DS)
			depthValueFloatClampCopy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
		else
			tcu::copy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
	}
	else if (target == GL_TEXTURE_CUBE_MAP_ARRAY)
	{
		TextureCubeArray& texture = unit.texCubeArrayBinding ? *unit.texCubeArrayBinding : unit.defaultCubeArrayTex;

		RC_IF_ERROR(!texture.hasLevel(level), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getLevel(level);

		RC_IF_ERROR(xoffset + width		> dst.getWidth()	||
					yoffset + height	> dst.getHeight()	||
					zoffset + depth		> dst.getDepth(),
					GL_INVALID_VALUE, RC_RET_VOID);

		// depth components are limited to [0,1] range
		if (dst.getFormat().order == tcu::TextureFormat::D || dst.getFormat().order == tcu::TextureFormat::DS)
			depthValueFloatClampCopy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
		else
			tcu::copy(tcu::getSubregion(dst, xoffset, yoffset, zoffset, width, height, depth), src);
	}
	else
		RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
}

void ReferenceContext::copyTexImage1D (deUint32 target, int level, deUint32 internalFormat, int x, int y, int width, int border)
{
	TextureUnit&							unit		= m_textureUnits[m_activeTexture];
	TextureFormat							storageFmt;
	rr::MultisampleConstPixelBufferAccess	src			= getReadColorbuffer();

	RC_IF_ERROR(border != 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(width < 0 || level < 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(isEmpty(src), GL_INVALID_OPERATION, RC_RET_VOID);

	// Map storage format.
	storageFmt = mapInternalFormat(internalFormat);
	RC_IF_ERROR(storageFmt.order	== TextureFormat::CHANNELORDER_LAST ||
				storageFmt.type		== TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	if (target == GL_TEXTURE_1D)
	{
		// Validate size and level.
		RC_IF_ERROR(width > m_limits.maxTexture2DSize, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTexture2DSize), GL_INVALID_VALUE, RC_RET_VOID);

		Texture1D* texture = unit.tex1DBinding ? unit.tex1DBinding : &unit.default1DTex;

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasLevel(level), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getLevel(level));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocLevel(level, storageFmt, width);

		// Copy from current framebuffer.
		PixelBufferAccess dst = texture->getLevel(level);
		for (int xo = 0; xo < width; xo++)
		{
			if (!de::inBounds(x+xo, 0, src.raw().getHeight()))
				continue; // Undefined pixel.

			dst.setPixel(rr::resolveMultisamplePixel(src, x+xo, y), xo, 0);
		}
	}
	else
		RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
}

void ReferenceContext::copyTexImage2D (deUint32 target, int level, deUint32 internalFormat, int x, int y, int width, int height, int border)
{
	TextureUnit&							unit		= m_textureUnits[m_activeTexture];
	TextureFormat							storageFmt;
	rr::MultisampleConstPixelBufferAccess	src			= getReadColorbuffer();

	RC_IF_ERROR(border != 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(width < 0 || height < 0 || level < 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(isEmpty(src), GL_INVALID_OPERATION, RC_RET_VOID);

	// Map storage format.
	storageFmt = mapInternalFormat(internalFormat);
	RC_IF_ERROR(storageFmt.order	== TextureFormat::CHANNELORDER_LAST ||
				storageFmt.type		== TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	if (target == GL_TEXTURE_2D)
	{
		// Validate size and level.
		RC_IF_ERROR(width > m_limits.maxTexture2DSize || height > m_limits.maxTexture2DSize, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTexture2DSize), GL_INVALID_VALUE, RC_RET_VOID);

		Texture2D* texture = unit.tex2DBinding ? unit.tex2DBinding : &unit.default2DTex;

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasLevel(level), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getLevel(level));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth()	||
						height		!= dst.getHeight(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocLevel(level, storageFmt, width, height);

		// Copy from current framebuffer.
		PixelBufferAccess dst = texture->getLevel(level);
		for (int yo = 0; yo < height; yo++)
		for (int xo = 0; xo < width; xo++)
		{
			if (!de::inBounds(x+xo, 0, src.raw().getHeight()) || !de::inBounds(y+yo, 0, src.raw().getDepth()))
				continue; // Undefined pixel.

			dst.setPixel(rr::resolveMultisamplePixel(src, x+xo, y+yo), xo, yo);
		}
	}
	else if (target == GL_TEXTURE_CUBE_MAP_NEGATIVE_X ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
			 target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_Y ||
			 target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_Z)
	{
		// Validate size and level.
		RC_IF_ERROR(width != height || width > m_limits.maxTextureCubeSize, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(level > deLog2Floor32(m_limits.maxTextureCubeSize), GL_INVALID_VALUE, RC_RET_VOID);

		TextureCube*	texture	= unit.texCubeBinding ? unit.texCubeBinding : &unit.defaultCubeTex;
		tcu::CubeFace	face	= mapGLCubeFace(target);

		if (texture->isImmutable())
		{
			RC_IF_ERROR(!texture->hasFace(level, face), GL_INVALID_OPERATION, RC_RET_VOID);

			ConstPixelBufferAccess dst(texture->getFace(level, face));
			RC_IF_ERROR(storageFmt	!= dst.getFormat()	||
						width		!= dst.getWidth()	||
						height		!= dst.getHeight(), GL_INVALID_OPERATION, RC_RET_VOID);
		}
		else
			texture->allocFace(level, face, storageFmt, width, height);

		// Copy from current framebuffer.
		PixelBufferAccess dst = texture->getFace(level, face);
		for (int yo = 0; yo < height; yo++)
		for (int xo = 0; xo < width; xo++)
		{
			if (!de::inBounds(x+xo, 0, src.raw().getHeight()) || !de::inBounds(y+yo, 0, src.raw().getDepth()))
				continue; // Undefined pixel.

			dst.setPixel(rr::resolveMultisamplePixel(src, x+xo, y+yo), xo, yo);
		}
	}
	else
		RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
}

void ReferenceContext::copyTexSubImage1D (deUint32 target, int level, int xoffset, int x, int y, int width)
{
	TextureUnit&							unit	= m_textureUnits[m_activeTexture];
	rr::MultisampleConstPixelBufferAccess	src		= getReadColorbuffer();

	RC_IF_ERROR(xoffset < 0,	GL_INVALID_VALUE,		RC_RET_VOID);
	RC_IF_ERROR(width < 0,		GL_INVALID_VALUE,		RC_RET_VOID);
	RC_IF_ERROR(isEmpty(src),	GL_INVALID_OPERATION,	RC_RET_VOID);

	if (target == GL_TEXTURE_1D)
	{
		Texture1D& texture = unit.tex1DBinding ? *unit.tex1DBinding : unit.default1DTex;

		RC_IF_ERROR(!texture.hasLevel(level), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getLevel(level);

		RC_IF_ERROR(xoffset + width > dst.getWidth(), GL_INVALID_VALUE, RC_RET_VOID);

		for (int xo = 0; xo < width; xo++)
		{
			if (!de::inBounds(x+xo, 0, src.raw().getHeight()))
				continue;

			dst.setPixel(rr::resolveMultisamplePixel(src, x+xo, y), xo+xoffset, 0);
		}
	}
	else
		RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
}

void ReferenceContext::copyTexSubImage2D (deUint32 target, int level, int xoffset, int yoffset, int x, int y, int width, int height)
{
	TextureUnit&							unit	= m_textureUnits[m_activeTexture];
	rr::MultisampleConstPixelBufferAccess	src		= getReadColorbuffer();

	RC_IF_ERROR(xoffset < 0 || yoffset < 0,					GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(width < 0 || height < 0,					GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(isEmpty(src),								GL_INVALID_OPERATION, RC_RET_VOID);

	if (target == GL_TEXTURE_2D)
	{
		Texture2D& texture = unit.tex2DBinding ? *unit.tex2DBinding : unit.default2DTex;

		RC_IF_ERROR(!texture.hasLevel(level), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getLevel(level);

		RC_IF_ERROR(xoffset + width		> dst.getWidth() ||
					yoffset + height	> dst.getHeight(),
					GL_INVALID_VALUE, RC_RET_VOID);

		for (int yo = 0; yo < height; yo++)
		for (int xo = 0; xo < width; xo++)
		{
			if (!de::inBounds(x+xo, 0, src.raw().getHeight()) || !de::inBounds(y+yo, 0, src.raw().getDepth()))
				continue;

			dst.setPixel(rr::resolveMultisamplePixel(src, x+xo, y+yo), xo+xoffset, yo+yoffset);
		}
	}
	else if (target == GL_TEXTURE_CUBE_MAP_NEGATIVE_X ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
			 target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_Y ||
			 target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z ||
			 target == GL_TEXTURE_CUBE_MAP_POSITIVE_Z)
	{
		TextureCube&	texture		= unit.texCubeBinding ? *unit.texCubeBinding : unit.defaultCubeTex;
		tcu::CubeFace	face		= mapGLCubeFace(target);

		RC_IF_ERROR(!texture.hasFace(level, face), GL_INVALID_VALUE, RC_RET_VOID);

		PixelBufferAccess dst = texture.getFace(level, face);

		RC_IF_ERROR(xoffset + width		> dst.getWidth() ||
					yoffset + height	> dst.getHeight(),
					GL_INVALID_VALUE, RC_RET_VOID);

		for (int yo = 0; yo < height; yo++)
		for (int xo = 0; xo < width; xo++)
		{
			if (!de::inBounds(x+xo, 0, src.raw().getHeight()) || !de::inBounds(y+yo, 0, src.raw().getDepth()))
				continue;

			dst.setPixel(rr::resolveMultisamplePixel(src, x+xo, y+yo), xo+xoffset, yo+yoffset);
		}
	}
	else
		RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
}

void ReferenceContext::copyTexSubImage3D (deUint32 target, int level, int xoffset, int yoffset, int zoffset, int x, int y, int width, int height)
{
	DE_UNREF(target && level && xoffset && yoffset && zoffset && x && y && width && height);
	DE_ASSERT(false);
}

void ReferenceContext::texStorage2D (deUint32 target, int levels, deUint32 internalFormat, int width, int height)
{
	TextureUnit&		unit		= m_textureUnits[m_activeTexture];
	TextureFormat		storageFmt;

	RC_IF_ERROR(width <= 0 || height <= 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(!de::inRange(levels, 1, (int)deLog2Floor32(de::max(width, height))+1), GL_INVALID_VALUE, RC_RET_VOID);

	// Map storage format.
	storageFmt = mapInternalFormat(internalFormat);
	RC_IF_ERROR(storageFmt.order	== TextureFormat::CHANNELORDER_LAST ||
				storageFmt.type		== TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	if (target == GL_TEXTURE_2D)
	{
		Texture2D& texture = unit.tex2DBinding ? *unit.tex2DBinding : unit.default2DTex;

		RC_IF_ERROR(width > m_limits.maxTexture2DSize || height >= m_limits.maxTexture2DSize, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(texture.isImmutable(), GL_INVALID_OPERATION, RC_RET_VOID);

		texture.clearLevels();
		texture.setImmutable();

		for (int level = 0; level < levels; level++)
		{
			int levelW = de::max(1, width >> level);
			int levelH = de::max(1, height >> level);

			texture.allocLevel(level, storageFmt, levelW, levelH);
		}
	}
	else if (target == GL_TEXTURE_CUBE_MAP)
	{
		TextureCube& texture = unit.texCubeBinding ? *unit.texCubeBinding : unit.defaultCubeTex;

		RC_IF_ERROR(width > m_limits.maxTextureCubeSize || height > m_limits.maxTextureCubeSize, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(texture.isImmutable(), GL_INVALID_OPERATION, RC_RET_VOID);

		texture.clearLevels();
		texture.setImmutable();

		for (int level = 0; level < levels; level++)
		{
			int levelW = de::max(1, width >> level);
			int levelH = de::max(1, height >> level);

			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
				texture.allocFace(level, (tcu::CubeFace)face, storageFmt, levelW, levelH);
		}
	}
	else
		RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
}

void ReferenceContext::texStorage3D (deUint32 target, int levels, deUint32 internalFormat, int width, int height, int depth)
{
	TextureUnit&		unit		= m_textureUnits[m_activeTexture];
	TextureFormat		storageFmt;

	RC_IF_ERROR(width <= 0 || height <= 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(!de::inRange(levels, 1, (int)deLog2Floor32(de::max(width, height))+1), GL_INVALID_VALUE, RC_RET_VOID);

	// Map storage format.
	storageFmt = mapInternalFormat(internalFormat);
	RC_IF_ERROR(storageFmt.order	== TextureFormat::CHANNELORDER_LAST ||
				storageFmt.type		== TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	if (target == GL_TEXTURE_2D_ARRAY)
	{
		Texture2DArray& texture = unit.tex2DArrayBinding ? *unit.tex2DArrayBinding : unit.default2DArrayTex;

		RC_IF_ERROR(width	>	m_limits.maxTexture2DSize	||
					height	>=	m_limits.maxTexture2DSize	||
					depth	>=	m_limits.maxTexture2DArrayLayers, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(texture.isImmutable(), GL_INVALID_OPERATION, RC_RET_VOID);

		texture.clearLevels();
		texture.setImmutable();

		for (int level = 0; level < levels; level++)
		{
			int levelW = de::max(1, width >> level);
			int levelH = de::max(1, height >> level);

			texture.allocLevel(level, storageFmt, levelW, levelH, depth);
		}
	}
	else if (target == GL_TEXTURE_3D)
	{
		Texture3D& texture = unit.tex3DBinding ? *unit.tex3DBinding : unit.default3DTex;

		RC_IF_ERROR(width	> m_limits.maxTexture3DSize	||
					height	> m_limits.maxTexture3DSize	||
					depth	> m_limits.maxTexture3DSize, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(texture.isImmutable(), GL_INVALID_OPERATION, RC_RET_VOID);

		texture.clearLevels();
		texture.setImmutable();

		for (int level = 0; level < levels; level++)
		{
			int levelW = de::max(1, width		>> level);
			int levelH = de::max(1, height	>> level);
			int levelD = de::max(1, depth		>> level);

			texture.allocLevel(level, storageFmt, levelW, levelH, levelD);
		}
	}
	else if (target == GL_TEXTURE_CUBE_MAP_ARRAY)
	{
		TextureCubeArray& texture = unit.texCubeArrayBinding ? *unit.texCubeArrayBinding : unit.defaultCubeArrayTex;

		RC_IF_ERROR(width		!=	height								||
					depth % 6	!= 0									||
					width		>	m_limits.maxTexture2DSize			||
					depth		>=	m_limits.maxTexture2DArrayLayers, GL_INVALID_VALUE, RC_RET_VOID);
		RC_IF_ERROR(texture.isImmutable(), GL_INVALID_OPERATION, RC_RET_VOID);

		texture.clearLevels();
		texture.setImmutable();

		for (int level = 0; level < levels; level++)
		{
			int levelW = de::max(1, width >> level);
			int levelH = de::max(1, height >> level);

			texture.allocLevel(level, storageFmt, levelW, levelH, depth);
		}
	}
	else
		RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
}

// \todo [2014-02-19 pyry] Duplicated with code in gluTextureUtil.hpp

static inline tcu::Sampler::WrapMode mapGLWrapMode (int value)
{
	switch (value)
	{
		case GL_CLAMP_TO_EDGE:		return tcu::Sampler::CLAMP_TO_EDGE;
		case GL_REPEAT:				return tcu::Sampler::REPEAT_GL;
		case GL_MIRRORED_REPEAT:	return tcu::Sampler::MIRRORED_REPEAT_GL;
		default:					return tcu::Sampler::WRAPMODE_LAST;
	}
}

static inline tcu::Sampler::FilterMode mapGLFilterMode (int value)
{
	switch (value)
	{
		case GL_NEAREST:				return tcu::Sampler::NEAREST;
		case GL_LINEAR:					return tcu::Sampler::LINEAR;
		case GL_NEAREST_MIPMAP_NEAREST:	return tcu::Sampler::NEAREST_MIPMAP_NEAREST;
		case GL_NEAREST_MIPMAP_LINEAR:	return tcu::Sampler::NEAREST_MIPMAP_LINEAR;
		case GL_LINEAR_MIPMAP_NEAREST:	return tcu::Sampler::LINEAR_MIPMAP_NEAREST;
		case GL_LINEAR_MIPMAP_LINEAR:	return tcu::Sampler::LINEAR_MIPMAP_LINEAR;
		default:						return tcu::Sampler::FILTERMODE_LAST;
	}
}

void ReferenceContext::texParameteri (deUint32 target, deUint32 pname, int value)
{
	TextureUnit&	unit		= m_textureUnits[m_activeTexture];
	Texture*		texture		= DE_NULL;

	switch (target)
	{
		case GL_TEXTURE_1D:				texture = unit.tex1DBinding			? unit.tex1DBinding			: &unit.default1DTex;			break;
		case GL_TEXTURE_2D:				texture = unit.tex2DBinding			? unit.tex2DBinding			: &unit.default2DTex;			break;
		case GL_TEXTURE_CUBE_MAP:		texture = unit.texCubeBinding		? unit.texCubeBinding		: &unit.defaultCubeTex;			break;
		case GL_TEXTURE_2D_ARRAY:		texture = unit.tex2DArrayBinding	? unit.tex2DArrayBinding	: &unit.default2DArrayTex;		break;
		case GL_TEXTURE_3D:				texture = unit.tex3DBinding			? unit.tex3DBinding			: &unit.default3DTex;			break;
		case GL_TEXTURE_CUBE_MAP_ARRAY:	texture = unit.texCubeArrayBinding	? unit.texCubeArrayBinding	: &unit.defaultCubeArrayTex;	break;

		default:					RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
	}

	switch (pname)
	{
		case GL_TEXTURE_WRAP_S:
		{
			tcu::Sampler::WrapMode wrapS = mapGLWrapMode(value);
			RC_IF_ERROR(wrapS == tcu::Sampler::WRAPMODE_LAST, GL_INVALID_VALUE, RC_RET_VOID);
			texture->getSampler().wrapS = wrapS;
			break;
		}

		case GL_TEXTURE_WRAP_T:
		{
			tcu::Sampler::WrapMode wrapT = mapGLWrapMode(value);
			RC_IF_ERROR(wrapT == tcu::Sampler::WRAPMODE_LAST, GL_INVALID_VALUE, RC_RET_VOID);
			texture->getSampler().wrapT = wrapT;
			break;
		}

		case GL_TEXTURE_WRAP_R:
		{
			tcu::Sampler::WrapMode wrapR = mapGLWrapMode(value);
			RC_IF_ERROR(wrapR == tcu::Sampler::WRAPMODE_LAST, GL_INVALID_VALUE, RC_RET_VOID);
			texture->getSampler().wrapR = wrapR;
			break;
		}

		case GL_TEXTURE_MIN_FILTER:
		{
			tcu::Sampler::FilterMode minMode = mapGLFilterMode(value);
			RC_IF_ERROR(minMode == tcu::Sampler::FILTERMODE_LAST, GL_INVALID_VALUE, RC_RET_VOID);
			texture->getSampler().minFilter = minMode;
			break;
		}

		case GL_TEXTURE_MAG_FILTER:
		{
			tcu::Sampler::FilterMode magMode = mapGLFilterMode(value);
			RC_IF_ERROR(magMode != tcu::Sampler::LINEAR && magMode != tcu::Sampler::NEAREST,
						GL_INVALID_VALUE, RC_RET_VOID);
			texture->getSampler().magFilter = magMode;
			break;
		}

		case GL_TEXTURE_MAX_LEVEL:
		{
			RC_IF_ERROR(value < 0, GL_INVALID_VALUE, RC_RET_VOID);
			texture->setMaxLevel(value);
			break;
		}

		default:
			RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
	}
}

static inline Framebuffer::AttachmentPoint mapGLAttachmentPoint (deUint32 attachment)
{
	switch (attachment)
	{
		case GL_COLOR_ATTACHMENT0:	return Framebuffer::ATTACHMENTPOINT_COLOR0;
		case GL_DEPTH_ATTACHMENT:	return Framebuffer::ATTACHMENTPOINT_DEPTH;
		case GL_STENCIL_ATTACHMENT:	return Framebuffer::ATTACHMENTPOINT_STENCIL;
		default:					return Framebuffer::ATTACHMENTPOINT_LAST;
	}
}

static inline Framebuffer::TexTarget mapGLFboTexTarget (deUint32 target)
{
	switch (target)
	{
		case GL_TEXTURE_2D:						return Framebuffer::TEXTARGET_2D;
		case GL_TEXTURE_CUBE_MAP_POSITIVE_X:	return Framebuffer::TEXTARGET_CUBE_MAP_POSITIVE_X;
		case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:	return Framebuffer::TEXTARGET_CUBE_MAP_POSITIVE_Y;
		case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:	return Framebuffer::TEXTARGET_CUBE_MAP_POSITIVE_Z;
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:	return Framebuffer::TEXTARGET_CUBE_MAP_NEGATIVE_X;
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:	return Framebuffer::TEXTARGET_CUBE_MAP_NEGATIVE_Y;
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:	return Framebuffer::TEXTARGET_CUBE_MAP_NEGATIVE_Z;
		default:								return Framebuffer::TEXTARGET_LAST;
	}
}

void ReferenceContext::acquireFboAttachmentReference (const Framebuffer::Attachment& attachment)
{
	switch (attachment.type)
	{
		case Framebuffer::ATTACHMENTTYPE_TEXTURE:
		{
			TCU_CHECK(attachment.name != 0);
			Texture* texture = m_textures.find(attachment.name);
			TCU_CHECK(texture);
			m_textures.acquireReference(texture);
			break;
		}

		case Framebuffer::ATTACHMENTTYPE_RENDERBUFFER:
		{
			TCU_CHECK(attachment.name != 0);
			Renderbuffer* rbo = m_renderbuffers.find(attachment.name);
			TCU_CHECK(rbo);
			m_renderbuffers.acquireReference(rbo);
			break;
		}

		default:
			break; // Silently ignore
	}
}

void ReferenceContext::releaseFboAttachmentReference (const Framebuffer::Attachment& attachment)
{
	switch (attachment.type)
	{
		case Framebuffer::ATTACHMENTTYPE_TEXTURE:
		{
			TCU_CHECK(attachment.name != 0);
			Texture* texture = m_textures.find(attachment.name);
			TCU_CHECK(texture);
			m_textures.releaseReference(texture);
			break;
		}

		case Framebuffer::ATTACHMENTTYPE_RENDERBUFFER:
		{
			TCU_CHECK(attachment.name != 0);
			Renderbuffer* rbo = m_renderbuffers.find(attachment.name);
			TCU_CHECK(rbo);
			m_renderbuffers.releaseReference(rbo);
			break;
		}

		default:
			break; // Silently ignore
	}
}

void ReferenceContext::framebufferTexture2D (deUint32 target, deUint32 attachment, deUint32 textarget, deUint32 texture, int level)
{
	if (attachment == GL_DEPTH_STENCIL_ATTACHMENT)
	{
		// Attach to both depth and stencil.
		framebufferTexture2D(target, GL_DEPTH_ATTACHMENT,	textarget, texture, level);
		framebufferTexture2D(target, GL_STENCIL_ATTACHMENT,	textarget, texture, level);
	}
	else
	{
		Framebuffer::AttachmentPoint	point			= mapGLAttachmentPoint(attachment);
		Texture*						texObj			= DE_NULL;
		Framebuffer::TexTarget			fboTexTarget	= mapGLFboTexTarget(textarget);

		RC_IF_ERROR(target != GL_FRAMEBUFFER		&&
					target != GL_DRAW_FRAMEBUFFER	&&
					target != GL_READ_FRAMEBUFFER,				GL_INVALID_ENUM,		RC_RET_VOID);
		RC_IF_ERROR(point == Framebuffer::ATTACHMENTPOINT_LAST,	GL_INVALID_ENUM,		RC_RET_VOID);

		// Select binding point.
		rc::Framebuffer* framebufferBinding = (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) ? m_drawFramebufferBinding : m_readFramebufferBinding;
		RC_IF_ERROR(!framebufferBinding, GL_INVALID_OPERATION, RC_RET_VOID);

		// If framebuffer object is bound for both reading and writing then we need to acquire/release multiple references.
		int bindingRefCount = (framebufferBinding == m_drawFramebufferBinding ? 1 : 0)
							+ (framebufferBinding == m_readFramebufferBinding ? 1 : 0);

		if (texture != 0)
		{
			texObj = m_textures.find(texture);

			RC_IF_ERROR(!texObj,		GL_INVALID_OPERATION,	RC_RET_VOID);
			RC_IF_ERROR(level != 0,		GL_INVALID_VALUE,		RC_RET_VOID); // \todo [2012-03-19 pyry] We should allow other levels as well.

			if (texObj->getType() == Texture::TYPE_2D)
				RC_IF_ERROR(fboTexTarget != Framebuffer::TEXTARGET_2D, GL_INVALID_OPERATION, RC_RET_VOID);
			else
			{
				TCU_CHECK(texObj->getType() == Texture::TYPE_CUBE_MAP);
				if (!deInRange32(fboTexTarget, Framebuffer::TEXTARGET_CUBE_MAP_POSITIVE_X, Framebuffer::TEXTARGET_CUBE_MAP_NEGATIVE_Z))
					RC_ERROR_RET(GL_INVALID_OPERATION, RC_RET_VOID);
			}
		}

		Framebuffer::Attachment& fboAttachment = framebufferBinding->getAttachment(point);
		for (int ndx = 0; ndx < bindingRefCount; ndx++)
			releaseFboAttachmentReference(fboAttachment);
		fboAttachment = Framebuffer::Attachment();

		if (texObj)
		{
			fboAttachment.type			= Framebuffer::ATTACHMENTTYPE_TEXTURE;
			fboAttachment.name			= texObj->getName();
			fboAttachment.texTarget		= fboTexTarget;
			fboAttachment.level			= level;

			for (int ndx = 0; ndx < bindingRefCount; ndx++)
				acquireFboAttachmentReference(fboAttachment);
		}
	}
}

void ReferenceContext::framebufferTextureLayer (deUint32 target, deUint32 attachment, deUint32 texture, int level, int layer)
{
	if (attachment == GL_DEPTH_STENCIL_ATTACHMENT)
	{
		// Attach to both depth and stencil.
		framebufferTextureLayer(target, GL_DEPTH_ATTACHMENT,	texture, level, layer);
		framebufferTextureLayer(target, GL_STENCIL_ATTACHMENT,	texture, level, layer);
	}
	else
	{
		Framebuffer::AttachmentPoint	point			= mapGLAttachmentPoint(attachment);
		Texture*						texObj			= DE_NULL;

		RC_IF_ERROR(target != GL_FRAMEBUFFER		&&
					target != GL_DRAW_FRAMEBUFFER	&&
					target != GL_READ_FRAMEBUFFER,				GL_INVALID_ENUM,		RC_RET_VOID);
		RC_IF_ERROR(point == Framebuffer::ATTACHMENTPOINT_LAST,	GL_INVALID_ENUM,		RC_RET_VOID);

		// Select binding point.
		rc::Framebuffer* framebufferBinding = (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) ? m_drawFramebufferBinding : m_readFramebufferBinding;
		RC_IF_ERROR(!framebufferBinding, GL_INVALID_OPERATION, RC_RET_VOID);

		// If framebuffer object is bound for both reading and writing then we need to acquire/release multiple references.
		int bindingRefCount = (framebufferBinding == m_drawFramebufferBinding ? 1 : 0)
							+ (framebufferBinding == m_readFramebufferBinding ? 1 : 0);

		if (texture != 0)
		{
			texObj = m_textures.find(texture);

			RC_IF_ERROR(!texObj,		GL_INVALID_OPERATION,	RC_RET_VOID);
			RC_IF_ERROR(level != 0,		GL_INVALID_VALUE,		RC_RET_VOID); // \todo [2012-03-19 pyry] We should allow other levels as well.

			RC_IF_ERROR(texObj->getType() != Texture::TYPE_2D_ARRAY			&&
						texObj->getType() != Texture::TYPE_3D				&&
						texObj->getType() != Texture::TYPE_CUBE_MAP_ARRAY,				GL_INVALID_OPERATION,	RC_RET_VOID);

			if (texObj->getType() == Texture::TYPE_2D_ARRAY || texObj->getType() == Texture::TYPE_CUBE_MAP_ARRAY)
			{
				RC_IF_ERROR((layer < 0) || (layer >= GL_MAX_ARRAY_TEXTURE_LAYERS),		GL_INVALID_VALUE,		RC_RET_VOID);
				RC_IF_ERROR((level < 0) || (level > deFloatLog2(GL_MAX_TEXTURE_SIZE)),	GL_INVALID_VALUE,		RC_RET_VOID);
			}
			else if	(texObj->getType() == Texture::TYPE_3D)
			{
				RC_IF_ERROR((layer < 0) || (layer >= GL_MAX_3D_TEXTURE_SIZE),				GL_INVALID_VALUE,		RC_RET_VOID);
				RC_IF_ERROR((level < 0) || (level > deFloatLog2(GL_MAX_3D_TEXTURE_SIZE)),	GL_INVALID_VALUE,		RC_RET_VOID);
			}
		}

		Framebuffer::Attachment& fboAttachment = framebufferBinding->getAttachment(point);
		for (int ndx = 0; ndx < bindingRefCount; ndx++)
			releaseFboAttachmentReference(fboAttachment);
		fboAttachment = Framebuffer::Attachment();

		if (texObj)
		{
			fboAttachment.type			= Framebuffer::ATTACHMENTTYPE_TEXTURE;
			fboAttachment.name			= texObj->getName();
			fboAttachment.texTarget		= texLayeredTypeToTarget(texObj->getType());
			fboAttachment.level			= level;
			fboAttachment.layer			= layer;

			DE_ASSERT(fboAttachment.texTarget != Framebuffer::TEXTARGET_LAST);

			for (int ndx = 0; ndx < bindingRefCount; ndx++)
				acquireFboAttachmentReference(fboAttachment);
		}
	}
}

void ReferenceContext::framebufferRenderbuffer (deUint32 target, deUint32 attachment, deUint32 renderbuffertarget, deUint32 renderbuffer)
{
	if (attachment == GL_DEPTH_STENCIL_ATTACHMENT)
	{
		// Attach both to depth and stencil.
		framebufferRenderbuffer(target, GL_DEPTH_ATTACHMENT,	renderbuffertarget, renderbuffer);
		framebufferRenderbuffer(target, GL_STENCIL_ATTACHMENT,	renderbuffertarget, renderbuffer);
	}
	else
	{
		Framebuffer::AttachmentPoint	point			= mapGLAttachmentPoint(attachment);
		Renderbuffer*					rbo				= DE_NULL;

		RC_IF_ERROR(target != GL_FRAMEBUFFER		&&
					target != GL_DRAW_FRAMEBUFFER	&&
					target != GL_READ_FRAMEBUFFER,				GL_INVALID_ENUM,		RC_RET_VOID);
		RC_IF_ERROR(point == Framebuffer::ATTACHMENTPOINT_LAST,	GL_INVALID_ENUM,		RC_RET_VOID);

		// Select binding point.
		rc::Framebuffer* framebufferBinding = (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) ? m_drawFramebufferBinding : m_readFramebufferBinding;
		RC_IF_ERROR(!framebufferBinding, GL_INVALID_OPERATION, RC_RET_VOID);

		// If framebuffer object is bound for both reading and writing then we need to acquire/release multiple references.
		int bindingRefCount = (framebufferBinding == m_drawFramebufferBinding ? 1 : 0)
							+ (framebufferBinding == m_readFramebufferBinding ? 1 : 0);

		if (renderbuffer != 0)
		{
			rbo = m_renderbuffers.find(renderbuffer);

			RC_IF_ERROR(renderbuffertarget != GL_RENDERBUFFER,	GL_INVALID_ENUM,		RC_RET_VOID);
			RC_IF_ERROR(!rbo,									GL_INVALID_OPERATION,	RC_RET_VOID);
		}

		Framebuffer::Attachment& fboAttachment = framebufferBinding->getAttachment(point);
		for (int ndx = 0; ndx < bindingRefCount; ndx++)
			releaseFboAttachmentReference(fboAttachment);
		fboAttachment = Framebuffer::Attachment();

		if (rbo)
		{
			fboAttachment.type	= Framebuffer::ATTACHMENTTYPE_RENDERBUFFER;
			fboAttachment.name	= rbo->getName();

			for (int ndx = 0; ndx < bindingRefCount; ndx++)
				acquireFboAttachmentReference(fboAttachment);
		}
	}
}

deUint32 ReferenceContext::checkFramebufferStatus (deUint32 target)
{
	RC_IF_ERROR(target != GL_FRAMEBUFFER		&&
				target != GL_DRAW_FRAMEBUFFER	&&
				target != GL_READ_FRAMEBUFFER, GL_INVALID_ENUM, 0);

	// Select binding point.
	rc::Framebuffer* framebufferBinding = (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) ? m_drawFramebufferBinding : m_readFramebufferBinding;

	// Default framebuffer is always complete.
	if (!framebufferBinding)
		return GL_FRAMEBUFFER_COMPLETE;

	int		width				= -1;
	int		height				= -1;
	bool	hasAttachment		= false;
	bool	attachmentComplete	= true;
	bool	dimensionsOk		= true;

	for (int point = 0; point < Framebuffer::ATTACHMENTPOINT_LAST; point++)
	{
		const Framebuffer::Attachment&	attachment			= framebufferBinding->getAttachment((Framebuffer::AttachmentPoint)point);
		int								attachmentWidth		= 0;
		int								attachmentHeight	= 0;
		tcu::TextureFormat				attachmentFormat;

		if (attachment.type == Framebuffer::ATTACHMENTTYPE_TEXTURE)
		{
			const Texture*					texture	= m_textures.find(attachment.name);
			tcu::ConstPixelBufferAccess		level;
			TCU_CHECK(texture);

			if (attachment.texTarget == Framebuffer::TEXTARGET_2D)
			{
				DE_ASSERT(texture->getType() == Texture::TYPE_2D);
				const Texture2D* tex2D = static_cast<const Texture2D*>(texture);

				if (tex2D->hasLevel(attachment.level))
					level = tex2D->getLevel(attachment.level);
			}
			else if (deInRange32(attachment.texTarget, Framebuffer::TEXTARGET_CUBE_MAP_POSITIVE_X,
													   Framebuffer::TEXTARGET_CUBE_MAP_NEGATIVE_Z))
			{
				DE_ASSERT(texture->getType() == Texture::TYPE_CUBE_MAP);

				const TextureCube*	texCube	= static_cast<const TextureCube*>(texture);
				const tcu::CubeFace	face	= texTargetToFace(attachment.texTarget);
				TCU_CHECK(de::inBounds<int>(face, 0, tcu::CUBEFACE_LAST));

				if (texCube->hasFace(attachment.level, face))
					level = texCube->getFace(attachment.level, face);
			}
			else if (attachment.texTarget == Framebuffer::TEXTARGET_2D_ARRAY)
			{
				DE_ASSERT(texture->getType() == Texture::TYPE_2D_ARRAY);
				const Texture2DArray* tex2DArr = static_cast<const Texture2DArray*>(texture);

				if (tex2DArr->hasLevel(attachment.level))
					level = tex2DArr->getLevel(attachment.level); // \note Slice doesn't matter here.
			}
			else if (attachment.texTarget == Framebuffer::TEXTARGET_3D)
			{
				DE_ASSERT(texture->getType() == Texture::TYPE_3D);
				const Texture3D* tex3D = static_cast<const Texture3D*>(texture);

				if (tex3D->hasLevel(attachment.level))
					level = tex3D->getLevel(attachment.level); // \note Slice doesn't matter here.
			}
			else if (attachment.texTarget == Framebuffer::TEXTARGET_CUBE_MAP_ARRAY)
			{
				DE_ASSERT(texture->getType() == Texture::TYPE_CUBE_MAP_ARRAY);
				const TextureCubeArray* texCubeArr = static_cast<const TextureCubeArray*>(texture);

				if (texCubeArr->hasLevel(attachment.level))
					level = texCubeArr->getLevel(attachment.level); // \note Slice doesn't matter here.
			}
			else
				TCU_FAIL("Framebuffer attached to a texture but no valid target specified");

			attachmentWidth		= level.getWidth();
			attachmentHeight	= level.getHeight();
			attachmentFormat	= level.getFormat();
		}
		else if (attachment.type == Framebuffer::ATTACHMENTTYPE_RENDERBUFFER)
		{
			const Renderbuffer* renderbuffer = m_renderbuffers.find(attachment.name);
			TCU_CHECK(renderbuffer);

			attachmentWidth		= renderbuffer->getWidth();
			attachmentHeight	= renderbuffer->getHeight();
			attachmentFormat	= renderbuffer->getFormat();
		}
		else
		{
			TCU_CHECK(attachment.type == Framebuffer::ATTACHMENTTYPE_LAST);
			continue; // Skip rest of checks.
		}

		if (!hasAttachment && attachmentWidth > 0 && attachmentHeight > 0)
		{
			width			= attachmentWidth;
			height			= attachmentHeight;
			hasAttachment	= true;
		}
		else if (attachmentWidth != width || attachmentHeight != height)
			dimensionsOk = false;

		// Validate attachment point compatibility.
		switch (attachmentFormat.order)
		{
			case TextureFormat::R:
			case TextureFormat::RG:
			case TextureFormat::RGB:
			case TextureFormat::RGBA:
			case TextureFormat::sRGB:
			case TextureFormat::sRGBA:
				if (point != Framebuffer::ATTACHMENTPOINT_COLOR0)
					attachmentComplete = false;
				break;

			case TextureFormat::D:
				if (point != Framebuffer::ATTACHMENTPOINT_DEPTH)
					attachmentComplete = false;
				break;

			case TextureFormat::S:
				if (point != Framebuffer::ATTACHMENTPOINT_STENCIL)
					attachmentComplete = false;
				break;

			case TextureFormat::DS:
				if (point != Framebuffer::ATTACHMENTPOINT_DEPTH &&
					point != Framebuffer::ATTACHMENTPOINT_STENCIL)
					attachmentComplete = false;
				break;

			default:
				TCU_FAIL("Unsupported attachment channel order");
		}
	}

	if (!attachmentComplete)
		return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
	else if (!hasAttachment)
		return GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
	else if (!dimensionsOk)
		return GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS;
	else
		return GL_FRAMEBUFFER_COMPLETE;
}

void ReferenceContext::getFramebufferAttachmentParameteriv (deUint32 target, deUint32 attachment, deUint32 pname, int* params)
{
	DE_UNREF(target && attachment && pname && params);
	TCU_CHECK(false); // \todo [pyry] Implement
}

void ReferenceContext::renderbufferStorage (deUint32 target, deUint32 internalformat, int width, int height)
{
	TextureFormat format = glu::mapGLInternalFormat(internalformat);

	RC_IF_ERROR(target != GL_RENDERBUFFER, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(!m_renderbufferBinding, GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(!deInRange32(width, 0, m_limits.maxRenderbufferSize) ||
				!deInRange32(height, 0, m_limits.maxRenderbufferSize),
				GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(format.order == TextureFormat::CHANNELORDER_LAST ||
				format.type == TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	m_renderbufferBinding->setStorage(format, (int)width, (int)height);
}

void ReferenceContext::renderbufferStorageMultisample (deUint32 target, int samples, deUint32 internalFormat, int width, int height)
{
	// \todo [2012-04-07 pyry] Implement MSAA support.
	DE_UNREF(samples);
	renderbufferStorage(target, internalFormat, width, height);
}

tcu::PixelBufferAccess ReferenceContext::getFboAttachment (const rc::Framebuffer& framebuffer, rc::Framebuffer::AttachmentPoint point)
{
	const Framebuffer::Attachment& attachment = framebuffer.getAttachment(point);

	switch (attachment.type)
	{
		case Framebuffer::ATTACHMENTTYPE_TEXTURE:
		{
			Texture* texture = m_textures.find(attachment.name);
			TCU_CHECK(texture);

			if (texture->getType() == Texture::TYPE_2D)
				return dynamic_cast<Texture2D*>(texture)->getLevel(attachment.level);
			else if (texture->getType() == Texture::TYPE_CUBE_MAP)
				return dynamic_cast<TextureCube*>(texture)->getFace(attachment.level, texTargetToFace(attachment.texTarget));
			else if (texture->getType() == Texture::TYPE_2D_ARRAY	||
					 texture->getType() == Texture::TYPE_3D			||
					 texture->getType() == Texture::TYPE_CUBE_MAP_ARRAY)
			{
				tcu::PixelBufferAccess level;

				if (texture->getType() == Texture::TYPE_2D_ARRAY)
					level = dynamic_cast<Texture2DArray*>(texture)->getLevel(attachment.level);
				else if (texture->getType() == Texture::TYPE_3D)
					level = dynamic_cast<Texture3D*>(texture)->getLevel(attachment.level);
				else if (texture->getType() == Texture::TYPE_CUBE_MAP_ARRAY)
					level = dynamic_cast<TextureCubeArray*>(texture)->getLevel(attachment.level);

				void* layerData = static_cast<deUint8*>(level.getDataPtr()) + level.getSlicePitch() * attachment.layer;

				return tcu::PixelBufferAccess(level.getFormat(), level.getWidth(), level.getHeight(), 1, level.getRowPitch(), 0, layerData);
			}
			else
				return nullAccess();
		}

		case Framebuffer::ATTACHMENTTYPE_RENDERBUFFER:
		{
			Renderbuffer* rbo = m_renderbuffers.find(attachment.name);
			TCU_CHECK(rbo);

			return rbo->getAccess();
		}

		default:
			return nullAccess();
	}
}

const Texture2D& ReferenceContext::getTexture2D (int unitNdx) const
{
	const TextureUnit& unit = m_textureUnits[unitNdx];
	return unit.tex2DBinding ? *unit.tex2DBinding : unit.default2DTex;
}

const TextureCube& ReferenceContext::getTextureCube (int unitNdx) const
{
	const TextureUnit& unit = m_textureUnits[unitNdx];
	return unit.texCubeBinding ? *unit.texCubeBinding : unit.defaultCubeTex;
}

static bool isValidBufferTarget (deUint32 target)
{
	switch (target)
	{
		case GL_ARRAY_BUFFER:
		case GL_COPY_READ_BUFFER:
		case GL_COPY_WRITE_BUFFER:
		case GL_DRAW_INDIRECT_BUFFER:
		case GL_ELEMENT_ARRAY_BUFFER:
		case GL_PIXEL_PACK_BUFFER:
		case GL_PIXEL_UNPACK_BUFFER:
		case GL_TRANSFORM_FEEDBACK_BUFFER:
		case GL_UNIFORM_BUFFER:
			return true;

		default:
			return false;
	}
}

void ReferenceContext::setBufferBinding (deUint32 target, DataBuffer* buffer)
{
	DataBuffer** bindingPoint = DE_NULL;
	VertexArray* vertexArrayObject = (m_vertexArrayBinding) ? (m_vertexArrayBinding) : (&m_clientVertexArray);

	switch (target)
	{
		case GL_ARRAY_BUFFER:				bindingPoint = &m_arrayBufferBinding;								break;
		case GL_COPY_READ_BUFFER:			bindingPoint = &m_copyReadBufferBinding;							break;
		case GL_COPY_WRITE_BUFFER:			bindingPoint = &m_copyWriteBufferBinding;							break;
		case GL_DRAW_INDIRECT_BUFFER:		bindingPoint = &m_drawIndirectBufferBinding;						break;
		case GL_ELEMENT_ARRAY_BUFFER:		bindingPoint = &vertexArrayObject->m_elementArrayBufferBinding;		break;
		case GL_PIXEL_PACK_BUFFER:			bindingPoint = &m_pixelPackBufferBinding;							break;
		case GL_PIXEL_UNPACK_BUFFER:		bindingPoint = &m_pixelUnpackBufferBinding;							break;
		case GL_TRANSFORM_FEEDBACK_BUFFER:	bindingPoint = &m_transformFeedbackBufferBinding;					break;
		case GL_UNIFORM_BUFFER:				bindingPoint = &m_uniformBufferBinding;								break;
		default:
			DE_ASSERT(false);
			return;
	}

	if (*bindingPoint)
	{
		m_buffers.releaseReference(*bindingPoint);
		*bindingPoint = DE_NULL;
	}

	if (buffer)
		m_buffers.acquireReference(buffer);

	*bindingPoint = buffer;
}

DataBuffer* ReferenceContext::getBufferBinding (deUint32 target) const
{
	const VertexArray* vertexArrayObject = (m_vertexArrayBinding) ? (m_vertexArrayBinding) : (&m_clientVertexArray);

	switch (target)
	{
		case GL_ARRAY_BUFFER:				return m_arrayBufferBinding;
		case GL_COPY_READ_BUFFER:			return m_copyReadBufferBinding;
		case GL_COPY_WRITE_BUFFER:			return m_copyWriteBufferBinding;
		case GL_DRAW_INDIRECT_BUFFER:		return m_drawIndirectBufferBinding;
		case GL_ELEMENT_ARRAY_BUFFER:		return vertexArrayObject->m_elementArrayBufferBinding;
		case GL_PIXEL_PACK_BUFFER:			return m_pixelPackBufferBinding;
		case GL_PIXEL_UNPACK_BUFFER:		return m_pixelUnpackBufferBinding;
		case GL_TRANSFORM_FEEDBACK_BUFFER:	return m_transformFeedbackBufferBinding;
		case GL_UNIFORM_BUFFER:				return m_uniformBufferBinding;
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

void ReferenceContext::bindBuffer (deUint32 target, deUint32 buffer)
{
	RC_IF_ERROR(!isValidBufferTarget(target), GL_INVALID_ENUM, RC_RET_VOID);

	rc::DataBuffer*	bufObj	= DE_NULL;

	if (buffer != 0)
	{
		bufObj = m_buffers.find(buffer);
		if (!bufObj)
		{
			bufObj = new DataBuffer(buffer);
			m_buffers.insert(bufObj);
		}
	}

	setBufferBinding(target, bufObj);
}

void ReferenceContext::genBuffers (int numBuffers, deUint32* buffers)
{
	RC_IF_ERROR(!buffers, GL_INVALID_VALUE, RC_RET_VOID);

	for (int ndx = 0; ndx < numBuffers; ndx++)
		buffers[ndx] = m_buffers.allocateName();
}

void ReferenceContext::deleteBuffers (int numBuffers, const deUint32* buffers)
{
	RC_IF_ERROR(numBuffers < 0, GL_INVALID_VALUE, RC_RET_VOID);

	for (int ndx = 0; ndx < numBuffers; ndx++)
	{
		deUint32	buffer	= buffers[ndx];
		DataBuffer*	bufObj	= DE_NULL;

		if (buffer == 0)
			continue;

		bufObj = m_buffers.find(buffer);

		if (bufObj)
			deleteBuffer(bufObj);
	}
}

void ReferenceContext::deleteBuffer (DataBuffer* buffer)
{
	static const deUint32 bindingPoints[] =
	{
		GL_ARRAY_BUFFER,
		GL_COPY_READ_BUFFER,
		GL_COPY_WRITE_BUFFER,
		GL_DRAW_INDIRECT_BUFFER,
		GL_ELEMENT_ARRAY_BUFFER,
		GL_PIXEL_PACK_BUFFER,
		GL_PIXEL_UNPACK_BUFFER,
		GL_TRANSFORM_FEEDBACK_BUFFER,
		GL_UNIFORM_BUFFER
	};

	for (int bindingNdx = 0; bindingNdx < DE_LENGTH_OF_ARRAY(bindingPoints); bindingNdx++)
	{
		if (getBufferBinding(bindingPoints[bindingNdx]) == buffer)
			setBufferBinding(bindingPoints[bindingNdx], DE_NULL);
	}

	{
		vector<VertexArray*> vertexArrays;
		m_vertexArrays.getAll(vertexArrays);
		vertexArrays.push_back(&m_clientVertexArray);

		for (vector<VertexArray*>::iterator i = vertexArrays.begin(); i != vertexArrays.end(); i++)
		{
			if ((*i)->m_elementArrayBufferBinding == buffer)
			{
				m_buffers.releaseReference(buffer);
				(*i)->m_elementArrayBufferBinding = DE_NULL;
			}

			for (size_t vertexAttribNdx = 0; vertexAttribNdx < (*i)->m_arrays.size(); ++vertexAttribNdx)
			{
				if ((*i)->m_arrays[vertexAttribNdx].bufferBinding == buffer)
				{
					m_buffers.releaseReference(buffer);
					(*i)->m_arrays[vertexAttribNdx].bufferDeleted = true;
					(*i)->m_arrays[vertexAttribNdx].bufferBinding = DE_NULL;
				}
			}
		}
	}

	DE_ASSERT(buffer->getRefCount() == 1);
	m_buffers.releaseReference(buffer);
}

void ReferenceContext::bufferData (deUint32 target, deIntptr size, const void* data, deUint32 usage)
{
	RC_IF_ERROR(!isValidBufferTarget(target), GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(size < 0, GL_INVALID_VALUE, RC_RET_VOID);

	DE_UNREF(usage);

	DataBuffer* buffer = getBufferBinding(target);
	RC_IF_ERROR(!buffer, GL_INVALID_OPERATION, RC_RET_VOID);

	DE_ASSERT((deIntptr)(int)size == size);
	buffer->setStorage((int)size);
	if (data)
		deMemcpy(buffer->getData(), data, (int)size);
}

void ReferenceContext::bufferSubData (deUint32 target, deIntptr offset, deIntptr size, const void* data)
{
	RC_IF_ERROR(!isValidBufferTarget(target), GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(offset < 0 || size < 0, GL_INVALID_VALUE, RC_RET_VOID);

	DataBuffer* buffer = getBufferBinding(target);

	RC_IF_ERROR(!buffer, GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR((int)(offset+size) > buffer->getSize(), GL_INVALID_VALUE, RC_RET_VOID);

	deMemcpy(buffer->getData()+offset, data, (int)size);
}

void ReferenceContext::clearColor (float red, float green, float blue, float alpha)
{
	m_clearColor = Vec4(de::clamp(red,	0.0f, 1.0f),
						de::clamp(green,	0.0f, 1.0f),
						de::clamp(blue,	0.0f, 1.0f),
						de::clamp(alpha,	0.0f, 1.0f));
}

void ReferenceContext::clearDepthf (float depth)
{
	m_clearDepth = de::clamp(depth, 0.0f, 1.0f);
}

void ReferenceContext::clearStencil (int stencil)
{
	m_clearStencil = stencil;
}

void ReferenceContext::scissor (int x, int y, int width, int height)
{
	RC_IF_ERROR(width < 0 || height < 0, GL_INVALID_VALUE, RC_RET_VOID);
	m_scissorBox = IVec4(x, y, width, height);
}

void ReferenceContext::enable (deUint32 cap)
{
	switch (cap)
	{
		case GL_BLEND:					m_blendEnabled				= true;	break;
		case GL_SCISSOR_TEST:			m_scissorEnabled			= true;	break;
		case GL_DEPTH_TEST:				m_depthTestEnabled			= true;	break;
		case GL_STENCIL_TEST:			m_stencilTestEnabled		= true;	break;
		case GL_POLYGON_OFFSET_FILL:	m_polygonOffsetFillEnabled	= true;	break;

		case GL_FRAMEBUFFER_SRGB:
			if (glu::isContextTypeGLCore(getType()))
			{
				m_sRGBUpdateEnabled = true;
				break;
			}
			setError(GL_INVALID_ENUM);
			break;

		case GL_DEPTH_CLAMP:
			if (glu::isContextTypeGLCore(getType()))
			{
				m_depthClampEnabled = true;
				break;
			}
			setError(GL_INVALID_ENUM);
			break;

		case GL_DITHER:
			// Not implemented - just ignored.
			break;

		case GL_PRIMITIVE_RESTART_FIXED_INDEX:
			if (!glu::isContextTypeGLCore(getType()))
			{
				m_primitiveRestartFixedIndex = true;
				break;
			}
			setError(GL_INVALID_ENUM);
			break;

		case GL_PRIMITIVE_RESTART:
			if (glu::isContextTypeGLCore(getType()))
			{
				m_primitiveRestartSettableIndex = true;
				break;
			}
			setError(GL_INVALID_ENUM);
			break;

		default:
			setError(GL_INVALID_ENUM);
			break;
	}
}

void ReferenceContext::disable (deUint32 cap)
{
	switch (cap)
	{
		case GL_BLEND:					m_blendEnabled				= false;	break;
		case GL_SCISSOR_TEST:			m_scissorEnabled			= false;	break;
		case GL_DEPTH_TEST:				m_depthTestEnabled			= false;	break;
		case GL_STENCIL_TEST:			m_stencilTestEnabled		= false;	break;
		case GL_POLYGON_OFFSET_FILL:	m_polygonOffsetFillEnabled	= false;	break;

		case GL_FRAMEBUFFER_SRGB:
			if (glu::isContextTypeGLCore(getType()))
			{
				m_sRGBUpdateEnabled = false;
				break;
			}
			setError(GL_INVALID_ENUM);
			break;

		case GL_DEPTH_CLAMP:
			if (glu::isContextTypeGLCore(getType()))
			{
				m_depthClampEnabled = false;
				break;
			}
			setError(GL_INVALID_ENUM);
			break;

		case GL_DITHER:
			break;

		case GL_PRIMITIVE_RESTART_FIXED_INDEX:
			if (!glu::isContextTypeGLCore(getType()))
			{
				m_primitiveRestartFixedIndex = false;
				break;
			}
			setError(GL_INVALID_ENUM);
			break;

		case GL_PRIMITIVE_RESTART:
			if (glu::isContextTypeGLCore(getType()))
			{
				m_primitiveRestartSettableIndex = false;
				break;
			}
			setError(GL_INVALID_ENUM);
			break;

		default:
			setError(GL_INVALID_ENUM);
			break;
	}
}

static bool isValidCompareFunc (deUint32 func)
{
	switch (func)
	{
		case GL_NEVER:
		case GL_LESS:
		case GL_LEQUAL:
		case GL_GREATER:
		case GL_GEQUAL:
		case GL_EQUAL:
		case GL_NOTEQUAL:
		case GL_ALWAYS:
			return true;

		default:
			return false;
	}
}

static bool isValidStencilOp (deUint32 op)
{
	switch (op)
	{
		case GL_KEEP:
		case GL_ZERO:
		case GL_REPLACE:
		case GL_INCR:
		case GL_INCR_WRAP:
		case GL_DECR:
		case GL_DECR_WRAP:
		case GL_INVERT:
			return true;

		default:
			return false;
	}
}

void ReferenceContext::stencilFunc (deUint32 func, int ref, deUint32 mask)
{
	stencilFuncSeparate(GL_FRONT_AND_BACK, func, ref, mask);
}

void ReferenceContext::stencilFuncSeparate (deUint32 face, deUint32 func, int ref, deUint32 mask)
{
	const bool	setFront	= face == GL_FRONT || face == GL_FRONT_AND_BACK;
	const bool	setBack		= face == GL_BACK || face == GL_FRONT_AND_BACK;

	RC_IF_ERROR(!isValidCompareFunc(func), GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(!setFront && !setBack, GL_INVALID_ENUM, RC_RET_VOID);

	for (int type = 0; type < rr::FACETYPE_LAST; ++type)
	{
		if ((type == rr::FACETYPE_FRONT && setFront) ||
			(type == rr::FACETYPE_BACK && setBack))
		{
			m_stencil[type].func	= func;
			m_stencil[type].ref		= ref;
			m_stencil[type].opMask	= mask;
		}
	}
}

void ReferenceContext::stencilOp (deUint32 sfail, deUint32 dpfail, deUint32 dppass)
{
	stencilOpSeparate(GL_FRONT_AND_BACK, sfail, dpfail, dppass);
}

void ReferenceContext::stencilOpSeparate (deUint32 face, deUint32 sfail, deUint32 dpfail, deUint32 dppass)
{
	const bool	setFront	= face == GL_FRONT || face == GL_FRONT_AND_BACK;
	const bool	setBack		= face == GL_BACK || face == GL_FRONT_AND_BACK;

	RC_IF_ERROR(!isValidStencilOp(sfail)	||
				!isValidStencilOp(dpfail)	||
				!isValidStencilOp(dppass),
				GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(!setFront && !setBack, GL_INVALID_ENUM, RC_RET_VOID);

	for (int type = 0; type < rr::FACETYPE_LAST; ++type)
	{
		if ((type == rr::FACETYPE_FRONT && setFront) ||
			(type == rr::FACETYPE_BACK && setBack))
		{
			m_stencil[type].opStencilFail	= sfail;
			m_stencil[type].opDepthFail		= dpfail;
			m_stencil[type].opDepthPass		= dppass;
		}
	}
}

void ReferenceContext::depthFunc (deUint32 func)
{
	RC_IF_ERROR(!isValidCompareFunc(func), GL_INVALID_ENUM, RC_RET_VOID);
	m_depthFunc = func;
}

void ReferenceContext::depthRangef (float n, float f)
{
	m_depthRangeNear = de::clamp(n, 0.0f, 1.0f);
	m_depthRangeFar = de::clamp(f, 0.0f, 1.0f);
}

void ReferenceContext::depthRange (double n, double f)
{
	depthRangef((float)n, (float)f);
}

void ReferenceContext::polygonOffset (float factor, float units)
{
	m_polygonOffsetFactor = factor;
	m_polygonOffsetUnits = units;
}

void ReferenceContext::provokingVertex (deUint32 convention)
{
	// only in core
	DE_ASSERT(glu::isContextTypeGLCore(getType()));

	switch (convention)
	{
		case GL_FIRST_VERTEX_CONVENTION:	m_provokingFirstVertexConvention = true; break;
		case GL_LAST_VERTEX_CONVENTION:		m_provokingFirstVertexConvention = false; break;

		default:
			RC_ERROR_RET(GL_INVALID_ENUM, RC_RET_VOID);
	}
}

void ReferenceContext::primitiveRestartIndex (deUint32 index)
{
	// only in core
	DE_ASSERT(glu::isContextTypeGLCore(getType()));
	m_primitiveRestartIndex = index;
}

static inline bool isValidBlendEquation (deUint32 mode)
{
	return mode == GL_FUNC_ADD				||
		   mode == GL_FUNC_SUBTRACT			||
		   mode == GL_FUNC_REVERSE_SUBTRACT	||
		   mode == GL_MIN					||
		   mode == GL_MAX;
}

static bool isValidBlendFactor (deUint32 factor)
{
	switch (factor)
	{
		case GL_ZERO:
		case GL_ONE:
		case GL_SRC_COLOR:
		case GL_ONE_MINUS_SRC_COLOR:
		case GL_DST_COLOR:
		case GL_ONE_MINUS_DST_COLOR:
		case GL_SRC_ALPHA:
		case GL_ONE_MINUS_SRC_ALPHA:
		case GL_DST_ALPHA:
		case GL_ONE_MINUS_DST_ALPHA:
		case GL_CONSTANT_COLOR:
		case GL_ONE_MINUS_CONSTANT_COLOR:
		case GL_CONSTANT_ALPHA:
		case GL_ONE_MINUS_CONSTANT_ALPHA:
		case GL_SRC_ALPHA_SATURATE:
			return true;

		default:
			return false;
	}
}

void ReferenceContext::blendEquation (deUint32 mode)
{
	RC_IF_ERROR(!isValidBlendEquation(mode), GL_INVALID_ENUM, RC_RET_VOID);

	m_blendModeRGB		= mode;
	m_blendModeAlpha	= mode;
}

void ReferenceContext::blendEquationSeparate (deUint32 modeRGB, deUint32 modeAlpha)
{
	RC_IF_ERROR(!isValidBlendEquation(modeRGB) ||
				!isValidBlendEquation(modeAlpha),
				GL_INVALID_ENUM, RC_RET_VOID);

	m_blendModeRGB		= modeRGB;
	m_blendModeAlpha	= modeAlpha;
}

void ReferenceContext::blendFunc (deUint32 src, deUint32 dst)
{
	RC_IF_ERROR(!isValidBlendFactor(src) ||
				!isValidBlendFactor(dst),
				GL_INVALID_ENUM, RC_RET_VOID);

	m_blendFactorSrcRGB		= src;
	m_blendFactorSrcAlpha	= src;
	m_blendFactorDstRGB		= dst;
	m_blendFactorDstAlpha	= dst;
}

void ReferenceContext::blendFuncSeparate (deUint32 srcRGB, deUint32 dstRGB, deUint32 srcAlpha, deUint32 dstAlpha)
{
	RC_IF_ERROR(!isValidBlendFactor(srcRGB)		||
				!isValidBlendFactor(dstRGB)		||
				!isValidBlendFactor(srcAlpha)	||
				!isValidBlendFactor(dstAlpha),
				GL_INVALID_ENUM, RC_RET_VOID);

	m_blendFactorSrcRGB		= srcRGB;
	m_blendFactorSrcAlpha	= srcAlpha;
	m_blendFactorDstRGB		= dstRGB;
	m_blendFactorDstAlpha	= dstAlpha;
}

void ReferenceContext::blendColor (float red, float green, float blue, float alpha)
{
	m_blendColor = Vec4(de::clamp(red,	0.0f, 1.0f),
						de::clamp(green,	0.0f, 1.0f),
						de::clamp(blue,	0.0f, 1.0f),
						de::clamp(alpha,	0.0f, 1.0f));
}

void ReferenceContext::colorMask (deBool r, deBool g, deBool b, deBool a)
{
	m_colorMask = tcu::BVec4(!!r, !!g, !!b, !!a);
}

void ReferenceContext::depthMask (deBool mask)
{
	m_depthMask = !!mask;
}

void ReferenceContext::stencilMask (deUint32 mask)
{
	stencilMaskSeparate(GL_FRONT_AND_BACK, mask);
}

void ReferenceContext::stencilMaskSeparate (deUint32 face, deUint32 mask)
{
	const bool	setFront	= face == GL_FRONT || face == GL_FRONT_AND_BACK;
	const bool	setBack		= face == GL_BACK || face == GL_FRONT_AND_BACK;

	RC_IF_ERROR(!setFront && !setBack, GL_INVALID_ENUM, RC_RET_VOID);

	if (setFront)	m_stencil[rr::FACETYPE_FRONT].writeMask	= mask;
	if (setBack)	m_stencil[rr::FACETYPE_BACK].writeMask	= mask;
}

static int getNumStencilBits (const tcu::TextureFormat& format)
{
	switch (format.order)
	{
		case tcu::TextureFormat::S:
			switch (format.type)
			{
				case tcu::TextureFormat::UNSIGNED_INT8:		return 8;
				case tcu::TextureFormat::UNSIGNED_INT16:	return 16;
				case tcu::TextureFormat::UNSIGNED_INT32:	return 32;
				default:
					DE_ASSERT(false);
					return 0;
			}

		case tcu::TextureFormat::DS:
			switch (format.type)
			{
				case tcu::TextureFormat::UNSIGNED_INT_24_8:				return 8;
				case tcu::TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV:	return 8;
				default:
					DE_ASSERT(false);
					return 0;
			}

		default:
			DE_ASSERT(false);
			return 0;
	}
}

static inline deUint32 maskStencil (int numBits, deUint32 s)
{
	return s & deBitMask32(0, numBits);
}

static inline void writeMaskedStencil (const rr::MultisamplePixelBufferAccess& access, int s, int x, int y, deUint32 stencil, deUint32 writeMask)
{
	DE_ASSERT(access.raw().getFormat().order == tcu::TextureFormat::S);

	const deUint32 oldVal = access.raw().getPixelUint(s, x, y).x();
	const deUint32 newVal = (oldVal & ~writeMask) | (stencil & writeMask);
	access.raw().setPixel(tcu::UVec4(newVal, 0u, 0u, 0u), s, x, y);
}

static inline void writeDepthOnly (const rr::MultisamplePixelBufferAccess& access, int s, int x, int y, float depth)
{
	access.raw().setPixDepth(depth, s, x, y);
}

static rr::MultisamplePixelBufferAccess getDepthMultisampleAccess (const rr::MultisamplePixelBufferAccess& combinedDSaccess)
{
	return rr::MultisamplePixelBufferAccess::fromMultisampleAccess(tcu::getEffectiveDepthStencilAccess(combinedDSaccess.raw(), tcu::Sampler::MODE_DEPTH));
}

static rr::MultisamplePixelBufferAccess getStencilMultisampleAccess (const rr::MultisamplePixelBufferAccess& combinedDSaccess)
{
	return rr::MultisamplePixelBufferAccess::fromMultisampleAccess(tcu::getEffectiveDepthStencilAccess(combinedDSaccess.raw(), tcu::Sampler::MODE_STENCIL));
}

deUint32 ReferenceContext::blitResolveMultisampleFramebuffer (deUint32 mask, const IVec4& srcRect, const IVec4& dstRect, bool flipX, bool flipY)
{
	if (mask & GL_COLOR_BUFFER_BIT)
	{
		rr::MultisampleConstPixelBufferAccess	src			= rr::getSubregion(getReadColorbuffer(), srcRect.x(), srcRect.y(), srcRect.z(), srcRect.w());
		tcu::PixelBufferAccess					dst			= tcu::getSubregion(getDrawColorbuffer().toSinglesampleAccess(), dstRect.x(), dstRect.y(), dstRect.z(), dstRect.w());
		tcu::TextureChannelClass				dstClass	= tcu::getTextureChannelClass(dst.getFormat().type);
		bool									dstIsFloat	= dstClass == tcu::TEXTURECHANNELCLASS_FLOATING_POINT		||
															  dstClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
															  dstClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT;
		bool									srcIsSRGB	= tcu::isSRGB(src.raw().getFormat());
		bool									dstIsSRGB	= tcu::isSRGB(dst.getFormat());
		const bool								convertSRGB	= m_sRGBUpdateEnabled && glu::isContextTypeES(getType());

		if (!convertSRGB)
		{
			tcu::ConstPixelBufferAccess	srcRaw	= src.raw();
			tcu::TextureFormat			srcFmt	= toNonSRGBFormat(srcRaw.getFormat());

			srcRaw	= tcu::ConstPixelBufferAccess(srcFmt, srcRaw.getWidth(), srcRaw.getHeight(), srcRaw.getDepth(), srcRaw.getRowPitch(), srcRaw.getSlicePitch(), srcRaw.getDataPtr());
			src		= rr::MultisampleConstPixelBufferAccess::fromMultisampleAccess(srcRaw);

			dst		= tcu::PixelBufferAccess(toNonSRGBFormat(dst.getFormat()), dst.getWidth(), dst.getHeight(), dst.getDepth(), dst.getRowPitch(), dst.getSlicePitch(), dst.getDataPtr());
		}

		for (int x = 0; x < dstRect.z(); ++x)
		for (int y = 0; y < dstRect.w(); ++y)
		{
			int srcX = (flipX) ? (srcRect.z() - x - 1) : (x);
			int srcY = (flipY) ? (srcRect.z() - y - 1) : (y);

			if (dstIsFloat || srcIsSRGB)
			{
				Vec4 p = src.raw().getPixel(0, srcX,srcY);
				dst.setPixel((dstIsSRGB && convertSRGB) ? tcu::linearToSRGB(p) : p, x, y);
			}
			else
				dst.setPixel(src.raw().getPixelInt(0, srcX, srcY), x, y);
		}
	}

	if (mask & GL_DEPTH_BUFFER_BIT)
	{
		rr::MultisampleConstPixelBufferAccess	src	= rr::getSubregion(getReadDepthbuffer(), srcRect.x(), srcRect.y(), srcRect.z(), srcRect.w());
		rr::MultisamplePixelBufferAccess		dst	= rr::getSubregion(getDrawDepthbuffer(), dstRect.x(), dstRect.y(), dstRect.z(), dstRect.w());

		for (int x = 0; x < dstRect.z(); ++x)
		for (int y = 0; y < dstRect.w(); ++y)
		{
			int srcX = (flipX) ? (srcRect.z() - x - 1) : (x);
			int srcY = (flipY) ? (srcRect.z() - y - 1) : (y);

			writeDepthOnly(dst, 0, x, y, src.raw().getPixel(0, srcX, srcY).x());
		}
	}

	if (mask & GL_STENCIL_BUFFER_BIT)
	{
		rr::MultisampleConstPixelBufferAccess	src	= getStencilMultisampleAccess(rr::getSubregion(getReadStencilbuffer(), srcRect.x(), srcRect.y(), srcRect.z(), srcRect.w()));
		rr::MultisamplePixelBufferAccess		dst	= getStencilMultisampleAccess(rr::getSubregion(getDrawStencilbuffer(), dstRect.x(), dstRect.y(), dstRect.z(), dstRect.w()));

		for (int x = 0; x < dstRect.z(); ++x)
		for (int y = 0; y < dstRect.w(); ++y)
		{
			int			srcX		= (flipX) ? (srcRect.z() - x - 1) : (x);
			int			srcY		= (flipY) ? (srcRect.z() - y - 1) : (y);
			deUint32	srcStencil	= src.raw().getPixelUint(0, srcX, srcY).x();

			writeMaskedStencil(dst, 0, x, y, srcStencil, m_stencil[rr::FACETYPE_FRONT].writeMask);
		}
	}

	return GL_NO_ERROR;
}

void ReferenceContext::blitFramebuffer (int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, deUint32 mask, deUint32 filter)
{
	// p0 in inclusive, p1 exclusive.
	// Negative width/height means swap.
	bool	swapSrcX	= srcX1 < srcX0;
	bool	swapSrcY	= srcY1 < srcY0;
	bool	swapDstX	= dstX1 < dstX0;
	bool	swapDstY	= dstY1 < dstY0;
	int		srcW		= de::abs(srcX1-srcX0);
	int		srcH		= de::abs(srcY1-srcY0);
	int		dstW		= de::abs(dstX1-dstX0);
	int		dstH		= de::abs(dstY1-dstY0);
	bool	scale		= srcW != dstW || srcH != dstH;
	int		srcOriginX	= swapSrcX ? srcX1 : srcX0;
	int		srcOriginY	= swapSrcY ? srcY1 : srcY0;
	int		dstOriginX	= swapDstX ? dstX1 : dstX0;
	int		dstOriginY	= swapDstY ? dstY1 : dstY0;
	IVec4	srcRect		= IVec4(srcOriginX, srcOriginY, srcW, srcH);
	IVec4	dstRect		= IVec4(dstOriginX, dstOriginY, dstW, dstH);

	RC_IF_ERROR(filter != GL_NEAREST && filter != GL_LINEAR, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR((mask & (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) != 0 && filter != GL_NEAREST, GL_INVALID_OPERATION, RC_RET_VOID);

	// Validate that both targets are complete.
	RC_IF_ERROR(checkFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE ||
				checkFramebufferStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, GL_INVALID_OPERATION, RC_RET_VOID);

	// Check samples count is valid
	RC_IF_ERROR(getDrawColorbuffer().getNumSamples() != 1, GL_INVALID_OPERATION, RC_RET_VOID);

	// Check size restrictions of multisampled case
	if (getReadColorbuffer().getNumSamples() != 1)
	{
		// Src and Dst rect dimensions must be the same
		RC_IF_ERROR(srcW != dstW || srcH != dstH, GL_INVALID_OPERATION, RC_RET_VOID);

		// Framebuffer formats must match
		if (mask & GL_COLOR_BUFFER_BIT)		RC_IF_ERROR(getReadColorbuffer().raw().getFormat()   != getDrawColorbuffer().raw().getFormat(),   GL_INVALID_OPERATION, RC_RET_VOID);
		if (mask & GL_DEPTH_BUFFER_BIT)		RC_IF_ERROR(getReadDepthbuffer().raw().getFormat()   != getDrawDepthbuffer().raw().getFormat(),   GL_INVALID_OPERATION, RC_RET_VOID);
		if (mask & GL_STENCIL_BUFFER_BIT)	RC_IF_ERROR(getReadStencilbuffer().raw().getFormat() != getDrawStencilbuffer().raw().getFormat(), GL_INVALID_OPERATION, RC_RET_VOID);
	}

	// Compute actual source rect.
	srcRect = (mask & GL_COLOR_BUFFER_BIT)		? intersect(srcRect, getBufferRect(getReadColorbuffer()))	: srcRect;
	srcRect = (mask & GL_DEPTH_BUFFER_BIT)		? intersect(srcRect, getBufferRect(getReadDepthbuffer()))	: srcRect;
	srcRect = (mask & GL_STENCIL_BUFFER_BIT)	? intersect(srcRect, getBufferRect(getReadStencilbuffer()))	: srcRect;

	// Compute destination rect.
	dstRect = (mask & GL_COLOR_BUFFER_BIT)		? intersect(dstRect, getBufferRect(getDrawColorbuffer()))	: dstRect;
	dstRect = (mask & GL_DEPTH_BUFFER_BIT)		? intersect(dstRect, getBufferRect(getDrawDepthbuffer()))	: dstRect;
	dstRect = (mask & GL_STENCIL_BUFFER_BIT)	? intersect(dstRect, getBufferRect(getDrawStencilbuffer()))	: dstRect;
	dstRect = m_scissorEnabled					? intersect(dstRect, m_scissorBox)							: dstRect;

	if (isEmpty(srcRect) || isEmpty(dstRect))
		return; // Don't attempt copy.

	// Multisampled read buffer is a special case
	if (getReadColorbuffer().getNumSamples() != 1)
	{
		deUint32 error = blitResolveMultisampleFramebuffer(mask, srcRect, dstRect, swapSrcX ^ swapDstX, swapSrcY ^ swapDstY);

		if (error != GL_NO_ERROR)
			setError(error);

		return;
	}

	// \note Multisample pixel buffers can now be accessed like non-multisampled because multisample read buffer case is already handled. => sample count must be 1

	// Coordinate transformation:
	// Dst offset space -> dst rectangle space -> src rectangle space -> src offset space.
	tcu::Mat3 transform = tcu::translationMatrix(Vec2((float)(srcX0 - srcRect.x()), (float)(srcY0 - srcRect.y())))
						* tcu::Mat3(Vec3((float)(srcX1-srcX0) / (float)(dstX1-dstX0),
										 (float)(srcY1-srcY0) / (float)(dstY1-dstY0),
										 1.0f))
						* tcu::translationMatrix(Vec2((float)(dstRect.x() - dstX0), (float)(dstRect.y() - dstY0)));

	if (mask & GL_COLOR_BUFFER_BIT)
	{
		tcu::ConstPixelBufferAccess		src			= tcu::getSubregion(getReadColorbuffer().toSinglesampleAccess(), srcRect.x(), srcRect.y(), srcRect.z(), srcRect.w());
		tcu::PixelBufferAccess			dst			= tcu::getSubregion(getDrawColorbuffer().toSinglesampleAccess(), dstRect.x(), dstRect.y(), dstRect.z(), dstRect.w());
		tcu::TextureChannelClass		dstClass	= tcu::getTextureChannelClass(dst.getFormat().type);
		bool							dstIsFloat	= dstClass == tcu::TEXTURECHANNELCLASS_FLOATING_POINT		||
													  dstClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT	||
													  dstClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT;
		tcu::Sampler::FilterMode		sFilter		= (scale && filter == GL_LINEAR) ? tcu::Sampler::LINEAR : tcu::Sampler::NEAREST;
		tcu::Sampler					sampler		(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
													 sFilter, sFilter, 0.0f /* lod threshold */, false /* non-normalized coords */);
		bool							srcIsSRGB	= tcu::isSRGB(src.getFormat());
		bool							dstIsSRGB	= tcu::isSRGB(dst.getFormat());
		const bool						convertSRGB	= m_sRGBUpdateEnabled && glu::isContextTypeES(getType());

		if (!convertSRGB)
		{
			src	= tcu::ConstPixelBufferAccess	(toNonSRGBFormat(src.getFormat()), src.getWidth(), src.getHeight(), src.getDepth(), src.getRowPitch(), src.getSlicePitch(), src.getDataPtr());
			dst	= tcu::PixelBufferAccess		(toNonSRGBFormat(dst.getFormat()), dst.getWidth(), dst.getHeight(), dst.getDepth(), dst.getRowPitch(), dst.getSlicePitch(), dst.getDataPtr());
		}

		// \note We don't check for unsupported conversions, unlike spec requires.

		for (int yo = 0; yo < dstRect.w(); yo++)
		{
			for (int xo = 0; xo < dstRect.z(); xo++)
			{
				float	dX	= (float)xo + 0.5f;
				float	dY	= (float)yo + 0.5f;

				// \note Only affine part is used.
				float	sX	= transform(0, 0)*dX + transform(0, 1)*dY + transform(0, 2);
				float	sY	= transform(1, 0)*dX + transform(1, 1)*dY + transform(1, 2);

				// do not copy pixels outside the modified source region (modified by buffer intersection)
				if (sX < 0.0f || sX >= (float)srcRect.z() ||
					sY < 0.0f || sY >= (float)srcRect.w())
					continue;

				if (dstIsFloat || srcIsSRGB || filter == tcu::Sampler::LINEAR)
				{
					Vec4 p = src.sample2D(sampler, sampler.minFilter, sX, sY, 0);
					dst.setPixel((dstIsSRGB && convertSRGB) ? tcu::linearToSRGB(p) : p, xo, yo);
				}
				else
					dst.setPixel(src.getPixelInt(deFloorFloatToInt32(sX), deFloorFloatToInt32(sY)), xo, yo);
			}
		}
	}

	if ((mask & GL_DEPTH_BUFFER_BIT) && m_depthMask)
	{
		rr::MultisampleConstPixelBufferAccess	src		= getDepthMultisampleAccess(rr::getSubregion(getReadDepthbuffer(), srcRect.x(), srcRect.y(), srcRect.z(), srcRect.w()));
		rr::MultisamplePixelBufferAccess		dst		= getDepthMultisampleAccess(rr::getSubregion(getDrawDepthbuffer(), dstRect.x(), dstRect.y(), dstRect.z(), dstRect.w()));

		for (int yo = 0; yo < dstRect.w(); yo++)
		{
			for (int xo = 0; xo < dstRect.z(); xo++)
			{
				const int sampleNdx = 0; // multisample read buffer case is already handled

				float	dX	= (float)xo + 0.5f;
				float	dY	= (float)yo + 0.5f;
				float	sX	= transform(0, 0)*dX + transform(0, 1)*dY + transform(0, 2);
				float	sY	= transform(1, 0)*dX + transform(1, 1)*dY + transform(1, 2);

				writeDepthOnly(dst, sampleNdx, xo, yo, src.raw().getPixDepth(sampleNdx, deFloorFloatToInt32(sX), deFloorFloatToInt32(sY)));
			}
		}
	}

	if (mask & GL_STENCIL_BUFFER_BIT)
	{
		rr::MultisampleConstPixelBufferAccess	src	= getStencilMultisampleAccess(rr::getSubregion(getReadStencilbuffer(), srcRect.x(), srcRect.y(), srcRect.z(), srcRect.w()));
		rr::MultisamplePixelBufferAccess		dst	= getStencilMultisampleAccess(rr::getSubregion(getDrawStencilbuffer(), dstRect.x(), dstRect.y(), dstRect.z(), dstRect.w()));

		for (int yo = 0; yo < dstRect.w(); yo++)
		{
			for (int xo = 0; xo < dstRect.z(); xo++)
			{
				const int	sampleNdx = 0; // multisample read buffer case is already handled

				float		dX			= (float)xo + 0.5f;
				float		dY			= (float)yo + 0.5f;
				float		sX			= transform(0, 0)*dX + transform(0, 1)*dY + transform(0, 2);
				float		sY			= transform(1, 0)*dX + transform(1, 1)*dY + transform(1, 2);
				deUint32	srcStencil	= src.raw().getPixelUint(sampleNdx, deFloorFloatToInt32(sX), deFloorFloatToInt32(sY)).x();

				writeMaskedStencil(dst, sampleNdx, xo, yo, srcStencil, m_stencil[rr::FACETYPE_FRONT].writeMask);
			}
		}
	}
}

void ReferenceContext::invalidateSubFramebuffer (deUint32 target, int numAttachments, const deUint32* attachments, int x, int y, int width, int height)
{
	RC_IF_ERROR(target != GL_FRAMEBUFFER, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR((numAttachments < 0) || (numAttachments > 1 && attachments == DE_NULL), GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(width < 0 || height < 0, GL_INVALID_VALUE, RC_RET_VOID);

	// \todo [2012-07-17 pyry] Support multiple color attachments.

	const Vec4		colorClearValue		(0.0f);
	const float		depthClearValue		= 1.0f;
	const int		stencilClearValue	= 0;

	bool			isFboBound			= m_drawFramebufferBinding != DE_NULL;
	bool			discardBuffers[3]	= { false, false, false }; // Color, depth, stencil

	for (int attNdx = 0; attNdx < numAttachments; attNdx++)
	{
		bool	isColor			= attachments[attNdx] == (isFboBound ? GL_COLOR_ATTACHMENT0		: GL_COLOR);
		bool	isDepth			= attachments[attNdx] == (isFboBound ? GL_DEPTH_ATTACHMENT		: GL_DEPTH);
		bool	isStencil		= attachments[attNdx] == (isFboBound ? GL_STENCIL_ATTACHMENT	: GL_STENCIL);
		bool	isDepthStencil	= isFboBound && attachments[attNdx] == GL_DEPTH_STENCIL_ATTACHMENT;

		RC_IF_ERROR(!isColor && !isDepth && !isStencil && !isDepthStencil, GL_INVALID_VALUE, RC_RET_VOID);

		if (isColor)						discardBuffers[0] = true;
		if (isDepth || isDepthStencil)		discardBuffers[1] = true;
		if (isStencil || isDepthStencil)	discardBuffers[2] = true;
	}

	for (int ndx = 0; ndx < 3; ndx++)
	{
		if (!discardBuffers[ndx])
			continue;

		bool								isColor					= ndx == 0;
		bool								isDepth					= ndx == 1;
		bool								isStencil				= ndx == 2;
		rr::MultisamplePixelBufferAccess	buf						= isColor ? getDrawColorbuffer()								:
																	  isDepth ? getDepthMultisampleAccess(getDrawDepthbuffer())		:
																				getStencilMultisampleAccess(getDrawStencilbuffer());

		if (isEmpty(buf))
			continue;

		tcu::IVec4							area					= intersect(tcu::IVec4(0, 0, buf.raw().getHeight(), buf.raw().getDepth()), tcu::IVec4(x, y, width, height));
		rr::MultisamplePixelBufferAccess	access					= rr::getSubregion(buf, area.x(), area.y(), area.z(), area.w());

		if (isColor)
			rr::clear(access, colorClearValue);
		else if (isDepth)
			rr::clear(access, tcu::Vec4(depthClearValue));
		else if (isStencil)
			rr::clear(access, tcu::IVec4(stencilClearValue));
	}
}

void ReferenceContext::invalidateFramebuffer (deUint32 target, int numAttachments, const deUint32* attachments)
{
	// \todo [2012-07-17 pyry] Support multiple color attachments.
	rr::MultisampleConstPixelBufferAccess	colorBuf0	= getDrawColorbuffer();
	rr::MultisampleConstPixelBufferAccess	depthBuf	= getDrawDepthbuffer();
	rr::MultisampleConstPixelBufferAccess	stencilBuf	= getDrawStencilbuffer();
	int										width		= 0;
	int										height		= 0;

	width = de::max(width, colorBuf0.raw().getHeight());
	width = de::max(width, depthBuf.raw().getHeight());
	width = de::max(width, stencilBuf.raw().getHeight());

	height = de::max(height, colorBuf0.raw().getDepth());
	height = de::max(height, depthBuf.raw().getDepth());
	height = de::max(height, stencilBuf.raw().getDepth());

	invalidateSubFramebuffer(target, numAttachments, attachments, 0, 0, width, height);
}

void ReferenceContext::clear (deUint32 buffers)
{
	RC_IF_ERROR((buffers & ~(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT)) != 0, GL_INVALID_VALUE, RC_RET_VOID);

	rr::MultisamplePixelBufferAccess	colorBuf0	= getDrawColorbuffer();
	rr::MultisamplePixelBufferAccess	depthBuf	= getDrawDepthbuffer();
	rr::MultisamplePixelBufferAccess	stencilBuf	= getDrawStencilbuffer();
	IVec4								baseArea	= m_scissorEnabled ? m_scissorBox : IVec4(0, 0, 0x7fffffff, 0x7fffffff);
	IVec4								colorArea	= intersect(baseArea, getBufferRect(colorBuf0));
	IVec4								depthArea	= intersect(baseArea, getBufferRect(depthBuf));
	IVec4								stencilArea	= intersect(baseArea, getBufferRect(stencilBuf));
	bool								hasColor0	= !isEmpty(colorArea);
	bool								hasDepth	= !isEmpty(depthArea);
	bool								hasStencil	= !isEmpty(stencilArea);

	if (hasColor0 && (buffers & GL_COLOR_BUFFER_BIT) != 0)
	{
		rr::MultisamplePixelBufferAccess	access		= rr::getSubregion(colorBuf0, colorArea.x(), colorArea.y(), colorArea.z(), colorArea.w());
		bool								isSRGB		= tcu::isSRGB(colorBuf0.raw().getFormat());
		Vec4								c			= (isSRGB && m_sRGBUpdateEnabled) ? tcu::linearToSRGB(m_clearColor) : m_clearColor;
		bool								maskUsed	= !m_colorMask[0] || !m_colorMask[1] || !m_colorMask[2] || !m_colorMask[3];
		bool								maskZero	= !m_colorMask[0] && !m_colorMask[1] && !m_colorMask[2] && !m_colorMask[3];

		if (!maskUsed)
			rr::clear(access, c);
		else if (!maskZero)
		{
			for (int y = 0; y < access.raw().getDepth(); y++)
				for (int x = 0; x < access.raw().getHeight(); x++)
					for (int s = 0; s < access.getNumSamples(); s++)
						access.raw().setPixel(tcu::select(c, access.raw().getPixel(s, x, y), m_colorMask), s, x, y);
		}
		// else all channels masked out
	}

	if (hasDepth && (buffers & GL_DEPTH_BUFFER_BIT) != 0 && m_depthMask)
	{
		rr::MultisamplePixelBufferAccess access = getDepthMultisampleAccess(rr::getSubregion(depthBuf, depthArea.x(), depthArea.y(), depthArea.z(), depthArea.w()));
		rr::clearDepth(access, m_clearDepth);
	}

	if (hasStencil && (buffers & GL_STENCIL_BUFFER_BIT) != 0)
	{
		rr::MultisamplePixelBufferAccess	access					= getStencilMultisampleAccess(rr::getSubregion(stencilBuf, stencilArea.x(), stencilArea.y(), stencilArea.z(), stencilArea.w()));
		int									stencilBits				= getNumStencilBits(stencilBuf.raw().getFormat());
		int									stencil					= maskStencil(stencilBits, m_clearStencil);

		if ((m_stencil[rr::FACETYPE_FRONT].writeMask & ((1u<<stencilBits)-1u)) != ((1u<<stencilBits)-1u))
		{
			// Slow path where depth or stencil is masked out in write.
			for (int y = 0; y < access.raw().getDepth(); y++)
				for (int x = 0; x < access.raw().getHeight(); x++)
					for (int s = 0; s < access.getNumSamples(); s++)
						writeMaskedStencil(access, s, x, y, stencil, m_stencil[rr::FACETYPE_FRONT].writeMask);
		}
		else
			rr::clearStencil(access, stencil);
	}
}

void ReferenceContext::clearBufferiv (deUint32 buffer, int drawbuffer, const int* value)
{
	RC_IF_ERROR(buffer != GL_COLOR && buffer != GL_STENCIL, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(drawbuffer != 0, GL_INVALID_VALUE, RC_RET_VOID); // \todo [2012-04-06 pyry] MRT support.

	IVec4 baseArea = m_scissorEnabled ? m_scissorBox : IVec4(0, 0, 0x7fffffff, 0x7fffffff);

	if (buffer == GL_COLOR)
	{
		rr::MultisamplePixelBufferAccess	colorBuf	= getDrawColorbuffer();
		bool								maskUsed	= !m_colorMask[0] || !m_colorMask[1] || !m_colorMask[2] || !m_colorMask[3];
		bool								maskZero	= !m_colorMask[0] && !m_colorMask[1] && !m_colorMask[2] && !m_colorMask[3];
		IVec4								area		= intersect(baseArea, getBufferRect(colorBuf));

		if (!isEmpty(area) && !maskZero)
		{
			rr::MultisamplePixelBufferAccess	access		= rr::getSubregion(colorBuf, area.x(), area.y(), area.z(), area.w());
			IVec4								color		(value[0], value[1], value[2], value[3]);

			if (!maskUsed)
				rr::clear(access, color);
			else
			{
				for (int y = 0; y < access.raw().getDepth(); y++)
					for (int x = 0; x < access.raw().getHeight(); x++)
						for (int s = 0; s < access.getNumSamples(); s++)
							access.raw().setPixel(tcu::select(color, access.raw().getPixelInt(s, x, y), m_colorMask), s, x, y);
			}
		}
	}
	else
	{
		TCU_CHECK_INTERNAL(buffer == GL_STENCIL);

		rr::MultisamplePixelBufferAccess	stencilBuf	= getDrawStencilbuffer();
		IVec4								area		= intersect(baseArea, getBufferRect(stencilBuf));

		if (!isEmpty(area) && m_stencil[rr::FACETYPE_FRONT].writeMask != 0)
		{
			rr::MultisamplePixelBufferAccess	access		= getStencilMultisampleAccess(rr::getSubregion(stencilBuf, area.x(), area.y(), area.z(), area.w()));
			int									stencil		= value[0];

			for (int y = 0; y < access.raw().getDepth(); y++)
				for (int x = 0; x < access.raw().getHeight(); x++)
					for (int s = 0; s < access.getNumSamples(); s++)
						writeMaskedStencil(access, s, x, y, stencil, m_stencil[rr::FACETYPE_FRONT].writeMask);
		}
	}
}

void ReferenceContext::clearBufferfv (deUint32 buffer, int drawbuffer, const float* value)
{
	RC_IF_ERROR(buffer != GL_COLOR && buffer != GL_DEPTH, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(drawbuffer != 0, GL_INVALID_VALUE, RC_RET_VOID); // \todo [2012-04-06 pyry] MRT support.

	IVec4 baseArea = m_scissorEnabled ? m_scissorBox : IVec4(0, 0, 0x7fffffff, 0x7fffffff);

	if (buffer == GL_COLOR)
	{
		rr::MultisamplePixelBufferAccess	colorBuf	= getDrawColorbuffer();
		bool								maskUsed	= !m_colorMask[0] || !m_colorMask[1] || !m_colorMask[2] || !m_colorMask[3];
		bool								maskZero	= !m_colorMask[0] && !m_colorMask[1] && !m_colorMask[2] && !m_colorMask[3];
		IVec4								area		= intersect(baseArea, getBufferRect(colorBuf));

		if (!isEmpty(area) && !maskZero)
		{
			rr::MultisamplePixelBufferAccess	access		= rr::getSubregion(colorBuf, area.x(), area.y(), area.z(), area.w());
			Vec4								color		(value[0], value[1], value[2], value[3]);

			if (m_sRGBUpdateEnabled && tcu::isSRGB(access.raw().getFormat()))
				color = tcu::linearToSRGB(color);

			if (!maskUsed)
				rr::clear(access, color);
			else
			{
				for (int y = 0; y < access.raw().getDepth(); y++)
					for (int x = 0; x < access.raw().getHeight(); x++)
						for (int s = 0; s < access.getNumSamples(); s++)
							access.raw().setPixel(tcu::select(color, access.raw().getPixel(s, x, y), m_colorMask), s, x, y);
			}
		}
	}
	else
	{
		TCU_CHECK_INTERNAL(buffer == GL_DEPTH);

		rr::MultisamplePixelBufferAccess	depthBuf	= getDrawDepthbuffer();
		IVec4								area		= intersect(baseArea, getBufferRect(depthBuf));

		if (!isEmpty(area) && m_depthMask)
		{
			rr::MultisamplePixelBufferAccess	access		= rr::getSubregion(depthBuf, area.x(), area.y(), area.z(), area.w());
			float								depth		= value[0];

			rr::clearDepth(access, depth);
		}
	}
}

void ReferenceContext::clearBufferuiv (deUint32 buffer, int drawbuffer, const deUint32* value)
{
	RC_IF_ERROR(buffer != GL_COLOR, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(drawbuffer != 0, GL_INVALID_VALUE, RC_RET_VOID); // \todo [2012-04-06 pyry] MRT support.

	IVec4 baseArea = m_scissorEnabled ? m_scissorBox : IVec4(0, 0, 0x7fffffff, 0x7fffffff);

	TCU_CHECK_INTERNAL(buffer == GL_COLOR);
	{
		rr::MultisamplePixelBufferAccess	colorBuf	= getDrawColorbuffer();
		bool								maskUsed	= !m_colorMask[0] || !m_colorMask[1] || !m_colorMask[2] || !m_colorMask[3];
		bool								maskZero	= !m_colorMask[0] && !m_colorMask[1] && !m_colorMask[2] && !m_colorMask[3];
		IVec4								area		= intersect(baseArea, getBufferRect(colorBuf));

		if (!isEmpty(area) && !maskZero)
		{
			rr::MultisamplePixelBufferAccess	access		= rr::getSubregion(colorBuf, area.x(), area.y(), area.z(), area.w());
			tcu::UVec4							color		(value[0], value[1], value[2], value[3]);

			if (!maskUsed)
				rr::clear(access, color.asInt());
			else
			{
				for (int y = 0; y < access.raw().getDepth(); y++)
					for (int x = 0; x < access.raw().getHeight(); x++)
						for (int s = 0; s < access.getNumSamples(); s++)
							access.raw().setPixel(tcu::select(color, access.raw().getPixelUint(s, x, y), m_colorMask), s, x, y);
			}
		}
	}
}

void ReferenceContext::clearBufferfi (deUint32 buffer, int drawbuffer, float depth, int stencil)
{
	RC_IF_ERROR(buffer != GL_DEPTH_STENCIL, GL_INVALID_ENUM, RC_RET_VOID);
	clearBufferfv(GL_DEPTH, drawbuffer, &depth);
	clearBufferiv(GL_STENCIL, drawbuffer, &stencil);
}

void ReferenceContext::bindVertexArray (deUint32 array)
{
	rc::VertexArray* vertexArrayObject = DE_NULL;

	if (array != 0)
	{
		vertexArrayObject = m_vertexArrays.find(array);
		if (!vertexArrayObject)
		{
			vertexArrayObject = new rc::VertexArray(array, m_limits.maxVertexAttribs);
			m_vertexArrays.insert(vertexArrayObject);
		}
	}

	// Create new references
	if (vertexArrayObject)
		m_vertexArrays.acquireReference(vertexArrayObject);

	// Remove old references
	if (m_vertexArrayBinding)
		m_vertexArrays.releaseReference(m_vertexArrayBinding);

	m_vertexArrayBinding = vertexArrayObject;
}

void ReferenceContext::genVertexArrays (int numArrays, deUint32* vertexArrays)
{
	RC_IF_ERROR(!vertexArrays, GL_INVALID_VALUE, RC_RET_VOID);

	for (int ndx = 0; ndx < numArrays; ndx++)
		vertexArrays[ndx] = m_vertexArrays.allocateName();
}

void ReferenceContext::deleteVertexArrays (int numArrays, const deUint32* vertexArrays)
{
	for (int i = 0; i < numArrays; i++)
	{
		deUint32		name		= vertexArrays[i];
		VertexArray*	vertexArray	= name ? m_vertexArrays.find(name) : DE_NULL;

		if (vertexArray)
			deleteVertexArray(vertexArray);
	}
}

void ReferenceContext::vertexAttribPointer (deUint32 index, int rawSize, deUint32 type, deBool normalized, int stride, const void *pointer)
{
	const bool allowBGRA	= !glu::isContextTypeES(getType());
	const int effectiveSize	= (allowBGRA && rawSize == GL_BGRA) ? (4) : (rawSize);

	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(effectiveSize <= 0 || effectiveSize > 4, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(type != GL_BYTE					&&	type != GL_UNSIGNED_BYTE	&&
				type != GL_SHORT				&&	type != GL_UNSIGNED_SHORT	&&
				type != GL_INT					&&	type != GL_UNSIGNED_INT		&&
				type != GL_FIXED				&&	type != GL_DOUBLE			&&
				type != GL_FLOAT				&&	type != GL_HALF_FLOAT		&&
				type != GL_INT_2_10_10_10_REV	&&	type != GL_UNSIGNED_INT_2_10_10_10_REV, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(normalized != GL_TRUE && normalized != GL_FALSE, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(stride < 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR((type == GL_INT_2_10_10_10_REV || type == GL_UNSIGNED_INT_2_10_10_10_REV) && effectiveSize != 4, GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(m_vertexArrayBinding != DE_NULL && m_arrayBufferBinding == DE_NULL && pointer != DE_NULL, GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(allowBGRA && rawSize == GL_BGRA && type != GL_INT_2_10_10_10_REV && type != GL_UNSIGNED_INT_2_10_10_10_REV && type != GL_UNSIGNED_BYTE, GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(allowBGRA && rawSize == GL_BGRA && normalized == GL_FALSE, GL_INVALID_OPERATION, RC_RET_VOID);

	rc::VertexArray& vao = (m_vertexArrayBinding) ? (*m_vertexArrayBinding) : (m_clientVertexArray);

	vao.m_arrays[index].size			= rawSize;
	vao.m_arrays[index].stride			= stride;
	vao.m_arrays[index].type			= type;
	vao.m_arrays[index].normalized		= normalized == GL_TRUE;
	vao.m_arrays[index].integer			= false;
	vao.m_arrays[index].pointer			= pointer;

	// acquire new reference
	if (m_arrayBufferBinding)
		m_buffers.acquireReference(m_arrayBufferBinding);

	// release old reference
	if (vao.m_arrays[index].bufferBinding)
		m_buffers.releaseReference(vao.m_arrays[index].bufferBinding);

	vao.m_arrays[index].bufferDeleted	= false;
	vao.m_arrays[index].bufferBinding	= m_arrayBufferBinding;
}

void ReferenceContext::vertexAttribIPointer (deUint32 index, int size, deUint32 type, int stride, const void *pointer)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(size <= 0 || size > 4, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(type != GL_BYTE					&&	type != GL_UNSIGNED_BYTE	&&
				type != GL_SHORT				&&	type != GL_UNSIGNED_SHORT	&&
				type != GL_INT					&&	type != GL_UNSIGNED_INT, GL_INVALID_ENUM, RC_RET_VOID);
	RC_IF_ERROR(stride < 0, GL_INVALID_VALUE, RC_RET_VOID);
	RC_IF_ERROR(m_vertexArrayBinding != DE_NULL && m_arrayBufferBinding == DE_NULL && pointer != DE_NULL, GL_INVALID_OPERATION, RC_RET_VOID);

	rc::VertexArray& vao = (m_vertexArrayBinding) ? (*m_vertexArrayBinding) : (m_clientVertexArray);

	vao.m_arrays[index].size			= size;
	vao.m_arrays[index].stride			= stride;
	vao.m_arrays[index].type			= type;
	vao.m_arrays[index].normalized		= false;
	vao.m_arrays[index].integer			= true;
	vao.m_arrays[index].pointer			= pointer;

	// acquire new reference
	if (m_arrayBufferBinding)
		m_buffers.acquireReference(m_arrayBufferBinding);

	// release old reference
	if (vao.m_arrays[index].bufferBinding)
		m_buffers.releaseReference(vao.m_arrays[index].bufferBinding);

	vao.m_arrays[index].bufferDeleted	= false;
	vao.m_arrays[index].bufferBinding	= m_arrayBufferBinding;
}

void ReferenceContext::enableVertexAttribArray (deUint32 index)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	rc::VertexArray& vao = (m_vertexArrayBinding) ? (*m_vertexArrayBinding) : (m_clientVertexArray);
	vao.m_arrays[index].enabled = true;
}

void ReferenceContext::disableVertexAttribArray (deUint32 index)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	rc::VertexArray& vao = (m_vertexArrayBinding) ? (*m_vertexArrayBinding) : (m_clientVertexArray);
	vao.m_arrays[index].enabled = false;
}

void ReferenceContext::vertexAttribDivisor (deUint32 index, deUint32 divisor)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	rc::VertexArray& vao = (m_vertexArrayBinding) ? (*m_vertexArrayBinding) : (m_clientVertexArray);
	vao.m_arrays[index].divisor = divisor;
}

void ReferenceContext::vertexAttrib1f (deUint32 index, float x)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	m_currentAttribs[index] = rr::GenericVec4(tcu::Vec4(x, 0, 0, 1));
}

void ReferenceContext::vertexAttrib2f (deUint32 index, float x, float y)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	m_currentAttribs[index] = rr::GenericVec4(tcu::Vec4(x, y, 0, 1));
}

void ReferenceContext::vertexAttrib3f (deUint32 index, float x, float y, float z)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	m_currentAttribs[index] = rr::GenericVec4(tcu::Vec4(x, y, z, 1));
}

void ReferenceContext::vertexAttrib4f (deUint32 index, float x, float y, float z, float w)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	m_currentAttribs[index] = rr::GenericVec4(tcu::Vec4(x, y, z, w));
}

void ReferenceContext::vertexAttribI4i (deUint32 index, deInt32 x, deInt32 y, deInt32 z, deInt32 w)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	m_currentAttribs[index] = rr::GenericVec4(tcu::IVec4(x, y, z, w));
}

void ReferenceContext::vertexAttribI4ui (deUint32 index, deUint32 x, deUint32 y, deUint32 z, deUint32 w)
{
	RC_IF_ERROR(index >= (deUint32)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

	m_currentAttribs[index] = rr::GenericVec4(tcu::UVec4(x, y, z, w));
}

deInt32 ReferenceContext::getAttribLocation (deUint32 program, const char *name)
{
	ShaderProgramObjectContainer* shaderProg = m_programs.find(program);

	RC_IF_ERROR(shaderProg == DE_NULL, GL_INVALID_OPERATION, -1);

	if (name)
	{
		std::string nameString(name);

		for (size_t ndx = 0; ndx < shaderProg->m_program->m_attributeNames.size(); ++ndx)
			if (shaderProg->m_program->m_attributeNames[ndx] == nameString)
				return (int)ndx;
	}

	return -1;
}

void ReferenceContext::uniformv (deInt32 location, glu::DataType type, deInt32 count, const void* v)
{
	RC_IF_ERROR(m_currentProgram == DE_NULL, GL_INVALID_OPERATION, RC_RET_VOID);

	std::vector<sglr::UniformSlot>& uniforms = m_currentProgram->m_program->m_uniforms;

	if (location == -1)
		return;

	RC_IF_ERROR(location < 0 || (size_t)location >= uniforms.size(), GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(uniforms[location].type != type, GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(count != 1, GL_INVALID_OPERATION, RC_RET_VOID); // \todo [2013-12-13 pyry] Array uniforms.

	{
		const int scalarSize = glu::getDataTypeScalarSize(type);
		DE_ASSERT(scalarSize*sizeof(deUint32) <= sizeof(uniforms[location].value));
		deMemcpy(&uniforms[location].value, v, scalarSize*(int)sizeof(deUint32));
	}
}

void ReferenceContext::uniform1iv (deInt32 location, deInt32 count, const deInt32* v)
{
	RC_IF_ERROR(m_currentProgram == DE_NULL, GL_INVALID_OPERATION, RC_RET_VOID);

	std::vector<sglr::UniformSlot>& uniforms = m_currentProgram->m_program->m_uniforms;

	if (location == -1)
		return;

	RC_IF_ERROR(location < 0 || (size_t)location >= uniforms.size(), GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(count != 1, GL_INVALID_OPERATION, RC_RET_VOID); // \todo [2013-12-13 pyry] Array uniforms.

	switch (uniforms[location].type)
	{
		case glu::TYPE_INT:		uniforms[location].value.i = *v;	return;

		// \note texture unit is stored to value
		case glu::TYPE_SAMPLER_2D:
		case glu::TYPE_UINT_SAMPLER_2D:
		case glu::TYPE_INT_SAMPLER_2D:
		case glu::TYPE_SAMPLER_CUBE:
		case glu::TYPE_UINT_SAMPLER_CUBE:
		case glu::TYPE_INT_SAMPLER_CUBE:
		case glu::TYPE_SAMPLER_2D_ARRAY:
		case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
		case glu::TYPE_INT_SAMPLER_2D_ARRAY:
		case glu::TYPE_SAMPLER_3D:
		case glu::TYPE_UINT_SAMPLER_3D:
		case glu::TYPE_INT_SAMPLER_3D:
		case glu::TYPE_SAMPLER_CUBE_ARRAY:
		case glu::TYPE_UINT_SAMPLER_CUBE_ARRAY:
		case glu::TYPE_INT_SAMPLER_CUBE_ARRAY:
			uniforms[location].value.i = *v;
			return;

		default:
			setError(GL_INVALID_OPERATION);
			return;
	}
}

void ReferenceContext::uniform1f (deInt32 location, const float v0)
{
	uniform1fv(location, 1, &v0);
}

void ReferenceContext::uniform1i (deInt32 location, deInt32 v0)
{
	uniform1iv(location, 1, &v0);
}

void ReferenceContext::uniform1fv (deInt32 location, deInt32 count, const float* v)
{
	uniformv(location, glu::TYPE_FLOAT, count, v);
}

void ReferenceContext::uniform2fv (deInt32 location, deInt32 count, const float* v)
{
	uniformv(location, glu::TYPE_FLOAT_VEC2, count, v);
}

void ReferenceContext::uniform3fv (deInt32 location, deInt32 count, const float* v)
{
	uniformv(location, glu::TYPE_FLOAT_VEC3, count, v);
}

void ReferenceContext::uniform4fv (deInt32 location, deInt32 count, const float* v)
{
	uniformv(location, glu::TYPE_FLOAT_VEC4, count, v);
}

void ReferenceContext::uniform2iv (deInt32 location, deInt32 count, const deInt32* v)
{
	uniformv(location, glu::TYPE_INT_VEC2, count, v);
}

void ReferenceContext::uniform3iv (deInt32 location, deInt32 count, const deInt32* v)
{
	uniformv(location, glu::TYPE_INT_VEC3, count, v);
}

void ReferenceContext::uniform4iv (deInt32 location, deInt32 count, const deInt32* v)
{
	uniformv(location, glu::TYPE_INT_VEC4, count, v);
}

void ReferenceContext::uniformMatrix3fv (deInt32 location, deInt32 count, deBool transpose, const float *value)
{
	RC_IF_ERROR(m_currentProgram == DE_NULL, GL_INVALID_OPERATION, RC_RET_VOID);

	std::vector<sglr::UniformSlot>& uniforms = m_currentProgram->m_program->m_uniforms;

	if (location == -1)
		return;

	RC_IF_ERROR(location < 0 || (size_t)location >= uniforms.size(), GL_INVALID_OPERATION, RC_RET_VOID);

	if (count == 0)
		return;

	RC_IF_ERROR(transpose != GL_TRUE && transpose != GL_FALSE, GL_INVALID_ENUM, RC_RET_VOID);

	switch (uniforms[location].type)
	{
		case glu::TYPE_FLOAT_MAT3:
			RC_IF_ERROR(count > 1, GL_INVALID_OPERATION, RC_RET_VOID);

			if (transpose == GL_FALSE) // input is column major => transpose from column major to internal row major
				for (int row = 0; row < 3; ++row)
				for (int col = 0; col < 3; ++col)
					uniforms[location].value.m3[row*3+col] = value[col*3+row];
			else // input is row major
				for (int row = 0; row < 3; ++row)
				for (int col = 0; col < 3; ++col)
					uniforms[location].value.m3[row*3+col] = value[row*3+col];

			break;

		default:
			setError(GL_INVALID_OPERATION);
			return;
	}
}

void ReferenceContext::uniformMatrix4fv (deInt32 location, deInt32 count, deBool transpose, const float *value)
{
	RC_IF_ERROR(m_currentProgram == DE_NULL, GL_INVALID_OPERATION, RC_RET_VOID);

	std::vector<sglr::UniformSlot>& uniforms = m_currentProgram->m_program->m_uniforms;

	if (location == -1)
		return;

	RC_IF_ERROR(location < 0 || (size_t)location >= uniforms.size(), GL_INVALID_OPERATION, RC_RET_VOID);

	if (count == 0)
		return;

	RC_IF_ERROR(transpose != GL_TRUE && transpose != GL_FALSE, GL_INVALID_ENUM, RC_RET_VOID);

	switch (uniforms[location].type)
	{
		case glu::TYPE_FLOAT_MAT4:
			RC_IF_ERROR(count > 1, GL_INVALID_OPERATION, RC_RET_VOID);

			if (transpose == GL_FALSE) // input is column major => transpose from column major to internal row major
				for (int row = 0; row < 4; ++row)
				for (int col = 0; col < 4; ++col)
					uniforms[location].value.m4[row*3+col] = value[col*3+row];
			else // input is row major
				for (int row = 0; row < 4; ++row)
				for (int col = 0; col < 4; ++col)
					uniforms[location].value.m4[row*3+col] = value[row*3+col];

			break;

		default:
			setError(GL_INVALID_OPERATION);
			return;
	}
}

deInt32 ReferenceContext::getUniformLocation (deUint32 program, const char *name)
{
	ShaderProgramObjectContainer* shaderProg = m_programs.find(program);
	RC_IF_ERROR(shaderProg == DE_NULL, GL_INVALID_OPERATION, -1);

	std::vector<sglr::UniformSlot>& uniforms = shaderProg->m_program->m_uniforms;

	for (size_t i = 0; i < uniforms.size(); ++i)
		if (name && deStringEqual(uniforms[i].name.c_str(), name))
			return (int)i;

	return -1;
}

void ReferenceContext::lineWidth (float w)
{
	RC_IF_ERROR(w < 0.0f, GL_INVALID_VALUE, RC_RET_VOID);
	m_lineWidth = w;
}

void ReferenceContext::deleteVertexArray (rc::VertexArray* vertexArray)
{
	if (m_vertexArrayBinding == vertexArray)
		bindVertexArray(0);

	if (vertexArray->m_elementArrayBufferBinding)
		m_buffers.releaseReference(vertexArray->m_elementArrayBufferBinding);

	for (size_t ndx = 0; ndx < vertexArray->m_arrays.size(); ++ndx)
		if (vertexArray->m_arrays[ndx].bufferBinding)
			m_buffers.releaseReference(vertexArray->m_arrays[ndx].bufferBinding);

	DE_ASSERT(vertexArray->getRefCount() == 1);
	m_vertexArrays.releaseReference(vertexArray);
}

void ReferenceContext::deleteProgramObject (rc::ShaderProgramObjectContainer* sp)
{
	// Unbinding program will delete it
	if (m_currentProgram == sp && sp->m_deleteFlag)
	{
		useProgram(0);
		return;
	}

	// Unbinding program will NOT delete it
	if (m_currentProgram == sp)
		useProgram(0);

	DE_ASSERT(sp->getRefCount() == 1);
	m_programs.releaseReference(sp);
}

void ReferenceContext::drawArrays (deUint32 mode, int first, int count)
{
	drawArraysInstanced(mode, first, count, 1);
}

void ReferenceContext::drawArraysInstanced (deUint32 mode, int first, int count, int instanceCount)
{
	// Error conditions
	{
		RC_IF_ERROR(first < 0 || count < 0 || instanceCount < 0, GL_INVALID_VALUE, RC_RET_VOID);

		if (!predrawErrorChecks(mode))
			return;
	}

	// All is ok
	{
		const rr::PrimitiveType primitiveType = sglr::rr_util::mapGLPrimitiveType(mode);

		drawWithReference(rr::PrimitiveList(primitiveType, count, first), instanceCount);
	}
}

void ReferenceContext::drawElements (deUint32 mode, int count, deUint32 type, const void *indices)
{
	drawElementsInstanced(mode, count, type, indices, 1);
}

void ReferenceContext::drawElementsBaseVertex (deUint32 mode, int count, deUint32 type, const void *indices, int baseVertex)
{
	drawElementsInstancedBaseVertex(mode, count, type, indices, 1, baseVertex);
}

void ReferenceContext::drawElementsInstanced (deUint32 mode, int count, deUint32 type, const void *indices, int instanceCount)
{
	drawElementsInstancedBaseVertex(mode, count, type, indices, instanceCount, 0);
}

void ReferenceContext::drawElementsInstancedBaseVertex (deUint32 mode, int count, deUint32 type, const void *indices, int instanceCount, int baseVertex)
{
	rc::VertexArray& vao = (m_vertexArrayBinding) ? (*m_vertexArrayBinding) : (m_clientVertexArray);

	// Error conditions
	{
		RC_IF_ERROR(type != GL_UNSIGNED_BYTE &&
					type != GL_UNSIGNED_SHORT &&
					type != GL_UNSIGNED_INT, GL_INVALID_ENUM, RC_RET_VOID);
		RC_IF_ERROR(count < 0 || instanceCount < 0, GL_INVALID_VALUE, RC_RET_VOID);

		if (!predrawErrorChecks(mode))
			return;
	}

	// All is ok
	{
		const rr::PrimitiveType primitiveType	= sglr::rr_util::mapGLPrimitiveType(mode);
		const void*				indicesPtr		= (vao.m_elementArrayBufferBinding) ? (vao.m_elementArrayBufferBinding->getData() + ((const deUint8*)indices - (const deUint8*)DE_NULL)) : (indices);

		drawWithReference(rr::PrimitiveList(primitiveType, count, rr::DrawIndices(indicesPtr, sglr::rr_util::mapGLIndexType(type), baseVertex)), instanceCount);
	}
}

void ReferenceContext::drawRangeElements (deUint32 mode, deUint32 start, deUint32 end, int count, deUint32 type, const void *indices)
{
	RC_IF_ERROR(end < start, GL_INVALID_VALUE, RC_RET_VOID);

	drawElements(mode, count, type, indices);
}

void ReferenceContext::drawRangeElementsBaseVertex (deUint32 mode, deUint32 start, deUint32 end, int count, deUint32 type, const void *indices, int baseVertex)
{
	RC_IF_ERROR(end < start, GL_INVALID_VALUE, RC_RET_VOID);

	drawElementsBaseVertex(mode, count, type, indices, baseVertex);
}

void ReferenceContext::drawArraysIndirect (deUint32 mode, const void *indirect)
{
	struct DrawArraysIndirectCommand
	{
		deUint32 count;
		deUint32 primCount;
		deUint32 first;
		deUint32 reservedMustBeZero;
	};

	const DrawArraysIndirectCommand* command;

	// Check errors

	if (!predrawErrorChecks(mode))
		return;

	// Check pointer validity

	RC_IF_ERROR(m_drawIndirectBufferBinding == DE_NULL, GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(!deIsAlignedPtr(indirect, 4), GL_INVALID_OPERATION, RC_RET_VOID);

	// \note watch for overflows, indirect might be close to 0xFFFFFFFF and indirect+something might overflow
	RC_IF_ERROR((size_t)((const char*)indirect - (const char*)DE_NULL)                                     > (size_t)m_drawIndirectBufferBinding->getSize(), GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR((size_t)((const char*)indirect - (const char*)DE_NULL) + sizeof(DrawArraysIndirectCommand) > (size_t)m_drawIndirectBufferBinding->getSize(), GL_INVALID_OPERATION, RC_RET_VOID);

	// Check values

	command = (const DrawArraysIndirectCommand*)(m_drawIndirectBufferBinding->getData() + ((const char*)indirect - (const char*)DE_NULL));
	RC_IF_ERROR(command->reservedMustBeZero != 0, GL_INVALID_OPERATION, RC_RET_VOID);

	// draw
	drawArraysInstanced(mode, command->first, command->count, command->primCount);
}

void ReferenceContext::drawElementsIndirect	(deUint32 mode, deUint32 type, const void *indirect)
{
	struct DrawElementsIndirectCommand
	{
		deUint32 count;
		deUint32 primCount;
		deUint32 firstIndex;
		deInt32  baseVertex;
		deUint32 reservedMustBeZero;
	};

	const DrawElementsIndirectCommand* command;

	// Check errors

	if (!predrawErrorChecks(mode))
		return;

	RC_IF_ERROR(type != GL_UNSIGNED_BYTE &&
				type != GL_UNSIGNED_SHORT &&
				type != GL_UNSIGNED_INT, GL_INVALID_ENUM, RC_RET_VOID);

	RC_IF_ERROR(!getBufferBinding(GL_ELEMENT_ARRAY_BUFFER), GL_INVALID_OPERATION, RC_RET_VOID);

	// Check pointer validity

	RC_IF_ERROR(m_drawIndirectBufferBinding == DE_NULL, GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR(!deIsAlignedPtr(indirect, 4), GL_INVALID_OPERATION, RC_RET_VOID);

	// \note watch for overflows, indirect might be close to 0xFFFFFFFF and indirect+something might overflow
	RC_IF_ERROR((size_t)((const char*)indirect - (const char*)DE_NULL)                                       > (size_t)m_drawIndirectBufferBinding->getSize(), GL_INVALID_OPERATION, RC_RET_VOID);
	RC_IF_ERROR((size_t)((const char*)indirect - (const char*)DE_NULL) + sizeof(DrawElementsIndirectCommand) > (size_t)m_drawIndirectBufferBinding->getSize(), GL_INVALID_OPERATION, RC_RET_VOID);

	// Check values

	command = (const DrawElementsIndirectCommand*)(m_drawIndirectBufferBinding->getData() + ((const char*)indirect - (const char*)DE_NULL));
	RC_IF_ERROR(command->reservedMustBeZero != 0, GL_INVALID_OPERATION, RC_RET_VOID);

	// Check command error conditions
	RC_IF_ERROR((int)command->count < 0 || (int)command->primCount < 0, GL_INVALID_VALUE, RC_RET_VOID);

	// Draw
	{
		const size_t			sizeOfType		= (type == GL_UNSIGNED_BYTE) ?  (1) : ((type == GL_UNSIGNED_SHORT) ? (2) : (4));
		const void*				indicesPtr		= (deUint8*)DE_NULL + (command->firstIndex * sizeOfType);

		drawElementsInstancedBaseVertex(mode, (int)command->count, type, indicesPtr, (int)command->primCount, command->baseVertex);
	}
}

void ReferenceContext::multiDrawArrays (deUint32 mode, const int* first, const int* count, int primCount)
{
	DE_UNREF(mode);
	DE_UNREF(first);
	DE_UNREF(count);
	DE_UNREF(primCount);

	// not supported in gles, prevent accidental use
	DE_ASSERT(false);
}

void ReferenceContext::multiDrawElements (deUint32 mode, const int* count, deUint32 type, const void** indices, int primCount)
{
	DE_UNREF(mode);
	DE_UNREF(count);
	DE_UNREF(type);
	DE_UNREF(indices);
	DE_UNREF(primCount);

	// not supported in gles, prevent accidental use
	DE_ASSERT(false);
}

void ReferenceContext::multiDrawElementsBaseVertex (deUint32 mode, const int* count, deUint32 type, const void** indices, int primCount, const int* baseVertex)
{
	DE_UNREF(mode);
	DE_UNREF(count);
	DE_UNREF(type);
	DE_UNREF(indices);
	DE_UNREF(primCount);
	DE_UNREF(baseVertex);

	// not supported in gles, prevent accidental use
	DE_ASSERT(false);
}

bool ReferenceContext::predrawErrorChecks (deUint32 mode)
{
	RC_IF_ERROR(mode != GL_POINTS &&
				mode != GL_LINE_STRIP && mode != GL_LINE_LOOP && mode != GL_LINES &&
				mode != GL_TRIANGLE_STRIP && mode != GL_TRIANGLE_FAN && mode != GL_TRIANGLES &&
				mode != GL_LINES_ADJACENCY && mode != GL_LINE_STRIP_ADJACENCY &&
				mode != GL_TRIANGLES_ADJACENCY && mode != GL_TRIANGLE_STRIP_ADJACENCY,
				GL_INVALID_ENUM, false);

	// \todo [jarkko] Uncomment following code when the buffer mapping support is added
	//for (size_t ndx = 0; ndx < vao.m_arrays.size(); ++ndx)
	//	if (vao.m_arrays[ndx].enabled && vao.m_arrays[ndx].bufferBinding && vao.m_arrays[ndx].bufferBinding->isMapped)
	//		RC_ERROR_RET(GL_INVALID_OPERATION, RC_RET_VOID);

	RC_IF_ERROR(checkFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, GL_INVALID_FRAMEBUFFER_OPERATION, false);

	// Geometry shader checks
	if (m_currentProgram && m_currentProgram->m_program->m_hasGeometryShader)
	{
		RC_IF_ERROR(m_currentProgram->m_program->rr::GeometryShader::getInputType() == rr::GEOMETRYSHADERINPUTTYPE_POINTS && mode != GL_POINTS, GL_INVALID_OPERATION, false);

		RC_IF_ERROR(m_currentProgram->m_program->rr::GeometryShader::getInputType() == rr::GEOMETRYSHADERINPUTTYPE_LINES &&
			(mode != GL_LINES &&
			 mode != GL_LINE_STRIP &&
			 mode != GL_LINE_LOOP),
			 GL_INVALID_OPERATION, false);

		RC_IF_ERROR(m_currentProgram->m_program->rr::GeometryShader::getInputType() == rr::GEOMETRYSHADERINPUTTYPE_TRIANGLES &&
			(mode != GL_TRIANGLES &&
			 mode != GL_TRIANGLE_STRIP &&
			 mode != GL_TRIANGLE_FAN),
			 GL_INVALID_OPERATION, false);

		RC_IF_ERROR(m_currentProgram->m_program->rr::GeometryShader::getInputType() == rr::GEOMETRYSHADERINPUTTYPE_LINES_ADJACENCY &&
			(mode != GL_LINES_ADJACENCY &&
			 mode != GL_LINE_STRIP_ADJACENCY),
			 GL_INVALID_OPERATION, false);

		RC_IF_ERROR(m_currentProgram->m_program->rr::GeometryShader::getInputType() == rr::GEOMETRYSHADERINPUTTYPE_TRIANGLES_ADJACENCY &&
			(mode != GL_TRIANGLES_ADJACENCY &&
			 mode != GL_TRIANGLE_STRIP_ADJACENCY),
			 GL_INVALID_OPERATION, false);
	}

	return true;
}

static rr::PrimitiveType getPrimitiveBaseType (rr::PrimitiveType derivedType)
{
	switch (derivedType)
	{
		case rr::PRIMITIVETYPE_TRIANGLES:
		case rr::PRIMITIVETYPE_TRIANGLE_STRIP:
		case rr::PRIMITIVETYPE_TRIANGLE_FAN:
		case rr::PRIMITIVETYPE_TRIANGLES_ADJACENCY:
		case rr::PRIMITIVETYPE_TRIANGLE_STRIP_ADJACENCY:
			return rr::PRIMITIVETYPE_TRIANGLES;

		case rr::PRIMITIVETYPE_LINES:
		case rr::PRIMITIVETYPE_LINE_STRIP:
		case rr::PRIMITIVETYPE_LINE_LOOP:
		case rr::PRIMITIVETYPE_LINES_ADJACENCY:
		case rr::PRIMITIVETYPE_LINE_STRIP_ADJACENCY:
			return rr::PRIMITIVETYPE_LINES;

		case rr::PRIMITIVETYPE_POINTS:
			return rr::PRIMITIVETYPE_POINTS;

		default:
			DE_ASSERT(false);
			return rr::PRIMITIVETYPE_LAST;
	}
}

static deUint32 getFixedRestartIndex (rr::IndexType indexType)
{
	switch (indexType)
	{
		case rr::INDEXTYPE_UINT8:		return 0xFF;
		case rr::INDEXTYPE_UINT16:		return 0xFFFF;
		case rr::INDEXTYPE_UINT32:		return 0xFFFFFFFFul;

		case rr::INDEXTYPE_LAST:
		default:
			DE_ASSERT(false);
			return 0;
	}
}

void ReferenceContext::drawWithReference (const rr::PrimitiveList& primitives, int instanceCount)
{
	// undefined results
	if (m_currentProgram == DE_NULL)
		return;

	rr::MultisamplePixelBufferAccess	colorBuf0	= getDrawColorbuffer();
	rr::MultisamplePixelBufferAccess	depthBuf	= getDepthMultisampleAccess(getDrawDepthbuffer());
	rr::MultisamplePixelBufferAccess	stencilBuf	= getStencilMultisampleAccess(getDrawStencilbuffer());
	const bool							hasStencil	= !isEmpty(stencilBuf);
	const int							stencilBits	= (hasStencil) ? (getNumStencilBits(stencilBuf.raw().getFormat())) : (0);

	const rr::RenderTarget				renderTarget(colorBuf0, depthBuf, stencilBuf);
	const rr::Program					program		(m_currentProgram->m_program->getVertexShader(),
													 m_currentProgram->m_program->getFragmentShader(),
													 (m_currentProgram->m_program->m_hasGeometryShader) ? (m_currentProgram->m_program->getGeometryShader()) : (DE_NULL));
	rr::RenderState						state		((rr::ViewportState)(colorBuf0));

	const rr::Renderer					referenceRenderer;
	std::vector<rr::VertexAttrib>		vertexAttribs;

	// Gen state
	{
		const rr::PrimitiveType	baseType							= getPrimitiveBaseType(primitives.getPrimitiveType());
		const bool				polygonOffsetEnabled				= (baseType == rr::PRIMITIVETYPE_TRIANGLES) ? (m_polygonOffsetFillEnabled) : (false);

		//state.cullMode											= m_cullMode

		state.fragOps.scissorTestEnabled							= m_scissorEnabled;
		state.fragOps.scissorRectangle								= rr::WindowRectangle(m_scissorBox.x(), m_scissorBox.y(), m_scissorBox.z(), m_scissorBox.w());

		state.fragOps.numStencilBits								= stencilBits;
		state.fragOps.stencilTestEnabled							= m_stencilTestEnabled;

		for (int faceType = 0; faceType < rr::FACETYPE_LAST; faceType++)
		{
			state.fragOps.stencilStates[faceType].compMask	= m_stencil[faceType].opMask;
			state.fragOps.stencilStates[faceType].writeMask	= m_stencil[faceType].writeMask;
			state.fragOps.stencilStates[faceType].ref		= m_stencil[faceType].ref;
			state.fragOps.stencilStates[faceType].func		= sglr::rr_util::mapGLTestFunc(m_stencil[faceType].func);
			state.fragOps.stencilStates[faceType].sFail		= sglr::rr_util::mapGLStencilOp(m_stencil[faceType].opStencilFail);
			state.fragOps.stencilStates[faceType].dpFail	= sglr::rr_util::mapGLStencilOp(m_stencil[faceType].opDepthFail);
			state.fragOps.stencilStates[faceType].dpPass	= sglr::rr_util::mapGLStencilOp(m_stencil[faceType].opDepthPass);
		}

		state.fragOps.depthTestEnabled								= m_depthTestEnabled;
		state.fragOps.depthFunc										= sglr::rr_util::mapGLTestFunc(m_depthFunc);
		state.fragOps.depthMask										= m_depthMask;

		state.fragOps.blendMode										= m_blendEnabled ? rr::BLENDMODE_STANDARD : rr::BLENDMODE_NONE;
		state.fragOps.blendRGBState.equation						= sglr::rr_util::mapGLBlendEquation(m_blendModeRGB);
		state.fragOps.blendRGBState.srcFunc							= sglr::rr_util::mapGLBlendFunc(m_blendFactorSrcRGB);
		state.fragOps.blendRGBState.dstFunc							= sglr::rr_util::mapGLBlendFunc(m_blendFactorDstRGB);
		state.fragOps.blendAState.equation							= sglr::rr_util::mapGLBlendEquation(m_blendModeAlpha);
		state.fragOps.blendAState.srcFunc							= sglr::rr_util::mapGLBlendFunc(m_blendFactorSrcAlpha);
		state.fragOps.blendAState.dstFunc							= sglr::rr_util::mapGLBlendFunc(m_blendFactorDstAlpha);
		state.fragOps.blendColor									= m_blendColor;

		state.fragOps.sRGBEnabled									= m_sRGBUpdateEnabled;

		state.fragOps.colorMask										= m_colorMask;

		state.fragOps.depthClampEnabled								= m_depthClampEnabled;

		state.viewport.rect											= rr::WindowRectangle(m_viewport.x(), m_viewport.y(), m_viewport.z(), m_viewport.w());
		state.viewport.zn											= m_depthRangeNear;
		state.viewport.zf											= m_depthRangeFar;

		//state.point.pointSize										= m_pointSize;
		state.line.lineWidth										= m_lineWidth;

		state.fragOps.polygonOffsetEnabled							= polygonOffsetEnabled;
		state.fragOps.polygonOffsetFactor							= m_polygonOffsetFactor;
		state.fragOps.polygonOffsetUnits							= m_polygonOffsetUnits;

		{
			const rr::IndexType indexType = primitives.getIndexType();

			if (m_primitiveRestartFixedIndex && indexType != rr::INDEXTYPE_LAST)
			{
				state.restart.enabled = true;
				state.restart.restartIndex = getFixedRestartIndex(indexType);
			}
			else if (m_primitiveRestartSettableIndex)
			{
				// \note PRIMITIVE_RESTART is active for non-indexed (DrawArrays) operations too.
				state.restart.enabled = true;
				state.restart.restartIndex = m_primitiveRestartIndex;
			}
			else
			{
				state.restart.enabled = false;
			}
		}

		state.provokingVertexConvention								= (m_provokingFirstVertexConvention) ? (rr::PROVOKINGVERTEX_FIRST) : (rr::PROVOKINGVERTEX_LAST);
	}

	// gen attributes
	{
		rc::VertexArray& vao = (m_vertexArrayBinding) ? (*m_vertexArrayBinding) : (m_clientVertexArray);

		vertexAttribs.resize(vao.m_arrays.size());
		for (size_t ndx = 0; ndx < vao.m_arrays.size(); ++ndx)
		{
			if (!vao.m_arrays[ndx].enabled)
			{
				vertexAttribs[ndx].type = rr::VERTEXATTRIBTYPE_DONT_CARE; // reading with wrong type is allowed, but results are undefined
				vertexAttribs[ndx].generic = m_currentAttribs[ndx];
			}
			else if (vao.m_arrays[ndx].bufferDeleted)
			{
				vertexAttribs[ndx].type = rr::VERTEXATTRIBTYPE_DONT_CARE; // reading from deleted buffer, output zeros
				vertexAttribs[ndx].generic = tcu::Vec4(0, 0, 0, 0);
			}
			else
			{
				vertexAttribs[ndx].type				= (vao.m_arrays[ndx].integer) ?
														(sglr::rr_util::mapGLPureIntegerVertexAttributeType(vao.m_arrays[ndx].type)) :
														(sglr::rr_util::mapGLFloatVertexAttributeType(vao.m_arrays[ndx].type, vao.m_arrays[ndx].normalized, vao.m_arrays[ndx].size, this->getType()));
				vertexAttribs[ndx].size				= sglr::rr_util::mapGLSize(vao.m_arrays[ndx].size);
				vertexAttribs[ndx].stride			= vao.m_arrays[ndx].stride;
				vertexAttribs[ndx].instanceDivisor	= vao.m_arrays[ndx].divisor;
				vertexAttribs[ndx].pointer			= (vao.m_arrays[ndx].bufferBinding) ? (vao.m_arrays[ndx].bufferBinding->getData() + ((const deUint8*)vao.m_arrays[ndx].pointer - (const deUint8*)DE_NULL)) : (vao.m_arrays[ndx].pointer);
			}
		}
	}

	// Set shader samplers
	for (size_t uniformNdx = 0; uniformNdx < m_currentProgram->m_program->m_uniforms.size(); ++uniformNdx)
	{
		const tcu::Sampler::DepthStencilMode	depthStencilMode	= tcu::Sampler::MODE_DEPTH; // \todo[jarkko] support sampler state
		const int								texNdx				= m_currentProgram->m_program->m_uniforms[uniformNdx].value.i;

		switch (m_currentProgram->m_program->m_uniforms[uniformNdx].type)
		{
			case glu::TYPE_SAMPLER_1D:
			case glu::TYPE_UINT_SAMPLER_1D:
			case glu::TYPE_INT_SAMPLER_1D:
			{
				rc::Texture1D* tex = DE_NULL;

				if (texNdx >= 0 && (size_t)texNdx < m_textureUnits.size())
					tex = (m_textureUnits[texNdx].tex1DBinding) ? (m_textureUnits[texNdx].tex1DBinding) : (&m_textureUnits[texNdx].default1DTex);

				if (tex && tex->isComplete())
				{
					tex->updateView(depthStencilMode);
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.tex1D = tex;
				}
				else
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.tex1D = &m_emptyTex1D;

				break;
			}
			case glu::TYPE_SAMPLER_2D:
			case glu::TYPE_UINT_SAMPLER_2D:
			case glu::TYPE_INT_SAMPLER_2D:
			{
				rc::Texture2D* tex = DE_NULL;

				if (texNdx >= 0 && (size_t)texNdx < m_textureUnits.size())
					tex = (m_textureUnits[texNdx].tex2DBinding) ? (m_textureUnits[texNdx].tex2DBinding) : (&m_textureUnits[texNdx].default2DTex);

				if (tex && tex->isComplete())
				{
					tex->updateView(depthStencilMode);
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.tex2D = tex;
				}
				else
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.tex2D = &m_emptyTex2D;

				break;
			}
			case glu::TYPE_SAMPLER_CUBE:
			case glu::TYPE_UINT_SAMPLER_CUBE:
			case glu::TYPE_INT_SAMPLER_CUBE:
			{
				rc::TextureCube* tex = DE_NULL;

				if (texNdx >= 0 && (size_t)texNdx < m_textureUnits.size())
					tex = (m_textureUnits[texNdx].texCubeBinding) ? (m_textureUnits[texNdx].texCubeBinding) : (&m_textureUnits[texNdx].defaultCubeTex);

				if (tex && tex->isComplete())
				{
					tex->updateView(depthStencilMode);
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.texCube = tex;
				}
				else
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.texCube = &m_emptyTexCube;

				break;
			}
			case glu::TYPE_SAMPLER_2D_ARRAY:
			case glu::TYPE_UINT_SAMPLER_2D_ARRAY:
			case glu::TYPE_INT_SAMPLER_2D_ARRAY:
			{
				rc::Texture2DArray* tex = DE_NULL;

				if (texNdx >= 0 && (size_t)texNdx < m_textureUnits.size())
					tex = (m_textureUnits[texNdx].tex2DArrayBinding) ? (m_textureUnits[texNdx].tex2DArrayBinding) : (&m_textureUnits[texNdx].default2DArrayTex);

				if (tex && tex->isComplete())
				{
					tex->updateView(depthStencilMode);
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.tex2DArray = tex;
				}
				else
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.tex2DArray = &m_emptyTex2DArray;

				break;
			}
			case glu::TYPE_SAMPLER_3D:
			case glu::TYPE_UINT_SAMPLER_3D:
			case glu::TYPE_INT_SAMPLER_3D:
			{
				rc::Texture3D* tex = DE_NULL;

				if (texNdx >= 0 && (size_t)texNdx < m_textureUnits.size())
					tex = (m_textureUnits[texNdx].tex3DBinding) ? (m_textureUnits[texNdx].tex3DBinding) : (&m_textureUnits[texNdx].default3DTex);

				if (tex && tex->isComplete())
				{
					tex->updateView(depthStencilMode);
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.tex3D = tex;
				}
				else
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.tex3D = &m_emptyTex3D;

				break;
			}
			case glu::TYPE_SAMPLER_CUBE_ARRAY:
			case glu::TYPE_UINT_SAMPLER_CUBE_ARRAY:
			case glu::TYPE_INT_SAMPLER_CUBE_ARRAY:
			{
				rc::TextureCubeArray* tex = DE_NULL;

				if (texNdx >= 0 && (size_t)texNdx < m_textureUnits.size())
					tex = (m_textureUnits[texNdx].texCubeArrayBinding) ? (m_textureUnits[texNdx].texCubeArrayBinding) : (&m_textureUnits[texNdx].defaultCubeArrayTex);

				if (tex && tex->isComplete())
				{
					tex->updateView(depthStencilMode);
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.texCubeArray = tex;
				}
				else
					m_currentProgram->m_program->m_uniforms[uniformNdx].sampler.texCubeArray = &m_emptyTexCubeArray;

				break;
			}
			default:
				// nothing
				break;
		}
	}

	referenceRenderer.drawInstanced(rr::DrawCommand(state, renderTarget, program, (int)vertexAttribs.size(), &vertexAttribs[0], primitives), instanceCount);
}

deUint32 ReferenceContext::createProgram (ShaderProgram* program)
{
	int name = m_programs.allocateName();

	m_programs.insert(new rc::ShaderProgramObjectContainer(name, program));

	return name;
}

void ReferenceContext::useProgram (deUint32 program)
{
	rc::ShaderProgramObjectContainer* shaderProg			= DE_NULL;
	rc::ShaderProgramObjectContainer* programToBeDeleted	= DE_NULL;

	if (program)
	{
		shaderProg = m_programs.find(program);

		// shader has not been linked
		if (!shaderProg || shaderProg->m_deleteFlag)
			RC_ERROR_RET(GL_INVALID_OPERATION, RC_RET_VOID);
	}

	if (m_currentProgram && m_currentProgram->m_deleteFlag)
		programToBeDeleted = m_currentProgram;

	m_currentProgram = shaderProg;

	if (programToBeDeleted)
	{
		DE_ASSERT(programToBeDeleted->getRefCount() == 1);
		deleteProgramObject(programToBeDeleted);
	}
}

void ReferenceContext::deleteProgram (deUint32 program)
{
	if (!program)
		return;

	rc::ShaderProgramObjectContainer* shaderProg = m_programs.find(program);
	if (shaderProg)
	{
		if (shaderProg == m_currentProgram)
		{
			m_currentProgram->m_deleteFlag = true;
		}
		else
		{
			DE_ASSERT(shaderProg->getRefCount() == 1);
			m_programs.releaseReference(shaderProg);
		}
	}
}

void ReferenceContext::readPixels (int x, int y, int width, int height, deUint32 format, deUint32 type, void* data)
{
	rr::MultisamplePixelBufferAccess	src = getReadColorbuffer();
	TextureFormat						transferFmt;

	// Map transfer format.
	transferFmt = glu::mapGLTransferFormat(format, type);
	RC_IF_ERROR(transferFmt.order	== TextureFormat::CHANNELORDER_LAST ||
				transferFmt.type	== TextureFormat::CHANNELTYPE_LAST, GL_INVALID_ENUM, RC_RET_VOID);

	// Clamp input values
	const int copyX			= deClamp32(x,		0, src.raw().getHeight());
	const int copyY			= deClamp32(y,		0, src.raw().getDepth());
	const int copyWidth		= deClamp32(width,	0, src.raw().getHeight()-x);
	const int copyHeight	= deClamp32(height,	0, src.raw().getDepth()-y);

	PixelBufferAccess dst(transferFmt, width, height, 1, deAlign32(width*transferFmt.getPixelSize(), m_pixelPackAlignment), 0, getPixelPackPtr(data));
	rr::resolveMultisampleColorBuffer(tcu::getSubregion(dst, 0, 0, copyWidth, copyHeight), rr::getSubregion(src, copyX, copyY, copyWidth, copyHeight));
}

deUint32 ReferenceContext::getError (void)
{
	deUint32 err = m_lastError;
	m_lastError = GL_NO_ERROR;
	return err;
}

void ReferenceContext::finish (void)
{
}

inline void ReferenceContext::setError (deUint32 error)
{
	if (m_lastError == GL_NO_ERROR)
		m_lastError = error;
}

void ReferenceContext::getIntegerv (deUint32 pname, int* param)
{
	switch (pname)
	{
		case GL_MAX_TEXTURE_SIZE:			*param = m_limits.maxTexture2DSize;			break;
		case GL_MAX_CUBE_MAP_TEXTURE_SIZE:	*param = m_limits.maxTextureCubeSize;		break;
		case GL_MAX_ARRAY_TEXTURE_LAYERS:	*param = m_limits.maxTexture2DArrayLayers;	break;
		case GL_MAX_3D_TEXTURE_SIZE:		*param = m_limits.maxTexture3DSize;			break;
		case GL_MAX_RENDERBUFFER_SIZE:		*param = m_limits.maxRenderbufferSize;		break;
		case GL_MAX_TEXTURE_IMAGE_UNITS:	*param = m_limits.maxTextureImageUnits;		break;
		case GL_MAX_VERTEX_ATTRIBS:			*param = m_limits.maxVertexAttribs;			break;

		default:
			setError(GL_INVALID_ENUM);
			break;
	}
}

const char* ReferenceContext::getString (deUint32 pname)
{
	switch (pname)
	{
		case GL_EXTENSIONS:		return m_limits.extensionStr.c_str();

		default:
			setError(GL_INVALID_ENUM);
			return DE_NULL;
	}
}

namespace rc
{

TextureLevelArray::TextureLevelArray (void)
{
}

TextureLevelArray::~TextureLevelArray (void)
{
	clear();
}

void TextureLevelArray::clear (void)
{
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(m_data) == DE_LENGTH_OF_ARRAY(m_access));

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(m_data); ndx++)
	{
		m_data[ndx].clear();
		m_access[ndx] = PixelBufferAccess();
	}
}

void TextureLevelArray::allocLevel (int level, const tcu::TextureFormat& format, int width, int height, int depth)
{
	const int dataSize = format.getPixelSize()*width*height*depth;

	DE_ASSERT(deInBounds32(level, 0, DE_LENGTH_OF_ARRAY(m_data)));

	if (hasLevel(level))
		clearLevel(level);

	m_data[level].setStorage(dataSize);
	m_access[level] = PixelBufferAccess(format, width, height, depth, m_data[level].getPtr());
}

void TextureLevelArray::clearLevel (int level)
{
	DE_ASSERT(deInBounds32(level, 0, DE_LENGTH_OF_ARRAY(m_data)));

	m_data[level].clear();
	m_access[level] = PixelBufferAccess();
}

void TextureLevelArray::updateSamplerMode (tcu::Sampler::DepthStencilMode mode)
{
	for (int levelNdx = 0; hasLevel(levelNdx); ++levelNdx)
		m_effectiveAccess[levelNdx] = tcu::getEffectiveDepthStencilAccess(m_access[levelNdx], mode);
}

Texture::Texture (deUint32 name, Type type)
	: NamedObject	(name)
	, m_type		(type)
	, m_immutable	(false)
	, m_sampler		(tcu::Sampler::REPEAT_GL,
					 tcu::Sampler::REPEAT_GL,
					 tcu::Sampler::REPEAT_GL,
					 tcu::Sampler::NEAREST_MIPMAP_LINEAR,
					 tcu::Sampler::LINEAR,
					 0.0f,				// LOD threshold
					 true,				// normalized coords
					 tcu::Sampler::COMPAREMODE_NONE,
					 0,					// cmp channel ndx
					 tcu::Vec4(0.0f),	// border color
					 true				// seamless cube map \todo [2014-02-19 pyry] Default value ok?
					 )
	, m_baseLevel	(0)
	, m_maxLevel	(1000)
{
}

Texture1D::Texture1D (deUint32 name)
	: Texture	(name, TYPE_1D)
	, m_view	(0, DE_NULL)
{
}

Texture1D::~Texture1D (void)
{
}

void Texture1D::allocLevel (int level, const tcu::TextureFormat& format, int width)
{
	m_levels.allocLevel(level, format, width, 1, 1);
}

bool Texture1D::isComplete (void) const
{
	const int	baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel))
	{
		const tcu::ConstPixelBufferAccess&	level0		= getLevel(baseLevel);
		const bool							mipmap		= isMipmapFilter(getSampler().minFilter);

		if (mipmap)
		{
			const TextureFormat&	format		= level0.getFormat();
			const int				w			= level0.getWidth();
			const int				numLevels	= de::min(getMaxLevel()-baseLevel+1, getNumMipLevels1D(w));

			for (int levelNdx = 1; levelNdx < numLevels; levelNdx++)
			{
				if (hasLevel(baseLevel+levelNdx))
				{
					const tcu::ConstPixelBufferAccess&	level		= getLevel(baseLevel+levelNdx);
					const int							expectedW	= getMipLevelSize(w, levelNdx);

					if (level.getWidth()	!= expectedW	||
						level.getFormat()	!= format)
						return false;
				}
				else
					return false;
			}
		}

		return true;
	}
	else
		return false;
}

tcu::Vec4 Texture1D::sample (float s, float lod) const
{
	return m_view.sample(getSampler(), s, 0.0f, lod);
}

void Texture1D::sample4 (tcu::Vec4 output[4], const float packetTexcoords[4], float lodBias) const
{
	const float texWidth = (float)m_view.getWidth();

	const float dFdx0 = packetTexcoords[1] - packetTexcoords[0];
	const float dFdx1 = packetTexcoords[3] - packetTexcoords[2];
	const float dFdy0 = packetTexcoords[2] - packetTexcoords[0];
	const float dFdy1 = packetTexcoords[3] - packetTexcoords[1];

	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
	{
		const float& dFdx = (fragNdx > 2) ? dFdx1 : dFdx0;
		const float& dFdy = (fragNdx % 2) ? dFdy1 : dFdy0;

		const float mu = de::max(de::abs(dFdx), de::abs(dFdy));
		const float p = mu * texWidth;

		const float	lod = deFloatLog2(p) + lodBias;

		output[fragNdx] = sample(packetTexcoords[fragNdx], lod);
	}
}

void Texture1D::updateView (tcu::Sampler::DepthStencilMode mode)
{
	const int baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel) && !isEmpty(getLevel(baseLevel)))
	{
		const int	width		= getLevel(baseLevel).getWidth();
		const bool	isMipmap	= isMipmapFilter(getSampler().minFilter);
		const int	numLevels	= isMipmap ? de::min(getMaxLevel()-baseLevel+1, getNumMipLevels1D(width)) : 1;

		m_levels.updateSamplerMode(mode);
		m_view = tcu::Texture2DView(numLevels, m_levels.getEffectiveLevels() + baseLevel);
	}
	else
		m_view = tcu::Texture2DView(0, DE_NULL);
}

Texture2D::Texture2D (deUint32 name)
	: Texture	(name, TYPE_2D)
	, m_view	(0, DE_NULL)
{
}

Texture2D::~Texture2D (void)
{
}

void Texture2D::allocLevel (int level, const tcu::TextureFormat& format, int width, int height)
{
	m_levels.allocLevel(level, format, width, height, 1);
}

bool Texture2D::isComplete (void) const
{
	const int	baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel))
	{
		const tcu::ConstPixelBufferAccess&	level0		= getLevel(baseLevel);
		const bool							mipmap		= isMipmapFilter(getSampler().minFilter);

		if (mipmap)
		{
			const TextureFormat&	format		= level0.getFormat();
			const int				w			= level0.getWidth();
			const int				h			= level0.getHeight();
			const int				numLevels	= de::min(getMaxLevel()-baseLevel+1, getNumMipLevels2D(w, h));

			for (int levelNdx = 1; levelNdx < numLevels; levelNdx++)
			{
				if (hasLevel(baseLevel+levelNdx))
				{
					const tcu::ConstPixelBufferAccess&	level		= getLevel(baseLevel+levelNdx);
					const int							expectedW	= getMipLevelSize(w, levelNdx);
					const int							expectedH	= getMipLevelSize(h, levelNdx);

					if (level.getWidth()	!= expectedW	||
						level.getHeight()	!= expectedH	||
						level.getFormat()	!= format)
						return false;
				}
				else
					return false;
			}
		}

		return true;
	}
	else
		return false;
}

void Texture2D::updateView (tcu::Sampler::DepthStencilMode mode)
{
	const int baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel) && !isEmpty(getLevel(baseLevel)))
	{
		// Update number of levels in mipmap pyramid.
		const int	width		= getLevel(baseLevel).getWidth();
		const int	height		= getLevel(baseLevel).getHeight();
		const bool	isMipmap	= isMipmapFilter(getSampler().minFilter);
		const int	numLevels	= isMipmap ? de::min(getMaxLevel()-baseLevel+1, getNumMipLevels2D(width, height)) : 1;

		m_levels.updateSamplerMode(mode);
		m_view = tcu::Texture2DView(numLevels, m_levels.getEffectiveLevels() + baseLevel);
	}
	else
		m_view = tcu::Texture2DView(0, DE_NULL);
}

tcu::Vec4 Texture2D::sample (float s, float t, float lod) const
{
	return m_view.sample(getSampler(), s, t, lod);
}

void Texture2D::sample4 (tcu::Vec4 output[4], const tcu::Vec2 packetTexcoords[4], float lodBias) const
{
	const float texWidth  = (float)m_view.getWidth();
	const float texHeight = (float)m_view.getHeight();

	const tcu::Vec2 dFdx0 = packetTexcoords[1] - packetTexcoords[0];
	const tcu::Vec2 dFdx1 = packetTexcoords[3] - packetTexcoords[2];
	const tcu::Vec2 dFdy0 = packetTexcoords[2] - packetTexcoords[0];
	const tcu::Vec2 dFdy1 = packetTexcoords[3] - packetTexcoords[1];

	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
	{
		const tcu::Vec2& dFdx = (fragNdx & 2) ? dFdx1 : dFdx0;
		const tcu::Vec2& dFdy = (fragNdx & 1) ? dFdy1 : dFdy0;

		const float mu = de::max(de::abs(dFdx.x()), de::abs(dFdy.x()));
		const float mv = de::max(de::abs(dFdx.y()), de::abs(dFdy.y()));
		const float p = de::max(mu * texWidth, mv * texHeight);

		const float	lod = deFloatLog2(p) + lodBias;

		output[fragNdx] = sample(packetTexcoords[fragNdx].x(), packetTexcoords[fragNdx].y(), lod);
	}
}

TextureCube::TextureCube (deUint32 name)
	: Texture(name, TYPE_CUBE_MAP)
{
}

TextureCube::~TextureCube (void)
{
}

void TextureCube::clearLevels (void)
{
	for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
		m_levels[face].clear();
}

void TextureCube::allocFace (int level, tcu::CubeFace face, const tcu::TextureFormat& format, int width, int height)
{
	m_levels[face].allocLevel(level, format, width, height, 1);
}

bool TextureCube::isComplete (void) const
{
	const int	baseLevel	= getBaseLevel();

	if (hasFace(baseLevel, tcu::CUBEFACE_NEGATIVE_X))
	{
		const int					width		= getFace(baseLevel, tcu::CUBEFACE_NEGATIVE_X).getWidth();
		const int					height		= getFace(baseLevel, tcu::CUBEFACE_NEGATIVE_X).getHeight();
		const tcu::TextureFormat&	format		= getFace(baseLevel, tcu::CUBEFACE_NEGATIVE_X).getFormat();
		const bool					mipmap		= isMipmapFilter(getSampler().minFilter);
		const int					numLevels	= mipmap ? de::min(getMaxLevel()-baseLevel+1, getNumMipLevels2D(width, height)) : 1;

		if (width != height)
			return false; // Non-square is not supported.

		// \note Level 0 is always checked for consistency
		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
		{
			const int levelW	= getMipLevelSize(width,	levelNdx);
			const int levelH	= getMipLevelSize(height,	levelNdx);

			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
			{
				if (hasFace(baseLevel+levelNdx, (tcu::CubeFace)face))
				{
					const tcu::ConstPixelBufferAccess& level = getFace(baseLevel+levelNdx, (tcu::CubeFace)face);

					if (level.getWidth()	!= levelW	||
						level.getHeight()	!= levelH	||
						level.getFormat()	!= format)
						return false;
				}
				else
					return false;
			}
		}

		return true;
	}
	else
		return false;
}

void TextureCube::updateView (tcu::Sampler::DepthStencilMode mode)
{
	const int							baseLevel	= getBaseLevel();
	const tcu::ConstPixelBufferAccess*	faces[tcu::CUBEFACE_LAST];

	deMemset(&faces[0], 0, sizeof(faces));

	if (isComplete())
	{
		const int	size		= getFace(baseLevel, tcu::CUBEFACE_NEGATIVE_X).getWidth();
		const bool	isMipmap	= isMipmapFilter(getSampler().minFilter);
		const int	numLevels	= isMipmap ? de::min(getMaxLevel()-baseLevel+1, getNumMipLevels1D(size)) : 1;

		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
		{
			m_levels[face].updateSamplerMode(mode);
			faces[face] = m_levels[face].getEffectiveLevels() + baseLevel;
		}

		m_view = tcu::TextureCubeView(numLevels, faces);
	}
	else
		m_view = tcu::TextureCubeView(0, faces);
}

tcu::Vec4 TextureCube::sample (float s, float t, float p, float lod) const
{
	return m_view.sample(getSampler(), s, t, p, lod);
}

void TextureCube::sample4 (tcu::Vec4 output[4], const tcu::Vec3 packetTexcoords[4], float lodBias) const
{
	const float cubeSide = (float)m_view.getSize();

	// Each tex coord might be in a different face.

	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
	{
		const tcu::CubeFace face		= tcu::selectCubeFace(packetTexcoords[fragNdx]);
		const tcu::Vec2		coords[4]	=
		{
			tcu::projectToFace(face, packetTexcoords[0]),
			tcu::projectToFace(face, packetTexcoords[1]),
			tcu::projectToFace(face, packetTexcoords[2]),
			tcu::projectToFace(face, packetTexcoords[3]),
		};

		const tcu::Vec2 dFdx0 = coords[1] - coords[0];
		const tcu::Vec2 dFdx1 = coords[3] - coords[2];
		const tcu::Vec2 dFdy0 = coords[2] - coords[0];
		const tcu::Vec2 dFdy1 = coords[3] - coords[1];

		const tcu::Vec2& dFdx = (fragNdx & 2) ? dFdx1 : dFdx0;
		const tcu::Vec2& dFdy = (fragNdx & 1) ? dFdy1 : dFdy0;

		const float mu = de::max(de::abs(dFdx.x()), de::abs(dFdy.x()));
		const float mv = de::max(de::abs(dFdx.y()), de::abs(dFdy.y()));
		const float p = de::max(mu * cubeSide, mv * cubeSide);

		const float	lod = deFloatLog2(p) + lodBias;

		output[fragNdx] = sample(packetTexcoords[fragNdx].x(), packetTexcoords[fragNdx].y(), packetTexcoords[fragNdx].z(), lod);
	}
}

Texture2DArray::Texture2DArray (deUint32 name)
	: Texture	(name, TYPE_2D_ARRAY)
	, m_view	(0, DE_NULL)
{
}

Texture2DArray::~Texture2DArray (void)
{
}

void Texture2DArray::allocLevel (int level, const tcu::TextureFormat& format, int width, int height, int numLayers)
{
	m_levels.allocLevel(level, format, width, height, numLayers);
}

bool Texture2DArray::isComplete (void) const
{
	const int	baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel))
	{
		const tcu::ConstPixelBufferAccess&	level0		= getLevel(baseLevel);
		const bool							mipmap		= isMipmapFilter(getSampler().minFilter);

		if (mipmap)
		{
			const TextureFormat&	format		= level0.getFormat();
			const int				w			= level0.getWidth();
			const int				h			= level0.getHeight();
			const int				numLayers	= level0.getDepth();
			const int				numLevels	= de::min(getMaxLevel()-baseLevel+1, getNumMipLevels2D(w, h));

			for (int levelNdx = 1; levelNdx < numLevels; levelNdx++)
			{
				if (hasLevel(baseLevel+levelNdx))
				{
					const tcu::ConstPixelBufferAccess&	level		= getLevel(baseLevel+levelNdx);
					const int							expectedW	= getMipLevelSize(w, levelNdx);
					const int							expectedH	= getMipLevelSize(h, levelNdx);

					if (level.getWidth()	!= expectedW	||
						level.getHeight()	!= expectedH	||
						level.getDepth()	!= numLayers	||
						level.getFormat()	!= format)
						return false;
				}
				else
					return false;
			}
		}

		return true;
	}
	else
		return false;
}

void Texture2DArray::updateView (tcu::Sampler::DepthStencilMode mode)
{
	const int baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel) && !isEmpty(getLevel(baseLevel)))
	{
		const int	width		= getLevel(baseLevel).getWidth();
		const int	height		= getLevel(baseLevel).getHeight();
		const bool	isMipmap	= isMipmapFilter(getSampler().minFilter);
		const int	numLevels	= isMipmap ? de::min(getMaxLevel()-baseLevel+1, getNumMipLevels2D(width, height)) : 1;

		m_levels.updateSamplerMode(mode);
		m_view = tcu::Texture2DArrayView(numLevels, m_levels.getEffectiveLevels() + baseLevel);
	}
	else
		m_view = tcu::Texture2DArrayView(0, DE_NULL);
}

tcu::Vec4 Texture2DArray::sample (float s, float t, float r, float lod) const
{
	return m_view.sample(getSampler(), s, t, r, lod);
}

void Texture2DArray::sample4 (tcu::Vec4 output[4], const tcu::Vec3 packetTexcoords[4], float lodBias) const
{
	const float texWidth  = (float)m_view.getWidth();
	const float texHeight = (float)m_view.getHeight();

	const tcu::Vec3 dFdx0 = packetTexcoords[1] - packetTexcoords[0];
	const tcu::Vec3 dFdx1 = packetTexcoords[3] - packetTexcoords[2];
	const tcu::Vec3 dFdy0 = packetTexcoords[2] - packetTexcoords[0];
	const tcu::Vec3 dFdy1 = packetTexcoords[3] - packetTexcoords[1];

	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
	{
		const tcu::Vec3& dFdx = (fragNdx & 2) ? dFdx1 : dFdx0;
		const tcu::Vec3& dFdy = (fragNdx & 1) ? dFdy1 : dFdy0;

		const float mu = de::max(de::abs(dFdx.x()), de::abs(dFdy.x()));
		const float mv = de::max(de::abs(dFdx.y()), de::abs(dFdy.y()));
		const float p = de::max(mu * texWidth, mv * texHeight);

		const float	lod = deFloatLog2(p) + lodBias;

		output[fragNdx] = sample(packetTexcoords[fragNdx].x(), packetTexcoords[fragNdx].y(), packetTexcoords[fragNdx].z(), lod);
	}
}

TextureCubeArray::TextureCubeArray (deUint32 name)
	: Texture	(name, TYPE_CUBE_MAP_ARRAY)
	, m_view	(0, DE_NULL)
{
}

TextureCubeArray::~TextureCubeArray (void)
{
}

void TextureCubeArray::allocLevel (int level, const tcu::TextureFormat& format, int width, int height, int numLayers)
{
	DE_ASSERT(numLayers % 6 == 0);
	m_levels.allocLevel(level, format, width, height, numLayers);
}

bool TextureCubeArray::isComplete (void) const
{
	const int	baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel))
	{
		const tcu::ConstPixelBufferAccess&	level0		= getLevel(baseLevel);
		const bool							mipmap		= isMipmapFilter(getSampler().minFilter);

		if (mipmap)
		{
			const TextureFormat&	format		= level0.getFormat();
			const int				w			= level0.getWidth();
			const int				h			= level0.getHeight();
			const int				numLayers	= level0.getDepth();
			const int				numLevels	= de::min(getMaxLevel()-baseLevel+1, getNumMipLevels2D(w, h));

			for (int levelNdx = 1; levelNdx < numLevels; levelNdx++)
			{
				if (hasLevel(baseLevel+levelNdx))
				{
					const tcu::ConstPixelBufferAccess&	level		= getLevel(baseLevel+levelNdx);
					const int							expectedW	= getMipLevelSize(w, levelNdx);
					const int							expectedH	= getMipLevelSize(h, levelNdx);

					if (level.getWidth()	!= expectedW	||
						level.getHeight()	!= expectedH	||
						level.getDepth()	!= numLayers	||
						level.getFormat()	!= format)
						return false;
				}
				else
					return false;
			}
		}

		return true;
	}
	else
		return false;
}

void TextureCubeArray::updateView (tcu::Sampler::DepthStencilMode mode)
{
	const int baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel) && !isEmpty(getLevel(baseLevel)))
	{
		const int	width		= getLevel(baseLevel).getWidth();
		const int	height		= getLevel(baseLevel).getHeight();
		const bool	isMipmap	= isMipmapFilter(getSampler().minFilter);
		const int	numLevels	= isMipmap ? de::min(getMaxLevel()-baseLevel+1, getNumMipLevels2D(width, height)) : 1;

		m_levels.updateSamplerMode(mode);
		m_view = tcu::TextureCubeArrayView(numLevels, m_levels.getEffectiveLevels() + baseLevel);
	}
	else
		m_view = tcu::TextureCubeArrayView(0, DE_NULL);
}

tcu::Vec4 TextureCubeArray::sample (float s, float t, float r, float q, float lod) const
{
	return m_view.sample(getSampler(), s, t, r, q, lod);
}

void TextureCubeArray::sample4 (tcu::Vec4 output[4], const tcu::Vec4 packetTexcoords[4], float lodBias) const
{
	const float		cubeSide		= (float)m_view.getSize();
	const tcu::Vec3	cubeCoords[4]	=
	{
		packetTexcoords[0].toWidth<3>(),
		packetTexcoords[1].toWidth<3>(),
		packetTexcoords[2].toWidth<3>(),
		packetTexcoords[3].toWidth<3>()
	};

	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
	{
		const tcu::CubeFace face			= tcu::selectCubeFace(cubeCoords[fragNdx]);
		const tcu::Vec2		faceCoords[4]	=
		{
			tcu::projectToFace(face, cubeCoords[0]),
			tcu::projectToFace(face, cubeCoords[1]),
			tcu::projectToFace(face, cubeCoords[2]),
			tcu::projectToFace(face, cubeCoords[3]),
		};

		const tcu::Vec2 dFdx0 = faceCoords[1] - faceCoords[0];
		const tcu::Vec2 dFdx1 = faceCoords[3] - faceCoords[2];
		const tcu::Vec2 dFdy0 = faceCoords[2] - faceCoords[0];
		const tcu::Vec2 dFdy1 = faceCoords[3] - faceCoords[1];

		const tcu::Vec2& dFdx = (fragNdx & 2) ? dFdx1 : dFdx0;
		const tcu::Vec2& dFdy = (fragNdx & 1) ? dFdy1 : dFdy0;

		const float mu = de::max(de::abs(dFdx.x()), de::abs(dFdy.x()));
		const float mv = de::max(de::abs(dFdx.y()), de::abs(dFdy.y()));
		const float p = de::max(mu * cubeSide, mv * cubeSide);

		const float	lod = deFloatLog2(p) + lodBias;

		output[fragNdx] = sample(packetTexcoords[fragNdx].x(), packetTexcoords[fragNdx].y(), packetTexcoords[fragNdx].z(), packetTexcoords[fragNdx].w(), lod);
	}
}

Texture3D::Texture3D (deUint32 name)
	: Texture	(name, TYPE_3D)
	, m_view	(0, DE_NULL)
{
}

Texture3D::~Texture3D (void)
{
}

void Texture3D::allocLevel (int level, const tcu::TextureFormat& format, int width, int height, int depth)
{
	m_levels.allocLevel(level, format, width, height, depth);
}

bool Texture3D::isComplete (void) const
{
	const int	baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel))
	{
		const tcu::ConstPixelBufferAccess&	level0		= getLevel(baseLevel);
		const bool							mipmap		= isMipmapFilter(getSampler().minFilter);

		if (mipmap)
		{
			const TextureFormat&	format		= level0.getFormat();
			const int				w			= level0.getWidth();
			const int				h			= level0.getHeight();
			const int				d			= level0.getDepth();
			const int				numLevels	= de::min(getMaxLevel()-baseLevel+1, getNumMipLevels3D(w, h, d));

			for (int levelNdx = 1; levelNdx < numLevels; levelNdx++)
			{
				if (hasLevel(baseLevel+levelNdx))
				{
					const tcu::ConstPixelBufferAccess&	level		= getLevel(baseLevel+levelNdx);
					const int							expectedW	= getMipLevelSize(w, levelNdx);
					const int							expectedH	= getMipLevelSize(h, levelNdx);
					const int							expectedD	= getMipLevelSize(d, levelNdx);

					if (level.getWidth()	!= expectedW	||
						level.getHeight()	!= expectedH	||
						level.getDepth()	!= expectedD	||
						level.getFormat()	!= format)
						return false;
				}
				else
					return false;
			}
		}

		return true;
	}
	else
		return false;
}

tcu::Vec4 Texture3D::sample (float s, float t, float r, float lod) const
{
	return m_view.sample(getSampler(), s, t, r, lod);
}

void Texture3D::sample4 (tcu::Vec4 output[4], const tcu::Vec3 packetTexcoords[4], float lodBias) const
{
	const float texWidth  = (float)m_view.getWidth();
	const float texHeight = (float)m_view.getHeight();
	const float texDepth  = (float)m_view.getDepth();

	const tcu::Vec3 dFdx0 = packetTexcoords[1] - packetTexcoords[0];
	const tcu::Vec3 dFdx1 = packetTexcoords[3] - packetTexcoords[2];
	const tcu::Vec3 dFdy0 = packetTexcoords[2] - packetTexcoords[0];
	const tcu::Vec3 dFdy1 = packetTexcoords[3] - packetTexcoords[1];

	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
	{
		const tcu::Vec3& dFdx = (fragNdx & 2) ? dFdx1 : dFdx0;
		const tcu::Vec3& dFdy = (fragNdx & 1) ? dFdy1 : dFdy0;

		const float mu = de::max(de::abs(dFdx.x()), de::abs(dFdy.x()));
		const float mv = de::max(de::abs(dFdx.y()), de::abs(dFdy.y()));
		const float mw = de::max(de::abs(dFdx.z()), de::abs(dFdy.z()));
		const float p = de::max(de::max(mu * texWidth, mv * texHeight), mw * texDepth);

		const float	lod = deFloatLog2(p) + lodBias;

		output[fragNdx] = sample(packetTexcoords[fragNdx].x(), packetTexcoords[fragNdx].y(), packetTexcoords[fragNdx].z(), lod);
	}
}

void Texture3D::updateView (tcu::Sampler::DepthStencilMode mode)
{
	const int baseLevel	= getBaseLevel();

	if (hasLevel(baseLevel) && !isEmpty(getLevel(baseLevel)))
	{
		const int	width		= getLevel(baseLevel).getWidth();
		const int	height		= getLevel(baseLevel).getHeight();
		const int	depth		= getLevel(baseLevel).getDepth();
		const bool	isMipmap	= isMipmapFilter(getSampler().minFilter);
		const int	numLevels	= isMipmap ? de::min(getMaxLevel()-baseLevel+1, getNumMipLevels3D(width, height, depth)) : 1;

		m_levels.updateSamplerMode(mode);
		m_view = tcu::Texture3DView(numLevels, m_levels.getEffectiveLevels() + baseLevel);
	}
	else
		m_view = tcu::Texture3DView(0, DE_NULL);
}

Renderbuffer::Renderbuffer (deUint32 name)
	: NamedObject		(name)
{
}

Renderbuffer::~Renderbuffer (void)
{
}

void Renderbuffer::setStorage (const TextureFormat& format, int width, int height)
{
	m_data.setStorage(format, width, height);
}

Framebuffer::Framebuffer (deUint32 name)
	: NamedObject(name)
{
}

Framebuffer::~Framebuffer (void)
{
}

VertexArray::VertexArray (deUint32 name, int maxVertexAttribs)
	: NamedObject					(name)
	, m_elementArrayBufferBinding	(DE_NULL)
	, m_arrays						(maxVertexAttribs)
{
	for (int i = 0; i < maxVertexAttribs; ++i)
	{
		m_arrays[i].enabled			= false;
		m_arrays[i].size			= 4;
		m_arrays[i].stride			= 0;
		m_arrays[i].type			= GL_FLOAT;
		m_arrays[i].normalized		= false;
		m_arrays[i].integer			= false;
		m_arrays[i].divisor			= 0;
		m_arrays[i].bufferDeleted	= false;
		m_arrays[i].bufferBinding	= DE_NULL;
		m_arrays[i].pointer			= DE_NULL;
	}
}

ShaderProgramObjectContainer::ShaderProgramObjectContainer (deUint32 name, ShaderProgram* program)
	: NamedObject	(name)
	, m_program		(program)
	, m_deleteFlag	(false)
{
}

ShaderProgramObjectContainer::~ShaderProgramObjectContainer (void)
{
}

} // rc
} // sglr