/*
 * 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/TextureProxy.h"

#include "include/gpu/graphite/Recorder.h"
#include "src/core/SkMipmap.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/ResourceProvider.h"
#include "src/gpu/graphite/ScratchResourceManager.h"
#include "src/gpu/graphite/Texture.h"
#include "src/gpu/graphite/TextureUtils.h"

namespace skgpu::graphite {

TextureProxy::TextureProxy(SkISize dimensions,
                           const TextureInfo& info,
                           std::string_view label,
                           skgpu::Budgeted budgeted)
        : fDimensions(dimensions)
        , fInfo(info)
        , fLabel(label)
        , fBudgeted(budgeted)
        , fVolatile(Volatile::kNo) {
    SkASSERT(fInfo.isValid());
}

TextureProxy::TextureProxy(sk_sp<Texture> texture)
        : fDimensions(texture->dimensions())
        , fInfo(texture->textureInfo())
        , fLabel(texture->getLabel())
        , fBudgeted(texture->budgeted())
        , fVolatile(Volatile::kNo)
        , fTexture(std::move(texture)) {
    SkASSERT(fInfo.isValid());
}

TextureProxy::TextureProxy(SkISize dimensions,
                           const TextureInfo& textureInfo,
                           skgpu::Budgeted budgeted,
                           Volatile isVolatile,
                           LazyInstantiateCallback&& callback)
        : fDimensions(dimensions)
        , fInfo(textureInfo)
        , fBudgeted(budgeted)
        , fVolatile(isVolatile)
        , fLazyInstantiateCallback(std::move(callback)) {
    SkASSERT(fInfo.isValid());
    SkASSERT(fLazyInstantiateCallback);
}

TextureProxy::~TextureProxy() {}

SkISize TextureProxy::dimensions() const {
    SkASSERT(!this->isFullyLazy() || this->isInstantiated());
    return this->isInstantiated() ? fTexture->dimensions() : fDimensions;
}

bool TextureProxy::isLazy() const {
    return SkToBool(fLazyInstantiateCallback);
}

bool TextureProxy::isFullyLazy() const {
    bool result = fDimensions.width() < 0;
    SkASSERT(result == (fDimensions.height() < 0));
    SkASSERT(!result || this->isLazy());
    return result;
}

bool TextureProxy::isVolatile() const {
    SkASSERT(fVolatile == Volatile::kNo || SkToBool(fLazyInstantiateCallback));

    return fVolatile == Volatile::kYes;
}

size_t TextureProxy::uninstantiatedGpuMemorySize() const {
    return ComputeSize(fDimensions, fInfo);
}

bool TextureProxy::instantiate(ResourceProvider* resourceProvider) {
    SkASSERT(!this->isLazy());

    if (fTexture) {
        return true;
    }

    // TODO(389908374): Once all tasks use the ScratchResourceManager, this can be updated to just
    // finding and creating a non-shareable AND non-budgeted texture.
    fTexture = resourceProvider->findOrCreateNonShareableTexture(
            fDimensions, fInfo, fLabel, fBudgeted);
    if (!fTexture) {
        return false;
    }
    SkDEBUGCODE(this->validateTexture(fTexture.get()));
    return true;
}

bool TextureProxy::lazyInstantiate(ResourceProvider* resourceProvider) {
    SkASSERT(this->isLazy());

    if (fTexture) {
        return true;
    }

    fTexture = fLazyInstantiateCallback(resourceProvider);
    if (!fTexture) {
        return false;
    }
    SkDEBUGCODE(this->validateTexture(fTexture.get()));
    return true;
}

bool TextureProxy::InstantiateIfNotLazy(ResourceProvider* resourceProvider,
                                        TextureProxy* textureProxy) {
    if (textureProxy->isLazy()) {
        return true;
    }

    return textureProxy->instantiate(resourceProvider);
}

bool TextureProxy::InstantiateIfNotLazy(ScratchResourceManager* scratchManager,
                                        TextureProxy* textureProxy) {
    if (textureProxy->isLazy() || textureProxy->isInstantiated()) {
        return true;
    }

    textureProxy->fTexture = scratchManager->getScratchTexture(textureProxy->dimensions(),
                                                               textureProxy->textureInfo(),
                                                               textureProxy->fLabel);
    if (!textureProxy->fTexture) {
        return false;
    }
    SkDEBUGCODE(textureProxy->validateTexture(textureProxy->fTexture.get()));
    return true;
}


void TextureProxy::deinstantiate() {
    SkASSERT(fVolatile == Volatile::kYes && SkToBool(fLazyInstantiateCallback));

    fTexture.reset();
}

sk_sp<Texture> TextureProxy::refTexture() const {
    return fTexture;
}

const Texture* TextureProxy::texture() const {
    return fTexture.get();
}

sk_sp<TextureProxy> TextureProxy::Make(const Caps* caps,
                                       ResourceProvider* resourceProvider,
                                       SkISize dimensions,
                                       const TextureInfo& textureInfo,
                                       std::string_view label,
                                       skgpu::Budgeted budgeted) {
    if (dimensions.width() < 1 || dimensions.height() < 1 ||
        dimensions.width() > caps->maxTextureSize() ||
        dimensions.height() > caps->maxTextureSize() ||
        !textureInfo.isValid()) {
        return nullptr;
    }

    sk_sp<TextureProxy> proxy{new TextureProxy(dimensions,
                                               textureInfo,
                                               std::move(label),
                                               budgeted)};
    if (budgeted == Budgeted::kNo) {
        // Instantiate immediately to avoid races later on if the client starts to use the wrapping
        // object on multiple threads.
        if (!proxy->instantiate(resourceProvider)) {
            return nullptr;
        }
    }
    return proxy;
}

sk_sp<TextureProxy> TextureProxy::MakeLazy(const Caps* caps,
                                           SkISize dimensions,
                                           const TextureInfo& textureInfo,
                                           skgpu::Budgeted budgeted,
                                           Volatile isVolatile,
                                           LazyInstantiateCallback&& callback) {
    SkASSERT(textureInfo.isValid());
    if (dimensions.width() < 1 || dimensions.height() < 1 ||
        dimensions.width() > caps->maxTextureSize() ||
        dimensions.height() > caps->maxTextureSize()) {
        return nullptr;
    }

    return sk_sp<TextureProxy>(new TextureProxy(dimensions,
                                                textureInfo,
                                                budgeted,
                                                isVolatile,
                                                std::move(callback)));
}

sk_sp<TextureProxy> TextureProxy::MakeFullyLazy(const TextureInfo& textureInfo,
                                                skgpu::Budgeted budgeted,
                                                Volatile isVolatile,
                                                LazyInstantiateCallback&& callback) {
    SkASSERT(textureInfo.isValid());

    return sk_sp<TextureProxy>(new TextureProxy(SkISize::Make(-1, -1),
                                                textureInfo,
                                                budgeted,
                                                isVolatile,
                                                std::move(callback)));
}

sk_sp<TextureProxy> TextureProxy::Wrap(sk_sp<Texture> texture) {
    return sk_sp<TextureProxy>(new TextureProxy(std::move(texture)));
}

#ifdef SK_DEBUG
void TextureProxy::validateTexture(const Texture* texture) {
    SkASSERT(this->isFullyLazy() || fDimensions == texture->dimensions());
    SkASSERTF(fInfo.canBeFulfilledBy(texture->textureInfo()),
              "proxy->fInfo[%s] incompatible with texture->fInfo[%s]",
              fInfo.toString().c_str(),
              texture->textureInfo().toString().c_str());
}
#endif

} // namespace skgpu::graphite
