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

#include "src/gpu/GrDataUtils.h"

#include "include/private/SkTPin.h"
#include "include/third_party/skcms/skcms.h"
#include "src/core/SkColorSpaceXformSteps.h"
#include "src/core/SkCompressedDataUtils.h"
#include "src/core/SkConvertPixels.h"
#include "src/core/SkMathPriv.h"
#include "src/core/SkMipmap.h"
#include "src/core/SkTLazy.h"
#include "src/core/SkTraceEvent.h"
#include "src/core/SkUtils.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrColor.h"
#include "src/gpu/GrImageInfo.h"
#include "src/gpu/GrPixmap.h"
#include "src/gpu/GrSwizzle.h"

struct ETC1Block {
    uint32_t fHigh;
    uint32_t fLow;
};

constexpr uint32_t kDiffBit = 0x2; // set -> differential; not-set -> individual

static inline int extend_5To8bits(int b) {
    int c = b & 0x1f;
    return (c << 3) | (c >> 2);
}

static const int kNumETC1ModifierTables = 8;
static const int kNumETC1PixelIndices = 4;

// The index of each row in this table is the ETC1 table codeword
// The index of each column in this table is the ETC1 pixel index value
static const int kETC1ModifierTables[kNumETC1ModifierTables][kNumETC1PixelIndices] = {
    /* 0 */ { 2,    8,  -2,   -8 },
    /* 1 */ { 5,   17,  -5,  -17 },
    /* 2 */ { 9,   29,  -9,  -29 },
    /* 3 */ { 13,  42, -13,  -42 },
    /* 4 */ { 18,  60, -18,  -60 },
    /* 5 */ { 24,  80, -24,  -80 },
    /* 6 */ { 33, 106, -33, -106 },
    /* 7 */ { 47, 183, -47, -183 }
};

// Evaluate one of the entries in 'kModifierTables' to see how close it can get (r8,g8,b8) to
// the original color (rOrig, gOrib, bOrig).
static int test_table_entry(int rOrig, int gOrig, int bOrig,
                            int r8, int g8, int b8,
                            int table, int offset) {
    SkASSERT(0 <= table && table < 8);
    SkASSERT(0 <= offset && offset < 4);

    r8 = SkTPin<int>(r8 + kETC1ModifierTables[table][offset], 0, 255);
    g8 = SkTPin<int>(g8 + kETC1ModifierTables[table][offset], 0, 255);
    b8 = SkTPin<int>(b8 + kETC1ModifierTables[table][offset], 0, 255);

    return SkTAbs(rOrig - r8) + SkTAbs(gOrig - g8) + SkTAbs(bOrig - b8);
}

// Create an ETC1 compressed block that is filled with 'col'
static void create_etc1_block(SkColor col, ETC1Block* block) {
    uint32_t high = 0;
    uint32_t low = 0;

    int rOrig = SkColorGetR(col);
    int gOrig = SkColorGetG(col);
    int bOrig = SkColorGetB(col);

    int r5 = SkMulDiv255Round(31, rOrig);
    int g5 = SkMulDiv255Round(31, gOrig);
    int b5 = SkMulDiv255Round(31, bOrig);

    int r8 = extend_5To8bits(r5);
    int g8 = extend_5To8bits(g5);
    int b8 = extend_5To8bits(b5);

    // We always encode solid color textures in differential mode (i.e., with a 555 base color) but
    // with zero diffs (i.e., bits 26-24, 18-16 and 10-8 are left 0).
    high |= (r5 << 27) | (g5 << 19) | (b5 << 11) | kDiffBit;

    int bestTableIndex = 0, bestPixelIndex = 0;
    int bestSoFar = 1024;
    for (int tableIndex = 0; tableIndex < kNumETC1ModifierTables; ++tableIndex) {
        for (int pixelIndex = 0; pixelIndex < kNumETC1PixelIndices; ++pixelIndex) {
            int score = test_table_entry(rOrig, gOrig, bOrig, r8, g8, b8,
                                         tableIndex, pixelIndex);

            if (bestSoFar > score) {
                bestSoFar = score;
                bestTableIndex = tableIndex;
                bestPixelIndex = pixelIndex;
            }
        }
    }

    high |= (bestTableIndex << 5) | (bestTableIndex << 2);

    if (bestPixelIndex & 0x1) {
        low |= 0xFFFF;
    }
    if (bestPixelIndex & 0x2) {
        low |= 0xFFFF0000;
    }

    block->fHigh = SkBSwap32(high);
    block->fLow = SkBSwap32(low);
}

