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

#include "src/gpu/graphite/ResourceProvider.h"

#include "include/core/SkSamplingOptions.h"
#include "include/core/SkTileMode.h"
#include "include/gpu/graphite/BackendTexture.h"
#include "src/gpu/graphite/Buffer.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/graphite/CommandBuffer.h"
#include "src/gpu/graphite/ComputePipeline.h"
#include "src/gpu/graphite/ContextPriv.h"
#include "src/gpu/graphite/GlobalCache.h"
#include "src/gpu/graphite/GraphicsPipeline.h"
#include "src/gpu/graphite/GraphicsPipelineDesc.h"
#include "src/gpu/graphite/Log.h"
#include "src/gpu/graphite/ResourceCache.h"
#include "src/gpu/graphite/Sampler.h"
#include "src/gpu/graphite/SharedContext.h"
#include "src/gpu/graphite/Texture.h"
#include "src/sksl/SkSLCompiler.h"

namespace skgpu::graphite {

ResourceProvider::ResourceProvider(SharedContext* sharedContext,
                                   SingleOwner* singleOwner)
        : fSharedContext(sharedContext)
        , fResourceCache(ResourceCache::Make(singleOwner))
        , fCompiler(std::make_unique<SkSL::Compiler>(fSharedContext->caps()->shaderCaps())) {}

ResourceProvider::~ResourceProvider() {
    fResourceCache->shutdown();
}

sk_sp<GraphicsPipeline> ResourceProvider::findOrCreateGraphicsPipeline(
        const RuntimeEffectDictionary* runtimeDict,
        const GraphicsPipelineDesc& pipelineDesc,
        const RenderPassDesc& renderPassDesc) {
    auto globalCache = fSharedContext->globalCache();
    UniqueKey pipelineKey = fSharedContext->caps()->makeGraphicsPipelineKey(pipelineDesc,
                                                                            renderPassDesc);
    sk_sp<GraphicsPipeline> pipeline = globalCache->findGraphicsPipeline(pipelineKey);
    if (!pipeline) {
        // Haven't encountered this pipeline, so create a new one. Since pipelines are shared
        // across Recorders, we could theoretically create equivalent pipelines on different
        // threads. If this happens, GlobalCache returns the first-through-gate pipeline and we
        // discard the redundant pipeline. While this is wasted effort in the rare event of a race,
        // it allows pipeline creation to be performed without locking the global cache.
        pipeline = this->createGraphicsPipeline(runtimeDict, pipelineDesc, renderPassDesc);
        if (pipeline) {
            // TODO: Should we store a null pipeline if we failed to create one so that subsequent
            // usage immediately sees that the pipeline cannot be created, vs. retrying every time?
            pipeline = globalCache->addGraphicsPipeline(pipelineKey, std::move(pipeline));
        }
    }
    return pipeline;
}

sk_sp<ComputePipeline> ResourceProvider::findOrCreateComputePipeline(
        const ComputePipelineDesc& pipelineDesc) {
    auto globalCache = fSharedContext->globalCache();
    UniqueKey pipelineKey = fSharedContext->caps()->makeComputePipelineKey(pipelineDesc);
    sk_sp<ComputePipeline> pipeline = globalCache->findComputePipeline(pipelineKey);
    if (!pipeline) {
        pipeline = this->createComputePipeline(pipelineDesc);
        if (pipeline) {
            pipeline = globalCache->addComputePipeline(pipelineKey, std::move(pipeline));
        }
    }
    return pipeline;
}

////////////////////////////////////////////////////////////////////////////////////////////////

sk_sp<Texture> ResourceProvider::findOrCreateScratchTexture(SkISize dimensions,
                                                            const TextureInfo& info,
                                                            skgpu::Budgeted budgeted) {
    SkASSERT(info.isValid());

    static const ResourceType kType = GraphiteResourceKey::GenerateResourceType();

    GraphiteResourceKey key;
    // Scratch textures are not shareable
    fSharedContext->caps()->buildKeyForTexture(dimensions, info, kType, Shareable::kNo, &key);

    return this->findOrCreateTextureWithKey(dimensions, info, key, budgeted);
}

sk_sp<Texture> ResourceProvider::findOrCreateDepthStencilAttachment(SkISize dimensions,
                                                                    const TextureInfo& info) {
    SkASSERT(info.isValid());

    static const ResourceType kType = GraphiteResourceKey::GenerateResourceType();

    GraphiteResourceKey key;
    // We always make depth and stencil attachments shareable. Between any render pass the values
    // are reset. Thus it is safe to be used by multiple different render passes without worry of
    // stomping on each other's data.
    fSharedContext->caps()->buildKeyForTexture(dimensions, info, kType, Shareable::kYes, &key);

    return this->findOrCreateTextureWithKey(dimensions, info, key, skgpu::Budgeted::kYes);
}

sk_sp<Texture> ResourceProvider::findOrCreateDiscardableMSAAAttachment(SkISize dimensions,
                                                                       const TextureInfo& info) {
    SkASSERT(info.isValid());

    static const ResourceType kType = GraphiteResourceKey::GenerateResourceType();

    GraphiteResourceKey key;
    // We always make discardable msaa attachments shareable. Between any render pass we discard
    // the values of the MSAA texture. Thus it is safe to be used by multiple different render
    // passes without worry of stomping on each other's data. It is the callings code responsiblity
    // to populate the discardable MSAA texture with data at the start of the render pass.
    fSharedContext->caps()->buildKeyForTexture(dimensions, info, kType, Shareable::kYes, &key);

    return this->findOrCreateTextureWithKey(dimensions, info, key, skgpu::Budgeted::kYes);
}

sk_sp<Texture> ResourceProvider::findOrCreateTextureWithKey(SkISize dimensions,
                                                            const TextureInfo& info,
                                                            const GraphiteResourceKey& key,
                                                            skgpu::Budgeted budgeted) {
    // If the resource is shareable it should be budgeted since it shouldn't be backing any client
    // owned object.
    SkASSERT(key.shareable() == Shareable::kNo || budgeted == skgpu::Budgeted::kYes);

    if (Resource* resource = fResourceCache->findAndRefResource(key, budgeted)) {
        return sk_sp<Texture>(static_cast<Texture*>(resource));
    }

    auto tex = this->createTexture(dimensions, info, budgeted);
    if (!tex) {
        return nullptr;
    }

    tex->setKey(key);
    fResourceCache->insertResource(tex.get());

    return tex;
}

sk_sp<Sampler> ResourceProvider::findOrCreateCompatibleSampler(const SkSamplingOptions& smplOptions,
                                                               SkTileMode xTileMode,
                                                               SkTileMode yTileMode) {
    static const ResourceType kType = GraphiteResourceKey::GenerateResourceType();

    GraphiteResourceKey key;
    {
        constexpr int kNumTileModeBits   = SkNextLog2_portable(int(SkTileMode::kLastTileMode)+1);
        constexpr int kNumFilterModeBits = SkNextLog2_portable(int(SkFilterMode::kLast)+1);
        constexpr int kNumMipmapModeBits = SkNextLog2_portable(int(SkMipmapMode::kLast)+1);

        constexpr int kTileModeXShift  = 0;
        constexpr int kTileModeYShift  = kTileModeXShift  + kNumTileModeBits;
        constexpr int kFilterModeShift = kTileModeYShift  + kNumTileModeBits;
        constexpr int kMipmapModeShift = kFilterModeShift + kNumFilterModeBits;

        static_assert(kMipmapModeShift + kNumMipmapModeBits <= 32);

        // For the key we need only one uint32_t.
        // TODO: add aniso value when used
        static_assert(sizeof(uint32_t) == 4);

        GraphiteResourceKey::Builder builder(&key, kType, 1, Shareable::kYes);
        uint32_t myKey =
        (static_cast<uint32_t>(xTileMode) << kTileModeXShift) |
                     (static_cast<uint32_t>(yTileMode) << kTileModeYShift) |
                     (static_cast<uint32_t>(smplOptions.filter) << kFilterModeShift) |
                     (static_cast<uint32_t>(smplOptions.mipmap) << kMipmapModeShift);
        builder[0] = myKey;
    }

    skgpu::Budgeted budgeted = skgpu::Budgeted::kYes;
    if (Resource* resource = fResourceCache->findAndRefResource(key, budgeted)) {
        return sk_sp<Sampler>(static_cast<Sampler*>(resource));
    }
    sk_sp<Sampler> sampler = this->createSampler(smplOptions, xTileMode, yTileMode);
    if (!sampler) {
        return nullptr;
    }

    sampler->setKey(key);
    fResourceCache->insertResource(sampler.get());
    return sampler;
}

sk_sp<Buffer> ResourceProvider::findOrCreateBuffer(size_t size,
                                                   BufferType type,
                                                   PrioritizeGpuReads prioritizeGpuReads) {
    static const ResourceType kType = GraphiteResourceKey::GenerateResourceType();

#ifdef SK_DEBUG
    // The size should already be aligned.
    size_t minAlignment = 1;
    if (type == BufferType::kStorage || type == BufferType::kIndirect ||
        type == BufferType::kVertexStorage || type == BufferType::kIndexStorage) {
        minAlignment = std::max(fSharedContext->caps()->requiredStorageBufferAlignment(),
                                minAlignment);
    } else if (type == BufferType::kUniform) {
        minAlignment = std::max(fSharedContext->caps()->requiredUniformBufferAlignment(),
                                minAlignment);
    } else if (type == BufferType::kXferCpuToGpu || type == BufferType::kXferGpuToCpu) {
        minAlignment = std::max(fSharedContext->caps()->requiredTransferBufferAlignment(),
                                minAlignment);
    }
    SkASSERT(size % minAlignment == 0);
#endif

    GraphiteResourceKey key;
    {
        // For the key we need ((sizeof(size_t) + (sizeof(uint32_t) - 1)) / (sizeof(uint32_t))
        // uint32_t's for the size and one uint32_t for the rest.
        static_assert(sizeof(uint32_t) == 4);
        static const int kSizeKeyNum32DataCnt = (sizeof(size_t) + 3) / 4;
        static const int kKeyNum32DataCnt =  kSizeKeyNum32DataCnt + 1;

        SkASSERT(static_cast<uint32_t>(type) < (1u << 4));
        SkASSERT(static_cast<uint32_t>(prioritizeGpuReads) < (1u << 1));

        GraphiteResourceKey::Builder builder(&key, kType, kKeyNum32DataCnt, Shareable::kNo);
        builder[0] = (static_cast<uint32_t>(type) << 0) |
                     (static_cast<uint32_t>(prioritizeGpuReads) << 4);
        size_t szKey = size;
        for (int i = 0; i < kSizeKeyNum32DataCnt; ++i) {
            builder[i + 1] = (uint32_t) szKey;

            // If size_t is 4 bytes, we cannot do a shift of 32 or else we get a warning/error that
            // shift amount is >= width of the type.
            if constexpr(kSizeKeyNum32DataCnt > 1) {
                szKey = szKey >> 32;
            }
        }
    }

    skgpu::Budgeted budgeted = skgpu::Budgeted::kYes;
    if (Resource* resource = fResourceCache->findAndRefResource(key, budgeted)) {
        return sk_sp<Buffer>(static_cast<Buffer*>(resource));
    }
    auto buffer = this->createBuffer(size, type, prioritizeGpuReads);
    if (!buffer) {
        return nullptr;
    }

    buffer->setKey(key);
    fResourceCache->insertResource(buffer.get());
    return buffer;
}

BackendTexture ResourceProvider::createBackendTexture(SkISize dimensions, const TextureInfo& info) {
    const auto maxTextureSize = fSharedContext->caps()->maxTextureSize();
    if (dimensions.isEmpty() ||
        dimensions.width()  > maxTextureSize ||
        dimensions.height() > maxTextureSize) {
        SKGPU_LOG_W("call to createBackendTexture has requested dimensions (%d, %d) larger than the"
                    " supported gpu max texture size: %d. Or the dimensions are empty.",
                    dimensions.fWidth, dimensions.fHeight, maxTextureSize);
        return {};
    }

    return this->onCreateBackendTexture(dimensions, info);
}

void ResourceProvider::deleteBackendTexture(BackendTexture& texture) {
    this->onDeleteBackendTexture(texture);
    // Invalidate the texture;
    texture = BackendTexture();
}

}  // namespace skgpu::graphite
