/*
 * Copyright 2018 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "src/gpu/ganesh/mtl/GrMtlTextureRenderTarget.h"

#include "include/gpu/ganesh/mtl/GrMtlBackendSurface.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/GrResourceProvider.h"
#include "src/gpu/ganesh/mtl/GrMtlGpu.h"
#include "src/gpu/ganesh/mtl/GrMtlUtil.h"

#if !__has_feature(objc_arc)
#error This file must be compiled with Arc. Use -fobjc-arc flag
#endif

GR_NORETAIN_BEGIN

GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu,
                                                   skgpu::Budgeted budgeted,
                                                   SkISize dimensions,
                                                   sk_sp<GrMtlAttachment> texture,
                                                   sk_sp<GrMtlAttachment> colorAttachment,
                                                   sk_sp<GrMtlAttachment> resolveAttachment,
                                                   GrMipmapStatus mipmapStatus,
                                                   std::string_view label)
        : GrSurface(gpu, dimensions, GrProtected::kNo, label)
        , GrMtlTexture(gpu, dimensions, std::move(texture), mipmapStatus, label)
        , GrMtlRenderTarget(gpu, dimensions, std::move(colorAttachment),
                            std::move(resolveAttachment), label) {
    this->registerWithCache(budgeted);
}

GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu,
                                                   SkISize dimensions,
                                                   sk_sp<GrMtlAttachment> texture,
                                                   sk_sp<GrMtlAttachment> colorAttachment,
                                                   sk_sp<GrMtlAttachment> resolveAttachment,
                                                   GrMipmapStatus mipmapStatus,
                                                   GrWrapCacheable cacheable,
                                                   std::string_view label)
        : GrSurface(gpu, dimensions, GrProtected::kNo, label)
        , GrMtlTexture(gpu, dimensions, std::move(texture), mipmapStatus, label)
        , GrMtlRenderTarget(gpu, dimensions, std::move(colorAttachment),
                            std::move(resolveAttachment), label) {
    this->registerWithCacheWrapped(cacheable);
}

bool create_rt_attachments(GrMtlGpu* gpu, SkISize dimensions, MTLPixelFormat format, int sampleCnt,
                           sk_sp<GrMtlAttachment> texture, sk_sp<GrMtlAttachment>* colorAttachment,
                           sk_sp<GrMtlAttachment>* resolveAttachment) {
    if (sampleCnt > 1) {
        auto rp = gpu->getContext()->priv().resourceProvider();
        sk_sp<GrAttachment> msaaAttachment =
                rp->makeMSAAAttachment(dimensions,
                                       GrBackendFormats::MakeMtl(format),
                                       sampleCnt,
                                       GrProtected::kNo,
                                       GrMemoryless::kNo);
        if (!msaaAttachment) {
            return false;
        }
        *colorAttachment =
                sk_sp<GrMtlAttachment>(static_cast<GrMtlAttachment*>(msaaAttachment.release()));
        *resolveAttachment = std::move(texture);
    } else {
        *colorAttachment = std::move(texture);
    }
    return true;
}

sk_sp<GrMtlTextureRenderTarget> GrMtlTextureRenderTarget::MakeNewTextureRenderTarget(
        GrMtlGpu* gpu,
        skgpu::Budgeted budgeted,
        SkISize dimensions,
        int sampleCnt,
        MTLPixelFormat format,
        uint32_t mipLevels,
        GrMipmapStatus mipmapStatus,
        std::string_view label) {
    sk_sp<GrMtlAttachment> textureAttachment =
            GrMtlAttachment::MakeTexture(gpu, dimensions, format, mipLevels, GrRenderable::kYes,
                                         /*numSamples=*/1, budgeted);
    if (!textureAttachment) {
        return nullptr;
    }
    if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
        SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) &
                 textureAttachment->mtlTexture().usage);
    }

    sk_sp<GrMtlAttachment> colorAttachment;
    sk_sp<GrMtlAttachment> resolveAttachment;
    if (!create_rt_attachments(gpu, dimensions, format, sampleCnt, textureAttachment,
                               &colorAttachment, &resolveAttachment)) {
        return nullptr;
    }
    SkASSERT(colorAttachment);
    SkASSERT(sampleCnt == 1 || resolveAttachment);

    return sk_sp<GrMtlTextureRenderTarget>(new GrMtlTextureRenderTarget(
            gpu, budgeted, dimensions, std::move(textureAttachment), std::move(colorAttachment),
            std::move(resolveAttachment), mipmapStatus, label));
}

