/*
* 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 <GLcommon/TextureUtils.h>
#include <GLcommon/GLESmacros.h>
#include <GLcommon/GLDispatch.h>
#include <GLcommon/GLESvalidate.h>
#include <stdio.h>
#include <cmath>
#include <memory>

#include "aemu/base/AlignedBuf.h"
#include "compressedTextureFormats/AstcCpuDecompressor.h"

using android::AlignedBuf;
using gfxstream::vk::AstcCpuDecompressor;

#define GL_R16 0x822A
#define GL_RG16 0x822C
#define GL_R16_SNORM 0x8F98
#define GL_RG16_SNORM 0x8F99

static constexpr size_t kASTCFormatsCount = 28;

#define ASTC_FORMATS_LIST(EXPAND_MACRO) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_4x4_KHR, 4, 4, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_5x4_KHR, 5, 4, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_5x5_KHR, 5, 5, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_6x5_KHR, 6, 5, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_6x6_KHR, 6, 6, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_8x5_KHR, 8, 5, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_8x6_KHR, 8, 6, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_8x8_KHR, 8, 8, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_10x5_KHR, 10, 5, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_10x6_KHR, 10, 6, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_10x8_KHR, 10, 8, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_10x10_KHR, 10, 10, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_12x10_KHR, 12, 10, false) \
    EXPAND_MACRO(GL_COMPRESSED_RGBA_ASTC_12x12_KHR, 12, 12, false) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, 4, 4, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR, 5, 4, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, 5, 5, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR, 6, 5, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, 6, 6, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, 8, 5, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, 8, 6, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR, 8, 8, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, 10, 5, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR, 10, 6, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR, 10, 8, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR, 10, 10, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, 12, 10, true) \
    EXPAND_MACRO(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR, 12, 12, true) \

int getCompressedFormats(int majorVersion, int* formats) {
    static constexpr size_t kCount = MAX_SUPPORTED_PALETTE + MAX_ETC_SUPPORTED + kASTCFormatsCount;
    int res = kCount;

    if (majorVersion > 1) {
        res -= MAX_SUPPORTED_PALETTE;
    }

    if (formats) {
        size_t i = 0;

        if (1 == majorVersion) {
            // Palette
            formats[i++] = GL_PALETTE4_RGBA8_OES;
            formats[i++] = GL_PALETTE4_RGBA4_OES;
            formats[i++] = GL_PALETTE8_RGBA8_OES;
            formats[i++] = GL_PALETTE8_RGBA4_OES;
            formats[i++] = GL_PALETTE4_RGB8_OES;
            formats[i++] = GL_PALETTE8_RGB8_OES;
            formats[i++] = GL_PALETTE4_RGB5_A1_OES;
            formats[i++] = GL_PALETTE8_RGB5_A1_OES;
            formats[i++] = GL_PALETTE4_R5_G6_B5_OES;
            formats[i++] = GL_PALETTE8_R5_G6_B5_OES;
        }

        // ETC
        formats[i++] = GL_ETC1_RGB8_OES;
        formats[i++] = GL_COMPRESSED_RGB8_ETC2;
        formats[i++] = GL_COMPRESSED_SIGNED_R11_EAC;
        formats[i++] = GL_COMPRESSED_RG11_EAC;
        formats[i++] = GL_COMPRESSED_SIGNED_RG11_EAC;
        formats[i++] = GL_COMPRESSED_RGB8_ETC2;
        formats[i++] = GL_COMPRESSED_SRGB8_ETC2;
        formats[i++] = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
        formats[i++] = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
        formats[i++] = GL_COMPRESSED_RGBA8_ETC2_EAC;
        formats[i++] = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
        formats[i++] = GL_COMPRESSED_R11_EAC;

        // ASTC
#define ASTC_FORMAT(typeName, blockWidth, blockHeight, srgbValue) \
        formats[i++] = typeName;

        ASTC_FORMATS_LIST(ASTC_FORMAT)
#undef ASTC_FORMAT

    }

    return res;
}

ETC2ImageFormat getEtcFormat(GLenum internalformat) {
    ETC2ImageFormat etcFormat = EtcRGB8;
    switch (internalformat) {
        case GL_COMPRESSED_RGB8_ETC2:
        case GL_ETC1_RGB8_OES:
            break;
        case GL_COMPRESSED_RGBA8_ETC2_EAC:
            etcFormat = EtcRGBA8;
            break;
        case GL_COMPRESSED_SRGB8_ETC2:
            break;
        case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
            etcFormat = EtcRGBA8;
            break;
        case GL_COMPRESSED_R11_EAC:
            etcFormat = EtcR11;
            break;
        case GL_COMPRESSED_SIGNED_R11_EAC:
            etcFormat = EtcSignedR11;
            break;
        case GL_COMPRESSED_RG11_EAC:
            etcFormat = EtcRG11;
            break;
        case GL_COMPRESSED_SIGNED_RG11_EAC:
            etcFormat = EtcSignedRG11;
            break;
        case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
            etcFormat = EtcRGB8A1;
            break;
        case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
            etcFormat = EtcRGB8A1;
            break;
    }
    return etcFormat;
}

void getAstcFormatInfo(GLenum internalformat, uint32_t* width, uint32_t* height, bool* srgb) {
    switch (internalformat) {
#define ASTC_FORMAT(typeName, blockWidth, blockHeight, srgbValue) \
        case typeName: \
            *width = blockWidth; *height = blockHeight; *srgb = srgbValue; break; \

        ASTC_FORMATS_LIST(ASTC_FORMAT)
#undef ASTC_FORMAT
        default:
            assert(false && "Invalid ASTC format");
            break;
    }
}

// Helper function to decompress an ASTC image.
bool astcDecompress(const uint8_t* astcData, size_t astcDataSize, uint32_t width, uint32_t height,
                    uint32_t blockWidth, uint32_t blockHeight, uint8_t* outBuffer,
                    size_t outBufferSize) {
    if (outBufferSize < width * height * 4) {
        WARN("ASTC output buffer too small: %d bytes for %d x %d", outBufferSize, width, height);
        return false;
    }
    int32_t status = AstcCpuDecompressor::get().decompress(width, height, blockWidth, blockHeight,
                                                           astcData, astcDataSize, outBuffer);
    if (status != 0) {
        WARN("astc decompression failed: %s", AstcCpuDecompressor::get().getStatusString(status));
        return false;
    }
    return true;
}

bool isAstcFormat(GLenum internalformat) {
    switch (internalformat) {
#define ASTC_FORMAT(typeName, blockWidth, blockHeight, srgbValue) \
        case typeName:

        ASTC_FORMATS_LIST(ASTC_FORMAT)
#undef ASTC_FORMAT
            return true;
        default:
            return false;
    }
}

bool isEtcFormat(GLenum internalformat) {
    switch (internalformat) {
    case GL_ETC1_RGB8_OES:
    case GL_COMPRESSED_RGB8_ETC2:
    case GL_COMPRESSED_SRGB8_ETC2:
    case GL_COMPRESSED_RGBA8_ETC2_EAC:
    case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
    case GL_COMPRESSED_R11_EAC:
    case GL_COMPRESSED_SIGNED_R11_EAC:
    case GL_COMPRESSED_RG11_EAC:
    case GL_COMPRESSED_SIGNED_RG11_EAC:
    case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
    case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
        return true;
    }
    return false;
}

bool isEtc2Format(GLenum internalformat) {
    switch (internalformat) {
    case GL_COMPRESSED_RGB8_ETC2:
    case GL_COMPRESSED_SRGB8_ETC2:
    case GL_COMPRESSED_RGBA8_ETC2_EAC:
    case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
    case GL_COMPRESSED_R11_EAC:
    case GL_COMPRESSED_SIGNED_R11_EAC:
    case GL_COMPRESSED_RG11_EAC:
    case GL_COMPRESSED_SIGNED_RG11_EAC:
    case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
    case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
        return true;
    }
    return false;
}

bool isBptcFormat(GLenum internalformat) {
    switch (internalformat) {
    case GL_COMPRESSED_RGBA_BPTC_UNORM_EXT:
    case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT:
    case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT:
    case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT:
        return true;
    }
    return false;
}

bool isS3tcFormat(GLenum internalformat) {
    switch (internalformat) {
    case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
    case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
    case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
    case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
    case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
    case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
    case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
    case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
        return true;
  }
  return false;
}

bool isPaletteFormat(GLenum internalformat)  {
    switch (internalformat) {
    case GL_PALETTE4_RGB8_OES:
    case GL_PALETTE4_RGBA8_OES:
    case GL_PALETTE4_R5_G6_B5_OES:
    case GL_PALETTE4_RGBA4_OES:
    case GL_PALETTE4_RGB5_A1_OES:
    case GL_PALETTE8_RGB8_OES:
    case GL_PALETTE8_RGBA8_OES:
    case GL_PALETTE8_R5_G6_B5_OES:
    case GL_PALETTE8_RGBA4_OES:
    case GL_PALETTE8_RGB5_A1_OES:
        return true;
    }
    return false;
}

GLenum decompressedInternalFormat(GLEScontext* ctx, GLenum compressedFormat) {
    bool needSizedInternalFormat =
        isCoreProfile() ||
        (ctx->getMajorVersion() >= 3);

    GLenum glrgb = needSizedInternalFormat ? GL_RGB8 : GL_RGB;
    GLenum glrgba = needSizedInternalFormat ? GL_RGBA8 : GL_RGBA;

    switch (compressedFormat) {
        // ETC2 formats
        case GL_COMPRESSED_RGB8_ETC2:
        case GL_ETC1_RGB8_OES:
            return glrgb;
        case GL_COMPRESSED_RGBA8_ETC2_EAC:
        case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
            return glrgba;
        case GL_COMPRESSED_SRGB8_ETC2:
            return GL_SRGB8;
        case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
            return GL_SRGB8_ALPHA8;
        case GL_COMPRESSED_R11_EAC:
        case GL_COMPRESSED_SIGNED_R11_EAC:
            return GL_R32F;
        case GL_COMPRESSED_RG11_EAC:
        case GL_COMPRESSED_SIGNED_RG11_EAC:
            return GL_RG32F;
        case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
            return GL_SRGB8_ALPHA8;
        // ASTC formats
        case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
        case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
        case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
        case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
        case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
        case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
        case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
        case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
        case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
        case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
        case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
        case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
        case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
        case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
            return glrgba;
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
            return GL_SRGB8_ALPHA8;
        // palette formats
        case GL_PALETTE4_RGB8_OES:
        case GL_PALETTE4_R5_G6_B5_OES:
        case GL_PALETTE8_RGB8_OES:
        case GL_PALETTE8_R5_G6_B5_OES:
            return glrgb;
        case GL_PALETTE4_RGBA8_OES:
        case GL_PALETTE4_RGBA4_OES:
        case GL_PALETTE4_RGB5_A1_OES:
        case GL_PALETTE8_RGBA8_OES:
        case GL_PALETTE8_RGBA4_OES:
        case GL_PALETTE8_RGB5_A1_OES:
            return glrgba;
        case GL_COMPRESSED_RED_RGTC1_EXT:               // BC4U
            return GL_R8;
        case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:        // BC4S
            return GL_R8_SNORM;
        case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:         // BC5U
            return GL_RG8;
        case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:  // BC5S
            return GL_RG8_SNORM;
        default:
            return compressedFormat;
    }
}

class ScopedFetchUnpackData {
    public:
        ScopedFetchUnpackData(GLEScontext* ctx, GLintptr offset,
            GLsizei dataSize) : mCtx(ctx) {
            mData = ctx->dispatcher().glMapBufferRange(
                    GL_PIXEL_UNPACK_BUFFER,
                    offset, dataSize, GL_MAP_READ_BIT);
            if (mData) {
                ctx->dispatcher().glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING,
                        &mUnpackBuffer);
                ctx->dispatcher().glBindBuffer(GL_PIXEL_UNPACK_BUFFER,
                        0);
            }
        }
        ~ScopedFetchUnpackData() {
            if (mData) {
                mCtx->dispatcher().glBindBuffer(GL_PIXEL_UNPACK_BUFFER,
                        mUnpackBuffer);
                mCtx->dispatcher().glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
            }
        }
        void* data() {
            return mData;
        }
    private:
        const GLEScontext* mCtx;
        void* mData = nullptr;
        GLint mUnpackBuffer = 0;
};

void doCompressedTexImage2D(GLEScontext* ctx, GLenum target, GLint level,
                            GLenum internalformat, GLsizei width,
                            GLsizei height, GLint border,
                            GLsizei imageSize, const GLvoid* data,
                            glTexImage2D_t glTexImage2DPtr) {
    /* XXX: This is just a hack to fix the resolve of glTexImage2D problem
       It will be removed when we'll no longer link against ligGL */
    /*typedef void (GLAPIENTRY *glTexImage2DPtr_t ) (
            GLenum target, GLint level, GLint internalformat,
            GLsizei width, GLsizei height, GLint border,
            GLenum format, GLenum type, const GLvoid *pixels);

    glTexImage2DPtr_t glTexImage2DPtr;
    glTexImage2DPtr = (glTexImage2DPtr_t)funcPtr;*/
    bool needUnpackBuffer = false;
    if (ctx->getMajorVersion() >= 3) {
        GLint unpackBuffer = 0;
        ctx->dispatcher().glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING,
                &unpackBuffer);
        needUnpackBuffer = unpackBuffer;
    }
    TextureUnpackReset unpack(ctx);
    const int32_t unpackAlignment = TextureUnpackReset::kUnpackAlignment;
    if (isEtcFormat(internalformat)) {
        GLint format = GL_RGB;
        GLint type = GL_UNSIGNED_BYTE;
        GLint convertedInternalFormat = decompressedInternalFormat(ctx, internalformat);
        ETC2ImageFormat etcFormat = EtcRGB8;
        switch (internalformat) {
            case GL_COMPRESSED_RGB8_ETC2:
            case GL_ETC1_RGB8_OES:
                break;
            case GL_COMPRESSED_RGBA8_ETC2_EAC:
                etcFormat = EtcRGBA8;
                format = GL_RGBA;
                break;
            case GL_COMPRESSED_SRGB8_ETC2:
                break;
            case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
                etcFormat = EtcRGBA8;
                format = GL_RGBA;
                break;
            case GL_COMPRESSED_R11_EAC:
                etcFormat = EtcR11;
                format = GL_RED;
                type = GL_FLOAT;
                break;
            case GL_COMPRESSED_SIGNED_R11_EAC:
                etcFormat = EtcSignedR11;
                format = GL_RED;
                type = GL_FLOAT;
                break;
            case GL_COMPRESSED_RG11_EAC:
                etcFormat = EtcRG11;
                format = GL_RG;
                type = GL_FLOAT;
                break;
            case GL_COMPRESSED_SIGNED_RG11_EAC:
                etcFormat = EtcSignedRG11;
                format = GL_RG;
                type = GL_FLOAT;
                break;
            case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
                etcFormat = EtcRGB8A1;
                format = GL_RGBA;
                break;
            case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
                etcFormat = EtcRGB8A1;
                format = GL_RGBA;
                break;
        }
        int pixelSize = etc_get_decoded_pixel_size(etcFormat);
        GLsizei compressedSize =
            etc_get_encoded_data_size(etcFormat, width, height);
        SET_ERROR_IF((compressedSize != imageSize), GL_INVALID_VALUE);
        std::unique_ptr<ScopedFetchUnpackData> unpackData;
        std::unique_ptr<char[]> emulatedData;
        if (needUnpackBuffer) {
            unpackData.reset(new ScopedFetchUnpackData(ctx,
                    reinterpret_cast<GLintptr>(data), compressedSize));
            data = unpackData->data();
            SET_ERROR_IF(!data, GL_INVALID_OPERATION);
        } else {
            if (!data) {
                emulatedData.reset(new char[compressedSize]);
                data = emulatedData.get();
            }
        }

        const int32_t align = unpackAlignment - 1;
        const int32_t bpr = ((width * pixelSize) + align) & ~align;
        const size_t size = bpr * height;
        std::unique_ptr<etc1_byte[]> pOut(new etc1_byte[size]);

        int res =
            etc2_decode_image(
                    (const etc1_byte*)data, etcFormat, pOut.get(),
                    width, height, bpr);
        SET_ERROR_IF(res!=0, GL_INVALID_VALUE);

        glTexImage2DPtr(target, level, convertedInternalFormat,
                        width, height, border, format, type, pOut.get());
    } else if (isAstcFormat(internalformat)) {
        std::unique_ptr<ScopedFetchUnpackData> unpackData;
        std::unique_ptr<char[]> emulatedData;
        if (needUnpackBuffer) {
            unpackData.reset(
                new ScopedFetchUnpackData(ctx, reinterpret_cast<GLintptr>(data), imageSize));
            data = unpackData->data();
            SET_ERROR_IF(!data, GL_INVALID_OPERATION);
        } else {
            if (!data) {
                emulatedData.reset(new char[imageSize]);
                data = emulatedData.get();
            }
        }
        uint32_t blockWidth = 0;
        uint32_t blockHeight = 0;
        bool srgb;
        getAstcFormatInfo(internalformat, &blockWidth, &blockHeight, &srgb);

        const int32_t align = unpackAlignment - 1;
        const int32_t stride = ((width * 4) + align) & ~align;
        const size_t size = stride * height;

        AlignedBuf<uint8_t, 64> alignedUncompressedData(size);

        const bool result = astcDecompress(
                reinterpret_cast<const uint8_t*>(data), imageSize, width,
                height, blockWidth, blockHeight, alignedUncompressedData.data(), size);
        SET_ERROR_IF(!result, GL_INVALID_VALUE);

        glTexImage2DPtr(target, level, srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8, width,
                        height, border, GL_RGBA, GL_UNSIGNED_BYTE,
                        alignedUncompressedData.data());
    } else if (isPaletteFormat(internalformat)) {
        // TODO: fix the case when GL_PIXEL_UNPACK_BUFFER is bound
        SET_ERROR_IF(
            level > log2(ctx->getMaxTexSize()) ||
            border !=0 || level > 0 ||
            !GLESvalidate::texImgDim(
                width, height, ctx->getMaxTexSize() + 2),
            GL_INVALID_VALUE);
        SET_ERROR_IF(!data,GL_INVALID_OPERATION);
        //the decoder fully packed the pixels.
        ctx->dispatcher().glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        int nMipmaps = -level + 1;
        GLsizei tmpWidth  = width;
        GLsizei tmpHeight = height;

        for(int i = 0; i < nMipmaps; i++)
        {
            GLenum uncompressedFrmt;
            unsigned char* uncompressed =
                uncompressTexture(internalformat, uncompressedFrmt,
                                  width, height, imageSize, data, i);
            glTexImage2DPtr(target, i, uncompressedFrmt,
                            tmpWidth, tmpHeight, border,
                            uncompressedFrmt, GL_UNSIGNED_BYTE, uncompressed);
            tmpWidth /= 2;
            tmpHeight /= 2;
            delete [] uncompressed;
        }
    } else if (isRgtcFormat(internalformat)) {
        GLint format, type;
        GLint convertedInternalFormat = decompressedInternalFormat(ctx, internalformat);
        RGTCImageFormat rgtcFormat;
        switch (internalformat) {
            case GL_COMPRESSED_RED_RGTC1_EXT:               // BC4U
                format = GL_RED;
                type = GL_UNSIGNED_BYTE;
                rgtcFormat = BC4_UNORM;
                break;
            case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:        // BC4S
                format = GL_RED;
                type = GL_BYTE;
                rgtcFormat = BC4_SNORM;
                break;
            case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:         // BC5U
                format = GL_RG;
                type = GL_UNSIGNED_BYTE;
                rgtcFormat = BC5_UNORM;
                break;
            case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:  // BC5S
                format = GL_RG;
                type = GL_BYTE;
                rgtcFormat = BC5_SNORM;
                break;
        }
        size_t pixelSize = rgtc_get_decoded_pixel_size(rgtcFormat);
        GLsizei compressedSize = rgtc_get_encoded_image_size(rgtcFormat, width, height);
        SET_ERROR_IF((compressedSize != imageSize), GL_INVALID_VALUE);
        std::unique_ptr<ScopedFetchUnpackData> unpackData;
        std::unique_ptr<char[]> emulatedData;
        if (needUnpackBuffer) {
            unpackData.reset(
                new ScopedFetchUnpackData(ctx, reinterpret_cast<GLintptr>(data), compressedSize));
            data = unpackData->data();
            SET_ERROR_IF(!data, GL_INVALID_OPERATION);
        } else {
            if (!data) {
                emulatedData.reset(new char[compressedSize]);
                data = emulatedData.get();
            }
        }
        const int32_t align = unpackAlignment - 1;
        const int32_t bpr = ((width * pixelSize) + align) & ~align;
        const size_t size = bpr * height;
        std::unique_ptr<uint8_t[]> pOut(new uint8_t[size]);

        int res =
            rgtc_decode_image((const uint8_t*)data, rgtcFormat, pOut.get(), width, height, bpr);
        SET_ERROR_IF(res != 0, GL_INVALID_VALUE);
        glTexImage2DPtr(target, level, convertedInternalFormat, width, height, border, format, type,
                        pOut.get());
    } else {
        SET_ERROR_IF(1, GL_INVALID_ENUM);
    }
}

