/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkTypes.h"
#if defined(SK_GL)
#include "include/core/SkRefCnt.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTraceMemoryDump.h"
#include "include/gpu/GpuTypes.h"
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/gl/GrGLTypes.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/gl/GrGLBuffer.h"
#include "src/gpu/ganesh/gl/GrGLDefines.h"
#include "src/gpu/ganesh/gl/GrGLGpu.h"
#include "src/gpu/ganesh/gl/GrGLRenderTarget.h"
#include "src/gpu/ganesh/gl/GrGLTexture.h"
#include "src/gpu/ganesh/gl/GrGLTextureRenderTarget.h"
#include "src/gpu/ganesh/gl/GrGLTypesPriv.h"
#include "tests/CtsEnforcement.h"
#include "tests/Test.h"

#include <cstddef>
#include <cstdint>
#include <utility>

class SkDiscardableMemory;
struct GrContextOptions;

/*
 * Build test for SkTraceMemoryDump.
 */
class TestSkTraceMemoryDump : public SkTraceMemoryDump {
public:
    TestSkTraceMemoryDump(bool shouldDumpWrappedObjects)
            : fShouldDumpWrappedObjects(shouldDumpWrappedObjects) {}
    ~TestSkTraceMemoryDump() override { }

    void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
                          uint64_t value) override {
        // Only count "size" dumps, others are just providing metadata.
        if (SkString("size") == SkString(valueName)) {
            ++fNumDumpedObjects;
            fDumpedObjectsSize += value;
        }
    }
    void setMemoryBacking(const char* dumpName, const char* backingType,
                          const char* backingObjectId) override { }
    void setDiscardableMemoryBacking(
        const char* dumpName,
        const SkDiscardableMemory& discardableMemoryObject) override { }
    LevelOfDetail getRequestedDetails() const override {
        return SkTraceMemoryDump::kObjectsBreakdowns_LevelOfDetail;
    }
    bool shouldDumpWrappedObjects() const override { return fShouldDumpWrappedObjects; }

    size_t numDumpedObjects() const { return fNumDumpedObjects; }
    size_t dumpedObjectsSize() const { return fDumpedObjectsSize; }

private:
    bool fShouldDumpWrappedObjects;
    size_t fNumDumpedObjects = 0;
    size_t fDumpedObjectsSize = 0;
};

void ValidateMemoryDumps(skiatest::Reporter* reporter, GrDirectContext* dContext,
                         size_t numDumpedObjects, size_t size, bool isOwned) {
    // Note than one entry in the dumped objects is expected for the text blob cache.
    TestSkTraceMemoryDump dump_with_wrapped(true /* shouldDumpWrappedObjects */);
    dContext->dumpMemoryStatistics(&dump_with_wrapped);
    REPORTER_ASSERT(reporter, numDumpedObjects == dump_with_wrapped.numDumpedObjects());
    REPORTER_ASSERT(reporter, size == dump_with_wrapped.dumpedObjectsSize());

    TestSkTraceMemoryDump dump_no_wrapped(false /* shouldDumpWrappedObjects */);
    dContext->dumpMemoryStatistics(&dump_no_wrapped);
    if (isOwned) {
        REPORTER_ASSERT(reporter, numDumpedObjects == dump_no_wrapped.numDumpedObjects());
        REPORTER_ASSERT(reporter, size == dump_no_wrapped.dumpedObjectsSize());
    } else {
        REPORTER_ASSERT(reporter, 1 == dump_no_wrapped.numDumpedObjects());
        REPORTER_ASSERT(reporter, 0 == dump_no_wrapped.dumpedObjectsSize());
    }
}

DEF_GANESH_TEST_FOR_GL_CONTEXT(SkTraceMemoryDump_ownedGLBuffer,
                               reporter,
                               ctxInfo,
                               CtsEnforcement::kApiLevel_T) {
    auto dContext = ctxInfo.directContext();
    GrGLGpu* gpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());
    const size_t kMemorySize = 1024;
    sk_sp<GrGLBuffer> buffer =
            GrGLBuffer::Make(gpu, kMemorySize, GrGpuBufferType::kVertex, kDynamic_GrAccessPattern);

    ValidateMemoryDumps(reporter, dContext, 2, kMemorySize, true /* isOwned */);
}

DEF_GANESH_TEST_FOR_GL_CONTEXT(SkTraceMemoryDump_ownedGLTexture,
                               reporter,
                               ctxInfo,
                               CtsEnforcement::kApiLevel_T) {
    auto dContext = ctxInfo.directContext();
    GrGLGpu* gpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());

    GrGLTexture::Desc desc;
    desc.fTarget = GR_GL_TEXTURE_2D;
    desc.fID = 7;  // Arbitrary, we don't actually use the texture.
    desc.fFormat = GrGLFormat::kRGBA8;
    desc.fOwnership = GrBackendObjectOwnership::kOwned;
    desc.fIsProtected = skgpu::Protected::kNo;
    desc.fSize = SkISize::Make(64, 64);

    auto texture = sk_make_sp<GrGLTexture>(gpu,
                                           skgpu::Budgeted::kNo,
                                           desc,
                                           GrMipmapStatus::kNotAllocated,
                                           /*label=*/"SkTraceMemoryDump_ownedGLTexture");

    ValidateMemoryDumps(reporter, dContext, 2, texture->gpuMemorySize(), true /* isOwned */);
}

