/*
* Copyright 2011 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.
*/

#include <GLcommon/GLEScontext.h>

#include "aemu/base/synchronization/Lock.h"
#include "aemu/base/containers/Lookup.h"
#include "aemu/base/files/StreamSerializing.h"
#include "host-common/feature_control.h"
#include "host-common/logging.h"

#include <GLcommon/GLconversion_macros.h>
#include <GLcommon/GLSnapshotSerializers.h>
#include <GLcommon/GLESmacros.h>
#include <GLcommon/TextureData.h>
#include <GLES/gl.h>
#include <GLES/glext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl31.h>
#include <GLcommon/GLESvalidate.h>
#include <GLcommon/TextureUtils.h>
#include <GLcommon/FramebufferData.h>
#include <GLcommon/ScopedGLState.h>
#ifndef _MSC_VER
#include <strings.h>
#endif
#include <string.h>

#include <numeric>
#include <map>

//decleration
static void convertFixedDirectLoop(const char* dataIn,unsigned int strideIn,void* dataOut,unsigned int nBytes,unsigned int strideOut,int attribSize);
static void convertFixedIndirectLoop(const char* dataIn,unsigned int strideIn,void* dataOut,GLsizei count,GLenum indices_type,const GLvoid* indices,unsigned int strideOut,int attribSize);
static void convertByteDirectLoop(const char* dataIn,unsigned int strideIn,void* dataOut,unsigned int nBytes,unsigned int strideOut,int attribSize);
static void convertByteIndirectLoop(const char* dataIn,unsigned int strideIn,void* dataOut,GLsizei count,GLenum indices_type,const GLvoid* indices,unsigned int strideOut,int attribSize);

void BufferBinding::onLoad(android::base::Stream* stream) {
    buffer = stream->getBe32();
    offset = stream->getBe32();
    size = stream->getBe32();
    stride = stream->getBe32();
    divisor = stream->getBe32();
    isBindBase = stream->getByte();
}

void BufferBinding::onSave(android::base::Stream* stream) const {
    stream->putBe32(buffer);
    stream->putBe32(offset);
    stream->putBe32(size);
    stream->putBe32(stride);
    stream->putBe32(divisor);
    stream->putByte(isBindBase);
}

VAOState::VAOState(android::base::Stream* stream) {
    element_array_buffer_binding = stream->getBe32();

    vertexAttribInfo.clear();
    for (uint32_t i = 0; i < kMaxVertexAttributes; ++i) {
        vertexAttribInfo.emplace_back(stream);
    }

    uint64_t arraysMapPtr = stream->getBe64();

    if (arraysMapPtr) {
        arraysMap.reset(new ArraysMap());
        size_t mapSize = stream->getBe32();
        for (size_t i = 0; i < mapSize; i++) {
            GLuint id = stream->getBe32();
            arraysMap->emplace(id, new GLESpointer(stream));
        }
        legacy = true;
    } else {
        arraysMap.reset();
    }

    loadContainer(stream, bindingState);
    bufferBacked = stream->getByte();
    everBound = stream->getByte();
}

void VAOState::onSave(android::base::Stream* stream) const {
    stream->putBe32(element_array_buffer_binding);
    for (uint32_t i = 0; i < kMaxVertexAttributes; ++i) {
        vertexAttribInfo[i].onSave(stream);
    }

    if (arraysMap) {
        stream->putBe64((uint64_t)(uintptr_t)arraysMap.get());
    } else {
        stream->putBe64(0);
    }

    if (arraysMap) {
        stream->putBe32(arraysMap->size());
        for (const auto& ite : *arraysMap) {
            stream->putBe32(ite.first);
            assert(ite.second);
            ite.second->onSave(stream);
        }
    }

    saveContainer(stream, bindingState);
    stream->putByte(bufferBacked);
    stream->putByte(everBound);
}

GLESConversionArrays::~GLESConversionArrays() {
    for(auto it = m_arrays.begin(); it != m_arrays.end(); ++it) {
        if((*it).second.allocated){
            if((*it).second.type == GL_FLOAT){
                GLfloat* p = (GLfloat *)((*it).second.data);
                if(p) delete[] p;
            } else if((*it).second.type == GL_SHORT){
                GLshort* p = (GLshort *)((*it).second.data);
                if(p) delete[] p;
            }
        }
    }
}

void GLESConversionArrays::allocArr(unsigned int size,GLenum type){
    if(type == GL_FIXED){
        m_arrays[m_current].data = new GLfloat[size];
        m_arrays[m_current].type = GL_FLOAT;
    } else if(type == GL_BYTE){
        m_arrays[m_current].data = new GLshort[size];
        m_arrays[m_current].type = GL_SHORT;
    }
    m_arrays[m_current].stride = 0;
    m_arrays[m_current].allocated = true;
}

void GLESConversionArrays::setArr(void* data,unsigned int stride,GLenum type){
   m_arrays[m_current].type = type;
   m_arrays[m_current].data = data;
   m_arrays[m_current].stride = stride;
   m_arrays[m_current].allocated = false;
}

void* GLESConversionArrays::getCurrentData(){
    return m_arrays[m_current].data;
}

ArrayData& GLESConversionArrays::getCurrentArray(){
    return m_arrays[m_current];
}

unsigned int GLESConversionArrays::getCurrentIndex(){
    return m_current;
}

ArrayData& GLESConversionArrays::operator[](int i){
    return m_arrays[i];
}

void GLESConversionArrays::operator++(){
    m_current++;
}

GLDispatch     GLEScontext::s_glDispatch;
android::base::Lock   GLEScontext::s_lock;
std::string*   GLEScontext::s_glExtensionsGles1 = NULL;
bool           GLEScontext::s_glExtensionsGles1Initialized = false;
std::string*   GLEScontext::s_glExtensionsGles31 = NULL;
bool           GLEScontext::s_glExtensionsGles31Initialized = false;
std::string*   GLEScontext::s_glExtensions = NULL;
bool           GLEScontext::s_glExtensionsInitialized = false;
std::string    GLEScontext::s_glVendorGles1;
std::string    GLEScontext::s_glRendererGles1;
std::string    GLEScontext::s_glVersionGles1;
std::string    GLEScontext::s_glVendorGles31;
std::string    GLEScontext::s_glRendererGles31;
std::string    GLEScontext::s_glVersionGles31;
std::string    GLEScontext::s_glVendor;
std::string    GLEScontext::s_glRenderer;
std::string    GLEScontext::s_glVersion;
GLSupport      GLEScontext::s_glSupport;
GLSupport      GLEScontext::s_glSupportGles1;
GLSupport      GLEScontext::s_glSupportGles31;

Version::Version(int major,int minor,int release):m_major(major),
                                                  m_minor(minor),
                                                  m_release(release){};

Version::Version(const Version& ver):m_major(ver.m_major),
                                     m_minor(ver.m_minor),
                                     m_release(ver.m_release){}

Version::Version(const char* versionString){
    m_release = 0;
    if((!versionString) ||
      ((!(sscanf(versionString,"%d.%d"   ,&m_major,&m_minor) == 2)) &&
       (!(sscanf(versionString,"%d.%d.%d",&m_major,&m_minor,&m_release) == 3)))){
        m_major = m_minor = 0; // the version is not in the right format
    }
}

Version& Version::operator=(const Version& ver){
    m_major   = ver.m_major;
    m_minor   = ver.m_minor;
    m_release = ver.m_release;
    return *this;
}

bool Version::operator<(const Version& ver) const{
    if(m_major < ver.m_major) return true;
    if(m_major == ver.m_major){
        if(m_minor < ver.m_minor) return true;
        if(m_minor == ver.m_minor){
           return m_release < ver.m_release;
        }
    }
    return false;
}

std::string getHostExtensionsString(GLDispatch* dispatch) {
    // glGetString(GL_EXTENSIONS) is deprecated in GL 3.0, one has to use
    // glGetStringi(GL_EXTENSIONS, index) instead to get individual extension
    // names. Recent desktop drivers implement glGetStringi() but have a
    // version of glGetString() that returns NULL, so deal with this by
    // doing the following:
    //
    //  - If glGetStringi() is available, and glGetIntegerv(GL_NUM_EXTENSIONS &num_exts)
    //    gives a nonzero value, use it to build the extensions
    //    string, using simple spaces to separate the names.
    //
    //  - Otherwise, fallback to getGetString(). If it returns NULL, return
    //    an empty string.
    //

    std::string result;
    int num_exts = 0;

    if (dispatch->glGetStringi) {
        dispatch->glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts);
        GLenum err = dispatch->glGetError();
        if (err == GL_NO_ERROR) {
            for (int n = 0; n < num_exts; n++) {
                const char* ext = reinterpret_cast<const char*>(
                        dispatch->glGetStringi(GL_EXTENSIONS, n));
                if (ext != NULL) {
                    if (!result.empty()) {
                        result += " ";
                    }
                    result += ext;
                }
            }
        }
    }

    // If glGetIntegerv does not affect the value,
    // our system does not actually support
    // GL 3.0 style extension getting.
    if (!dispatch->glGetStringi || num_exts == 0) {
        const char* extensions = reinterpret_cast<const char*>(
                dispatch->glGetString(GL_EXTENSIONS));
        if (extensions) {
            result = extensions;
        }
    }

    // For the sake of initCapsLocked() add a starting and trailing space.
    if (!result.empty()) {
        if (result[0] != ' ') {
            result.insert(0, 1, ' ');
        }
        if (result[result.size() - 1U] != ' ') {
            result += ' ';
        }
    }
    return result;
}

static GLuint getIndex(GLenum indices_type, const GLvoid* indices, unsigned int i) {
    switch (indices_type) {
        case GL_UNSIGNED_BYTE:
            return static_cast<const GLubyte*>(indices)[i];
        case GL_UNSIGNED_SHORT:
            return static_cast<const GLushort*>(indices)[i];
        case GL_UNSIGNED_INT:
            return static_cast<const GLuint*>(indices)[i];
        default:
            ERR("**** ERROR unknown type 0x%x", indices_type);
            return 0;
    }
}

void GLEScontext::addVertexArrayObjects(GLsizei n, GLuint* arrays) {
    for (int i = 0; i < n; i++) {
        addVertexArrayObject(arrays[i]);
    }
}

void GLEScontext::removeVertexArrayObjects(GLsizei n, const GLuint* arrays) {
    for (int i = 0; i < n; i++) {
        removeVertexArrayObject(arrays[i]);
    }
}

void GLEScontext::addVertexArrayObject(GLuint array) {
    ArraysMap* map = new ArraysMap();
    for (int i = 0; i < s_glSupport.maxVertexAttribs; i++) {
        map->insert(
                ArraysMap::value_type(
                    i,
                    new GLESpointer()));
    }
    assert(m_vaoStateMap.count(array) == 0);  // Overwriting existing entry, leaking memory
    m_vaoStateMap[array] = VAOState(0, map, std::max(s_glSupport.maxVertexAttribs, s_glSupport.maxVertexAttribBindings));
}

void GLEScontext::removeVertexArrayObject(GLuint array) {
    if (array == 0) return;
    if (m_vaoStateMap.find(array) == m_vaoStateMap.end())
        return;
    if (array == m_currVaoState.vaoId()) {
        setVertexArrayObject(0);
    }

    auto& state = m_vaoStateMap[array];

    if (state.arraysMap) {
        for (auto elem : *(state.arraysMap)) {
            delete elem.second;
        }
    }

    m_vaoStateMap.erase(array);
}

bool GLEScontext::setVertexArrayObject(GLuint array) {
    VAOStateMap::iterator it = m_vaoStateMap.find(array);
    if (it != m_vaoStateMap.end()) {
        m_currVaoState = VAOStateRef(it);
        return true;
    }
    return false;
}

void GLEScontext::setVAOEverBound() {
    m_currVaoState.setEverBound();
}

GLuint GLEScontext::getVertexArrayObject() const {
    return m_currVaoState.vaoId();
}

bool GLEScontext::vertexAttributesBufferBacked() {
    const auto& info = m_currVaoState.attribInfo_const();
    for (uint32_t i = 0; i  < kMaxVertexAttributes; ++i) {
        const auto& pointerInfo = info[i];
        if (pointerInfo.isEnable() &&
            !m_currVaoState.bufferBindings()[pointerInfo.getBindingIndex()].buffer) {
            return false;
        }
    }

    return true;
}

static EGLiface*      s_eglIface = nullptr;

// static
EGLiface* GLEScontext::eglIface() {
    return s_eglIface;
}

// static
void GLEScontext::initEglIface(EGLiface* iface) {
    if (!s_eglIface) s_eglIface = iface;
}

void GLEScontext::initGlobal(EGLiface* iface) {
    initEglIface(iface);
    s_lock.lock();
    if (!s_glExtensions) {
        s_glExtensions = new std::string();
    }
    if (!s_glExtensionsGles1) {
        s_glExtensionsGles1 = new std::string();
    }
    if (!s_glExtensionsGles31) {
        s_glExtensionsGles31 = new std::string();
    }
    s_lock.unlock();
}

void GLEScontext::init() {
    if (!m_initialized) {
        initExtensionString();

        m_maxTexUnits = getMaxCombinedTexUnits();
        m_texState = new textureUnitState[m_maxTexUnits];
        for (int i=0;i<m_maxTexUnits;++i) {
            for (int j=0;j<NUM_TEXTURE_TARGETS;++j)
            {
                m_texState[i][j].texture = 0;
                m_texState[i][j].enabled = GL_FALSE;
            }
        }

        m_indexedTransformFeedbackBuffers.resize(getCaps()->maxTransformFeedbackSeparateAttribs);
        m_indexedUniformBuffers.resize(getCaps()->maxUniformBufferBindings);
        m_indexedAtomicCounterBuffers.resize(getCaps()->maxAtomicCounterBufferBindings);
        m_indexedShaderStorageBuffers.resize(getCaps()->maxShaderStorageBufferBindings);
        m_blendStates.resize(getCaps()->ext_GL_EXT_draw_buffers_indexed ? getCaps()->maxDrawBuffers
                                                                        : 1);
    }
}

void GLEScontext::restore() {
    postLoadRestoreShareGroup();
    if (m_needRestoreFromSnapshot) {
        postLoadRestoreCtx();
        m_needRestoreFromSnapshot = false;
    }
}

bool GLEScontext::needRestore() {
    bool ret = m_needRestoreFromSnapshot;
    if (m_shareGroup) {
        ret |= m_shareGroup->needRestore();
    }
    return ret;
}

GLenum GLEScontext::getGLerror() {
    return m_glError;
}

void GLEScontext::setGLerror(GLenum err) {
    m_glError = err;
}

void GLEScontext::setActiveTexture(GLenum tex) {
   m_activeTexture = tex - GL_TEXTURE0;
   m_maxUsedTexUnit = std::max(m_activeTexture, m_maxUsedTexUnit);
}

GLEScontext::GLEScontext() {}

