//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// trace_fixture.cpp:
//   Common code for the ANGLE trace replays.
//

#ifdef UNSAFE_BUFFERS_BUILD
#    pragma allow_unsafe_buffers
#endif

#include "trace_fixture.h"

#include "angle_trace_gl.h"

#include <filesystem>
#include <string>

namespace
{
void UpdateResourceMap(GLuint *resourceMap, GLuint id, GLsizei readBufferOffset)
{
    GLuint returnedID;
    memcpy(&returnedID, &gReadBuffer[readBufferOffset], sizeof(GLuint));
    resourceMap[id] = returnedID;
}

void UpdateResourceMapPerContext(GLuint **resourceArray,
                                 GLuint contextId,
                                 GLuint id,
                                 GLsizei readBufferOffset)
{
    GLuint returnedID;
    memcpy(&returnedID, &gReadBuffer[readBufferOffset], sizeof(GLuint));
    resourceArray[contextId][id] = returnedID;
}

uint32_t gMaxContexts                  = 1;
angle::TraceCallbacks *gTraceCallbacks = nullptr;
std::vector<std::string> *gRequestedExtensions = nullptr;

EGLClientBuffer GetClientBuffer(EGLenum target, uintptr_t key)
{
    switch (target)
    {
        case EGL_GL_TEXTURE_2D:
        case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
        case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
        case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
        case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
        case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
        case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
        case EGL_GL_TEXTURE_3D:
        {
            uintptr_t id = static_cast<uintptr_t>(gTextureMap[key]);
            return reinterpret_cast<EGLClientBuffer>(id);
        }
        case EGL_GL_RENDERBUFFER:
        {
            uintptr_t id = static_cast<uintptr_t>(gRenderbufferMap[key]);
            return reinterpret_cast<EGLClientBuffer>(id);
        }
        default:
        {
            const auto &iData = gClientBufferMap.find(key);
            return iData != gClientBufferMap.end() ? iData->second : nullptr;
        }
    }
}

ValidateSerializedStateCallback gValidateSerializedStateCallback;
std::unordered_map<GLuint, std::vector<GLint>> gInternalUniformLocationsMap;

constexpr size_t kMaxClientArrays = 16;

std::vector<std::string> *LoadRequestedExtensions()
{
    // Read in requested extensions if the file exists
    constexpr const char *REQUESTED_EXTENSIONS_FILENAME = "angle_trace_requested_extensions";

    std::filesystem::path tempDir     = std::filesystem::temp_directory_path();
    std::filesystem::path extFilePath = tempDir / REQUESTED_EXTENSIONS_FILENAME;
    std::ifstream extFile(extFilePath);
    std::vector<std::string> *requestedExtensions = nullptr;

    if (extFile.is_open())
    {
        requestedExtensions = new std::vector<std::string>();
        std::string ext;
        while (std::getline(extFile, ext))
        {
            requestedExtensions->push_back(ext);
        }
        extFile.close();
        // Delete the file to prevent unexpected results in future runs
        std::filesystem::remove(extFilePath);
    }
    return requestedExtensions;
}
}  // namespace

GLint **gUniformLocations;
GLuint gCurrentProgram = 0;
GLuint gCurrentContext = 0;
GLuint *gCurrentProgramPerContext;

// TODO(jmadill): Hide from the traces. http://anglebug.com/42266223
BlockIndexesMap gUniformBlockIndexes;

void UpdateUniformLocation(GLuint program, const char *name, GLint location, GLint count)
{
    std::vector<GLint> &programLocations = gInternalUniformLocationsMap[program];
    if (static_cast<GLint>(programLocations.size()) < location + count)
    {
        programLocations.resize(location + count, 0);
    }
    GLuint mappedProgramID = gShaderProgramMap[program];
    for (GLint arrayIndex = 0; arrayIndex < count; ++arrayIndex)
    {
        programLocations[location + arrayIndex] =
            glGetUniformLocation(mappedProgramID, name) + arrayIndex;
    }
    gUniformLocations[program] = programLocations.data();
}

