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

#if SK_SUPPORT_GPU

#include "GrContext.h"
#include "GrResource.h"
#include "GrResourceCache.h"
#include "GrStencilBuffer.h"
#include "GrTexture.h"
#include "SkBenchmark.h"
#include "SkCanvas.h"

enum {
    CACHE_SIZE_COUNT = 2048,
    CACHE_SIZE_BYTES = 2 * 1024 * 1024,
};

class StencilResource : public GrResource {
public:
    SK_DECLARE_INST_COUNT(StencilResource);
    StencilResource(GrGpu* gpu, int id)
        : INHERITED(gpu, false),
          fID(id) {
    }
    ~StencilResource() {
        this->release();
    }

    virtual size_t sizeInBytes() const SK_OVERRIDE {
        return 100 + ((fID % 1 == 0) ? -5 : 6);
    }

    static GrResourceKey ComputeKey(int width, int height, int sampleCnt) {
        return GrStencilBuffer::ComputeKey(width, height, sampleCnt);
    }

    int fID;

private:
    typedef GrResource INHERITED;
};

class TextureResource : public GrResource {
public:
    SK_DECLARE_INST_COUNT(TextureResource);
    TextureResource(GrGpu* gpu, int id)
        : INHERITED(gpu, false),
          fID(id) {
    }
    ~TextureResource() {
        this->release();
    }

    virtual size_t sizeInBytes() const SK_OVERRIDE {
        return 100 + ((fID % 1 == 0) ? -40 : 33);
    }

    static GrResourceKey ComputeKey(const GrTextureDesc& desc) {
        return GrTexture::ComputeScratchKey(desc);
    }

    int fID;

private:
    typedef GrResource INHERITED;
};

static void get_stencil(int i, int* w, int* h, int* s) {
    *w = i % 1024;
    *h = i * 2 % 1024;
    *s = i % 1 == 0 ? 0 : 4;
}

static void get_texture_desc(int i, GrTextureDesc* desc) {
    desc->fFlags = kRenderTarget_GrTextureFlagBit |
        kNoStencil_GrTextureFlagBit;
    desc->fWidth  = i % 1024;
    desc->fHeight = i * 2 % 1024;
    desc->fConfig = static_cast<GrPixelConfig>(i % (kLast_GrPixelConfig + 1));
    desc->fSampleCnt = i % 1 == 0 ? 0 : 4;
}

static void populate_cache(GrResourceCache* cache, GrGpu* gpu, int resourceCount) {
    for (int i = 0; i < resourceCount; ++i) {
        int w, h, s;
        get_stencil(i, &w, &h, &s);
        GrResourceKey key = GrStencilBuffer::ComputeKey(w, h, s);
        GrResource* resource = SkNEW_ARGS(StencilResource, (gpu, i));
        cache->purgeAsNeeded(1, resource->sizeInBytes());
        cache->addResource(key, resource);
        resource->unref();
    }

    for (int i = 0; i < resourceCount; ++i) {
        GrTextureDesc desc;
        get_texture_desc(i, &desc);
        GrResourceKey key =  TextureResource::ComputeKey(desc);
        GrResource* resource = SkNEW_ARGS(TextureResource, (gpu, i));
        cache->purgeAsNeeded(1, resource->sizeInBytes());
        cache->addResource(key, resource);
        resource->unref();
    }
}

static void check_cache_contents_or_die(GrResourceCache* cache, int k) {
    // Benchmark find calls that succeed.
    {
        GrTextureDesc desc;
        get_texture_desc(k, &desc);
        GrResourceKey key = TextureResource::ComputeKey(desc);
        GrResource* item = cache->find(key);
        if (NULL == item) {
            GrCrash("cache add does not work as expected");
            return;
        }
        if (static_cast<TextureResource*>(item)->fID != k) {
            GrCrash("cache add does not work as expected");
            return;
        }
    }
    {
        int w, h, s;
        get_stencil(k, &w, &h, &s);
        GrResourceKey key = StencilResource::ComputeKey(w, h, s);
        GrResource* item = cache->find(key);
        if (NULL == item) {
            GrCrash("cache add does not work as expected");
            return;
        }
        if (static_cast<TextureResource*>(item)->fID != k) {
            GrCrash("cache add does not work as expected");
            return;
        }
    }

    // Benchmark also find calls that always fail.
    {
        GrTextureDesc desc;
        get_texture_desc(k, &desc);
        desc.fHeight |= 1;
        GrResourceKey key = TextureResource::ComputeKey(desc);
        GrResource* item = cache->find(key);
        if (NULL != item) {
            GrCrash("cache add does not work as expected");
            return;
        }
    }
    {
        int w, h, s;
        get_stencil(k, &w, &h, &s);
        h |= 1;
        GrResourceKey key = StencilResource::ComputeKey(w, h, s);
        GrResource* item = cache->find(key);
        if (NULL != item) {
            GrCrash("cache add does not work as expected");
            return;
        }
    }
}

class GrResourceCacheBenchAdd : public SkBenchmark {
    enum {
        RESOURCE_COUNT = CACHE_SIZE_COUNT / 2,
        DUPLICATE_COUNT = CACHE_SIZE_COUNT / 4,
    };

public:
    virtual bool isSuitableFor(Backend backend) SK_OVERRIDE {
        return backend == kGPU_Backend;
    }

protected:
    virtual const char* onGetName() SK_OVERRIDE {
        return "grresourcecache_add";
    }

    virtual void onDraw(const int loops, SkCanvas* canvas) SK_OVERRIDE {
        GrGpu* gpu = canvas->getGrContext()->getGpu();

        for (int i = 0; i < loops; ++i) {
            GrResourceCache cache(CACHE_SIZE_COUNT, CACHE_SIZE_BYTES);
            populate_cache(&cache, gpu, DUPLICATE_COUNT);
            populate_cache(&cache, gpu, RESOURCE_COUNT);

            // Check that cache works.
            for (int k = 0; k < RESOURCE_COUNT; k += 33) {
                check_cache_contents_or_die(&cache, k);
            }
            cache.purgeAllUnlocked();
        }
    }

private:
    typedef SkBenchmark INHERITED;
};

class GrResourceCacheBenchFind : public SkBenchmark {
    enum {
        RESOURCE_COUNT = (CACHE_SIZE_COUNT / 2) - 100,
        DUPLICATE_COUNT = 100
    };

public:
    virtual bool isSuitableFor(Backend backend) SK_OVERRIDE {
        return backend == kGPU_Backend;
    }

protected:
    virtual const char* onGetName() SK_OVERRIDE {
        return "grresourcecache_find";
    }

    virtual void onDraw(const int loops, SkCanvas* canvas) SK_OVERRIDE {
        GrGpu* gpu = canvas->getGrContext()->getGpu();
        GrResourceCache cache(CACHE_SIZE_COUNT, CACHE_SIZE_BYTES);
        populate_cache(&cache, gpu, DUPLICATE_COUNT);
        populate_cache(&cache, gpu, RESOURCE_COUNT);

        for (int i = 0; i < loops; ++i) {
            for (int k = 0; k < RESOURCE_COUNT; ++k) {
                check_cache_contents_or_die(&cache, k);
            }
        }
    }

private:
    typedef SkBenchmark INHERITED;
};

DEF_BENCH( return new GrResourceCacheBenchAdd(); )
DEF_BENCH( return new GrResourceCacheBenchFind(); )

#endif