void deleteRenderbufferGlobal(GLuint rbo) {
    if (rbo) {
        GLEScontext::dispatcher().glDeleteRenderbuffers(1, &rbo);
    }
}

bool isCubeMapFaceTarget(GLenum target) {
    switch (target) {
        case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
        case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
        case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
        case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
        case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
        case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
            return true;
    }
    return false;
}

bool isCoreProfileEmulatedFormat(GLenum format) {
    switch (format) {
    case GL_ALPHA:
    case GL_LUMINANCE:
    case GL_LUMINANCE_ALPHA:
        return true;
    default:
        return false;
    }
}

GLenum getCoreProfileEmulatedFormat(GLenum format) {
    switch (format) {
        case GL_ALPHA:
        case GL_LUMINANCE:
            return GL_RED;
        case GL_LUMINANCE_ALPHA:
            return GL_RG;
    }
    return format;
}

GLint getCoreProfileEmulatedInternalFormat(GLint internalformat, GLenum type) {
    switch (internalformat) {
        case GL_ALPHA:
        case GL_LUMINANCE:
            switch (type) {
                case GL_UNSIGNED_BYTE:
                    return GL_R8;
                case GL_FLOAT:
                    return GL_R32F;
                case GL_HALF_FLOAT:
                    return GL_R16F;
            }
            return GL_R8;
        case GL_LUMINANCE_ALPHA:
            switch (type) {
                case GL_UNSIGNED_BYTE:
                    return GL_RG8;
                case GL_FLOAT:
                    return GL_RG32F;
                case GL_HALF_FLOAT:
                    return GL_RG16F;
            }
            return GL_RG8;
    }
    fprintf(stderr,
            "%s: warning: unsupported alpha/luminance internal format 0x%x type 0x%x\n",
            __func__, internalformat, type);
    return GL_R8;
}

