/*
* Copyright (C) 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 "GLESv2Context.h"

#include "ProgramData.h"
#include "SamplerData.h"
#include "ShaderParser.h"
#include "TransformFeedbackData.h"
#include "aemu/base/synchronization/Lock.h"
#include "aemu/base/files/StreamSerializing.h"

#include "host-common/crash_reporter.h"

#include <string.h>

static const char kGLES20StringPart[] = "OpenGL ES 2.0";
static const char kGLES30StringPart[] = "OpenGL ES 3.0";
static const char kGLES31StringPart[] = "OpenGL ES 3.1";
static const char kGLES32StringPart[] = "OpenGL ES 3.2";

static GLESVersion s_maxGlesVersion = GLES_2_0;

static const char* sPickVersionStringPart(int maj, int min) {
    switch (maj) {
        case 2:
            return kGLES20StringPart;
        case 3:
            switch (min) {
                case 0:
                    return kGLES30StringPart;
                case 1:
                    return kGLES31StringPart;
                case 2:
                    return kGLES32StringPart;
                default:
                    return nullptr;
            }
        default:
            return nullptr;
    }
    return nullptr;
}

void GLESv2Context::setMaxGlesVersion(GLESVersion version) {
    s_maxGlesVersion = version;
}

void GLESv2Context::initGlobal(EGLiface* iface) {
    s_glDispatch.dispatchFuncs(s_maxGlesVersion, iface->eglGetGlLibrary(), iface->getProcAddress);
    GLEScontext::initGlobal(iface);
}

void GLESv2Context::init() {
    android::base::AutoLock mutex(s_lock);
    if(!m_initialized) {
        GLEScontext::init();
        addVertexArrayObject(0);
        setVertexArrayObject(0);
        setAttribute0value(0.0, 0.0, 0.0, 1.0);

        buildStrings(m_glesMajorVersion,
                     m_glesMinorVersion,
                     (const char*)dispatcher().glGetString(GL_VENDOR),
                     (const char*)dispatcher().glGetString(GL_RENDERER),
                     (const char*)dispatcher().glGetString(GL_VERSION),
                     sPickVersionStringPart(m_glesMajorVersion, m_glesMinorVersion));
        if (m_glesMajorVersion > 2 && !isGles2Gles()) {
            // OpenGL ES assumes that colors computed by / given to shaders will be converted to / from SRGB automatically
            // by the underlying implementation.
            // Desktop OpenGL makes no such assumption, and requires glEnable(GL_FRAMEBUFFER_SRGB) for the automatic conversion
            // to work.
            // This should work in most cases: just glEnable(GL_FRAMEBUFFER_SRGB) for every context.
            // But, that's not the whole story:
            // TODO: For dEQP tests standalone, we can just glEnable GL_FRAMEBUFFER_SRGB from the beginning and
            // pass all the framebuffer blit tests. However with CTS dEQP, EGL gets failures in color clear
            // and some dEQP-GLES3 framebuffer blit tests fail.
            // So we need to start out each context with GL_FRAMEBUFFER_SRGB disabled, and then enable it depending on
            // whether or not the current draw or read framebuffer has a SRGB texture color attachment.
            dispatcher().glDisable(GL_FRAMEBUFFER_SRGB);
            // Desktop OpenGL allows one to make cube maps seamless _or not_, but
            // OpenGL ES assumes seamless cubemaps are activated 100% of the time.
            // Many dEQP cube map tests fail without this enable.
            dispatcher().glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
        }

        initEmulatedVAO();
        initEmulatedBuffers();
        // init emulated transform feedback
        if (m_glesMajorVersion >= 3) {
            m_transformFeedbackNameSpace->genName(
                    GenNameInfo(NamedObjectType::TRANSFORM_FEEDBACK), 0, false);
            TransformFeedbackData* tf = new TransformFeedbackData();
            tf->setMaxSize(getCaps()->maxTransformFeedbackSeparateAttribs);
            m_transformFeedbackNameSpace->setObjectData(0, ObjectDataPtr(tf));
        }
    }
    m_initialized = true;
}

void GLESv2Context::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) {
    GLEScontext::initDefaultFBO(
            width, height, colorFormat, depthstencilFormat, multisamples,
            eglSurfaceRBColorId, eglSurfaceRBDepthId,
            readWidth, readHeight, readColorFormat, readDepthstencilFormat, readMultisamples,
            eglReadSurfaceRBColorId, eglReadSurfaceRBDepthId
            );
}


void GLESv2Context::initEmulatedVAO() {
    if (!isCoreProfile()) return;

    // Create emulated default VAO
    genVAOName(0, false);
    dispatcher().glBindVertexArray(getVAOGlobalName(0));
}

void GLESv2Context::initEmulatedBuffers() {
    if (m_emulatedClientVBOs.empty()) {
        // Create emulated client VBOs
        GLint neededClientVBOs = 0;
        dispatcher().glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &neededClientVBOs);

        // Spec minimum: 16 attribs. Some drivers won't report the right values.
        neededClientVBOs = std::max(neededClientVBOs, 16);

        m_emulatedClientVBOs.resize(neededClientVBOs, 0);
        dispatcher().glGenBuffers(neededClientVBOs, &m_emulatedClientVBOs[0]);
    }

    if (!m_emulatedClientIBO) {
        // Create emulated IBO
        dispatcher().glGenBuffers(1, &m_emulatedClientIBO);
    }
}

GLESv2Context::GLESv2Context(int maj, int min, GlobalNameSpace* globalNameSpace,
        android::base::Stream* stream, GlLibrary* glLib)
        : GLEScontext(globalNameSpace, stream, glLib) {
    if (stream) {
        assert(maj == m_glesMajorVersion);
        assert(min == m_glesMinorVersion);
        stream->read(m_attribute0value, sizeof(m_attribute0value));
        m_attribute0valueChanged = stream->getByte();
        m_att0ArrayLength = stream->getBe32();
        if (m_att0ArrayLength != 0) {
            m_att0Array.reset(new GLfloat[4 * m_att0ArrayLength]);
            stream->read(m_att0Array.get(), sizeof(GLfloat) * 4 * m_att0ArrayLength);
        }
        m_att0NeedsDisable = stream->getByte();
        m_useProgram = stream->getBe32();
        android::base::loadCollection(stream, &m_bindSampler,
                [](android::base::Stream* stream) {
                    GLuint idx = stream->getBe32();
                    GLuint val = stream->getBe32();
                    return std::make_pair(idx, val);
                });
    } else {
        m_glesMajorVersion = maj;
        m_glesMinorVersion = min;
    }
    ObjectData::loadObject_t loader = [this](NamedObjectType type,
                                             long long unsigned int localName,
                                             android::base::Stream* stream) {
        return loadObject(type, localName, stream);
    };
    m_transformFeedbackNameSpace =
            new NameSpace(NamedObjectType::TRANSFORM_FEEDBACK, globalNameSpace,
                          stream, loader);
}

GLESv2Context::~GLESv2Context() {
    if (m_emulatedClientIBO) {
        s_glDispatch.glDeleteBuffers(1, &m_emulatedClientIBO);
    }

    if (!m_emulatedClientVBOs.empty()) {
        s_glDispatch.glDeleteBuffers(
            m_emulatedClientVBOs.size(),
            &m_emulatedClientVBOs[0]);
    }

    deleteVAO(0);
    delete m_transformFeedbackNameSpace;
}

void GLESv2Context::onSave(android::base::Stream* stream) const {
    GLEScontext::onSave(stream);
    stream->write(m_attribute0value, sizeof(m_attribute0value));
    stream->putByte(m_attribute0valueChanged);
    stream->putBe32(m_att0ArrayLength);
    stream->write(m_att0Array.get(), sizeof(GLfloat) * 4 * m_att0ArrayLength);
    stream->putByte(m_att0NeedsDisable);
    stream->putBe32(m_useProgram);
    android::base::saveCollection(stream, m_bindSampler,
            [](android::base::Stream* stream,
                const std::pair<const GLenum, GLuint>& item) {
                stream->putBe32(item.first);
                stream->putBe32(item.second);
            });
    m_transformFeedbackNameSpace->onSave(stream);
}

void GLESv2Context::addVertexArrayObject(GLuint array) {
    m_vaoStateMap[array] = VAOState(0, nullptr, kMaxVertexAttributes);
}

void GLESv2Context::enableArr(GLenum arrType, bool enable) {
    uint32_t index = (uint32_t)arrType;
    if (index > kMaxVertexAttributes) return;
    m_currVaoState.attribInfo()[index].enable(enable);
}

const GLESpointer* GLESv2Context::getPointer(GLenum arrType) {
    uint32_t index = (uint32_t)arrType;
    if (index > kMaxVertexAttributes) return nullptr;
    return m_currVaoState.attribInfo().data() + index;
}

void GLESv2Context::postLoadRestoreCtx() {
    initExtensionString();
    GLDispatch& dispatcher = GLEScontext::dispatcher();
    m_useProgramData = shareGroup()->getObjectDataPtr(
            NamedObjectType::SHADER_OR_PROGRAM, m_useProgram);
    const GLuint globalProgramName = shareGroup()->getGlobalName(
            NamedObjectType::SHADER_OR_PROGRAM, m_useProgram);
    dispatcher.glUseProgram(globalProgramName);

    initEmulatedBuffers();
    initEmulatedVAO();

    // vertex attribute pointers
    for (const auto& vaoIte : m_vaoStateMap) {
        if (vaoIte.first != 0) {
            genVAOName(vaoIte.first, false);
        }
        dispatcher.glBindVertexArray(getVAOGlobalName(vaoIte.first));
        for (uint32_t i = 0; i < kMaxVertexAttributes; ++i) {
            GLESpointer* glesPointer =
                (GLESpointer*)(vaoIte.second.vertexAttribInfo.data() + i);

            // don't skip enabling if the guest assumes it was enabled.
            if (glesPointer->isEnable()) {
                dispatcher.glEnableVertexAttribArray(i);
            }

            // attribute 0 are bound right before draw, no need to bind it here
            if (glesPointer->getAttribType() == GLESpointer::VALUE
                    && i == 0) {
                continue;
            }
            switch (glesPointer->getAttribType()) {
                case GLESpointer::BUFFER: {
                    const GLuint globalBufferName = shareGroup()
                            ->getGlobalName(NamedObjectType::VERTEXBUFFER,
                                            glesPointer->getBufferName());
                    if (!globalBufferName) {
                        continue;
                    }
                    glesPointer->restoreBufferObj(getBufferObj);
                    dispatcher.glBindBuffer(GL_ARRAY_BUFFER,
                            globalBufferName);
                    if (glesPointer->isIntPointer()) {
                        dispatcher.glVertexAttribIPointer(i,
                                glesPointer->getSize(),
                                glesPointer->getType(),
                                glesPointer->getStride(),
                                (GLvoid*)(size_t)glesPointer->getBufferOffset());
                    } else {
                        dispatcher.glVertexAttribPointer(i,
                                glesPointer->getSize(),
                                glesPointer->getType(), glesPointer->isNormalize(),
                                glesPointer->getStride(),
                                (GLvoid*)(size_t)glesPointer->getBufferOffset());
                    }
                    break;
                }
                case GLESpointer::VALUE:
                    switch (glesPointer->getValueCount()) {
                        case 1:
                            dispatcher.glVertexAttrib1fv(i,
                                    glesPointer->getValues());
                            break;
                        case 2:
                            dispatcher.glVertexAttrib2fv(i,
                                    glesPointer->getValues());
                            break;
                        case 3:
                            dispatcher.glVertexAttrib3fv(i,
                                    glesPointer->getValues());
                            break;
                        case 4:
                            dispatcher.glVertexAttrib4fv(i,
                                    glesPointer->getValues());
                            break;
                    }
                    break;
                case GLESpointer::ARRAY:
                    // client arrays are set up right before draw calls
                    // so we do nothing here
                    break;
            }
        }
        for (size_t i = 0; i < vaoIte.second.bindingState.size(); i++) {
            const BufferBinding& bufferBinding = vaoIte.second.bindingState[i];
            if (bufferBinding.divisor) {
                dispatcher.glVertexAttribDivisor(i, bufferBinding.divisor);
            }
        }
    }
    dispatcher.glBindVertexArray(getVAOGlobalName(m_currVaoState.vaoId()));
    if (m_glesMajorVersion >= 3) {
        auto bindBufferRangeFunc =
                [this](GLenum target,
                    const std::vector<BufferBinding>& bufferBindings) {
                    for (unsigned int i = 0; i < bufferBindings.size(); i++) {
                        const BufferBinding& bd = bufferBindings[i];
                        GLuint globalName = this->shareGroup()->getGlobalName(
                                NamedObjectType::VERTEXBUFFER,
                                bd.buffer);
                        assert(bd.buffer == 0 || globalName != 0);
                        if (bd.isBindBase || bd.buffer == 0) {
                            this->dispatcher().glBindBufferBase(target,
                                    i, globalName);
                        } else {
                            this->dispatcher().glBindBufferRange(target,
                                    i, globalName, bd.offset, bd.size);
                        }
                    }
                };
        bindBufferRangeFunc(GL_TRANSFORM_FEEDBACK_BUFFER,
                m_indexedTransformFeedbackBuffers);
        bindBufferRangeFunc(GL_UNIFORM_BUFFER,
                m_indexedUniformBuffers);

        if (m_glesMinorVersion >= 1) {
            bindBufferRangeFunc(GL_ATOMIC_COUNTER_BUFFER,
                    m_indexedAtomicCounterBuffers);
            bindBufferRangeFunc(GL_SHADER_STORAGE_BUFFER,
                    m_indexedShaderStorageBuffers);
        }

        // buffer bindings
        auto bindBuffer = [this](GLenum target, GLuint buffer) {
            this->dispatcher().glBindBuffer(target,
                    m_shareGroup->getGlobalName(NamedObjectType::VERTEXBUFFER, buffer));
        };
        bindBuffer(GL_COPY_READ_BUFFER, m_copyReadBuffer);
        bindBuffer(GL_COPY_WRITE_BUFFER, m_copyWriteBuffer);
        bindBuffer(GL_PIXEL_PACK_BUFFER, m_pixelPackBuffer);
        bindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pixelUnpackBuffer);
        bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_transformFeedbackBuffer);
        bindBuffer(GL_UNIFORM_BUFFER, m_uniformBuffer);

        if (m_glesMinorVersion >= 1) {
            bindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_atomicCounterBuffer);
            bindBuffer(GL_DISPATCH_INDIRECT_BUFFER, m_dispatchIndirectBuffer);
            bindBuffer(GL_DRAW_INDIRECT_BUFFER, m_drawIndirectBuffer);
            bindBuffer(GL_SHADER_STORAGE_BUFFER, m_shaderStorageBuffer);
            if (getCaps()->textureBufferAny()) {
                bindBuffer(GL_TEXTURE_BUFFER, m_textureBuffer);
            }
        }
        for (const auto& bindSampler : m_bindSampler) {
            dispatcher.glBindSampler(bindSampler.first,
                    shareGroup()->getGlobalName(NamedObjectType::SAMPLER,
                        bindSampler.second));
        }
        m_transformFeedbackNameSpace->postLoadRestore(
                [this](NamedObjectType p_type, ObjectLocalName p_localName) {
                    switch (p_type) {
                        case NamedObjectType::FRAMEBUFFER:
                            return getFBOGlobalName(p_localName);
                        case NamedObjectType::TRANSFORM_FEEDBACK:
                            return getTransformFeedbackGlobalName(p_localName);
                        default:
                            return m_shareGroup->getGlobalName(p_type,
                                                               p_localName);
                    }
                });
        dispatcher.glBindTransformFeedback(
                GL_TRANSFORM_FEEDBACK,
                getTransformFeedbackGlobalName(m_transformFeedbackBuffer));
    }

    GLEScontext::postLoadRestoreCtx();
}

ObjectDataPtr GLESv2Context::loadObject(NamedObjectType type,
            ObjectLocalName localName, android::base::Stream* stream) const {
    switch (type) {
        case NamedObjectType::VERTEXBUFFER:
        case NamedObjectType::TEXTURE:
        case NamedObjectType::FRAMEBUFFER:
        case NamedObjectType::RENDERBUFFER:
            return GLEScontext::loadObject(type, localName, stream);
        case NamedObjectType::SAMPLER:
            return ObjectDataPtr(new SamplerData(stream));
        case NamedObjectType::SHADER_OR_PROGRAM:
            // load the first bit to see if it is a program or shader
            switch (stream->getByte()) {
                case LOAD_PROGRAM:
                    return ObjectDataPtr(new ProgramData(stream));
                case LOAD_SHADER:
                    return ObjectDataPtr(new ShaderParser(stream));
                default:
                    fprintf(stderr, "corrupted snapshot\n");
                    assert(false);
                    return nullptr;
            }
        case NamedObjectType::TRANSFORM_FEEDBACK:
            return ObjectDataPtr(new TransformFeedbackData(stream));
        default:
            return nullptr;
    }
}

void GLESv2Context::setAttribValue(int idx, unsigned int count,
        const GLfloat* val) {
    m_currVaoState.attribInfo()[idx].setValue(count, val);
}

void GLESv2Context::setAttribute0value(float x, float y, float z, float w)
{
    m_attribute0valueChanged |=
            x != m_attribute0value[0] || y != m_attribute0value[1] ||
            z != m_attribute0value[2] || w != m_attribute0value[3];
    m_attribute0value[0] = x;
    m_attribute0value[1] = y;
    m_attribute0value[2] = z;
    m_attribute0value[3] = w;
}

bool GLESv2Context::needAtt0PreDrawValidation()
{
    m_att0NeedsDisable = false;

    // We could go into the driver here and call
    //      s_glDispatch.glGetVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled)
    // ... but it's too much for a simple check that runs on almost every draw
    // call.
    return !isArrEnabled(0);
}

void GLESv2Context::validateAtt0PreDraw(unsigned int count)
{
    if (count == 0) {
        return;
    }

    if (count > m_att0ArrayLength) {
        const unsigned newLen = std::max(count, 2 * m_att0ArrayLength);
        m_att0Array.reset(new GLfloat[4 * newLen]);
        m_att0ArrayLength = newLen;
        m_attribute0valueChanged = true;
    }
    if (m_attribute0valueChanged) {
        for(unsigned int i = 0; i<m_att0ArrayLength; i++) {
            memcpy(m_att0Array.get()+i*4, m_attribute0value,
                    sizeof(m_attribute0value));
        }
        m_attribute0valueChanged = false;
    }

    GLuint prevArrayBuffer;
    s_glDispatch.glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&prevArrayBuffer);

    s_glDispatch.glBindBuffer(GL_ARRAY_BUFFER, m_emulatedClientVBOs[0]);
    s_glDispatch.glBufferData(GL_ARRAY_BUFFER, m_att0ArrayLength * sizeof(GLfloat), m_att0Array.get(), GL_STREAM_DRAW);

    s_glDispatch.glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
    s_glDispatch.glEnableVertexAttribArray(0);

    s_glDispatch.glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer);

    m_att0NeedsDisable = true;
}

void GLESv2Context::validateAtt0PostDraw(void)
{
    if (m_att0NeedsDisable) {
        s_glDispatch.glDisableVertexAttribArray(0);
        m_att0NeedsDisable = false;
    }
}

void GLESv2Context::drawWithEmulations(
    DrawCallCmd cmd,
    GLenum mode,
    GLint first,
    GLsizei count,
    GLenum type,
    const GLvoid* indices,
    GLsizei primcount,
    GLuint start,
    GLuint end) {

    if (getMajorVersion() < 3) {
        drawValidate();
    }

    bool needClientVBOSetup = !vertexAttributesBufferBacked();

    bool needClientIBOSetup =
        (cmd != DrawCallCmd::Arrays &&
         cmd != DrawCallCmd::ArraysInstanced) &&
        !isBindedBuffer(GL_ELEMENT_ARRAY_BUFFER);
    bool needPointEmulation = mode == GL_POINTS && !isGles2Gles();

#ifdef __APPLE__
    if (!isGles2Gles() && primitiveRestartEnabled() && type) {
        updatePrimitiveRestartIndex(type);
    }
#endif

    if (needPointEmulation) {
        s_glDispatch.glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
        if (!isCoreProfile()) {
            // Enable texture generation for GL_POINTS and gl_PointSize shader variable
            // GLES2 assumes this is enabled by default, we need to set this state for GL
            s_glDispatch.glEnable(GL_POINT_SPRITE);
        }
    }

    bool needEnablingPostDraw[kMaxVertexAttributes];
    memset(needEnablingPostDraw, 0, sizeof(needEnablingPostDraw));

    if (needClientVBOSetup) {
        GLESConversionArrays tmpArrs;
        bool needPauseTransformFeedback = boundTransformFeedback()
                && boundTransformFeedback()->mIsActive
                && !boundTransformFeedback()->mIsPaused;
        if (needPauseTransformFeedback) {
            s_glDispatch.glPauseTransformFeedback();
        }
        setupArraysPointers(tmpArrs, 0, count, type, indices, false, needEnablingPostDraw);
        if (needPauseTransformFeedback) {
            s_glDispatch.glResumeTransformFeedback();
        }
        if (needAtt0PreDrawValidation()) {
            if (indices) {
                validateAtt0PreDraw(findMaxIndex(count, type, indices));
            } else {
                validateAtt0PreDraw(count);
            }
        }
    }

    GLuint prevIBO;
    if (needClientIBOSetup) {
        int bpv = 2;
        switch (type) {
            case GL_UNSIGNED_BYTE:
                bpv = 1;
                break;
            case GL_UNSIGNED_SHORT:
                bpv = 2;
                break;
            case GL_UNSIGNED_INT:
                bpv = 4;
                break;
        }

        size_t dataSize = bpv * count;

        s_glDispatch.glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint*)&prevIBO);
        s_glDispatch.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_emulatedClientIBO);
        s_glDispatch.glBufferData(GL_ELEMENT_ARRAY_BUFFER, dataSize, indices, GL_STREAM_DRAW);
    }

    const GLvoid* indicesOrOffset =
        needClientIBOSetup ? nullptr : indices;

    switch (cmd) {
        case DrawCallCmd::Elements:
            s_glDispatch.glDrawElements(mode, count, type, indicesOrOffset);
            break;
        case DrawCallCmd::ElementsInstanced:
            s_glDispatch.glDrawElementsInstanced(mode, count, type,
                                                 indicesOrOffset,
                                                 primcount);
            break;
        case DrawCallCmd::RangeElements:
            s_glDispatch.glDrawRangeElements(mode, start, end, count, type,
                                             indicesOrOffset);
            break;
        case DrawCallCmd::Arrays:
            s_glDispatch.glDrawArrays(mode, first, count);
            break;
        case DrawCallCmd::ArraysInstanced:
            s_glDispatch.glDrawArraysInstanced(mode, first, count, primcount);
            break;
        default:
            emugl::emugl_crash_reporter(
                "drawWithEmulations has corrupt call parameters!");
    }

    if (needClientIBOSetup) {
        s_glDispatch.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, prevIBO);
    }

    if (needClientVBOSetup) {
        validateAtt0PostDraw();
    }

    if (needPointEmulation) {
        s_glDispatch.glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
        if (!isCoreProfile()) {
            s_glDispatch.glDisable(GL_POINT_SPRITE);
        }
    }

    for (int i = 0; i < kMaxVertexAttributes; ++i) {
        if (needEnablingPostDraw[i]) {
            s_glDispatch.glEnableVertexAttribArray(i);
        }
    }
}

void GLESv2Context::setupArraysPointers(GLESConversionArrays& cArrs,GLint first,GLsizei count,GLenum type,const GLvoid* indices,bool direct, bool* needEnablingPostDraw) {
    //going over all clients arrays Pointers
    for (uint32_t i = 0; i < kMaxVertexAttributes; ++i) {
        GLESpointer* p = m_currVaoState.attribInfo().data() + i;
        if (!p->isEnable() || p->getAttribType() == GLESpointer::VALUE) {
            continue;
        }

        setupArrWithDataSize(
            p->getDataSize(),
            p->getArrayData(),
            i,
            p->getType(),
            p->getSize(),
            p->getStride(),
            p->getNormalized(),
            -1,
            p->isIntPointer(),
            p->getBufferName(),
            needEnablingPostDraw);
    }
}

//setting client side arr
void GLESv2Context::setupArrWithDataSize(GLsizei datasize, const GLvoid* arr,
                                         GLenum arrayType, GLenum dataType,
                                         GLint size, GLsizei stride, GLboolean normalized, int index, bool isInt, GLuint ptrBufferName, bool* needEnablingPostDraw){
    // is not really a client side arr.
    if (arr == NULL) {
        GLint isEnabled;
        s_glDispatch.glGetVertexAttribiv((int)arrayType, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &isEnabled);
        if (isEnabled && !ptrBufferName) {
            s_glDispatch.glDisableVertexAttribArray(arrayType);
            if (needEnablingPostDraw)
                needEnablingPostDraw[arrayType] = true;
        }

        return;
    }

    GLuint prevArrayBuffer;
    s_glDispatch.glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&prevArrayBuffer);

    if (arrayType < m_emulatedClientVBOs.size()) {
        s_glDispatch.glBindBuffer(GL_ARRAY_BUFFER, m_emulatedClientVBOs[arrayType]);
    } else {
        fprintf(stderr, "%s: invalid attribute index: %d\n", __func__, (int)arrayType);
    }

    s_glDispatch.glBufferData(GL_ARRAY_BUFFER, datasize, arr, GL_STREAM_DRAW);

    if (isInt) {
        s_glDispatch.glVertexAttribIPointer(arrayType, size, dataType, stride, 0);
    } else {
        s_glDispatch.glVertexAttribPointer(arrayType, size, dataType, normalized, stride, 0);
    }

    s_glDispatch.glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer);
}

void GLESv2Context::setVertexAttribDivisor(GLuint bindingindex, GLuint divisor) {
    if (bindingindex >= m_currVaoState.bufferBindings().size()) {
        return;
    }
    m_currVaoState.bufferBindings()[bindingindex].divisor = divisor;
}

void GLESv2Context::setVertexAttribBindingIndex(GLuint attribindex, GLuint bindingindex) {
    if (attribindex > kMaxVertexAttributes) return;

    m_currVaoState.attribInfo()[attribindex].setBindingIndex(bindingindex);
}

void GLESv2Context::setVertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint reloffset, bool isInt) {
    if (attribindex > kMaxVertexAttributes) return;
    m_currVaoState.attribInfo()[attribindex].setFormat(size, type, normalized == GL_TRUE, reloffset, isInt);
}

void GLESv2Context::setBindSampler(GLuint unit, GLuint sampler) {
    m_bindSampler[unit] = sampler;
}

bool GLESv2Context::needConvert(GLESConversionArrays& cArrs,GLint first,GLsizei count,GLenum type,const GLvoid* indices,bool direct,GLESpointer* p,GLenum array_id) {

    bool usingVBO = p->getAttribType() == GLESpointer::BUFFER;
    GLenum arrType = p->getType();

    /*
     conversion is not necessary in the following cases:
      (*) array type is not fixed
    */
    if(arrType != GL_FIXED) return false;

    if(!usingVBO) {
        if (direct) {
            convertDirect(cArrs,first,count,array_id,p);
        } else {
            convertIndirect(cArrs,count,type,indices,array_id,p);
        }
    } else {
        if (direct) {
            convertDirectVBO(cArrs,first,count,array_id,p);
        } else {
            convertIndirectVBO(cArrs,count,type,indices,array_id,p);
        }
    }
    return true;
}