GLEScontext::GLEScontext(GlobalNameSpace* globalNameSpace,
        android::base::Stream* stream, GlLibrary* glLib) {
    if (stream) {
        m_initialized = stream->getByte();
        m_glesMajorVersion = stream->getBe32();
        m_glesMinorVersion = stream->getBe32();
        if (m_initialized) {
            m_activeTexture = (GLuint)stream->getBe32();

            loadNameMap<VAOStateMap>(stream, m_vaoStateMap);
            uint32_t vaoId = stream->getBe32();
            setVertexArrayObject(vaoId);

            m_copyReadBuffer = static_cast<GLuint>(stream->getBe32());
            m_copyWriteBuffer = static_cast<GLuint>(stream->getBe32());
            m_pixelPackBuffer = static_cast<GLuint>(stream->getBe32());
            m_pixelUnpackBuffer = static_cast<GLuint>(stream->getBe32());
            m_transformFeedbackBuffer = static_cast<GLuint>(stream->getBe32());
            m_uniformBuffer = static_cast<GLuint>(stream->getBe32());
            m_atomicCounterBuffer = static_cast<GLuint>(stream->getBe32());
            m_dispatchIndirectBuffer = static_cast<GLuint>(stream->getBe32());
            m_drawIndirectBuffer = static_cast<GLuint>(stream->getBe32());
            m_shaderStorageBuffer = static_cast<GLuint>(stream->getBe32());
            m_textureBuffer = static_cast<GLuint>(stream->getBe32());

            loadContainer(stream, m_indexedTransformFeedbackBuffers);
            loadContainer(stream, m_indexedUniformBuffers);
            loadContainer(stream, m_indexedAtomicCounterBuffers);
            loadContainer(stream, m_indexedShaderStorageBuffers);

            // TODO: handle the case where the loaded size and the supported
            // side does not match

            m_isViewport = stream->getByte();
            m_viewportX = static_cast<GLint>(stream->getBe32());
            m_viewportY = static_cast<GLint>(stream->getBe32());
            m_viewportWidth = static_cast<GLsizei>(stream->getBe32());
            m_viewportHeight = static_cast<GLsizei>(stream->getBe32());

            m_polygonOffsetFactor = static_cast<GLfloat>(stream->getFloat());
            m_polygonOffsetUnits = static_cast<GLfloat>(stream->getFloat());

            m_isScissor = stream->getByte();
            m_scissorX = static_cast<GLint>(stream->getBe32());
            m_scissorY = static_cast<GLint>(stream->getBe32());
            m_scissorWidth = static_cast<GLsizei>(stream->getBe32());
            m_scissorHeight = static_cast<GLsizei>(stream->getBe32());

            loadCollection(stream, &m_glEnableList,
                    [](android::base::Stream* stream) {
                        GLenum item = stream->getBe32();
                        bool enabled = stream->getByte();
                        return std::make_pair(item, enabled);
            });
            int blendStateCount = stream->getBe32();
            m_blendStates.resize(blendStateCount);

            stream->read(m_blendStates.data(), sizeof(BlendState) * blendStateCount);

            loadCollection(stream, &m_glPixelStoreiList,
                    [](android::base::Stream* stream) {
                        GLenum item = stream->getBe32();
                        GLint val = stream->getBe32();
                        return std::make_pair(item, val);
            });

            m_cullFace = static_cast<GLenum>(stream->getBe32());
            m_frontFace = static_cast<GLenum>(stream->getBe32());
            m_depthFunc = static_cast<GLenum>(stream->getBe32());
            m_depthMask = static_cast<GLboolean>(stream->getByte());
            m_zNear = static_cast<GLclampf>(stream->getFloat());
            m_zFar = static_cast<GLclampf>(stream->getFloat());

            m_lineWidth = static_cast<GLclampf>(stream->getFloat());

            m_sampleCoverageVal = static_cast<GLclampf>(stream->getFloat());
            m_sampleCoverageInvert = static_cast<GLboolean>(stream->getByte());

            stream->read(m_stencilStates, sizeof(m_stencilStates));

            m_clearColorR = static_cast<GLclampf>(stream->getFloat());
            m_clearColorG = static_cast<GLclampf>(stream->getFloat());
            m_clearColorB = static_cast<GLclampf>(stream->getFloat());
            m_clearColorA = static_cast<GLclampf>(stream->getFloat());

            m_clearDepth = static_cast<GLclampf>(stream->getFloat());
            m_clearStencil = static_cast<GLint>(stream->getBe32());

            // share group is supposed to be loaded by EglContext and reset
            // when loading EglContext
            //int sharegroupId = stream->getBe32();
            m_glError = static_cast<GLenum>(stream->getBe32());
            m_maxTexUnits = static_cast<int>(stream->getBe32());
            m_maxUsedTexUnit = static_cast<int>(stream->getBe32());
            m_texState = new textureUnitState[m_maxTexUnits];
            stream->read(m_texState, sizeof(textureUnitState) * m_maxTexUnits);
            m_arrayBuffer = static_cast<unsigned int>(stream->getBe32());
            m_elementBuffer = static_cast<unsigned int>(stream->getBe32());
            m_renderbuffer = static_cast<GLuint>(stream->getBe32());
            m_drawFramebuffer = static_cast<GLuint>(stream->getBe32());
            m_readFramebuffer = static_cast<GLuint>(stream->getBe32());
            m_defaultFBODrawBuffer = static_cast<GLenum>(stream->getBe32());
            m_defaultFBOReadBuffer = static_cast<GLenum>(stream->getBe32());

            m_needRestoreFromSnapshot = true;
        }
    }
    ObjectData::loadObject_t loader = [this](NamedObjectType type,
                                             long long unsigned int localName,
                                             android::base::Stream* stream) {
        return loadObject(type, localName, stream);
    };
    m_fboNameSpace = new NameSpace(NamedObjectType::FRAMEBUFFER,
                                   globalNameSpace, stream, loader);
    // do not load m_vaoNameSpace
    m_vaoNameSpace = new NameSpace(NamedObjectType::VERTEX_ARRAY_OBJECT,
                                   globalNameSpace, nullptr, loader);
}

GLEScontext::~GLEScontext() {
    auto& gl = dispatcher();

    if (m_blitState.program) {
        gl.glDeleteProgram(m_blitState.program);
        gl.glDeleteTextures(1, &m_blitState.tex);
        gl.glDeleteVertexArrays(1, &m_blitState.vao);
        gl.glDeleteBuffers(1, &m_blitState.vbo);
        gl.glDeleteFramebuffers(1, &m_blitState.fbo);
    }

    if (m_textureEmulationProg) {
        gl.glDeleteProgram(m_textureEmulationProg);
        gl.glDeleteTextures(2, m_textureEmulationTextures);
        gl.glDeleteFramebuffers(1, &m_textureEmulationFBO);
        gl.glDeleteVertexArrays(1, &m_textureEmulationVAO);
    }

    if (m_defaultFBO) {
        gl.glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFBO);
        gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
        gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
        gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
        gl.glBindFramebuffer(GL_FRAMEBUFFER, 0);
        gl.glDeleteFramebuffers(1, &m_defaultFBO);
    }

    if (m_defaultReadFBO && (m_defaultReadFBO != m_defaultFBO)) {
        gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, m_defaultReadFBO);
        gl.glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
        gl.glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
        gl.glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
        gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
        gl.glDeleteFramebuffers(1, &m_defaultReadFBO);
    }

    m_defaultFBO = 0;
    m_defaultReadFBO = 0;

    for (auto&& vao : m_vaoStateMap) {
        if (vao.second.arraysMap) {
            for (auto elem : *(vao.second.arraysMap)) {
                delete elem.second;
            }
            vao.second.arraysMap.reset();
        }
    }

    delete[] m_texState;
    m_texState = nullptr;
    delete m_fboNameSpace;
    m_fboNameSpace = nullptr;
    delete m_vaoNameSpace;
    m_vaoNameSpace = nullptr;
}

void GLEScontext::postLoad() {
    m_fboNameSpace->postLoad(
            [this](NamedObjectType p_type, ObjectLocalName p_localName) {
                if (p_type == NamedObjectType::FRAMEBUFFER) {
                    return this->getFBODataPtr(p_localName);
                } else {
                    return m_shareGroup->getObjectDataPtr(p_type, p_localName);
                }
            });
}

void GLEScontext::onSave(android::base::Stream* stream) const {
    stream->putByte(m_initialized);
    stream->putBe32(m_glesMajorVersion);
    stream->putBe32(m_glesMinorVersion);
    if (m_initialized) {
        stream->putBe32(m_activeTexture);

        saveNameMap(stream, m_vaoStateMap);
        stream->putBe32(getVertexArrayObject());

        stream->putBe32(m_copyReadBuffer);
        stream->putBe32(m_copyWriteBuffer);
        stream->putBe32(m_pixelPackBuffer);
        stream->putBe32(m_pixelUnpackBuffer);
        stream->putBe32(m_transformFeedbackBuffer);
        stream->putBe32(m_uniformBuffer);
        stream->putBe32(m_atomicCounterBuffer);
        stream->putBe32(m_dispatchIndirectBuffer);
        stream->putBe32(m_drawIndirectBuffer);
        stream->putBe32(m_shaderStorageBuffer);
        stream->putBe32(m_textureBuffer);

        saveContainer(stream, m_indexedTransformFeedbackBuffers);
        saveContainer(stream, m_indexedUniformBuffers);
        saveContainer(stream, m_indexedAtomicCounterBuffers);
        saveContainer(stream, m_indexedShaderStorageBuffers);

        stream->putByte(m_isViewport);
        stream->putBe32(m_viewportX);
        stream->putBe32(m_viewportY);
        stream->putBe32(m_viewportWidth);
        stream->putBe32(m_viewportHeight);

        stream->putFloat(m_polygonOffsetFactor);
        stream->putFloat(m_polygonOffsetUnits);

        stream->putByte(m_isScissor);
        stream->putBe32(m_scissorX);
        stream->putBe32(m_scissorY);
        stream->putBe32(m_scissorWidth);
        stream->putBe32(m_scissorHeight);

        saveCollection(stream, m_glEnableList, [](android::base::Stream* stream,
                const std::pair<const GLenum, bool>& enableItem) {
                    stream->putBe32(enableItem.first);
                    stream->putByte(enableItem.second);
        });
        stream->putBe32((int)m_blendStates.size());
        stream->write(m_blendStates.data(), sizeof(BlendState) * m_blendStates.size());

        saveCollection(stream, m_glPixelStoreiList, [](android::base::Stream* stream,
                const std::pair<const GLenum, GLint>& pixelStore) {
                    stream->putBe32(pixelStore.first);
                    stream->putBe32(pixelStore.second);
        });

        stream->putBe32(m_cullFace);
        stream->putBe32(m_frontFace);
        stream->putBe32(m_depthFunc);
        stream->putByte(m_depthMask);
        stream->putFloat(m_zNear);
        stream->putFloat(m_zFar);

        stream->putFloat(m_lineWidth);

        stream->putFloat(m_sampleCoverageVal);
        stream->putByte(m_sampleCoverageInvert);

        stream->write(m_stencilStates, sizeof(m_stencilStates));

        stream->putFloat(m_clearColorR);
        stream->putFloat(m_clearColorG);
        stream->putFloat(m_clearColorB);
        stream->putFloat(m_clearColorA);

        stream->putFloat(m_clearDepth);
        stream->putBe32(m_clearStencil);

        // share group is supposed to be saved / loaded by EglContext
        stream->putBe32(m_glError);
        stream->putBe32(m_maxTexUnits);
        stream->putBe32(m_maxUsedTexUnit);
        stream->write(m_texState, sizeof(textureUnitState) * m_maxTexUnits);
        stream->putBe32(m_arrayBuffer);
        stream->putBe32(m_elementBuffer);
        stream->putBe32(m_renderbuffer);
        stream->putBe32(m_drawFramebuffer);
        stream->putBe32(m_readFramebuffer);
        stream->putBe32(m_defaultFBODrawBuffer);
        stream->putBe32(m_defaultFBOReadBuffer);
    }
    m_fboNameSpace->onSave(stream);
    // do not save m_vaoNameSpace
}

void GLEScontext::postSave(android::base::Stream* stream) const {
    (void)stream;
    // We need to mark the textures dirty, for those that has been bound to
    // a potential render target.
    for (ObjectDataMap::const_iterator it = m_fboNameSpace->objDataMapBegin();
        it != m_fboNameSpace->objDataMapEnd();
        it ++) {
        FramebufferData* fbData = (FramebufferData*)it->second.get();
        fbData->makeTextureDirty([this](NamedObjectType p_type,
            ObjectLocalName p_localName) {
                if (p_type == NamedObjectType::FRAMEBUFFER) {
                    return this->getFBODataPtr(p_localName);
                } else {
                    return m_shareGroup->getObjectDataPtr(p_type, p_localName);
                }
            });
    }
}

void GLEScontext::postLoadRestoreShareGroup() {
    m_shareGroup->postLoadRestore();
}

