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

#include "Test.h"

#if SK_SUPPORT_GPU
#include "GrClip.h"
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrProxyProvider.h"
#include "GrRenderTargetContext.h"
#include "GrResourceProvider.h"
#include "GrTexture.h"
#include "effects/GrSimpleTextureEffect.h"

template <typename I>
static SK_WHEN(std::is_integral<I>::value && 4 == sizeof(I), void)
check_pixels(skiatest::Reporter* reporter, int w, int h, const I exepctedData[],
             const I actualData[], const char* testName) {
    for (int j = 0; j < h; ++j) {
        for (int i = 0; i < w; ++i) {
            I expected = exepctedData[j * w + i];
            I actual = actualData[j * w + i];
            if (expected != actual) {
                ERRORF(reporter, "[%s] Expected 0x08%x, got 0x%08x at %d, %d.", testName, expected,
                       actual, i, j);
                return;
            }
        }
    }
}

DEF_GPUTEST_FOR_RENDERING_CONTEXTS(IntTexture, reporter, ctxInfo) {
    GrContext* context = ctxInfo.grContext();
    if (!context->caps()->isConfigTexturable(kRGBA_8888_sint_GrPixelConfig)) {
        return;
    }

    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
    auto resourceProvider = context->contextPriv().resourceProvider();

    static const int kS = UINT8_MAX + 1;
    static const size_t kRowBytes = kS * sizeof(int32_t);

    GrSurfaceDesc desc;
    desc.fOrigin = kTopLeft_GrSurfaceOrigin;
    desc.fConfig = kRGBA_8888_sint_GrPixelConfig;
    desc.fWidth = kS;
    desc.fHeight = kS;

    std::unique_ptr<int32_t[]> testData(new int32_t[kS * kS]);
    for (int j = 0; j < kS; ++j) {
        for (int i = 0; i < kS; ++i) {
            uint32_t r = i - INT8_MIN;
            uint32_t g = j - INT8_MIN;
            uint32_t b = INT8_MAX - r;
            uint32_t a = INT8_MAX - g;
            testData.get()[j * kS + i] = (a << 24) | (b << 16) | (g << 8) | r;
        }
    }

    // Test that attempting to create a integer texture with multiple MIP levels fails.
    {
        GrMipLevel levels[2];
        levels[0].fPixels = testData.get();
        levels[0].fRowBytes = kRowBytes;
        levels[1].fPixels = testData.get();
        levels[1].fRowBytes = (kS / 2) * sizeof(int32_t);

        sk_sp<GrTextureProxy> temp = proxyProvider->createMipMapProxy(
                                                                desc, SkBudgeted::kYes, levels, 2);
        REPORTER_ASSERT(reporter, !temp);
    }

    sk_sp<GrSurfaceContext> sContext;
    // Test that we can create a non-mipmapped integer texture.
    {
        sk_sp<GrTextureProxy> proxy = proxyProvider->createTextureProxy(desc, SkBudgeted::kYes,
                                                                        testData.get(), kRowBytes);
        REPORTER_ASSERT(reporter, proxy);
        if (!proxy) {
            return;
        }

        sContext = context->contextPriv().makeWrappedSurfaceContext(std::move(proxy));
        if (!sContext) {
            return;
        }
    }

    std::unique_ptr<int32_t[]> readData(new int32_t[kS * kS]);
    // Test that reading to a non-integer config fails.
    {
        bool success = context->contextPriv().readSurfacePixels(sContext.get(),
                                                                0, 0, kS, kS,
                                                                kRGBA_8888_GrPixelConfig,
                                                                nullptr, readData.get());
        REPORTER_ASSERT(reporter, !success);
    }
    {
        std::unique_ptr<uint16_t[]> halfData(new uint16_t[4 * kS * kS]);
        bool success = context->contextPriv().readSurfacePixels(sContext.get(),
                                                                0, 0, kS, kS,
                                                                kRGBA_half_GrPixelConfig,
                                                                nullptr, halfData.get());
        REPORTER_ASSERT(reporter, !success);
    }
    {
        // Can read back as ints. (ES only requires being able to read back into 32bit ints which
        // we don't support. Right now this test is counting on GR_RGBA_INTEGER/GL_BYTE being the
        // implementation-dependent second format).
        sk_bzero(readData.get(), sizeof(int32_t) * kS * kS);
        bool success = context->contextPriv().readSurfacePixels(sContext.get(),
                                                                0, 0, kS, kS,
                                                                kRGBA_8888_sint_GrPixelConfig,
                                                                nullptr, readData.get());
        REPORTER_ASSERT(reporter, success);
        if (success) {
            check_pixels(reporter, kS, kS, testData.get(), readData.get(), "readPixels");
        }
    }
    {
        // readPixels should fail if we attempt to use the unpremul flag with an integer texture.
        bool success = context->contextPriv().readSurfacePixels(
                                                sContext.get(),
                                                0, 0, kS, kS,
                                                kRGBA_8888_sint_GrPixelConfig,
                                                nullptr, readData.get(), 0,
                                                GrContextPriv::kUnpremul_PixelOpsFlag);
        REPORTER_ASSERT(reporter, !success);
    }

    // Test that copying from one integer texture to another succeeds.
    {
        sk_sp<GrSurfaceContext> dstContext(GrSurfaceProxy::TestCopy(context, desc,
                                                                    sContext->asSurfaceProxy()));
        REPORTER_ASSERT(reporter, dstContext);
        if (!dstContext || !dstContext->asTextureProxy()) {
            return;
        }

        sk_bzero(readData.get(), sizeof(int32_t) * kS * kS);
        bool success = context->contextPriv().readSurfacePixels(dstContext.get(), 0, 0, kS, kS,
                                                                kRGBA_8888_sint_GrPixelConfig,
                                                                nullptr, readData.get());
        REPORTER_ASSERT(reporter, success);
        if (success) {
            check_pixels(reporter, kS, kS, testData.get(), readData.get(), "copyIntegerToInteger");
        }
    }


    // Test that copying to a non-integer (8888) texture fails.
    {
        GrSurfaceDesc nonIntDesc = desc;
        nonIntDesc.fConfig = kRGBA_8888_GrPixelConfig;

        sk_sp<GrSurfaceContext> dstContext(GrSurfaceProxy::TestCopy(context, nonIntDesc,
                                                                    sContext->asSurfaceProxy()));
        REPORTER_ASSERT(reporter, !dstContext);
    }

    // Test that copying to a non-integer (RGBA_half) texture fails.
    if (context->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) {
        GrSurfaceDesc nonIntDesc = desc;
        nonIntDesc.fConfig = kRGBA_half_GrPixelConfig;

        sk_sp<GrSurfaceContext> dstContext(GrSurfaceProxy::TestCopy(context, nonIntDesc,
                                                                    sContext->asSurfaceProxy()));
        REPORTER_ASSERT(reporter, !dstContext);
    }

    // We overwrite the top left quarter of the texture with the bottom right quarter of the
    // original data.
    const void* bottomRightQuarter = testData.get() + kS / 2 * kS + kS / 2;

    {
        // Can't write pixels from a non-int config.
        bool success = context->contextPriv().writeSurfacePixels(sContext.get(),
                                                                 0, 0, kS/2, kS/2,
                                                                 kRGBA_8888_GrPixelConfig, nullptr,
                                                                 bottomRightQuarter, kRowBytes);
        REPORTER_ASSERT(reporter, !success);
    }
    {
        // Can't use unpremul flag.
        bool success = context->contextPriv().writeSurfacePixels(
                                            sContext.get(),
                                            0, 0, kS/2, kS/2,
                                            kRGBA_8888_sint_GrPixelConfig,
                                            nullptr,
                                            bottomRightQuarter, kRowBytes,
                                            GrContextPriv::kUnpremul_PixelOpsFlag);
        REPORTER_ASSERT(reporter, !success);
    }
    {
        bool success = context->contextPriv().writeSurfacePixels(sContext.get(),
                                                                 0, 0, kS/2, kS/2,
                                                                 kRGBA_8888_sint_GrPixelConfig,
                                                                 nullptr,
                                                                 bottomRightQuarter, kRowBytes);
        REPORTER_ASSERT(reporter, success);
        if (!success) {
            return;
        }

        sk_bzero(readData.get(), sizeof(int32_t) * kS * kS);
        success = context->contextPriv().readSurfacePixels(sContext.get(),
                                                           0, 0, kS, kS,
                                                           kRGBA_8888_sint_GrPixelConfig,
                                                           nullptr, readData.get(), 0);
        REPORTER_ASSERT(reporter, success);
        if (!success) {
            return;
        }
        std::unique_ptr<int32_t[]> overwrittenTestData(new int32_t[kS * kS]);
        memcpy(overwrittenTestData.get(), testData.get(), sizeof(int32_t) * kS * kS);
        char* dst = (char*)overwrittenTestData.get();
        char* src = (char*)(testData.get() + kS/2 * kS + kS/2);
        for (int i = 0; i < kS/2; ++i) {
            memcpy(dst, src, sizeof(int32_t) * kS/2);
            dst += kRowBytes;
            src += kRowBytes;
        }
        check_pixels(reporter, kS, kS, overwrittenTestData.get(), readData.get(), "overwrite");
    }

    // Test drawing from the integer texture to a fixed point texture. To avoid any premul issues
    // we init the int texture with 0s and 1s and make alpha always be 1. We expect that 1s turn
    // into 0xffs and zeros stay zero.
    std::unique_ptr<uint32_t[]> expectedData(new uint32_t[kS * kS]);
    std::unique_ptr<uint32_t[]> actualData(new uint32_t[kS * kS]);
    for (int i = 0; i < kS*kS; ++i) {
        int32_t a = 0x1;
        int32_t b = ((i & 0x1) ? 1 : 0);
        int32_t g = ((i & 0x1) ? 0 : 1);
        int32_t r = ((i & 0x2) ? 1 : 0);
        testData.get()[i] = (a << 24) | (b << 16) | (g << 8) | r;
        expectedData.get()[i] = ((0xFF * a) << 24) | ((0xFF * b) << 16) |
                                ((0xFF * g) << 8) | (0xFF * r);
    }
    context->contextPriv().writeSurfacePixels(sContext.get(),
                                              0, 0, kS, kS,
                                              kRGBA_8888_sint_GrPixelConfig, nullptr,
                                              testData.get(), 0);

    sk_sp<GrRenderTargetContext> rtContext = context->makeDeferredRenderTargetContext(
            SkBackingFit::kExact, kS, kS, kRGBA_8888_GrPixelConfig, nullptr);

    struct {
        GrSamplerState::Filter fMode;
        const char* fName;
    } kNamedFilters[] = {{GrSamplerState::Filter::kNearest, "filter-none"},
                         {GrSamplerState::Filter::kBilerp, "filter-bilerp"},
                         {GrSamplerState::Filter::kMipMap, "filter-mipmap"}};

    for (auto filter : kNamedFilters) {
        auto fp = GrSimpleTextureEffect::Make(sContext->asTextureProxyRef(), SkMatrix::I(),
                                              filter.fMode);
        REPORTER_ASSERT(reporter, fp);
        if (!fp) {
            return;
        }
        rtContext->clear(nullptr, 0xDDAABBCC, GrRenderTargetContext::CanClearFullscreen::kYes);
        GrPaint paint;
        paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
        paint.addColorFragmentProcessor(std::move(fp));
        rtContext->drawPaint(GrNoClip(), std::move(paint), SkMatrix::I());
        SkImageInfo readInfo = SkImageInfo::Make(kS, kS, kRGBA_8888_SkColorType,
                                                 kPremul_SkAlphaType);
        rtContext->readPixels(readInfo, actualData.get(), 0, 0, 0);
        check_pixels(reporter, kS, kS, expectedData.get(), actualData.get(), filter.fName);
    }

    {
        // No rendering to integer textures.
        GrSurfaceDesc intRTDesc = desc;
        intRTDesc.fFlags = kRenderTarget_GrSurfaceFlag;
        sk_sp<GrTexture> temp(resourceProvider->createTexture(intRTDesc, SkBudgeted::kYes));
        REPORTER_ASSERT(reporter, !temp);
    }
}

#endif