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

#include "tests/Test.h"

#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkPoint.h"
#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/Recorder.h"
#include "include/gpu/graphite/Recording.h"
#include "include/gpu/graphite/Surface.h"
#include "src/gpu/graphite/RecordingPriv.h"
#include "src/gpu/graphite/Surface_Graphite.h"

namespace skgpu::graphite {

constexpr SkIVector kNoOffset = SkIVector::Make(0, 0);
constexpr SkIRect kEmptyClip = SkIRect::MakeEmpty();

using DrawCallback = std::function<void(SkCanvas*)>;

struct Expectation {
    int fX;
    int fY;
    SkColor4f fColor;
};

void run_test(skiatest::Reporter* reporter,
              Context* context,
              SkISize surfaceSize,
              SkISize recordingSize,
              SkIVector replayOffset,
              SkIRect replayClip,
              skgpu::Mipmapped canvasMipmapped,
              skgpu::Mipmapped targetMipmapped,
              DrawCallback draw,
              const std::vector<Expectation>& expectations) {
    const SkImageInfo surfaceImageInfo = SkImageInfo::Make(
            surfaceSize, SkColorType::kRGBA_8888_SkColorType, SkAlphaType::kPremul_SkAlphaType);

    std::unique_ptr<Recorder> surfaceRecorder = context->makeRecorder();
    sk_sp<SkSurface> surface =
            SkSurfaces::RenderTarget(surfaceRecorder.get(), surfaceImageInfo, canvasMipmapped);
    Surface* graphiteSurface = static_cast<Surface*>(surface.get());
    const TextureInfo& textureInfo = graphiteSurface->backingTextureProxy()->textureInfo();

    // Flush the initial clear added by MakeGraphite.
    std::unique_ptr<skgpu::graphite::Recording> surfaceRecording = surfaceRecorder->snap();
    context->insertRecording({surfaceRecording.get()});

    // Snap a recording without a bound target.
    const SkImageInfo recordingImageInfo = surfaceImageInfo.makeDimensions(recordingSize);
    std::unique_ptr<Recorder> recorder = context->makeRecorder();
    SkCanvas* canvas = recorder->makeDeferredCanvas(recordingImageInfo, textureInfo);
    draw(canvas);
    std::unique_ptr<Recording> recording = recorder->snap();

    // Play back recording. If the mipmap settings don't match, we have to create a new surface.
    if (canvasMipmapped != targetMipmapped) {
        surface =
                SkSurfaces::RenderTarget(surfaceRecorder.get(), surfaceImageInfo, targetMipmapped);
        // Flush the initial clear for this new surface.
        surfaceRecording = surfaceRecorder->snap();
        context->insertRecording({surfaceRecording.get()});
    }
    context->insertRecording({recording.get(), surface.get(), replayOffset, replayClip});

    // Read pixels.
    SkBitmap bitmap;
    SkPixmap pixmap;
    bitmap.allocPixels(surfaceImageInfo);
    SkAssertResult(bitmap.peekPixels(&pixmap));
    if (!surface->readPixels(pixmap, 0, 0)) {
        ERRORF(reporter, "readPixels failed");
        return;
    }

    // Veryify expectations are met and recording is uninstantiated.
    REPORTER_ASSERT(reporter, !recording->priv().isTargetProxyInstantiated());
    for (const Expectation& e : expectations) {
        SkColor4f color = pixmap.getColor4f(e.fX, e.fY);
#ifdef SK_DEBUG
        if (color != e.fColor) {
            SkDebugf("Wrong color\n\texpected: %f %f %f %f\n\tactual: %f %f %f %f",
                     e.fColor.fR,
                     e.fColor.fG,
                     e.fColor.fB,
                     e.fColor.fA,
                     color.fR,
                     color.fG,
                     color.fB,
                     color.fA);
        }
#endif
        REPORTER_ASSERT(reporter, color == e.fColor);
    }
}

// Tests that clear does not clear an entire replayed-to surface if recorded onto a smaller surface.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestClear, reporter, context,
                                   CtsEnforcement::kApiLevel_V) {
    SkISize surfaceSize = SkISize::Make(8, 4);
    SkISize recordingSize = SkISize::Make(4, 4);

    auto draw = [](SkCanvas* canvas) { canvas->clear(SkColors::kRed); };

    std::vector<Expectation> expectations = {{0, 0, SkColors::kRed},
                                             {4, 0, SkColors::kTransparent}};

    run_test(reporter,
             context,
             surfaceSize,
             recordingSize,
             kNoOffset,
             kEmptyClip,
             skgpu::Mipmapped::kNo,
             skgpu::Mipmapped::kNo,
             draw,
             expectations);
}

