/*
 * Copyright 2020 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/d3d/GrD3DTexture.h"

#include "src/gpu/GrTexture.h"
#include "src/gpu/d3d/GrD3DGpu.h"
#include "src/gpu/d3d/GrD3DUtil.h"

#include "include/gpu/d3d/GrD3DTypes.h"

// Because this class is virtually derived from GrSurface we must explicitly call its constructor.
GrD3DTexture::GrD3DTexture(GrD3DGpu* gpu,
                           SkBudgeted budgeted,
                           SkISize dimensions,
                           const GrD3DTextureResourceInfo& info,
                           sk_sp<GrD3DResourceState> state,
                           const GrD3DDescriptorHeap::CPUHandle& shaderResourceView,
                           GrMipmapStatus mipmapStatus)
        : GrSurface(gpu, dimensions, info.fProtected)
        , GrD3DTextureResource(info, std::move(state))
        , INHERITED(gpu, dimensions, info.fProtected, GrTextureType::k2D, mipmapStatus)
        , fShaderResourceView(shaderResourceView) {
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == info.fLevelCount));
    this->registerWithCache(budgeted);
    if (GrDxgiFormatIsCompressed(info.fFormat)) {
        this->setReadOnly();
    }
}

GrD3DTexture::GrD3DTexture(GrD3DGpu* gpu, SkISize dimensions, const GrD3DTextureResourceInfo& info,
                           sk_sp<GrD3DResourceState> state,
                           const GrD3DDescriptorHeap::CPUHandle& shaderResourceView,
                           GrMipmapStatus mipmapStatus, GrWrapCacheable cacheable,
                           GrIOType ioType)
        : GrSurface(gpu, dimensions, info.fProtected)
        , GrD3DTextureResource(info, std::move(state))
        , INHERITED(gpu, dimensions, info.fProtected, GrTextureType::k2D, mipmapStatus)
        , fShaderResourceView(shaderResourceView) {
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == info.fLevelCount));
    if (ioType == kRead_GrIOType) {
        this->setReadOnly();
    }
    this->registerWithCacheWrapped(cacheable);
}

// Because this class is virtually derived from GrSurface we must explicitly call its constructor.
GrD3DTexture::GrD3DTexture(GrD3DGpu* gpu,
                           SkISize dimensions,
                           const GrD3DTextureResourceInfo& info,
                           sk_sp<GrD3DResourceState> state,
                           const GrD3DDescriptorHeap::CPUHandle& shaderResourceView,
                           GrMipmapStatus mipmapStatus)
        : GrSurface(gpu, dimensions, info.fProtected)
        , GrD3DTextureResource(info, state)
        , INHERITED(gpu, dimensions, info.fProtected, GrTextureType::k2D, mipmapStatus)
        , fShaderResourceView(shaderResourceView) {
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == info.fLevelCount));
}

sk_sp<GrD3DTexture> GrD3DTexture::MakeNewTexture(GrD3DGpu* gpu, SkBudgeted budgeted,
                                                 SkISize dimensions,
                                                 const D3D12_RESOURCE_DESC& desc,
                                                 GrProtected isProtected,
                                                 GrMipmapStatus mipmapStatus) {
    GrD3DTextureResourceInfo info;
    if (!GrD3DTextureResource::InitTextureResourceInfo(gpu, desc,
                                                       D3D12_RESOURCE_STATE_COPY_DEST,
                                                       isProtected, nullptr, &info)) {
        return nullptr;
    }

    sk_sp<GrD3DResourceState> state(
            new GrD3DResourceState(static_cast<D3D12_RESOURCE_STATES>(info.fResourceState)));

    GrD3DDescriptorHeap::CPUHandle shaderResourceView =
            gpu->resourceProvider().createShaderResourceView(info.fResource.get());

    GrD3DTexture* tex = new GrD3DTexture(gpu, budgeted, dimensions, info, std::move(state),
                                         shaderResourceView, mipmapStatus);

    return sk_sp<GrD3DTexture>(tex);
}

sk_sp<GrD3DTexture> GrD3DTexture::MakeWrappedTexture(GrD3DGpu* gpu,
                                                     SkISize dimensions,
                                                     GrWrapCacheable cacheable,
                                                     GrIOType ioType,
                                                     const GrD3DTextureResourceInfo& info,
                                                     sk_sp<GrD3DResourceState> state) {
    // TODO: If a client uses their own heap to allocate, how do we manage that?
    // Adopted textures require both image and allocation because we're responsible for freeing
    //SkASSERT(info.fTexture &&
    //         (kBorrow_GrWrapOwnership == wrapOwnership || VK_NULL_HANDLE != info.fAlloc.fMemory));

    GrMipmapStatus mipmapStatus = info.fLevelCount > 1 ? GrMipmapStatus::kValid
                                                       : GrMipmapStatus::kNotAllocated;

    GrD3DDescriptorHeap::CPUHandle shaderResourceView =
            gpu->resourceProvider().createShaderResourceView(info.fResource.get());

    return sk_sp<GrD3DTexture>(new GrD3DTexture(gpu, dimensions, info, std::move(state),
                                                shaderResourceView, mipmapStatus, cacheable,
                                                ioType));
}

void GrD3DTexture::onRelease() {
    // We're about to be severed from our GrManagedResource. If there are "finish" idle procs we
    // have to decide who will handle them. If the resource is still tied to a command buffer we let
    // it handle them. Otherwise, we handle them.
    SkASSERT(this->resource());
    if (this->resource()->isQueuedForWorkOnGpu()) {
        this->removeFinishIdleProcs();
    }

    GrD3DGpu* gpu = this->getD3DGpu();
    gpu->resourceProvider().recycleConstantOrShaderView(fShaderResourceView);
    this->releaseResource(gpu);

    INHERITED::onRelease();
}

void GrD3DTexture::onAbandon() {
    // We're about to be severed from our GrManagedResource. If there are "finish" idle procs we
    // have to decide who will handle them. If the resource is still tied to a command buffer we let
    // it handle them. Otherwise, we handle them.
    SkASSERT(this->resource());
    if (this->resource()->isQueuedForWorkOnGpu()) {
        this->removeFinishIdleProcs();
    }

    GrD3DGpu* gpu = this->getD3DGpu();
    gpu->resourceProvider().recycleConstantOrShaderView(fShaderResourceView);
    this->releaseResource(gpu);
    INHERITED::onAbandon();
}

GrBackendTexture GrD3DTexture::getBackendTexture() const {
    return GrBackendTexture(this->width(), this->height(), fInfo, this->grD3DResourceState());
}

GrD3DGpu* GrD3DTexture::getD3DGpu() const {
    SkASSERT(!this->wasDestroyed());
    return static_cast<GrD3DGpu*>(this->getGpu());
}

void GrD3DTexture::addIdleProc(sk_sp<GrRefCntedCallback> idleProc, IdleState type) {
    INHERITED::addIdleProc(idleProc, type);
    if (type == IdleState::kFinished) {
        this->addResourceIdleProc(this, std::move(idleProc));
    }
}

void GrD3DTexture::callIdleProcsOnBehalfOfResource() {
    // If we got here then the resource is being removed from its last command buffer and the
    // texture is idle in the cache. Any kFlush idle procs should already have been called. So
    // the texture and resource should have the same set of procs.
    SkASSERT(this->resourceIdleProcCnt() == fIdleProcs.count());
#ifdef SK_DEBUG
    for (int i = 0; i < fIdleProcs.count(); ++i) {
        SkASSERT(fIdleProcs[i] == this->resourceIdleProc(i));
    }
#endif
    fIdleProcs.reset();
    this->resetResourceIdleProcs();
}

void GrD3DTexture::willRemoveLastRef() {
    if (!fIdleProcs.count()) {
        return;
    }
    // This is called when the GrTexture is purgeable. However, we need to check whether the
    // Resource is still owned by any command buffers. If it is then it will call the proc.
    if (!this->resourceIsQueuedForWorkOnGpu()) {
        // Everything must go!
        fIdleProcs.reset();
        this->resetResourceIdleProcs();
    } else {
        // The procs that should be called on flush but not finish are those that are owned
        // by the GrD3DTexture and not the Resource. We do this by copying the resource's array
        // and thereby dropping refs to procs we own but the resource does not.
        fIdleProcs.reset(this->resourceIdleProcCnt());
        for (int i = 0; i < fIdleProcs.count(); ++i) {
            fIdleProcs[i] = this->resourceIdleProc(i);
        }
    }
}

void GrD3DTexture::removeFinishIdleProcs() {
    // This should only be called by onRelease/onAbandon when we have already checked for a
    // resource.
    SkSTArray<4, sk_sp<GrRefCntedCallback>> procsToKeep;
    int resourceIdx = 0;
    // The idle procs that are common between the GrD3DTexture and its Resource should be found in
    // the same order.
    for (int i = 0; i < fIdleProcs.count(); ++i) {
        if (fIdleProcs[i] == this->resourceIdleProc(resourceIdx)) {
            ++resourceIdx;
        } else {
            procsToKeep.push_back(fIdleProcs[i]);
        }
    }
    SkASSERT(resourceIdx == this->resourceIdleProcCnt());
    fIdleProcs = procsToKeep;
}