void DeleteUniformLocations(GLuint program)
{
    // No-op. We leave uniform locations around so deleted current programs can still use them.
}

void UpdateUniformBlockIndex(GLuint program, const char *name, GLuint index)
{
    gUniformBlockIndexes[program][index] = glGetUniformBlockIndex(program, name);
}

void UniformBlockBinding(GLuint program, GLuint uniformblockIndex, GLuint binding)
{
    glUniformBlockBinding(gShaderProgramMap[program],
                          gUniformBlockIndexes[gShaderProgramMap[program]][uniformblockIndex],
                          binding);
}

void UpdateCurrentProgram(GLuint program)
{
    gCurrentProgram = program;
    // gCurrentContext will be zero for legacy traces
    gCurrentProgramPerContext[0] = program;
}

void UpdateCurrentContext(GLuint context)
{
    gCurrentContext = context;
}

void UpdateCurrentProgramPerContext(GLuint program)
{
    gCurrentProgramPerContext[gCurrentContext] = program;
}

uint8_t *gBinaryData;
angle::FrameCaptureBinaryData *gFrameCaptureBinaryData;
uint8_t *gReadBuffer;
uint8_t *gClientArrays[kMaxClientArrays];
GLuint *gResourceIDBuffer;
SyncResourceMap gSyncMap;
ContextMap gContextMap;
GLuint gShareContextId;
GLuint *gBufferMap;
GLuint *gFenceNVMap;
GLuint *gFramebufferMap;
GLuint **gFramebufferMapPerContext;
GLuint *gMemoryObjectMap;
GLuint *gProgramPipelineMap;
GLuint *gQueryMap;
GLuint *gRenderbufferMap;
GLuint *gSamplerMap;
GLuint *gSemaphoreMap;
GLuint *gShaderProgramMap;
GLuint *gTextureMap;
GLuint *gTransformFeedbackMap;
GLuint *gVertexArrayMap;

// TODO(jmadill): Consolidate. http://anglebug.com/42266223
ClientBufferMap gClientBufferMap;
EGLImageMap gEGLImageMap;
SurfaceMap gSurfaceMap;

GLeglImageOES *gEGLImageMap2;
GLuint *gEGLImageMap2Resources;
EGLSurface *gSurfaceMap2;
EGLContext *gContextMap2;
GLsync *gSyncMap2;
EGLSync *gEGLSyncMap;
EGLDisplay gEGLDisplay;

std::string gBinaryDataDir = ".";

angle::ReplayResourceMode gReplayResourceMode = angle::ReplayResourceMode::Active;

template <typename T>
T *AllocateZeroedValues(size_t count)
{
    T *mem = new T[count + 1];
    memset(mem, 0, sizeof(T) * (count + 1));
    return mem;
}

GLuint *AllocateZeroedUints(size_t count)
{
    return AllocateZeroedValues<GLuint>(count);
}

void InitializeReplay5(const char *binaryDataFileName,
                       size_t maxClientArraySize,
                       size_t readBufferSize,
                       size_t resourceIDBufferSize,
                       GLuint contextId,
                       uint32_t maxBuffer,
                       uint32_t maxContext,
                       uint32_t maxFenceNV,
                       uint32_t maxFramebuffer,
                       uint32_t maxImage,
                       uint32_t maxMemoryObject,
                       uint32_t maxProgramPipeline,
                       uint32_t maxQuery,
                       uint32_t maxRenderbuffer,
                       uint32_t maxSampler,
                       uint32_t maxSemaphore,
                       uint32_t maxShaderProgram,
                       uint32_t maxSurface,
                       uint32_t maxSync,
                       uint32_t maxTexture,
                       uint32_t maxTransformFeedback,
                       uint32_t maxVertexArray,
                       GLuint maxEGLSyncID)
{
    gFrameCaptureBinaryData = gTraceCallbacks->ConfigureBinaryDataLoader(binaryDataFileName);

    InitializeReplay4(binaryDataFileName, maxClientArraySize, readBufferSize, resourceIDBufferSize,
                      contextId, maxBuffer, maxContext, maxFenceNV, maxFramebuffer, maxImage,
                      maxMemoryObject, maxProgramPipeline, maxQuery, maxRenderbuffer, maxSampler,
                      maxSemaphore, maxShaderProgram, maxSurface, maxSync, maxTexture,
                      maxTransformFeedback, maxVertexArray, maxEGLSyncID);
}

