//
// Copyright 2017 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.
//
// ResourceManager11:
//   Centralized point of allocation for all D3D11 Resources.

#ifndef LIBANGLE_RENDERER_D3D_D3D11_RESOURCEFACTORY11_H_
#define LIBANGLE_RENDERER_D3D_D3D11_RESOURCEFACTORY11_H_

#include <array>
#include <memory>

#include "common/MemoryBuffer.h"
#include "common/angleutils.h"
#include "common/debug.h"
#include "libANGLE/Error.h"
#include "libANGLE/renderer/renderer_utils.h"

namespace rx
{
// These two methods are declared here to prevent circular includes.
namespace d3d11
{
HRESULT SetDebugName(ID3D11DeviceChild *resource, const char *name);

template <typename T>
HRESULT SetDebugName(angle::ComPtr<T> &resource, const char *name)
{
    return SetDebugName(resource.Get(), name);
}
}  // namespace d3d11

class Renderer11;
class ResourceManager11;
template <typename T>
class SharedResource11;
class TextureHelper11;

using InputElementArray = WrappedArray<D3D11_INPUT_ELEMENT_DESC>;
using ShaderData        = WrappedArray<uint8_t>;

// Format: ResourceType, D3D11 type, DESC type, init data type.
#define ANGLE_RESOURCE_TYPE_OP(NAME, OP)                                                     \
    OP(NAME, BlendState, ID3D11BlendState, D3D11_BLEND_DESC, void)                           \
    OP(NAME, Buffer, ID3D11Buffer, D3D11_BUFFER_DESC, const D3D11_SUBRESOURCE_DATA)          \
    OP(NAME, ComputeShader, ID3D11ComputeShader, ShaderData, void)                           \
    OP(NAME, DepthStencilState, ID3D11DepthStencilState, D3D11_DEPTH_STENCIL_DESC, void)     \
    OP(NAME, DepthStencilView, ID3D11DepthStencilView, D3D11_DEPTH_STENCIL_VIEW_DESC,        \
       ID3D11Resource)                                                                       \
    OP(NAME, GeometryShader, ID3D11GeometryShader, ShaderData,                               \
       const std::vector<D3D11_SO_DECLARATION_ENTRY>)                                        \
    OP(NAME, InputLayout, ID3D11InputLayout, InputElementArray, const ShaderData)            \
    OP(NAME, PixelShader, ID3D11PixelShader, ShaderData, void)                               \
    OP(NAME, Query, ID3D11Query, D3D11_QUERY_DESC, void)                                     \
    OP(NAME, RasterizerState, ID3D11RasterizerState, D3D11_RASTERIZER_DESC, void)            \
    OP(NAME, RenderTargetView, ID3D11RenderTargetView, D3D11_RENDER_TARGET_VIEW_DESC,        \
       ID3D11Resource)                                                                       \
    OP(NAME, SamplerState, ID3D11SamplerState, D3D11_SAMPLER_DESC, void)                     \
    OP(NAME, ShaderResourceView, ID3D11ShaderResourceView, D3D11_SHADER_RESOURCE_VIEW_DESC,  \
       ID3D11Resource)                                                                       \
    OP(NAME, Texture2D, ID3D11Texture2D, D3D11_TEXTURE2D_DESC, const D3D11_SUBRESOURCE_DATA) \
    OP(NAME, Texture3D, ID3D11Texture3D, D3D11_TEXTURE3D_DESC, const D3D11_SUBRESOURCE_DATA) \
    OP(NAME, VertexShader, ID3D11VertexShader, ShaderData, void)

#define ANGLE_RESOURCE_TYPE_LIST(NAME, RESTYPE, D3D11TYPE, DESCTYPE, INITDATATYPE) RESTYPE,

enum class ResourceType
{
    ANGLE_RESOURCE_TYPE_OP(List, ANGLE_RESOURCE_TYPE_LIST) Last
};

#undef ANGLE_RESOURCE_TYPE_LIST

constexpr size_t ResourceTypeIndex(ResourceType resourceType)
{
    return static_cast<size_t>(resourceType);
}

constexpr size_t NumResourceTypes = ResourceTypeIndex(ResourceType::Last);

#define ANGLE_RESOURCE_TYPE_TO_D3D11(NAME, RESTYPE, D3D11TYPE, DESCTYPE, INITDATATYPE) \
    \
template<> struct NAME<ResourceType::RESTYPE>                                          \
    {                                                                                  \
        using Value = D3D11TYPE;                                                       \
    };

#define ANGLE_RESOURCE_TYPE_TO_DESC(NAME, RESTYPE, D3D11TYPE, DESCTYPE, INITDATATYPE) \
    \
template<> struct NAME<ResourceType::RESTYPE>                                         \
    {                                                                                 \
        using Value = DESCTYPE;                                                       \
    };

#define ANGLE_RESOURCE_TYPE_TO_INIT_DATA(NAME, RESTYPE, D3D11TYPE, DESCTYPE, INITDATATYPE) \
    \
template<> struct NAME<ResourceType::RESTYPE>                                              \
    {                                                                                      \
        using Value = INITDATATYPE;                                                        \
    };

#define ANGLE_RESOURCE_TYPE_TO_TYPE(NAME, OP) \
    template <ResourceType Param>             \
    struct NAME;                              \
    ANGLE_RESOURCE_TYPE_OP(NAME, OP)          \
    \
template<ResourceType Param> struct NAME      \
    {                                         \
    };                                        \
    \
template<ResourceType Param> using Get##NAME = typename NAME<Param>::Value;

ANGLE_RESOURCE_TYPE_TO_TYPE(D3D11Type, ANGLE_RESOURCE_TYPE_TO_D3D11)
ANGLE_RESOURCE_TYPE_TO_TYPE(DescType, ANGLE_RESOURCE_TYPE_TO_DESC)
ANGLE_RESOURCE_TYPE_TO_TYPE(InitDataType, ANGLE_RESOURCE_TYPE_TO_INIT_DATA)

#undef ANGLE_RESOURCE_TYPE_TO_D3D11
#undef ANGLE_RESOURCE_TYPE_TO_DESC
#undef ANGLE_RESOURCE_TYPE_TO_INIT_DATA
#undef ANGLE_RESOURCE_TYPE_TO_TYPE

#define ANGLE_TYPE_TO_RESOURCE_TYPE(NAME, OP)               \
    template <typename Param>                               \
    struct NAME;                                            \
    ANGLE_RESOURCE_TYPE_OP(NAME, OP)                        \
    \
template<typename Param> struct NAME                        \
    {                                                       \
    };                                                      \
    \
template<typename Param> constexpr ResourceType Get##NAME() \
    {                                                       \
        return NAME<Param>::Value;                          \
    }

#define ANGLE_D3D11_TO_RESOURCE_TYPE(NAME, RESTYPE, D3D11TYPE, DESCTYPE, INITDATATYPE) \
    \
template<> struct NAME<D3D11TYPE>                                                      \
    {                                                                                  \
        static constexpr ResourceType Value = ResourceType::RESTYPE;                   \
    };

ANGLE_TYPE_TO_RESOURCE_TYPE(ResourceTypeFromD3D11, ANGLE_D3D11_TO_RESOURCE_TYPE)

#undef ANGLE_D3D11_TO_RESOURCE_TYPE
#undef ANGLE_TYPE_TO_RESOURCE_TYPE

template <typename T>
using GetDescFromD3D11 = GetDescType<ResourceTypeFromD3D11<T>::Value>;

template <typename T>
using GetInitDataFromD3D11 = GetInitDataType<ResourceTypeFromD3D11<T>::Value>;

template <typename T>
constexpr size_t ResourceTypeIndex()
{
    return static_cast<size_t>(GetResourceTypeFromD3D11<T>());
}

template <typename T>
struct TypedData
{
    TypedData() {}
    ~TypedData();