sk_sp<GrMtlTextureRenderTarget> GrMtlTextureRenderTarget::MakeWrappedTextureRenderTarget(
        GrMtlGpu* gpu,
        SkISize dimensions,
        int sampleCnt,
        id<MTLTexture> texture,
        GrWrapCacheable cacheable) {
    SkASSERT(nil != texture);
    if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
        SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & texture.usage);
    }
    GrMipmapStatus mipmapStatus = texture.mipmapLevelCount > 1
                                            ? GrMipmapStatus::kDirty
                                            : GrMipmapStatus::kNotAllocated;
    GrAttachment::UsageFlags textureUsageFlags = GrAttachment::UsageFlags::kTexture |
                                                 GrAttachment::UsageFlags::kColorAttachment;
    sk_sp<GrMtlAttachment> textureAttachment =
            GrMtlAttachment::MakeWrapped(gpu, dimensions, texture, textureUsageFlags, cacheable,
                                         /*label=*/"MtlAttachment_TextureAttachment");
    if (!textureAttachment) {
        return nullptr;
    }

    sk_sp<GrMtlAttachment> colorAttachment;
    sk_sp<GrMtlAttachment> resolveAttachment;
    if (!create_rt_attachments(gpu, dimensions, texture.pixelFormat, sampleCnt, textureAttachment,
                               &colorAttachment, &resolveAttachment)) {
        return nullptr;
    }
    SkASSERT(colorAttachment);
    SkASSERT(sampleCnt == 1 || resolveAttachment);

    return sk_sp<GrMtlTextureRenderTarget>(new GrMtlTextureRenderTarget(
            gpu, dimensions, std::move(textureAttachment), std::move(colorAttachment),
            std::move(resolveAttachment), mipmapStatus, cacheable,
                      /*label=*/"MtlWrappedTextureRenderTarget"));
}

size_t GrMtlTextureRenderTarget::onGpuMemorySize() const {
    // The GrTexture[RenderTarget] is built up by a bunch of attachments each of which are their
    // own GrGpuResource. Ideally the GrRenderTarget would not be a GrGpuResource and the GrTexture
    // would just merge with the new GrSurface/Attachment world. Then we could just depend on each
    // attachment to give its own size since we don't have GrGpuResources owning other
    // GrGpuResources. Until we get to that point we need to live in some hybrid world. We will let
    // the msaa and stencil attachments track their own size because they do get cached separately.
    // For all GrTexture* based things we will continue to to use the GrTexture* to report size and
    // the owned attachments will have no size and be uncached.
#ifdef SK_DEBUG
    // The nonMSAA attachment (either color or resolve depending on numSamples should have size of
    // zero since it is a texture attachment.
    SkASSERT(this->nonMSAAAttachment()->gpuMemorySize() == 0);
    if (this->numSamples() > 1) {
        // Msaa attachment should have a valid size
        SkASSERT(this->colorAttachment()->gpuMemorySize() ==
                 GrSurface::ComputeSize(this->backendFormat(),
                                        this->dimensions(),
                                        this->numSamples(),
                                        skgpu::Mipmapped::kNo));
    }
#endif
    return GrSurface::ComputeSize(this->backendFormat(), this->dimensions(),
                                  1 /*colorSamplesPerPixel*/, this->mipmapped());
}

void GrMtlTextureRenderTarget::onSetLabel() {
    GrMtlRenderTarget::onSetLabel();
    GrMtlTexture::onSetLabel();
}

GR_NORETAIN_END