void GLEScontext::postLoadRestoreCtx() {
    GLDispatch& dispatcher = GLEScontext::dispatcher();

    assert(!m_shareGroup->needRestore());
    m_fboNameSpace->postLoadRestore(
            [this](NamedObjectType p_type, ObjectLocalName p_localName) {
                if (p_type == NamedObjectType::FRAMEBUFFER) {
                    return getFBOGlobalName(p_localName);
                } else {
                    return m_shareGroup->getGlobalName(p_type, p_localName);
                }
            }
        );

    // buffer bindings
    auto bindBuffer = [this](GLenum target, GLuint buffer) {
        this->dispatcher().glBindBuffer(target,
                m_shareGroup->getGlobalName(NamedObjectType::VERTEXBUFFER, buffer));
    };
    bindBuffer(GL_ARRAY_BUFFER, m_arrayBuffer);
    bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_currVaoState.iboId());

    // framebuffer binding
    auto bindFrameBuffer = [this](GLenum target, GLuint buffer) {
        this->dispatcher().glBindFramebuffer(target,
                getFBOGlobalName(buffer));
    };
    bindFrameBuffer(GL_READ_FRAMEBUFFER, m_readFramebuffer);
    bindFrameBuffer(GL_DRAW_FRAMEBUFFER, m_drawFramebuffer);

    for (unsigned int i = 0; i <= m_maxUsedTexUnit; i++) {
        for (unsigned int j = 0; j < NUM_TEXTURE_TARGETS; j++) {
            textureTargetState& texState = m_texState[i][j];
            if (texState.texture || texState.enabled) {
                this->dispatcher().glActiveTexture(i + GL_TEXTURE0);
                GLenum texTarget = GL_TEXTURE_2D;
                switch (j) {
                    case TEXTURE_2D:
                        texTarget = GL_TEXTURE_2D;
                        break;
                    case TEXTURE_CUBE_MAP:
                        texTarget = GL_TEXTURE_CUBE_MAP;
                        break;
                    case TEXTURE_2D_ARRAY:
                        texTarget = GL_TEXTURE_2D_ARRAY;
                        break;
                    case TEXTURE_3D:
                        texTarget = GL_TEXTURE_3D;
                        break;
                    case TEXTURE_2D_MULTISAMPLE:
                        texTarget = GL_TEXTURE_2D_MULTISAMPLE;
                        break;
                    case TEXTURE_BUFFER:
                        texTarget = GL_TEXTURE_BUFFER;
                        break;
                    default:
                        fprintf(stderr,
                                "Warning: unsupported texture target 0x%x.\n",
                                j);
                        break;
                }
                // TODO: refactor the following line since it is duplicated in
                // GLESv2Imp and GLEScmImp as well
                ObjectLocalName texName = texState.texture != 0 ?
                        texState.texture : getDefaultTextureName(texTarget);
                this->dispatcher().glBindTexture(
                        texTarget,
                        m_shareGroup->getGlobalName(
                            NamedObjectType::TEXTURE, texName));
                if (!isCoreProfile() && texState.enabled) {
                    dispatcher.glEnable(texTarget);
                }
            }
        }
    }
    dispatcher.glActiveTexture(m_activeTexture + GL_TEXTURE0);

    // viewport & scissor
    if (m_isViewport) {
        dispatcher.glViewport(m_viewportX, m_viewportY,
                m_viewportWidth, m_viewportHeight);
    }
    if (m_isScissor) {
        dispatcher.glScissor(m_scissorX, m_scissorY,
                m_scissorWidth, m_scissorHeight);
    }
    dispatcher.glPolygonOffset(m_polygonOffsetFactor,
            m_polygonOffsetUnits);

    for (auto item : m_glEnableList) {
        if (item.first == GL_TEXTURE_2D
                || item.first == GL_TEXTURE_CUBE_MAP_OES) {
            continue;
        }
        std::function<void(GLenum)> enableFunc = item.second ? dispatcher.glEnable :
                                                   dispatcher.glDisable;
        if (item.first==GL_TEXTURE_GEN_STR_OES) {
            enableFunc(GL_TEXTURE_GEN_S);
            enableFunc(GL_TEXTURE_GEN_T);
            enableFunc(GL_TEXTURE_GEN_R);
        } else {
            enableFunc(item.first);
        }
    }

    if (getCaps()->ext_GL_EXT_draw_buffers_indexed) {
        for (GLuint i = 0; i < m_blendStates.size(); ++i) {
            if (m_blendStates[i].bEnable)
                dispatcher.glEnableiEXT(GL_BLEND, i);
            else
                dispatcher.glDisableiEXT(GL_BLEND, i);
            auto& blend = m_blendStates[i];
            dispatcher.glBlendEquationSeparateiEXT(i, blend.blendEquationRgb, blend.blendEquationAlpha);
            dispatcher.glBlendFuncSeparateiEXT(i, blend.blendSrcRgb, blend.blendDstRgb,
                                               blend.blendSrcAlpha, blend.blendDstAlpha);
            dispatcher.glColorMaskiEXT(i, blend.colorMaskR, blend.colorMaskG, blend.colorMaskB, blend.colorMaskA);
        }
    } else {
        if (m_blendStates[0].bEnable)
            dispatcher.glEnable(GL_BLEND);
        else
            dispatcher.glDisable(GL_BLEND);
        auto& blend = m_blendStates[0];
        dispatcher.glBlendEquationSeparate(blend.blendEquationRgb, blend.blendEquationAlpha);
        dispatcher.glBlendFuncSeparate(blend.blendSrcRgb, blend.blendDstRgb,
                                           blend.blendSrcAlpha, blend.blendDstAlpha);
        dispatcher.glColorMask(blend.colorMaskR, blend.colorMaskG, blend.colorMaskB, blend.colorMaskA);
    }

    for (const auto& pixelStore : m_glPixelStoreiList) {
        dispatcher.glPixelStorei(pixelStore.first, pixelStore.second);
    }

    dispatcher.glCullFace(m_cullFace);
    dispatcher.glFrontFace(m_frontFace);
    dispatcher.glDepthFunc(m_depthFunc);
    dispatcher.glDepthMask(m_depthMask);

    dispatcher.glLineWidth(m_lineWidth);

    dispatcher.glSampleCoverage(m_sampleCoverageVal, m_sampleCoverageInvert);

    for (int i = 0; i < 2; i++) {
        GLenum face = i == StencilFront ? GL_FRONT
                                       : GL_BACK;
        dispatcher.glStencilFuncSeparate(face, m_stencilStates[i].m_func,
                m_stencilStates[i].m_ref, m_stencilStates[i].m_funcMask);
        dispatcher.glStencilMaskSeparate(face, m_stencilStates[i].m_writeMask);
        dispatcher.glStencilOpSeparate(face, m_stencilStates[i].m_sfail,
                m_stencilStates[i].m_dpfail, m_stencilStates[i].m_dppass);
    }

    dispatcher.glClearColor(m_clearColorR, m_clearColorG, m_clearColorB,
            m_clearColorA);
    if (isGles2Gles()) {
        dispatcher.glClearDepthf(m_clearDepth);
        dispatcher.glDepthRangef(m_zNear, m_zFar);
    } else {
        dispatcher.glClearDepth(m_clearDepth);
        dispatcher.glDepthRange(m_zNear, m_zFar);
    }
    dispatcher.glClearStencil(m_clearStencil);


    // report any GL errors when loading from a snapshot
    GLenum err = 0;
    do {
        err = dispatcher.glGetError();
#ifdef _DEBUG
        if (err) {
            ERR("warning: get GL error %d while restoring a snapshot", err);
        }
#endif
    } while (err != 0);
}

ObjectDataPtr GLEScontext::loadObject(NamedObjectType type,
            ObjectLocalName localName, android::base::Stream* stream) const {
    switch (type) {
        case NamedObjectType::VERTEXBUFFER:
            return ObjectDataPtr(new GLESbuffer(stream));
        case NamedObjectType::TEXTURE:
            return ObjectDataPtr(new TextureData(stream));
        case NamedObjectType::FRAMEBUFFER:
            return ObjectDataPtr(new FramebufferData(stream));
        case NamedObjectType::RENDERBUFFER:
            return ObjectDataPtr(new RenderbufferData(stream));
        default:
            return {};
    }
}

const GLvoid* GLEScontext::setPointer(GLenum arrType,GLint size,GLenum type,GLsizei stride,const GLvoid* data, GLsizei dataSize, bool normalize, bool isInt) {
    GLuint bufferName = m_arrayBuffer;
    GLESpointer* glesPointer = nullptr;

    if (m_currVaoState.it->second.legacy) {
        auto vertexAttrib = m_currVaoState.find(arrType);
        if (vertexAttrib == m_currVaoState.end()) {
            return nullptr;
        }
        glesPointer = m_currVaoState[arrType];
    } else {
        uint32_t attribIndex = (uint32_t)arrType;
        if (attribIndex > kMaxVertexAttributes) return nullptr;
        glesPointer = m_currVaoState.attribInfo().data() + (uint32_t)arrType;
    }

    if(bufferName) {
        unsigned int offset = SafeUIntFromPointer(data);
        GLESbuffer* vbo = static_cast<GLESbuffer*>(
                m_shareGroup
                        ->getObjectData(NamedObjectType::VERTEXBUFFER,
                                        bufferName));
        if(offset >= vbo->getSize() || vbo->getSize() - offset < size) {
#ifdef _DEBUG
            ERR("Warning: Invalid pointer offset %u, arrType %d, type %d", offset, arrType, type);
#endif
            return nullptr;
        }

        glesPointer->setBuffer(size,type,stride,vbo,bufferName,offset,normalize, isInt);

        return  static_cast<const unsigned char*>(vbo->getData()) +  offset;
    }
    glesPointer->setArray(size,type,stride,data,dataSize,normalize,isInt);
    return data;
}

void GLEScontext::enableArr(GLenum arr,bool enable) {
    auto vertexAttrib = m_currVaoState.find(arr);
    if (vertexAttrib != m_currVaoState.end()) {
        vertexAttrib->second->enable(enable);
    }
}

bool GLEScontext::isArrEnabled(GLenum arr) {
    if (m_currVaoState.it->second.legacy) {
        return m_currVaoState[arr]->isEnable();
    } else {
        if ((uint32_t)arr > kMaxVertexAttributes) return false;
        return m_currVaoState.attribInfo()[(uint32_t)arr].isEnable();
    }
}

const GLESpointer* GLEScontext::getPointer(GLenum arrType) {
    const auto it = m_currVaoState.find(arrType);
    return it != m_currVaoState.end() ? it->second : nullptr;
}

static void convertFixedDirectLoop(const char* dataIn,unsigned int strideIn,void* dataOut,unsigned int nBytes,unsigned int strideOut,int attribSize) {

    for(unsigned int i = 0; i < nBytes;i+=strideOut) {
        const GLfixed* fixed_data = (const GLfixed *)dataIn;
        //filling attrib
        for(int j=0;j<attribSize;j++) {
            reinterpret_cast<GLfloat*>(&static_cast<unsigned char*>(dataOut)[i])[j] = X2F(fixed_data[j]);
        }
        dataIn += strideIn;
    }
}

static void convertFixedIndirectLoop(const char* dataIn,unsigned int strideIn,void* dataOut,GLsizei count,GLenum indices_type,const GLvoid* indices,unsigned int strideOut,int attribSize) {
    for(int i = 0 ;i < count ;i++) {
        GLuint index = getIndex(indices_type, indices, i);

        const GLfixed* fixed_data = (GLfixed *)(dataIn  + index*strideIn);
        GLfloat* float_data = reinterpret_cast<GLfloat*>(static_cast<unsigned char*>(dataOut) + index*strideOut);

        for(int j=0;j<attribSize;j++) {
            float_data[j] = X2F(fixed_data[j]);
         }
    }
}

static void convertByteDirectLoop(const char* dataIn,unsigned int strideIn,void* dataOut,unsigned int nBytes,unsigned int strideOut,int attribSize) {

    for(unsigned int i = 0; i < nBytes;i+=strideOut) {
        const GLbyte* byte_data = (const GLbyte *)dataIn;
        //filling attrib
        for(int j=0;j<attribSize;j++) {
            reinterpret_cast<GLshort*>(&static_cast<unsigned char*>(dataOut)[i])[j] = B2S(byte_data[j]);
        }
        dataIn += strideIn;
    }
}

static void convertByteIndirectLoop(const char* dataIn,unsigned int strideIn,void* dataOut,GLsizei count,GLenum indices_type,const GLvoid* indices,unsigned int strideOut,int attribSize) {
    for(int i = 0 ;i < count ;i++) {
        GLuint index = getIndex(indices_type, indices, i);
        const GLbyte* bytes_data = (GLbyte *)(dataIn  + index*strideIn);
        GLshort* short_data = reinterpret_cast<GLshort*>(static_cast<unsigned char*>(dataOut) + index*strideOut);

        for(int j=0;j<attribSize;j++) {
            short_data[j] = B2S(bytes_data[j]);
         }
    }
}
static void directToBytesRanges(GLint first,GLsizei count,GLESpointer* p,RangeList& list) {

    int attribSize = p->getSize()*4; //4 is the sizeof GLfixed or GLfloat in bytes
    int stride = p->getStride()?p->getStride():attribSize;
    int start  = p->getBufferOffset()+first*stride;
    if(!p->getStride()) {
        list.addRange(Range(start,count*attribSize));
    } else {
        for(int i = 0 ;i < count; i++,start+=stride) {
            list.addRange(Range(start,attribSize));
        }
    }
}

static void indirectToBytesRanges(const GLvoid* indices,GLenum indices_type,GLsizei count,GLESpointer* p,RangeList& list) {

    int attribSize = p->getSize() * 4; //4 is the sizeof GLfixed or GLfloat in bytes
    int stride = p->getStride()?p->getStride():attribSize;
    int start  = p->getBufferOffset();
    for(int i=0 ; i < count; i++) {
        GLuint index = getIndex(indices_type, indices, i);
        list.addRange(Range(start+index*stride,attribSize));

    }
}

int bytesRangesToIndices(RangeList& ranges,GLESpointer* p,GLuint* indices) {

    int attribSize = p->getSize() * 4; //4 is the sizeof GLfixed or GLfloat in bytes
    int stride = p->getStride()?p->getStride():attribSize;
    int offset = p->getBufferOffset();

    int n = 0;
    for(int i=0;i<ranges.size();i++) {
        int startIndex = (ranges[i].getStart() - offset) / stride;
        int nElements = ranges[i].getSize()/attribSize;
        for(int j=0;j<nElements;j++) {
            indices[n++] = startIndex+j;
        }
    }
    return n;
}

void GLEScontext::convertDirect(GLESConversionArrays& cArrs,GLint first,GLsizei count,GLenum array_id,GLESpointer* p) {

    GLenum type    = p->getType();
    int attribSize = p->getSize();
    unsigned int size = attribSize*count + first;
    unsigned int bytes = type == GL_FIXED ? sizeof(GLfixed):sizeof(GLbyte);
    cArrs.allocArr(size,type);
    int stride = p->getStride()?p->getStride():bytes*attribSize;
    const char* data = (const char*)p->getArrayData() + (first*stride);

    if(type == GL_FIXED) {
        convertFixedDirectLoop(data,stride,cArrs.getCurrentData(),size*sizeof(GLfloat),attribSize*sizeof(GLfloat),attribSize);
    } else if(type == GL_BYTE) {
        convertByteDirectLoop(data,stride,cArrs.getCurrentData(),size*sizeof(GLshort),attribSize*sizeof(GLshort),attribSize);
    }
}

void GLEScontext::convertDirectVBO(GLESConversionArrays& cArrs,GLint first,GLsizei count,GLenum array_id,GLESpointer* p) {

    RangeList ranges;
    RangeList conversions;
    GLuint* indices = NULL;
    int attribSize = p->getSize();
    int stride = p->getStride()?p->getStride():sizeof(GLfixed)*attribSize;
    char* data = (char*)p->getBufferData();

    if(p->bufferNeedConversion()) {
        directToBytesRanges(first,count,p,ranges); //converting indices range to buffer bytes ranges by offset
        p->getBufferConversions(ranges,conversions); // getting from the buffer the relevant ranges that still needs to be converted

        if(conversions.size()) { // there are some elements to convert
           indices = new GLuint[count];
           int nIndices = bytesRangesToIndices(conversions,p,indices); //converting bytes ranges by offset to indices in this array
           convertFixedIndirectLoop(data,stride,data,nIndices,GL_UNSIGNED_INT,indices,stride,attribSize);
        }
    }
    if(indices) delete[] indices;
    cArrs.setArr(data,p->getStride(),GL_FLOAT);
}

unsigned int GLEScontext::findMaxIndex(GLsizei count,GLenum type,const GLvoid* indices) {
    //finding max index
    unsigned int max = 0;
    if(type == GL_UNSIGNED_BYTE) {
        GLubyte*  b_indices  =(GLubyte *)indices;
        for(int i=0;i<count;i++) {
            if(b_indices[i] > max) max = b_indices[i];
        }
    } else if (type == GL_UNSIGNED_SHORT) {
        GLushort* us_indices =(GLushort *)indices;
        for(int i=0;i<count;i++) {
            if(us_indices[i] > max) max = us_indices[i];
        }
    } else { // type == GL_UNSIGNED_INT
        GLuint* ui_indices =(GLuint *)indices;
        for(int i=0;i<count;i++) {
            if(ui_indices[i] > max) max = ui_indices[i];
        }
    }
    return max;
}

void GLEScontext::convertIndirect(GLESConversionArrays& cArrs,GLsizei count,GLenum indices_type,const GLvoid* indices,GLenum array_id,GLESpointer* p) {
    GLenum type    = p->getType();
    int maxElements = findMaxIndex(count,indices_type,indices) + 1;

    int attribSize = p->getSize();
    int size = attribSize * maxElements;
    unsigned int bytes = type == GL_FIXED ? sizeof(GLfixed):sizeof(GLbyte);
    cArrs.allocArr(size,type);
    int stride = p->getStride()?p->getStride():bytes*attribSize;

    const char* data = (const char*)p->getArrayData();
    if(type == GL_FIXED) {
        convertFixedIndirectLoop(data,stride,cArrs.getCurrentData(),count,indices_type,indices,attribSize*sizeof(GLfloat),attribSize);
    } else if(type == GL_BYTE){
        convertByteIndirectLoop(data,stride,cArrs.getCurrentData(),count,indices_type,indices,attribSize*sizeof(GLshort),attribSize);
    }
}

void GLEScontext::convertIndirectVBO(GLESConversionArrays& cArrs,GLsizei count,GLenum indices_type,const GLvoid* indices,GLenum array_id,GLESpointer* p) {
    RangeList ranges;
    RangeList conversions;
    GLuint* conversionIndices = NULL;
    int attribSize = p->getSize();
    int stride = p->getStride()?p->getStride():sizeof(GLfixed)*attribSize;
    char* data = static_cast<char*>(p->getBufferData());
    if(p->bufferNeedConversion()) {
        indirectToBytesRanges(indices,indices_type,count,p,ranges); //converting indices range to buffer bytes ranges by offset
        p->getBufferConversions(ranges,conversions); // getting from the buffer the relevant ranges that still needs to be converted
        if(conversions.size()) { // there are some elements to convert
            conversionIndices = new GLuint[count];
            int nIndices = bytesRangesToIndices(conversions,p,conversionIndices); //converting bytes ranges by offset to indices in this array
            convertFixedIndirectLoop(data,stride,data,nIndices,GL_UNSIGNED_INT,conversionIndices,stride,attribSize);
        }
    }
    if(conversionIndices) delete[] conversionIndices;
    cArrs.setArr(data,p->getStride(),GL_FLOAT);
}

