//
// Copyright 2014 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.
//

// FramebufferAttachment.cpp: the gl::FramebufferAttachment class and its derived classes
// objects and related functionality. [OpenGL ES 2.0.24] section 4.4.3 page 108.

#include "libANGLE/FramebufferAttachment.h"

#include "common/utilities.h"
#include "libANGLE/Config.h"
#include "libANGLE/Context.h"
#include "libANGLE/Renderbuffer.h"
#include "libANGLE/Surface.h"
#include "libANGLE/Texture.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/FramebufferAttachmentObjectImpl.h"
#include "libANGLE/renderer/FramebufferImpl.h"

namespace gl
{

////// FramebufferAttachment::Target Implementation //////

const GLsizei FramebufferAttachment::kDefaultNumViews             = 1;
const GLint FramebufferAttachment::kDefaultBaseViewIndex          = 0;
const GLint FramebufferAttachment::kDefaultRenderToTextureSamples = 0;

FramebufferAttachment::Target::Target() : mBinding(GL_NONE), mTextureIndex() {}

FramebufferAttachment::Target::Target(GLenum binding, const ImageIndex &imageIndex)
    : mBinding(binding), mTextureIndex(imageIndex)
{}

FramebufferAttachment::Target::Target(const Target &other)
    : mBinding(other.mBinding), mTextureIndex(other.mTextureIndex)
{}

FramebufferAttachment::Target &FramebufferAttachment::Target::operator=(const Target &other)
{
    this->mBinding      = other.mBinding;
    this->mTextureIndex = other.mTextureIndex;
    return *this;
}

////// FramebufferAttachment Implementation //////

FramebufferAttachment::FramebufferAttachment()
    : mType(GL_NONE),
      mResource(nullptr),
      mNumViews(kDefaultNumViews),
      mIsMultiview(false),
      mBaseViewIndex(kDefaultBaseViewIndex),
      mRenderToTextureSamples(kDefaultRenderToTextureSamples)
{}

FramebufferAttachment::FramebufferAttachment(const Context *context,
                                             GLenum type,
                                             GLenum binding,
                                             const ImageIndex &textureIndex,
                                             FramebufferAttachmentObject *resource,
                                             rx::UniqueSerial framebufferSerial)
    : mResource(nullptr)
{
    attach(context, type, binding, textureIndex, resource, kDefaultNumViews, kDefaultBaseViewIndex,
           false, kDefaultRenderToTextureSamples, framebufferSerial);
}

FramebufferAttachment::FramebufferAttachment(FramebufferAttachment &&other)
    : FramebufferAttachment()
{
    *this = std::move(other);
}

FramebufferAttachment &FramebufferAttachment::operator=(FramebufferAttachment &&other)
{
    std::swap(mType, other.mType);
    std::swap(mTarget, other.mTarget);
    std::swap(mResource, other.mResource);
    std::swap(mNumViews, other.mNumViews);
    std::swap(mIsMultiview, other.mIsMultiview);
    std::swap(mBaseViewIndex, other.mBaseViewIndex);
    std::swap(mRenderToTextureSamples, other.mRenderToTextureSamples);
    return *this;
}

FramebufferAttachment::~FramebufferAttachment()
{
    ASSERT(!isAttached());
}

void FramebufferAttachment::detach(const Context *context, rx::UniqueSerial framebufferSerial)
{
    mType = GL_NONE;
    if (mResource != nullptr)
    {
        mResource->onDetach(context, framebufferSerial);
        mResource = nullptr;
    }
    mNumViews      = kDefaultNumViews;
    mIsMultiview   = false;
    mBaseViewIndex = kDefaultBaseViewIndex;

    // not technically necessary, could omit for performance
    mTarget = Target();
}

void FramebufferAttachment::attach(const Context *context,
                                   GLenum type,
                                   GLenum binding,
                                   const ImageIndex &textureIndex,
                                   FramebufferAttachmentObject *resource,
                                   GLsizei numViews,
                                   GLuint baseViewIndex,
                                   bool isMultiview,
                                   GLsizei samples,
                                   rx::UniqueSerial framebufferSerial)
{
    if (resource == nullptr)
    {
        detach(context, framebufferSerial);
        return;
    }

    mType                   = type;
    mTarget                 = Target(binding, textureIndex);
    mNumViews               = numViews;
    mBaseViewIndex          = baseViewIndex;
    mIsMultiview            = isMultiview;
    mRenderToTextureSamples = type == GL_RENDERBUFFER ? kDefaultRenderToTextureSamples : samples;
    if (mIsMultiview)
    {
        // Multiview framebuffer attachments can only be created through:
        // 1) glFramebufferTextureMultiviewOVR
        // 2) glNamedFramebufferTextureMultiviewOVR
        // 3) glFramebufferTextureMultisampleMultiviewOVR
        // All of the above method require the attachment to be texture, not renderbuffer.
        ASSERT(type != GL_RENDERBUFFER);
    }
    resource->onAttach(context, framebufferSerial);

    if (mResource != nullptr)
    {
        mResource->onDetach(context, framebufferSerial);
    }

    mResource = resource;
}

GLuint FramebufferAttachment::getRedSize() const
{
    return isSpecified() ? getFormat().info->redBits : 0;
}

GLuint FramebufferAttachment::getGreenSize() const
{
    return isSpecified() ? getFormat().info->greenBits : 0;
}

GLuint FramebufferAttachment::getBlueSize() const
{
    return isSpecified() ? getFormat().info->blueBits : 0;
}

GLuint FramebufferAttachment::getAlphaSize() const
{
    return isSpecified() ? getFormat().info->alphaBits : 0;
}

GLuint FramebufferAttachment::getDepthSize() const
{
    return isSpecified() ? getFormat().info->depthBits : 0;
}

GLuint FramebufferAttachment::getStencilSize() const
{
    return isSpecified() ? getFormat().info->stencilBits : 0;
}

GLenum FramebufferAttachment::getComponentType() const
{
    return getFormat().info->componentType;
}

GLenum FramebufferAttachment::getColorEncoding() const
{
    return getFormat().info->colorEncoding;
}

GLuint FramebufferAttachment::id() const
{
    return mResource->getId();
}

TextureTarget FramebufferAttachment::cubeMapFace() const
{
    ASSERT(mType == GL_TEXTURE);

    const auto &index = mTarget.textureIndex();
    return index.getType() == TextureType::CubeMap ? index.getTarget() : TextureTarget::InvalidEnum;
}

GLint FramebufferAttachment::mipLevel() const
{
    ASSERT(type() == GL_TEXTURE);
    return mTarget.textureIndex().getLevelIndex();
}

GLint FramebufferAttachment::layer() const
{
    ASSERT(mType == GL_TEXTURE);

    const gl::ImageIndex &index = mTarget.textureIndex();
    return (index.has3DLayer() ? index.getLayerIndex() : 0);
}

bool FramebufferAttachment::hasLayer() const
{
    return mTarget.textureIndex().hasLayer();
}

bool FramebufferAttachment::isLayered() const
{
    return mTarget.textureIndex().isLayered();
}

bool FramebufferAttachment::isMultiview() const
{
    return mIsMultiview;
}

GLint FramebufferAttachment::getBaseViewIndex() const
{
    return mBaseViewIndex;
}

bool FramebufferAttachment::isRenderToTexture() const
{
    ASSERT(mRenderToTextureSamples == kDefaultRenderToTextureSamples || mType == GL_TEXTURE);

    if (mType == GL_RENDERBUFFER)
    {
        return getRenderbuffer()->getMultisamplingMode() ==
               MultisamplingMode::MultisampledRenderToTexture;
    }
    return mRenderToTextureSamples != kDefaultRenderToTextureSamples;
}

GLsizei FramebufferAttachment::getRenderToTextureSamples() const
{
    if (!isRenderToTexture())
    {
        return 0;
    }

    ASSERT(mRenderToTextureSamples == kDefaultRenderToTextureSamples || mType == GL_TEXTURE);

    if (mType == GL_RENDERBUFFER)
    {
        return getRenderbuffer()->getState().getSamples();
    }
    return mRenderToTextureSamples;
}

Texture *FramebufferAttachment::getTexture() const
{
    return rx::GetAs<Texture>(mResource);
}

Renderbuffer *FramebufferAttachment::getRenderbuffer() const
{
    return rx::GetAs<Renderbuffer>(mResource);
}

const egl::Surface *FramebufferAttachment::getSurface() const
{
    return rx::GetAs<egl::Surface>(mResource);
}

FramebufferAttachmentObject *FramebufferAttachment::getResource() const
{
    return mResource;
}

bool FramebufferAttachment::operator==(const FramebufferAttachment &other) const
{
    if (mResource != other.mResource || mType != other.mType || mNumViews != other.mNumViews ||
        mIsMultiview != other.mIsMultiview || mBaseViewIndex != other.mBaseViewIndex ||
        mRenderToTextureSamples != other.mRenderToTextureSamples)
    {
        return false;
    }

    if (mType == GL_TEXTURE && getTextureImageIndex() != other.getTextureImageIndex())
    {
        return false;
    }

    return true;
}

bool FramebufferAttachment::operator!=(const FramebufferAttachment &other) const
{
    return !(*this == other);
}

InitState FramebufferAttachment::initState() const
{
    return mResource ? mResource->initState(mTarget.binding(), mTarget.textureIndex())
                     : InitState::Initialized;
}

angle::Result FramebufferAttachment::initializeContents(const Context *context) const
{
    ASSERT(mResource);
    ANGLE_TRY(mResource->initializeContents(context, mTarget.binding(), mTarget.textureIndex()));
    setInitState(InitState::Initialized);
    return angle::Result::Continue;
}

void FramebufferAttachment::setInitState(InitState initState) const
{
    ASSERT(mResource);
    mResource->setInitState(mTarget.binding(), mTarget.textureIndex(), initState);
}

////// FramebufferAttachmentObject Implementation //////

FramebufferAttachmentObject::FramebufferAttachmentObject() {}

FramebufferAttachmentObject::~FramebufferAttachmentObject() {}

angle::Result FramebufferAttachmentObject::getAttachmentRenderTarget(
    const Context *context,
    GLenum binding,
    const ImageIndex &imageIndex,
    GLsizei samples,
    rx::FramebufferAttachmentRenderTarget **rtOut) const
{
    return getAttachmentImpl()->getAttachmentRenderTarget(
        context, binding, OwnImageIndex(imageIndex), samples, rtOut);
}

angle::Result FramebufferAttachmentObject::initializeContents(const Context *context,
                                                              GLenum binding,
                                                              const ImageIndex &imageIndex)
{
    ASSERT(context->isRobustResourceInitEnabled());

    // Because gl::Texture cannot support tracking individual layer dirtiness, we only handle
    // initializing entire mip levels for textures with layers
    if (imageIndex.usesTex3D() && imageIndex.hasLayer())
    {
        // Compute the layer count so we get a correct layer index.
        const gl::Extents &size = getAttachmentSize(imageIndex);

        ImageIndex fullMipIndex = ImageIndex::MakeFromType(
            imageIndex.getType(), imageIndex.getLevelIndex(), ImageIndex::kEntireLevel, size.depth);
        return getAttachmentImpl()->initializeContents(context, binding,
                                                       OwnImageIndex(fullMipIndex));
    }
    else
    {
        return getAttachmentImpl()->initializeContents(context, binding, OwnImageIndex(imageIndex));
    }
}

}  // namespace gl
