blob: 916c3e15cbd8a2e041c1863d284c0ac328b087ea [file] [log] [blame]
/*
* 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/ganesh/GrDataUtils.h"
#include "include/core/SkAlphaType.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkSize.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkMath.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/base/SkTo.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "modules/skcms/skcms.h"
#include "src/base/SkArenaAlloc.h"
#include "src/base/SkRectMemcpy.h"
#include "src/base/SkTLazy.h"
#include "src/core/SkColorSpaceXformSteps.h"
#include "src/core/SkRasterPipeline.h"
#include "src/core/SkRasterPipelineOpContexts.h"
#include "src/core/SkRasterPipelineOpList.h"
#include "src/core/SkTraceEvent.h"
#include "src/gpu/Swizzle.h"
#include "src/gpu/ganesh/GrImageInfo.h"
#include "src/gpu/ganesh/GrPixmap.h"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <functional>
using namespace skia_private;
#if defined(GR_TEST_UTILS)
// The following four helpers are copied from src/gpu/DataUtils.cpp to support the test only
// GrTwoColorBC1Compress function. Ideally we would copy the test function into DataUtils.cpp
// instead, but we're currently trying to avoid using the GR_TEST_UTILS define in src/gpu.
static int num_4x4_blocks(int size) {
return ((size + 3) & ~3) >> 2;
}
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;
}
}
// 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,
TArray<size_t>* individualMipOffsets, int mipLevelCount) {
SkASSERT(individualMipOffsets && individualMipOffsets->empty());
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->size() == mipLevelCount);
return combinedBufferSize;
}
static skgpu::Swizzle get_load_and_src_swizzle(GrColorType ct, SkRasterPipelineOp* load,
bool* isNormalized, bool* isSRGB) {
skgpu::Swizzle swizzle("rgba");
*isNormalized = true;
*isSRGB = false;
switch (ct) {
case GrColorType::kAlpha_8: *load = SkRasterPipelineOp::load_a8; break;
case GrColorType::kAlpha_16: *load = SkRasterPipelineOp::load_a16; break;
case GrColorType::kBGR_565: *load = SkRasterPipelineOp::load_565; break;
case GrColorType::kRGB_565: swizzle = skgpu::Swizzle("bgr1");
*load = SkRasterPipelineOp::load_565; break;
case GrColorType::kABGR_4444: *load = SkRasterPipelineOp::load_4444; break;
case GrColorType::kARGB_4444: swizzle = skgpu::Swizzle("bgra");
*load = SkRasterPipelineOp::load_4444; break;
case GrColorType::kBGRA_4444: swizzle = skgpu::Swizzle("gbar");
*load = SkRasterPipelineOp::load_4444; break;
case GrColorType::kRGBA_8888: *load = SkRasterPipelineOp::load_8888; break;
case GrColorType::kRG_88: *load = SkRasterPipelineOp::load_rg88; break;
case GrColorType::kRGBA_1010102: *load = SkRasterPipelineOp::load_1010102; break;
case GrColorType::kBGRA_1010102: *load = SkRasterPipelineOp::load_1010102;
swizzle = skgpu::Swizzle("bgra");
break;
case GrColorType::kRGBA_10x6: *load = SkRasterPipelineOp::load_10x6; break;
case GrColorType::kAlpha_F16: *load = SkRasterPipelineOp::load_af16; break;
case GrColorType::kRGBA_F16_Clamped: *load = SkRasterPipelineOp::load_f16; break;
case GrColorType::kRG_1616: *load = SkRasterPipelineOp::load_rg1616; break;
case GrColorType::kRGBA_16161616: *load = SkRasterPipelineOp::load_16161616; break;
case GrColorType::kRGBA_8888_SRGB: *load = SkRasterPipelineOp::load_8888;
*isSRGB = true;
break;
case GrColorType::kRG_F16: *load = SkRasterPipelineOp::load_rgf16;
*isNormalized = false;
break;
case GrColorType::kRGBA_F16: *load = SkRasterPipelineOp::load_f16;
*isNormalized = false;
break;
case GrColorType::kRGBA_F32: *load = SkRasterPipelineOp::load_f32;
*isNormalized = false;
break;
case GrColorType::kAlpha_8xxx: *load = SkRasterPipelineOp::load_8888;
swizzle = skgpu::Swizzle("000r");
break;
case GrColorType::kAlpha_F32xxx: *load = SkRasterPipelineOp::load_f32;
swizzle = skgpu::Swizzle("000r");
break;
case GrColorType::kGray_8xxx: *load = SkRasterPipelineOp::load_8888;
swizzle = skgpu::Swizzle("rrr1");
break;
case GrColorType::kGray_8: *load = SkRasterPipelineOp::load_a8;
swizzle = skgpu::Swizzle("aaa1");
break;
case GrColorType::kR_8xxx: *load = SkRasterPipelineOp::load_8888;
swizzle = skgpu::Swizzle("r001");
break;
case GrColorType::kR_8: *load = SkRasterPipelineOp::load_a8;
swizzle = skgpu::Swizzle("a001");
break;
case GrColorType::kGrayAlpha_88: *load = SkRasterPipelineOp::load_rg88;
swizzle = skgpu::Swizzle("rrrg");
break;
case GrColorType::kBGRA_8888: *load = SkRasterPipelineOp::load_8888;
swizzle = skgpu::Swizzle("bgra");
break;
case GrColorType::kRGB_888x: *load = SkRasterPipelineOp::load_8888;
swizzle = skgpu::Swizzle("rgb1");
break;
// These are color types we don't expect to ever have to load.
case GrColorType::kRGB_888:
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 skgpu::Swizzle get_dst_swizzle_and_store(GrColorType ct, SkRasterPipelineOp* store,
LumMode* lumMode, bool* isNormalized,
bool* isSRGB) {
skgpu::Swizzle swizzle("rgba");
*isNormalized = true;
*isSRGB = false;
*lumMode = LumMode::kNone;
switch (ct) {
case GrColorType::kAlpha_8: *store = SkRasterPipelineOp::store_a8; break;
case GrColorType::kAlpha_16: *store = SkRasterPipelineOp::store_a16; break;
case GrColorType::kBGR_565: *store = SkRasterPipelineOp::store_565; break;
case GrColorType::kRGB_565: swizzle = skgpu::Swizzle("bgr1");
*store = SkRasterPipelineOp::store_565; break;
case GrColorType::kABGR_4444: *store = SkRasterPipelineOp::store_4444; break;
case GrColorType::kARGB_4444: swizzle = skgpu::Swizzle("bgra");
*store = SkRasterPipelineOp::store_4444; break;
case GrColorType::kBGRA_4444: swizzle = skgpu::Swizzle("argb");
*store = SkRasterPipelineOp::store_4444; break;
case GrColorType::kRGBA_8888: *store = SkRasterPipelineOp::store_8888; break;
case GrColorType::kRG_88: *store = SkRasterPipelineOp::store_rg88; break;
case GrColorType::kRGBA_1010102: *store = SkRasterPipelineOp::store_1010102; break;
case GrColorType::kBGRA_1010102: swizzle = skgpu::Swizzle("bgra");
*store = SkRasterPipelineOp::store_1010102;
break;
case GrColorType::kRGBA_10x6: *store = SkRasterPipelineOp::store_10x6; break;
case GrColorType::kRGBA_F16_Clamped: *store = SkRasterPipelineOp::store_f16; break;
case GrColorType::kRG_1616: *store = SkRasterPipelineOp::store_rg1616; break;
case GrColorType::kRGBA_16161616: *store = SkRasterPipelineOp::store_16161616; break;
case GrColorType::kRGBA_8888_SRGB: *store = SkRasterPipelineOp::store_8888;
*isSRGB = true;
break;
case GrColorType::kRG_F16: *store = SkRasterPipelineOp::store_rgf16;
*isNormalized = false;
break;
case GrColorType::kAlpha_F16: *store = SkRasterPipelineOp::store_af16;
*isNormalized = false;
break;
case GrColorType::kRGBA_F16: *store = SkRasterPipelineOp::store_f16;
*isNormalized = false;
break;
case GrColorType::kRGBA_F32: *store = SkRasterPipelineOp::store_f32;
*isNormalized = false;
break;
case GrColorType::kAlpha_8xxx: *store = SkRasterPipelineOp::store_8888;
swizzle = skgpu::Swizzle("a000");
break;
case GrColorType::kAlpha_F32xxx: *store = SkRasterPipelineOp::store_f32;
swizzle = skgpu::Swizzle("a000");
break;
case GrColorType::kBGRA_8888: swizzle = skgpu::Swizzle("bgra");
*store = SkRasterPipelineOp::store_8888;
break;
case GrColorType::kRGB_888x: swizzle = skgpu::Swizzle("rgb1");
*store = SkRasterPipelineOp::store_8888;
break;
case GrColorType::kR_8xxx: swizzle = skgpu::Swizzle("r001");
*store = SkRasterPipelineOp::store_8888;
break;
case GrColorType::kR_8: swizzle = skgpu::Swizzle("agbr");
*store = SkRasterPipelineOp::store_a8;
break;
case GrColorType::kR_16: swizzle = skgpu::Swizzle("agbr");
*store = SkRasterPipelineOp::store_a16;
break;
case GrColorType::kR_F16: swizzle = skgpu::Swizzle("agbr");
*store = SkRasterPipelineOp::store_af16;
break;
case GrColorType::kGray_F16: *lumMode = LumMode::kToAlpha;
*store = SkRasterPipelineOp::store_af16;
break;
case GrColorType::kGray_8: *lumMode = LumMode::kToAlpha;
*store = SkRasterPipelineOp::store_a8;
break;
case GrColorType::kGrayAlpha_88: *lumMode = LumMode::kToRGB;
swizzle = skgpu::Swizzle("ragb");
*store = SkRasterPipelineOp::store_rg88;
break;
case GrColorType::kGray_8xxx: *lumMode = LumMode::kToRGB;
*store = SkRasterPipelineOp::store_8888;
swizzle = skgpu::Swizzle("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;
}
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;
}
SkRasterPipelineOp load;
bool srcIsNormalized;
bool srcIsSRGB;
auto loadSwizzle = get_load_and_src_swizzle(src.colorType(),
&load,
&srcIsNormalized,
&srcIsSRGB);
SkRasterPipelineOp store;
LumMode lumMode;
bool dstIsNormalized;
bool dstIsSRGB;
auto storeSwizzle = get_dst_swizzle_and_store(dst.colorType(),
&store,
&lumMode,
&dstIsNormalized,
&dstIsSRGB);
SkTLazy<SkColorSpaceXformSteps> steps;
skgpu::Swizzle loadStoreSwizzle;
if (alphaOrCSConversion) {
steps.init(src.colorSpace(), src.alphaType(), dst.colorSpace(), dst.alphaType());
} else {
loadStoreSwizzle = skgpu::Swizzle::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 || 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;
SkRasterPipeline_<256> pipeline;
pipeline.append(load, &srcCtx);
if (hasConversion) {
loadSwizzle.apply(&pipeline);
if (srcIsSRGB) {
pipeline.appendTransferFunction(*skcms_sRGB_TransferFunction());
}
if (alphaOrCSConversion) {
steps->apply(&pipeline);
}
switch (lumMode) {
case LumMode::kNone:
break;
case LumMode::kToRGB:
pipeline.append(SkRasterPipelineOp::bt709_luminance_or_luma_to_rgb);
break;
case LumMode::kToAlpha:
pipeline.append(SkRasterPipelineOp::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.appendTransferFunction(*skcms_sRGB_Inverse_TransferFunction());
}
storeSwizzle.apply(&pipeline);
} else {
loadStoreSwizzle.apply(&pipeline);
}
pipeline.append(store, &dstCtx);
auto pipelineFn = pipeline.compile();
for (int i = 0; i < cnt; ++i) {
pipelineFn(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;
SkRasterPipelineOp store;
skgpu::Swizzle 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.appendConstantColor(&alloc, color.data());
switch (lumMode) {
case LumMode::kNone:
break;
case LumMode::kToRGB:
pipeline.append(SkRasterPipelineOp::bt709_luminance_or_luma_to_rgb);
break;
case LumMode::kToAlpha:
pipeline.append(SkRasterPipelineOp::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.appendTransferFunction(*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;
}