void InitializeReplay4(const char *binaryDataFileName,
                       size_t maxClientArraySize,
                       size_t readBufferSize,
                       size_t resourceIDBufferSize,
                       GLuint contextId,
                       uint32_t maxBuffer,
                       uint32_t maxContext,
                       uint32_t maxFenceNV,
                       uint32_t maxFramebuffer,
                       uint32_t maxImage,
                       uint32_t maxMemoryObject,
                       uint32_t maxProgramPipeline,
                       uint32_t maxQuery,
                       uint32_t maxRenderbuffer,
                       uint32_t maxSampler,
                       uint32_t maxSemaphore,
                       uint32_t maxShaderProgram,
                       uint32_t maxSurface,
                       uint32_t maxSync,
                       uint32_t maxTexture,
                       uint32_t maxTransformFeedback,
                       uint32_t maxVertexArray,
                       GLuint maxEGLSyncID)
{
    InitializeReplay3(binaryDataFileName, maxClientArraySize, readBufferSize, resourceIDBufferSize,
                      contextId, maxBuffer, maxContext, maxFenceNV, maxFramebuffer, maxImage,
                      maxMemoryObject, maxProgramPipeline, maxQuery, maxRenderbuffer, maxSampler,
                      maxSemaphore, maxShaderProgram, maxSurface, maxSync, maxTexture,
                      maxTransformFeedback, maxVertexArray);
    gEGLSyncMap = AllocateZeroedValues<EGLSync>(maxEGLSyncID);
    gEGLDisplay = eglGetCurrentDisplay();
}

void InitializeReplay3(const char *binaryDataFileName,
                       size_t maxClientArraySize,
                       size_t readBufferSize,
                       size_t resourceIDBufferSize,
                       GLuint contextId,
                       uint32_t maxBuffer,
                       uint32_t maxContext,
                       uint32_t maxFenceNV,
                       uint32_t maxFramebuffer,
                       uint32_t maxImage,
                       uint32_t maxMemoryObject,
                       uint32_t maxProgramPipeline,
                       uint32_t maxQuery,
                       uint32_t maxRenderbuffer,
                       uint32_t maxSampler,
                       uint32_t maxSemaphore,
                       uint32_t maxShaderProgram,
                       uint32_t maxSurface,
                       uint32_t maxSync,
                       uint32_t maxTexture,
                       uint32_t maxTransformFeedback,
                       uint32_t maxVertexArray)
{
    InitializeReplay2(binaryDataFileName, maxClientArraySize, readBufferSize, contextId, maxBuffer,
                      maxContext, maxFenceNV, maxFramebuffer, maxImage, maxMemoryObject,
                      maxProgramPipeline, maxQuery, maxRenderbuffer, maxSampler, maxSemaphore,
                      maxShaderProgram, maxSurface, maxTexture, maxTransformFeedback,
                      maxVertexArray);

    gSyncMap2         = AllocateZeroedValues<GLsync>(maxSync);
    gResourceIDBuffer = AllocateZeroedUints(resourceIDBufferSize);
}