static int num_4x4_blocks(int size) {
    return ((size + 3) & ~3) >> 2;
}

static int num_ETC1_blocks(int w, int h) {
    w = num_4x4_blocks(w);
    h = num_4x4_blocks(h);

    return w * h;
}

struct BC1Block {
    uint16_t fColor0;
    uint16_t fColor1;
    uint32_t fIndices;
};

static uint16_t to565(SkColor col) {
    int r5 = SkMulDiv255Round(31, SkColorGetR(col));
    int g6 = SkMulDiv255Round(63, SkColorGetG(col));
    int b5 = SkMulDiv255Round(31, SkColorGetB(col));

    return (r5 << 11) | (g6 << 5) | b5;
}

// Create a BC1 compressed block that has two colors but is initialized to 'col0'
static void create_BC1_block(SkColor col0, SkColor col1, BC1Block* block) {
    block->fColor0 = to565(col0);
    block->fColor1 = to565(col1);
    SkASSERT(block->fColor0 <= block->fColor1); // we always assume transparent blocks

    if (col0 == SK_ColorTRANSPARENT) {
        // This sets all 16 pixels to just use color3 (under the assumption
        // that this is a kBC1_RGBA8_UNORM texture. Note that in this case
        // fColor0 will be opaque black.
        block->fIndices = 0xFFFFFFFF;
    } else {
        // This sets all 16 pixels to just use 'fColor0'
        block->fIndices = 0;
    }
}

size_t GrNumBlocks(SkImage::CompressionType type, SkISize baseDimensions) {
    switch (type) {
        case SkImage::CompressionType::kNone:
            return baseDimensions.width() * baseDimensions.height();
        case SkImage::CompressionType::kETC2_RGB8_UNORM:
        case SkImage::CompressionType::kBC1_RGB8_UNORM:
        case SkImage::CompressionType::kBC1_RGBA8_UNORM: {
            int numBlocksWidth = num_4x4_blocks(baseDimensions.width());
            int numBlocksHeight = num_4x4_blocks(baseDimensions.height());

            return numBlocksWidth * numBlocksHeight;
        }
    }
    SkUNREACHABLE;
}

size_t GrCompressedRowBytes(SkImage::CompressionType type, int width) {
    switch (type) {
        case SkImage::CompressionType::kNone:
            return 0;
        case SkImage::CompressionType::kETC2_RGB8_UNORM:
        case SkImage::CompressionType::kBC1_RGB8_UNORM:
        case SkImage::CompressionType::kBC1_RGBA8_UNORM: {
            int numBlocksWidth = num_4x4_blocks(width);

            static_assert(sizeof(ETC1Block) == sizeof(BC1Block));
            return numBlocksWidth * sizeof(ETC1Block);
        }
    }
    SkUNREACHABLE;
}

SkISize GrCompressedDimensions(SkImage::CompressionType type, SkISize baseDimensions) {
    switch (type) {
        case SkImage::CompressionType::kNone:
            return baseDimensions;
        case SkImage::CompressionType::kETC2_RGB8_UNORM:
        case SkImage::CompressionType::kBC1_RGB8_UNORM:
        case SkImage::CompressionType::kBC1_RGBA8_UNORM: {
            int numBlocksWidth = num_4x4_blocks(baseDimensions.width());
            int numBlocksHeight = num_4x4_blocks(baseDimensions.height());

            // Each BC1_RGB8_UNORM and ETC1 block has 16 pixels
            return { 4 * numBlocksWidth, 4 * numBlocksHeight };
        }
    }
    SkUNREACHABLE;
}

// Fill in 'dest' with ETC1 blocks derived from 'colorf'
static void fillin_ETC1_with_color(SkISize dimensions, const SkColor4f& colorf, char* dest) {
    SkColor color = colorf.toSkColor();

    ETC1Block block;
    create_etc1_block(color, &block);

    int numBlocks = num_ETC1_blocks(dimensions.width(), dimensions.height());

    for (int i = 0; i < numBlocks; ++i) {
        memcpy(dest, &block, sizeof(ETC1Block));
        dest += sizeof(ETC1Block);
    }
}