TextureSwizzle getSwizzleForEmulatedFormat(GLenum format) {
    TextureSwizzle res;
    switch (format) {
        case GL_ALPHA:
            res.toRed   = GL_ZERO;
            res.toGreen = GL_ZERO;
            res.toBlue  = GL_ZERO;
            res.toAlpha = GL_RED;
            break;
        case GL_LUMINANCE:
            res.toRed   = GL_RED;
            res.toGreen = GL_RED;
            res.toBlue  = GL_RED;
            res.toAlpha = GL_ONE;
            break;
        case GL_LUMINANCE_ALPHA:
            res.toRed   = GL_RED;
            res.toGreen = GL_RED;
            res.toBlue  = GL_RED;
            res.toAlpha = GL_GREEN;
            break;
        default:
            break;
    }
    return res;
}

// Inverse swizzle: if we were writing fragments back to this texture,
// how should the components be re-arranged?
TextureSwizzle getInverseSwizzleForEmulatedFormat(GLenum format) {
    TextureSwizzle res;
    switch (format) {
        case GL_ALPHA:
            res.toRed   = GL_ALPHA;
            res.toGreen = GL_ZERO;
            res.toBlue  = GL_ZERO;
            res.toAlpha = GL_ZERO;
            break;
        case GL_LUMINANCE:
            res.toRed   = GL_RED;
            res.toGreen = GL_ZERO;
            res.toBlue  = GL_ZERO;
            res.toAlpha = GL_ZERO;
            break;
        case GL_LUMINANCE_ALPHA:
            res.toRed   = GL_RED;
            res.toGreen = GL_ALPHA;
            res.toBlue  = GL_ZERO;
            res.toAlpha = GL_ZERO;
            break;
        default:
            break;
    }
    return res;
}