void InitializeReplay2(const char *binaryDataFileName,
                       size_t maxClientArraySize,
                       size_t readBufferSize,
                       GLuint contextId,
                       uint32_t maxBuffer,
                       uint32_t maxContext,
                       uint32_t maxFenceNV,
                       uint32_t maxFramebuffer,
                       uint32_t maxImage,
                       uint32_t maxMemoryObject,
                       uint32_t maxProgramPipeline,
                       uint32_t maxQuery,
                       uint32_t maxRenderbuffer,
                       uint32_t maxSampler,
                       uint32_t maxSemaphore,
                       uint32_t maxShaderProgram,
                       uint32_t maxSurface,
                       uint32_t maxTexture,
                       uint32_t maxTransformFeedback,
                       uint32_t maxVertexArray)
{
    gMaxContexts = maxContext + 1;
    InitializeReplay(binaryDataFileName, maxClientArraySize, readBufferSize, maxBuffer, maxFenceNV,
                     maxFramebuffer, maxMemoryObject, maxProgramPipeline, maxQuery, maxRenderbuffer,
                     maxSampler, maxSemaphore, maxShaderProgram, maxTexture, maxTransformFeedback,
                     maxVertexArray);

    gContextMap2           = AllocateZeroedValues<EGLContext>(maxContext);
    gEGLImageMap2          = AllocateZeroedValues<EGLImage>(maxImage);
    gEGLImageMap2Resources = AllocateZeroedValues<GLuint>(maxImage);
    gSurfaceMap2           = AllocateZeroedValues<EGLSurface>(maxSurface);

    gContextMap2[0]         = EGL_NO_CONTEXT;
    gShareContextId         = contextId;
    gContextMap2[contextId] = eglGetCurrentContext();
}

void InitializeReplay(const char *binaryDataFileName,
                      size_t maxClientArraySize,
                      size_t readBufferSize,
                      uint32_t maxBuffer,
                      uint32_t maxFenceNV,
                      uint32_t maxFramebuffer,
                      uint32_t maxMemoryObject,
                      uint32_t maxProgramPipeline,
                      uint32_t maxQuery,
                      uint32_t maxRenderbuffer,
                      uint32_t maxSampler,
                      uint32_t maxSemaphore,
                      uint32_t maxShaderProgram,
                      uint32_t maxTexture,
                      uint32_t maxTransformFeedback,
                      uint32_t maxVertexArray)
{
    if (!gFrameCaptureBinaryData)
    {
        gBinaryData = gTraceCallbacks->LoadBinaryData(binaryDataFileName);
    }

    for (uint8_t *&clientArray : gClientArrays)
    {
        clientArray = new uint8_t[maxClientArraySize];
    }

    gReadBuffer = new uint8_t[readBufferSize];

    gBufferMap            = AllocateZeroedUints(maxBuffer);
    gFenceNVMap           = AllocateZeroedUints(maxFenceNV);
    gFramebufferMap       = AllocateZeroedUints(maxFramebuffer);
    gMemoryObjectMap      = AllocateZeroedUints(maxMemoryObject);
    gProgramPipelineMap   = AllocateZeroedUints(maxProgramPipeline);
    gQueryMap             = AllocateZeroedUints(maxQuery);
    gRenderbufferMap      = AllocateZeroedUints(maxRenderbuffer);
    gSamplerMap           = AllocateZeroedUints(maxSampler);
    gSemaphoreMap         = AllocateZeroedUints(maxSemaphore);
    gShaderProgramMap     = AllocateZeroedUints(maxShaderProgram);
    gTextureMap           = AllocateZeroedUints(maxTexture);
    gTransformFeedbackMap = AllocateZeroedUints(maxTransformFeedback);
    gVertexArrayMap       = AllocateZeroedUints(maxVertexArray);

    gUniformLocations = new GLint *[maxShaderProgram + 1];
    memset(gUniformLocations, 0, sizeof(GLint *) * (maxShaderProgram + 1));

    gContextMap[0] = EGL_NO_CONTEXT;

    gCurrentProgramPerContext = new GLuint[gMaxContexts];
    gFramebufferMapPerContext = new GLuint *[gMaxContexts];
    memset(gFramebufferMapPerContext, 0, sizeof(GLuint *) * (gMaxContexts));
    for (uint8_t i = 0; i < gMaxContexts; i++)
    {
        gFramebufferMapPerContext[i] = AllocateZeroedValues<GLuint>(maxFramebuffer);
    }

    // Pull in requested extension list from file created by ANGLEPerfTest
    gRequestedExtensions = LoadRequestedExtensions();
}

