/*
 * Copyright 2017 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/GrMtlTexture.h"

#include "include/gpu/ganesh/mtl/GrMtlBackendSurface.h"
#include "src/gpu/ganesh/GrTexture.h"
#include "src/gpu/ganesh/mtl/GrMtlGpu.h"
#include "src/gpu/mtl/MtlUtilsPriv.h"

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

GR_NORETAIN_BEGIN

GrMtlTexture::GrMtlTexture(GrMtlGpu* gpu,
                           skgpu::Budgeted budgeted,
                           SkISize dimensions,
                           sk_sp<GrMtlAttachment> texture,
                           GrMipmapStatus mipmapStatus,
                           std::string_view label)
        : GrSurface(gpu, dimensions, GrProtected::kNo, label)
        , INHERITED(gpu, dimensions, GrProtected::kNo, GrTextureType::k2D, mipmapStatus, label)
        , fTexture(std::move(texture)) {
    SkDEBUGCODE(id<MTLTexture> mtlTexture = fTexture->mtlTexture();)
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == mtlTexture.mipmapLevelCount));
    if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
        SkASSERT(SkToBool(mtlTexture.usage & MTLTextureUsageShaderRead));
    }
    SkASSERT(!mtlTexture.framebufferOnly);
    this->registerWithCache(budgeted);
    if (skgpu::MtlFormatIsCompressed(fTexture->mtlFormat())) {
        this->setReadOnly();
    }
}

GrMtlTexture::GrMtlTexture(GrMtlGpu* gpu,
                           Wrapped,
                           SkISize dimensions,
                           sk_sp<GrMtlAttachment> texture,
                           GrMipmapStatus mipmapStatus,
                           GrWrapCacheable cacheable,
                           GrIOType ioType,
                           std::string_view label)
        : GrSurface(gpu, dimensions, GrProtected::kNo, label)
        , INHERITED(gpu, dimensions, GrProtected::kNo, GrTextureType::k2D, mipmapStatus, label)
        , fTexture(std::move(texture)) {
    SkDEBUGCODE(id<MTLTexture> mtlTexture = fTexture->mtlTexture();)
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == mtlTexture.mipmapLevelCount));
    if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
        SkASSERT(SkToBool(mtlTexture.usage & MTLTextureUsageShaderRead));
    }
    SkASSERT(!mtlTexture.framebufferOnly);
    if (ioType == kRead_GrIOType) {
        this->setReadOnly();
    }
    this->registerWithCacheWrapped(cacheable);
}

GrMtlTexture::GrMtlTexture(GrMtlGpu* gpu,
                           SkISize dimensions,
                           sk_sp<GrMtlAttachment> texture,
                           GrMipmapStatus mipmapStatus,
                           std::string_view label)
        : GrSurface(gpu, dimensions, GrProtected::kNo, label)
        , INHERITED(gpu, dimensions, GrProtected::kNo, GrTextureType::k2D, mipmapStatus, label)
        , fTexture(std::move(texture)) {
    SkDEBUGCODE(id<MTLTexture> mtlTexture = fTexture->mtlTexture();)
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == mtlTexture.mipmapLevelCount));
    if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
        SkASSERT(SkToBool(mtlTexture.usage & MTLTextureUsageShaderRead));
    }
    SkASSERT(!mtlTexture.framebufferOnly);
}

sk_sp<GrMtlTexture> GrMtlTexture::MakeNewTexture(GrMtlGpu* gpu,
                                                 skgpu::Budgeted budgeted,
                                                 SkISize dimensions,
                                                 MTLPixelFormat format,
                                                 uint32_t mipLevels,
                                                 GrMipmapStatus mipmapStatus,
                                                 std::string_view label) {
    sk_sp<GrMtlAttachment> texture = GrMtlAttachment::MakeTexture(
            gpu, dimensions, format, mipLevels, GrRenderable::kNo, /*numSamples=*/1, budgeted);

    if (!texture) {
        return nullptr;
    }
    return sk_sp<GrMtlTexture>(new GrMtlTexture(gpu, budgeted, dimensions, std::move(texture),
                                                mipmapStatus, label));
}

sk_sp<GrMtlTexture> GrMtlTexture::MakeWrappedTexture(GrMtlGpu* gpu,
                                                     SkISize dimensions,
                                                     id<MTLTexture> texture,
                                                     GrWrapCacheable cacheable,
                                                     GrIOType ioType) {
    SkASSERT(nil != texture);
    if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
        SkASSERT(SkToBool(texture.usage & MTLTextureUsageShaderRead));
    }
    sk_sp<GrMtlAttachment> attachment =
            GrMtlAttachment::MakeWrapped(gpu, dimensions, texture,
                                         GrAttachment::UsageFlags::kTexture, cacheable,
                                         /*label=*/"MtlAttachment_MakeWrapped");
    if (!attachment) {
        return nullptr;
    }

    GrMipmapStatus mipmapStatus = texture.mipmapLevelCount > 1 ? GrMipmapStatus::kValid
                                                               : GrMipmapStatus::kNotAllocated;
    return sk_sp<GrMtlTexture>(
            new GrMtlTexture(gpu, kWrapped, dimensions, std::move(attachment), mipmapStatus,
                             cacheable, ioType, /*label=*/"MtlTextureWrappedTexture"));
}

GrMtlTexture::~GrMtlTexture() {
    SkASSERT(nil == fTexture);
}

GrMtlGpu* GrMtlTexture::getMtlGpu() const {
    SkASSERT(!this->wasDestroyed());
    return static_cast<GrMtlGpu*>(this->getGpu());
}

GrBackendTexture GrMtlTexture::getBackendTexture() const {
    skgpu::Mipmapped mipmapped = fTexture->mtlTexture().mipmapLevelCount > 1
                                         ? skgpu::Mipmapped::kYes
                                         : skgpu::Mipmapped::kNo;
    GrMtlTextureInfo info;
    info.fTexture.reset(GrRetainPtrFromId(fTexture->mtlTexture()));
    return GrBackendTextures::MakeMtl(this->width(), this->height(), mipmapped, info);
}

GrBackendFormat GrMtlTexture::backendFormat() const {
    return GrBackendFormats::MakeMtl(fTexture->mtlFormat());
}

void GrMtlTexture::onSetLabel() {
    SkASSERT(fTexture);
    if (!this->getLabel().empty()) {
        NSString* labelStr = @(this->getLabel().c_str());
        fTexture->mtlTexture().label = [@"_Skia_" stringByAppendingString:labelStr];
    }
}

GR_NORETAIN_END
