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

#include "aemu/base/ArraySize.h"
#include "aemu/base/containers/SmallVector.h"
#include "aemu/base/files/StreamSerializing.h"
#include "aemu/base/system/System.h"

#include "GLcommon/GLEScontext.h"
#include "GLcommon/GLutils.h"
#include "GLcommon/TextureUtils.h"

#include "host-common/crash_reporter.h"
#include "host-common/logging.h"

#include <algorithm>

#define SAVEABLE_TEXTURE_DEBUG 0

#if SAVEABLE_TEXTURE_DEBUG
#define D(fmt,...) printf("%s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__);
#else
#define D(fmt,...)
#endif

// using android::base::ScopedMemoryProfiler;
// using android::base::LazyInstance;
// using android::base::MemoryProfiler;
// using android::base::StringView;

static const GLenum kTexParam[] = {
    GL_TEXTURE_MIN_FILTER,
    GL_TEXTURE_MAG_FILTER,
    GL_TEXTURE_WRAP_S,
    GL_TEXTURE_WRAP_T,
};

static const GLenum kTexParamGles3[] = {
    GL_TEXTURE_BASE_LEVEL,
    GL_TEXTURE_COMPARE_FUNC,
    GL_TEXTURE_COMPARE_MODE,
    GL_TEXTURE_MIN_LOD,
    GL_TEXTURE_MAX_LOD,
    GL_TEXTURE_MAX_LEVEL,
    GL_TEXTURE_SWIZZLE_R,
    GL_TEXTURE_SWIZZLE_G,
    GL_TEXTURE_SWIZZLE_B,
    GL_TEXTURE_SWIZZLE_A,
    GL_TEXTURE_WRAP_R,
};

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;
}

static uint32_t s_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;
}

struct TextureDataReader {
    GLESVersion glesVersion = GLES_2_0;
    GLenum fbTarget = GL_FRAMEBUFFER;
    GLint prevViewport[4] = { 0, 0, 0, 0 };
    GLuint fbo = 0;
    GLuint prevFbo = 0;

    void setupFbo() {
        GLenum fbBindingTarget;
        auto gl = GLEScontext::dispatcher();

        glesVersion = gl.getGLESVersion();
        if (glesVersion >= GLES_3_0) {
            fbTarget = GL_READ_FRAMEBUFFER;
            fbBindingTarget = GL_READ_FRAMEBUFFER_BINDING;
        } else {
            fbTarget = GL_FRAMEBUFFER;
            fbBindingTarget = GL_FRAMEBUFFER_BINDING;
        }
        gl.glGetIntegerv(GL_VIEWPORT, prevViewport);
        gl.glGenFramebuffers(1, &fbo);
        gl.glGetIntegerv(fbBindingTarget, (GLint*)&prevFbo);
        gl.glBindFramebuffer(fbTarget, fbo);
    }

    void teardownFbo() {
        if (!fbo) return;
        auto gl = GLEScontext::dispatcher();

        gl.glBindFramebuffer(fbTarget, prevFbo);
        gl.glDeleteFramebuffers(1, &fbo);
        gl.glViewport(prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]);