// Fill in 'dest' with BC1 blocks derived from 'colorf'
static void fillin_BC1_with_color(SkISize dimensions, const SkColor4f& colorf, char* dest) {
    SkColor color = colorf.toSkColor();

    BC1Block block;
    create_BC1_block(color, color, &block);

    int numBlocks = num_ETC1_blocks(dimensions.width(), dimensions.height());

    for (int i = 0; i < numBlocks; ++i) {
        memcpy(dest, &block, sizeof(BC1Block));
        dest += sizeof(BC1Block);
    }
}

#if GR_TEST_UTILS

// Fill in 'dstPixels' with BC1 blocks derived from the 'pixmap'.
void GrTwoColorBC1Compress(const SkPixmap& pixmap, SkColor otherColor, char* dstPixels) {
    BC1Block* dstBlocks = reinterpret_cast<BC1Block*>(dstPixels);
    SkASSERT(pixmap.colorType() == SkColorType::kRGBA_8888_SkColorType);

    BC1Block block;

    // black -> fColor0, otherColor -> fColor1
    create_BC1_block(SK_ColorBLACK, otherColor, &block);

    int numXBlocks = num_4x4_blocks(pixmap.width());
    int numYBlocks = num_4x4_blocks(pixmap.height());

    for (int y = 0; y < numYBlocks; ++y) {
        for (int x = 0; x < numXBlocks; ++x) {
            int shift = 0;
            int offsetX = 4 * x, offsetY = 4 * y;
            block.fIndices = 0;  // init all the pixels to color0 (i.e., opaque black)
            for (int i = 0; i < 4; ++i) {
                for (int j = 0; j < 4; ++j, shift += 2) {
                    if (offsetX + j >= pixmap.width() || offsetY + i >= pixmap.height()) {
                        // This can happen for the topmost levels of a mipmap and for
                        // non-multiple of 4 textures
                        continue;
                    }

                    SkColor tmp = pixmap.getColor(offsetX + j, offsetY + i);
                    if (tmp == SK_ColorTRANSPARENT) {
                        // For RGBA BC1 images color3 is set to transparent black
                        block.fIndices |= 3 << shift;
                    } else if (tmp != SK_ColorBLACK) {
                        block.fIndices |= 1 << shift; // color1
                    }
                }
            }

            dstBlocks[y*numXBlocks + x] = block;
        }
    }
}

#endif

size_t GrComputeTightCombinedBufferSize(size_t bytesPerPixel, SkISize baseDimensions,
                                        SkTArray<size_t>* individualMipOffsets, int mipLevelCount) {
    SkASSERT(individualMipOffsets && !individualMipOffsets->count());
    SkASSERT(mipLevelCount >= 1);

    individualMipOffsets->push_back(0);

    size_t combinedBufferSize = baseDimensions.width() * bytesPerPixel * baseDimensions.height();
    SkISize levelDimensions = baseDimensions;

    // The Vulkan spec for copying a buffer to an image requires that the alignment must be at
    // least 4 bytes and a multiple of the bytes per pixel of the image config.
    SkASSERT(bytesPerPixel == 1 || bytesPerPixel == 2 || bytesPerPixel == 3 ||
             bytesPerPixel == 4 || bytesPerPixel == 8 || bytesPerPixel == 16);
    int desiredAlignment = (bytesPerPixel == 3) ? 12 : (bytesPerPixel > 4 ? bytesPerPixel : 4);

    for (int currentMipLevel = 1; currentMipLevel < mipLevelCount; ++currentMipLevel) {
        levelDimensions = {std::max(1, levelDimensions.width() /2),
                           std::max(1, levelDimensions.height()/2)};

        size_t trimmedSize = levelDimensions.area() * bytesPerPixel;
        const size_t alignmentDiff = combinedBufferSize % desiredAlignment;
        if (alignmentDiff != 0) {
            combinedBufferSize += desiredAlignment - alignmentDiff;
        }
        SkASSERT((0 == combinedBufferSize % 4) && (0 == combinedBufferSize % bytesPerPixel));

        individualMipOffsets->push_back(combinedBufferSize);
        combinedBufferSize += trimmedSize;
    }

    SkASSERT(individualMipOffsets->count() == mipLevelCount);
    return combinedBufferSize;
}

