//
// Copyright 2020 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.
//
// frame_capture_utils.cpp:
//   ANGLE frame capture util implementation.
//

#include "libANGLE/frame_capture_utils.h"

#include <vector>

#include "common/Color.h"
#include "common/MemoryBuffer.h"
#include "common/angleutils.h"
#include "libANGLE/BinaryStream.h"
#include "libANGLE/Buffer.h"
#include "libANGLE/Caps.h"
#include "libANGLE/Context.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/Query.h"
#include "libANGLE/RefCountObject.h"
#include "libANGLE/ResourceMap.h"
#include "libANGLE/Sampler.h"
#include "libANGLE/State.h"
#include "libANGLE/TransformFeedback.h"
#include "libANGLE/VertexAttribute.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/FramebufferImpl.h"
#include "libANGLE/renderer/RenderbufferImpl.h"

// Note: when diagnosing serialization comparison failures, you can disable the unused function
// compiler warning to allow bisecting the comparison function. One first check is to disable
// Framebuffer Attachment pixel comparison which includes the pixel contents of the default FBO.
// ANGLE_DISABLE_UNUSED_FUNCTION_WARNING

namespace angle
{

namespace
{

void SerializeColorF(gl::BinaryOutputStream *bos, const ColorF &color)
{
    bos->writeFloat(color.red);
    bos->writeFloat(color.green);
    bos->writeFloat(color.blue);
    bos->writeFloat(color.alpha);
}

void SerializeColorI(gl::BinaryOutputStream *bos, const ColorI &color)
{
    bos->writeInt(color.red);
    bos->writeInt(color.green);
    bos->writeInt(color.blue);
    bos->writeInt(color.alpha);
}

void SerializeColorUI(gl::BinaryOutputStream *bos, const ColorUI &color)
{
    bos->writeInt(color.red);
    bos->writeInt(color.green);
    bos->writeInt(color.blue);
    bos->writeInt(color.alpha);
}

template <class ObjectType>
void SerializeOffsetBindingPointerVector(
    gl::BinaryOutputStream *bos,
    const std::vector<gl::OffsetBindingPointer<ObjectType>> &offsetBindingPointerVector)
{
    for (size_t i = 0; i < offsetBindingPointerVector.size(); i++)
    {
        bos->writeInt(offsetBindingPointerVector[i].id().value);
        bos->writeInt(offsetBindingPointerVector[i].getOffset());
        bos->writeInt(offsetBindingPointerVector[i].getSize());
    }
}

template <class ObjectType>
void SerializeBindingPointerVector(
    gl::BinaryOutputStream *bos,
    const std::vector<gl::BindingPointer<ObjectType>> &bindingPointerVector)
{
    for (size_t i = 0; i < bindingPointerVector.size(); i++)
    {
        bos->writeInt(bindingPointerVector[i].id().value);
    }
}

template <class T>
void SerializeRange(gl::BinaryOutputStream *bos, const gl::Range<T> &range)
{
    bos->writeInt(range.low());
    bos->writeInt(range.high());
}

bool IsValidColorAttachmentBinding(GLenum binding, size_t colorAttachmentsCount)
{
    return binding == GL_BACK || (binding >= GL_COLOR_ATTACHMENT0 &&
                                  (binding - GL_COLOR_ATTACHMENT0) < colorAttachmentsCount);
}

Result ReadPixelsFromAttachment(const gl::Context *context,
                                gl::Framebuffer *framebuffer,
                                const gl::FramebufferAttachment &framebufferAttachment,
                                ScratchBuffer *scratchBuffer,
                                MemoryBuffer **pixels)
{
    gl::Extents extents       = framebufferAttachment.getSize();
    GLenum binding            = framebufferAttachment.getBinding();
    gl::InternalFormat format = *framebufferAttachment.getFormat().info;
    if (IsValidColorAttachmentBinding(binding,
                                      framebuffer->getState().getColorAttachments().size()))
    {
        format = framebuffer->getImplementation()->getImplementationColorReadFormat(context);
    }
    ANGLE_CHECK_GL_ALLOC(const_cast<gl::Context *>(context),
                         scratchBuffer->getInitialized(
                             format.pixelBytes * extents.width * extents.height, pixels, 0));
    ANGLE_TRY(framebuffer->readPixels(context, gl::Rectangle{0, 0, extents.width, extents.height},
                                      format.format, format.type, gl::PixelPackState{}, nullptr,
                                      (*pixels)->data()));
    return Result::Continue;
}
void SerializeImageIndex(gl::BinaryOutputStream *bos, const gl::ImageIndex &imageIndex)
{
    bos->writeEnum(imageIndex.getType());
    bos->writeInt(imageIndex.getLevelIndex());
    bos->writeInt(imageIndex.getLayerIndex());
    bos->writeInt(imageIndex.getLayerCount());
}

Result SerializeFramebufferAttachment(const gl::Context *context,
                                      gl::BinaryOutputStream *bos,
                                      ScratchBuffer *scratchBuffer,
                                      gl::Framebuffer *framebuffer,
                                      const gl::FramebufferAttachment &framebufferAttachment)
{
    if (framebufferAttachment.type() == GL_TEXTURE ||
        framebufferAttachment.type() == GL_RENDERBUFFER)
    {
        bos->writeInt(framebufferAttachment.id());
    }
    bos->writeInt(framebufferAttachment.type());
    // serialize target variable
    bos->writeInt(framebufferAttachment.getBinding());
    if (framebufferAttachment.type() == GL_TEXTURE)
    {
        SerializeImageIndex(bos, framebufferAttachment.getTextureImageIndex());
    }
    bos->writeInt(framebufferAttachment.getNumViews());
    bos->writeBool(framebufferAttachment.isMultiview());
    bos->writeInt(framebufferAttachment.getBaseViewIndex());
    bos->writeInt(framebufferAttachment.getRenderToTextureSamples());

    if (framebufferAttachment.type() != GL_TEXTURE &&
        framebufferAttachment.type() != GL_RENDERBUFFER)
    {
        GLenum prevReadBufferState = framebuffer->getReadBufferState();
        GLenum binding             = framebufferAttachment.getBinding();
        if (IsValidColorAttachmentBinding(binding,
                                          framebuffer->getState().getColorAttachments().size()))
        {
            framebuffer->setReadBuffer(framebufferAttachment.getBinding());
            ANGLE_TRY(framebuffer->syncState(context, GL_FRAMEBUFFER, gl::Command::Other));
        }
        MemoryBuffer *pixelsPtr = nullptr;
        ANGLE_TRY(ReadPixelsFromAttachment(context, framebuffer, framebufferAttachment,
                                           scratchBuffer, &pixelsPtr));
        bos->writeBytes(pixelsPtr->data(), pixelsPtr->size());
        // Reset framebuffer state
        framebuffer->setReadBuffer(prevReadBufferState);
    }
    return Result::Continue;
}

Result SerializeFramebufferState(const gl::Context *context,
                                 gl::BinaryOutputStream *bos,
                                 ScratchBuffer *scratchBuffer,
                                 gl::Framebuffer *framebuffer,
                                 const gl::FramebufferState &framebufferState)
{
    bos->writeInt(framebufferState.id().value);
    bos->writeString(framebufferState.getLabel());
    bos->writeIntVector(framebufferState.getDrawBufferStates());
    bos->writeInt(framebufferState.getReadBufferState());
    bos->writeInt(framebufferState.getDefaultWidth());
    bos->writeInt(framebufferState.getDefaultHeight());
    bos->writeInt(framebufferState.getDefaultSamples());
    bos->writeBool(framebufferState.getDefaultFixedSampleLocations());
    bos->writeInt(framebufferState.getDefaultLayers());

    const std::vector<gl::FramebufferAttachment> &colorAttachments =
        framebufferState.getColorAttachments();
    for (const gl::FramebufferAttachment &colorAttachment : colorAttachments)
    {
        if (colorAttachment.isAttached())
        {
            ANGLE_TRY(SerializeFramebufferAttachment(context, bos, scratchBuffer, framebuffer,
                                                     colorAttachment));
        }
    }
    if (framebuffer->getDepthStencilAttachment())
    {
        ANGLE_TRY(SerializeFramebufferAttachment(context, bos, scratchBuffer, framebuffer,
                                                 *framebuffer->getDepthStencilAttachment()));
    }
    else
    {
        if (framebuffer->getDepthAttachment())
        {
            ANGLE_TRY(SerializeFramebufferAttachment(context, bos, scratchBuffer, framebuffer,
                                                     *framebuffer->getDepthAttachment()));
        }
        if (framebuffer->getStencilAttachment())
        {
            ANGLE_TRY(SerializeFramebufferAttachment(context, bos, scratchBuffer, framebuffer,
                                                     *framebuffer->getStencilAttachment()));
        }
    }
    return Result::Continue;
}

Result SerializeFramebuffer(const gl::Context *context,
                            gl::BinaryOutputStream *bos,
                            ScratchBuffer *scratchBuffer,
                            gl::Framebuffer *framebuffer)
{
    return SerializeFramebufferState(context, bos, scratchBuffer, framebuffer,
                                     framebuffer->getState());
}

void SerializeRasterizerState(gl::BinaryOutputStream *bos,
                              const gl::RasterizerState &rasterizerState)
{
    bos->writeBool(rasterizerState.cullFace);
    bos->writeEnum(rasterizerState.cullMode);
    bos->writeInt(rasterizerState.frontFace);
    bos->writeBool(rasterizerState.polygonOffsetFill);
    bos->writeFloat(rasterizerState.polygonOffsetFactor);
    bos->writeFloat(rasterizerState.polygonOffsetUnits);
    bos->writeBool(rasterizerState.pointDrawMode);
    bos->writeBool(rasterizerState.multiSample);
    bos->writeBool(rasterizerState.rasterizerDiscard);
    bos->writeBool(rasterizerState.dither);
}

void SerializeRectangle(gl::BinaryOutputStream *bos, const gl::Rectangle &rectangle)
{
    bos->writeInt(rectangle.x);
    bos->writeInt(rectangle.y);
    bos->writeInt(rectangle.width);
    bos->writeInt(rectangle.height);
}

void SerializeBlendStateExt(gl::BinaryOutputStream *bos, const gl::BlendStateExt &blendStateExt)
{
    bos->writeInt(blendStateExt.mEnabledMask.bits());
    bos->writeInt(blendStateExt.mDstColor);
    bos->writeInt(blendStateExt.mDstAlpha);
    bos->writeInt(blendStateExt.mSrcColor);
    bos->writeInt(blendStateExt.mSrcAlpha);
    bos->writeInt(blendStateExt.mEquationColor);
    bos->writeInt(blendStateExt.mEquationAlpha);
    bos->writeInt(blendStateExt.mColorMask);
}

void SerializeDepthStencilState(gl::BinaryOutputStream *bos,
                                const gl::DepthStencilState &depthStencilState)
{
    bos->writeBool(depthStencilState.depthTest);
    bos->writeInt(depthStencilState.depthFunc);
    bos->writeBool(depthStencilState.depthMask);
    bos->writeBool(depthStencilState.stencilTest);
    bos->writeInt(depthStencilState.stencilFunc);
    bos->writeInt(depthStencilState.stencilMask);
    bos->writeInt(depthStencilState.stencilFail);
    bos->writeInt(depthStencilState.stencilPassDepthFail);
    bos->writeInt(depthStencilState.stencilPassDepthPass);
    bos->writeInt(depthStencilState.stencilWritemask);
    bos->writeInt(depthStencilState.stencilBackFunc);
    bos->writeInt(depthStencilState.stencilBackMask);
    bos->writeInt(depthStencilState.stencilBackFail);
    bos->writeInt(depthStencilState.stencilBackPassDepthFail);
    bos->writeInt(depthStencilState.stencilBackPassDepthPass);
    bos->writeInt(depthStencilState.stencilBackWritemask);
}

void SerializeVertexAttribCurrentValueData(
    gl::BinaryOutputStream *bos,
    const gl::VertexAttribCurrentValueData &vertexAttribCurrentValueData)
{
    ASSERT(vertexAttribCurrentValueData.Type == gl::VertexAttribType::Float ||
           vertexAttribCurrentValueData.Type == gl::VertexAttribType::Int ||
           vertexAttribCurrentValueData.Type == gl::VertexAttribType::UnsignedInt);
    if (vertexAttribCurrentValueData.Type == gl::VertexAttribType::Float)
    {
        bos->writeFloat(vertexAttribCurrentValueData.Values.FloatValues[0]);
        bos->writeFloat(vertexAttribCurrentValueData.Values.FloatValues[1]);
        bos->writeFloat(vertexAttribCurrentValueData.Values.FloatValues[2]);
        bos->writeFloat(vertexAttribCurrentValueData.Values.FloatValues[3]);
    }
    else if (vertexAttribCurrentValueData.Type == gl::VertexAttribType::Int)
    {
        bos->writeInt(vertexAttribCurrentValueData.Values.IntValues[0]);
        bos->writeInt(vertexAttribCurrentValueData.Values.IntValues[1]);
        bos->writeInt(vertexAttribCurrentValueData.Values.IntValues[2]);
        bos->writeInt(vertexAttribCurrentValueData.Values.IntValues[3]);
    }
    else
    {
        bos->writeInt(vertexAttribCurrentValueData.Values.UnsignedIntValues[0]);
        bos->writeInt(vertexAttribCurrentValueData.Values.UnsignedIntValues[1]);
        bos->writeInt(vertexAttribCurrentValueData.Values.UnsignedIntValues[2]);
        bos->writeInt(vertexAttribCurrentValueData.Values.UnsignedIntValues[3]);
    }
}

void SerializePixelPackState(gl::BinaryOutputStream *bos, const gl::PixelPackState &pixelPackState)
{
    bos->writeInt(pixelPackState.alignment);
    bos->writeInt(pixelPackState.rowLength);
    bos->writeInt(pixelPackState.skipRows);
    bos->writeInt(pixelPackState.skipPixels);
    bos->writeInt(pixelPackState.imageHeight);
    bos->writeInt(pixelPackState.skipImages);
    bos->writeBool(pixelPackState.reverseRowOrder);
}

void SerializePixelUnpackState(gl::BinaryOutputStream *bos,
                               const gl::PixelUnpackState &pixelUnpackState)
{
    bos->writeInt(pixelUnpackState.alignment);
    bos->writeInt(pixelUnpackState.rowLength);
    bos->writeInt(pixelUnpackState.skipRows);
    bos->writeInt(pixelUnpackState.skipPixels);
    bos->writeInt(pixelUnpackState.imageHeight);
    bos->writeInt(pixelUnpackState.skipImages);
}

void SerializeImageUnit(gl::BinaryOutputStream *bos, const gl::ImageUnit &imageUnit)
{
    bos->writeInt(imageUnit.level);
    bos->writeInt(imageUnit.layered);
    bos->writeInt(imageUnit.layer);
    bos->writeInt(imageUnit.access);
    bos->writeInt(imageUnit.format);
    bos->writeInt(imageUnit.texture.id().value);
}

void SerializeGLContextStates(gl::BinaryOutputStream *bos, const gl::State &state)
{
    bos->writeInt(state.getClientType());
    bos->writeInt(state.getContextPriority());
    bos->writeInt(state.getClientMajorVersion());
    bos->writeInt(state.getClientMinorVersion());

    SerializeColorF(bos, state.getColorClearValue());
    bos->writeFloat(state.getDepthClearValue());
    bos->writeInt(state.getStencilClearValue());
    SerializeRasterizerState(bos, state.getRasterizerState());
    bos->writeBool(state.isScissorTestEnabled());
    SerializeRectangle(bos, state.getScissor());
    SerializeBlendStateExt(bos, state.getBlendStateExt());
    SerializeColorF(bos, state.getBlendColor());
    bos->writeBool(state.isSampleAlphaToCoverageEnabled());
    bos->writeBool(state.isSampleCoverageEnabled());
    bos->writeFloat(state.getSampleCoverageValue());
    bos->writeBool(state.getSampleCoverageInvert());
    bos->writeBool(state.isSampleMaskEnabled());
    bos->writeInt(state.getMaxSampleMaskWords());
    const auto &sampleMaskValues = state.getSampleMaskValues();
    for (size_t i = 0; i < sampleMaskValues.size(); i++)
    {
        bos->writeInt(sampleMaskValues[i]);
    }
    SerializeDepthStencilState(bos, state.getDepthStencilState());
    bos->writeInt(state.getStencilRef());
    bos->writeInt(state.getStencilBackRef());
    bos->writeFloat(state.getLineWidth());
    bos->writeInt(state.getGenerateMipmapHint());
    bos->writeInt(state.getTextureFilteringHint());
    bos->writeInt(state.getFragmentShaderDerivativeHint());
    bos->writeBool(state.isBindGeneratesResourceEnabled());
    bos->writeBool(state.areClientArraysEnabled());
    SerializeRectangle(bos, state.getViewport());
    bos->writeFloat(state.getNearPlane());
    bos->writeFloat(state.getFarPlane());
    if (state.getReadFramebuffer())
    {
        bos->writeInt(state.getReadFramebuffer()->id().value);
    }
    if (state.getDrawFramebuffer())
    {
        bos->writeInt(state.getDrawFramebuffer()->id().value);
    }
    bos->writeInt(state.getRenderbufferId().value);
    if (state.getProgram())
    {
        bos->writeInt(state.getProgram()->id().value);
    }
    if (state.getProgramPipeline())
    {
        bos->writeInt(state.getProgramPipeline()->id().value);
    }
    bos->writeEnum(state.getProvokingVertex());
    const std::vector<gl::VertexAttribCurrentValueData> &vertexAttribCurrentValues =
        state.getVertexAttribCurrentValues();
    for (size_t i = 0; i < vertexAttribCurrentValues.size(); i++)
    {
        SerializeVertexAttribCurrentValueData(bos, vertexAttribCurrentValues[i]);
    }
    if (state.getVertexArray())
    {
        bos->writeInt(state.getVertexArray()->id().value);
    }
    bos->writeInt(state.getCurrentValuesTypeMask().to_ulong());
    bos->writeInt(state.getActiveSampler());
    for (const auto &textures : state.getBoundTexturesForCapture())
    {
        SerializeBindingPointerVector<gl::Texture>(bos, textures);
    }
    bos->writeInt(state.getTexturesIncompatibleWithSamplers().to_ulong());
    SerializeBindingPointerVector<gl::Sampler>(bos, state.getSamplers());
    for (const gl::ImageUnit &imageUnit : state.getImageUnits())
    {
        SerializeImageUnit(bos, imageUnit);
    }
    for (const auto &query : state.getActiveQueriesForCapture())
    {
        bos->writeInt(query.id().value);
    }
    for (const auto &boundBuffer : state.getBoundBuffersForCapture())
    {
        bos->writeInt(boundBuffer.id().value);
    }
    SerializeOffsetBindingPointerVector<gl::Buffer>(bos,
                                                    state.getOffsetBindingPointerUniformBuffers());
    SerializeOffsetBindingPointerVector<gl::Buffer>(
        bos, state.getOffsetBindingPointerAtomicCounterBuffers());
    SerializeOffsetBindingPointerVector<gl::Buffer>(
        bos, state.getOffsetBindingPointerShaderStorageBuffers());
    if (state.getCurrentTransformFeedback())
    {
        bos->writeInt(state.getCurrentTransformFeedback()->id().value);
    }
    SerializePixelUnpackState(bos, state.getUnpackState());
    SerializePixelPackState(bos, state.getPackState());
    bos->writeBool(state.isPrimitiveRestartEnabled());
    bos->writeBool(state.isMultisamplingEnabled());
    bos->writeBool(state.isSampleAlphaToOneEnabled());
    bos->writeInt(state.getCoverageModulation());
    bos->writeBool(state.getFramebufferSRGB());
    bos->writeBool(state.isRobustResourceInitEnabled());
    bos->writeBool(state.isProgramBinaryCacheEnabled());
    bos->writeBool(state.isTextureRectangleEnabled());
    bos->writeInt(state.getMaxShaderCompilerThreads());
    bos->writeInt(state.getEnabledClipDistances().to_ulong());
    bos->writeInt(state.getBlendFuncConstantAlphaDrawBuffers().to_ulong());
    bos->writeInt(state.getBlendFuncConstantColorDrawBuffers().to_ulong());
    bos->writeBool(state.noSimultaneousConstantColorAndAlphaBlendFunc());
}

void SerializeBufferState(gl::BinaryOutputStream *bos, const gl::BufferState &bufferState)
{
    bos->writeString(bufferState.getLabel());
    bos->writeEnum(bufferState.getUsage());
    bos->writeInt(bufferState.getSize());
    bos->writeInt(bufferState.getAccessFlags());
    bos->writeInt(bufferState.getAccess());
    bos->writeInt(bufferState.isMapped());
    bos->writeInt(bufferState.getMapOffset());
    bos->writeInt(bufferState.getMapLength());
}

Result SerializeBuffer(const gl::Context *context,
                       gl::BinaryOutputStream *bos,
                       ScratchBuffer *scratchBuffer,
                       gl::Buffer *buffer)
{
    SerializeBufferState(bos, buffer->getState());
    MemoryBuffer *dataPtr = nullptr;
    ANGLE_CHECK_GL_ALLOC(
        const_cast<gl::Context *>(context),
        scratchBuffer->getInitialized(static_cast<size_t>(buffer->getSize()), &dataPtr, 0));
    ANGLE_TRY(buffer->getSubData(context, 0, dataPtr->size(), dataPtr->data()));
    bos->writeBytes(dataPtr->data(), dataPtr->size());
    return Result::Continue;
}
void SerializeColorGeneric(gl::BinaryOutputStream *bos, const ColorGeneric &colorGeneric)
{
    ASSERT(colorGeneric.type == ColorGeneric::Type::Float ||
           colorGeneric.type == ColorGeneric::Type::Int ||
           colorGeneric.type == ColorGeneric::Type::UInt);
    bos->writeEnum(colorGeneric.type);
    if (colorGeneric.type == ColorGeneric::Type::Float)
    {
        SerializeColorF(bos, colorGeneric.colorF);
    }
    else if (colorGeneric.type == ColorGeneric::Type::Int)
    {
        SerializeColorI(bos, colorGeneric.colorI);
    }
    else
    {
        SerializeColorUI(bos, colorGeneric.colorUI);
    }
}

void SerializeSamplerState(gl::BinaryOutputStream *bos, const gl::SamplerState &samplerState)
{
    bos->writeInt(samplerState.getMinFilter());
    bos->writeInt(samplerState.getMagFilter());
    bos->writeInt(samplerState.getWrapS());
    bos->writeInt(samplerState.getWrapT());
    bos->writeInt(samplerState.getWrapR());
    bos->writeFloat(samplerState.getMaxAnisotropy());
    bos->writeFloat(samplerState.getMinLod());
    bos->writeFloat(samplerState.getMaxLod());
    bos->writeInt(samplerState.getCompareMode());
    bos->writeInt(samplerState.getCompareFunc());
    bos->writeInt(samplerState.getSRGBDecode());
    SerializeColorGeneric(bos, samplerState.getBorderColor());
}

void SerializeSampler(gl::BinaryOutputStream *bos, gl::Sampler *sampler)
{
    bos->writeString(sampler->getLabel());
    SerializeSamplerState(bos, sampler->getSamplerState());
}

void SerializeSwizzleState(gl::BinaryOutputStream *bos, const gl::SwizzleState &swizzleState)
{
    bos->writeInt(swizzleState.swizzleRed);
    bos->writeInt(swizzleState.swizzleGreen);
    bos->writeInt(swizzleState.swizzleBlue);
    bos->writeInt(swizzleState.swizzleAlpha);
}

void SerializeExtents(gl::BinaryOutputStream *bos, const gl::Extents &extents)
{
    bos->writeInt(extents.width);
    bos->writeInt(extents.height);
    bos->writeInt(extents.depth);
}

void SerializeInternalFormat(gl::BinaryOutputStream *bos, const gl::InternalFormat *internalFormat)
{
    bos->writeInt(internalFormat->internalFormat);
}

void SerializeFormat(gl::BinaryOutputStream *bos, const gl::Format &format)
{
    SerializeInternalFormat(bos, format.info);
}

void SerializeRenderbufferState(gl::BinaryOutputStream *bos,
                                const gl::RenderbufferState &renderbufferState)
{
    bos->writeInt(renderbufferState.getWidth());
    bos->writeInt(renderbufferState.getHeight());
    SerializeFormat(bos, renderbufferState.getFormat());
    bos->writeInt(renderbufferState.getSamples());
    bos->writeEnum(renderbufferState.getInitState());
}

Result SerializeRenderbuffer(const gl::Context *context,
                             gl::BinaryOutputStream *bos,
                             ScratchBuffer *scratchBuffer,
                             gl::Renderbuffer *renderbuffer)
{
    SerializeRenderbufferState(bos, renderbuffer->getState());
    bos->writeString(renderbuffer->getLabel());
    MemoryBuffer *pixelsPtr = nullptr;
    ANGLE_CHECK_GL_ALLOC(
        const_cast<gl::Context *>(context),
        scratchBuffer->getInitialized(renderbuffer->getMemorySize(), &pixelsPtr, 0));
    gl::PixelPackState packState;
    packState.alignment = 1;
    ANGLE_TRY(renderbuffer->getImplementation()->getRenderbufferImage(
        context, packState, nullptr, renderbuffer->getImplementationColorReadFormat(context),
        renderbuffer->getImplementationColorReadType(context), pixelsPtr->data()));
    bos->writeBytes(pixelsPtr->data(), pixelsPtr->size());
    return Result::Continue;
}

void SerializeWorkGroupSize(gl::BinaryOutputStream *bos, const sh::WorkGroupSize &workGroupSize)
{
    bos->writeInt(workGroupSize[0]);
    bos->writeInt(workGroupSize[1]);
    bos->writeInt(workGroupSize[2]);
}

void SerializeShaderVariable(gl::BinaryOutputStream *bos, const sh::ShaderVariable &shaderVariable)
{
    bos->writeInt(shaderVariable.type);
    bos->writeInt(shaderVariable.precision);
    bos->writeString(shaderVariable.name);
    bos->writeString(shaderVariable.mappedName);
    bos->writeIntVector(shaderVariable.arraySizes);
    bos->writeBool(shaderVariable.staticUse);
    bos->writeBool(shaderVariable.active);
    for (const sh::ShaderVariable &field : shaderVariable.fields)
    {
        SerializeShaderVariable(bos, field);
    }
    bos->writeString(shaderVariable.structOrBlockName);
    bos->writeString(shaderVariable.mappedStructOrBlockName);
    bos->writeBool(shaderVariable.isRowMajorLayout);
    bos->writeInt(shaderVariable.location);
    bos->writeInt(shaderVariable.binding);
    bos->writeInt(shaderVariable.imageUnitFormat);
    bos->writeInt(shaderVariable.offset);
    bos->writeBool(shaderVariable.readonly);
    bos->writeBool(shaderVariable.writeonly);
    bos->writeInt(shaderVariable.index);
    bos->writeBool(shaderVariable.yuv);
    bos->writeEnum(shaderVariable.interpolation);
    bos->writeBool(shaderVariable.isInvariant);
    bos->writeBool(shaderVariable.texelFetchStaticUse);
}

void SerializeShaderVariablesVector(gl::BinaryOutputStream *bos,
                                    const std::vector<sh::ShaderVariable> &shaderVariables)
{
    for (const sh::ShaderVariable &shaderVariable : shaderVariables)
    {
        SerializeShaderVariable(bos, shaderVariable);
    }
}

void SerializeInterfaceBlocksVector(gl::BinaryOutputStream *bos,
                                    const std::vector<sh::InterfaceBlock> &interfaceBlocks)
{
    for (const sh::InterfaceBlock &interfaceBlock : interfaceBlocks)
    {
        bos->writeString(interfaceBlock.name);
        bos->writeString(interfaceBlock.mappedName);
        bos->writeString(interfaceBlock.instanceName);
        bos->writeInt(interfaceBlock.arraySize);
        bos->writeEnum(interfaceBlock.layout);
        bos->writeInt(interfaceBlock.binding);
        bos->writeBool(interfaceBlock.staticUse);
        bos->writeBool(interfaceBlock.active);
        bos->writeEnum(interfaceBlock.blockType);
        SerializeShaderVariablesVector(bos, interfaceBlock.fields);
    }
}

void SerializeShaderState(gl::BinaryOutputStream *bos, const gl::ShaderState &shaderState)
{
    bos->writeString(shaderState.getLabel());
    bos->writeEnum(shaderState.getShaderType());
    bos->writeInt(shaderState.getShaderVersion());
    bos->writeString(shaderState.getTranslatedSource());
    bos->writeString(shaderState.getSource());
    SerializeWorkGroupSize(bos, shaderState.getLocalSize());
    SerializeShaderVariablesVector(bos, shaderState.getInputVaryings());
    SerializeShaderVariablesVector(bos, shaderState.getOutputVaryings());
    SerializeShaderVariablesVector(bos, shaderState.getUniforms());
    SerializeInterfaceBlocksVector(bos, shaderState.getUniformBlocks());
    SerializeInterfaceBlocksVector(bos, shaderState.getShaderStorageBlocks());
    SerializeShaderVariablesVector(bos, shaderState.getAllAttributes());
    SerializeShaderVariablesVector(bos, shaderState.getActiveAttributes());
    SerializeShaderVariablesVector(bos, shaderState.getActiveOutputVariables());
    bos->writeBool(shaderState.getEarlyFragmentTestsOptimization());
    bos->writeInt(shaderState.getNumViews());
    bos->writeInt(shaderState.getSpecConstUsageBits().bits());
    if (shaderState.getGeometryShaderInputPrimitiveType().valid())
    {
        bos->writeEnum(shaderState.getGeometryShaderInputPrimitiveType().value());
    }
    if (shaderState.getGeometryShaderOutputPrimitiveType().valid())
    {
        bos->writeEnum(shaderState.getGeometryShaderOutputPrimitiveType().value());
    }
    if (shaderState.getGeometryShaderInvocations().valid())
    {
        bos->writeInt(shaderState.getGeometryShaderInvocations().value());
    }
    bos->writeEnum(shaderState.getCompileStatus());
}

void SerializeShader(gl::BinaryOutputStream *bos, gl::Shader *shader)
{
    SerializeShaderState(bos, shader->getState());
    bos->writeInt(shader->getHandle().value);
    bos->writeInt(shader->getRefCount());
    bos->writeBool(shader->isFlaggedForDeletion());
    // Do not serialize mType because it is already serialized in SerializeShaderState.
    bos->writeString(shader->getInfoLogString());
    // Do not serialize compiler resources string because it can vary between test modes.
    bos->writeInt(shader->getCurrentMaxComputeWorkGroupInvocations());
    bos->writeInt(shader->getMaxComputeSharedMemory());
}

void SerializeVariableLocationsVector(gl::BinaryOutputStream *bos,
                                      const std::vector<gl::VariableLocation> &variableLocations)
{
    for (const gl::VariableLocation &variableLocation : variableLocations)
    {
        bos->writeInt(variableLocation.arrayIndex);
        bos->writeInt(variableLocation.index);
        bos->writeBool(variableLocation.ignored);
    }
}

void SerializeBlockMemberInfo(gl::BinaryOutputStream *bos,
                              const sh::BlockMemberInfo &blockMemberInfo)
{
    bos->writeInt(blockMemberInfo.offset);
    bos->writeInt(blockMemberInfo.arrayStride);
    bos->writeInt(blockMemberInfo.matrixStride);
    bos->writeBool(blockMemberInfo.isRowMajorMatrix);
    bos->writeInt(blockMemberInfo.topLevelArrayStride);
}

void SerializeActiveVariable(gl::BinaryOutputStream *bos, const gl::ActiveVariable &activeVariable)
{
    bos->writeInt(activeVariable.activeShaders().to_ulong());
}

void SerializeBufferVariablesVector(gl::BinaryOutputStream *bos,
                                    const std::vector<gl::BufferVariable> &bufferVariables)
{
    for (const gl::BufferVariable &bufferVariable : bufferVariables)
    {
        bos->writeInt(bufferVariable.bufferIndex);
        SerializeBlockMemberInfo(bos, bufferVariable.blockInfo);
        bos->writeInt(bufferVariable.topLevelArraySize);
        SerializeActiveVariable(bos, bufferVariable);
        SerializeShaderVariable(bos, bufferVariable);
    }
}

void SerializeProgramAliasedBindings(gl::BinaryOutputStream *bos,
                                     const gl::ProgramAliasedBindings &programAliasedBindings)
{
    for (const auto &programAliasedBinding : programAliasedBindings)
    {
        bos->writeString(programAliasedBinding.first);
        bos->writeInt(programAliasedBinding.second.location);
        bos->writeBool(programAliasedBinding.second.aliased);
    }
}

void SerializeProgramState(gl::BinaryOutputStream *bos, const gl::ProgramState &programState)
{
    bos->writeString(programState.getLabel());
    SerializeWorkGroupSize(bos, programState.getComputeShaderLocalSize());
    for (gl::Shader *shader : programState.getAttachedShaders())
    {
        if (shader)
        {
            bos->writeInt(shader->getHandle().value);
        }
        else
        {
            bos->writeInt(0);
        }
    }
    for (bool isAttached : programState.getAttachedShadersMarkedForDetach())
    {
        bos->writeBool(isAttached);
    }
    bos->writeInt(programState.getLocationsUsedForXfbExtension());
    for (const std::string &transformFeedbackVaryingName :
         programState.getTransformFeedbackVaryingNames())
    {
        bos->writeString(transformFeedbackVaryingName);
    }
    bos->writeInt(programState.getActiveUniformBlockBindingsMask().to_ulong());
    SerializeVariableLocationsVector(bos, programState.getUniformLocations());
    SerializeBufferVariablesVector(bos, programState.getBufferVariables());
    SerializeRange(bos, programState.getAtomicCounterUniformRange());
    SerializeVariableLocationsVector(bos, programState.getSecondaryOutputLocations());
    bos->writeInt(programState.getActiveOutputVariables().to_ulong());
    for (GLenum outputVariableType : programState.getOutputVariableTypes())
    {
        bos->writeInt(outputVariableType);
    }
    bos->writeInt(programState.getDrawBufferTypeMask().to_ulong());
    bos->writeBool(programState.hasBinaryRetrieveableHint());
    bos->writeBool(programState.isSeparable());
    bos->writeBool(programState.hasEarlyFragmentTestsOptimization());
    bos->writeInt(programState.getNumViews());
    bos->writeInt(programState.getDrawIDLocation());
    bos->writeInt(programState.getBaseVertexLocation());
    bos->writeInt(programState.getBaseInstanceLocation());
    SerializeProgramAliasedBindings(bos, programState.getUniformLocationBindings());
}

void SerializeProgramBindings(gl::BinaryOutputStream *bos,
                              const gl::ProgramBindings &programBindings)
{
    for (const auto &programBinding : programBindings)
    {
        bos->writeString(programBinding.first);
        bos->writeInt(programBinding.second);
    }
}

void SerializeProgram(gl::BinaryOutputStream *bos, gl::Program *program)
{
    SerializeProgramState(bos, program->getState());
    bos->writeBool(program->isValidated());
    SerializeProgramBindings(bos, program->getAttributeBindings());
    SerializeProgramAliasedBindings(bos, program->getFragmentOutputLocations());
    SerializeProgramAliasedBindings(bos, program->getFragmentOutputIndexes());
    bos->writeBool(program->isLinked());
    bos->writeBool(program->isFlaggedForDeletion());
    bos->writeInt(program->getRefCount());
    bos->writeInt(program->id().value);
}

void SerializeImageDesc(gl::BinaryOutputStream *bos, const gl::ImageDesc &imageDesc)
{
    SerializeExtents(bos, imageDesc.size);
    SerializeFormat(bos, imageDesc.format);
    bos->writeInt(imageDesc.samples);
    bos->writeBool(imageDesc.fixedSampleLocations);
    bos->writeEnum(imageDesc.initState);
}

void SerializeTextureState(gl::BinaryOutputStream *bos, const gl::TextureState &textureState)
{
    bos->writeEnum(textureState.getType());
    SerializeSwizzleState(bos, textureState.getSwizzleState());
    SerializeSamplerState(bos, textureState.getSamplerState());
    bos->writeEnum(textureState.getSRGBOverride());
    bos->writeInt(textureState.getBaseLevel());
    bos->writeInt(textureState.getMaxLevel());
    bos->writeInt(textureState.getDepthStencilTextureMode());
    bos->writeBool(textureState.hasBeenBoundAsImage());
    bos->writeBool(textureState.getImmutableFormat());
    bos->writeInt(textureState.getImmutableLevels());
    bos->writeInt(textureState.getUsage());
    const std::vector<gl::ImageDesc> &imageDescs = textureState.getImageDescs();
    for (const gl::ImageDesc &imageDesc : imageDescs)
    {
        SerializeImageDesc(bos, imageDesc);
    }
    SerializeRectangle(bos, textureState.getCrop());

    bos->writeInt(textureState.getGenerateMipmapHint());
    bos->writeEnum(textureState.getInitState());
}

Result SerializeTextureData(gl::BinaryOutputStream *bos,
                            const gl::Context *context,
                            gl::Texture *texture,
                            ScratchBuffer *scratchBuffer)
{
    gl::ImageIndexIterator imageIter = gl::ImageIndexIterator::MakeGeneric(
        texture->getType(), 0, texture->getMipmapMaxLevel() + 1, gl::ImageIndex::kEntireLevel,
        gl::ImageIndex::kEntireLevel);
    while (imageIter.hasNext())
    {
        gl::ImageIndex index = imageIter.next();

        const gl::ImageDesc &desc = texture->getTextureState().getImageDesc(index);

        if (desc.size.empty())
            continue;

        const gl::InternalFormat &format = *desc.format.info;

        // Check for supported textures
        ASSERT(index.getType() == gl::TextureType::_2D || index.getType() == gl::TextureType::_3D ||
               index.getType() == gl::TextureType::_2DArray ||
               index.getType() == gl::TextureType::CubeMap);

        GLenum getFormat = format.format;
        GLenum getType   = format.type;

        const gl::Extents size(desc.size.width, desc.size.height, desc.size.depth);
        const gl::PixelUnpackState &unpack = context->getState().getUnpackState();

        GLuint endByte  = 0;
        bool unpackSize = format.computePackUnpackEndByte(getType, size, unpack, true, &endByte);
        ASSERT(unpackSize);
        MemoryBuffer *texelsPtr = nullptr;
        ANGLE_CHECK_GL_ALLOC(const_cast<gl::Context *>(context),
                             scratchBuffer->getInitialized(endByte, &texelsPtr, 0));

        gl::PixelPackState packState;
        packState.alignment = 1;

        ANGLE_TRY(texture->getTexImage(context, packState, nullptr, index.getTarget(),
                                       index.getLevelIndex(), getFormat, getType,
                                       texelsPtr->data()));
        bos->writeBytes(texelsPtr->data(), texelsPtr->size());
    }
    return Result::Continue;
}

Result SerializeTexture(const gl::Context *context,
                        gl::BinaryOutputStream *bos,
                        ScratchBuffer *scratchBuffer,
                        gl::Texture *texture)
{
    SerializeTextureState(bos, texture->getState());
    bos->writeString(texture->getLabel());
    // FrameCapture can not serialize mBoundSurface and mBoundStream
    // because they are likely to change with each run
    ANGLE_TRY(SerializeTextureData(bos, context, texture, scratchBuffer));
    return Result::Continue;
}

void SerializeFormat(gl::BinaryOutputStream *bos, const angle::Format *format)
{
    bos->writeInt(format->glInternalFormat);
}

void SerializeVertexAttributeVector(gl::BinaryOutputStream *bos,
                                    const std::vector<gl::VertexAttribute> &vertexAttributes)
{
    for (const gl::VertexAttribute &vertexAttribute : vertexAttributes)
    {
        bos->writeBool(vertexAttribute.enabled);
        ASSERT(vertexAttribute.format);
        SerializeFormat(bos, vertexAttribute.format);
        bos->writeInt(vertexAttribute.relativeOffset);
        bos->writeInt(vertexAttribute.vertexAttribArrayStride);
        bos->writeInt(vertexAttribute.bindingIndex);
    }
}

void SerializeVertexBindingsVector(gl::BinaryOutputStream *bos,
                                   const std::vector<gl::VertexBinding> &vertexBindings)
{
    for (const gl::VertexBinding &vertexBinding : vertexBindings)
    {
        bos->writeInt(vertexBinding.getStride());
        bos->writeInt(vertexBinding.getDivisor());
        bos->writeInt(vertexBinding.getOffset());
        bos->writeInt(vertexBinding.getBuffer().id().value);
        bos->writeInt(vertexBinding.getBoundAttributesMask().to_ulong());
    }
}

void SerializeVertexArrayState(gl::BinaryOutputStream *bos,
                               const gl::VertexArrayState &vertexArrayState)
{
    bos->writeString(vertexArrayState.getLabel());
    SerializeVertexAttributeVector(bos, vertexArrayState.getVertexAttributes());
    if (vertexArrayState.getElementArrayBuffer())
    {
        bos->writeInt(vertexArrayState.getElementArrayBuffer()->id().value);
    }
    else
    {
        bos->writeInt(0);
    }
    SerializeVertexBindingsVector(bos, vertexArrayState.getVertexBindings());
    bos->writeInt(vertexArrayState.getEnabledAttributesMask().to_ulong());
    bos->writeInt(vertexArrayState.getVertexAttributesTypeMask().to_ulong());
    bos->writeInt(vertexArrayState.getClientMemoryAttribsMask().to_ulong());
    bos->writeInt(vertexArrayState.getNullPointerClientMemoryAttribsMask().to_ulong());
}

void SerializeVertexArray(gl::BinaryOutputStream *bos, gl::VertexArray *vertexArray)
{
    bos->writeInt(vertexArray->id().value);
    SerializeVertexArrayState(bos, vertexArray->getState());
    bos->writeBool(vertexArray->isBufferAccessValidationEnabled());
}

}  // namespace

Result SerializeContext(gl::BinaryOutputStream *bos, const gl::Context *context)
{
    SerializeGLContextStates(bos, context->getState());
    ScratchBuffer scratchBuffer(1);
    const gl::FramebufferManager &framebufferManager =
        context->getState().getFramebufferManagerForCapture();
    for (const auto &framebuffer : framebufferManager)
    {
        gl::Framebuffer *framebufferPtr = framebuffer.second;
        ANGLE_TRY(SerializeFramebuffer(context, bos, &scratchBuffer, framebufferPtr));
    }
    const gl::BufferManager &bufferManager = context->getState().getBufferManagerForCapture();
    for (const auto &buffer : bufferManager)
    {
        gl::Buffer *bufferPtr = buffer.second;
        ANGLE_TRY(SerializeBuffer(context, bos, &scratchBuffer, bufferPtr));
    }
    const gl::SamplerManager &samplerManager = context->getState().getSamplerManagerForCapture();
    for (const auto &sampler : samplerManager)
    {
        gl::Sampler *samplerPtr = sampler.second;
        SerializeSampler(bos, samplerPtr);
    }
    const gl::RenderbufferManager &renderbufferManager =
        context->getState().getRenderbufferManagerForCapture();
    for (const auto &renderbuffer : renderbufferManager)
    {
        gl::Renderbuffer *renderbufferPtr = renderbuffer.second;
        ANGLE_TRY(SerializeRenderbuffer(context, bos, &scratchBuffer, renderbufferPtr));
    }
    const gl::ShaderProgramManager &shaderProgramManager =
        context->getState().getShaderProgramManagerForCapture();
    const gl::ResourceMap<gl::Shader, gl::ShaderProgramID> &shaderManager =
        shaderProgramManager.getShadersForCapture();
    for (const auto &shader : shaderManager)
    {
        gl::Shader *shaderPtr = shader.second;
        SerializeShader(bos, shaderPtr);
    }
    const gl::ResourceMap<gl::Program, gl::ShaderProgramID> &programManager =
        shaderProgramManager.getProgramsForCaptureAndPerf();
    for (const auto &program : programManager)
    {
        gl::Program *programPtr = program.second;
        SerializeProgram(bos, programPtr);
    }
    const gl::TextureManager &textureManager = context->getState().getTextureManagerForCapture();
    for (const auto &texture : textureManager)
    {
        gl::Texture *texturePtr = texture.second;
        ANGLE_TRY(SerializeTexture(context, bos, &scratchBuffer, texturePtr));
    }
    const gl::VertexArrayMap &vertexArrayMap = context->getVertexArraysForCapture();
    for (auto &vertexArray : vertexArrayMap)
    {
        gl::VertexArray *vertexArrayPtr = vertexArray.second;
        SerializeVertexArray(bos, vertexArrayPtr);
    }

    scratchBuffer.clear();
    return Result::Continue;
}

}  // namespace angle
