blob: 5bc95bae84b92dee56aa25368583867ae21d90b7 [file] [edit]
/*
* Copyright 2026 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/SkColorType.h"
#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/TextureInfo.h"
#include "include/private/base/SkTArray.h"
#include "src/base/SkFloatBits.h"
#include "src/base/SkHalf.h"
#include "src/core/SkColorSpaceXformSteps.h"
#include "src/core/SkRasterPipeline.h"
#include "src/gpu/Swizzle.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/graphite/ContextPriv.h"
#include "src/gpu/graphite/TextureFormat.h"
#include "src/gpu/graphite/TextureFormatXferFn.h"
#include "src/gpu/graphite/TextureInfoPriv.h"
#include "tools/ToolUtils.h"
// Uncomment the SK_ABORT() to exit on first pixel mismatch to help debugging
#define STOP_ON_TRANSFER_FAILURE // SK_ABORT();
namespace skgpu::graphite {
// Types for defining color type and format behavior expectations
enum ChannelDataType {
UNorm, Signed, Float, FNorm, sRGB, XR, Pad
};
struct Channel {
char fName; // rgbayuv01 and G (for gray)
int fBits;
ChannelDataType fType;
};
struct ColorTypeExpectation {
SkColorType fColorType;
Swizzle fReadSwizzle;
std::optional<Swizzle> fWriteSwizzle; // not set implies not renderable
};
struct FormatExpectation {
TextureFormat fFormat;
skia_private::TArray<Channel> fChannels;
SkTextureCompressionType fCompressionType = SkTextureCompressionType::kNone;
// Not set implies transfers are disabled; it is composed with a ColorTypeExpectation's
// read or write swizzle to produce the expected readback/upload swizzle.
std::optional<Swizzle> fXferSwizzle;
// The first color type expectation is assumed to be the best fit.
skia_private::TArray<ColorTypeExpectation> fCompatibleColorTypes;
// All of the expectations for the fixed properties of a TextureFormat are derived from its
// fChannels definition.
bool isFloatingPoint() const { return this->hasType(Float) || this->hasType(FNorm); }
bool hasDepth() const { return this->hasChannel('d'); }
bool hasStencil() const { return this->hasChannel('s'); }
bool hasDepthOrStencil() const { return this->hasDepth() || this->hasStencil(); }
bool isMultiplanar() const {
return this->hasChannel('y') && this->hasChannel('u') && this->hasChannel('v');
}
bool autoClamps() const {
// Auto clamping is derived from the type of the channel with the most bits
bool autoClamp = true;
int maxBitSize = 0;
for (const Channel& c : fChannels) {
if (c.fBits > maxBitSize) {
autoClamp = c.fType == UNorm || c.fType == sRGB;
maxBitSize = c.fBits;
}
}
return autoClamp;
}
int bytesPerBlock() const {
if (fCompressionType != SkTextureCompressionType::kNone) {
// At the moment, all supported compression types have the same bytes per block
return 8;
}
int bitCount = 0;
for (const Channel& c : fChannels) {
bitCount += c.fBits;
}
return bitCount / 8;
}
uint32_t channelMask() const {
uint32_t mask = 0;
if (this->hasChannel('r') || this->hasChannel('y')) {
mask |= kRed_SkColorChannelFlag;
}
if (this->hasChannel('g') || this->hasChannel('u')) {
mask |= kGreen_SkColorChannelFlag;
}
if (this->hasChannel('b') || this->hasChannel('v')) {
mask |= kBlue_SkColorChannelFlag;
}
if (this->hasChannel('a')) {
mask |= kAlpha_SkColorChannelFlag;
}
// Other channels do not contribute to SkColorChannel mask
return mask;
}
private:
bool hasChannel(char channel) const {
for (const Channel& c : fChannels) {
if (c.fName == channel) {
return true;
}
}
return false;
}
bool hasType(ChannelDataType type) const {
for (const Channel& c : fChannels) {
if (c.fType == type) {
return true;
}
}
return false;
}
};
using PixelData = std::array<uint8_t, 16>; // The largest texel/pixel size is RGBA32F = 16 bytes
uint32_t channel_to_bits(const Channel& channel, float value) {
switch (channel.fType) {
case Pad:
// Pad should only be used with 'x', which produces NaN in channel_to_float, so
// replace `value` with the default 'x' bit pattern in gen_channel_values, and then
// fall through to UNorm handling to adjust it to the right bit depth
SkASSERT(channel.fName == 'x');
value = 0b0101 / 15.f;
[[fallthrough]];
case sRGB:
// sRGB data is stored in a non-linear gamma and automatically decodes to linear when
// being sampled or rendered into. This means an SRGB_8888 image with a linear
// SkColorSpace behaves like a regular 8888 image with an sRGB SkColorSpace. But
// transfers between CPU and GPU assume the SkColorSpace is the same, so we need to
// interpret the `v` as if it were an 8888 image with a linear SkColorSpace and map to
// the sRGB encoding.
if (channel.fType == sRGB) {
value = skcms_TransferFunction_eval(skcms_sRGB_Inverse_TransferFunction(), value);
}
[[fallthrough]];
case UNorm:
return (uint32_t) std::round(value * ((1 << channel.fBits) - 1));
case XR:
// See SkRP_opts::store_1010102_xr
SkASSERT(channel.fBits == 10);
return std::min((uint32_t) std::round(value * 510 + 384), 1023u);
case FNorm:
// For simplicity fall through to Float, the Norm is just a hint about range
[[fallthrough]];
case Float:
SkASSERT(channel.fBits == 16 || channel.fBits == 32);
return channel.fBits == 16 ? (uint32_t) SkFloatToHalf(value) : SkFloat2Bits(value);
case Signed:
// This should only be used for 's' channels, which are already not reaching this code
SK_ABORT("Should not be generating Signed values for pixel data");
}
SkUNREACHABLE;
}
float channel_to_float(const Channel& channel, uint32_t bits) {
switch (channel.fType) {
case sRGB: [[fallthrough]]; // first treat as unorm then apply gamma TF
case UNorm: {
float vf = bits * (1 / (float) ((1 << channel.fBits) - 1));
if (channel.fType == sRGB) {
vf = skcms_TransferFunction_eval(skcms_sRGB_TransferFunction(), vf);
}
return vf;
}
case XR: return (bits - 384.f) * (1/510.f);
case FNorm: [[fallthrough]]; // Values are interpreted the same, values are in [0,1]
case Float: return channel.fBits == 16 ? SkHalfToFloat((SkHalf) bits) : SkBits2Float(bits);
case Signed: [[fallthrough]];
case Pad: return SK_FloatNaN; // No floating point printing
}
SkUNREACHABLE;
}
char apply_swizzle_to_name(char dstName, const Swizzle& srcToDst) {
switch (dstName) {
case 'r': return srcToDst[0];
case 'g': return srcToDst[1];
case 'b': return srcToDst[2];
case 'a': return srcToDst[3];
default: return dstName;
}
}
// Generate a unique value for `channelName` by applying `srcToDst` to the source channels.
float gen_channel_value(char name,
const Swizzle& loadSrcSwizzle,
SkSpan<const Channel> srcChannels) {
name = apply_swizzle_to_name(name, loadSrcSwizzle);
// Try to look for an exact channel match, in which case we use the unique value corresponding
// to that channel name.
for (size_t c = 0; c < srcChannels.size(); ++c) {
if (srcChannels[c].fName == name ||
(srcChannels[c].fName == 'G' && (name == 'r' || name == 'g' || name == 'b'))) {
uint32_t v;
switch (srcChannels[c].fName) {
case 'r': v = 0b0001; break;
case 'g': v = 0b0010; break;
case 'b': v = 0b0100; break;
case 'a': v = 0b1010; break; // `A` must additionally fit into 2 bit channel formats
case 'x': v = 0b0101; break;
case '0': v = 0b0000; break;
case '1': v = 0b1111; break;
case 'G': v = 0b1100; break; // arbitrary starting Gray value
default:
// NOTE: 'd', 's', 'y', 'u', 'v' are valid channel names, but the formats that
// have those should not be participating in the read/write transfer testing.
SK_ABORT("Bad source channel name for pixel data generation: %c", name);
}
// Route through the source channel's bit representation to account for rounding
return channel_to_float(srcChannels[c], channel_to_bits(srcChannels[c], v / 15.f));
}
}
// We didn't find an exact channel match, so we either use default values or apply gray handling
switch (name) {
// Trivial default values
case 'x': return SK_FloatNaN;
case 'r': case 'g': case 'b': case '0': return 0.f;
case 'a': case '1': return 1.f;
// Construct a gray value from r, g, and b values of the source
case 'G': {
float r = gen_channel_value('r', loadSrcSwizzle, srcChannels);
float g = gen_channel_value('g', loadSrcSwizzle, srcChannels);
float b = gen_channel_value('b', loadSrcSwizzle, srcChannels);
// Since this is a linear sum, if we divided or multiplied by A after converting
// through source's precision, we would arrive at the same value as if we applied
// the premul/unpremul multiplication in gen_pixel_data applied to G
return 0.2126f * r + 0.7142f * g + 0.0722f * b;
}
default:
SK_ABORT("Bad dst channel name for pixel data generation: %c", name);
}
}
// Src and Dst must be (uint32_t, PixelData) or (PixelData, uint32_t), and `dst` should be
// zero-initialized before calling. Returns the bitOffset for the next channel.
template <typename Src, typename Dst>
int copy_unaligned_bits(int numBits, int bitOffset, const Src& src, Dst& dst) {
static_assert(std::is_same_v<Src, uint32_t> || std::is_same_v<Src, PixelData>);
static_assert(std::is_same_v<Dst, uint32_t> || std::is_same_v<Dst, PixelData>);
static_assert(!std::is_same_v<Src, Dst>);
static constexpr bool kBytesToChannel = std::is_same_v<Src, PixelData>;
int srcBitsLeft = numBits;
while (srcBitsLeft > 0) {
int arrayIndex = bitOffset / 8;
int arrayBitsLeft = (arrayIndex + 1) * 8 - bitOffset;
int copyBits = std::min(arrayBitsLeft, srcBitsLeft);
int channelBitShift = numBits - srcBitsLeft;
int arrayBitShift = 8 - arrayBitsLeft;
if constexpr (kBytesToChannel) {
dst |= ((src[arrayIndex] >> arrayBitShift) & ((1<<copyBits) - 1)) << channelBitShift;
} else {
dst[arrayIndex] |= ((src >> channelBitShift) & ((1<<copyBits) - 1)) << arrayBitShift;
}
srcBitsLeft -= copyBits;
bitOffset += copyBits;
}
return bitOffset;
}
bool needs_premul(SkAlphaType srcAlphaType, SkAlphaType dstAlphaType) {
return dstAlphaType == kPremul_SkAlphaType && (srcAlphaType == kUnpremul_SkAlphaType ||
srcAlphaType == kUnknown_SkAlphaType);
}
bool needs_unpremul(SkAlphaType srcAlphaType, SkAlphaType dstAlphaType) {
return srcAlphaType == kPremul_SkAlphaType && (dstAlphaType == kUnpremul_SkAlphaType ||
dstAlphaType == kUnknown_SkAlphaType);
}
PixelData gen_pixel_data(SkSpan<const Channel> dstChannels,
Swizzle storeDstSwizzle,
SkAlphaType dstAlphaType,
SkSpan<const Channel> srcChannels,
Swizzle loadSrcSwizzle,
SkAlphaType srcAlphaType) {
PixelData pixel{}; // zero-initialize all bytes
int bitOffset = 0;
for (size_t c = 0; c < dstChannels.size(); ++c) {
char name = apply_swizzle_to_name(dstChannels[c].fName, storeDstSwizzle);
float srcValue = gen_channel_value(name, loadSrcSwizzle, srcChannels);
// Handle alpha type conversion
if (name == 'a') {
if (srcAlphaType == kOpaque_SkAlphaType) {
srcValue = 1.f;
}
} else if (name == 'r' || name == 'g' || name == 'b' || name == 'G') {
if (needs_premul(srcAlphaType, dstAlphaType)) {
// Must multiply other channels by the alpha value
const float srcAlpha = gen_channel_value('a', loadSrcSwizzle, srcChannels);
srcValue *= srcAlpha;
} else if (needs_unpremul(srcAlphaType, dstAlphaType)) {
// Must divide the other channels by the alpha value
const float srcAlpha = gen_channel_value('a', loadSrcSwizzle, srcChannels);
srcValue *= 1.f / srcAlpha;
}
// else we're the same alpha type on both sides, or a mix of opaque and unknown, in
// which case premul vs. unpremul is a no-op.
}
uint32_t channelValue = channel_to_bits(dstChannels[c], srcValue);
bitOffset = copy_unaligned_bits(dstChannels[c].fBits, bitOffset, channelValue, pixel);
}
return pixel;
}
// No swizzling or data conversion variant for input data generation
PixelData gen_pixel_data(SkSpan<const Channel> channels, Swizzle readSwizzle, SkAlphaType alphaType) {
return gen_pixel_data(channels, readSwizzle.invert(), alphaType,
channels, readSwizzle, alphaType);
}
const char* channel_type_name(ChannelDataType type) {
switch (type) {
case UNorm: return "u";
case Signed: return "s";
case Float: return "f";
case FNorm: return "fn";
case sRGB: return "srgb";
case XR: return "xr";
case Pad: return "_";
}
SkUNREACHABLE;
}
skia_private::TArray<SkString> print_channel_names(SkSpan<const Channel> channels) {
skia_private::TArray<SkString> names;
names.push_back(SkString()); // empty to align with channel values row label
names.push_back(SkString("raw"));
for (const Channel& c : channels) {
const char* typeName = channel_type_name(c.fType);
names.push_back(SkStringPrintf("%c(%s%d)", c.fName, typeName, c.fBits));
}
return names;
}
skia_private::TArray<SkString> print_channel_values(
const char* prefix, SkSpan<const Channel> channels, const PixelData& pixel) {
skia_private::TArray<SkString> values;
values.push_back(SkString(prefix));
values.push_back(SkString()); // Raw hex value
for (int i = 0; i < 16; ++i) {
values[1].appendf(" %0.2x", pixel[i]);
}
int bitOffset = 0;
for (const Channel& c : channels) {
uint32_t channelValue = 0;
bitOffset = copy_unaligned_bits(c.fBits, bitOffset, pixel, channelValue);
SkString binary;
for (int i = 0; i < c.fBits; ++i) {
binary.prependUnichar((channelValue >> i) & 1 ? '1' : '0');
}
float vf = channel_to_float(c, channelValue);
SkString numeric;
switch (c.fType) {
case Pad: numeric = "-"; break;
case Signed: numeric = SkStringPrintf("%u", channelValue); break;
case sRGB: [[fallthrough]];
case XR:
case UNorm: numeric = SkStringPrintf("0x%x / %.2f", channelValue, vf); break;
case FNorm: [[fallthrough]];
case Float: numeric = SkStringPrintf("%0.4f", vf); break;
}
values.push_back(SkStringPrintf("%s (%s)", binary.c_str(), numeric.c_str()));
}
return values;
}
void dump_string(const SkString& str, int length) {
int strLen = SkTo<int>(str.size());
SkASSERT(strLen <= length);
SkDebugf("%*s", std::min(length, strLen), str.data());
length -= strLen;
while(length > 0) {
SkDebugf(" ");
length--;
}
}
void dump_pixel_comparison(const SkString& inputName,
SkSpan<const Channel> inputChannels,
const PixelData& inputPixel,
const SkString& outputName,
SkSpan<const Channel> outputChannels,
const PixelData& expectedOutputPixel,
const PixelData& actualOutputPixel) {
SkDebugf("Transfer from %s to %s:\n", inputName.c_str(), outputName.c_str());
auto inputChannelNames = print_channel_names(inputChannels);
auto inputChannelValues = print_channel_values("Input", inputChannels, inputPixel);
auto outputChannelNames = print_channel_names(outputChannels);
auto expectedChannelValues =
print_channel_values("Expected", outputChannels, expectedOutputPixel);
auto actualChannelValues =
print_channel_values("Actual", outputChannels, actualOutputPixel);
const int colCount = std::max(inputChannelNames.size(), outputChannelNames.size());
auto rows = {inputChannelNames,
inputChannelValues,
outputChannelNames,
expectedChannelValues,
actualChannelValues};
for (auto&& row : rows) {
for (int c = 0; c < std::min(colCount, row.size()); ++c) {
if (c == 0) {
SkDebugf(" ");
} else {
SkDebugf(" | ");
}
int colWidth = 0;
for (auto&& otherRow : rows) {
if (c < otherRow.size()) {
colWidth = std::max(colWidth, SkTo<int>(otherRow[c].size()));
}
}
dump_string(row[c], colWidth);
}
SkDebugf("\n");
}
}
int channel_tolerance(SkSpan<const Channel> srcChannels, SkAlphaType srcAT,
SkSpan<const Channel> dstChannels, SkAlphaType dstAT) {
bool srcHasSRGBOrGray = false;
for (const Channel c : srcChannels) {
srcHasSRGBOrGray |= c.fType == sRGB || c.fName == 'G';
}
bool dstHasSRGBOrGray = false;
for (const Channel c : dstChannels) {
dstHasSRGBOrGray |= c.fType == sRGB || c.fName == 'G';
}
// If the input or output data involves sRGB encoding/decoding or conversion to gray, or are
// doing premul/unpremul math, we allow 1 bit of difference because of mismatches between how
// SkRasterPipeline calculates channel values vs. the value generation in these tests.
return srcHasSRGBOrGray ||
dstHasSRGBOrGray ||
needs_premul(srcAT, dstAT) ||
needs_unpremul(srcAT, dstAT) ? 1 : 0;
}
bool compare_pixels(SkSpan<const Channel> channels,
const PixelData& expected,
const PixelData& actual,
int channelTolerance) {
int bitOffset = 0;
for (const Channel& c : channels) {
uint32_t actualChannelBits = 0;
uint32_t expectedChannelBits = 0;
copy_unaligned_bits(c.fBits, bitOffset, expected, expectedChannelBits);
copy_unaligned_bits(c.fBits, bitOffset, actual, actualChannelBits);
bitOffset += c.fBits;
int64_t channelDiff = static_cast<int64_t>(actualChannelBits) -
static_cast<int64_t>(expectedChannelBits);
if (c.fType != Pad && std::abs(channelDiff) > channelTolerance) {
return false;
}
}
return true;
}
PixelData transfer_data(const TextureFormatXferFn& xferFn, const PixelData& inputData) {
PixelData outputData{}; // zero-initialize for comparison stability on unwritten values.
xferFn.run(1, 1, &inputData, sizeof(PixelData), &outputData, sizeof(PixelData));
return outputData;
}
// Define the channel layout for every SkColorType for use in generating and validating the
// result of transferring data to or from a texture format.
[[maybe_unused]] static const struct ColorTypeChannels {
SkColorType fColorType;
Swizzle fEffectiveSwizzle; // Derivable from channel mask
skia_private::TArray<Channel> fChannels;
} kColorTypeChannels[] {
{kAlpha_8_SkColorType, Swizzle("000a"), {{'a', 8, UNorm}}},
// NOTE: 565 and 4444 are misnamed and are BGR and ABGR respectively.
{kRGB_565_SkColorType, Swizzle("rgb1"), {{'b', 5, UNorm},
{'g', 6, UNorm},
{'r', 5, UNorm}}},
{kARGB_4444_SkColorType, Swizzle("rgba"), {{'a', 4, UNorm},
{'b', 4, UNorm},
{'g', 4, UNorm},
{'r', 4, UNorm}}},
{kRGBA_8888_SkColorType, Swizzle("rgba"), {{'r', 8, UNorm},
{'g', 8, UNorm},
{'b', 8, UNorm},
{'a', 8, UNorm}}},
{kRGB_888x_SkColorType, Swizzle("rgb1"), {{'r', 8, UNorm},
{'g', 8, UNorm},
{'b', 8, UNorm},
{'x', 8, Pad}}},
{kBGRA_8888_SkColorType, Swizzle("rgba"), {{'b', 8, UNorm},
{'g', 8, UNorm},
{'r', 8, UNorm},
{'a', 8, UNorm}}},
{kRGBA_1010102_SkColorType, Swizzle("rgba"), {{'r', 10, UNorm},
{'g', 10, UNorm},
{'b', 10, UNorm},
{'a', 2, UNorm}}},
{kBGRA_1010102_SkColorType, Swizzle("rgba"), {{'b', 10, UNorm},
{'g', 10, UNorm},
{'r', 10, UNorm},
{'a', 2, UNorm}}},
{kRGB_101010x_SkColorType, Swizzle("rgb1"), {{'r', 10, UNorm},
{'g', 10, UNorm},
{'b', 10, UNorm},
{'x', 2, Pad}}},
{kBGR_101010x_SkColorType, Swizzle("rgb1"), {{'b', 10, UNorm},
{'g', 10, UNorm},
{'r', 10, UNorm},
{'x', 2, Pad}}},
{kBGR_101010x_XR_SkColorType, Swizzle("rgb1"), {{'b', 10, XR},
{'g', 10, XR},
{'r', 10, XR},
{'x', 2, Pad}}},
{kBGRA_10101010_XR_SkColorType, Swizzle("rgba"), {{'x', 6, Pad}, {'b', 10, XR},
{'x', 6, Pad}, {'g', 10, XR},
{'x', 6, Pad}, {'r', 10, XR},
{'x', 6, Pad}, {'a', 10, XR}}},
{kRGBA_10x6_SkColorType, Swizzle("rgba"), {{'x', 6, Pad}, {'r', 10, UNorm},
{'x', 6, Pad}, {'g', 10, UNorm},
{'x', 6, Pad}, {'b', 10, UNorm},
{'x', 6, Pad}, {'a', 10, UNorm}}},
// NOTE: The swizzle is rrr1 since we store gray in the red channel of the texture, but we use
// 'G' as the channel to force generating gray/luminance values in the tests instead of just 'r'
{kGray_8_SkColorType, Swizzle("rrr1"), {{'G', 8, UNorm}}},
{kRGBA_F16Norm_SkColorType, Swizzle("rgba"), {{'r', 16, FNorm},
{'g', 16, FNorm},
{'b', 16, FNorm},
{'a', 16, FNorm}}},
{kRGBA_F16_SkColorType, Swizzle("rgba"), {{'r', 16, Float},
{'g', 16, Float},
{'b', 16, Float},
{'a', 16, Float}}},
{kRGB_F16F16F16x_SkColorType, Swizzle("rgb1"), {{'r', 16, Float},
{'g', 16, Float},
{'b', 16, Float},
{'x', 16, Pad}}},
{kRGBA_F32_SkColorType, Swizzle("rgba"), {{'r', 32, Float},
{'g', 32, Float},
{'b', 32, Float},
{'a', 32, Float}}},
{kR8G8_unorm_SkColorType, Swizzle("rg01"), {{'r', 8, UNorm}, {'g', 8, UNorm}}},
{kA16_float_SkColorType, Swizzle("000a"), {{'a', 16, Float}}},
{kR16_float_SkColorType, Swizzle("r001"), {{'r', 16, Float}}},
{kR16G16_float_SkColorType, Swizzle("rg01"), {{'r', 16, Float}, {'g', 16, Float}}},
{kA16_unorm_SkColorType, Swizzle("000a"), {{'a', 16, UNorm}}},
{kR16_unorm_SkColorType, Swizzle("r001"), {{'r', 16, UNorm}}},
{kR16G16_unorm_SkColorType, Swizzle("rg01"), {{'r', 16, UNorm}, {'g', 16, UNorm}}},
{kR16G16B16A16_unorm_SkColorType, Swizzle("rgba"), {{'r', 16, UNorm},
{'g', 16, UNorm},
{'b', 16, UNorm},
{'a', 16, UNorm}}},
{kSRGBA_8888_SkColorType, Swizzle("rgba"), {{'r', 8, sRGB},
{'g', 8, sRGB},
{'b', 8, sRGB},
{'a', 8, UNorm}}},
{kR8_unorm_SkColorType, Swizzle("r001"), {{'r', 8, UNorm}}},
};
// Must include one per SkColorType except for kUnknown
static_assert(std::size(kColorTypeChannels) == kSkColorTypeCnt - 1,
"Missing channel definition for SkColorType");
// Full definition of compatibility and behavior between SkColorType and TextureFormat:
static const FormatExpectation kExpectations[] {
{.fFormat=TextureFormat::kUnsupported,
.fChannels={},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={}},
{.fFormat=TextureFormat::kR8,
.fChannels={{'r', 8, UNorm}},
.fXferSwizzle=Swizzle("r001"),
.fCompatibleColorTypes={{kR8_unorm_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kAlpha_8_SkColorType, Swizzle("000r"), Swizzle("a000")},
{kGray_8_SkColorType, Swizzle("rrra"), std::nullopt}}},
{.fFormat=TextureFormat::kR16,
.fChannels={{'r', 16, UNorm}},
.fXferSwizzle=Swizzle("r001"),
.fCompatibleColorTypes={{kR16_unorm_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kA16_unorm_SkColorType, Swizzle("000r"), Swizzle("a000")}}},
{.fFormat=TextureFormat::kR16F,
.fChannels={{'r', 16, Float}},
.fXferSwizzle=Swizzle("r001"),
.fCompatibleColorTypes={{kR16_float_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kA16_float_SkColorType, Swizzle("000r"), Swizzle("a000")}}},
{.fFormat=TextureFormat::kR32F,
.fChannels={{'r', 32, Float}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kR16_float_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kA8,
.fChannels={{'a', 8, UNorm}},
.fXferSwizzle=Swizzle("000a"),
.fCompatibleColorTypes={{kAlpha_8_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRG8,
.fChannels={{'r', 8, UNorm}, {'g', 8, UNorm}},
.fXferSwizzle=Swizzle("rg01"),
.fCompatibleColorTypes={{kR8G8_unorm_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRG16,
.fChannels={{'r', 16, UNorm}, {'g', 16, UNorm}},
.fXferSwizzle=Swizzle("rg01"),
.fCompatibleColorTypes={{kR16G16_unorm_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRG16F,
.fChannels={{'r', 16, Float}, {'g', 16, Float}},
.fXferSwizzle=Swizzle("rg01"),
.fCompatibleColorTypes={{kR16G16_float_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRG32F,
.fChannels={{'r', 32, Float}, {'g', 32, Float}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kR16G16_float_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGB8,
.fChannels={{'r', 8, UNorm}, {'g', 8, UNorm}, {'b', 8, UNorm}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kRGB_888x_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kBGR8,
.fChannels={{'b', 8, UNorm}, {'g', 8, UNorm}, {'r', 8, UNorm}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kRGB_888x_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kB5_G6_R5,
.fChannels={{'b', 5, UNorm}, {'g', 6, UNorm}, {'r', 5, UNorm}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kRGB_565_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kR5_G6_B5,
.fChannels={{'r', 5, UNorm}, {'g', 6, UNorm}, {'b', 5, UNorm}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kRGB_565_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGB16,
.fChannels={{'r', 16, UNorm}, {'g', 16, UNorm}, {'b', 16, UNorm}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kR16G16B16A16_unorm_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGB16F,
.fChannels={{'r', 16, Float}, {'g', 16, Float}, {'b', 16, Float}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kRGB_F16F16F16x_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGB32F,
.fChannels={{'r', 32, Float}, {'g', 32, Float}, {'b', 32, Float}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kRGBA_F32_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGB8_sRGB,
.fChannels={{'r', 8, sRGB}, {'g', 8, sRGB}, {'b', 8, sRGB}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kSRGBA_8888_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kBGR10_XR,
.fChannels={{'b', 10, XR}, {'g', 10, XR}, {'r', 10, XR}, {'x', 2, Pad}},
.fXferSwizzle=Swizzle("rgb1"),
.fCompatibleColorTypes={{kBGR_101010x_XR_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGBA8,
.fChannels={{'r', 8, UNorm}, {'g', 8, UNorm}, {'b', 8, UNorm}, {'a', 8, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kRGBA_8888_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kBGRA_8888_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kRGB_888x_SkColorType, Swizzle::RGB1(), std::nullopt}}},
{.fFormat=TextureFormat::kRGBA16,
.fChannels={{'r', 16, UNorm}, {'g', 16, UNorm}, {'b', 16, UNorm}, {'a', 16, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kR16G16B16A16_unorm_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGBA16F,
.fChannels={{'r', 16, Float}, {'g', 16, Float}, {'b', 16, Float}, {'a', 16, Float}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kRGBA_F16_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kRGBA_F16Norm_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kRGB_F16F16F16x_SkColorType, Swizzle::RGB1(), std::nullopt}}},
{.fFormat=TextureFormat::kRGBA32F,
.fChannels={{'r', 32, Float}, {'g', 32, Float}, {'b', 32, Float}, {'a', 32, Float}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kRGBA_F32_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGB10_A2,
.fChannels={{'r', 10, UNorm}, {'g', 10, UNorm}, {'b', 10, UNorm}, {'a', 2, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kRGBA_1010102_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kRGB_101010x_SkColorType, Swizzle::RGB1(), std::nullopt},
{kBGRA_1010102_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kBGR_101010x_SkColorType, Swizzle::RGB1(), std::nullopt}}},
{.fFormat=TextureFormat::kRGBA10x6,
.fChannels={{'x', 6, Pad}, {'r', 10, UNorm}, {'x', 6, Pad}, {'g', 10, UNorm},
{'x', 6, Pad}, {'b', 10, UNorm}, {'x', 6, Pad}, {'a', 10, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kRGBA_10x6_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kRGBA8_sRGB,
.fChannels={{'r', 8, sRGB}, {'g', 8, sRGB}, {'b', 8, sRGB}, {'a', 8, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kSRGBA_8888_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kBGRA8,
.fChannels={{'b', 8, UNorm}, {'g', 8, UNorm}, {'r', 8, UNorm}, {'a', 8, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kBGRA_8888_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kRGBA_8888_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kRGB_888x_SkColorType, Swizzle::RGB1(), std::nullopt}}},
{.fFormat=TextureFormat::kBGR10_A2,
.fChannels={{'b', 10, UNorm}, {'g', 10, UNorm}, {'r', 10, UNorm}, {'a', 2, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kBGRA_1010102_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kBGR_101010x_SkColorType, Swizzle::RGB1(), std::nullopt},
{kRGBA_1010102_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()},
{kRGB_101010x_SkColorType, Swizzle::RGB1(), std::nullopt}}},
{.fFormat=TextureFormat::kBGRA8_sRGB,
.fChannels={{'b', 8, sRGB}, {'g', 8, sRGB}, {'r', 8, sRGB}, {'a', 8, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kSRGBA_8888_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kABGR4,
.fChannels={{'a', 4, UNorm}, {'b', 4, UNorm}, {'g', 4, UNorm}, {'r', 4, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kARGB_4444_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
{.fFormat=TextureFormat::kARGB4,
// TODO(michaelludwig): kARGB_4444 color type is actually BGRA order. Historically, we
// configured kARGB4 format to swizzle the channels on read and write in the shader so that the
// CPU data could be uploaded directly. When we can perform a RB channel swap as part of
// upload/readback, then this can change to RGBA swizzles.
.fChannels={{'a', 4, UNorm}, {'r', 4, UNorm}, {'g', 4, UNorm}, {'b', 4, UNorm}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kARGB_4444_SkColorType, Swizzle::BGRA(), Swizzle::BGRA()}}},
{.fFormat=TextureFormat::kBGRA10x6_XR,
.fChannels={{'x', 6, Pad}, {'b', 10, XR}, {'x', 6, Pad}, {'g', 10, XR},
{'x', 6, Pad}, {'r', 10, XR}, {'x', 6, Pad}, {'a', 10, XR}},
.fXferSwizzle=Swizzle("rgba"),
.fCompatibleColorTypes={{kBGRA_10101010_XR_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}},
// For compressed formats, the bytes per block represents actual compressed block size, not
// just the size of a pixel.
{.fFormat=TextureFormat::kRGB8_ETC2,
.fChannels={{'r', 10, UNorm}, {'g', 8, UNorm}, {'b', 8, UNorm}},
.fCompressionType=SkTextureCompressionType::kETC2_RGB8_UNORM,
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kRGB_888x_SkColorType, Swizzle::RGBA(), std::nullopt}}},
{.fFormat=TextureFormat::kRGB8_ETC2_sRGB,
.fChannels={{'r', 10, sRGB}, {'g', 8, sRGB}, {'b', 8, sRGB}},
.fCompressionType=SkTextureCompressionType::kETC2_RGB8_UNORM,
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kSRGBA_8888_SkColorType, Swizzle::RGBA(), std::nullopt}}},
{.fFormat=TextureFormat::kRGB8_BC1,
.fChannels={{'r', 10, UNorm}, {'g', 8, UNorm}, {'b', 8, UNorm}},
.fCompressionType=SkTextureCompressionType::kBC1_RGB8_UNORM,
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kRGB_888x_SkColorType, Swizzle::RGBA(), std::nullopt}}},
{.fFormat=TextureFormat::kRGBA8_BC1,
.fChannels={{'r', 10, UNorm}, {'g', 8, UNorm}, {'b', 8, UNorm}, {'a', 8, UNorm}},
.fCompressionType=SkTextureCompressionType::kBC1_RGBA8_UNORM,
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kRGBA_8888_SkColorType, Swizzle::RGBA(), std::nullopt},
{kRGB_888x_SkColorType, Swizzle::RGB1(), std::nullopt}}},
{.fFormat=TextureFormat::kRGBA8_BC1_sRGB,
.fChannels={{'r', 10, sRGB}, {'g', 8, sRGB}, {'b', 8, sRGB}, {'a', 8, UNorm}},
.fCompressionType=SkTextureCompressionType::kBC1_RGBA8_UNORM,
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kSRGBA_8888_SkColorType, Swizzle::RGBA(), std::nullopt}}},
// For these multiplanar formats, we set the bytes per block assuming the UV planes are the
// same size as the Y plane, which is an overestimate of the total texture memory.
{.fFormat=TextureFormat::kYUV8_P2_420,
.fChannels={{'y', 8, UNorm}, {'u', 8, UNorm}, {'v', 8, UNorm}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kRGB_888x_SkColorType, Swizzle::RGBA(), std::nullopt}}},
{.fFormat=TextureFormat::kYUV8_P3_420,
.fChannels={{'y', 8, UNorm}, {'u', 8, UNorm}, {'v', 8, UNorm}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kRGB_888x_SkColorType, Swizzle::RGBA(), std::nullopt}}},
{.fFormat=TextureFormat::kYUV10x6_P2_420,
.fChannels={{'y', 10, UNorm}, {'x', 6, Pad}, {'u', 10, UNorm}, {'x', 6, Pad},
{'v', 10, UNorm}, {'x', 6, Pad}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kRGBA_10x6_SkColorType, Swizzle::RGBA(), std::nullopt}}},
{.fFormat=TextureFormat::kExternal,
// We don't really know this, but most Skia behavior defaults to assuming 8-bit color
.fChannels={{'r', 8, UNorm}, {'g', 8, UNorm}, {'b', 8, UNorm}, {'a', 8, UNorm}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={{kRGBA_8888_SkColorType, Swizzle::RGBA(), std::nullopt},
{kRGB_888x_SkColorType, Swizzle::RGB1(), std::nullopt}}},
{.fFormat=TextureFormat::kS8,
.fChannels={{'s', 8, Signed}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={}},
{.fFormat=TextureFormat::kD16,
.fChannels={{'d', 16, UNorm}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={}},
{.fFormat=TextureFormat::kD32F,
.fChannels={{'d', 32, Float}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={}},
{.fFormat=TextureFormat::kD24_S8,
.fChannels={{'d', 24, UNorm}, {'s', 8, Signed}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={}},
{.fFormat=TextureFormat::kD32F_S8,
.fChannels={{'d', 32, Float}, {'s', 8, Signed}},
.fXferSwizzle=std::nullopt,
.fCompatibleColorTypes={}},
};
void test_format_transfers(skiatest::Reporter* r,
const FormatExpectation& textureFormat,
const ColorTypeExpectation& textureCT,
SkAlphaType srcAT,
SkAlphaType dstAT,
bool applyCS) {
// When transferring to CPU->GPU, we want to apply the textureCT's write swizzle, but if that
// is undefined because rendering is disabled, switch to RGB1. This is applicable for the
// RGBx cases and for gray (alongside adjusting the texture channel to produce 'G').
Swizzle writeSwizzle = textureCT.fWriteSwizzle.value_or(Swizzle::RGB1());
skia_private::TArray<Channel> expectedTextureChannels = textureFormat.fChannels;
// Adjust the R8 channel to be 'G' for gray-storing textures so that gen_pixel_data includes
// any conversion to or from luminance.
if (textureCT.fColorType == kGray_8_SkColorType) {
SkASSERT(expectedTextureChannels.size() == 1 && expectedTextureChannels[0].fName == 'r');
expectedTextureChannels[0].fName = 'G';
}
// This colorspace is constructed to have a linear gamma curve and an identity gamut transform
// so that any operations that it applies are just multiplies by 0 or 1. Anything else adds
// minor bit variations to processed data, making checking expectations harder.
sk_sp<SkColorSpace> srcCS = SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear,
SkNamedGamut::kXYZ);
sk_sp<SkColorSpace> dstCS = applyCS ? srcCS->makeColorSpin() : srcCS;
SkColorSpaceXformSteps csSteps{srcCS.get(), srcAT, dstCS.get(), dstAT};
SkString gpuLabel = SkStringPrintf("GPU format %s as %s",
TextureFormatName(textureFormat.fFormat),
ToolUtils::colortype_name(textureCT.fColorType));
// Transfering from srcCT into a GPU textureFormat interpreted as textureCT
for (const ColorTypeChannels& src : kColorTypeChannels) {
std::optional<TextureFormatXferFn> xferFn =
TextureFormatXferFn::MakeCpuToGpu(src.fColorType,
csSteps,
textureFormat.fFormat,
textureCT.fReadSwizzle);
REPORTER_ASSERT(r, textureFormat.fXferSwizzle.has_value() == xferFn.has_value());
if (textureFormat.fXferSwizzle.has_value() && xferFn.has_value()) {
PixelData cpuPixel = gen_pixel_data(src.fChannels, Swizzle::RGBA(), srcAT);
// The expected GPU value is formed by applying the source colortype's effective
// swizzle (i.e. fill in missing channels), and the format's compatible colortype's
// write swizzle to the channel definition of format.
Swizzle loadSrc = src.fEffectiveSwizzle;
if (applyCS) {
// The colorspace conversion is constructed to be a spin via gamut transform matrix,
// so the end effect should match a rotation swizzle on R,G,B (ignoring alpha).
loadSrc = Swizzle::Concat(loadSrc, Swizzle("gbra"));
}
PixelData expectedGpuPixel = gen_pixel_data(expectedTextureChannels,
writeSwizzle,
dstAT,
src.fChannels,
loadSrc,
srcAT);
PixelData actualGpuPixel = transfer_data(*xferFn, cpuPixel);
const int tol = channel_tolerance(src.fChannels, srcAT,
expectedTextureChannels, dstAT);
if (!compare_pixels(textureFormat.fChannels, expectedGpuPixel, actualGpuPixel, tol)) {
SkString ctLabel = SkStringPrintf("CPU colortype %s",
ToolUtils::colortype_name(src.fColorType));
dump_pixel_comparison(ctLabel,
src.fChannels,
cpuPixel,
gpuLabel,
textureFormat.fChannels,
expectedGpuPixel,
actualGpuPixel);
REPORTER_ASSERT(r, false, "Pixel mismatch uploading from %s, alpha %s -> %s%s",
ctLabel.c_str(),
ToolUtils::alphatype_name(srcAT),
ToolUtils::alphatype_name(dstAT),
applyCS ? " with colorspace conversion" : "");
STOP_ON_TRANSFER_FAILURE
}
}
}
// Transfering from a GPU textureFormat interpreted as textureCT into dstCT
for (const ColorTypeChannels& dst : kColorTypeChannels) {
std::optional<TextureFormatXferFn> xferFn =
TextureFormatXferFn::MakeGpuToCpu(textureFormat.fFormat,
textureCT.fReadSwizzle,
csSteps,
dst.fColorType);
REPORTER_ASSERT(r, textureFormat.fXferSwizzle.has_value() == xferFn.has_value());
if (textureFormat.fXferSwizzle.has_value() && xferFn.has_value()) {
PixelData gpuPixel = gen_pixel_data(expectedTextureChannels,
textureCT.fReadSwizzle,
srcAT);
// The expected CPU value is formed by applying the TextureFormat's implicit transfer
// swizzle (i.e. fill in missing channels), its compatible colortype's read swizzle
// to the channel definition of the dst color type.
Swizzle loadSrc = Swizzle::Concat(*textureFormat.fXferSwizzle, textureCT.fReadSwizzle);
if (applyCS) {
loadSrc = Swizzle::Concat(loadSrc, Swizzle("gbra")); // See above
}
PixelData expectedCpuPixel = gen_pixel_data(dst.fChannels,
Swizzle::RGBA(),
dstAT,
expectedTextureChannels,
loadSrc,
srcAT);
PixelData actualCpuPixel = transfer_data(*xferFn, gpuPixel);
const int tol = channel_tolerance(expectedTextureChannels, srcAT,
dst.fChannels, dstAT);
if (!compare_pixels(dst.fChannels, expectedCpuPixel, actualCpuPixel, tol)) {
SkString ctLabel = SkStringPrintf("CPU colortype %s",
ToolUtils::colortype_name(dst.fColorType));
dump_pixel_comparison(gpuLabel,
textureFormat.fChannels,
gpuPixel,
ctLabel,
dst.fChannels,
expectedCpuPixel,
actualCpuPixel);
REPORTER_ASSERT(r, false, "Pixel mismatch reading back to %s, alpha %s -> %s%s",
ctLabel.c_str(),
ToolUtils::alphatype_name(srcAT),
ToolUtils::alphatype_name(dstAT),
applyCS ? " with colorspace conversion" : "");
STOP_ON_TRANSFER_FAILURE
}
}
}
}
void run_texture_format_test(skiatest::Reporter* r, const Caps* caps, TextureFormat format) {
bool foundExpectation = false;
for (auto&& e : kExpectations) {
if (e.fFormat != format) {
continue;
}
// Should only find it once
REPORTER_ASSERT(r, !foundExpectation, "Format expectation listed multiple times");
foundExpectation = true;
skiatest::ReporterContext scope(r, SkStringPrintf("Format %s", TextureFormatName(format)));
// Found the expectation for the requested format. Check fixed properties first.
REPORTER_ASSERT(r, e.fCompressionType == TextureFormatCompressionType(format));
REPORTER_ASSERT(r, e.bytesPerBlock() == TextureFormatBytesPerBlock(format));
REPORTER_ASSERT(r, e.channelMask() == TextureFormatChannelMask(format));
REPORTER_ASSERT(r, e.hasDepthOrStencil() == TextureFormatIsDepthOrStencil(format));
REPORTER_ASSERT(r, e.hasDepth() == TextureFormatHasDepth(format));
REPORTER_ASSERT(r, e.hasStencil() == TextureFormatHasStencil(format));
REPORTER_ASSERT(r, e.isMultiplanar() == TextureFormatIsMultiplanar(format));
REPORTER_ASSERT(r, e.autoClamps() == TextureFormatAutoClamps(format));
REPORTER_ASSERT(r, e.isFloatingPoint() == TextureFormatIsFloatingPoint(format));
// Verify compatible color types
auto [baseColorType, _] = TextureFormatColorTypeInfo(format);
if (baseColorType == kUnknown_SkColorType) {
REPORTER_ASSERT(r, e.fCompatibleColorTypes.empty());
} else {
// Should be the first listed compatible color type
REPORTER_ASSERT(r, !e.fCompatibleColorTypes.empty());
REPORTER_ASSERT(r, e.fCompatibleColorTypes[0].fColorType == baseColorType);
}
for (int c = 0; c <= kLastEnum_SkColorType; ++c) {
SkColorType ct = static_cast<SkColorType>(c);
skiatest::ReporterContext ctScope{
r, SkStringPrintf("color type %s\n", ToolUtils::colortype_name(ct))};
bool foundColorExpectation = false;
for (auto&& ec : e.fCompatibleColorTypes) {
if (ec.fColorType == ct) {
// Expected to be compatible (and should only find it once)
REPORTER_ASSERT(r, !foundColorExpectation,
"Color type listed multiple times: %s",
ToolUtils::colortype_name(ec.fColorType));
foundColorExpectation = true;
// Check swizzles and transfers here, the rest of the color type checks happen
// outside the loop based on `foundColorExpectation`.
Swizzle actualReadSwizzle = ReadSwizzleForColorType(ct, format);
REPORTER_ASSERT(r, ec.fReadSwizzle == actualReadSwizzle,
"actual %s vs. expected %s",
actualReadSwizzle.asString().c_str(),
ec.fReadSwizzle.asString().c_str());
auto actualWriteSwizzle = WriteSwizzleForColorType(ct, format);
if (ec.fWriteSwizzle.has_value()) {
REPORTER_ASSERT(r, actualWriteSwizzle.has_value());
REPORTER_ASSERT(r, ec.fWriteSwizzle == actualWriteSwizzle,
"actual %s vs. expected %s",
actualWriteSwizzle ? actualWriteSwizzle->asString().c_str()
: "null",
ec.fWriteSwizzle->asString().c_str());
} else {
REPORTER_ASSERT(r, !actualWriteSwizzle.has_value());
// This is a proxy for "the format can represent CT, and there are some
// formats that can render CT, but this format does not render w/ CT".
TextureInfo renderableInfo = caps->getDefaultSampledTextureInfo(
ct, Mipmapped::kNo, Protected::kNo, Renderable::kYes);
REPORTER_ASSERT(r, format != TextureInfoPriv::ViewFormat(renderableInfo));
}
// Test all combinations of alpha type x 2 (texture vs cpu) and whether or not
// colorspace conversions are handled in the transfer.
static constexpr int kAlphaTypeCount = (int) kLastEnum_SkAlphaType + 1;
for (bool applyCS : {false, true}) {
for (int srcAT = 0; srcAT < kAlphaTypeCount; ++srcAT) {
for (int dstAT = 0; dstAT < kAlphaTypeCount; ++dstAT) {
test_format_transfers(r, e, ec,
static_cast<SkAlphaType>(srcAT),
static_cast<SkAlphaType>(dstAT),
applyCS);
}
}
}
}
}
// If we found an expectation, it should be detected as compatible (and false otherwise)
const bool actualCompatible = AreColorTypeAndFormatCompatible(ct, format);
REPORTER_ASSERT(r, foundColorExpectation == actualCompatible,
"actual (%d) vs expected (%d)",
actualCompatible, foundColorExpectation);
}
}
// All formats should have expectations
REPORTER_ASSERT(r, foundExpectation, "Missing expectation for %s", TextureFormatName(format));
}
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(TextureFormatTest, r, ctx, CtsEnforcement::kNextRelease) {
for (int i = 0; i < kTextureFormatCount; ++i) {
run_texture_format_test(r, ctx->priv().caps(), static_cast<TextureFormat>(i));
}
}
} // namespace skgpu::graphite