void GrFillInCompressedData(SkImage::CompressionType type, SkISize dimensions,
                            GrMipmapped mipMapped, char* dstPixels, const SkColor4f& colorf) {
    TRACE_EVENT0("skia.gpu", TRACE_FUNC);

    int numMipLevels = 1;
    if (mipMapped == GrMipmapped::kYes) {
        numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
    }

    size_t offset = 0;

    for (int i = 0; i < numMipLevels; ++i) {
        size_t levelSize = SkCompressedDataSize(type, dimensions, nullptr, false);

        if (SkImage::CompressionType::kETC2_RGB8_UNORM == type) {
            fillin_ETC1_with_color(dimensions, colorf, &dstPixels[offset]);
        } else {
            SkASSERT(type == SkImage::CompressionType::kBC1_RGB8_UNORM ||
                     type == SkImage::CompressionType::kBC1_RGBA8_UNORM);
            fillin_BC1_with_color(dimensions, colorf, &dstPixels[offset]);
        }

        offset += levelSize;
        dimensions = {std::max(1, dimensions.width()/2), std::max(1, dimensions.height()/2)};
    }
}

static GrSwizzle get_load_and_src_swizzle(GrColorType ct, SkRasterPipeline::StockStage* load,
                                          bool* isNormalized, bool* isSRGB) {
    GrSwizzle swizzle("rgba");
    *isNormalized = true;
    *isSRGB = false;
    switch (ct) {
        case GrColorType::kAlpha_8:          *load = SkRasterPipeline::load_a8;       break;
        case GrColorType::kAlpha_16:         *load = SkRasterPipeline::load_a16;      break;
        case GrColorType::kBGR_565:          *load = SkRasterPipeline::load_565;      break;
        case GrColorType::kABGR_4444:        *load = SkRasterPipeline::load_4444;     break;
        case GrColorType::kARGB_4444:        swizzle = GrSwizzle("bgra");
                                             *load = SkRasterPipeline::load_4444;     break;
        case GrColorType::kBGRA_4444:        swizzle = GrSwizzle("gbar");
                                             *load = SkRasterPipeline::load_4444;     break;
        case GrColorType::kRGBA_8888:        *load = SkRasterPipeline::load_8888;     break;
        case GrColorType::kRG_88:            *load = SkRasterPipeline::load_rg88;     break;
        case GrColorType::kRGBA_1010102:     *load = SkRasterPipeline::load_1010102;  break;
        case GrColorType::kBGRA_1010102:     *load = SkRasterPipeline::load_1010102;
                                             swizzle = GrSwizzle("bgra");
                                             break;
        case GrColorType::kAlpha_F16:        *load = SkRasterPipeline::load_af16;     break;
        case GrColorType::kRGBA_F16_Clamped: *load = SkRasterPipeline::load_f16;      break;
        case GrColorType::kRG_1616:          *load = SkRasterPipeline::load_rg1616;   break;
        case GrColorType::kRGBA_16161616:    *load = SkRasterPipeline::load_16161616; break;

        case GrColorType::kRGBA_8888_SRGB:   *load = SkRasterPipeline::load_8888;
                                             *isSRGB = true;
                                             break;
        case GrColorType::kRG_F16:           *load = SkRasterPipeline::load_rgf16;
                                             *isNormalized = false;
                                             break;
        case GrColorType::kRGBA_F16:         *load = SkRasterPipeline::load_f16;
                                             *isNormalized = false;
                                             break;
        case GrColorType::kRGBA_F32:         *load = SkRasterPipeline::load_f32;
                                             *isNormalized = false;
                                             break;
        case GrColorType::kAlpha_8xxx:       *load = SkRasterPipeline::load_8888;
                                             swizzle = GrSwizzle("000r");
                                             break;
        case GrColorType::kAlpha_F32xxx:     *load = SkRasterPipeline::load_f32;
                                             swizzle = GrSwizzle("000r");
                                             break;
        case GrColorType::kGray_8xxx:       *load = SkRasterPipeline::load_8888;
                                             swizzle = GrSwizzle("rrr1");
                                             break;
        case GrColorType::kGray_8:           *load = SkRasterPipeline::load_a8;
                                             swizzle = GrSwizzle("aaa1");
                                             break;
        case GrColorType::kGrayAlpha_88:    *load = SkRasterPipeline::load_rg88;
                                             swizzle = GrSwizzle("rrrg");
                                             break;
        case GrColorType::kBGRA_8888:        *load = SkRasterPipeline::load_8888;
                                             swizzle = GrSwizzle("bgra");
                                             break;
        case GrColorType::kRGB_888x:         *load = SkRasterPipeline::load_8888;
                                             swizzle = GrSwizzle("rgb1");
                                             break;

        // These are color types we don't expect to ever have to load.
        case GrColorType::kRGB_888:
        case GrColorType::kR_8:
        case GrColorType::kR_16:
        case GrColorType::kR_F16:
        case GrColorType::kGray_F16:
        case GrColorType::kUnknown:
            SK_ABORT("unexpected CT");
    }
    return swizzle;
}