GLuint GLEScontext::bindBuffer(GLenum target,GLuint buffer) {
    switch(target) {
    case GL_ARRAY_BUFFER:
        m_arrayBuffer = buffer;
        break;
    case GL_ELEMENT_ARRAY_BUFFER:
        m_currVaoState.iboId() = buffer;
        break;
    case GL_COPY_READ_BUFFER:
        m_copyReadBuffer = buffer;
        break;
    case GL_COPY_WRITE_BUFFER:
        m_copyWriteBuffer = buffer;
        break;
    case GL_PIXEL_PACK_BUFFER:
        m_pixelPackBuffer = buffer;
        break;
    case GL_PIXEL_UNPACK_BUFFER:
        m_pixelUnpackBuffer = buffer;
        break;
    case GL_TRANSFORM_FEEDBACK_BUFFER:
        m_transformFeedbackBuffer = buffer;
        break;
    case GL_UNIFORM_BUFFER:
        m_uniformBuffer = buffer;
        break;
    case GL_ATOMIC_COUNTER_BUFFER:
        m_atomicCounterBuffer = buffer;
        break;
    case GL_DISPATCH_INDIRECT_BUFFER:
        m_dispatchIndirectBuffer = buffer;
        break;
    case GL_DRAW_INDIRECT_BUFFER:
        m_drawIndirectBuffer = buffer;
        break;
    case GL_SHADER_STORAGE_BUFFER:
        m_shaderStorageBuffer = buffer;
        break;
    case GL_TEXTURE_BUFFER:
        m_textureBuffer = buffer;
        break;
    default:
        m_arrayBuffer = buffer;
        break;
    }

    if (!buffer) return 0;

    auto sg = m_shareGroup.get();

    if (!sg) return 0;

    return sg->ensureObjectOnBind(NamedObjectType::VERTEXBUFFER, buffer);
}

void GLEScontext::bindIndexedBuffer(GLenum target, GLuint index, GLuint buffer,
        GLintptr offset, GLsizeiptr size, GLintptr stride, bool isBindBase) {
    VertexAttribBindingVector* bindings = nullptr;
    switch (target) {
    case GL_UNIFORM_BUFFER:
        bindings = &m_indexedUniformBuffers;
        break;
    case GL_ATOMIC_COUNTER_BUFFER:
        bindings = &m_indexedAtomicCounterBuffers;
        break;
    case GL_SHADER_STORAGE_BUFFER:
        bindings = &m_indexedShaderStorageBuffers;
        break;
    default:
        bindings = &m_currVaoState.bufferBindings();
        break;
    }
    if (index >= bindings->size()) {
            return;
    }
    auto& bufferBinding = (*bindings)[index];
    bufferBinding.buffer = buffer;
    bufferBinding.offset = offset;
    bufferBinding.size = size;
    bufferBinding.stride = stride;
    bufferBinding.isBindBase = isBindBase;
}

void GLEScontext::bindIndexedBuffer(GLenum target, GLuint index, GLuint buffer) {
    GLint sz;
    getBufferSizeById(buffer, &sz);
    bindIndexedBuffer(target, index, buffer, 0, sz, 0, true);
}

static void sClearIndexedBufferBinding(GLuint id, std::vector<BufferBinding>& bindings) {
    for (size_t i = 0; i < bindings.size(); i++) {
        if (bindings[i].buffer == id) {
            bindings[i].offset = 0;
            bindings[i].size = 0;
            bindings[i].stride = 0;
            bindings[i].buffer = 0;
            bindings[i].isBindBase = false;
        }
    }
}

void GLEScontext::unbindBuffer(GLuint buffer) {
    if (m_arrayBuffer == buffer)
        m_arrayBuffer = 0;
    if (m_currVaoState.iboId() == buffer)
        m_currVaoState.iboId() = 0;
    if (m_copyReadBuffer == buffer)
        m_copyReadBuffer = 0;
    if (m_copyWriteBuffer == buffer)
        m_copyWriteBuffer = 0;
    if (m_pixelPackBuffer == buffer)
        m_pixelPackBuffer = 0;
    if (m_pixelUnpackBuffer == buffer)
        m_pixelUnpackBuffer = 0;
    if (m_transformFeedbackBuffer == buffer)
        m_transformFeedbackBuffer = 0;
    if (m_uniformBuffer == buffer)
        m_uniformBuffer = 0;
    if (m_atomicCounterBuffer == buffer)
        m_atomicCounterBuffer = 0;
    if (m_dispatchIndirectBuffer == buffer)
        m_dispatchIndirectBuffer = 0;
    if (m_drawIndirectBuffer == buffer)
        m_drawIndirectBuffer = 0;
    if (m_shaderStorageBuffer == buffer)
        m_shaderStorageBuffer = 0;
    if (m_textureBuffer == buffer)
        m_textureBuffer = 0;

    // One might think that indexed buffer bindings for transform feedbacks
    // must be cleared as well, but transform feedbacks are
    // considered GL objects with attachments, so even if the buffer is
    // deleted (unbindBuffer is called), the state query with
    // glGetIntegeri_v must still return the deleted name [1].
    // sClearIndexedBufferBinding(buffer, m_indexedTransformFeedbackBuffers);
    // [1] OpenGL ES 3.0.5 spec Appendix D.1.3
    sClearIndexedBufferBinding(buffer, m_indexedUniformBuffers);
    sClearIndexedBufferBinding(buffer, m_indexedAtomicCounterBuffers);
    sClearIndexedBufferBinding(buffer, m_indexedShaderStorageBuffers);
    sClearIndexedBufferBinding(buffer, m_currVaoState.bufferBindings());
}

//checks if any buffer is binded to target
bool GLEScontext::isBindedBuffer(GLenum target) {
    switch(target) {
    case GL_ARRAY_BUFFER:
        return m_arrayBuffer != 0;
    case GL_ELEMENT_ARRAY_BUFFER:
        return m_currVaoState.iboId() != 0;
    case GL_COPY_READ_BUFFER:
        return m_copyReadBuffer != 0;
    case GL_COPY_WRITE_BUFFER:
        return m_copyWriteBuffer != 0;
    case GL_PIXEL_PACK_BUFFER:
        return m_pixelPackBuffer != 0;
    case GL_PIXEL_UNPACK_BUFFER:
        return m_pixelUnpackBuffer != 0;
    case GL_TRANSFORM_FEEDBACK_BUFFER:
        return m_transformFeedbackBuffer != 0;
    case GL_UNIFORM_BUFFER:
        return m_uniformBuffer != 0;
    case GL_ATOMIC_COUNTER_BUFFER:
        return m_atomicCounterBuffer != 0;
    case GL_DISPATCH_INDIRECT_BUFFER:
        return m_dispatchIndirectBuffer != 0;
    case GL_DRAW_INDIRECT_BUFFER:
        return m_drawIndirectBuffer != 0;
    case GL_SHADER_STORAGE_BUFFER:
        return m_shaderStorageBuffer != 0;
    case GL_TEXTURE_BUFFER:
        return m_textureBuffer != 0;
    default:
        return m_arrayBuffer != 0;
    }
}

GLuint GLEScontext::getBuffer(GLenum target) {
    switch(target) {
    case GL_ARRAY_BUFFER:
        return m_arrayBuffer;
    case GL_ELEMENT_ARRAY_BUFFER:
        return m_currVaoState.iboId();
    case GL_COPY_READ_BUFFER:
        return m_copyReadBuffer;
    case GL_COPY_WRITE_BUFFER:
        return m_copyWriteBuffer;
    case GL_PIXEL_PACK_BUFFER:
        return m_pixelPackBuffer;
    case GL_PIXEL_UNPACK_BUFFER:
        return m_pixelUnpackBuffer;
    case GL_TRANSFORM_FEEDBACK_BUFFER:
        return m_transformFeedbackBuffer;
    case GL_UNIFORM_BUFFER:
        return m_uniformBuffer;
    case GL_ATOMIC_COUNTER_BUFFER:
        return m_atomicCounterBuffer;
    case GL_DISPATCH_INDIRECT_BUFFER:
        return m_dispatchIndirectBuffer;
    case GL_DRAW_INDIRECT_BUFFER:
        return m_drawIndirectBuffer;
    case GL_SHADER_STORAGE_BUFFER:
        return m_shaderStorageBuffer;
    case GL_TEXTURE_BUFFER:
        return m_textureBuffer;
    default:
        return m_arrayBuffer;
    }
}

GLuint GLEScontext::getIndexedBuffer(GLenum target, GLuint index) {
    switch (target) {
    case GL_UNIFORM_BUFFER:
        return m_indexedUniformBuffers[index].buffer;
    case GL_ATOMIC_COUNTER_BUFFER:
        return m_indexedAtomicCounterBuffers[index].buffer;
    case GL_SHADER_STORAGE_BUFFER:
        return m_indexedShaderStorageBuffers[index].buffer;
    default:
        return m_currVaoState.bufferBindings()[index].buffer;
    }
}


GLvoid* GLEScontext::getBindedBuffer(GLenum target) {
    GLuint bufferName = getBuffer(target);
    if(!bufferName) return NULL;

    GLESbuffer* vbo = static_cast<GLESbuffer*>(
            m_shareGroup
                    ->getObjectData(NamedObjectType::VERTEXBUFFER, bufferName));
    return vbo->getData();
}

void GLEScontext::getBufferSize(GLenum target,GLint* param) {
    GLuint bufferName = getBuffer(target);
    getBufferSizeById(bufferName, param);
}

void GLEScontext::getBufferSizeById(GLuint bufferName, GLint* param) {
    if (!bufferName) { *param = 0; return; }
    GLESbuffer* vbo = static_cast<GLESbuffer*>(
            m_shareGroup
                    ->getObjectData(NamedObjectType::VERTEXBUFFER, bufferName));
    *param = vbo->getSize();
}

void GLEScontext::getBufferUsage(GLenum target,GLint* param) {
    GLuint bufferName = getBuffer(target);
    GLESbuffer* vbo = static_cast<GLESbuffer*>(
            m_shareGroup
                    ->getObjectData(NamedObjectType::VERTEXBUFFER, bufferName));
    *param = vbo->getUsage();
}

bool GLEScontext::setBufferData(GLenum target,GLsizeiptr size,const GLvoid* data,GLenum usage) {
    GLuint bufferName = getBuffer(target);
    if(!bufferName) return false;
    GLESbuffer* vbo = static_cast<GLESbuffer*>(
            m_shareGroup
                    ->getObjectData(NamedObjectType::VERTEXBUFFER, bufferName));
    return vbo->setBuffer(size,usage,data);
}

bool GLEScontext::setBufferSubData(GLenum target,GLintptr offset,GLsizeiptr size,const GLvoid* data) {

    GLuint bufferName = getBuffer(target);
    if(!bufferName) return false;
    GLESbuffer* vbo = static_cast<GLESbuffer*>(
            m_shareGroup
                    ->getObjectData(NamedObjectType::VERTEXBUFFER, bufferName));
    return vbo->setSubBuffer(offset,size,data);
}

void GLEScontext::setViewport(GLint x, GLint y, GLsizei width, GLsizei height) {
    m_isViewport = true;
    m_viewportX = x;
    m_viewportY = y;
    m_viewportWidth = width;
    m_viewportHeight = height;
}

void GLEScontext::getViewport(GLint* params) {
    if (!m_isViewport) {
        dispatcher().glGetIntegerv(GL_VIEWPORT, params);
    } else {
        params[0] = m_viewportX;
        params[1] = m_viewportY;
        params[2] = m_viewportWidth;
        params[3] = m_viewportHeight;
    }
}

void GLEScontext::setScissor(GLint x, GLint y, GLsizei width, GLsizei height) {
    m_isScissor = true;
    m_scissorX = x;
    m_scissorY = y;
    m_scissorWidth = width;
    m_scissorHeight = height;
}

void GLEScontext::setPolygonOffset(GLfloat factor, GLfloat units) {
    m_polygonOffsetFactor = factor;
    m_polygonOffsetUnits = units;
}

void GLEScontext::setEnable(GLenum item, bool isEnable) {
    switch (item) {
        case GL_TEXTURE_2D:
        case GL_TEXTURE_CUBE_MAP_OES:
        case GL_TEXTURE_3D:
        case GL_TEXTURE_2D_ARRAY:
        case GL_TEXTURE_2D_MULTISAMPLE:
        case GL_TEXTURE_BUFFER:
            setTextureEnabled(item,true);
            break;
        case GL_BLEND:
            for (auto& blend : m_blendStates) {
                blend.bEnable = isEnable ? GL_TRUE : GL_FALSE;
            }
            break;
        default:
            m_glEnableList[item] = isEnable;
            break;
    }
}


void GLEScontext::setEnablei(GLenum item, GLuint index, bool isEnable) {
    switch (item) {
        case GL_BLEND:
            if (index < m_blendStates.size()) {
                m_blendStates[index].bEnable = isEnable ? GL_TRUE : GL_FALSE;
            }
            break;
    }
}

bool GLEScontext::isEnabled(GLenum item) const {
    switch (item) {
        case GL_TEXTURE_2D:
        case GL_TEXTURE_CUBE_MAP_OES:
        case GL_TEXTURE_3D:
        case GL_TEXTURE_2D_ARRAY:
        case GL_TEXTURE_2D_MULTISAMPLE:
        case GL_TEXTURE_BUFFER:
            return m_texState[m_activeTexture][GLTextureTargetToLocal(item)].enabled;
        case GL_BLEND:
            return m_blendStates[0].bEnable!=GL_FALSE;
        default:
            return android::base::findOrDefault(m_glEnableList, item, false);
    }
}

void GLEScontext::setBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha) {
    for (auto & blendState : m_blendStates) {
        blendState.blendEquationRgb = modeRGB;
        blendState.blendEquationAlpha = modeAlpha;
    }
}

void GLEScontext::setBlendEquationSeparatei(GLenum buf, GLenum modeRGB, GLenum modeAlpha) {
    if (buf < m_blendStates.size()) {
        m_blendStates[buf].blendEquationRgb = modeRGB;
        m_blendStates[buf].blendEquationAlpha = modeAlpha;
    }
}

void GLEScontext::setBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB,
            GLenum srcAlpha, GLenum dstAlpha) {
    for (auto& blendState : m_blendStates) {
        blendState.blendSrcRgb = srcRGB;
        blendState.blendDstRgb = dstRGB;
        blendState.blendSrcAlpha = srcAlpha;
        blendState.blendDstAlpha = dstAlpha;
    }
}

void GLEScontext::setBlendFuncSeparatei(GLenum buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha,
                                       GLenum dstAlpha) {
    if (buf < m_blendStates.size()) {
        m_blendStates[buf].blendSrcRgb = srcRGB;
        m_blendStates[buf].blendDstRgb = dstRGB;
        m_blendStates[buf].blendSrcAlpha = srcAlpha;
        m_blendStates[buf].blendDstAlpha = dstAlpha;
    }
}

void GLEScontext::setPixelStorei(GLenum pname, GLint param) {
    m_glPixelStoreiList[pname] = param;
}

void GLEScontext::setCullFace(GLenum mode) {
    m_cullFace = mode;
}

void GLEScontext::setFrontFace(GLenum mode) {
    m_frontFace = mode;
}

void GLEScontext::setDepthFunc(GLenum func) {
    m_depthFunc = func;
}