// Tests that a draw is translated correctly when replayed with an offset.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestDraw, reporter, context,
                                   CtsEnforcement::kNextRelease) {
    SkISize surfaceSize = SkISize::Make(8, 4);
    SkISize recordingSize = SkISize::Make(4, 4);
    SkIVector replayOffset = SkIVector::Make(4, 0);

    auto draw = [](SkCanvas* canvas) {
        canvas->drawIRect(SkIRect::MakeXYWH(0, 0, 4, 4), SkPaint(SkColors::kRed));
    };


    std::vector<Expectation> expectations = {{0, 0, SkColors::kTransparent},
                                             {4, 0, SkColors::kRed}};

    run_test(reporter,
             context,
             surfaceSize,
             recordingSize,
             replayOffset,
             kEmptyClip,
             skgpu::Mipmapped::kNo,
             skgpu::Mipmapped::kNo,
             draw,
             expectations);
}

// Tests that writePixels is translated correctly when replayed with an offset.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestWritePixels, reporter, context,
                                   CtsEnforcement::kApiLevel_V) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(4, 4, true);
    SkCanvas bitmapCanvas(bitmap);
    SkPaint paint;
    paint.setColor(SkColors::kRed);
    bitmapCanvas.drawIRect(SkIRect::MakeXYWH(0, 0, 4, 4), paint);

    SkISize surfaceSize = SkISize::Make(8, 4);
    SkISize recordingSize = SkISize::Make(4, 4);
    SkIVector replayOffset = SkIVector::Make(4, 0);

    auto draw = [&bitmap](SkCanvas* canvas) { canvas->writePixels(bitmap, 0, 0); };

    std::vector<Expectation> expectations = {{0, 0, SkColors::kTransparent},
                                             {4, 0, SkColors::kRed}};

    run_test(reporter,
             context,
             surfaceSize,
             recordingSize,
             replayOffset,
             kEmptyClip,
             skgpu::Mipmapped::kNo,
             skgpu::Mipmapped::kNo,
             draw,
             expectations);
}

// Tests that the result of writePixels is cropped correctly when offscreen.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestWritePixelsOffscreen, reporter, context,
                                   CtsEnforcement::kApiLevel_V) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(4, 4, true);
    SkCanvas bitmapCanvas(bitmap);
    SkPaint paint;
    paint.setColor(SkColors::kRed);
    bitmapCanvas.drawIRect(SkIRect::MakeXYWH(0, 0, 4, 4), paint);
    paint.setColor(SkColors::kGreen);
    bitmapCanvas.drawIRect(SkIRect::MakeXYWH(2, 2, 2, 2), paint);

    SkISize surfaceSize = SkISize::Make(4, 4);
    SkISize recordingSize = SkISize::Make(4, 4);
    SkIVector replayOffset = SkIVector::Make(-2, -2);

    auto draw = [&bitmap](SkCanvas* canvas) { canvas->writePixels(bitmap, 0, 0); };

    std::vector<Expectation> expectations = {{0, 0, SkColors::kGreen}};

    run_test(reporter,
             context,
             surfaceSize,
             recordingSize,
             replayOffset,
             kEmptyClip,
             skgpu::Mipmapped::kNo,
             skgpu::Mipmapped::kNo,
             draw,
             expectations);
}

