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

#include "include/gpu/GrContextOptions.h"
#include "include/gpu/GrDirectContext.h"
#include "src/core/SkOpts.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/GrFragmentProcessor.h"
#include "src/gpu/ganesh/GrProcessor.h"
#include "src/gpu/ganesh/GrRenderTarget.h"
#include "src/gpu/ganesh/GrStencilSettings.h"
#include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h"
#include "src/gpu/ganesh/vk/GrVkGpu.h"
#include "src/gpu/ganesh/vk/GrVkPipelineState.h"
#include "src/gpu/ganesh/vk/GrVkPipelineStateBuilder.h"
#include "src/gpu/ganesh/vk/GrVkResourceProvider.h"

#ifdef SK_DEBUG
// Display pipeline state cache usage
static const bool c_DisplayVkPipelineCache{false};
#endif

struct GrVkResourceProvider::PipelineStateCache::Entry {
    Entry(GrVkGpu* gpu, GrVkPipelineState* pipelineState)
    : fGpu(gpu)
    , fPipelineState(pipelineState) {}

    ~Entry() {
        if (fPipelineState) {
            fPipelineState->freeGPUResources(fGpu);
        }
    }

    GrVkGpu* fGpu;
    std::unique_ptr<GrVkPipelineState> fPipelineState;
};

GrVkResourceProvider::PipelineStateCache::PipelineStateCache(GrVkGpu* gpu)
    : fMap(gpu->getContext()->priv().options().fRuntimeProgramCacheSize)
    , fGpu(gpu) {
}

GrVkResourceProvider::PipelineStateCache::~PipelineStateCache() {
    SkASSERT(0 == fMap.count());
    // dump stats
#ifdef SK_DEBUG
    if (c_DisplayVkPipelineCache) {
        using CacheResult = Stats::ProgramCacheResult;

        int misses = fStats.numInlineProgramCacheResult(CacheResult::kMiss) +
                     fStats.numPreProgramCacheResult(CacheResult::kMiss);

        int total = misses + fStats.numInlineProgramCacheResult(CacheResult::kHit) +
                             fStats.numPreProgramCacheResult(CacheResult::kHit);

        SkDebugf("--- Pipeline State Cache ---\n");
        SkDebugf("Total requests: %d\n", total);
        SkDebugf("Cache misses: %d\n", misses);
        SkDebugf("Cache miss %%: %f\n", (total > 0) ? 100.f * misses / total : 0.0f);
    }
#endif
}

void GrVkResourceProvider::PipelineStateCache::release() {
    fMap.reset();
}

GrVkPipelineState* GrVkResourceProvider::PipelineStateCache::findOrCreatePipelineState(
        GrRenderTarget* renderTarget,
        const GrProgramInfo& programInfo,
        VkRenderPass compatibleRenderPass,
        bool overrideSubpassForResolveLoad) {
#ifdef SK_DEBUG
    if (programInfo.isStencilEnabled()) {
        SkASSERT(renderTarget->getStencilAttachment(programInfo.numSamples() > 1));
        SkASSERT(renderTarget->numStencilBits(programInfo.numSamples() > 1) == 8);
        SkASSERT(renderTarget->getStencilAttachment(programInfo.numSamples() > 1)->numSamples() ==
                 programInfo.numSamples());
    }
#endif

    auto flags = overrideSubpassForResolveLoad
            ? GrCaps::ProgramDescOverrideFlags::kVulkanHasResolveLoadSubpass
            : GrCaps::ProgramDescOverrideFlags::kNone;

    GrProgramDesc desc = fGpu->caps()->makeDesc(renderTarget, programInfo, flags);
    if (!desc.isValid()) {
        GrCapsDebugf(fGpu->caps(), "Failed to build vk program descriptor!\n");
        return nullptr;
    }

    Stats::ProgramCacheResult stat;
    auto tmp = this->findOrCreatePipelineStateImpl(desc, programInfo, compatibleRenderPass,
                                                   overrideSubpassForResolveLoad, &stat);
    if (!tmp) {
        fStats.incNumInlineCompilationFailures();
    } else {
        fStats.incNumInlineProgramCacheResult(stat);
    }

    return tmp;
}

GrVkPipelineState* GrVkResourceProvider::PipelineStateCache::findOrCreatePipelineStateImpl(
        const GrProgramDesc& desc,
        const GrProgramInfo& programInfo,
        VkRenderPass compatibleRenderPass,
        bool overrideSubpassForResolveLoad,
        Stats::ProgramCacheResult* stat) {
    if (stat) {
        *stat = Stats::ProgramCacheResult::kHit;
    }

    std::unique_ptr<Entry>* entry = fMap.find(desc);
    if (!entry) {
        if (stat) {
            *stat = Stats::ProgramCacheResult::kMiss;
        }
        GrVkPipelineState* pipelineState(GrVkPipelineStateBuilder::CreatePipelineState(
                fGpu, desc, programInfo, compatibleRenderPass, overrideSubpassForResolveLoad));
        if (!pipelineState) {
            return nullptr;
        }
        entry = fMap.insert(desc, std::make_unique<Entry>(fGpu, pipelineState));
        return (*entry)->fPipelineState.get();
    }
    return (*entry)->fPipelineState.get();
}