        *this = {};
    }

    bool shouldUseReadPixels(
        GLenum target, GLenum level, GLenum format, GLenum type) {

        auto gl = GLEScontext::dispatcher();
        if (!gl.glGetTexImage) return true;

        // TODO: if (isGles2Gles()) return true

        // TODO: Query extensions for support for these kinds of things
        if (target != GL_TEXTURE_2D || level != 0) return false;

#define KNOWN_GOOD_READ_PIXELS_COMBINATION(goodFormat, goodType) \
        if (format == goodFormat && type == goodType) return true;

        KNOWN_GOOD_READ_PIXELS_COMBINATION(GL_RGB, GL_UNSIGNED_BYTE)
        KNOWN_GOOD_READ_PIXELS_COMBINATION(GL_RGBA, GL_UNSIGNED_BYTE)

        return false;
    }

    void getTexImage(
        GLuint globalName, GLenum target, GLenum level, GLenum format, GLenum type,
        GLint width, GLint height, GLint depth, uint8_t* data) {

        D("Reading: %u 0x%x %u 0x%x 0x%x %d x %d x %d...",
          globalName, target, level, format, type, width, height, depth);

        auto gl = GLEScontext::dispatcher();

        if (!shouldUseReadPixels(target, level, format, type)) {
            D("with underlying glGetTexImage");
            gl.glGetTexImage(target, level, format, type, data);
            return;
        }


        GLenum attachment = GL_COLOR_ATTACHMENT0;

        switch (format) {
            case GL_DEPTH_COMPONENT:
                attachment = GL_DEPTH_ATTACHMENT;
                break;
            case GL_DEPTH_STENCIL:
                attachment = GL_DEPTH_STENCIL_ATTACHMENT;
                break;
        }

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

        switch (target) {
            case GL_TEXTURE_2D:
            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:
                D("with glFramebufferTexture2D + glReadPixels");
                gl.glFramebufferTexture2D(
                        fbTarget, attachment, target,
                        globalName, level);
                gl.glReadPixels(0, 0, width, height,
                        format, type,
                        data);
                gl.glFramebufferTexture2D(
                        fbTarget, attachment, target,
                        0, level);
                break;
            case GL_TEXTURE_3D: {
                D("with glFramebufferTexture3DOES + glReadPixels");
                unsigned int layerImgSize = s_texImageSize(
                        format, type, 1, width, height);
                for (unsigned int d = 0; d < depth; d++) {
                    gl.glFramebufferTexture3DOES(
                            fbTarget, attachment, target,
                            globalName, level, d);
                    gl.glReadPixels(0, 0, width,
                            height, format,
                            type, data +
                            layerImgSize * d);
                    gl.glFramebufferTexture3DOES(
                            fbTarget, attachment, target,
                            0, level, d);
                }
                break;
            }
            case GL_TEXTURE_2D_ARRAY: {
                D("with glFramebufferTextureLayer + glReadPixels");
                unsigned int layerImgSize = s_texImageSize(
                        format, type, 1, width, height);
                for (unsigned int d = 0; d < depth; d++) {
                    gl.glFramebufferTextureLayer(
                            fbTarget, attachment,
                            globalName, level, d);
                    gl.glReadPixels(0, 0, width,
                            height, format,
                            type, data +
                            layerImgSize * d);
                    gl.glFramebufferTextureLayer(
                            fbTarget, attachment,
                            0, level, d);
                }
                break;
            }
        }
    }

    void preSave() {
        setupFbo();
    }

    void postSave() {
        teardownFbo();
    }
};

static TextureDataReader* sTextureDataReader() {
    static TextureDataReader* r = new TextureDataReader;
    return r;
}

void SaveableTexture::preSave() {
    sTextureDataReader()->preSave();
}

void SaveableTexture::postSave() {
    sTextureDataReader()->postSave();
}

SaveableTexture::SaveableTexture(const TextureData& texture)
    : m_target(texture.target),
      m_width(texture.width),
      m_height(texture.height),
      m_depth(texture.depth),
      m_format(texture.format),
      m_internalFormat(texture.internalFormat),
      m_type(texture.type),
      m_border(texture.border),
      m_texStorageLevels(texture.texStorageLevels),
      m_globalName(texture.getGlobalName()),
      m_isDirty(true) {}

SaveableTexture::SaveableTexture(GlobalNameSpace* globalNameSpace,
                                 loader_t&& loader)
    : m_loader(std::move(loader)),
      m_globalNamespace(globalNameSpace),
      m_isDirty(false) {
    mNeedRestore = true;
}