void FinishReplay()
{
    delete[] gReadBuffer;
    for (uint8_t *&clientArray : gClientArrays)
    {
        delete[] clientArray;
    }
    delete[] gResourceIDBuffer;
    delete[] gBufferMap;
    delete[] gContextMap2;
    delete[] gEGLImageMap2;
    delete[] gEGLSyncMap;
    delete[] gRenderbufferMap;
    delete[] gTextureMap;
    delete[] gFramebufferMap;
    delete[] gShaderProgramMap;
    delete[] gFenceNVMap;
    delete[] gMemoryObjectMap;
    delete[] gProgramPipelineMap;
    delete[] gQueryMap;
    delete[] gSamplerMap;
    delete[] gSemaphoreMap;
    delete[] gSurfaceMap2;
    delete[] gSyncMap2;
    delete[] gTransformFeedbackMap;
    delete[] gVertexArrayMap;

    delete gRequestedExtensions;

    for (uint8_t i = 0; i < gMaxContexts; i++)
    {
        delete[] gFramebufferMapPerContext[i];
    }
    delete[] gFramebufferMapPerContext;
    delete[] gCurrentProgramPerContext;

    if (gFrameCaptureBinaryData)
    {
        gFrameCaptureBinaryData->closeBinaryDataLoader();
        delete gFrameCaptureBinaryData;
        gFrameCaptureBinaryData = nullptr;
    }
}

void SetValidateSerializedStateCallback(ValidateSerializedStateCallback callback)
{
    gValidateSerializedStateCallback = callback;
}

angle::TraceInfo gTraceInfo;
std::string gTraceGzPath;

struct TraceFunctionsImpl : angle::TraceFunctions
{
    void SetupReplay() override { ::SetupReplay(); }

    void ReplayFrame(uint32_t frameIndex) override { ::ReplayFrame(frameIndex); }

    void ResetReplay() override { ::ResetReplay(); }

    void SetupFirstFrame() override {}

    void FinishReplay() override { ::FinishReplay(); }

    void SetBinaryDataDir(const char *dataDir) override { gBinaryDataDir = dataDir; }

    void SetReplayResourceMode(const angle::ReplayResourceMode resourceMode) override
    {
        gReplayResourceMode = resourceMode;
    }

    void SetTraceInfo(const angle::TraceInfo &traceInfo) override { gTraceInfo = traceInfo; }

    void SetTraceGzPath(const std::string &traceGzPath) override { gTraceGzPath = traceGzPath; }
};

TraceFunctionsImpl gTraceFunctionsImpl;

void SetupEntryPoints(angle::TraceCallbacks *traceCallbacks, angle::TraceFunctions **traceFunctions)
{
    gTraceCallbacks = traceCallbacks;
    *traceFunctions = &gTraceFunctionsImpl;
}

void UpdateClientArrayPointer(int arrayIndex, const void *data, uint64_t size)
{
    memcpy(gClientArrays[arrayIndex], data, static_cast<size_t>(size));
}

void UpdateClientArrayPointerWithOffset(int arrayIndex,
                                        const void *data,
                                        uint64_t size,
                                        uint64_t offset)
{
    uintptr_t dest =
        reinterpret_cast<uintptr_t>(gClientArrays[arrayIndex]) + static_cast<size_t>(offset);
    memcpy(reinterpret_cast<uint8_t *>(dest), data, static_cast<size_t>(size));
}

BufferHandleMap gMappedBufferData;

void UpdateClientBufferData(GLuint bufferID, const void *source, GLsizei size)
{
    memcpy(gMappedBufferData[gBufferMap[bufferID]], source, size);
}

void UpdateClientBufferDataWithOffset(GLuint bufferID,
                                      const void *source,
                                      GLsizei size,
                                      GLsizei offset)
{
    uintptr_t dest = reinterpret_cast<uintptr_t>(gMappedBufferData[gBufferMap[bufferID]]) + offset;
    memcpy(reinterpret_cast<void *>(dest), source, size);
}

void UpdateResourceIDBuffer(int resourceIndex, GLuint id)
{
    gResourceIDBuffer[resourceIndex] = id;
}