void GLEScontext::setDepthMask(GLboolean flag) {
    m_depthMask = flag;
}

void GLEScontext::setDepthRangef(GLclampf zNear, GLclampf zFar) {
    m_zNear = zNear;
    m_zFar = zFar;
}

void GLEScontext::setLineWidth(GLfloat lineWidth) {
    m_lineWidth = lineWidth;
}

void GLEScontext::setSampleCoverage(GLclampf value, GLboolean invert) {
    m_sampleCoverageVal = value;
    m_sampleCoverageInvert = invert;
}
void GLEScontext::setStencilFuncSeparate(GLenum face, GLenum func, GLint ref,
        GLuint mask) {
    if (face == GL_FRONT_AND_BACK) {
        setStencilFuncSeparate(GL_FRONT, func, ref, mask);
        setStencilFuncSeparate(GL_BACK, func, ref, mask);
        return;
    }
    int idx = 0;
    switch (face) {
        case GL_FRONT:
            idx = StencilFront;
            break;
        case GL_BACK:
            idx = StencilBack;
            break;
        default:
            return;
    }
    m_stencilStates[idx].m_func = func;
    m_stencilStates[idx].m_ref = ref;
    m_stencilStates[idx].m_funcMask = mask;
}

void GLEScontext::setStencilMaskSeparate(GLenum face, GLuint mask) {
    if (face == GL_FRONT_AND_BACK) {
        setStencilMaskSeparate(GL_FRONT, mask);
        setStencilMaskSeparate(GL_BACK, mask);
        return;
    }
    int idx = 0;
    switch (face) {
        case GL_FRONT:
            idx = StencilFront;
            break;
        case GL_BACK:
            idx = StencilBack;
            break;
        default:
            return;
    }
    m_stencilStates[idx].m_writeMask = mask;
}

void GLEScontext::setStencilOpSeparate(GLenum face, GLenum fail, GLenum zfail,
        GLenum zpass) {
    if (face == GL_FRONT_AND_BACK) {
        setStencilOpSeparate(GL_FRONT, fail, zfail, zpass);
        setStencilOpSeparate(GL_BACK, fail, zfail, zpass);
        return;
    }
    int idx = 0;
    switch (face) {
        case GL_FRONT:
            idx = StencilFront;
            break;
        case GL_BACK:
            idx = StencilBack;
            break;
        default:
            return;
    }
    m_stencilStates[idx].m_sfail = fail;
    m_stencilStates[idx].m_dpfail = zfail;
    m_stencilStates[idx].m_dppass = zpass;
}

void GLEScontext::setColorMask(GLboolean red, GLboolean green, GLboolean blue,
        GLboolean alpha) {
    for (auto& blend : m_blendStates) {
        blend.colorMaskR = red;
        blend.colorMaskG = green;
        blend.colorMaskB = blue;
        blend.colorMaskA = alpha;
    }
}

void GLEScontext::setColorMaski(GLuint buf, GLboolean red, GLboolean green, GLboolean blue,
    GLboolean alpha) {
    if (buf < m_blendStates.size()) {
        m_blendStates[buf].colorMaskR = red;
        m_blendStates[buf].colorMaskG = green;
        m_blendStates[buf].colorMaskB = blue;
        m_blendStates[buf].colorMaskA = alpha;
    }
}

void GLEScontext::setClearColor(GLclampf red, GLclampf green, GLclampf blue,
        GLclampf alpha) {
    m_clearColorR = red;
    m_clearColorG = green;
    m_clearColorB = blue;
    m_clearColorA = alpha;
}

void GLEScontext::setClearDepth(GLclampf depth) {
    m_clearDepth = depth;
}

void GLEScontext::setClearStencil(GLint s) {
    m_clearStencil = s;
}

const char* GLEScontext::getExtensionString(bool isGles1) {
    const char * ret;
    s_lock.lock();
    if (isGles1) {
        if (s_glExtensionsGles1)
            ret = s_glExtensionsGles1->c_str();
        else
            ret="";
    } else {
        if (m_glesMajorVersion == 3 && m_glesMinorVersion == 1) {
            if (s_glExtensionsGles31)
                ret = s_glExtensionsGles31->c_str();
            else
                ret = "";
        } else {
            if (s_glExtensions)
                ret = s_glExtensions->c_str();
            else
                ret = "";
        }
    }
    s_lock.unlock();
    return ret;
}

const char* GLEScontext::getVendorString(bool isGles1) const {
    if (isGles1) {
        return s_glVendorGles1.c_str();
    } else {
        if (m_glesMajorVersion == 3 && m_glesMinorVersion == 1) {
            return s_glVendorGles31.c_str();
        } else {
            return s_glVendor.c_str();
        }
    }
}

const char* GLEScontext::getRendererString(bool isGles1) const {
    if (isGles1) {
        return s_glRendererGles1.c_str();
    } else {
        if (m_glesMajorVersion == 3 && m_glesMinorVersion == 1) {
            return s_glRendererGles31.c_str();
        } else {
            return s_glRenderer.c_str();
        }
    }
}

const char* GLEScontext::getVersionString(bool isGles1) const {
    if (isGles1) {
        return s_glVersionGles1.c_str();
    } else {
        if (m_glesMajorVersion == 3 && m_glesMinorVersion == 1) {
            return s_glVersionGles31.c_str();
        } else {
            return s_glVersion.c_str();
        }
    }
}

void GLEScontext::getGlobalLock() {
    s_lock.lock();
}

void GLEScontext::releaseGlobalLock() {
    s_lock.unlock();
}

void GLEScontext::initCapsLocked(const GLubyte * extensionString, GLSupport& glSupport)
{
    const char* cstring = (const char*)extensionString;

    s_glDispatch.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &glSupport.maxVertexAttribs);

    if (glSupport.maxVertexAttribs > kMaxVertexAttributes) {
        glSupport.maxVertexAttribs = kMaxVertexAttributes;
    }

    s_glDispatch.glGetIntegerv(GL_MAX_CLIP_PLANES, &glSupport.maxClipPlane);
    s_glDispatch.glGetIntegerv(GL_MAX_LIGHTS, &glSupport.maxLights);
    s_glDispatch.glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glSupport.maxTexSize);
    s_glDispatch.glGetIntegerv(GL_MAX_TEXTURE_UNITS, &glSupport.maxTexUnits);
    // Core profile lacks a fixed-function pipeline with texture units,
    // but we still want glDrawTexOES to work in core profile.
    // So, set it to 8.
    if ((::isCoreProfile() || isGles2Gles()) && !glSupport.maxTexUnits) {
        glSupport.maxTexUnits = 8;
    }
    s_glDispatch.glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &glSupport.maxTexImageUnits);
    s_glDispatch.glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
                               &glSupport.maxCombinedTexImageUnits);
    s_glDispatch.glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
                               &glSupport.maxTransformFeedbackSeparateAttribs);
    s_glDispatch.glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &glSupport.maxUniformBufferBindings);
    s_glDispatch.glGetIntegerv(GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS,
                               &glSupport.maxAtomicCounterBufferBindings);
    s_glDispatch.glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS,
                               &glSupport.maxShaderStorageBufferBindings);
    s_glDispatch.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &glSupport.maxDrawBuffers);
    s_glDispatch.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &glSupport.maxVertexAttribBindings);

    // Compressed texture format query
    if(feature_is_enabled(kFeature_NativeTextureDecompression)) {
        bool hasEtc2Support = false;
        bool hasAstcSupport = false;
        int numCompressedFormats = 0;
        s_glDispatch.glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numCompressedFormats);
        if (numCompressedFormats > 0) {
            int numEtc2Formats = 0;
            int numAstcFormats = 0;
            int numEtc2FormatsSupported = 0;
            int numAstcFormatsSupported = 0;

            std::map<GLint, bool> found;
            forEachEtc2Format([&numEtc2Formats, &found](GLint format) { ++numEtc2Formats; found[format] = false;});
            forEachAstcFormat([&numAstcFormats, &found](GLint format) { ++numAstcFormats; found[format] = false;});

            std::vector<GLint> formats(numCompressedFormats);
            s_glDispatch.glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, formats.data());

            for (int i = 0; i < numCompressedFormats; ++i) {
                GLint format = formats[i];
                if (isEtc2Format(format)) {
                    ++numEtc2FormatsSupported;
                    found[format] = true;
                } else if (isAstcFormat(format)) {
                    ++numAstcFormatsSupported;
                    found[format] = true;
                }
            }

            if (numEtc2Formats == numEtc2FormatsSupported) {
                hasEtc2Support = true; // Supports ETC2 underneath
            } else {
                // It is unusual to support only some. Record what happened.
                ERR("Not supporting etc2: %d vs %d",
                    numEtc2FormatsSupported, numEtc2Formats);
                for (auto it : found) {
                    if (!it.second) {
                        ERR("Not found: 0x%x", it.first);
                    }
                }
            }

            if (numAstcFormats == numAstcFormatsSupported) {
                hasAstcSupport = true; // Supports ASTC underneath
            } else {
                // It is unusual to support only some. Record what happened.
                ERR("Not supporting astc: %d vs %d",
                    numAstcFormatsSupported, numAstcFormats);
                for (auto it : found) {
                    if (!it.second) {
                        ERR("Not found: 0x%x", it.first);
                    }
                }
            }
        }
        glSupport.hasEtc2Support = hasEtc2Support;
        glSupport.hasAstcSupport = hasAstcSupport;
    }

    // Clear GL error in case these enums not supported.
    s_glDispatch.glGetError();

    const GLubyte* glslVersion = s_glDispatch.glGetString(GL_SHADING_LANGUAGE_VERSION);
    glSupport.glslVersion = Version((const char*)(glslVersion));
    const GLubyte* glVersion = s_glDispatch.glGetString(GL_VERSION);

    // fprintf(stderr, "%s: vendor renderer version [%s] [%s] [%s]\n", __func__,
    //         s_glDispatch.glGetString(GL_VENDOR),
    //         s_glDispatch.glGetString(GL_RENDERER),
    //         s_glDispatch.glGetString(GL_VERSION));

    if (strstr(cstring,"GL_EXT_bgra ")!=NULL ||
        (isGles2Gles() && strstr(cstring, "GL_EXT_texture_format_BGRA8888")) ||
        (!isGles2Gles() && !(Version((const char*)glVersion) < Version("1.2"))))
        glSupport.GL_EXT_TEXTURE_FORMAT_BGRA8888 = true;

    if (::isCoreProfile() ||
        strstr(cstring,"GL_EXT_framebuffer_object ")!=NULL)
        glSupport.GL_EXT_FRAMEBUFFER_OBJECT = true;

    if (strstr(cstring,"GL_ARB_vertex_blend ")!=NULL) glSupport.GL_ARB_VERTEX_BLEND = true;

    if (strstr(cstring,"GL_ARB_matrix_palette ")!=NULL) glSupport.GL_ARB_MATRIX_PALETTE = true;

    if (strstr(cstring,"GL_EXT_packed_depth_stencil ")!=NULL ||
        strstr(cstring,"GL_OES_packed_depth_stencil ")!=NULL)
        glSupport.GL_EXT_PACKED_DEPTH_STENCIL = true;

    if (strstr(cstring,"GL_OES_read_format ")!=NULL) glSupport.GL_OES_READ_FORMAT = true;

    if (strstr(cstring,"GL_ARB_half_float_pixel ")!=NULL ||
        strstr(cstring,"GL_OES_texture_half_float ")!=NULL)
        glSupport.GL_ARB_HALF_FLOAT_PIXEL = true;

    if (strstr(cstring,"GL_NV_half_float ")!=NULL) glSupport.GL_NV_HALF_FLOAT = true;

    if (strstr(cstring,"GL_ARB_half_float_vertex ")!=NULL ||
        strstr(cstring,"GL_OES_vertex_half_float ")!=NULL)
        glSupport.GL_ARB_HALF_FLOAT_VERTEX = true;

    if (strstr(cstring,"GL_SGIS_generate_mipmap ")!=NULL)
        glSupport.GL_SGIS_GENERATE_MIPMAP = true;

    if (strstr(cstring,"GL_ARB_ES2_compatibility ")!=NULL
            || isGles2Gles())
        glSupport.GL_ARB_ES2_COMPATIBILITY = true;

    if (strstr(cstring,"GL_OES_standard_derivatives ")!=NULL)
        glSupport.GL_OES_STANDARD_DERIVATIVES = true;

    if (::isCoreProfile() ||
        strstr(cstring,"GL_ARB_texture_non_power_of_two")!=NULL ||
        strstr(cstring,"GL_OES_texture_npot")!=NULL)
        glSupport.GL_OES_TEXTURE_NPOT = true;

    if (::isCoreProfile() ||
        strstr(cstring,"GL_ARB_color_buffer_float")!=NULL ||
        strstr(cstring,"GL_EXT_color_buffer_float")!=NULL)
        glSupport.ext_GL_EXT_color_buffer_float = true;

    if (::isCoreProfile() ||
        strstr(cstring,"GL_EXT_color_buffer_half_float")!=NULL)
        glSupport.ext_GL_EXT_color_buffer_half_float = true;

    if (strstr(cstring,"GL_EXT_shader_framebuffer_fetch")!=NULL) {
        glSupport.ext_GL_EXT_shader_framebuffer_fetch = true;
    }

    if (!(Version((const char*)glVersion) < Version("3.0")) || strstr(cstring,"GL_OES_rgb8_rgba8")!=NULL)
        glSupport.GL_OES_RGB8_RGBA8 = true;

    if (strstr(cstring, "GL_EXT_memory_object") != NULL) {
        glSupport.ext_GL_EXT_memory_object = true;
    }

    if (strstr(cstring, "GL_EXT_semaphore") != NULL) {
        glSupport.ext_GL_EXT_semaphore = true;
    }

    if (strstr(cstring, "GL_EXT_texture_buffer") != NULL) {
        glSupport.ext_GL_EXT_texture_buffer = true;
    }

    if (strstr(cstring, "GL_OES_texture_buffer") != NULL) {
        glSupport.ext_GL_OES_texture_buffer = true;
    }

    if (strstr(cstring, "GL_EXT_draw_buffers_indexed") != NULL) {
        glSupport.ext_GL_EXT_draw_buffers_indexed = true;
    }

    // ASTC
    if (strstr(cstring, "GL_KHR_texture_compression_astc_ldr") != NULL) {
        glSupport.ext_GL_KHR_texture_compression_astc_ldr = true;
    }

    // BPTC extension detection
    if (strstr(cstring, "GL_EXT_texture_compression_bptc") != NULL) {
        glSupport.hasBptcSupport = true;
    }

    // S3TC extension detection
    if (strstr(cstring, "GL_EXT_texture_compression_s3tc") != NULL) {
        glSupport.hasS3tcSupport = true;
    }

    if (strstr(cstring, "GL_EXT_texture_compression_rgtc") != NULL) {
        glSupport.hasRgtcSupport = true;
    }
}