GLenum swizzleComponentOf(const TextureSwizzle& s, GLenum component) {
    switch (component) {
    case GL_RED: return s.toRed;
    case GL_GREEN: return s.toGreen;
    case GL_BLUE: return s.toBlue;
    case GL_ALPHA: return s.toAlpha;
    }
    // Identity map for GL_ZERO / GL_ONE
    return component;
}

TextureSwizzle concatSwizzles(const TextureSwizzle& first,
                              const TextureSwizzle& next) {

    TextureSwizzle result;
    result.toRed = swizzleComponentOf(first, next.toRed);
    result.toGreen = swizzleComponentOf(first, next.toGreen);
    result.toBlue = swizzleComponentOf(first, next.toBlue);
    result.toAlpha = swizzleComponentOf(first, next.toAlpha);
    return result;
}

bool isSwizzleParam(GLenum pname) {
    switch (pname) {
    case GL_TEXTURE_SWIZZLE_R:
    case GL_TEXTURE_SWIZZLE_G:
    case GL_TEXTURE_SWIZZLE_B:
    case GL_TEXTURE_SWIZZLE_A:
        return true;
    default:
        return false;
    }
}

bool isIntegerInternalFormat(GLint internalformat) {
    switch (internalformat) {
        case GL_R8I:
        case GL_R8UI:
        case GL_R16I:
        case GL_R16UI:
        case GL_R32I:
        case GL_R32UI:
        case GL_RG8I:
        case GL_RG8UI:
        case GL_RG16I:
        case GL_RG16UI:
        case GL_RG32I:
        case GL_RG32UI:
        case GL_RGB8I:
        case GL_RGB8UI:
        case GL_RGB16I:
        case GL_RGB16UI:
        case GL_RGB32I:
        case GL_RGB32UI:
        case GL_RGBA8I:
        case GL_RGBA8UI:
        case GL_RGBA16I:
        case GL_RGBA16UI:
        case GL_RGBA32I:
        case GL_RGBA32UI:
            return true;
        default:
            return false;
    }
}