enum class LumMode {
    kNone,
    kToRGB,
    kToAlpha
};

static GrSwizzle get_dst_swizzle_and_store(GrColorType ct, SkRasterPipeline::StockStage* store,
                                           LumMode* lumMode, bool* isNormalized, bool* isSRGB) {
    GrSwizzle swizzle("rgba");
    *isNormalized = true;
    *isSRGB = false;
    *lumMode = LumMode::kNone;
    switch (ct) {
        case GrColorType::kAlpha_8:          *store = SkRasterPipeline::store_a8;       break;
        case GrColorType::kAlpha_16:         *store = SkRasterPipeline::store_a16;      break;
        case GrColorType::kBGR_565:          *store = SkRasterPipeline::store_565;      break;
        case GrColorType::kABGR_4444:        *store = SkRasterPipeline::store_4444;     break;
        case GrColorType::kARGB_4444:        swizzle = GrSwizzle("bgra");
                                             *store = SkRasterPipeline::store_4444;     break;
        case GrColorType::kBGRA_4444:        swizzle = GrSwizzle("argb");
                                             *store = SkRasterPipeline::store_4444;     break;
        case GrColorType::kRGBA_8888:        *store = SkRasterPipeline::store_8888;     break;
        case GrColorType::kRG_88:            *store = SkRasterPipeline::store_rg88;     break;
        case GrColorType::kRGBA_1010102:     *store = SkRasterPipeline::store_1010102;  break;
        case GrColorType::kBGRA_1010102:     swizzle = GrSwizzle("bgra");
                                             *store = SkRasterPipeline::store_1010102;
                                             break;
        case GrColorType::kRGBA_F16_Clamped: *store = SkRasterPipeline::store_f16;      break;
        case GrColorType::kRG_1616:          *store = SkRasterPipeline::store_rg1616;   break;
        case GrColorType::kRGBA_16161616:    *store = SkRasterPipeline::store_16161616; break;

        case GrColorType::kRGBA_8888_SRGB:   *store = SkRasterPipeline::store_8888;
                                             *isSRGB = true;
                                             break;
        case GrColorType::kRG_F16:           *store = SkRasterPipeline::store_rgf16;
                                             *isNormalized = false;
                                             break;
        case GrColorType::kAlpha_F16:        *store = SkRasterPipeline::store_af16;
                                             *isNormalized = false;
                                             break;
        case GrColorType::kRGBA_F16:         *store = SkRasterPipeline::store_f16;
                                             *isNormalized = false;
                                             break;
        case GrColorType::kRGBA_F32:         *store = SkRasterPipeline::store_f32;
                                             *isNormalized = false;
                                             break;
        case GrColorType::kAlpha_8xxx:       *store = SkRasterPipeline::store_8888;
                                             swizzle = GrSwizzle("a000");
                                             break;
        case GrColorType::kAlpha_F32xxx:     *store = SkRasterPipeline::store_f32;
                                             swizzle = GrSwizzle("a000");
                                             break;
        case GrColorType::kBGRA_8888:        swizzle = GrSwizzle("bgra");
                                             *store = SkRasterPipeline::store_8888;
                                             break;
        case GrColorType::kRGB_888x:         swizzle = GrSwizzle("rgb1");
                                             *store = SkRasterPipeline::store_8888;
                                             break;
        case GrColorType::kR_8:              swizzle = GrSwizzle("agbr");
                                             *store = SkRasterPipeline::store_a8;
                                             break;
        case GrColorType::kR_16:             swizzle = GrSwizzle("agbr");
                                             *store = SkRasterPipeline::store_a16;
                                             break;
        case GrColorType::kR_F16:            swizzle = GrSwizzle("agbr");
                                             *store = SkRasterPipeline::store_af16;
                                             break;
        case GrColorType::kGray_F16:         *lumMode = LumMode::kToAlpha;
                                             *store = SkRasterPipeline::store_af16;
                                             break;
        case GrColorType::kGray_8:           *lumMode = LumMode::kToAlpha;
                                             *store = SkRasterPipeline::store_a8;
                                             break;
        case GrColorType::kGrayAlpha_88:     *lumMode = LumMode::kToRGB;
                                             swizzle = GrSwizzle("ragb");
                                             *store = SkRasterPipeline::store_rg88;
                                             break;
        case GrColorType::kGray_8xxx:        *lumMode = LumMode::kToRGB;
                                             *store = SkRasterPipeline::store_8888;
                                             swizzle = GrSwizzle("r000");
                                             break;

        // These are color types we don't expect to ever have to store.
        case GrColorType::kRGB_888:  // This is handled specially in GrConvertPixels.
        case GrColorType::kUnknown:
            SK_ABORT("unexpected CT");
    }
    return swizzle;
}