void SaveableTexture::loadFromStream(android::base::Stream* stream) {
    m_target = stream->getBe32();
    m_width = stream->getBe32();
    m_height = stream->getBe32();
    m_depth = stream->getBe32();
    m_format = stream->getBe32();
    m_internalFormat = stream->getBe32();
    m_type = stream->getBe32();
    m_border = stream->getBe32();
    m_texStorageLevels = stream->getBe32();
    m_maxMipmapLevel = stream->getBe32();
    // TODO: handle other texture targets
    if (m_target == GL_TEXTURE_2D || m_target == GL_TEXTURE_CUBE_MAP ||
        m_target == GL_TEXTURE_3D || m_target == GL_TEXTURE_2D_ARRAY) {
        unsigned int numLevels = m_texStorageLevels ? m_texStorageLevels :
                m_maxMipmapLevel + 1;
        auto loadTex = [stream, numLevels](
                               std::unique_ptr<LevelImageData[]>& levelData,
                               bool isDepth) {
            levelData.reset(new LevelImageData[numLevels]);
            for (unsigned int level = 0; level < numLevels; level++) {
                levelData[level].m_width = stream->getBe32();
                levelData[level].m_height = stream->getBe32();
                if (isDepth) {
                    levelData[level].m_depth = stream->getBe32();
                }
                loadBuffer(stream, &levelData[level].m_data);
            }
        };
        switch (m_target) {
            case GL_TEXTURE_2D:
                loadTex(m_levelData[0], false);
                break;
            case GL_TEXTURE_CUBE_MAP:
                for (int i = 0; i < 6; i++) {
                    loadTex(m_levelData[i], false);
                }
                break;
            case GL_TEXTURE_3D:
            case GL_TEXTURE_2D_ARRAY:
                loadTex(m_levelData[0], true);
                break;
            default:
                break;
        }
        // Load tex param
        loadCollection(stream, &m_texParam,
                [](android::base::Stream* stream)
                    -> std::unordered_map<GLenum, GLint>::value_type {
                    GLenum pname = stream->getBe32();
                    GLint value = stream->getBe32();
                    return std::make_pair(pname, value);
                });
    } else if (m_target != 0) {
        GL_LOG("SaveableTexture::%s: warning: texture target 0x%x not "
               "supported\n",
               __func__, m_target);
        fprintf(stderr, "Warning: texture target %d not supported\n", m_target);
    }
    m_loadedFromStream.store(true);
}