void GLEScontext::buildStrings(int major, int minor, const char* baseVendor,
        const char* baseRenderer, const char* baseVersion, const char* version)
{
    static const char VENDOR[]   = {"Google ("};
    static const char RENDERER[] = {"Android Emulator OpenGL ES Translator ("};
    const size_t VENDOR_LEN   = sizeof(VENDOR) - 1;
    const size_t RENDERER_LEN = sizeof(RENDERER) - 1;

    // Sanitize the strings as some OpenGL implementations return NULL
    // when asked the basic questions (this happened at least once on a client
    // machine)
    if (!baseVendor) {
        baseVendor = "N/A";
    }
    if (!baseRenderer) {
        baseRenderer = "N/A";
    }
    if (!baseVersion) {
        baseVersion = "N/A";
    }
    if (!version) {
        version = "N/A";
    }

    bool isES31 = major == 3 && minor == 1;
    bool isES11 = major == 1;
    std::string& vendorString = isES11 ? s_glVendorGles1 : (isES31? s_glVendorGles31 : s_glVendor);
    std::string& rendererString = isES11 ? s_glRendererGles1 : (isES31? s_glRendererGles31 : s_glRenderer);
    std::string& versionString =
        isES11 ? s_glVersionGles1 : (isES31 ? s_glVersionGles31 : s_glVersion);

    size_t baseVendorLen = strlen(baseVendor);
    vendorString.clear();
    vendorString.reserve(baseVendorLen + VENDOR_LEN + 1);
    vendorString.append(VENDOR, VENDOR_LEN);
    vendorString.append(baseVendor, baseVendorLen);
    vendorString.append(")", 1);

    size_t baseRendererLen = strlen(baseRenderer);
    rendererString.clear();
    rendererString.reserve(baseRendererLen + RENDERER_LEN + 1);
    rendererString.append(RENDERER, RENDERER_LEN);
    rendererString.append(baseRenderer, baseRendererLen);
    rendererString.append(")", 1);

    size_t baseVersionLen = strlen(baseVersion);
    size_t versionLen = strlen(version);
    versionString.clear();
    versionString.reserve(baseVersionLen + versionLen + 3);
    versionString.append(version, versionLen);
    versionString.append(" (", 2);
    versionString.append(baseVersion, baseVersionLen);
    versionString.append(")", 1);
}

bool GLEScontext::isTextureUnitEnabled(GLenum unit) {
    for (int i=0;i<NUM_TEXTURE_TARGETS;++i) {
        if (m_texState[unit-GL_TEXTURE0][i].enabled)
            return true;
    }
    return false;
}

bool GLEScontext::glGetBooleanv(GLenum pname, GLboolean *params)
{
    GLint iParam;

    if(glGetIntegerv(pname, &iParam))
    {
        *params = (iParam != 0);
        return true;
    }

    return false;
}

bool GLEScontext::glGetFixedv(GLenum pname, GLfixed *params)
{
    bool result = false;
    GLint numParams = 1;

    GLint* iParams = new GLint[numParams];
    if (numParams>0 && glGetIntegerv(pname,iParams)) {
        while(numParams >= 0)
        {
            params[numParams] = I2X(iParams[numParams]);
            numParams--;
        }
        result = true;
    }
    delete [] iParams;

    return result;
}

bool GLEScontext::glGetFloatv(GLenum pname, GLfloat *params)
{
    bool result = false;
    GLint numParams = 1;

    GLint* iParams = new GLint[numParams];
    if (numParams>0 && glGetIntegerv(pname,iParams)) {
        while(numParams >= 0)
        {
            params[numParams] = (GLfloat)iParams[numParams];
            numParams--;
        }
        result = true;
    }
    delete [] iParams;

    return result;
}

bool GLEScontext::glGetIntegerv(GLenum pname, GLint *params)
{
    switch(pname)
    {
        case GL_ARRAY_BUFFER_BINDING:
            *params = m_arrayBuffer;
            break;

        case GL_ELEMENT_ARRAY_BUFFER_BINDING:
            *params = m_currVaoState.iboId();
            break;

        case GL_TEXTURE_BINDING_CUBE_MAP:
            *params = m_texState[m_activeTexture][TEXTURE_CUBE_MAP].texture;
            break;

        case GL_TEXTURE_BINDING_2D:
            *params = m_texState[m_activeTexture][TEXTURE_2D].texture;
            break;

        case GL_ACTIVE_TEXTURE:
            *params = m_activeTexture+GL_TEXTURE0;
            break;

        case GL_MAX_TEXTURE_SIZE:
            *params = getMaxTexSize();
            break;
        default:
            return false;
    }

    return true;
}

TextureTarget GLEScontext::GLTextureTargetToLocal(GLenum target) {
    TextureTarget value=TEXTURE_2D;
    switch (target) {
    case GL_TEXTURE_CUBE_MAP:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
        value = TEXTURE_CUBE_MAP;
        break;
    case GL_TEXTURE_2D:
        value = TEXTURE_2D;
        break;
    case GL_TEXTURE_2D_ARRAY:
        value = TEXTURE_2D_ARRAY;
        break;
    case GL_TEXTURE_3D:
        value = TEXTURE_3D;
        break;
    case GL_TEXTURE_2D_MULTISAMPLE:
        value = TEXTURE_2D_MULTISAMPLE;
        break;
    case GL_TEXTURE_BUFFER:
        value = TEXTURE_BUFFER;
        break;
    }
    return value;
}

unsigned int GLEScontext::getBindedTexture(GLenum target) {
    TextureTarget pos = GLTextureTargetToLocal(target);
    return m_texState[m_activeTexture][pos].texture;
}

unsigned int GLEScontext::getBindedTexture(GLenum unit, GLenum target) {
    TextureTarget pos = GLTextureTargetToLocal(target);
    return m_texState[unit-GL_TEXTURE0][pos].texture;
}

void GLEScontext::setBindedTexture(GLenum target, unsigned int tex) {
    TextureTarget pos = GLTextureTargetToLocal(target);
    m_texState[m_activeTexture][pos].texture = tex;
}

void GLEScontext::setTextureEnabled(GLenum target, GLenum enable) {
    TextureTarget pos = GLTextureTargetToLocal(target);
    m_texState[m_activeTexture][pos].enabled = enable;
}

#define INTERNAL_NAME(x) (x +0x100000000ll);

ObjectLocalName GLEScontext::getDefaultTextureName(GLenum target) {
    ObjectLocalName name = 0;
    switch (GLTextureTargetToLocal(target)) {
    case TEXTURE_2D:
        name = INTERNAL_NAME(0);
        break;
    case TEXTURE_CUBE_MAP:
        name = INTERNAL_NAME(1);
        break;
    case TEXTURE_2D_ARRAY:
        name = INTERNAL_NAME(2);
        break;
    case TEXTURE_3D:
        name = INTERNAL_NAME(3);
        break;
    case TEXTURE_2D_MULTISAMPLE:
        name = INTERNAL_NAME(4);
        break;
    case TEXTURE_BUFFER:
        name = INTERNAL_NAME(5);
        break;
    default:
        name = 0;
        break;
    }
    return name;
}

ObjectLocalName GLEScontext::getTextureLocalName(GLenum target,
        unsigned int tex) {
    return (tex!=0? tex : getDefaultTextureName(target));
}

void GLEScontext::drawValidate(void)
{
    if(m_drawFramebuffer == 0)
        return;

    auto fbObj = getFBOData(m_drawFramebuffer);
    if (!fbObj)
        return;

    fbObj->validate(this);
}

void GLEScontext::initEmulatedEGLSurface(GLint width, GLint height,
                             GLint colorFormat, GLint depthstencilFormat, GLint multisamples,
                             GLuint rboColor, GLuint rboDepth) {
    dispatcher().glBindRenderbuffer(GL_RENDERBUFFER, rboColor);
    if (multisamples) {
        dispatcher().glRenderbufferStorageMultisample(GL_RENDERBUFFER, multisamples, colorFormat, width, height);
        GLint err = dispatcher().glGetError();
        if (err != GL_NO_ERROR) {
            ERR("error setting up multisampled RBO! 0x%x", err);
        }
    } else {
        dispatcher().glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, width, height);
    }

    dispatcher().glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
    if (multisamples) {
        dispatcher().glRenderbufferStorageMultisample(GL_RENDERBUFFER, multisamples, depthstencilFormat, width, height);
        GLint err = dispatcher().glGetError();
        if (err != GL_NO_ERROR) {
            ERR("error setting up multisampled RBO! 0x%x", err);
        }
    } else {
        dispatcher().glRenderbufferStorage(GL_RENDERBUFFER, depthstencilFormat, width, height);
    }
}

void GLEScontext::initDefaultFBO(
        GLint width, GLint height, GLint colorFormat, GLint depthstencilFormat, GLint multisamples,
        GLuint* eglSurfaceRBColorId, GLuint* eglSurfaceRBDepthId,
        GLuint readWidth, GLint readHeight, GLint readColorFormat, GLint readDepthStencilFormat, GLint readMultisamples,
        GLuint* eglReadSurfaceRBColorId, GLuint* eglReadSurfaceRBDepthId) {
    bool needUpdateDefaultFbo = false;
    if (!m_defaultFBO) {
        dispatcher().glGenFramebuffers(1, &m_defaultFBO);
        m_defaultReadFBO = m_defaultFBO;
        needUpdateDefaultFbo = true;
    }

    bool needReallocateRbo = false;
    bool separateReadRbo = false;
    bool needReallocateReadRbo = false;

    separateReadRbo =
        eglReadSurfaceRBColorId !=
        eglSurfaceRBColorId;

    if (separateReadRbo && (m_defaultReadFBO == m_defaultFBO)) {
        dispatcher().glGenFramebuffers(1, &m_defaultReadFBO);
    }

    if (!(*eglSurfaceRBColorId)) {
        dispatcher().glGenRenderbuffers(1, eglSurfaceRBColorId);
        dispatcher().glGenRenderbuffers(1, eglSurfaceRBDepthId);
        needReallocateRbo = true;
    }

    if (!(*eglReadSurfaceRBColorId) && separateReadRbo) {
        dispatcher().glGenRenderbuffers(1, eglReadSurfaceRBColorId);
        dispatcher().glGenRenderbuffers(1, eglReadSurfaceRBDepthId);
        needReallocateReadRbo = true;
    }

    m_defaultFBOColorFormat = colorFormat;
    m_defaultFBOWidth = width;
    m_defaultFBOHeight = height;
    m_defaultFBOSamples = multisamples;

    GLint prevRbo;
    dispatcher().glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRbo);

    // OS X in legacy opengl mode does not actually support GL_RGB565 as a renderbuffer.
    // Just replace it with GL_RGB8 for now.
    // TODO: Re-enable GL_RGB565 for OS X when we move to core profile.
#ifdef __APPLE__
    if (colorFormat == GL_RGB565)
        colorFormat = GL_RGB8;
    if (readColorFormat == GL_RGB565)
        readColorFormat = GL_RGB8;
#endif

    if (needReallocateRbo) {
        initEmulatedEGLSurface(width, height, colorFormat, depthstencilFormat, multisamples,
                                *eglSurfaceRBColorId, *eglSurfaceRBDepthId);
        needUpdateDefaultFbo = true;
    }

    if (needReallocateReadRbo) {
        initEmulatedEGLSurface(readWidth, readHeight, readColorFormat, readDepthStencilFormat, readMultisamples,
                                *eglReadSurfaceRBColorId, *eglReadSurfaceRBDepthId);
        needUpdateDefaultFbo = true;
    }

    needUpdateDefaultFbo |=
        m_defaultFboRBColor != *eglSurfaceRBColorId || m_defaultFboRBDepth != *eglSurfaceRBDepthId;
    needUpdateDefaultFbo |=
        separateReadRbo && (m_defaultReadFboRBColor != *eglReadSurfaceRBColorId ||
                            m_defaultReadFboRBDepth != *eglReadSurfaceRBDepthId);

    if (!needUpdateDefaultFbo) {
        return;
    }
    dispatcher().glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFBO);

    dispatcher().glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *eglSurfaceRBColorId);
    dispatcher().glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *eglSurfaceRBDepthId);
    dispatcher().glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, *eglSurfaceRBDepthId);

    m_defaultFboRBColor = *eglSurfaceRBColorId;
    m_defaultFboRBDepth = *eglSurfaceRBDepthId;

    if (m_defaultFBODrawBuffer != GL_COLOR_ATTACHMENT0) {
        dispatcher().glDrawBuffers(1, &m_defaultFBODrawBuffer);
    }
    if (m_defaultFBOReadBuffer != GL_COLOR_ATTACHMENT0) {
        dispatcher().glReadBuffer(m_defaultFBOReadBuffer);
    }

    if (separateReadRbo) {
        dispatcher().glBindFramebuffer(GL_READ_FRAMEBUFFER, m_defaultReadFBO);
        dispatcher().glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *eglReadSurfaceRBColorId);
        dispatcher().glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *eglReadSurfaceRBDepthId);
        dispatcher().glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, *eglReadSurfaceRBDepthId);
        m_defaultReadFboRBColor = *eglReadSurfaceRBColorId;
        m_defaultReadFboRBDepth = *eglReadSurfaceRBDepthId;
    }

    dispatcher().glBindRenderbuffer(GL_RENDERBUFFER, prevRbo);
    GLuint prevDrawFBOBinding = getFramebufferBinding(GL_FRAMEBUFFER);
    GLuint prevReadFBOBinding = getFramebufferBinding(GL_READ_FRAMEBUFFER);

    if (prevDrawFBOBinding)
        dispatcher().glBindFramebuffer(GL_FRAMEBUFFER, getFBOGlobalName(prevDrawFBOBinding));
    if (prevReadFBOBinding)
        dispatcher().glBindFramebuffer(GL_READ_FRAMEBUFFER, getFBOGlobalName(prevReadFBOBinding));

    // We might be initializing a surfaceless context underneath
    // where the viewport is initialized to 0x0 width and height.
    // Set to our wanted pbuffer dimensions if this is the first time
    // the viewport has been set.
    if (!m_isViewport) {
        setViewport(0, 0, width, height);
        dispatcher().glViewport(0, 0, width, height);
    }
    // same for the scissor
    if (!m_isScissor) {
        setScissor(0, 0, width, height);
        dispatcher().glScissor(0, 0, width, height);
    }
}


void GLEScontext::prepareCoreProfileEmulatedTexture(TextureData* texData, bool is3d, GLenum target,
                                                    GLenum format, GLenum type,
                                                    GLint* internalformat_out, GLenum* format_out) {
    if (format != GL_ALPHA &&
        format != GL_LUMINANCE &&
        format != GL_LUMINANCE_ALPHA) {
        return;
    }

    if (isCubeMapFaceTarget(target)) {
        target = is3d ? GL_TEXTURE_CUBE_MAP_ARRAY_EXT : GL_TEXTURE_CUBE_MAP;
    }

    // Set up the swizzle from the underlying supported
    // host format to the emulated format.
    // Make sure to re-apply any user-specified custom swizlz
    TextureSwizzle userSwz; // initialized to identity map

    if (texData) {
        userSwz.toRed = texData->getSwizzle(GL_TEXTURE_SWIZZLE_R);
        userSwz.toGreen = texData->getSwizzle(GL_TEXTURE_SWIZZLE_G);
        userSwz.toBlue = texData->getSwizzle(GL_TEXTURE_SWIZZLE_B);
        userSwz.toAlpha = texData->getSwizzle(GL_TEXTURE_SWIZZLE_A);
    }

    TextureSwizzle swz =
        concatSwizzles(getSwizzleForEmulatedFormat(format),
                       userSwz);

    dispatcher().glTexParameteri(target, GL_TEXTURE_SWIZZLE_R, swz.toRed);
    dispatcher().glTexParameteri(target, GL_TEXTURE_SWIZZLE_G, swz.toGreen);
    dispatcher().glTexParameteri(target, GL_TEXTURE_SWIZZLE_B, swz.toBlue);
    dispatcher().glTexParameteri(target, GL_TEXTURE_SWIZZLE_A, swz.toAlpha);

    // Change the format/internalformat communicated to GL.
    GLenum emulatedFormat =
        getCoreProfileEmulatedFormat(format);
    GLint emulatedInternalFormat =
        getCoreProfileEmulatedInternalFormat(format, type);

    if (format_out) *format_out = emulatedFormat;
    if (internalformat_out) *internalformat_out = emulatedInternalFormat;
}

bool GLEScontext::isFBO(ObjectLocalName p_localName) {
    return m_fboNameSpace->isObject(p_localName);
}