void doCompressedTexImage2DNative(GLEScontext* ctx, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid* data) {
    // AlignedBuf<uint8_t, 64> alignedData(imageSize);
    // memcpy(alignedData.data(), data, imageSize);
    // GLint err = ctx->dispatcher().glGetError();
    ctx->dispatcher().glCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data);
    //     fprintf(stderr, "%s: tex %u target 0x%x level 0x%x iformat 0x%x w h b %d %d %d imgSize %d\n", __func__, ctx->getBindedTexture(target), target, level, internalformat, width, height, border, imageSize);
    // err = ctx->dispatcher().glGetError(); if (err) {
    //     fprintf(stderr, "%s:%d err 0x%x\n", __func__, __LINE__, err);
    // }
}

void doCompressedTexSubImage2DNative(GLEScontext* ctx, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid* data) {
    // AlignedBuf<uint8_t, 64> alignedData(imageSize);
    // memcpy(alignedData.data(), data, imageSize);
    // GLint err = ctx->dispatcher().glGetError();
    ctx->dispatcher().glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data);
    //     fprintf(stderr, "%s: tex %u target 0x%x level 0x%x format 0x%x x y w h %d %d %d %d imgSize %d\n", __func__, ctx->getBindedTexture(target), target, level, format, xoffset, yoffset, width, height, imageSize);
    // err = ctx->dispatcher().glGetError(); if (err) {
    //     fprintf(stderr, "%s:%d err 0x%x\n", __func__, __LINE__, err);
    // }
}