void SaveableTexture::onSave(
        android::base::Stream* stream) {
    stream->putBe32(m_target);
    stream->putBe32(m_width);
    stream->putBe32(m_height);
    stream->putBe32(m_depth);
    stream->putBe32(m_format);
    stream->putBe32(m_internalFormat);
    stream->putBe32(m_type);
    stream->putBe32(m_border);
    stream->putBe32(m_texStorageLevels);
    stream->putBe32(m_maxMipmapLevel);
    // TODO: handle other texture targets
    if (m_target == GL_TEXTURE_2D || m_target == GL_TEXTURE_CUBE_MAP ||
        m_target == GL_TEXTURE_3D || m_target == GL_TEXTURE_2D_ARRAY) {
        static constexpr GLenum pixelStoreIndexes[] = {
                GL_PACK_ROW_LENGTH, GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS,
                GL_PACK_ALIGNMENT,
        };
        static constexpr GLint pixelStoreDesired[] = {0, 0, 0, 1};
        GLint pixelStorePrev[android::base::arraySize(pixelStoreIndexes)];

        GLint prevTex = 0;
        GLDispatch& dispatcher = GLEScontext::dispatcher();
        assert(dispatcher.glGetIntegerv);
        for (int i = 0; i != android::base::arraySize(pixelStoreIndexes); ++i) {
            if (isGles2Gles() && pixelStoreIndexes[i] != GL_PACK_ALIGNMENT &&
                pixelStoreIndexes[i] != GL_UNPACK_ALIGNMENT) {
                continue;
            }
            dispatcher.glGetIntegerv(pixelStoreIndexes[i], &pixelStorePrev[i]);
            if (pixelStorePrev[i] != pixelStoreDesired[i]) {
                dispatcher.glPixelStorei(pixelStoreIndexes[i],
                                         pixelStoreDesired[i]);
            }
        }
        switch (m_target) {
            case GL_TEXTURE_2D:
                dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex);
                break;
            case GL_TEXTURE_CUBE_MAP:
                dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &prevTex);
                break;
            case GL_TEXTURE_3D:
                dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_3D, &prevTex);
                break;
            case GL_TEXTURE_2D_ARRAY:
                dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &prevTex);
                break;
            default:
                break;
        }

        dispatcher.glBindTexture(m_target, getGlobalName());
        // Get the number of mipmap levels.
        unsigned int numLevels = m_texStorageLevels ? m_texStorageLevels :
                m_maxMipmapLevel + 1;

        // bug: 112749908
        // Texture saving causes hundreds of megabytes of memory ballooning.
        // This could be behind nullptr dereferences in crash reports if
        // the user ran out of commit charge on Windows, which is not measured
        // in android::base::System::isUnderMemoryPressure.
        //
        // To debug this issue, avoid keeping the imgData buffers around,
        // and log the memory usage.
        //
        // bool isLowMem = android::base::System::isUnderMemoryPressure();
        bool isLowMem = true;

        auto saveTex = [this, stream, numLevels, &dispatcher, isLowMem](
                                GLenum target, bool isDepth,
                                std::unique_ptr<LevelImageData[]>& imgData) {

            if (m_isDirty) {
                imgData.reset(new LevelImageData[numLevels]);
                for (unsigned int level = 0; level < numLevels; level++) {
                    unsigned int& width = imgData.get()[level].m_width;
                    unsigned int& height = imgData.get()[level].m_height;
                    unsigned int& depth = imgData.get()[level].m_depth;
                    width = level == 0 ? m_width :
                        std::max<unsigned int>(
                            imgData.get()[level - 1].m_width / 2, 1);
                    height = level == 0 ? m_height :
                        std::max<unsigned int>(
                            imgData.get()[level - 1].m_height / 2, 1);
                    depth = level == 0 ? m_depth :
                        std::max<unsigned int>(
                            imgData.get()[level - 1].m_depth / 2, 1);

                    // ScopedMemoryProfiler::Callback memoryProfilerCallback =
                    //     [this, level, width, height, depth]
                    //     (StringView tag, StringView stage,
                    //      MemoryProfiler::MemoryUsageBytes currentResident,
                    //      MemoryProfiler::MemoryUsageBytes change) {

                    //     double megabyte = 1024.0 * 1024.0;

                    //     GL_LOG("%s %s: %f mb current. change: %f mb. texture:"
                    //            "format 0x%x type 0x%x level 0x%x dims (%u, %u, %u)\n",
                    //            c_str(tag).get(),
                    //            c_str(stage).get(),
                    //            (double)currentResident / megabyte,
                    //            (double)change / megabyte,
                    //            m_format,
                    //            m_type,
                    //            level,
                    //            width, height, depth);
                    // };

                    // ScopedMemoryProfiler mem("saveTexture", memoryProfilerCallback);

                    android::base::SmallFixedVector<unsigned char, 16>& buffer
                        = imgData.get()[level].m_data;
                    if (!isGles2Gles()) {
                        GLint glWidth;
                        GLint glHeight;
                        dispatcher.glGetTexLevelParameteriv(target, level,
                                GL_TEXTURE_WIDTH, &glWidth);
                        dispatcher.glGetTexLevelParameteriv(target, level,
                                GL_TEXTURE_HEIGHT, &glHeight);
                        width = static_cast<unsigned int>(glWidth);
                        height = static_cast<unsigned int>(glHeight);
                    }
                    if (isDepth) {
                        if (!isGles2Gles()) {
                            GLint glDepth;
                            dispatcher.glGetTexLevelParameteriv(target, level,
                                    GL_TEXTURE_DEPTH, &glDepth);
                            depth = static_cast<unsigned int>(std::max(glDepth,
                                    1));
                        }
                    } else {
                        depth = 1;
                    }
                    // Snapshot texture data
                    buffer.clear();
                    buffer.resize_noinit(
                            s_texImageSize(m_format, m_type, 1, width, height) *
                            depth);
                    if (!buffer.empty()) {
                        GLenum neededBufferFormat = m_format;
                        if (isCoreProfile()) {
                            neededBufferFormat =
                                getCoreProfileEmulatedFormat(m_format);
                        }
                        sTextureDataReader()->getTexImage(
                            m_globalName, target, level, neededBufferFormat, m_type, width, height, depth, buffer.data());
                    }
                }
            }
            for (unsigned int level = 0; level < numLevels; level++) {
                stream->putBe32(imgData.get()[level].m_width);
                stream->putBe32(imgData.get()[level].m_height);
                if (isDepth) {
                    stream->putBe32(imgData.get()[level].m_depth);
                }
                saveBuffer(stream, imgData.get()[level].m_data);
            }

            // If under memory pressure, delete this intermediate buffer.
            if (isLowMem) {
                imgData.reset();
            }
        };
        switch (m_target) {
            case GL_TEXTURE_2D:
                saveTex(GL_TEXTURE_2D, false, m_levelData[0]);
                break;
            case GL_TEXTURE_CUBE_MAP:
                saveTex(GL_TEXTURE_CUBE_MAP_POSITIVE_X, false, m_levelData[0]);
                saveTex(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, false, m_levelData[1]);
                saveTex(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, false, m_levelData[2]);
                saveTex(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, false, m_levelData[3]);
                saveTex(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, false, m_levelData[4]);
                saveTex(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, false, m_levelData[5]);
                break;
            case GL_TEXTURE_3D:
                saveTex(GL_TEXTURE_3D, true, m_levelData[0]);
                break;
            case GL_TEXTURE_2D_ARRAY:
                saveTex(GL_TEXTURE_2D_ARRAY, true, m_levelData[0]);
                break;
            default:
                break;
        }
        // Snapshot texture param
        TextureSwizzle emulatedBaseSwizzle;
        if (isCoreProfile()) {
            emulatedBaseSwizzle = getSwizzleForEmulatedFormat(m_format);
        }
        std::unordered_map<GLenum, GLint> texParam;
        auto saveParam = [this, &texParam, &dispatcher,
                emulatedBaseSwizzle](
                const GLenum* plist, size_t plistSize) {
            GLint param;
            for (size_t i = 0; i < plistSize; i++) {
                dispatcher.glGetTexParameteriv(m_target, plist[i], &param);
                if (isSwizzleParam(plist[i]) && param != GL_ZERO &&
                        param != GL_ONE) {
                    if (param == emulatedBaseSwizzle.toRed) {
                        param = GL_RED;
                    } else if (param == emulatedBaseSwizzle.toGreen) {
                        param = GL_GREEN;
                    } else if (param == emulatedBaseSwizzle.toBlue) {
                        param = GL_BLUE;
                    } else if (param == emulatedBaseSwizzle.toAlpha) {
                        param = GL_ALPHA;
                    }
                }
                texParam.emplace(plist[i], param);
            }
        };
        saveParam(kTexParam, sizeof(kTexParam) / sizeof(kTexParam[0]));
        if (dispatcher.getGLESVersion() >= GLES_3_0) {
            saveParam(kTexParamGles3,
                    sizeof(kTexParamGles3) / sizeof(kTexParamGles3[0]));
        }
        saveCollection(stream, texParam,
                [](android::base::Stream* s,
                    const std::unordered_map<GLenum, GLint>::value_type& pair) {
                    s->putBe32(pair.first);
                    s->putBe32(pair.second);
                });
        // Restore environment
        for (int i = 0; i != android::base::arraySize(pixelStoreIndexes); ++i) {
            if (isGles2Gles() && pixelStoreIndexes[i] != GL_PACK_ALIGNMENT &&
                pixelStoreIndexes[i] != GL_UNPACK_ALIGNMENT) {
                continue;
            }
            if (pixelStorePrev[i] != pixelStoreDesired[i]) {
                dispatcher.glPixelStorei(pixelStoreIndexes[i],
                                         pixelStorePrev[i]);
            }
        }
        dispatcher.glBindTexture(m_target, prevTex);

        // If we were under memory pressure, we deleted the intermediate
        // buffer, so we need to maintain the invariant that m_isDirty = false
        // textures requires that the intermediate buffer is still around.
        // Therefore, mark as dirty if we were under memory pressure.
        //
        // TODO: Don't keep those around in memory regardless of memory
        // pressure

        m_isDirty = false || isLowMem;
    } else if (m_target != 0) {
        // SaveableTexture is uninitialized iff a texture hasn't been bound,
        // which will give m_target==0
        GL_LOG("SaveableTexture::onSave: warning: texture target 0x%x not supported\n", m_target);
        fprintf(stderr, "Warning: texture target 0x%x not supported\n", m_target);
    }
}

