/*
 * 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 "GrMtlBuffer.h"
#include "GrMtlGpu.h"
#include "GrGpuResourcePriv.h"
#include "GrTypesPriv.h"

#ifdef SK_DEBUG
#define VALIDATE() this->validate()
#else
#define VALIDATE() do {} while(false)
#endif

sk_sp<GrMtlBuffer> GrMtlBuffer::Make(GrMtlGpu* gpu, size_t size, GrGpuBufferType intendedType,
                                     GrAccessPattern accessPattern, const void* data) {
    sk_sp<GrMtlBuffer> buffer(new GrMtlBuffer(gpu, size, intendedType, accessPattern));
    if (data && !buffer->onUpdateData(data, size)) {
        return nullptr;
    }
    return buffer;
}

GrMtlBuffer::GrMtlBuffer(GrMtlGpu* gpu, size_t size, GrGpuBufferType intendedType,
                         GrAccessPattern accessPattern)
        : INHERITED(gpu, size, intendedType, accessPattern)
        , fIsDynamic(accessPattern == kDynamic_GrAccessPattern) {
    // TODO: We are treating all buffers as static access since we don't have an implementation to
    // synchronize gpu and cpu access of a resource yet. See comments in GrMtlBuffer::internalMap()
    // and interalUnmap() for more details.
    fIsDynamic = false;

    // The managed resource mode is only available for macOS. iOS should use shared.
    fMtlBuffer = size == 0 ? nil :
            [gpu->device() newBufferWithLength: size
                                       options: !fIsDynamic ? MTLResourceStorageModePrivate
#ifdef SK_BUILD_FOR_MAC
                                                            : MTLResourceStorageModeManaged];
#else
                                                            : MTLResourceStorageModeShared];
#endif
    this->registerWithCache(SkBudgeted::kYes);
    VALIDATE();
}

GrMtlBuffer::~GrMtlBuffer() {
    SkASSERT(fMtlBuffer == nil);
    SkASSERT(fMappedBuffer == nil);
    SkASSERT(fMapPtr == nullptr);
}

bool GrMtlBuffer::onUpdateData(const void* src, size_t srcInBytes) {
    if (fMtlBuffer == nil) {
        return false;
    }
    if (srcInBytes > fMtlBuffer.length) {
        return false;
    }
    VALIDATE();

    this->internalMap(srcInBytes);
    if (fMapPtr == nil) {
        return false;
    }
    SkASSERT(fMappedBuffer);
    SkASSERT(srcInBytes == fMappedBuffer.length);
    memcpy(fMapPtr, src, srcInBytes);
    this->internalUnmap(srcInBytes);

    VALIDATE();
    return true;
}

inline GrMtlGpu* GrMtlBuffer::mtlGpu() const {
    SkASSERT(!this->wasDestroyed());
    return static_cast<GrMtlGpu*>(this->getGpu());
}

void GrMtlBuffer::onAbandon() {
    fMtlBuffer = nil;
    fMappedBuffer = nil;
    fMapPtr = nullptr;
    VALIDATE();
    INHERITED::onAbandon();
}

void GrMtlBuffer::onRelease() {
    if (!this->wasDestroyed()) {
        VALIDATE();
        fMtlBuffer = nil;
        fMappedBuffer = nil;
        fMapPtr = nullptr;
        VALIDATE();
    }
    INHERITED::onRelease();
}

void GrMtlBuffer::internalMap(size_t sizeInBytes) {
    SkASSERT(fMtlBuffer);
    if (this->wasDestroyed()) {
        return;
    }
    VALIDATE();
    SkASSERT(!this->isMapped());
    if (fIsDynamic) {
        // TODO: We will want to decide if we need to create a new buffer here in order to avoid
        // possibly invalidating a buffer which is being used by the gpu.
        fMappedBuffer = fMtlBuffer;
        fMapPtr = fMappedBuffer.contents;
    } else {
        SK_BEGIN_AUTORELEASE_BLOCK
        // TODO: We can't ensure that map will only be called once on static access buffers until
        // we actually enable dynamic access.
        // SkASSERT(fMappedBuffer == nil);
        fMappedBuffer =
                [this->mtlGpu()->device() newBufferWithLength: sizeInBytes
#ifdef SK_BUILD_FOR_MAC
                                                      options: MTLResourceStorageModeManaged];
#else
                                                      options: MTLResourceStorageModeShared];
#endif
        fMapPtr = fMappedBuffer.contents;
        SK_END_AUTORELEASE_BLOCK
    }
    VALIDATE();
}

void GrMtlBuffer::internalUnmap(size_t sizeInBytes) {
    SkASSERT(fMtlBuffer);
    if (this->wasDestroyed()) {
        return;
    }
    VALIDATE();
    SkASSERT(this->isMapped());
    if (fMtlBuffer == nil) {
        fMappedBuffer = nil;
        fMapPtr = nullptr;
        return;
    }
#ifdef SK_BUILD_FOR_MAC
    // TODO: by calling didModifyRange here we invalidate the buffer. This will cause problems for
    // dynamic access buffers if they are being used by the gpu.
    [fMappedBuffer didModifyRange: NSMakeRange(0, sizeInBytes)];
#endif
    if (!fIsDynamic) {
        SK_BEGIN_AUTORELEASE_BLOCK
        id<MTLBlitCommandEncoder> blitCmdEncoder =
                [this->mtlGpu()->commandBuffer() blitCommandEncoder];
        [blitCmdEncoder copyFromBuffer: fMappedBuffer
                          sourceOffset: 0
                              toBuffer: fMtlBuffer
                     destinationOffset: 0
                                  size: sizeInBytes];
        [blitCmdEncoder endEncoding];
        SK_END_AUTORELEASE_BLOCK
    }
    fMappedBuffer = nil;
    fMapPtr = nullptr;
}

void GrMtlBuffer::onMap() {
    this->internalMap(fMtlBuffer.length);
}

void GrMtlBuffer::onUnmap() {
    this->internalUnmap(fMappedBuffer.length);
}

#ifdef SK_DEBUG
void GrMtlBuffer::validate() const {
    SkASSERT(fMtlBuffer == nil ||
             this->intendedType() == GrGpuBufferType::kVertex ||
             this->intendedType() == GrGpuBufferType::kIndex ||
             this->intendedType() == GrGpuBufferType::kXferCpuToGpu ||
             this->intendedType() == GrGpuBufferType::kXferGpuToCpu);
    SkASSERT(fMappedBuffer == nil || fMtlBuffer == nil ||
             fMappedBuffer.length <= fMtlBuffer.length);
    SkASSERT(fIsDynamic == false); // TODO: implement synchronization to allow dynamic access.
}
#endif