/*
 * 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 "include/gpu/graphite/Recording.h"

#include "src/gpu/RefCntedCallback.h"
#include "src/gpu/graphite/CommandBuffer.h"
#include "src/gpu/graphite/ContextPriv.h"
#include "src/gpu/graphite/Log.h"
#include "src/gpu/graphite/RecordingPriv.h"
#include "src/gpu/graphite/Resource.h"
#include "src/gpu/graphite/ResourceProvider.h"
#include "src/gpu/graphite/Surface_Graphite.h"
#include "src/gpu/graphite/TaskGraph.h"
#include "src/gpu/graphite/Texture.h"
#include "src/gpu/graphite/TextureProxy.h"

#include <unordered_set>

namespace skgpu::graphite {

Recording::Recording(std::unique_ptr<TaskGraph> graph,
                     std::unordered_set<sk_sp<TextureProxy>, ProxyHash>&& nonVolatileLazyProxies,
                     std::unordered_set<sk_sp<TextureProxy>, ProxyHash>&& volatileLazyProxies,
                     std::unique_ptr<LazyProxyData> targetProxyData,
                     SkTArray<sk_sp<RefCntedCallback>>&& finishedProcs)
        : fGraph(std::move(graph))
        , fNonVolatileLazyProxies(std::move(nonVolatileLazyProxies))
        , fVolatileLazyProxies(std::move(volatileLazyProxies))
        , fTargetProxyData(std::move(targetProxyData))
        , fFinishedProcs(std::move(finishedProcs)) {}

Recording::~Recording() {
    // Any finished procs that haven't been passed to a CommandBuffer fail
    this->priv().setFailureResultForFinishedProcs();
}

#if GRAPHITE_TEST_UTILS
bool Recording::isTargetProxyInstantiated() const {
    return fTargetProxyData->lazyProxy()->isInstantiated();
}
#endif

////////////////////////////////////////////////////////////////////////////////
bool RecordingPriv::hasNonVolatileLazyProxies() const {
    return !fRecording->fNonVolatileLazyProxies.empty();
}

bool RecordingPriv::instantiateNonVolatileLazyProxies(ResourceProvider* resourceProvider) {
    SkASSERT(this->hasNonVolatileLazyProxies());

    for (auto proxy : fRecording->fNonVolatileLazyProxies) {
        if (!proxy->lazyInstantiate(resourceProvider)) {
            return false;
        }
    }

    // Note: once all the lazy proxies have been instantiated, that's it - there are no more
    // chances to instantiate.
    fRecording->fNonVolatileLazyProxies.clear();
    return true;
}

bool RecordingPriv::hasVolatileLazyProxies() const {
    return !fRecording->fVolatileLazyProxies.empty();
}

bool RecordingPriv::instantiateVolatileLazyProxies(ResourceProvider* resourceProvider) {
    SkASSERT(this->hasVolatileLazyProxies());

    for (auto proxy : fRecording->fVolatileLazyProxies) {
        if (!proxy->lazyInstantiate(resourceProvider)) {
            return false;
        }
    }

    return true;
}

void RecordingPriv::deinstantiateVolatileLazyProxies() {
    if (!this->hasVolatileLazyProxies()) {
        return;
    }

    for (auto proxy : fRecording->fVolatileLazyProxies) {
        SkASSERT(proxy->isVolatile());
        proxy->deinstantiate();
    }
}

void RecordingPriv::setFailureResultForFinishedProcs() {
    for (int i = 0; i < fRecording->fFinishedProcs.size(); ++i) {
        fRecording->fFinishedProcs[i]->setFailureResult();
    }
    fRecording->fFinishedProcs.clear();
}

#if GRAPHITE_TEST_UTILS
int RecordingPriv::numVolatilePromiseImages() const {
    return fRecording->fVolatileLazyProxies.size();
}

int RecordingPriv::numNonVolatilePromiseImages() const {
    return fRecording->fNonVolatileLazyProxies.size();
}

bool RecordingPriv::hasTasks() const { return fRecording->fGraph->hasTasks(); }
#endif

bool RecordingPriv::addCommands(Context* context,
                                CommandBuffer* commandBuffer,
                                Surface* replaySurface,
                                SkIVector replayTranslation) {
    AutoDeinstantiateTextureProxy autoDeinstantiateTargetProxy(
            fRecording->fTargetProxyData ? fRecording->fTargetProxyData->lazyProxy() : nullptr);

    const Texture* replayTarget = nullptr;
    SkASSERT(SkToBool(fRecording->fTargetProxyData) == SkToBool(replaySurface));
    ResourceProvider* resourceProvider = context->priv().resourceProvider();
    if (fRecording->fTargetProxyData) {
        if (!replaySurface) {
            SKGPU_LOG_E("No surface provided to instantiate target texture proxy.");
            return false;
        }
        TextureProxy* surfaceTexture = replaySurface->backingTextureProxy();
        if (!surfaceTexture->instantiate(resourceProvider)) {
            SKGPU_LOG_E("Could not instantiate target texture proxy.");
            return false;
        }
        if (!fRecording->fTargetProxyData->lazyInstantiate(resourceProvider,
                                                           surfaceTexture->refTexture())) {
            SKGPU_LOG_E("Could not instantiate deferred texture proxy.");
            return false;
        }
        replayTarget = surfaceTexture->texture();
    }

    if (!fRecording->fGraph->addCommands(
                context, commandBuffer, {replayTarget, replayTranslation})) {
        return false;
    }
    for (size_t i = 0; i < fRecording->fExtraResourceRefs.size(); ++i) {
        commandBuffer->trackResource(fRecording->fExtraResourceRefs[i]);
    }
    for (int i = 0; i < fRecording->fFinishedProcs.size(); ++i) {
        commandBuffer->addFinishedProc(std::move(fRecording->fFinishedProcs[i]));
    }
    fRecording->fFinishedProcs.clear();

    return true;
}

void RecordingPriv::addResourceRef(sk_sp<Resource> resource) {
    fRecording->fExtraResourceRefs.push_back(std::move(resource));
}

void RecordingPriv::addTask(sk_sp<Task> task) {
    fRecording->fGraph->prepend(std::move(task));
}

Recording::LazyProxyData::LazyProxyData(const TextureInfo& textureInfo) {
    fTargetProxy = TextureProxy::MakeFullyLazy(
            textureInfo, skgpu::Budgeted::kNo, Volatile::kYes, [this](ResourceProvider*) {
                SkASSERT(SkToBool(fTarget));
                return std::move(fTarget);
            });
}

TextureProxy* Recording::LazyProxyData::lazyProxy() { return fTargetProxy.get(); }

sk_sp<TextureProxy> Recording::LazyProxyData::refLazyProxy() { return fTargetProxy; }

bool Recording::LazyProxyData::lazyInstantiate(ResourceProvider* resourceProvider,
                                               sk_sp<Texture> texture) {
    fTarget = std::move(texture);
    return fTargetProxy->lazyInstantiate(resourceProvider);
}

} // namespace skgpu::graphite