void SaveableTexture::restore() {
    assert(m_loader);
    m_loader(this);

    if (!m_loadedFromStream.load()) {
        return;
    }

    m_globalTexObj.reset(new NamedObject(
            GenNameInfo(NamedObjectType::TEXTURE), m_globalNamespace));
    if (!m_globalTexObj) {
        GL_LOG("SaveableTexture::%s: %p: could not allocate NamedObject for texture\n", __func__, this);
        emugl::emugl_crash_reporter(
                "Fatal: could not allocate SaveableTexture m_globalTexObj\n");
    }

    m_globalName = m_globalTexObj->getGlobalName();
    if (m_target == GL_TEXTURE_2D || m_target == GL_TEXTURE_CUBE_MAP ||
        m_target == GL_TEXTURE_3D || m_target == GL_TEXTURE_2D_ARRAY) {
        // restore the texture
        GLDispatch& dispatcher = GLEScontext::dispatcher();
        // Make sure we are using the right dispatcher
        assert(dispatcher.glGetIntegerv);

        static constexpr GLenum pixelStoreIndexes[] = {
                GL_UNPACK_ROW_LENGTH,  GL_UNPACK_IMAGE_HEIGHT,
                GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS,
                GL_UNPACK_SKIP_IMAGES, GL_UNPACK_ALIGNMENT,
        };

        static constexpr GLint pixelStoreDesired[] = {0, 0, 0, 0, 0, 1};

        GLint pixelStorePrev[android::base::arraySize(pixelStoreIndexes)];
        for (int i = 0; i != android::base::arraySize(pixelStoreIndexes); ++i) {
            if (isGles2Gles() && pixelStoreIndexes[i] != GL_PACK_ALIGNMENT &&
                pixelStoreIndexes[i] != GL_UNPACK_ALIGNMENT) {
                continue;
            }
            dispatcher.glGetIntegerv(pixelStoreIndexes[i], &pixelStorePrev[i]);
            if (pixelStorePrev[i] != pixelStoreDesired[i]) {
                dispatcher.glPixelStorei(pixelStoreIndexes[i],
                                         pixelStoreDesired[i]);
            }
        }

        GLint prevTex = 0;
        switch (m_target) {
            case GL_TEXTURE_2D:
                dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex);
                break;
            case GL_TEXTURE_CUBE_MAP:
                dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &prevTex);
                break;
            case GL_TEXTURE_3D:
                dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_3D, &prevTex);
                break;
            case GL_TEXTURE_2D_ARRAY:
                dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &prevTex);
                break;
            default:
                break;
        }
        dispatcher.glBindTexture(m_target, getGlobalName());
        // Restore texture data
        dispatcher.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        // Get the number of mipmap levels.
        unsigned int numLevels = m_texStorageLevels ? m_texStorageLevels :
                m_maxMipmapLevel + 1;
        GLint resultInternalFormat = m_internalFormat;
        GLenum resultFormat = m_format;
        // Desktop OpenGL doesn't support GL_BGRA_EXT as internal format.
        if (!isGles2Gles() && m_type == GL_UNSIGNED_BYTE && m_format == GL_BGRA_EXT &&
            resultInternalFormat == GL_BGRA_EXT) {
            resultInternalFormat = GL_RGBA;
        } else if (isCoreProfile() && isCoreProfileEmulatedFormat(m_format)) {
            resultInternalFormat = getCoreProfileEmulatedInternalFormat(
                    m_format, m_type);
            resultFormat = getCoreProfileEmulatedFormat(m_format);
        }
        if (m_texStorageLevels) {
            switch (m_target) {
                case GL_TEXTURE_2D:
                case GL_TEXTURE_CUBE_MAP:
                    dispatcher.glTexStorage2D(m_target, m_texStorageLevels,
                            m_internalFormat,
                            m_levelData[0].get()[0].m_width,
                            m_levelData[0].get()[0].m_height);
                    break;
                case GL_TEXTURE_3D:
                case GL_TEXTURE_2D_ARRAY:
                    dispatcher.glTexStorage3D(m_target, m_texStorageLevels,
                            m_internalFormat,
                            m_levelData[0].get()[0].m_width,
                            m_levelData[0].get()[0].m_height,
                            m_levelData[0].get()[0].m_depth);
                    break;
            }
        }

        auto restoreTex2D =
                [this, numLevels, resultInternalFormat,
                resultFormat, &dispatcher](
                        GLenum target,
                        std::unique_ptr<LevelImageData[]>& levelData) {
                    for (unsigned int level = 0; level < numLevels; level++) {
                        const void* pixels =
                                levelData[level].m_data.empty()
                                        ? nullptr
                                        : levelData[level].m_data.data();
                        if (!level || pixels) {
                            if (m_texStorageLevels) {
                                dispatcher.glTexSubImage2D(
                                        target, level, 0, 0,
                                        levelData[level].m_width,
                                        levelData[level].m_height,
                                        resultFormat, m_type, pixels);
                            } else {
                                dispatcher.glTexImage2D(
                                        target, level, resultInternalFormat,
                                        levelData[level].m_width,
                                        levelData[level].m_height,
                                        m_border, resultFormat, m_type, pixels);
                            }
                        }
                    }
                };
        auto restoreTex3D =
                [this, numLevels, resultFormat, &dispatcher](
                        GLenum target,
                        std::unique_ptr<LevelImageData[]>& levelData) {
                    for (unsigned int level = 0; level < numLevels; level++) {
                        const void* pixels =
                                levelData[level].m_data.empty()
                                        ? nullptr
                                        : levelData[level].m_data.data();
                        if (!level || pixels) {
                            if (m_texStorageLevels) {
                                dispatcher.glTexSubImage3D(
                                        target, level, 0, 0, 0,
                                        levelData[level].m_width,
                                        levelData[level].m_height,
                                        levelData[level].m_depth,
                                        resultFormat, m_type, pixels);
                            } else {
                                dispatcher.glTexImage3D(
                                        target, level, m_internalFormat,
                                        levelData[level].m_width,
                                        levelData[level].m_height,
                                        levelData[level].m_depth, m_border,
                                        resultFormat, m_type, pixels);
                            }
                        }
                    }
                };
        switch (m_target) {
            case GL_TEXTURE_2D:
                restoreTex2D(GL_TEXTURE_2D, m_levelData[0]);
                break;
            case GL_TEXTURE_CUBE_MAP:
                restoreTex2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, m_levelData[0]);
                restoreTex2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, m_levelData[1]);
                restoreTex2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, m_levelData[2]);
                restoreTex2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, m_levelData[3]);
                restoreTex2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, m_levelData[4]);
                restoreTex2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, m_levelData[5]);
                break;
            case GL_TEXTURE_3D:
            case GL_TEXTURE_2D_ARRAY:
                restoreTex3D(m_target, m_levelData[0]);
                break;
            default:
                break;
        }
        // Restore tex param
        TextureSwizzle emulatedBaseSwizzle;
        if (isCoreProfile()) {
            emulatedBaseSwizzle = getSwizzleForEmulatedFormat(m_format);
        }
        for (const auto& param : m_texParam) {
            if (isSwizzleParam(param.first)) {
                GLenum hostEquivalentSwizzle =
                    swizzleComponentOf(emulatedBaseSwizzle, param.second);
                dispatcher.glTexParameteri(m_target, param.first,
                        hostEquivalentSwizzle);
            } else {
                dispatcher.glTexParameteri(m_target, param.first, param.second);
            }
        }
        m_texParam.clear();
        // Restore environment
        for (int i = 0; i != android::base::arraySize(pixelStoreIndexes); ++i) {
            if (isGles2Gles() && pixelStoreIndexes[i] != GL_PACK_ALIGNMENT &&
                pixelStoreIndexes[i] != GL_UNPACK_ALIGNMENT) {
                continue;
            }
            if (pixelStorePrev[i] != pixelStoreDesired[i]) {
                dispatcher.glPixelStorei(pixelStoreIndexes[i],
                                         pixelStorePrev[i]);
            }
        }
        dispatcher.glBindTexture(m_target, prevTex);
    }
}