void GLESv2Context::setUseProgram(GLuint program,
        const ObjectDataPtr& programData) {
    m_useProgram = program;
    assert(!programData ||
            programData->getDataType() == ObjectDataType::PROGRAM_DATA);
    m_useProgramData = programData;
}

GLuint GLESv2Context::getCurrentProgram() const {
    return m_useProgram;
}

ProgramData* GLESv2Context::getUseProgram() {
    return (ProgramData*)m_useProgramData.get();
}


void InitExtensionString(GLSupport& glSupport, std::string& ext) {
    ext =
        "GL_OES_EGL_sync GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_depth24 GL_OES_depth32 "
        "GL_OES_element_index_uint "
        "GL_OES_texture_float GL_OES_texture_float_linear "
        "GL_OES_compressed_paletted_texture GL_OES_compressed_ETC1_RGB8_texture "
        "GL_OES_depth_texture ";
    if (glSupport.GL_ARB_HALF_FLOAT_PIXEL || glSupport.GL_NV_HALF_FLOAT)
        ext += "GL_OES_texture_half_float GL_OES_texture_half_float_linear ";
    if (glSupport.GL_EXT_PACKED_DEPTH_STENCIL) ext += "GL_OES_packed_depth_stencil ";
    if (glSupport.GL_ARB_HALF_FLOAT_VERTEX) ext += "GL_OES_vertex_half_float ";
    if (glSupport.GL_OES_STANDARD_DERIVATIVES) ext += "GL_OES_standard_derivatives ";
    if (glSupport.GL_OES_TEXTURE_NPOT) ext += "GL_OES_texture_npot ";
    if (glSupport.GL_OES_RGB8_RGBA8) ext += "GL_OES_rgb8_rgba8 ";
    if (glSupport.ext_GL_EXT_color_buffer_float) ext += "GL_EXT_color_buffer_float ";
    if (glSupport.ext_GL_EXT_color_buffer_half_float) ext += "GL_EXT_color_buffer_half_float ";
    if (glSupport.ext_GL_EXT_shader_framebuffer_fetch) ext += "GL_EXT_shader_framebuffer_fetch ";
    if (glSupport.GL_EXT_TEXTURE_FORMAT_BGRA8888) {
        ext += "GL_EXT_texture_format_BGRA8888 GL_APPLE_texture_format_BGRA8888 ";
    }
    if (glSupport.ext_GL_EXT_texture_buffer) {
        ext += "GL_EXT_texture_buffer ";
    }
    if (glSupport.ext_GL_OES_texture_buffer) {
        ext += "GL_OES_texture_buffer ";
    }
    if (glSupport.ext_GL_EXT_draw_buffers_indexed) {
        ext += "GL_EXT_draw_buffers_indexed ";
    }
}