    T *object                  = nullptr;
    ResourceManager11 *manager = nullptr;
};

// Smart pointer type. Wraps the resource and a factory for safe deletion.
template <typename T, template <class> class Pointer, typename DataT>
class Resource11Base : angle::NonCopyable
{
  public:
    T *get() const { return mData->object; }
    T *const *getPointer() const { return &mData->object; }

    void setDebugName(const char *name) { d3d11::SetDebugName(mData->object, name); }

    void set(T *object)
    {
        ASSERT(!valid());
        mData->object = object;
    }

    bool valid() const { return (mData->object != nullptr); }

    void reset()
    {
        if (valid())
            mData.reset(new DataT());
    }

    ResourceSerial getSerial() const
    {
        return ResourceSerial(reinterpret_cast<uintptr_t>(mData->object));
    }

  protected:
    friend class TextureHelper11;

    Resource11Base() : mData(new DataT()) {}

    Resource11Base(Resource11Base &&movedObj) : mData(new DataT())
    {
        std::swap(mData, movedObj.mData);
    }

    virtual ~Resource11Base() { mData.reset(); }

    Resource11Base &operator=(Resource11Base &&movedObj)
    {
        std::swap(mData, movedObj.mData);
        return *this;
    }

    Pointer<DataT> mData;
};

template <typename T>
using UniquePtr = typename std::unique_ptr<T, std::default_delete<T>>;

template <typename ResourceT>
class Resource11 : public Resource11Base<ResourceT, UniquePtr, TypedData<ResourceT>>
{
  public:
    Resource11() {}
    Resource11(Resource11 &&other)
        : Resource11Base<ResourceT, UniquePtr, TypedData<ResourceT>>(std::move(other))
    {
    }
    Resource11 &operator=(Resource11 &&other)
    {
        std::swap(this->mData, other.mData);
        return *this;
    }

