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

// This is a GPU-backend specific test. It relies on static intializers to work

#include "SkTypes.h"

#if SK_SUPPORT_GPU

#include "GrContextFactory.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "GrResourceProvider.h"
#include "GrSurfaceProxy.h"
#include "GrTexture.h"
#include "GrTest.h"
#include "SkGr.h"
#include "SkSurface.h"
#include "Test.h"

using sk_gpu_test::GrContextFactory;

void fill_transfer_data(int left, int top, int width, int height, int bufferWidth,
                        GrColor* data) {

    // build red-green gradient
    for (int j = top; j < top + height; ++j) {
        for (int i = left; i < left + width; ++i) {
            unsigned int red = (unsigned int)(256.f*((i - left) / (float)width));
            unsigned int green = (unsigned int)(256.f*((j - top) / (float)height));
            data[i + j*bufferWidth] = GrColorPackRGBA(red - (red>>8),
                                                      green - (green>>8), 0xff, 0xff);
        }
    }
}

bool does_full_buffer_contain_correct_values(GrColor* srcBuffer,
                                             GrColor* dstBuffer,
                                             int width,
                                             int height,
                                             int bufferWidth,
                                             int bufferHeight,
                                             GrSurfaceOrigin origin) {
    GrColor* srcPtr = srcBuffer;
    bool bottomUp = SkToBool(kBottomLeft_GrSurfaceOrigin == origin);
    GrColor* dstPtr = bottomUp ? dstBuffer + bufferWidth*(bufferHeight-1) : dstBuffer;
    int dstIncrement = bottomUp ? -bufferWidth : +bufferWidth;

    for (int j = 0; j < height; ++j) {
        for (int i = 0; i < width; ++i) {
            if (srcPtr[i] != dstPtr[i]) {
                return false;
            }
        }
        srcPtr += bufferWidth;
        dstPtr += dstIncrement;
    }
    return true;
}

void basic_transfer_test(skiatest::Reporter* reporter, GrContext* context, GrPixelConfig config,
                         GrSurfaceOrigin origin, bool renderTarget) {
    if (GrCaps::kNone_MapFlags == context->caps()->mapBufferFlags()) {
        return;
    }

    auto resourceProvider = context->contextPriv().resourceProvider();
    GrGpu* gpu = context->contextPriv().getGpu();

    if (!gpu->caps()->isConfigTexturable(config)) {
        return;
    }
    if (renderTarget && !gpu->caps()->isConfigRenderable(config, false)) {
        return;
    }

    // set up the data
    const int kTextureWidth = 16;
    const int kTextureHeight = 16;
    const int kBufferWidth = 20;
    const int kBufferHeight = 16;
    size_t rowBytes = kBufferWidth * sizeof(GrColor);
    SkAutoTMalloc<GrColor> srcBuffer(kBufferWidth*kBufferHeight);
    SkAutoTMalloc<GrColor> dstBuffer(kBufferWidth*kBufferHeight);

    fill_transfer_data(0, 0, kTextureWidth, kTextureHeight, kBufferWidth, srcBuffer.get());

    // create and fill transfer buffer
    size_t size = rowBytes*kBufferHeight;
    uint32_t bufferFlags = GrResourceProvider::kNoPendingIO_Flag;
    sk_sp<GrBuffer> buffer(resourceProvider->createBuffer(size,
                                                          kXferCpuToGpu_GrBufferType,
                                                          kDynamic_GrAccessPattern,
                                                          bufferFlags));
    if (!buffer) {
        return;
    }

    void* data = buffer->map();
    memcpy(data, srcBuffer.get(), size);
    buffer->unmap();

    // create texture
    GrSurfaceDesc desc;
    desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
    desc.fOrigin = origin;
    desc.fWidth = kTextureWidth;
    desc.fHeight = kTextureHeight;
    desc.fConfig = config;
    desc.fSampleCnt = 1;
    sk_sp<GrTexture> tex = resourceProvider->createTexture(desc, SkBudgeted::kNo);

    //////////////////////////
    // transfer full data

    bool result;
    result = gpu->transferPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight,
                                 config, buffer.get(), 0, rowBytes);
    REPORTER_ASSERT(reporter, result);

    memset(dstBuffer.get(), 0xCDCD, size);
    result = gpu->readPixels(tex.get(), origin, 0, 0, kTextureWidth, kTextureHeight,
                             config, dstBuffer.get(), rowBytes);
    if (result) {
        REPORTER_ASSERT(reporter, does_full_buffer_contain_correct_values(srcBuffer,
                                                                          dstBuffer,
                                                                          kTextureWidth,
                                                                          kTextureHeight,
                                                                          kBufferWidth,
                                                                          kBufferHeight,
                                                                          origin));
    }

    //////////////////////////
    // transfer partial data

    const int kLeft = 2;
    const int kTop = 10;
    const int kWidth = 10;
    const int kHeight = 2;

    // change color of subrectangle
    fill_transfer_data(kLeft, kTop, kWidth, kHeight, kBufferWidth, srcBuffer.get());
    data = buffer->map();
    memcpy(data, srcBuffer.get(), size);
    buffer->unmap();

    size_t offset = sizeof(GrColor)*(kTop*kBufferWidth + kLeft);
    result = gpu->transferPixels(tex.get(), kLeft, kTop, kWidth, kHeight, config,
                                 buffer.get(), offset, rowBytes);
    REPORTER_ASSERT(reporter, result);

    memset(dstBuffer.get(), 0xCDCD, size);
    result = gpu->readPixels(tex.get(), origin, 0, 0, kTextureWidth, kTextureHeight,
                             config, dstBuffer.get(), rowBytes);
    if (result) {
        REPORTER_ASSERT(reporter, does_full_buffer_contain_correct_values(srcBuffer,
                                                                          dstBuffer,
                                                                          kTextureWidth,
                                                                          kTextureHeight,
                                                                          kBufferWidth,
                                                                          kBufferHeight,
                                                                          origin));
    }
}

DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TransferPixelsTest, reporter, ctxInfo) {
    // RGBA
    basic_transfer_test(reporter, ctxInfo.grContext(), kRGBA_8888_GrPixelConfig,
                        kTopLeft_GrSurfaceOrigin, false);
    basic_transfer_test(reporter, ctxInfo.grContext(), kRGBA_8888_GrPixelConfig,
                        kTopLeft_GrSurfaceOrigin, true);
    basic_transfer_test(reporter, ctxInfo.grContext(), kRGBA_8888_GrPixelConfig,
                        kBottomLeft_GrSurfaceOrigin, false);
    basic_transfer_test(reporter, ctxInfo.grContext(), kRGBA_8888_GrPixelConfig,
                        kBottomLeft_GrSurfaceOrigin, true);

    // BGRA
    basic_transfer_test(reporter, ctxInfo.grContext(), kBGRA_8888_GrPixelConfig,
                        kTopLeft_GrSurfaceOrigin, false);
    basic_transfer_test(reporter, ctxInfo.grContext(), kBGRA_8888_GrPixelConfig,
                        kTopLeft_GrSurfaceOrigin, true);
    basic_transfer_test(reporter, ctxInfo.grContext(), kBGRA_8888_GrPixelConfig,
                        kBottomLeft_GrSurfaceOrigin, false);
    basic_transfer_test(reporter, ctxInfo.grContext(), kBGRA_8888_GrPixelConfig,
                        kBottomLeft_GrSurfaceOrigin, true);
}

#endif