void GLESv2Context::initExtensionString() {
    if (m_glesMajorVersion == 3 && m_glesMinorVersion == 1) {
        if (s_glExtensionsGles31Initialized) return;
        initCapsLocked((const GLubyte*)getHostExtensionsString(&s_glDispatch).c_str(), s_glSupportGles31);
        InitExtensionString(s_glSupportGles31, *s_glExtensionsGles31);
        s_glExtensionsGles31Initialized = true;
    } else {
        if (s_glExtensionsInitialized) return;
        initCapsLocked((const GLubyte*)getHostExtensionsString(&s_glDispatch).c_str(),
                       s_glSupport);
        InitExtensionString(s_glSupport, *s_glExtensions);
        s_glExtensionsInitialized = true;
    }
}

int GLESv2Context::getMaxTexUnits() {
    return getCaps()->maxTexImageUnits;
}

int GLESv2Context::getMaxCombinedTexUnits() {
    return getCaps()->maxCombinedTexImageUnits;
}

unsigned int GLESv2Context::getTransformFeedbackGlobalName(
        ObjectLocalName p_localName) {
    return m_transformFeedbackNameSpace->getGlobalName(p_localName);
}

bool GLESv2Context::hasBoundTransformFeedback(
        ObjectLocalName transformFeedback) {
    return transformFeedback &&
           m_transformFeedbackNameSpace->getObjectDataPtr(transformFeedback)
                   .get();
}