static inline void append_clamp_gamut(SkRasterPipeline* pipeline) {
    // SkRasterPipeline may not know our color type and also doesn't like caller to directly
    // append clamp_gamut. Fake it out.
    static SkImageInfo fakeII = SkImageInfo::MakeN32Premul(1, 1);
    pipeline->append_gamut_clamp_if_normalized(fakeII);
}

bool GrConvertPixels(const GrPixmap& dst, const GrCPixmap& src, bool flipY) {
    TRACE_EVENT0("skia.gpu", TRACE_FUNC);
    if (src.dimensions().isEmpty() || dst.dimensions().isEmpty()) {
        return false;
    }
    if (src.colorType() == GrColorType::kUnknown || dst.colorType() == GrColorType::kUnknown) {
        return false;
    }
    if (!src.hasPixels() || !dst.hasPixels()) {
        return false;
    }
    if (dst.dimensions() != src.dimensions()) {
        return false;
    }
    if (dst.colorType() == GrColorType::kRGB_888) {
        // SkRasterPipeline doesn't handle writing to RGB_888. So we have it write to RGB_888x and
        // then do another conversion that does the 24bit packing. We could be cleverer and skip the
        // temp pixmap if this is the only conversion but this is rare so keeping it simple.
        GrPixmap temp = GrPixmap::Allocate(dst.info().makeColorType(GrColorType::kRGB_888x));
        if (!GrConvertPixels(temp, src, flipY)) {
            return false;
        }
        auto* tRow = reinterpret_cast<const char*>(temp.addr());
        auto* dRow = reinterpret_cast<char*>(dst.addr());
        for (int y = 0; y < dst.height(); ++y, tRow += temp.rowBytes(), dRow += dst.rowBytes()) {
            for (int x = 0; x < dst.width(); ++x) {
                auto t = tRow + x*sizeof(uint32_t);
                auto d = dRow + x*3;
                memcpy(d, t, 3);
            }
        }
        return true;
    } else if (src.colorType() == GrColorType::kRGB_888) {
        // SkRasterPipeline doesn't handle reading from RGB_888. So convert it to RGB_888x and then
        // do a recursive call if there is any remaining conversion.
        GrPixmap temp = GrPixmap::Allocate(src.info().makeColorType(GrColorType::kRGB_888x));
        auto* sRow = reinterpret_cast<const char*>(src.addr());
        auto* tRow = reinterpret_cast<char*>(temp.addr());
        for (int y = 0; y < src.height(); ++y, sRow += src.rowBytes(), tRow += temp.rowBytes()) {
            for (int x = 0; x < src.width(); ++x) {
                auto s = sRow + x*3;
                auto t = tRow + x*sizeof(uint32_t);
                memcpy(t, s, 3);
                t[3] = static_cast<char>(0xFF);
            }
        }
        return GrConvertPixels(dst, temp, flipY);
    }

    size_t srcBpp = src.info().bpp();
    size_t dstBpp = dst.info().bpp();

    // SkRasterPipeline operates on row-pixels not row-bytes.
    SkASSERT(dst.rowBytes() % dstBpp == 0);
    SkASSERT(src.rowBytes() % srcBpp == 0);

    bool premul   = src.alphaType() == kUnpremul_SkAlphaType &&
                    dst.alphaType() == kPremul_SkAlphaType;
    bool unpremul = src.alphaType() == kPremul_SkAlphaType &&
                    dst.alphaType() == kUnpremul_SkAlphaType;
    bool alphaOrCSConversion =
            premul || unpremul || !SkColorSpace::Equals(src.colorSpace(), dst.colorSpace());

    if (src.colorType() == dst.colorType() && !alphaOrCSConversion) {
        size_t tightRB = dstBpp * dst.width();
        if (flipY) {
            auto s = static_cast<const char*>(src.addr());
            auto d = SkTAddOffset<char>(dst.addr(), dst.rowBytes()*(dst.height() - 1));
            for (int y = 0; y < dst.height(); ++y, d -= dst.rowBytes(), s += src.rowBytes()) {
                memcpy(d, s, tightRB);
            }
        } else {
            SkRectMemcpy(dst.addr(), dst.rowBytes(),
                         src.addr(), src.rowBytes(),
                         tightRB, src.height());
        }
        return true;
    }

    SkRasterPipeline::StockStage load;
    bool srcIsNormalized;
    bool srcIsSRGB;
    auto loadSwizzle = get_load_and_src_swizzle(src.colorType(),
                                                &load,
                                                &srcIsNormalized,
                                                &srcIsSRGB);

    SkRasterPipeline::StockStage store;
    LumMode lumMode;
    bool dstIsNormalized;
    bool dstIsSRGB;
    auto storeSwizzle = get_dst_swizzle_and_store(dst.colorType(),
                                                  &store,
                                                  &lumMode,
                                                  &dstIsNormalized,
                                                  &dstIsSRGB);

    bool clampGamut;
    SkTLazy<SkColorSpaceXformSteps> steps;
    GrSwizzle loadStoreSwizzle;
    if (alphaOrCSConversion) {
        steps.init(src.colorSpace(), src.alphaType(), dst.colorSpace(), dst.alphaType());
        clampGamut = dstIsNormalized && dst.alphaType() == kPremul_SkAlphaType;
    } else {
        clampGamut = dstIsNormalized && !srcIsNormalized && dst.alphaType() == kPremul_SkAlphaType;
        if (!clampGamut) {
            loadStoreSwizzle = GrSwizzle::Concat(loadSwizzle, storeSwizzle);
        }
    }
    int cnt = 1;
    int height = src.height();
    SkRasterPipeline_MemoryCtx
            srcCtx{const_cast<void*>(src.addr()), SkToInt(src.rowBytes()/srcBpp)},
            dstCtx{                   dst.addr(), SkToInt(dst.rowBytes()/dstBpp)};

    if (flipY) {
        // It *almost* works to point the src at the last row and negate the stride and run the
        // whole rectangle. However, SkRasterPipeline::run()'s control loop uses size_t loop
        // variables so it winds up relying on unsigned overflow math. It works out in practice
        // but UBSAN says "no!" as it's technically undefined and in theory a compiler could emit
        // code that didn't do what is intended. So we go one row at a time. :(
        srcCtx.pixels = static_cast<char*>(srcCtx.pixels) + src.rowBytes()*(height - 1);
        std::swap(cnt, height);
    }

    bool hasConversion = alphaOrCSConversion || clampGamut || lumMode != LumMode::kNone;

    if (srcIsSRGB && dstIsSRGB && !hasConversion) {
        // No need to convert from srgb if we are just going to immediately convert it back.
        srcIsSRGB = dstIsSRGB = false;
    }

    hasConversion = hasConversion || srcIsSRGB || dstIsSRGB;

    for (int i = 0; i < cnt; ++i) {
        SkRasterPipeline_<256> pipeline;
        pipeline.append(load, &srcCtx);
        if (hasConversion) {
            loadSwizzle.apply(&pipeline);
            if (srcIsSRGB) {
                pipeline.append_transfer_function(*skcms_sRGB_TransferFunction());
            }
            if (alphaOrCSConversion) {
                steps->apply(&pipeline);
            }
            if (clampGamut) {
                append_clamp_gamut(&pipeline);
            }
            switch (lumMode) {
                case LumMode::kNone:
                    break;
                case LumMode::kToRGB:
                    pipeline.append(SkRasterPipeline::StockStage::bt709_luminance_or_luma_to_rgb);
                    break;
                case LumMode::kToAlpha:
                    pipeline.append(SkRasterPipeline::StockStage::bt709_luminance_or_luma_to_alpha);
                    // If we ever need to store srgb-encoded gray (e.g. GL_SLUMINANCE8) then we
                    // should use ToRGB and then a swizzle stage rather than ToAlpha. The subsequent
                    // transfer function stage ignores the alpha channel (where we just stashed the
                    // gray).
                    SkASSERT(!dstIsSRGB);
                    break;
            }
            if (dstIsSRGB) {
                pipeline.append_transfer_function(*skcms_sRGB_Inverse_TransferFunction());
            }
            storeSwizzle.apply(&pipeline);
        } else {
            loadStoreSwizzle.apply(&pipeline);
        }
        pipeline.append(store, &dstCtx);
        pipeline.run(0, 0, src.width(), height);
        srcCtx.pixels = static_cast<char*>(srcCtx.pixels) - src.rowBytes();
        dstCtx.pixels = static_cast<char*>(dstCtx.pixels) + dst.rowBytes();
    }
    return true;
}