ObjectLocalName GLEScontext::genFBOName(ObjectLocalName p_localName,
        bool genLocal) {
    return m_fboNameSpace->genName(GenNameInfo(NamedObjectType::FRAMEBUFFER),
            p_localName, genLocal);
}

void GLEScontext::setFBOData(ObjectLocalName p_localName, ObjectDataPtr data) {
    m_fboNameSpace->setObjectData(p_localName, data);
}

void GLEScontext::deleteFBO(ObjectLocalName p_localName) {
    m_fboNameSpace->deleteName(p_localName);
}

FramebufferData* GLEScontext::getFBOData(ObjectLocalName p_localName) const {
    return (FramebufferData*)getFBODataPtr(p_localName).get();
}

ObjectDataPtr GLEScontext::getFBODataPtr(ObjectLocalName p_localName) const {
    return m_fboNameSpace->getObjectDataPtr(p_localName);
}

unsigned int GLEScontext::getFBOGlobalName(ObjectLocalName p_localName) const {
    return m_fboNameSpace->getGlobalName(p_localName);
}

ObjectLocalName GLEScontext::getFBOLocalName(unsigned int p_globalName) const {
    return m_fboNameSpace->getLocalName(p_globalName);
}

int GLEScontext::queryCurrFboBits(ObjectLocalName localFboName, GLenum pname) {
    GLint colorInternalFormat = 0;
    GLint depthInternalFormat = 0;
    GLint stencilInternalFormat = 0;
    bool combinedDepthStencil = false;

    if (!localFboName) {
        colorInternalFormat = m_defaultFBOColorFormat;
        // FBO 0 defaulting to d24s8
        depthInternalFormat =
            m_defaultFBODepthFormat ? m_defaultFBODepthFormat : GL_DEPTH24_STENCIL8;
        stencilInternalFormat =
            m_defaultFBOStencilFormat ? m_defaultFBOStencilFormat : GL_DEPTH24_STENCIL8;
    } else {
        FramebufferData* fbData = getFBOData(localFboName);

        std::vector<GLenum> colorAttachments(getCaps()->maxDrawBuffers);
        std::iota(colorAttachments.begin(), colorAttachments.end(), GL_COLOR_ATTACHMENT0);

        bool hasColorAttachment = false;
        for (auto attachment : colorAttachments) {
            GLint internalFormat =
                fbData->getAttachmentInternalFormat(this, attachment);

            // Only defined if all used color attachments are the same
            // internal format.
            if (internalFormat) {
                if (hasColorAttachment &&
                    colorInternalFormat != internalFormat) {
                    colorInternalFormat = 0;
                    break;
                }
                colorInternalFormat = internalFormat;
                hasColorAttachment = true;
            }
        }

        GLint depthStencilFormat =
            fbData->getAttachmentInternalFormat(this, GL_DEPTH_STENCIL_ATTACHMENT);

        if (depthStencilFormat) {
            combinedDepthStencil = true;
            depthInternalFormat = depthStencilFormat;
            stencilInternalFormat = depthStencilFormat;
        }

        if (!combinedDepthStencil) {
            depthInternalFormat =
                fbData->getAttachmentInternalFormat(this, GL_DEPTH_ATTACHMENT);
            stencilInternalFormat =
                fbData->getAttachmentInternalFormat(this, GL_STENCIL_ATTACHMENT);
        }
    }

    FramebufferChannelBits res =
        glFormatToChannelBits(colorInternalFormat,
                              depthInternalFormat,
                              stencilInternalFormat);

    switch (pname) {
    case GL_RED_BITS:
        return res.red;
    case GL_GREEN_BITS:
        return res.green;
    case GL_BLUE_BITS:
        return res.blue;
    case GL_ALPHA_BITS:
        return res.alpha;
    case GL_DEPTH_BITS:
        return res.depth;
    case GL_STENCIL_BITS:
        return res.stencil;
    }

    return 0;
}

static const char kTexImageEmulationVShaderSrc[] = R"(
precision highp float;
out vec2 v_texcoord;
void main() {
    const vec2 quad_pos[6] = vec2[6](
        vec2(0.0, 0.0),
        vec2(0.0, 1.0),
        vec2(1.0, 0.0),
        vec2(0.0, 1.0),
        vec2(1.0, 0.0),
        vec2(1.0, 1.0));

    gl_Position = vec4((quad_pos[gl_VertexID] * 2.0) - 1.0, 0.0, 1.0);
    v_texcoord = quad_pos[gl_VertexID];
})";

static const char kTexImageEmulationVShaderSrcFlipped[] = R"(
precision highp float;
layout (location = 0) in vec2 a_pos;
out vec2 v_texcoord;
void main() {
    gl_Position = vec4((a_pos.xy) * 2.0 - 1.0, 0.0, 1.0);
    v_texcoord = a_pos;
    v_texcoord.y = 1.0 - v_texcoord.y;
})";

static const char kTexImageEmulationFShaderSrc[] = R"(
precision highp float;
uniform sampler2D source_tex;
in vec2 v_texcoord;
out vec4 color;
void main() {
   color = texture(source_tex, v_texcoord);
})";

void GLEScontext::initTexImageEmulation() {
    if (m_textureEmulationProg) return;

    auto& gl = dispatcher();

    std::string vshaderSrc = isCoreProfile() ? "#version 330 core\n" : "#version 300 es\n";
    vshaderSrc += kTexImageEmulationVShaderSrc;
    std::string fshaderSrc = isCoreProfile() ? "#version 330 core\n" : "#version 300 es\n";
    fshaderSrc += kTexImageEmulationFShaderSrc;

    GLuint vshader =
        compileAndValidateCoreShader(GL_VERTEX_SHADER,
                                     vshaderSrc.c_str());
    GLuint fshader =
        compileAndValidateCoreShader(GL_FRAGMENT_SHADER,
                                     fshaderSrc.c_str());
    m_textureEmulationProg = linkAndValidateProgram(vshader, fshader);
    m_textureEmulationSamplerLoc =
        gl.glGetUniformLocation(m_textureEmulationProg, "source_tex");

    gl.glGenFramebuffers(1, &m_textureEmulationFBO);
    gl.glGenTextures(2, m_textureEmulationTextures);
    gl.glGenVertexArrays(1, &m_textureEmulationVAO);
}

void GLEScontext::copyTexImageWithEmulation(
        TextureData* texData,
        bool isSubImage,
        GLenum target,
        GLint level,
        GLenum internalformat,
        GLint xoffset, GLint yoffset,
        GLint x, GLint y,
        GLsizei width, GLsizei height,
        GLint border) {

    // Create objects used for emulation if they don't exist already.
    initTexImageEmulation();
    auto& gl = dispatcher();

    // Save all affected state.
    ScopedGLState state;
    state.pushForCoreProfileTextureEmulation();

    // render to an intermediate texture with the same format:
    // 1. Get the format
    FramebufferData* fbData =
        getFBOData(getFramebufferBinding(GL_READ_FRAMEBUFFER));
    GLint readFbInternalFormat =
        fbData ? fbData->getAttachmentInternalFormat(this, GL_COLOR_ATTACHMENT0) :
                 m_defaultFBOColorFormat;

    // 2. Create the texture for textures[0] with this format, and initialize
    // it to the current FBO read buffer.
    gl.glBindTexture(GL_TEXTURE_2D, m_textureEmulationTextures[0]);
    gl.glCopyTexImage2D(GL_TEXTURE_2D, 0, readFbInternalFormat,
                        x, y, width, height, 0);

    // 3. Set swizzle of textures[0] so they are read in the right way
    // when drawing to textures[1].
    TextureSwizzle swz = getInverseSwizzleForEmulatedFormat(texData->format);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, swz.toRed);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, swz.toGreen);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, swz.toBlue);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, swz.toAlpha);
    // Also, nearest filtering
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // 4. Initialize textures[1] with same width/height, and use it to back
    // the FBO that holds the swizzled results.
    gl.glBindTexture(GL_TEXTURE_2D, m_textureEmulationTextures[1]);
    gl.glTexImage2D(GL_TEXTURE_2D, 0, readFbInternalFormat, width, height, 0,
                    baseFormatOfInternalFormat(readFbInternalFormat),
                    accurateTypeOfInternalFormat(readFbInternalFormat),
                    nullptr);
    // Also, nearest filtering
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    gl.glBindFramebuffer(GL_FRAMEBUFFER, m_textureEmulationFBO);
    gl.glFramebufferTexture2D(
        GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
        m_textureEmulationTextures[1], 0);

    // 5. Draw textures[0] to our FBO, making sure all state is compatible.
    gl.glDisable(GL_BLEND);
    gl.glDisable(GL_SCISSOR_TEST);
    gl.glDisable(GL_DEPTH_TEST);
    gl.glDisable(GL_STENCIL_TEST);
    gl.glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    gl.glDisable(GL_SAMPLE_COVERAGE);
    gl.glDisable(GL_CULL_FACE);
    gl.glDisable(GL_POLYGON_OFFSET_FILL);
    gl.glDisable(GL_RASTERIZER_DISCARD);

    gl.glViewport(0, 0, width, height);

    if (isGles2Gles()) {
        gl.glDepthRangef(0.0f, 1.0f);
    } else {
        gl.glDepthRange(0.0f, 1.0f);
    }

    gl.glColorMask(1, 1, 1, 1);

    gl.glBindTexture(GL_TEXTURE_2D, m_textureEmulationTextures[0]);
    GLint texUnit; gl.glGetIntegerv(GL_ACTIVE_TEXTURE, &texUnit);

    gl.glUseProgram(m_textureEmulationProg);
    gl.glUniform1i(m_textureEmulationSamplerLoc, texUnit - GL_TEXTURE0);

    gl.glBindVertexArray(m_textureEmulationVAO);

    gl.glDrawArrays(GL_TRIANGLES, 0, 6);

    // now the emulated version has been rendered and written to the read FBO
    // with the correct swizzle.
    if (isCubeMapFaceTarget(target)) {
        gl.glBindTexture(GL_TEXTURE_CUBE_MAP, texData->getGlobalName());
    } else {
        gl.glBindTexture(target, texData->getGlobalName());
    }

    if (isSubImage) {
        gl.glCopyTexSubImage2D(target, level, xoffset, yoffset, 0, 0, width, height);
    } else {
        gl.glCopyTexImage2D(target, level, internalformat, 0, 0, width, height, border);
    }
}

// static
GLuint GLEScontext::compileAndValidateCoreShader(GLenum shaderType, const char* src) {
    GLDispatch& gl = dispatcher();

    GLuint shader = gl.glCreateShader(shaderType);
    gl.glShaderSource(shader, 1, (const GLchar* const*)&src, nullptr);
    gl.glCompileShader(shader);

    GLint compileStatus;
    gl.glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);

    if (compileStatus != GL_TRUE) {
        GLsizei infoLogLength = 0;
        gl.glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
        std::vector<char> infoLog(infoLogLength + 1, 0);
        gl.glGetShaderInfoLog(shader, infoLogLength, nullptr, &infoLog[0]);
        ERR("fail to compile. infolog %s", &infoLog[0]);
    }

    return shader;
}

// static
GLuint GLEScontext::linkAndValidateProgram(GLuint vshader, GLuint fshader) {
    GLDispatch& gl = dispatcher();

    GLuint program = gl.glCreateProgram();
    gl.glAttachShader(program, vshader);
    gl.glAttachShader(program, fshader);
    gl.glLinkProgram(program);

    GLint linkStatus;
    gl.glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

    if (linkStatus != GL_TRUE) {
        GLsizei infoLogLength = 0;
        gl.glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
        std::vector<char> infoLog(infoLogLength + 1, 0);
        gl.glGetProgramInfoLog(program, infoLogLength, nullptr, &infoLog[0]);
        ERR("fail to link program. infolog: %s", &infoLog[0]);
    }

    gl.glDeleteShader(vshader);
    gl.glDeleteShader(fshader);

    return program;
}

int GLEScontext::getReadBufferSamples() {
    GLuint readFboBinding = getFramebufferBinding(GL_READ_FRAMEBUFFER);
    bool defaultFboReadBufferBound = readFboBinding == 0;
    if (defaultFboReadBufferBound) {
        return m_defaultFBOSamples;
    } else {
        FramebufferData* fbData = (FramebufferData*)(getFBODataPtr(readFboBinding).get());
        return fbData ? fbData->getAttachmentSamples(this, fbData->getReadBuffer()) : 0;
    }
}

int GLEScontext::getReadBufferInternalFormat() {
    GLuint readFboBinding = getFramebufferBinding(GL_READ_FRAMEBUFFER);
    bool defaultFboReadBufferBound = readFboBinding == 0;
    if (defaultFboReadBufferBound) {
        return m_defaultFBOColorFormat;
    } else {
        FramebufferData* fbData = (FramebufferData*)(getFBODataPtr(readFboBinding).get());
        return fbData ? fbData->getAttachmentInternalFormat(this, fbData->getReadBuffer()) : 0;
    }
}

void GLEScontext::getReadBufferDimensions(GLint* width, GLint* height) {
    GLuint readFboBinding = getFramebufferBinding(GL_READ_FRAMEBUFFER);
    bool defaultFboReadBufferBound = readFboBinding == 0;
    if (defaultFboReadBufferBound) {
        *width = m_defaultFBOWidth;
        *height = m_defaultFBOHeight;
    } else {
        FramebufferData* fbData = (FramebufferData*)(getFBODataPtr(readFboBinding).get());
        if (fbData) {
            fbData->getAttachmentDimensions(
                this, fbData->getReadBuffer(), width, height);
        }
    }
}

void GLEScontext::setupImageBlitState() {
    auto& gl = dispatcher();
    m_blitState.prevSamples = m_blitState.samples;
    m_blitState.samples = getReadBufferSamples();

    if (m_blitState.program) return;

    std::string vshaderSrc =
        isCoreProfile() ? "#version 330 core\n" : "#version 300 es\n";
    vshaderSrc += kTexImageEmulationVShaderSrcFlipped;

    std::string fshaderSrc =
        isCoreProfile() ? "#version 330 core\n" : "#version 300 es\n";
    fshaderSrc += kTexImageEmulationFShaderSrc;

    GLuint vshader =
        compileAndValidateCoreShader(GL_VERTEX_SHADER, vshaderSrc.c_str());
    GLuint fshader =
        compileAndValidateCoreShader(GL_FRAGMENT_SHADER, fshaderSrc.c_str());

    m_blitState.program = linkAndValidateProgram(vshader, fshader);
    m_blitState.samplerLoc =
        gl.glGetUniformLocation(m_blitState.program, "source_tex");

    gl.glGenFramebuffers(1, &m_blitState.fbo);
    gl.glGenFramebuffers(1, &m_blitState.resolveFbo);
    gl.glGenTextures(1, &m_blitState.tex);
    gl.glGenVertexArrays(1, &m_blitState.vao);

    gl.glGenBuffers(1, &m_blitState.vbo);
    float blitVbo[] = {
        0.0f, 0.0f,
        1.0f, 0.0f,
        0.0f, 1.0f,
        1.0f, 0.0f,
        1.0f, 1.0f,
        0.0f, 1.0f,
    };

    GLint buf;
    gl.glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &buf);

    gl.glBindBuffer(GL_ARRAY_BUFFER, m_blitState.vbo);
    gl.glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), blitVbo, GL_STATIC_DRAW);

    gl.glBindVertexArray(m_blitState.vao);
    gl.glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
    gl.glEnableVertexAttribArray(0);

    gl.glBindBuffer(GL_ARRAY_BUFFER, buf);
}