ObjectLocalName GLESv2Context::genTransformFeedbackName(
        ObjectLocalName p_localName,
        bool genLocal) {
    return m_transformFeedbackNameSpace->genName(
            GenNameInfo(NamedObjectType::TRANSFORM_FEEDBACK), p_localName,
            genLocal);
}

void GLESv2Context::bindTransformFeedback(ObjectLocalName p_localName) {
    if (m_transformFeedbackDeletePending &&
        m_bindTransformFeedback != p_localName) {
        m_transformFeedbackNameSpace->deleteName(m_bindTransformFeedback);
        m_transformFeedbackDeletePending = false;
    }
    m_bindTransformFeedback = p_localName;
    if (p_localName &&
        !m_transformFeedbackNameSpace->getGlobalName(p_localName)) {
        genTransformFeedbackName(p_localName, false);
    }
    if (p_localName &&
        !m_transformFeedbackNameSpace->getObjectDataPtr(p_localName).get()) {
        TransformFeedbackData* tf = new TransformFeedbackData();
        tf->setMaxSize(getCaps()->maxTransformFeedbackSeparateAttribs);
        m_transformFeedbackNameSpace->setObjectData(p_localName,
                                                    ObjectDataPtr(tf));
    }
}

ObjectLocalName GLESv2Context::getTransformFeedbackBinding() {
    return m_bindTransformFeedback;
}