bool GrClearImage(const GrImageInfo& dstInfo, void* dst, size_t dstRB, std::array<float, 4> color) {
    TRACE_EVENT0("skia.gpu", TRACE_FUNC);

    if (!dstInfo.isValid()) {
        return false;
    }
    if (!dst) {
        return false;
    }
    if (dstRB < dstInfo.minRowBytes()) {
        return false;
    }
    if (dstInfo.colorType() == GrColorType::kRGB_888) {
        // SkRasterPipeline doesn't handle writing to RGB_888. So we handle that specially here.
        uint32_t rgba = SkColor4f{color[0], color[1], color[2], color[3]}.toBytes_RGBA();
        for (int y = 0; y < dstInfo.height(); ++y) {
            char* d = static_cast<char*>(dst) + y * dstRB;
            for (int x = 0; x < dstInfo.width(); ++x, d += 3) {
                memcpy(d, &rgba, 3);
            }
        }
        return true;
    }

    LumMode lumMode;
    bool isNormalized;
    bool dstIsSRGB;
    SkRasterPipeline::StockStage store;
    GrSwizzle storeSwizzle = get_dst_swizzle_and_store(dstInfo.colorType(), &store, &lumMode,
                                                       &isNormalized, &dstIsSRGB);
    char block[64];
    SkArenaAlloc alloc(block, sizeof(block), 1024);
    SkRasterPipeline_<256> pipeline;
    pipeline.append_constant_color(&alloc, color.data());
    switch (lumMode) {
        case LumMode::kNone:
            break;
        case LumMode::kToRGB:
            pipeline.append(SkRasterPipeline::StockStage::bt709_luminance_or_luma_to_rgb);
            break;
        case LumMode::kToAlpha:
            pipeline.append(SkRasterPipeline::StockStage::bt709_luminance_or_luma_to_alpha);
            // If we ever need to store srgb-encoded gray (e.g. GL_SLUMINANCE8) then we should use
            // ToRGB and then a swizzle stage rather than ToAlpha. The subsequent transfer function
            // stage ignores the alpha channel (where we just stashed the gray).
            SkASSERT(!dstIsSRGB);
            break;
    }
    if (dstIsSRGB) {
        pipeline.append_transfer_function(*skcms_sRGB_Inverse_TransferFunction());
    }
    storeSwizzle.apply(&pipeline);
    SkRasterPipeline_MemoryCtx dstCtx{dst, SkToInt(dstRB/dstInfo.bpp())};
    pipeline.append(store, &dstCtx);
    pipeline.run(0, 0, dstInfo.width(), dstInfo.height());

    return true;
}