bool GLEScontext::setupImageBlitForTexture(uint32_t width,
                                           uint32_t height,
                                           GLint internalFormat) {
    GLint sizedInternalFormat = GL_RGBA8;
    if (internalFormat != GL_RGBA8 &&
        internalFormat != GL_RGB8 &&
        internalFormat != GL_RGB565) {
        switch (internalFormat) {
        case GL_RGB:
            sizedInternalFormat = GL_RGB8;
            break;
        case GL_RGBA:
            sizedInternalFormat = GL_RGBA8;
            break;
        default:
            break;
        }
    }

    auto& gl = dispatcher();
    // In eglSwapBuffers, the surface must be bound as the draw surface of
    // the current context, which corresponds to m_defaultFBO here.
    //
    // EGL 1.4 spec:
    //
    // 3.9.3 Posting Semantics surface must be bound to the draw surface of the
    // calling thread’s current context, for the current rendering API. This
    // restriction may be lifted in future EGL revisions.
    // copy the draw buffer to a texture.

    GLint read_iformat = m_defaultFBOColorFormat;
    GLint read_format = baseFormatOfInternalFormat(read_iformat);

    if (isIntegerInternalFormat(read_iformat) ||
        read_iformat == GL_RGB10_A2) {
        // Is not a blittable format. Just create the texture for now to
        // make image blit state consistent.
        gl.glTexImage2D(GL_TEXTURE_2D, 0, sizedInternalFormat, width, height, 0,
                baseFormatOfInternalFormat(internalFormat), GL_UNSIGNED_BYTE, 0);
        return false;
    }

    if (width != m_blitState.width || height != m_blitState.height ||
        internalFormat != m_blitState.internalFormat ||
        m_blitState.samples != m_blitState.prevSamples) {

        m_blitState.width = width;
        m_blitState.height = height;
        m_blitState.internalFormat = internalFormat;

        gl.glTexImage2D(GL_TEXTURE_2D, 0,
                        read_iformat, width, height, 0, read_format, GL_UNSIGNED_BYTE, 0);
        if (m_blitState.samples > 0) {
            gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_blitState.resolveFbo);
            gl.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                    GL_TEXTURE_2D, m_blitState.tex, 0);
        }

        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }

    if (m_blitState.samples > 0) {
        // Resolve MSAA
        GLint rWidth = m_defaultFBOWidth;
        GLint rHeight = m_defaultFBOHeight;
        gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, m_defaultFBO);
        gl.glBindTexture(GL_TEXTURE_2D, 0);
        gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_blitState.resolveFbo);
        gl.glBlitFramebuffer(0, 0, rWidth, rHeight, 0, 0, rWidth, rHeight,
                GL_COLOR_BUFFER_BIT, GL_NEAREST);
        gl.glBindTexture(GL_TEXTURE_2D, m_blitState.tex);
    } else {
        gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, m_defaultFBO);
        gl.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height);
    }
    return true;
}

void GLEScontext::blitFromReadBufferToTextureFlipped(GLuint globalTexObj,
                                                     GLuint width,
                                                     GLuint height,
                                                     GLint internalFormat,
                                                     GLenum format,
                                                     GLenum type) {
    // TODO: these might also matter
    (void)format;
    (void)type;

    auto& gl = dispatcher();
    GLint prevViewport[4];
    getViewport(prevViewport);

    setupImageBlitState();

    GLint prevTex2D = 0;
    gl.glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex2D);

    gl.glBindTexture(GL_TEXTURE_2D, m_blitState.tex);

    bool shouldBlit = setupImageBlitForTexture(width, height, internalFormat);

    if (!shouldBlit) {
        gl.glBindTexture(GL_TEXTURE_2D, prevTex2D);
        return;
    }



    // b/159670873: The texture to blit doesn't necessarily match the display
    // size. If it doesn't match, then we might not be using the right mipmap
    // level, which can result in a black screen. Set to always use level 0.
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_blitState.fbo);
    gl.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_TEXTURE_2D, globalTexObj, 0);

    gl.glDisable(GL_SCISSOR_TEST);
    gl.glDisable(GL_DEPTH_TEST);
    gl.glDisable(GL_STENCIL_TEST);
    gl.glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    gl.glDisable(GL_SAMPLE_COVERAGE);
    gl.glDisable(GL_CULL_FACE);
    gl.glDisable(GL_POLYGON_OFFSET_FILL);
    gl.glDisable(GL_RASTERIZER_DISCARD);

    gl.glViewport(0, 0, width, height);
    if (isGles2Gles()) {
        gl.glDepthRangef(0.0f, 1.0f);
    } else {
        gl.glDepthRange(0.0f, 1.0f);
    }

    if (getCaps()->ext_GL_EXT_draw_buffers_indexed) {
        gl.glDisableiEXT(GL_BLEND, 0);
        gl.glColorMaskiEXT(0, 1, 1, 1, 1);
    } else {
        gl.glDisable(GL_BLEND);
        gl.glColorMask(1, 1, 1, 1);
    }

    gl.glUseProgram(m_blitState.program);
    gl.glUniform1i(m_blitState.samplerLoc, m_activeTexture);

    gl.glBindVertexArray(m_blitState.vao);
    gl.glDrawArrays(GL_TRIANGLES, 0, 6);

    // state restore
    const GLuint globalProgramName = shareGroup()->getGlobalName(
        NamedObjectType::SHADER_OR_PROGRAM, m_useProgram);
    gl.glUseProgram(globalProgramName);

    gl.glBindVertexArray(getVAOGlobalName(m_currVaoState.vaoId()));

    gl.glBindTexture(
        GL_TEXTURE_2D,
        shareGroup()->getGlobalName(
            NamedObjectType::TEXTURE,
            getTextureLocalName(GL_TEXTURE_2D,
                                getBindedTexture(GL_TEXTURE_2D))));

    GLuint drawFboBinding = getFramebufferBinding(GL_DRAW_FRAMEBUFFER);
    GLuint readFboBinding = getFramebufferBinding(GL_READ_FRAMEBUFFER);

    gl.glBindFramebuffer(
        GL_DRAW_FRAMEBUFFER,
        drawFboBinding ? getFBOGlobalName(drawFboBinding) : m_defaultFBO);
    gl.glBindFramebuffer(
        GL_READ_FRAMEBUFFER,
        readFboBinding ? getFBOGlobalName(readFboBinding) : m_defaultReadFBO);

    if (isEnabled(GL_SCISSOR_TEST)) gl.glEnable(GL_SCISSOR_TEST);
    if (isEnabled(GL_DEPTH_TEST)) gl.glEnable(GL_DEPTH_TEST);
    if (isEnabled(GL_STENCIL_TEST)) gl.glEnable(GL_STENCIL_TEST);
    if (isEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE)) gl.glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    if (isEnabled(GL_SAMPLE_COVERAGE)) gl.glEnable(GL_SAMPLE_COVERAGE);
    if (isEnabled(GL_CULL_FACE)) gl.glEnable(GL_CULL_FACE);
    if (isEnabled(GL_POLYGON_OFFSET_FILL)) gl.glEnable(GL_POLYGON_OFFSET_FILL);
    if (isEnabled(GL_RASTERIZER_DISCARD)) gl.glEnable(GL_RASTERIZER_DISCARD);

    gl.glViewport(prevViewport[0], prevViewport[1],
                  prevViewport[2], prevViewport[3]);

    if (isGles2Gles()) {
        gl.glDepthRangef(m_zNear, m_zFar);
    } else {
        gl.glDepthRange(m_zNear, m_zFar);
    }

    if (getCaps()->ext_GL_EXT_draw_buffers_indexed) {
        if (isEnabled(GL_BLEND)) gl.glEnableiEXT(GL_BLEND, 0);
        gl.glColorMaskiEXT(0, m_blendStates[0].colorMaskR, m_blendStates[0].colorMaskG,
                       m_blendStates[0].colorMaskB, m_blendStates[0].colorMaskA);
    } else {
        if (isEnabled(GL_BLEND)) gl.glEnable(GL_BLEND);
        gl.glColorMask(m_blendStates[0].colorMaskR, m_blendStates[0].colorMaskG,
                       m_blendStates[0].colorMaskB, m_blendStates[0].colorMaskA);
    }

    gl.glFlush();
}

void GLEScontext::blitFromReadBufferToEGLImage(EGLImage image, GLint internalFormat, int width, int height) {

    auto& gl = dispatcher();
    GLint prevViewport[4];
    getViewport(prevViewport);

    setupImageBlitState();

    GLint prevTex2D = 0;
    gl.glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex2D);

    gl.glBindTexture(GL_TEXTURE_2D, m_blitState.tex);

    bool shouldBlit = setupImageBlitForTexture(width, height, internalFormat);

    if (!shouldBlit) {
        gl.glBindTexture(GL_TEXTURE_2D, prevTex2D);
        return;
    }

    // b/159670873: The texture to blit doesn't necessarily match the display
    // size. If it doesn't match, then we might not be using the right mipmap
    // level, which can result in a black screen. Set to always use level 0.
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    if (!m_blitState.eglImageTex) {
        gl.glGenTextures(1, &m_blitState.eglImageTex);
    }

    gl.glBindTexture(GL_TEXTURE_2D, m_blitState.eglImageTex);
    gl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
    gl.glBindTexture(GL_TEXTURE_2D, m_blitState.tex);
    gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_blitState.fbo);
    gl.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_TEXTURE_2D, m_blitState.eglImageTex, 0);

    gl.glDisable(GL_SCISSOR_TEST);
    gl.glDisable(GL_DEPTH_TEST);
    gl.glDisable(GL_STENCIL_TEST);
    gl.glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    gl.glDisable(GL_SAMPLE_COVERAGE);
    gl.glDisable(GL_CULL_FACE);
    gl.glDisable(GL_POLYGON_OFFSET_FILL);
    gl.glDisable(GL_RASTERIZER_DISCARD);

    gl.glViewport(0, 0, width, height);
    if (isGles2Gles()) {
        gl.glDepthRangef(0.0f, 1.0f);
    } else {
        gl.glDepthRange(0.0f, 1.0f);
    }
    if (getCaps()->ext_GL_EXT_draw_buffers_indexed) {
        gl.glDisableiEXT(GL_BLEND, 0);
        gl.glColorMaskiEXT(0, 1, 1, 1, 1);
    } else {
        gl.glDisable(GL_BLEND);
        gl.glColorMask(1, 1, 1, 1);
    }

    gl.glUseProgram(m_blitState.program);
    gl.glUniform1i(m_blitState.samplerLoc, m_activeTexture);

    gl.glBindVertexArray(m_blitState.vao);
    gl.glDrawArrays(GL_TRIANGLES, 0, 6);

    // state restore
    const GLuint globalProgramName = shareGroup()->getGlobalName(
        NamedObjectType::SHADER_OR_PROGRAM, m_useProgram);
    gl.glUseProgram(globalProgramName);

    gl.glBindVertexArray(getVAOGlobalName(m_currVaoState.vaoId()));

    gl.glBindTexture(
        GL_TEXTURE_2D,
        shareGroup()->getGlobalName(
            NamedObjectType::TEXTURE,
            getTextureLocalName(GL_TEXTURE_2D,
                                getBindedTexture(GL_TEXTURE_2D))));

    GLuint drawFboBinding = getFramebufferBinding(GL_DRAW_FRAMEBUFFER);
    GLuint readFboBinding = getFramebufferBinding(GL_READ_FRAMEBUFFER);

    gl.glBindFramebuffer(
        GL_DRAW_FRAMEBUFFER,
        drawFboBinding ? getFBOGlobalName(drawFboBinding) : m_defaultFBO);
    gl.glBindFramebuffer(
        GL_READ_FRAMEBUFFER,
        readFboBinding ? getFBOGlobalName(readFboBinding) : m_defaultReadFBO);

    if (isEnabled(GL_SCISSOR_TEST)) gl.glEnable(GL_SCISSOR_TEST);
    if (isEnabled(GL_DEPTH_TEST)) gl.glEnable(GL_DEPTH_TEST);
    if (isEnabled(GL_STENCIL_TEST)) gl.glEnable(GL_STENCIL_TEST);
    if (isEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE)) gl.glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    if (isEnabled(GL_SAMPLE_COVERAGE)) gl.glEnable(GL_SAMPLE_COVERAGE);
    if (isEnabled(GL_CULL_FACE)) gl.glEnable(GL_CULL_FACE);
    if (isEnabled(GL_POLYGON_OFFSET_FILL)) gl.glEnable(GL_POLYGON_OFFSET_FILL);
    if (isEnabled(GL_RASTERIZER_DISCARD)) gl.glEnable(GL_RASTERIZER_DISCARD);

    gl.glViewport(prevViewport[0], prevViewport[1],
                  prevViewport[2], prevViewport[3]);

    if (isGles2Gles()) {
        gl.glDepthRangef(m_zNear, m_zFar);
    } else {
        gl.glDepthRange(m_zNear, m_zFar);
    }

    if (getCaps()->ext_GL_EXT_draw_buffers_indexed) {
        if (isEnabled(GL_BLEND)) gl.glEnableiEXT(GL_BLEND, 0);
        gl.glColorMaskiEXT(0, m_blendStates[0].colorMaskR, m_blendStates[0].colorMaskG,
                           m_blendStates[0].colorMaskB, m_blendStates[0].colorMaskA);
    } else {
        if (isEnabled(GL_BLEND)) gl.glEnable(GL_BLEND);
        gl.glColorMask(m_blendStates[0].colorMaskR, m_blendStates[0].colorMaskG,
                       m_blendStates[0].colorMaskB, m_blendStates[0].colorMaskA);
    }

    gl.glFlush();
}

// Primitive restart emulation
#define GL_PRIMITIVE_RESTART              0x8F9D
#define GL_PRIMITIVE_RESTART_INDEX        0x8F9E

void GLEScontext::setPrimitiveRestartEnabled(bool enabled) {
    auto& gl = dispatcher();

    if (enabled) {
        gl.glEnable(GL_PRIMITIVE_RESTART);
    } else {
        gl.glDisable(GL_PRIMITIVE_RESTART);
    }

    m_primitiveRestartEnabled = enabled;
}

void GLEScontext::updatePrimitiveRestartIndex(GLenum type) {
    auto& gl = dispatcher();
    switch (type) {
    case GL_UNSIGNED_BYTE:
        gl.glPrimitiveRestartIndex(0xff);
        break;
    case GL_UNSIGNED_SHORT:
        gl.glPrimitiveRestartIndex(0xffff);
        break;
    case GL_UNSIGNED_INT:
        gl.glPrimitiveRestartIndex(0xffffffff);
        break;
    }
}

bool GLEScontext::isVAO(ObjectLocalName p_localName) {
    VAOStateMap::iterator it = m_vaoStateMap.find(p_localName);
    if (it == m_vaoStateMap.end()) return false;
    VAOStateRef vao(it);
    return vao.isEverBound();
}

ObjectLocalName GLEScontext::genVAOName(ObjectLocalName p_localName,
        bool genLocal) {
    return m_vaoNameSpace->genName(GenNameInfo(NamedObjectType::VERTEX_ARRAY_OBJECT),
            p_localName, genLocal);
}

void GLEScontext::deleteVAO(ObjectLocalName p_localName) {
    m_vaoNameSpace->deleteName(p_localName);
}

unsigned int GLEScontext::getVAOGlobalName(ObjectLocalName p_localName) {
    return m_vaoNameSpace->getGlobalName(p_localName);
}

ObjectLocalName GLEScontext::getVAOLocalName(unsigned int p_globalName) {
    return m_vaoNameSpace->getLocalName(p_globalName);
}

void GLEScontext::setDefaultFBODrawBuffer(GLenum buffer) {
    m_defaultFBODrawBuffer = buffer;
}

void GLEScontext::setDefaultFBOReadBuffer(GLenum buffer) {
    m_defaultFBOReadBuffer = buffer;
}
