/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/

#ifndef GrVkResourceProvider_DEFINED
#define GrVkResourceProvider_DEFINED

#include "GrGpu.h"
#include "GrResourceHandle.h"
#include "GrVkDescriptorPool.h"
#include "GrVkDescriptorSetManager.h"
#include "GrVkPipelineState.h"
#include "GrVkRenderPass.h"
#include "GrVkResource.h"
#include "GrVkUtil.h"
#include "SkLRUCache.h"
#include "SkTArray.h"
#include "SkTDynamicHash.h"
#include "SkTInternalLList.h"

#include "vk/GrVkDefines.h"

class GrPipeline;
class GrPrimitiveProcessor;
class GrSamplerParams;
class GrVkCopyPipeline;
class GrVkGpu;
class GrVkPipeline;
class GrVkPrimaryCommandBuffer;
class GrVkRenderTarget;
class GrVkSampler;
class GrVkSecondaryCommandBuffer;
class GrVkUniformHandler;

class GrVkResourceProvider {
public:
    GrVkResourceProvider(GrVkGpu* gpu);
    ~GrVkResourceProvider();

    // Set up any initial vk objects
    void init();

    GrVkPipeline* createPipeline(const GrPipeline& pipeline,
                                 const GrStencilSettings& stencil,
                                 const GrPrimitiveProcessor& primProc,
                                 VkPipelineShaderStageCreateInfo* shaderStageInfo,
                                 int shaderStageCount,
                                 GrPrimitiveType primitiveType,
                                 const GrVkRenderPass& renderPass,
                                 VkPipelineLayout layout);

    GrVkCopyPipeline* findOrCreateCopyPipeline(const GrVkRenderTarget* dst,
                                               VkPipelineShaderStageCreateInfo*,
                                               VkPipelineLayout);

    GR_DEFINE_RESOURCE_HANDLE_CLASS(CompatibleRPHandle);

    // Finds or creates a simple render pass that matches the target, increments the refcount,
    // and returns. The caller can optionally pass in a pointer to a CompatibleRPHandle. If this is
    // non null it will be set to a handle that can be used in the furutre to quickly return a
    // compatible GrVkRenderPasses without the need inspecting a GrVkRenderTarget.
    const GrVkRenderPass* findCompatibleRenderPass(const GrVkRenderTarget& target,
                                                   CompatibleRPHandle* compatibleHandle = nullptr);
    // The CompatibleRPHandle must be a valid handle previously set by a call to
    // findCompatibleRenderPass(GrVkRenderTarget&, CompatibleRPHandle*).
    const GrVkRenderPass* findCompatibleRenderPass(const CompatibleRPHandle& compatibleHandle);

    // Finds or creates a render pass that matches the target and LoadStoreOps, increments the
    // refcount, and returns. The caller can optionally pass in a pointer to a CompatibleRPHandle.
    // If this is non null it will be set to a handle that can be used in the furutre to quickly
    // return a GrVkRenderPasses without the need inspecting a GrVkRenderTarget.
    const GrVkRenderPass* findRenderPass(const GrVkRenderTarget& target,
                                         const GrVkRenderPass::LoadStoreOps& colorOps,
                                         const GrVkRenderPass::LoadStoreOps& stencilOps,
                                         CompatibleRPHandle* compatibleHandle = nullptr);

    // The CompatibleRPHandle must be a valid handle previously set by a call to findRenderPass or
    // findCompatibleRenderPass.
    const GrVkRenderPass* findRenderPass(const CompatibleRPHandle& compatibleHandle,
                                         const GrVkRenderPass::LoadStoreOps& colorOps,
                                         const GrVkRenderPass::LoadStoreOps& stencilOps);

    GrVkPrimaryCommandBuffer* findOrCreatePrimaryCommandBuffer();
    void checkCommandBuffers();

    GrVkSecondaryCommandBuffer* findOrCreateSecondaryCommandBuffer();
    void recycleSecondaryCommandBuffer(GrVkSecondaryCommandBuffer* cb);

    // Finds or creates a compatible GrVkDescriptorPool for the requested type and count.
    // The refcount is incremented and a pointer returned.
    // TODO: Currently this will just create a descriptor pool without holding onto a ref itself
    //       so we currently do not reuse them. Rquires knowing if another draw is currently using
    //       the GrVkDescriptorPool, the ability to reset pools, and the ability to purge pools out
    //       of our cache of GrVkDescriptorPools.
    GrVkDescriptorPool* findOrCreateCompatibleDescriptorPool(VkDescriptorType type, uint32_t count);

    // Finds or creates a compatible GrVkSampler based on the GrSamplerParams.
    // The refcount is incremented and a pointer returned.
    GrVkSampler* findOrCreateCompatibleSampler(const GrSamplerParams&, uint32_t mipLevels);

    sk_sp<GrVkPipelineState> findOrCreateCompatiblePipelineState(const GrPipeline&,
                                                                 const GrPrimitiveProcessor&,
                                                                 GrPrimitiveType,
                                                                 const GrVkRenderPass& renderPass);

    void getSamplerDescriptorSetHandle(const GrVkUniformHandler&,
                                       GrVkDescriptorSetManager::Handle* handle);
    void getSamplerDescriptorSetHandle(const SkTArray<uint32_t>& visibilities,
                                       GrVkDescriptorSetManager::Handle* handle);

    // Returns the compatible VkDescriptorSetLayout to use for uniform buffers. The caller does not
    // own the VkDescriptorSetLayout and thus should not delete it. This function should be used
    // when the caller needs the layout to create a VkPipelineLayout.
    VkDescriptorSetLayout getUniformDSLayout() const;