  private:
    template <typename T>
    friend class SharedResource11;
    friend class ResourceManager11;

    Resource11(ResourceT *object, ResourceManager11 *manager)
    {
        this->mData->object  = object;
        this->mData->manager = manager;
    }
};

template <typename T>
class SharedResource11 : public Resource11Base<T, std::shared_ptr, TypedData<T>>
{
  public:
    SharedResource11() {}
    SharedResource11(SharedResource11 &&movedObj)
        : Resource11Base<T, std::shared_ptr, TypedData<T>>(std::move(movedObj))
    {
    }

    SharedResource11 &operator=(SharedResource11 &&other)
    {
        std::swap(this->mData, other.mData);
        return *this;
    }

    SharedResource11 makeCopy() const
    {
        SharedResource11 copy;
        copy.mData = this->mData;
        return std::move(copy);
    }

  private:
    friend class ResourceManager11;
    SharedResource11(Resource11<T> &&obj) : Resource11Base<T, std::shared_ptr, TypedData<T>>()
    {
        std::swap(this->mData->manager, obj.mData->manager);

        // Can't use std::swap because of ID3D11Resource.
        auto temp           = this->mData->object;
        this->mData->object = obj.mData->object;
        obj.mData->object   = static_cast<T *>(temp);
    }
};

class ResourceManager11 final : angle::NonCopyable
{
  public:
    ResourceManager11();
    ~ResourceManager11();

    template <typename T>
    gl::Error allocate(Renderer11 *renderer,
                       const GetDescFromD3D11<T> *desc,
                       GetInitDataFromD3D11<T> *initData,
                       Resource11<T> *resourceOut);

    template <typename T>
    gl::Error allocate(Renderer11 *renderer,
                       const GetDescFromD3D11<T> *desc,
                       GetInitDataFromD3D11<T> *initData,
                       SharedResource11<T> *sharedRes)
    {
        Resource11<T> res;
        ANGLE_TRY(allocate(renderer, desc, initData, &res));
        *sharedRes = std::move(res);
        return gl::NoError();
    }

    template <typename T>
    void onRelease(T *resource)
    {
        onReleaseGeneric(GetResourceTypeFromD3D11<T>(), resource);
    }

    void onReleaseGeneric(ResourceType resourceType, ID3D11DeviceChild *resource);

  private:
    void incrResource(ResourceType resourceType, size_t memorySize);
    void decrResource(ResourceType resourceType, size_t memorySize);

    template <typename T>
    GetInitDataFromD3D11<T> *createInitDataIfNeeded(const GetDescFromD3D11<T> *desc);

    std::array<size_t, NumResourceTypes> mAllocatedResourceCounts;
    std::array<size_t, NumResourceTypes> mAllocatedResourceDeviceMemory;
    angle::MemoryBuffer mZeroMemory;

    std::vector<D3D11_SUBRESOURCE_DATA> mShadowInitData;
};

template <typename ResourceT>
TypedData<ResourceT>::~TypedData()
{
    if (object)
    {
        // We can have a nullptr factory when holding passed-in resources.
        if (manager)
        {
            manager->onRelease(object);
        }
        object->Release();
    }
}

#define ANGLE_RESOURCE_TYPE_CLASS(NAME, RESTYPE, D3D11TYPE, DESCTYPE, INITDATATYPE) \
    using RESTYPE = Resource11<D3D11TYPE>;

namespace d3d11
{
ANGLE_RESOURCE_TYPE_OP(ClassList, ANGLE_RESOURCE_TYPE_CLASS)

using SharedSRV = SharedResource11<ID3D11ShaderResourceView>;
}  // namespace d3d11

#undef ANGLE_RESOURCE_TYPE_CLASS

}  // namespace rx

#endif  // LIBANGLE_RENDERER_D3D_D3D11_RESOURCEFACTORY11_H_