void UpdateBufferID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gBufferMap, id, readBufferOffset);
}

void UpdateFenceNVID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gFenceNVMap, id, readBufferOffset);
}

void UpdateFramebufferID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gFramebufferMap, id, readBufferOffset);
}

void UpdateFramebufferID2(GLuint contextId, GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMapPerContext(gFramebufferMapPerContext, contextId, id, readBufferOffset);
}

void UpdateMemoryObjectID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gMemoryObjectMap, id, readBufferOffset);
}

void UpdateProgramPipelineID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gProgramPipelineMap, id, readBufferOffset);
}

void UpdateQueryID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gQueryMap, id, readBufferOffset);
}

void UpdateRenderbufferID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gRenderbufferMap, id, readBufferOffset);
}

void UpdateSamplerID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gSamplerMap, id, readBufferOffset);
}

void UpdateSemaphoreID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gSemaphoreMap, id, readBufferOffset);
}

void UpdateShaderProgramID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gShaderProgramMap, id, readBufferOffset);
}

void UpdateTextureID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gTextureMap, id, readBufferOffset);
}

void UpdateTransformFeedbackID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gTransformFeedbackMap, id, readBufferOffset);
}

void UpdateVertexArrayID(GLuint id, GLsizei readBufferOffset)
{
    UpdateResourceMap(gVertexArrayMap, id, readBufferOffset);
}

void SetFramebufferID(GLuint id)
{
    glGenFramebuffers(1, &gFramebufferMap[id]);
}

void SetFramebufferID2(GLuint contextID, GLuint id)
{
    glGenFramebuffers(1, &gFramebufferMapPerContext[contextID][id]);
}

void SetBufferID(GLuint id)
{
    glGenBuffers(1, &gBufferMap[id]);
}

void SetRenderbufferID(GLuint id)
{
    glGenRenderbuffers(1, &gRenderbufferMap[id]);
}

void SetTextureID(GLuint id)
{
    glGenTextures(1, &gTextureMap[id]);
}

void ValidateSerializedState(const char *serializedState, const char *fileName, uint32_t line)
{
    if (gValidateSerializedStateCallback)
    {
        gValidateSerializedStateCallback(serializedState, fileName, line);
    }
}

void MapBufferRange(GLenum target,
                    GLintptr offset,
                    GLsizeiptr length,
                    GLbitfield access,
                    GLuint buffer)
{
    gMappedBufferData[gBufferMap[buffer]] = glMapBufferRange(target, offset, length, access);
}

void MapBufferRangeEXT(GLenum target,
                       GLintptr offset,
                       GLsizeiptr length,
                       GLbitfield access,
                       GLuint buffer)
{
    gMappedBufferData[gBufferMap[buffer]] = glMapBufferRangeEXT(target, offset, length, access);
}

void MapBufferOES(GLenum target, GLbitfield access, GLuint buffer)
{
    gMappedBufferData[gBufferMap[buffer]] = glMapBufferOES(target, access);
}

void CreateShader(GLenum shaderType, GLuint shaderProgram)
{
    gShaderProgramMap[shaderProgram] = glCreateShader(shaderType);
}

void CreateProgram(GLuint shaderProgram)
{
    gShaderProgramMap[shaderProgram] = glCreateProgram();
}

void CreateShaderProgramv(GLenum type,
                          GLsizei count,
                          const GLchar *const *strings,
                          GLuint shaderProgram)
{
    gShaderProgramMap[shaderProgram] = glCreateShaderProgramv(type, count, strings);
}

void FenceSync(GLenum condition, GLbitfield flags, uintptr_t fenceSync)
{
    gSyncMap[fenceSync] = glFenceSync(condition, flags);
}

void FenceSync2(GLenum condition, GLbitfield flags, uintptr_t fenceSync)
{
    gSyncMap2[fenceSync] = glFenceSync(condition, flags);
}