void GLESv2Context::deleteTransformFeedback(ObjectLocalName p_localName) {
    // Note: GLES3.0 says it should be pending for delete if it is active
    // GLES3.2 says report error in this situation
    if (m_bindTransformFeedback == p_localName) {
        m_transformFeedbackDeletePending = true;
        return;
    }
    m_transformFeedbackNameSpace->deleteName(p_localName);
}

TransformFeedbackData* GLESv2Context::boundTransformFeedback() {
    return (TransformFeedbackData*)m_transformFeedbackNameSpace
            ->getObjectDataPtr(m_bindTransformFeedback)
            .get();
}

GLuint GLESv2Context::getIndexedBuffer(GLenum target, GLuint index) {
    switch (target) {
        case GL_TRANSFORM_FEEDBACK_BUFFER:
            return boundTransformFeedback()->getIndexedBuffer(index);
        default:
            return GLEScontext::getIndexedBuffer(target, index);
    }
}

void GLESv2Context::bindIndexedBuffer(GLenum target,
                                      GLuint index,
                                      GLuint buffer,
                                      GLintptr offset,
                                      GLsizeiptr size,
                                      GLintptr stride,
                                      bool isBindBase) {
    switch (target) {
        case GL_TRANSFORM_FEEDBACK_BUFFER: {
            TransformFeedbackData* tf = boundTransformFeedback();
            tf->bindIndexedBuffer(index, buffer, offset, size, stride,
                                  isBindBase);
            break;
        }
        default:
            GLEScontext::bindIndexedBuffer(target, index, buffer, offset, size,
                                           stride, isBindBase);
    }
}

void GLESv2Context::bindIndexedBuffer(GLenum target,
                                      GLuint index,
                                      GLuint buffer) {
    GLEScontext::bindIndexedBuffer(target, index, buffer);
}

void GLESv2Context::unbindBuffer(GLuint buffer) {
    if (m_glesMajorVersion >= 3) {
        boundTransformFeedback()->unbindBuffer(buffer);
    }
    GLEScontext::unbindBuffer(buffer);
}
