/*
 * 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/ganesh/d3d/GrD3DTextureRenderTarget.h"

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

GrD3DTextureRenderTarget::GrD3DTextureRenderTarget(
        GrD3DGpu* gpu,
        skgpu::Budgeted budgeted,
        SkISize dimensions,
        const GrD3DTextureResourceInfo& info,
        sk_sp<GrD3DResourceState> state,
        const GrD3DDescriptorHeap::CPUHandle& shaderResourceView,
        const GrD3DTextureResourceInfo& msaaInfo,
        sk_sp<GrD3DResourceState> msaaState,
        const GrD3DDescriptorHeap::CPUHandle& colorRenderTargetView,
        const GrD3DDescriptorHeap::CPUHandle& resolveRenderTargetView,
        GrMipmapStatus mipmapStatus,
        std::string_view label)
        : GrSurface(gpu, dimensions, info.fProtected, label)
        , GrD3DTextureResource(info, state)
        , GrD3DTexture(gpu, dimensions, info, state, shaderResourceView, mipmapStatus, label)
        , GrD3DRenderTarget(gpu,
                            dimensions,
                            info,
                            state,
                            msaaInfo,
                            std::move(msaaState),
                            colorRenderTargetView,
                            resolveRenderTargetView,
                            label) {
    SkASSERT(info.fProtected == msaaInfo.fProtected);
    this->registerWithCache(budgeted);
}

GrD3DTextureRenderTarget::GrD3DTextureRenderTarget(
        GrD3DGpu* gpu,
        skgpu::Budgeted budgeted,
        SkISize dimensions,
        const GrD3DTextureResourceInfo& info,
        sk_sp<GrD3DResourceState> state,
        const GrD3DDescriptorHeap::CPUHandle& shaderResourceView,
        const GrD3DDescriptorHeap::CPUHandle& renderTargetView,
        GrMipmapStatus mipmapStatus,
        std::string_view label)
        : GrSurface(gpu, dimensions, info.fProtected, label)
        , GrD3DTextureResource(info, state)
        , GrD3DTexture(gpu, dimensions, info, state, shaderResourceView, mipmapStatus, label)
        , GrD3DRenderTarget(gpu, dimensions, info, state, renderTargetView, label) {
    this->registerWithCache(budgeted);
}

GrD3DTextureRenderTarget::GrD3DTextureRenderTarget(
        GrD3DGpu* gpu,
        SkISize dimensions,
        const GrD3DTextureResourceInfo& info,
        sk_sp<GrD3DResourceState> state,
        const GrD3DDescriptorHeap::CPUHandle& shaderResourceView,
        const GrD3DTextureResourceInfo& msaaInfo,
        sk_sp<GrD3DResourceState> msaaState,
        const GrD3DDescriptorHeap::CPUHandle& colorRenderTargetView,
        const GrD3DDescriptorHeap::CPUHandle& resolveRenderTargetView,
        GrMipmapStatus mipmapStatus,
        GrWrapCacheable cacheable,
        std::string_view label)
        : GrSurface(gpu, dimensions, info.fProtected, label)
        , GrD3DTextureResource(info, state)
        , GrD3DTexture(gpu, dimensions, info, state, shaderResourceView, mipmapStatus, label)
        , GrD3DRenderTarget(gpu,
                            dimensions,
                            info,
                            state,
                            msaaInfo,
                            std::move(msaaState),
                            colorRenderTargetView,
                            resolveRenderTargetView,
                            label) {
    SkASSERT(info.fProtected == msaaInfo.fProtected);
    this->registerWithCacheWrapped(cacheable);
}

GrD3DTextureRenderTarget::GrD3DTextureRenderTarget(
        GrD3DGpu* gpu,
        SkISize dimensions,
        const GrD3DTextureResourceInfo& info,
        sk_sp<GrD3DResourceState> state,
        const GrD3DDescriptorHeap::CPUHandle& shaderResourceView,
        const GrD3DDescriptorHeap::CPUHandle& renderTargetView,
        GrMipmapStatus mipmapStatus,
        GrWrapCacheable cacheable,
        std::string_view label)
        : GrSurface(gpu, dimensions, info.fProtected, label)
        , GrD3DTextureResource(info, state)
        , GrD3DTexture(gpu, dimensions, info, state, shaderResourceView, mipmapStatus, label)
        , GrD3DRenderTarget(gpu, dimensions, info, state, renderTargetView, label) {
    this->registerWithCacheWrapped(cacheable);
}

sk_sp<GrD3DTextureRenderTarget> GrD3DTextureRenderTarget::MakeNewTextureRenderTarget(
        GrD3DGpu* gpu,
        skgpu::Budgeted budgeted,
        SkISize dimensions,
        int sampleCnt,
        const D3D12_RESOURCE_DESC& resourceDesc,
        GrProtected isProtected,
        GrMipmapStatus mipmapStatus,
        std::string_view label) {
    GrD3DTextureResourceInfo info;
    D3D12_RESOURCE_STATES initialState = sampleCnt > 1 ? D3D12_RESOURCE_STATE_RESOLVE_DEST
                                                       : D3D12_RESOURCE_STATE_RENDER_TARGET;

    D3D12_CLEAR_VALUE clearValue = {};
    clearValue.Format = resourceDesc.Format;
    clearValue.Color[0] = 0;
    clearValue.Color[1] = 0;
    clearValue.Color[2] = 0;
    clearValue.Color[3] = 0;

    if (!GrD3DTextureResource::InitTextureResourceInfo(gpu, resourceDesc, initialState,
                                                       isProtected, &clearValue, &info)) {
        return nullptr;
    }
    sk_sp<GrD3DResourceState> state(new GrD3DResourceState(
                                          static_cast<D3D12_RESOURCE_STATES>(info.fResourceState)));

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

    const GrD3DDescriptorHeap::CPUHandle renderTargetView =
            gpu->resourceProvider().createRenderTargetView(info.fResource.get());

    if (sampleCnt > 1) {
        GrD3DTextureResourceInfo msInfo;
        sk_sp<GrD3DResourceState> msState;
        // created MSAA surface we assume will be used for masks, so clear to transparent black
        SkColor4f clearColor = { 0, 0, 0, 0 };
        std::tie(msInfo, msState) =
                GrD3DTextureResource::CreateMSAA(gpu, dimensions, sampleCnt, info, clearColor);
        if (!msInfo.fResource || !msState) {
            return nullptr;
        }

        const GrD3DDescriptorHeap::CPUHandle msaaRenderTargetView =
                gpu->resourceProvider().createRenderTargetView(msInfo.fResource.get());

        GrD3DTextureRenderTarget* trt = new GrD3DTextureRenderTarget(gpu,
                                                                     budgeted,
                                                                     dimensions,
                                                                     info,
                                                                     std::move(state),
                                                                     shaderResourceView,
                                                                     msInfo,
                                                                     std::move(msState),
                                                                     msaaRenderTargetView,
                                                                     renderTargetView,
                                                                     mipmapStatus,
                                                                     label);
        return sk_sp<GrD3DTextureRenderTarget>(trt);
    } else {
        GrD3DTextureRenderTarget* trt = new GrD3DTextureRenderTarget(gpu,
                                                                     budgeted,
                                                                     dimensions,
                                                                     info,
                                                                     std::move(state),
                                                                     shaderResourceView,
                                                                     renderTargetView,
                                                                     mipmapStatus,
                                                                     label);
        return sk_sp<GrD3DTextureRenderTarget>(trt);
    }
}

sk_sp<GrD3DTextureRenderTarget> GrD3DTextureRenderTarget::MakeWrappedTextureRenderTarget(
        GrD3DGpu* gpu,
        SkISize dimensions,
        int sampleCnt,
        GrWrapCacheable cacheable,
        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(VK_NULL_HANDLE != info.fImage &&
    //         (kBorrow_GrWrapOwnership == wrapOwnership || VK_NULL_HANDLE != info.fAlloc.fMemory));

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

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

    const GrD3DDescriptorHeap::CPUHandle renderTargetView =
            gpu->resourceProvider().createRenderTargetView(info.fResource.get());

    if (sampleCnt > 1) {
        GrD3DTextureResourceInfo msInfo;
        sk_sp<GrD3DResourceState> msState;
        // for wrapped MSAA surface we assume clear to white
        SkColor4f clearColor = { 1, 1, 1, 1 };
        std::tie(msInfo, msState) =
                GrD3DTextureResource::CreateMSAA(gpu, dimensions, sampleCnt, info, clearColor);
        if (!msInfo.fResource || !msState) {
            return nullptr;
        }

        const GrD3DDescriptorHeap::CPUHandle msaaRenderTargetView =
                gpu->resourceProvider().createRenderTargetView(msInfo.fResource.get());

        GrD3DTextureRenderTarget* trt = new GrD3DTextureRenderTarget(
                gpu,
                dimensions,
                info,
                std::move(state),
                shaderResourceView,
                msInfo,
                std::move(msState),
                msaaRenderTargetView,
                renderTargetView,
                mipmapStatus,
                cacheable,
                /*label=*/"MakeWrappedTextureRenderTargetWithMSAASurface");
        return sk_sp<GrD3DTextureRenderTarget>(trt);
    } else {
        return sk_sp<GrD3DTextureRenderTarget>(
                new GrD3DTextureRenderTarget(gpu,
                                             dimensions,
                                             info,
                                             std::move(state),
                                             shaderResourceView,
                                             renderTargetView,
                                             mipmapStatus,
                                             cacheable,
                                             /*label=*/"MakeWrappedTextureRenderTarget"));
    }
}

size_t GrD3DTextureRenderTarget::onGpuMemorySize() const {
    int numColorSamples = this->numSamples();
    if (numColorSamples > 1) {
        // Add one to account for the resolve VkImage.
        ++numColorSamples;
    }
    return GrSurface::ComputeSize(this->backendFormat(), this->dimensions(),
                                  numColorSamples,  // TODO: this still correct?
                                  this->mipmapped());
}

void GrD3DTextureRenderTarget::onSetLabel() {
    GrD3DRenderTarget::onSetLabel();
}