void forEachEtc2Format(std::function<void(GLint format)> f) {
    f(GL_COMPRESSED_RGB8_ETC2);
    f(GL_COMPRESSED_SRGB8_ETC2);
    f(GL_COMPRESSED_RGBA8_ETC2_EAC);
    f(GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC);
    f(GL_COMPRESSED_R11_EAC);
    f(GL_COMPRESSED_SIGNED_R11_EAC);
    f(GL_COMPRESSED_RG11_EAC);
    f(GL_COMPRESSED_SIGNED_RG11_EAC);
    f(GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2);
    f(GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2);
}

void forEachAstcFormat(std::function<void(GLint format)> f) {

#define CALL_ON_ASTC_FORMAT(typeName, blockWidth, blockHeight, srgbValue) \
    f(typeName);

    ASTC_FORMATS_LIST(CALL_ON_ASTC_FORMAT)
}

void forEachBptcFormat(std::function<void(GLint format)> f) {
    f(GL_COMPRESSED_RGBA_BPTC_UNORM_EXT);
    f(GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT);
    f(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT);
    f(GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT);
}

void forEachS3tcFormat(std::function<void(GLint format)> f) {
    f(GL_COMPRESSED_RGB_S3TC_DXT1_EXT);
    f(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT);
    f(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT);
    f(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT);
    f(GL_COMPRESSED_SRGB_S3TC_DXT1_EXT);
    f(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT);
    f(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT);
    f(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT);
}

bool isRgtcFormat(GLenum format) {
    switch (format) {
        case GL_COMPRESSED_RED_RGTC1_EXT: // BC4U
        case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: // BC4S
        case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: // BC5U
        case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: // BC5S
            return true;
        default:
            break;
    }
    return false;
}

bool isEtc2OrAstcFormat(GLenum format) {
    switch (format) {
    case GL_COMPRESSED_RGB8_ETC2:
    case GL_COMPRESSED_SRGB8_ETC2:
    case GL_COMPRESSED_RGBA8_ETC2_EAC:
    case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
    case GL_COMPRESSED_R11_EAC:
    case GL_COMPRESSED_SIGNED_R11_EAC:
    case GL_COMPRESSED_RG11_EAC:
    case GL_COMPRESSED_SIGNED_RG11_EAC:
    case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
    case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
        return true;
    default:
        break;
    }
    return isAstcFormat(format);
}

bool shouldPassthroughCompressedFormat(GLEScontext* ctx, GLenum internalformat) {
    if (isEtc2Format(internalformat)) {
        return ctx->getCaps()->hasEtc2Support;
    } else if (isAstcFormat(internalformat)) {
        return ctx->getCaps()->hasAstcSupport;
    } else if (isBptcFormat(internalformat)) {
        return ctx->getCaps()->hasBptcSupport;
    } else if (isS3tcFormat(internalformat)) {
        return ctx->getCaps()->hasS3tcSupport;
    } else if (isRgtcFormat(internalformat)) {
        return ctx->getCaps()->hasRgtcSupport;
    }
    return false;
}

static uint32_t s_texAlign(uint32_t v, uint32_t align) {
    uint32_t rem = v % align;
    return rem ? (v + (align - rem)) : v;
}

// s_computePixelSize is both in the host and the guest. Consider moving it to
// android-emugl/shared