GLuint CreateEGLImageResource(GLsizei width, GLsizei height)
{
    GLint previousTexId;
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousTexId);
    GLint previousAlignment;
    glGetIntegerv(GL_UNPACK_ALIGNMENT, &previousAlignment);

    // Create a texture and fill with a placeholder green value
    GLuint stagingTexId;
    glGenTextures(1, &stagingTexId);
    glBindTexture(GL_TEXTURE_2D, stagingTexId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    std::vector<GLubyte> pixels;
    pixels.reserve(width * height * 4);
    for (int i = 0; i < width * height; i++)
    {
        pixels.push_back(61);
        pixels.push_back(220);
        pixels.push_back(132);
        pixels.push_back(255);
    }
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 pixels.data());

    glPixelStorei(GL_UNPACK_ALIGNMENT, previousAlignment);
    glBindTexture(GL_TEXTURE_2D, previousTexId);
    return stagingTexId;
}

void UpdateEGLImageData(GLuint imageID, GLsizei width, GLsizei height, const void *imageData)
{
    GLint restoreTexture;
    GLint restoreAlignment;
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &restoreTexture);
    glGetIntegerv(GL_UNPACK_ALIGNMENT, &restoreAlignment);

    if (gEGLImageMap2[imageID] != nullptr)
    {
        // EGLImage already exists, update backing texture if there is imageData
        GLuint textureID = gEGLImageMap2Resources[imageID];
        if ((textureID != 0) && (imageData != nullptr))
        {
            glBindTexture(GL_TEXTURE_2D, textureID);
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
                            imageData);
        }
    }
    else
    {
        // First eglImage binding, create eglImage and a staging texture for it
        GLuint stagingTexture;
        if (imageData != nullptr)
        {
            glGenTextures(1, &stagingTexture);
            glBindTexture(GL_TEXTURE_2D, stagingTexture);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         imageData);
        }
        else
        {
            // Fallback is to use a static, solid green placeholder texture
            stagingTexture = CreateEGLImageResource(width, height);
        }

        gEGLImageMap2Resources[imageID] = stagingTexture;
        gEGLImageMap2[imageID] =
            eglCreateImageKHR(gEGLDisplay, eglGetCurrentContext(), EGL_GL_TEXTURE_2D,
                              reinterpret_cast<EGLClientBuffer>(stagingTexture), nullptr);
    }

    glPixelStorei(GL_UNPACK_ALIGNMENT, restoreAlignment);
    glBindTexture(GL_TEXTURE_2D, restoreTexture);
}

void CreateEGLImage(EGLDisplay dpy,
                    EGLContext ctx,
                    EGLenum target,
                    uintptr_t buffer,
                    const EGLAttrib *attrib_list,
                    GLsizei width,
                    GLsizei height,
                    GLuint imageID)
{
    if (target == EGL_NATIVE_BUFFER_ANDROID || buffer == 0)
    {
        // If image was created from an AHB or the backing resource wasn't captured, create a new
        // texture to use instead, which will be filled in an UpdateEGLImageData call we insert just
        // before the bind call. Substituting a regular texture for the AHB allows the trace to run
        // on non-Android systems.
        gEGLImageMap2Resources[imageID] = CreateEGLImageResource(width, height);
        gEGLImageMap2[imageID]          = eglCreateImage(
            dpy, eglGetCurrentContext(), EGL_GL_TEXTURE_2D,
            reinterpret_cast<EGLClientBuffer>(gEGLImageMap2Resources[imageID]), attrib_list);
    }
    else
    {
        EGLClientBuffer clientBuffer = GetClientBuffer(target, buffer);
        gEGLImageMap2[imageID]       = eglCreateImage(dpy, ctx, target, clientBuffer, attrib_list);
    }
}