    // Returns the compatible VkDescriptorSetLayout to use for a specific sampler handle. The caller
    // does not own the VkDescriptorSetLayout and thus should not delete it. This function should be
    // used when the caller needs the layout to create a VkPipelineLayout.
    VkDescriptorSetLayout getSamplerDSLayout(const GrVkDescriptorSetManager::Handle&) const;

    // Returns a GrVkDescriptorSet that can be used for uniform buffers. The GrVkDescriptorSet
    // is already reffed for the caller.
    const GrVkDescriptorSet* getUniformDescriptorSet();

    // Returns a GrVkDescriptorSet that can be used for sampler descriptors that are compatible with
    // the GrVkDescriptorSetManager::Handle passed in. The GrVkDescriptorSet is already reffed for
    // the caller.
    const GrVkDescriptorSet* getSamplerDescriptorSet(const GrVkDescriptorSetManager::Handle&);


    // Signals that the descriptor set passed it, which is compatible with the passed in handle,
    // can be reused by the next allocation request.
    void recycleDescriptorSet(const GrVkDescriptorSet* descSet,
                              const GrVkDescriptorSetManager::Handle&);

    // Creates or finds free uniform buffer resources of size GrVkUniformBuffer::kStandardSize.
    // Anything larger will need to be created and released by the client.
    const GrVkResource* findOrCreateStandardUniformBufferResource();

    // Signals that the resource passed to it (which should be a uniform buffer resource)
    // can be reused by the next uniform buffer resource request.
    void recycleStandardUniformBufferResource(const GrVkResource*);

    // Destroy any cached resources. To be called before destroying the VkDevice.
    // The assumption is that all queues are idle and all command buffers are finished.
    // For resource tracing to work properly, this should be called after unrefing all other
    // resource usages.
    // If deviceLost is true, then resources will not be checked to see if they've finished
    // before deleting (see section 4.2.4 of the Vulkan spec).
    void destroyResources(bool deviceLost);

    // Abandon any cached resources. To be used when the context/VkDevice is lost.
    // For resource tracing to work properly, this should be called after unrefing all other
    // resource usages.
    void abandonResources();

private:
#ifdef SK_DEBUG
#define GR_PIPELINE_STATE_CACHE_STATS
#endif

    class PipelineStateCache : public ::SkNoncopyable {
    public:
        PipelineStateCache(GrVkGpu* gpu);
        ~PipelineStateCache();

        void abandon();
        void release();
        sk_sp<GrVkPipelineState> refPipelineState(const GrPipeline&,
                                                  const GrPrimitiveProcessor&,
                                                  GrPrimitiveType,
                                                  const GrVkRenderPass& renderPass);

    private:
        enum {
            // We may actually have kMaxEntries+1 PipelineStates in context because we create a new
            // PipelineState before evicting from the cache.
            kMaxEntries = 128,
        };

        struct Entry;

        struct DescHash {
            uint32_t operator()(const GrProgramDesc& desc) const {
                return SkOpts::hash_fn(desc.asKey(), desc.keyLength(), 0);
            }
        };

        SkLRUCache<const GrVkPipelineState::Desc, std::unique_ptr<Entry>, DescHash> fMap;

        GrVkGpu*                    fGpu;

#ifdef GR_PIPELINE_STATE_CACHE_STATS
        int                         fTotalRequests;
        int                         fCacheMisses;
#endif
    };

    class CompatibleRenderPassSet {
    public:
        // This will always construct the basic load store render pass (all attachments load and
        // store their data) so that there is at least one compatible VkRenderPass that can be used
        // with this set.
        CompatibleRenderPassSet(const GrVkGpu* gpu, const GrVkRenderTarget& target);

        bool isCompatible(const GrVkRenderTarget& target) const;

        GrVkRenderPass* getCompatibleRenderPass() const {
            // The first GrVkRenderpass should always exist since we create the basic load store
            // render pass on create
            SkASSERT(fRenderPasses[0]);
            return fRenderPasses[0];
        }

        GrVkRenderPass* getRenderPass(const GrVkGpu* gpu,
                                      const GrVkRenderPass::LoadStoreOps& colorOps,
                                      const GrVkRenderPass::LoadStoreOps& stencilOps);

        void releaseResources(const GrVkGpu* gpu);
        void abandonResources();

    private:
        SkSTArray<4, GrVkRenderPass*> fRenderPasses;
        int                           fLastReturnedIndex;
    };

    GrVkGpu* fGpu;

    // Central cache for creating pipelines
    VkPipelineCache fPipelineCache;

    // Cache of previously created copy pipelines
    SkTArray<GrVkCopyPipeline*> fCopyPipelines;

    SkSTArray<4, CompatibleRenderPassSet> fRenderPassArray;

    // Array of PrimaryCommandBuffers that are currently in flight
    SkSTArray<4, GrVkPrimaryCommandBuffer*, true> fActiveCommandBuffers;
    // Array of available primary command buffers that are not in flight
    SkSTArray<4, GrVkPrimaryCommandBuffer*, true> fAvailableCommandBuffers;

    // Array of available secondary command buffers
    SkSTArray<16, GrVkSecondaryCommandBuffer*, true> fAvailableSecondaryCommandBuffers;

    // Array of available uniform buffer resources
    SkSTArray<16, const GrVkResource*, true> fAvailableUniformBufferResources;

    // Stores GrVkSampler objects that we've already created so we can reuse them across multiple
    // GrVkPipelineStates
    SkTDynamicHash<GrVkSampler, uint16_t> fSamplers;

    // Cache of GrVkPipelineStates
    PipelineStateCache* fPipelineStateCache;

    SkSTArray<4, GrVkDescriptorSetManager, true> fDescriptorSetManagers;

    GrVkDescriptorSetManager::Handle fUniformDSHandle;
};

#endif