// Tests that the result of a draw is cropped correctly with a provided clip on replay.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestDrawWithClip, reporter, context,
                                   CtsEnforcement::kNextRelease) {
    SkISize surfaceSize = SkISize::Make(8, 4);
    SkISize recordingSize = SkISize::Make(8, 4);

    auto draw = [](SkCanvas* canvas) {
        canvas->drawIRect(SkIRect::MakeXYWH(0, 0, 4, 4), SkPaint(SkColors::kRed));
        canvas->drawIRect(SkIRect::MakeXYWH(4, 0, 4, 4), SkPaint(SkColors::kGreen));
    };

    {
        // Test that the clip applies in translated space.
        SkIVector replayOffset = SkIVector::Make(4, 0);
        SkIRect replayClip = SkIRect::MakeXYWH(0, 0, 2, 4);

        std::vector<Expectation> expectations = {{0, 0, SkColors::kTransparent},
                                                 {4, 0, SkColors::kRed},
                                                 {6, 0, SkColors::kTransparent}};

        run_test(reporter,
                 context,
                 surfaceSize,
                 recordingSize,
                 replayOffset,
                 replayClip,
                 skgpu::Mipmapped::kNo,
                 skgpu::Mipmapped::kNo,
                 draw,
                 expectations);
    }

    {
        // Test that the draw is not translated by the clip.
        SkIRect replayClip = SkIRect::MakeXYWH(4, 0, 2, 4);

        std::vector<Expectation> expectations = {{4, 0, SkColors::kGreen}};

        run_test(reporter,
                 context,
                 surfaceSize,
                 recordingSize,
                 kNoOffset,
                 replayClip,
                 skgpu::Mipmapped::kNo,
                 skgpu::Mipmapped::kNo,
                 draw,
                 expectations);
    }
}

// Tests that the result of writePixels is cropped correctly with a provided clip on replay.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestWritePixelsWithClip, reporter, context,
                                   CtsEnforcement::kNextRelease) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(4, 4, true);
    SkCanvas bitmapCanvas(bitmap);
    SkPaint paint;
    paint.setColor(SkColors::kRed);
    bitmapCanvas.drawIRect(SkIRect::MakeXYWH(0, 0, 4, 4), paint);

    SkISize surfaceSize = SkISize::Make(8, 4);
    SkISize recordingSize = SkISize::Make(4, 4);
    SkIVector replayOffset = SkIVector::Make(4, 0);
    SkIRect replayClip = SkIRect::MakeXYWH(0, 0, 2, 4);

    auto draw = [&bitmap](SkCanvas* canvas) { canvas->writePixels(bitmap, 0, 0); };

    std::vector<Expectation> expectations = {{0, 0, SkColors::kTransparent},
                                             {4, 0, SkColors::kRed},
                                             {6, 0, SkColors::kTransparent}};

    run_test(reporter,
             context,
             surfaceSize,
             recordingSize,
             replayOffset,
             replayClip,
             skgpu::Mipmapped::kNo,
             skgpu::Mipmapped::kNo,
             draw,
             expectations);
}

DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestMipmapped, reporter, context,
                                   CtsEnforcement::kNextRelease) {
    SkISize recordingSize = SkISize::Make(1, 1);

    auto draw = [](SkCanvas* canvas) { canvas->clear(SkColors::kRed); };

    {
        SkISize surfaceSize = SkISize::Make(1, 1);

        std::vector<Expectation> expectations = {{0, 0, SkColors::kRed}};

        run_test(reporter,
                 context,
                 surfaceSize,
                 recordingSize,
                 kNoOffset,
                 kEmptyClip,
                 skgpu::Mipmapped::kYes,
                 skgpu::Mipmapped::kYes,
                 draw,
                 expectations);
    }

    {
        // Test that draw fails if canvas and surface mipmap settings don't match.
        SkISize surfaceSize = SkISize::Make(1, 1);

        std::vector<Expectation> expectations = {{0, 0, SkColors::kTransparent}};

        run_test(reporter,
                 context,
                 surfaceSize,
                 recordingSize,
                 kNoOffset,
                 kEmptyClip,
                 skgpu::Mipmapped::kYes,
                 skgpu::Mipmapped::kNo,
                 draw,
                 expectations);

        run_test(reporter,
                 context,
                 surfaceSize,
                 recordingSize,
                 kNoOffset,
                 kEmptyClip,
                 skgpu::Mipmapped::kNo,
                 skgpu::Mipmapped::kYes,
                 draw,
                 expectations);
    }

    {
        // Test that draw fails if canvas and surface dimensions don't match.
        SkISize surfaceSize = SkISize::Make(2, 1);

        std::vector<Expectation> expectations = {{0, 0, SkColors::kTransparent}};

        run_test(reporter,
                 context,
                 surfaceSize,
                 recordingSize,
                 kNoOffset,
                 kEmptyClip,
                 skgpu::Mipmapped::kYes,
                 skgpu::Mipmapped::kYes,
                 draw,
                 expectations);
    }

    {
        // Test that draw fails if offset is provided.
        SkISize surfaceSize = SkISize::Make(1, 1);
        SkIVector replayOffset = SkIVector::Make(1, 0);

        std::vector<Expectation> expectations = {{0, 0, SkColors::kTransparent}};

        run_test(reporter,
                 context,
                 surfaceSize,
                 recordingSize,
                 replayOffset,
                 kEmptyClip,
                 skgpu::Mipmapped::kYes,
                 skgpu::Mipmapped::kYes,
                 draw,
                 expectations);
    }

    {
        // Test that draw fails if clip is provided.
        SkISize surfaceSize = SkISize::Make(1, 1);
        SkIRect replayClip = SkIRect::MakeXYWH(0, 0, 1, 1);

        std::vector<Expectation> expectations = {{0, 0, SkColors::kTransparent}};

        run_test(reporter,
                 context,
                 surfaceSize,
                 recordingSize,
                 kNoOffset,
                 replayClip,
                 skgpu::Mipmapped::kYes,
                 skgpu::Mipmapped::kYes,
                 draw,
                 expectations);
    }
}

DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestMipmappedWritePixels, reporter, context,
                                   CtsEnforcement::kNextRelease) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(1, 1, true);
    SkCanvas bitmapCanvas(bitmap);
    SkPaint paint;
    paint.setColor(SkColors::kRed);
    bitmapCanvas.drawIRect(SkIRect::MakeXYWH(0, 0, 1, 1), paint);

    SkISize recordingSize = SkISize::Make(1, 1);
    SkISize surfaceSize = SkISize::Make(1, 1);

    auto draw = [&bitmap](SkCanvas* canvas) { canvas->writePixels(bitmap, 0, 0); };

    std::vector<Expectation> expectations = {{0, 0, SkColors::kRed}};

    run_test(reporter,
             context,
             surfaceSize,
             recordingSize,
             kNoOffset,
             kEmptyClip,
             skgpu::Mipmapped::kYes,
             skgpu::Mipmapped::kYes,
             draw,
             expectations);
}

// Tests that you can't create two deferred canvases before snapping the first.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestTwoCanvases, reporter, context,
                                   CtsEnforcement::kNextRelease) {
    const SkImageInfo kImageInfo = SkImageInfo::Make(SkISize::Make(1, 1),
                                                     SkColorType::kRGBA_8888_SkColorType,
                                                     SkAlphaType::kPremul_SkAlphaType);
    std::unique_ptr<Recorder> recorder = context->makeRecorder();
    sk_sp<SkSurface> surface =
            SkSurfaces::RenderTarget(recorder.get(), kImageInfo, skgpu::Mipmapped::kNo);
    const TextureInfo& textureInfo =
            static_cast<Surface*>(surface.get())->backingTextureProxy()->textureInfo();

    // First canvas is created successfully.
    REPORTER_ASSERT(reporter, recorder->makeDeferredCanvas(kImageInfo, textureInfo) != nullptr);
    // Second canvas fails to be created.
    REPORTER_ASSERT(reporter, recorder->makeDeferredCanvas(kImageInfo, textureInfo) == nullptr);
}

// Tests that inserting a recording with a surface does not crash even if no draws to a deferred
// canvas were recorded.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(RecordingSurfacesTestUnnecessarySurface, reporter, context,
                                   CtsEnforcement::kNextRelease) {
    const SkImageInfo kImageInfo = SkImageInfo::Make(SkISize::Make(1, 1),
                                                     SkColorType::kRGBA_8888_SkColorType,
                                                     SkAlphaType::kPremul_SkAlphaType);
    std::unique_ptr<Recorder> recorder = context->makeRecorder();

    sk_sp<SkSurface> surface =
            SkSurfaces::RenderTarget(recorder.get(), kImageInfo, skgpu::Mipmapped::kNo);
    std::unique_ptr<Recording> recording = recorder->snap();
    REPORTER_ASSERT(reporter, context->insertRecording({recording.get(), surface.get()}));
}

}  // namespace skgpu::graphite