DEF_GANESH_TEST_FOR_GL_CONTEXT(SkTraceMemoryDump_unownedGLTexture,
                               reporter,
                               ctxInfo,
                               CtsEnforcement::kApiLevel_T) {
    auto dContext = ctxInfo.directContext();
    GrGLGpu* gpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());

    GrGLTexture::Desc desc;
    desc.fTarget = GR_GL_TEXTURE_2D;
    desc.fID = 7;  // Arbitrary, we don't actually use the texture.
    desc.fFormat = GrGLFormat::kRGBA8;
    desc.fOwnership = GrBackendObjectOwnership::kBorrowed;
    desc.fSize = SkISize::Make(64, 64);
    desc.fIsProtected = skgpu::Protected::kNo;

    auto params = sk_make_sp<GrGLTextureParameters>();

    auto texture = GrGLTexture::MakeWrapped(gpu,
                                            GrMipmapStatus::kNotAllocated,
                                            desc,
                                            std::move(params),
                                            GrWrapCacheable::kNo,
                                            kRead_GrIOType,
                                            /*label=*/{});

    ValidateMemoryDumps(reporter, dContext, 2, texture->gpuMemorySize(), false /* isOwned */);
}

DEF_GANESH_TEST_FOR_GL_CONTEXT(SkTraceMemoryDump_ownedGLRenderTarget,
                               reporter,
                               ctxInfo,
                               CtsEnforcement::kApiLevel_T) {
    auto dContext = ctxInfo.directContext();
    GrGLGpu* gpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());

    static constexpr auto kSize = SkISize::Make(64, 64);

    GrGLRenderTarget::IDs rtIDs;
    rtIDs.fMultisampleFBOID = 0;
    rtIDs.fRTFBOOwnership = GrBackendObjectOwnership::kOwned;
    rtIDs.fSingleSampleFBOID = 20;
    rtIDs.fMSColorRenderbufferID = 0;
    rtIDs.fTotalMemorySamplesPerPixel = 1;

    sk_sp<GrGLRenderTarget> rt = GrGLRenderTarget::MakeWrapped(gpu,
                                                               kSize,
                                                               GrGLFormat::kRGBA8,
                                                               1,
                                                               rtIDs,
                                                               0,
                                                               skgpu::Protected::kNo,
                                                               /*label=*/{});

    ValidateMemoryDumps(reporter, dContext, 2, rt->gpuMemorySize(), true /* isOwned */);
}

DEF_GANESH_TEST_FOR_GL_CONTEXT(SkTraceMemoryDump_unownedGLRenderTarget,
                               reporter,
                               ctxInfo,
                               CtsEnforcement::kApiLevel_T) {
    auto dContext = ctxInfo.directContext();
    GrGLGpu* gpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());

    static constexpr auto kSize = SkISize::Make(64, 64);

    GrGLRenderTarget::IDs rtIDs;
    rtIDs.fMultisampleFBOID = 12;
    rtIDs.fRTFBOOwnership = GrBackendObjectOwnership::kBorrowed;
    rtIDs.fSingleSampleFBOID = 0;
    rtIDs.fMSColorRenderbufferID = 0;
    rtIDs.fTotalMemorySamplesPerPixel = 4;

    sk_sp<GrGLRenderTarget> rt = GrGLRenderTarget::MakeWrapped(gpu,
                                                               kSize,
                                                               GrGLFormat::kRGBA8,
                                                               4,
                                                               rtIDs,
                                                               0,
                                                               skgpu::Protected::kNo,
                                                               /*label=*/{});

    ValidateMemoryDumps(reporter, dContext, 2, rt->gpuMemorySize(), false /* isOwned */);
}

DEF_GANESH_TEST_FOR_GL_CONTEXT(SkTraceMemoryDump_ownedGLTextureRenderTarget,
                               reporter,
                               ctxInfo,
                               CtsEnforcement::kApiLevel_T) {
    auto dContext = ctxInfo.directContext();
    GrGLGpu* gpu = static_cast<GrGLGpu*>(dContext->priv().getGpu());

    static constexpr auto kSize = SkISize::Make(64, 64);

    GrGLTexture::Desc texDesc;
    texDesc.fSize = kSize;
    texDesc.fTarget = GR_GL_TEXTURE_2D;
    texDesc.fID = 17;
    texDesc.fFormat = GrGLFormat::kRGBA8;
    texDesc.fOwnership = GrBackendObjectOwnership::kOwned;
    texDesc.fIsProtected = skgpu::Protected::kNo;

    GrGLRenderTarget::IDs rtIDs;
    rtIDs.fMultisampleFBOID = 12;
    rtIDs.fRTFBOOwnership = GrBackendObjectOwnership::kOwned;
    rtIDs.fSingleSampleFBOID = 20;
    rtIDs.fMSColorRenderbufferID = 22;
    rtIDs.fTotalMemorySamplesPerPixel = 9;

    auto texRT = sk_make_sp<GrGLTextureRenderTarget>(
            gpu,
            skgpu::Budgeted::kYes,
            8,
            texDesc,
            rtIDs,
            GrMipmapStatus::kNotAllocated,
            /*label=*/"SkTraceMemoryDump_ownedGLTextureRenderTarget");

    ValidateMemoryDumps(reporter, dContext, 3, texRT->gpuMemorySize(), true /* isOwned */);
}

#endif  // defined(SK_GL)