static int s_computePixelSize(GLenum format, GLenum type) {
#define FORMAT_ERROR(format, type)                                         \
    fprintf(stderr, "%s:%d unknown format/type 0x%x 0x%x\n", __FUNCTION__, \
            __LINE__, format, type);

    switch (type) {
        case GL_BYTE:
            switch (format) {
                case GL_R8:
                case GL_R8I:
                case GL_R8_SNORM:
                case GL_RED:
                    return 1;
                case GL_RED_INTEGER:
                    return 1;
                case GL_RG8:
                case GL_RG8I:
                case GL_RG8_SNORM:
                case GL_RG:
                    return 1 * 2;
                case GL_RG_INTEGER:
                    return 1 * 2;
                case GL_RGB8:
                case GL_RGB8I:
                case GL_RGB8_SNORM:
                case GL_RGB:
                    return 1 * 3;
                case GL_RGB_INTEGER:
                    return 1 * 3;
                case GL_RGBA8:
                case GL_RGBA8I:
                case GL_RGBA8_SNORM:
                case GL_RGBA:
                    return 1 * 4;
                case GL_RGBA_INTEGER:
                    return 1 * 4;
                default:
                    FORMAT_ERROR(format, type);
            }
            break;
        case GL_UNSIGNED_BYTE:
            switch (format) {
                case GL_R8:
                case GL_R8UI:
                case GL_RED:
                    return 1;
                case GL_RED_INTEGER:
                    return 1;
                case GL_ALPHA8_EXT:
                case GL_ALPHA:
                    return 1;
                case GL_LUMINANCE8_EXT:
                case GL_LUMINANCE:
                    return 1;
                case GL_LUMINANCE8_ALPHA8_EXT:
                case GL_LUMINANCE_ALPHA:
                    return 1 * 2;
                case GL_RG8:
                case GL_RG8UI:
                case GL_RG:
                    return 1 * 2;
                case GL_RG_INTEGER:
                    return 1 * 2;
                case GL_RGB8:
                case GL_RGB8UI:
                case GL_SRGB8:
                case GL_RGB:
                    return 1 * 3;
                case GL_RGB_INTEGER:
                    return 1 * 3;
                case GL_RGBA8:
                case GL_RGBA8UI:
                case GL_SRGB8_ALPHA8:
                case GL_RGBA:
                    return 1 * 4;
                case GL_RGBA_INTEGER:
                    return 1 * 4;
                case GL_BGRA_EXT:
                case GL_BGRA8_EXT:
                    return 1 * 4;
                default:
                    FORMAT_ERROR(format, type);
            }
            break;
        case GL_SHORT:
            switch (format) {
                case GL_R16I:
                case GL_RED_INTEGER:
                    return 2;
                case GL_RG16I:
                case GL_RG_INTEGER:
                    return 2 * 2;
                case GL_RGB16I:
                case GL_RGB_INTEGER:
                    return 2 * 3;
                case GL_RGBA16I:
                case GL_RGBA_INTEGER:
                    return 2 * 4;
                default:
                    FORMAT_ERROR(format, type);
            }
            break;
        case GL_UNSIGNED_SHORT:
            switch (format) {
                case GL_DEPTH_COMPONENT16:
                case GL_DEPTH_COMPONENT:
                    return 2;
                case GL_R16UI:
                case GL_RED_INTEGER:
                    return 2;
                case GL_RG16UI:
                case GL_RG_INTEGER:
                    return 2 * 2;
                case GL_RGB16UI:
                case GL_RGB_INTEGER:
                    return 2 * 3;
                case GL_RGBA16UI:
                case GL_RGBA_INTEGER:
                    return 2 * 4;
                default:
                    FORMAT_ERROR(format, type);
            }
            break;
        case GL_INT:
            switch (format) {
                case GL_R32I:
                case GL_RED_INTEGER:
                    return 4;
                case GL_RG32I:
                case GL_RG_INTEGER:
                    return 4 * 2;
                case GL_RGB32I:
                case GL_RGB_INTEGER:
                    return 4 * 3;
                case GL_RGBA32I:
                case GL_RGBA_INTEGER:
                    return 4 * 4;
                default:
                    FORMAT_ERROR(format, type);
            }
            break;
        case GL_UNSIGNED_INT:
            switch (format) {
                case GL_DEPTH_COMPONENT16:
                case GL_DEPTH_COMPONENT24:
                case GL_DEPTH_COMPONENT32_OES:
                case GL_DEPTH_COMPONENT:
                    return 4;
                case GL_R32UI:
                case GL_RED_INTEGER:
                    return 4;
                case GL_RG32UI:
                case GL_RG_INTEGER:
                    return 4 * 2;
                case GL_RGB32UI:
                case GL_RGB_INTEGER:
                    return 4 * 3;
                case GL_RGBA32UI:
                case GL_RGBA_INTEGER:
                    return 4 * 4;
                default:
                    FORMAT_ERROR(format, type);
            }
            break;
        case GL_UNSIGNED_SHORT_4_4_4_4:
        case GL_UNSIGNED_SHORT_5_5_5_1:
        case GL_UNSIGNED_SHORT_5_6_5:
        case GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT:
        case GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT:
            return 2;
        case GL_UNSIGNED_INT_10F_11F_11F_REV:
        case GL_UNSIGNED_INT_5_9_9_9_REV:
        case GL_UNSIGNED_INT_2_10_10_10_REV:
        case GL_UNSIGNED_INT_24_8_OES:
            return 4;
        case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
            return 4 + 4;
        case GL_FLOAT:
            switch (format) {
                case GL_DEPTH_COMPONENT32F:
                case GL_DEPTH_COMPONENT:
                    return 4;
                case GL_ALPHA32F_EXT:
                case GL_ALPHA:
                    return 4;
                case GL_LUMINANCE32F_EXT:
                case GL_LUMINANCE:
                    return 4;
                case GL_LUMINANCE_ALPHA32F_EXT:
                case GL_LUMINANCE_ALPHA:
                    return 4 * 2;
                case GL_RED:
                    return 4;
                case GL_R32F:
                    return 4;
                case GL_RG:
                    return 4 * 2;
                case GL_RG32F:
                    return 4 * 2;
                case GL_RGB:
                    return 4 * 3;
                case GL_RGB32F:
                    return 4 * 3;
                case GL_RGBA:
                    return 4 * 4;
                case GL_RGBA32F:
                    return 4 * 4;
                default:
                    FORMAT_ERROR(format, type);
            }
            break;
        case GL_HALF_FLOAT:
        case GL_HALF_FLOAT_OES:
            switch (format) {
                case GL_ALPHA16F_EXT:
                case GL_ALPHA:
                    return 2;
                case GL_LUMINANCE16F_EXT:
                case GL_LUMINANCE:
                    return 2;
                case GL_LUMINANCE_ALPHA16F_EXT:
                case GL_LUMINANCE_ALPHA:
                    return 2 * 2;
                case GL_RED:
                    return 2;
                case GL_R16F:
                    return 2;
                case GL_RG:
                    return 2 * 2;
                case GL_RG16F:
                    return 2 * 2;
                case GL_RGB:
                    return 2 * 3;
                case GL_RGB16F:
                    return 2 * 3;
                case GL_RGBA:
                    return 2 * 4;
                case GL_RGBA16F:
                    return 2 * 4;
                default:
                    FORMAT_ERROR(format, type);
            }
            break;
        default:
            FORMAT_ERROR(format, type);
    }

    return 0;
}