const NamedObjectPtr& SaveableTexture::getGlobalObject() {
    touch();
    return m_globalTexObj;
}

void SaveableTexture::fillEglImage(EglImage* eglImage) {
    touch();
    eglImage->border = m_border;
    eglImage->format = m_format;
    eglImage->height = m_height;
    eglImage->globalTexObj = m_globalTexObj;
    eglImage->internalFormat = m_internalFormat;
    eglImage->type = m_type;
    eglImage->width = m_width;
    eglImage->texStorageLevels = m_texStorageLevels;
    eglImage->sync = nullptr;
    if (!eglImage->globalTexObj) {
        GL_LOG("%s: EGL image %p has no global texture object!\n",
               __func__, eglImage);
    }
}

void SaveableTexture::makeDirty() {
    m_isDirty = true;
}

bool SaveableTexture::isDirty() const {
    return m_isDirty;
}

void SaveableTexture::setTarget(GLenum target) {
    m_target = target;
}

void SaveableTexture::setMipmapLevelAtLeast(unsigned int level) {
    m_maxMipmapLevel = std::max(level, m_maxMipmapLevel);
}

unsigned int SaveableTexture::getGlobalName() {
    if (m_globalTexObj) {
        return m_globalTexObj->getGlobalName();
    }
    return m_globalName;
}