void CreateEGLImageKHR(EGLDisplay dpy,
                       EGLContext ctx,
                       EGLenum target,
                       uintptr_t buffer,
                       const EGLint *attrib_list,
                       GLsizei width,
                       GLsizei height,
                       GLuint imageID)
{
    // This function is nearly identical to CreateEGLImage() above, but remains separated
    // because of a unique function signature. See comments above.
    if (target == EGL_NATIVE_BUFFER_ANDROID || buffer == 0)
    {
        gEGLImageMap2Resources[imageID] = CreateEGLImageResource(width, height);
        gEGLImageMap2[imageID]          = eglCreateImageKHR(
            dpy, eglGetCurrentContext(), EGL_GL_TEXTURE_2D,
            reinterpret_cast<EGLClientBuffer>(gEGLImageMap2Resources[imageID]), attrib_list);
    }
    else
    {
        EGLClientBuffer clientBuffer = GetClientBuffer(target, buffer);
        gEGLImageMap2[imageID] = eglCreateImageKHR(dpy, ctx, target, clientBuffer, attrib_list);
    }
}

void DestroyEGLImage(EGLDisplay dpy, EGLImage image, GLuint imageID)
{
    if (gEGLImageMap2Resources[imageID])
    {
        glDeleteTextures(1, &gEGLImageMap2Resources[imageID]);
        gEGLImageMap2Resources[imageID] = 0;
    }
    eglDestroyImage(dpy, image);
}

void DestroyEGLImageKHR(EGLDisplay dpy, EGLImageKHR image, GLuint imageID)
{
    if (gEGLImageMap2Resources[imageID])
    {
        glDeleteTextures(1, &gEGLImageMap2Resources[imageID]);
        gEGLImageMap2Resources[imageID] = 0;
    }
    eglDestroyImageKHR(dpy, image);
}

void CreateEGLSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list, GLuint syncID)
{
    gEGLSyncMap[syncID] = eglCreateSyncKHR(dpy, type, attrib_list);
}

void CreateEGLSync(EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list, GLuint syncID)
{
    gEGLSyncMap[syncID] = eglCreateSync(dpy, type, attrib_list);
}

void CreatePbufferSurface(EGLDisplay dpy,
                          EGLConfig config,
                          const EGLint *attrib_list,
                          GLuint surfaceID)
{
    gSurfaceMap2[surfaceID] = eglCreatePbufferSurface(dpy, config, attrib_list);
}

void CreateNativeClientBufferANDROID(const EGLint *attrib_list, uintptr_t clientBuffer)
{
    gClientBufferMap[clientBuffer] = eglCreateNativeClientBufferANDROID(attrib_list);
}

// The test harness can set specific extensions, but only for the main context
// so enable the same set of extensions for each side-context.
void EnableSideContextExtensions(GLuint contextID)
{
    if (gRequestedExtensions)
    {
        // Change to newly-created side-context
        eglMakeCurrent(NULL, NULL, NULL, gContextMap2[contextID]);
        for (auto &ext : *gRequestedExtensions)
        {
            glRequestExtensionANGLE(ext.c_str());
        }
        // Switch back to main context
        eglMakeCurrent(NULL, NULL, NULL, gContextMap2[gShareContextId]);
    }
}

void CreateContext(GLuint contextID)
{
    EGLContext shareContext = gContextMap2[gShareContextId];
    EGLContext context      = eglCreateContext(nullptr, nullptr, shareContext, nullptr);
    gContextMap2[contextID] = context;

    // Extensions set using --request-extensions must be propagated to side-contexts
    if (gRequestedExtensions)
    {
        EnableSideContextExtensions(contextID);
    }
}

void SetCurrentContextID(GLuint id)
{
    gContextMap2[id] = eglGetCurrentContext();
}

const uint8_t *GetBinaryData(const size_t offset)
{
    return gFrameCaptureBinaryData->getData(offset);
}

void InitializeBinaryDataLoader()
{
    gFrameCaptureBinaryData->initializeBinaryDataLoader();
}

ANGLE_REPLAY_EXPORT PFNEGLCREATEIMAGEPROC r_eglCreateImage;
ANGLE_REPLAY_EXPORT PFNEGLCREATEIMAGEKHRPROC r_eglCreateImageKHR;
ANGLE_REPLAY_EXPORT PFNEGLDESTROYIMAGEPROC r_eglDestroyImage;
ANGLE_REPLAY_EXPORT PFNEGLDESTROYIMAGEKHRPROC r_eglDestroyImageKHR;