uint32_t texImageSize(GLenum internalformat,
                      GLenum type,
                      int unpackAlignment,
                      GLsizei width,
                      GLsizei height) {

    uint32_t alignedWidth = s_texAlign(width, unpackAlignment);
    uint32_t pixelSize = s_computePixelSize(internalformat, type);
    uint32_t totalSize = pixelSize * alignedWidth * height;

    return totalSize;
}

GLenum getFormatFromInternalFormat(GLint internalFormat) {
    switch (internalFormat) {
        case GL_R8:
            return GL_RED;
        case GL_RG8:
            return GL_RG;
        case GL_RGB8:
        case GL_RGB565:
        case GL_RGB16F:
            return GL_RGB;
        case GL_RGBA8:
        case GL_RGB5_A1_OES:
        case GL_RGBA4_OES:
        case GL_UNSIGNED_INT_10_10_10_2_OES:
        case GL_RGB10_A2:
        case GL_RGBA16F:
            return GL_RGBA;
        case GL_BGRA8_EXT:
            return GL_BGRA_EXT;
        default: // already unsized
            return internalFormat;
    }
}

GLenum getTypeFromInternalFormat(GLint internalFormat) {
    switch (internalFormat) {
        case GL_RGB:
        case GL_RGB8:
            return GL_UNSIGNED_BYTE;
        case GL_RGB565_OES:
            return GL_UNSIGNED_SHORT_5_6_5;
        case GL_RGBA:
        case GL_RGBA8:
        case GL_RGB5_A1_OES:
        case GL_RGBA4_OES:
            return GL_UNSIGNED_BYTE;
        case GL_UNSIGNED_INT_10_10_10_2_OES:
            return GL_UNSIGNED_SHORT;
        case GL_RGB10_A2:
            return GL_UNSIGNED_INT_2_10_10_10_REV;
        case GL_RGB16F:
            return GL_HALF_FLOAT;
        case GL_RGBA16F:
            return GL_HALF_FLOAT;
        case GL_LUMINANCE:
            return GL_UNSIGNED_SHORT;
        case GL_BGRA_EXT:
            return GL_UNSIGNED_BYTE;
        case GL_R8:
        case GL_RED:
            return GL_UNSIGNED_BYTE;
        case GL_RG8:
        case GL_RG:
            return GL_UNSIGNED_BYTE;
        default:
            fprintf(stderr, "%s: Unknown format 0x%x\n", __func__,
                    internalFormat);
            return GL_UNSIGNED_BYTE;
    }
}


GLint TextureUnpackReset::unpackCheckAndUpdate(GLenum name, GLint newValue) {
    GLint curValue;
    glesContext->dispatcher().glGetIntegerv(name, &curValue);
    if (curValue != newValue) {
        glesContext->dispatcher().glPixelStorei(name, newValue);
    }
    return curValue;
}

TextureUnpackReset::TextureUnpackReset(GLEScontext* ctx) : glesContext(ctx) {
    unpackAlignment = unpackCheckAndUpdate(GL_UNPACK_ALIGNMENT, kUnpackAlignment);
    if (glesContext->getMajorVersion() >= 3) {
        unpackRowLength = unpackCheckAndUpdate(GL_UNPACK_ROW_LENGTH, kUnpackRowLength);
        unpackImageHeight = unpackCheckAndUpdate(GL_UNPACK_IMAGE_HEIGHT, kUnpackImageHeight);
        unpackSkipRows = unpackCheckAndUpdate(GL_UNPACK_SKIP_ROWS, kUnpackSkipRows);
        unpackSkipPixels = unpackCheckAndUpdate(GL_UNPACK_SKIP_PIXELS, kUnpackSkipPixels);
        unpackSkipImages = unpackCheckAndUpdate(GL_UNPACK_SKIP_IMAGES, kUnpackSkipImages);
    } else {
        // avoid clang-tidy warnings on uninitialized values
        unpackRowLength = 0;
        unpackImageHeight = 0;
        unpackSkipRows = 0;
        unpackSkipPixels = 0;
        unpackSkipImages = 0;
    }
}
TextureUnpackReset::~TextureUnpackReset() {
    unpackCheckAndUpdate(GL_UNPACK_ALIGNMENT, unpackAlignment);
    if (glesContext->getMajorVersion() >= 3) {
        unpackCheckAndUpdate(GL_UNPACK_ROW_LENGTH, unpackRowLength);
        unpackCheckAndUpdate(GL_UNPACK_IMAGE_HEIGHT, unpackImageHeight);
        unpackCheckAndUpdate(GL_UNPACK_SKIP_ROWS, unpackSkipRows);
        unpackCheckAndUpdate(GL_UNPACK_SKIP_PIXELS, unpackSkipPixels);
        unpackCheckAndUpdate(GL_UNPACK_SKIP_IMAGES, unpackSkipImages);
    }
}

