/*-------------------------------------------------------------------------
 * 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 "gluContextInfo.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deMemory.h"
#include "rrFragmentOperations.hpp"
#include "rrRenderer.hpp"

#include <cstdint>

namespace sglr
{

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

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

// Reference context implementation
using namespace rc;

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

// Utilities for ReferenceContext
#define RC_RET_VOID

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

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

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(uint32_t 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)
    , subpixelBits(0)
{
    const glw::Functions &gl = renderCtx.getFunctions();

    // When the OpenGL ES's major version bigger than 3, and the expect context version is 3,
    // we need query the real GL context version and update the real version to reference context.
    if (glu::IsES3Compatible(gl) && isES2Context(contextType))
    {
        int majorVersion = contextType.getMajorVersion();
        int minorVersion = contextType.getMinorVersion();
        gl.getIntegerv(GL_MAJOR_VERSION, &majorVersion);
        gl.getIntegerv(GL_MINOR_VERSION, &minorVersion);
        contextType.setAPI(glu::ApiType::es(majorVersion, minorVersion));
    }

    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);
    gl.getIntegerv(GL_SUBPIXEL_BITS, &subpixelBits);

    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(isES2Context(limits.contextType))
    , m_emptyTexCube(!isES2Context(limits.contextType))
    , 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);

    for (int unitNdx = 0; unitNdx < m_limits.maxTextureImageUnits; unitNdx++)
        m_textureUnits[unitNdx].defaultCubeTex.getSampler().seamlessCubeMap = !isES2Context(limits.contextType);

    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(uint32_t texture)
{
    if (deInBounds32(texture, GL_TEXTURE0, GL_TEXTURE0 + (uint32_t)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(uint32_t target, uint32_t 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.
            bool seamlessCubeMap = !isES2Context(m_limits.contextType);
            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, seamlessCubeMap);
                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, uint32_t *textures)
{
    while (numTextures--)
        *textures++ = m_textures.allocateName();
}

void ReferenceContext::deleteTextures(int numTextures, const uint32_t *textures)
{
    for (int i = 0; i < numTextures; i++)
    {
        uint32_t 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(uint32_t target, uint32_t 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++)
    {
        uint32_t 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, uint32_t *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 uint32_t *framebuffers)
{
    for (int i = 0; i < numFramebuffers; i++)
    {
        uint32_t name            = framebuffers[i];
        Framebuffer *framebuffer = name ? m_framebuffers.find(name) : DE_NULL;

        if (framebuffer)
            deleteFramebuffer(framebuffer);
    }
}

void ReferenceContext::bindRenderbuffer(uint32_t target, uint32_t 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, uint32_t *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 uint32_t *renderbuffers)
{
    for (int i = 0; i < numRenderbuffers; i++)
    {
        uint32_t name              = renderbuffers[i];
        Renderbuffer *renderbuffer = name ? m_renderbuffers.find(name) : DE_NULL;

        if (renderbuffer)
            deleteRenderbuffer(renderbuffer);
    }
}

void ReferenceContext::pixelStorei(uint32_t 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 uint8_t *ptr = (const uint8_t *)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 uint8_t *ptr = (const uint8_t *)data + m_pixelUnpackSkipImages * slicePitch +
                         m_pixelUnpackSkipRows * rowPitch + m_pixelUnpackSkipPixels * pixelSize;

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

static tcu::TextureFormat mapInternalFormat(uint32_t 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(uint32_t target, int level, uint32_t internalFormat, int width, int border,
                                  uint32_t format, uint32_t type, const void *data)
{
    texImage2D(target, level, internalFormat, width, 1, border, format, type, data);
}

void ReferenceContext::texImage2D(uint32_t target, int level, uint32_t internalFormat, int width, int height,
                                  int border, uint32_t format, uint32_t 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(uint32_t target, int level, uint32_t internalFormat, int width, int height, int depth,
                                  int border, uint32_t format, uint32_t 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(uint32_t target, int level, int xoffset, int width, uint32_t format, uint32_t type,
                                     const void *data)
{
    texSubImage2D(target, level, xoffset, 0, width, 1, format, type, data);
}

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

void ReferenceContext::texSubImage3D(uint32_t target, int level, int xoffset, int yoffset, int zoffset, int width,
                                     int height, int depth, uint32_t format, uint32_t 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(uint32_t target, int level, uint32_t 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(uint32_t target, int level, uint32_t 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(uint32_t 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(uint32_t 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(uint32_t 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(uint32_t target, int levels, uint32_t 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(uint32_t target, int levels, uint32_t 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(uint32_t target, uint32_t 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(uint32_t 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(uint32_t 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(uint32_t target, uint32_t attachment, uint32_t textarget, uint32_t 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(uint32_t target, uint32_t attachment, uint32_t 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 > deLog2Floor32(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 > deLog2Floor32(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(uint32_t target, uint32_t attachment, uint32_t renderbuffertarget,
                                               uint32_t 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);
        }
    }
}

uint32_t ReferenceContext::checkFramebufferStatus(uint32_t 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(uint32_t target, uint32_t attachment, uint32_t pname,
                                                           int *params)
{
    DE_UNREF(target && attachment && pname && params);
    TCU_CHECK(false); // \todo [pyry] Implement
}

void ReferenceContext::renderbufferStorage(uint32_t target, uint32_t 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(uint32_t target, int samples, uint32_t 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)
        {
            if (Texture2D *texture2D = dynamic_cast<Texture2D *>(texture))
                return texture2D->getLevel(attachment.level);
            else
                return nullAccess();
        }
        else if (texture->getType() == Texture::TYPE_CUBE_MAP)
        {
            if (TextureCube *cubeMap = dynamic_cast<TextureCube *>(texture))
                return cubeMap->getFace(attachment.level, texTargetToFace(attachment.texTarget));
            else
                return nullAccess();
        }
        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)
            {
                if (Texture2DArray *texture2DArray = dynamic_cast<Texture2DArray *>(texture))
                    level = texture2DArray->getLevel(attachment.level);
            }
            else if (texture->getType() == Texture::TYPE_3D)
            {
                if (Texture3D *texture3D = dynamic_cast<Texture3D *>(texture))
                    level = texture3D->getLevel(attachment.level);
            }
            else if (texture->getType() == Texture::TYPE_CUBE_MAP_ARRAY)
            {
                if (TextureCubeArray *cubeArray = dynamic_cast<TextureCubeArray *>(texture))
                    level = cubeArray->getLevel(attachment.level);
            }

            void *layerData = static_cast<uint8_t *>(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(uint32_t 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(uint32_t 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(uint32_t 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(uint32_t target, uint32_t 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, uint32_t *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 uint32_t *buffers)
{
    RC_IF_ERROR(numBuffers < 0, GL_INVALID_VALUE, RC_RET_VOID);

    for (int ndx = 0; ndx < numBuffers; ndx++)
    {
        uint32_t 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 uint32_t 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(uint32_t target, intptr_t size, const void *data, uint32_t 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((intptr_t)(int)size == size);
    buffer->setStorage((int)size);
    if (data)
        deMemcpy(buffer->getData(), data, (int)size);
}

void ReferenceContext::bufferSubData(uint32_t target, intptr_t offset, intptr_t 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(uint32_t 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(uint32_t 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(uint32_t 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(uint32_t 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(uint32_t func, int ref, uint32_t mask)
{
    stencilFuncSeparate(GL_FRONT_AND_BACK, func, ref, mask);
}

void ReferenceContext::stencilFuncSeparate(uint32_t face, uint32_t func, int ref, uint32_t 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(uint32_t sfail, uint32_t dpfail, uint32_t dppass)
{
    stencilOpSeparate(GL_FRONT_AND_BACK, sfail, dpfail, dppass);
}

void ReferenceContext::stencilOpSeparate(uint32_t face, uint32_t sfail, uint32_t dpfail, uint32_t 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(uint32_t 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(uint32_t 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(uint32_t index)
{
    // only in core
    DE_ASSERT(glu::isContextTypeGLCore(getType()));
    m_primitiveRestartIndex = index;
}

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

static bool isValidBlendFactor(uint32_t 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(uint32_t mode)
{
    RC_IF_ERROR(!isValidBlendEquation(mode), GL_INVALID_ENUM, RC_RET_VOID);

    m_blendModeRGB   = mode;
    m_blendModeAlpha = mode;
}

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

    m_blendModeRGB   = modeRGB;
    m_blendModeAlpha = modeAlpha;
}

void ReferenceContext::blendFunc(uint32_t src, uint32_t 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(uint32_t srcRGB, uint32_t dstRGB, uint32_t srcAlpha, uint32_t 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(bool r, bool g, bool b, bool a)
{
    m_colorMask = tcu::BVec4(!!r, !!g, !!b, !!a);
}

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

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

void ReferenceContext::stencilMaskSeparate(uint32_t face, uint32_t 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 uint32_t maskStencil(int numBits, uint32_t s)
{
    return s & deBitMask32(0, numBits);
}

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

    const uint32_t oldVal = access.raw().getPixelUint(s, x, y).x();
    const uint32_t 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));
}

uint32_t ReferenceContext::blitResolveMultisampleFramebuffer(uint32_t 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);
                uint32_t 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, uint32_t mask, uint32_t 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)
    {
        uint32_t 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);
                uint32_t 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(uint32_t target, int numAttachments, const uint32_t *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(uint32_t target, int numAttachments, const uint32_t *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(uint32_t 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(uint32_t 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(uint32_t 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(uint32_t buffer, int drawbuffer, const uint32_t *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(uint32_t 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(uint32_t 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, uint32_t *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 uint32_t *vertexArrays)
{
    for (int i = 0; i < numArrays; i++)
    {
        uint32_t name            = vertexArrays[i];
        VertexArray *vertexArray = name ? m_vertexArrays.find(name) : DE_NULL;

        if (vertexArray)
            deleteVertexArray(vertexArray);
    }
}

void ReferenceContext::vertexAttribPointer(uint32_t index, int rawSize, uint32_t type, bool 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 >= (uint32_t)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(uint32_t index, int size, uint32_t type, int stride, const void *pointer)
{
    RC_IF_ERROR(index >= (uint32_t)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(uint32_t index)
{
    RC_IF_ERROR(index >= (uint32_t)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(uint32_t index)
{
    RC_IF_ERROR(index >= (uint32_t)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(uint32_t index, uint32_t divisor)
{
    RC_IF_ERROR(index >= (uint32_t)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(uint32_t index, float x)
{
    RC_IF_ERROR(index >= (uint32_t)m_limits.maxVertexAttribs, GL_INVALID_VALUE, RC_RET_VOID);

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

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

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

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

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

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

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

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

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

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

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

int32_t ReferenceContext::getAttribLocation(uint32_t 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(int32_t location, glu::DataType type, int32_t 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(uint32_t) <= sizeof(uniforms[location].value));
        deMemcpy(&uniforms[location].value, v, scalarSize * (int)sizeof(uint32_t));
    }
}

void ReferenceContext::uniform1iv(int32_t location, int32_t count, const int32_t *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(int32_t location, const float v0)
{
    uniform1fv(location, 1, &v0);
}

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

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

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

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

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

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

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

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

void ReferenceContext::uniformMatrix3fv(int32_t location, int32_t count, bool 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(int32_t location, int32_t count, bool 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;
    }
}

int32_t ReferenceContext::getUniformLocation(uint32_t 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(uint32_t mode, int first, int count)
{
    drawArraysInstanced(mode, first, count, 1);
}

void ReferenceContext::drawArraysInstanced(uint32_t 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(uint32_t mode, int count, uint32_t type, const void *indices)
{
    drawElementsInstanced(mode, count, type, indices, 1);
}

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

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

void ReferenceContext::drawElementsInstancedBaseVertex(uint32_t mode, int count, uint32_t 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() + reinterpret_cast<uintptr_t>(indices)) :
                (indices);

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

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

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

void ReferenceContext::drawRangeElementsBaseVertex(uint32_t mode, uint32_t start, uint32_t end, int count,
                                                   uint32_t 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(uint32_t mode, const void *indirect)
{
    struct DrawArraysIndirectCommand
    {
        uint32_t count;
        uint32_t primCount;
        uint32_t first;
        uint32_t 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) reinterpret_cast<uintptr_t>(indirect) > (size_t)m_drawIndirectBufferBinding->getSize(),
                GL_INVALID_OPERATION, RC_RET_VOID);
    RC_IF_ERROR((size_t) reinterpret_cast<uintptr_t>(indirect) + sizeof(DrawArraysIndirectCommand) >
                    (size_t)m_drawIndirectBufferBinding->getSize(),
                GL_INVALID_OPERATION, RC_RET_VOID);

    // Check values

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

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

void ReferenceContext::drawElementsIndirect(uint32_t mode, uint32_t type, const void *indirect)
{
    struct DrawElementsIndirectCommand
    {
        uint32_t count;
        uint32_t primCount;
        uint32_t firstIndex;
        int32_t baseVertex;
        uint32_t 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) reinterpret_cast<uintptr_t>(indirect) > (size_t)m_drawIndirectBufferBinding->getSize(),
                GL_INVALID_OPERATION, RC_RET_VOID);
    RC_IF_ERROR((size_t) reinterpret_cast<uintptr_t>(indirect) + sizeof(DrawElementsIndirectCommand) >
                    (size_t)m_drawIndirectBufferBinding->getSize(),
                GL_INVALID_OPERATION, RC_RET_VOID);

    // Check values

    command = (const DrawElementsIndirectCommand *)(m_drawIndirectBufferBinding->getData() +
                                                    reinterpret_cast<uintptr_t>(indirect));
    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  = glu::BufferOffsetAsPointer(command->firstIndex * sizeOfType);

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

void ReferenceContext::multiDrawArrays(uint32_t 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(uint32_t mode, const int *count, uint32_t 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(uint32_t mode, const int *count, uint32_t 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(uint32_t 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 uint32_t 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), m_limits.subpixelBits);

    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() +
                                                  reinterpret_cast<uintptr_t>(vao.m_arrays[ndx].pointer)) :
                                                         (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);
}

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

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

    return name;
}

void ReferenceContext::useProgram(uint32_t 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(uint32_t 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, uint32_t format, uint32_t 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));
}

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

void ReferenceContext::finish(void)
{
}

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

void ReferenceContext::getIntegerv(uint32_t 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(uint32_t 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(uint32_t name, Type type, bool seamless)
    : 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
                seamless         // seamless cube map, Default value is True.
                )
    , m_baseLevel(0)
    , m_maxLevel(1000)
{
}

Texture1D::Texture1D(uint32_t 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(uint32_t name, bool es2) : Texture(name, TYPE_2D), m_view(0, DE_NULL, es2)
{
}

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(uint32_t name, bool seamless) : Texture(name, TYPE_CUBE_MAP, seamless)
{
}

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(uint32_t 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(uint32_t 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(uint32_t 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(uint32_t name) : NamedObject(name)
{
}

Renderbuffer::~Renderbuffer(void)
{
}

void Renderbuffer::setStorage(const TextureFormat &format, int width, int height)
{
    m_data.setStorage(format, width, height);
}

Framebuffer::Framebuffer(uint32_t name) : NamedObject(name)
{
}

Framebuffer::~Framebuffer(void)
{
}

VertexArray::VertexArray(uint32_t 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(uint32_t name, ShaderProgram *program)
    : NamedObject(name)
    , m_program(program)
    , m_deleteFlag(false)
{
}

ShaderProgramObjectContainer::~ShaderProgramObjectContainer(void)
{
}

} // namespace rc
} // namespace sglr
