| /* | |
| * 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/SkTArray.h" | |
| #include "src/core/SkColorSpaceXformSteps.h" | |
| #include "src/core/SkFloatBits.h" | |
| #include "src/core/SkHalf.h" | |
| #include "src/core/SkImageInfoPriv.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: { | |
| uint32_t denominator = channel.fBits == 32 ? ~0 : ((1 << channel.fBits) - 1); | |
| return (uint32_t) std::round(value * denominator); | |
| } | |
| 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: { | |
| uint32_t denominator = channel.fBits == 32 ? ~0 : ((1 << channel.fBits) - 1); | |
| float vf = bits * (1 / (float) denominator); | |
| 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); | |
| } | |
| // Returns the number of set *bits* within PixelData. | |
| std::pair<PixelData, size_t> gen_pixel_data(SkSpan<const Channel> dstChannels, | |
| Swizzle storeDstSwizzle, | |
| SkAlphaType dstAlphaType, | |
| SkSpan<const Channel> srcChannels, | |
| Swizzle loadSrcSwizzle, | |
| SkAlphaType srcAlphaType, | |
| bool forInput=false) { | |
| PixelData pixel{}; // zero-initialize all bytes | |
| int bitOffset = 0; | |
| for (size_t c = 0; c < dstChannels.size(); ++c) { | |
| Channel channel = dstChannels[c]; | |
| channel.fName = apply_swizzle_to_name(dstChannels[c].fName, storeDstSwizzle); | |
| float srcValue = gen_channel_value(channel.fName, loadSrcSwizzle, srcChannels); | |
| // Handle alpha type conversion | |
| if (forInput && ((channel.fName == 'a' && srcAlphaType == kUnknown_SkAlphaType) || | |
| (channel.fName == '1' && dstChannels[c].fName == 'a'))) { | |
| // Fill junk alpha that is masked by a later read swizzle as if it were padding | |
| channel.fType = Pad; | |
| channel.fName = 'x'; | |
| } else if (channel.fName == 'a' && srcAlphaType == kOpaque_SkAlphaType) { | |
| srcValue = 1.f; | |
| // NOTE: We don't force alpha = 1 when dstAlphaType = opaque, since that is interpreted | |
| // as a trust-the-user scenario and we assume input values are already opaque and don't | |
| // modify the alpha. | |
| } else if (channel.fName == 'r' || channel.fName == 'g' || channel.fName == 'b' || | |
| channel.fName == '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(channel, srcValue); | |
| bitOffset = copy_unaligned_bits(channel.fBits, bitOffset, channelValue, pixel); | |
| } | |
| return {pixel, bitOffset}; | |
| } | |
| // No swizzling or data conversion variant for input data generation | |
| std::pair<PixelData, size_t> gen_pixel_data(SkSpan<const Channel> channels, | |
| Swizzle readSwizzle, | |
| SkAlphaType alphaType) { | |
| return gen_pixel_data(channels, readSwizzle.invert(), alphaType, | |
| channels, readSwizzle, alphaType, /*forInput=*/true); | |
| } | |
| 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, | |
| std::optional<char> ignoreChannel=std::nullopt) { | |
| 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 && // ignore padding channels | |
| !(ignoreChannel.has_value() && c.fName == *ignoreChannel) && // ignore alpha channels | |
| std::abs(channelDiff) > channelTolerance) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| PixelData transfer_data(const TextureFormatXferFn& xferFn, | |
| const PixelData& inputData, | |
| size_t srcPixelBits, | |
| size_t dstPixelBits) { | |
| // First do a conversion on a 1x1 image, which will represent the expected output when | |
| // testing various width/height/rowbyte combinations for a solid input. | |
| PixelData outputData{}; // zero-initialize for comparison stability on unwritten values. | |
| xferFn.run(1, 1, &inputData, sizeof(PixelData), &outputData, sizeof(PixelData)); | |
| SkASSERT(srcPixelBits % sizeof(std::uint8_t) == 0); | |
| SkASSERT(dstPixelBits % sizeof(std::uint8_t) == 0); | |
| const size_t srcPixelBytes = srcPixelBits / 8; | |
| const size_t dstPixelBytes = dstPixelBits / 8; | |
| static constexpr int kMaxDim = 32; // > 128-bit SIMD processing for 8-bit formats | |
| using ImageBytes = std::array<uint8_t, kMaxDim*kMaxDim*sizeof(PixelData)>; | |
| std::unique_ptr<ImageBytes> src = std::make_unique<ImageBytes>(); | |
| std::unique_ptr<ImageBytes> dst = std::make_unique<ImageBytes>(); | |
| auto fill_image = [&](int w, int h, size_t rowBytes) { | |
| uint8_t* dataStart = src->data(); | |
| for (int x = 0; x < w; ++x) { | |
| memcpy(src->data() + x*srcPixelBytes, &inputData, srcPixelBytes); | |
| } | |
| for (int y = 1; y < h; ++y) { | |
| memcpy(dataStart + rowBytes, dataStart, rowBytes); | |
| dataStart += rowBytes; | |
| } | |
| }; | |
| auto check_image = [&](int w, int h, size_t rowBytes) { | |
| const uint8_t* dataStart = dst->data(); | |
| PixelData pxData{}; // zero-initialize all bytes | |
| for (int y = 0; y < h; ++y) { | |
| for (int x = 0; x < w; ++x) { | |
| memcpy(&pxData, dataStart + x*dstPixelBytes, dstPixelBytes); | |
| if (pxData != outputData) { | |
| SkDebugf("Pixel mismatch at (%d, %d) in bulk transfer.\n", x, y); | |
| outputData = pxData; // propagate this error out for pixel dumping | |
| return false; | |
| } | |
| } | |
| dataStart += rowBytes; | |
| } | |
| return true; | |
| }; | |
| // RasterPipeline isn't the fastest in debug builds and transfer_data is called ~70k times | |
| // in this unit test, so only test a specific width and height. | |
| static constexpr int kWidth = 17; // > 16 to test padding logic for 128-bit SIMD processing | |
| static constexpr int kHeight = 7; // > 1 for multirow testing, but low to help performance | |
| for (bool inputDense : {true, false}) { | |
| size_t srcRowBytes = (inputDense ? kWidth : kMaxDim) * srcPixelBytes; | |
| fill_image(kWidth, kHeight, srcRowBytes); | |
| for (bool outputDense : {true, false}) { | |
| size_t dstRowBytes = (outputDense ? kWidth : kMaxDim) * dstPixelBytes; | |
| xferFn.run(kWidth, kHeight, src->data(), srcRowBytes, dst->data(), dstRowBytes); | |
| if (!check_image(kWidth, kHeight, dstRowBytes)) { | |
| return outputData; | |
| } | |
| } | |
| } | |
| 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, | |
| .fChannels={{'a', 4, UNorm}, {'r', 4, UNorm}, {'g', 4, UNorm}, {'b', 4, UNorm}}, | |
| .fXferSwizzle=Swizzle("rgba"), | |
| .fCompatibleColorTypes={{kARGB_4444_SkColorType, Swizzle::RGBA(), Swizzle::RGBA()}}}, | |
| {.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::kYUV8_P2_422, | |
| .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_422, | |
| .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_422, | |
| .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::kYUV8_P2_444, | |
| .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_444, | |
| .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_444, | |
| .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={}}, | |
| }; | |
| // Match convention used with WrapTexture, where unknown alpha is forced to opaque via swizzle | |
| Swizzle adjust_swizzle_for_alphatype(Swizzle readSwizzle, SkAlphaType at) { | |
| if (at == kUnknown_SkAlphaType) { | |
| return Swizzle::Concat(readSwizzle, Swizzle::RGB1()); | |
| } else { | |
| return readSwizzle; | |
| } | |
| } | |
| bool raster_pipeline_allowed_in_transfer(SkSpan<const Channel> src, | |
| SkSpan<const Channel> dst, | |
| const SkColorSpaceXformSteps& cs) { | |
| // Colorspace conversions have to happen in raster pipeline | |
| if (SkToBool(cs)) { | |
| return true; | |
| } | |
| if (src.size() != dst.size()) { | |
| // Converting between RGB and RGBA shouldn't require raster pipeline, | |
| // but larger channel swizzlings such as RGBA to A do. | |
| if ((src.size() != 3 && src.size() != 4) || | |
| (dst.size() != 3 && dst.size() != 4)) { | |
| return true; | |
| } | |
| } | |
| auto summarize = [](SkSpan<const Channel> channels) { | |
| int maxChannelBitDepth = 0; | |
| ChannelDataType primaryType = UNorm; | |
| bool holdsLuminance = false; | |
| for (const Channel& c : channels) { | |
| holdsLuminance = c.fName == 'G'; | |
| if (c.fType != Pad) { | |
| maxChannelBitDepth = std::max(maxChannelBitDepth, c.fBits); | |
| primaryType = std::max(primaryType, c.fType); | |
| } | |
| } | |
| return std::make_tuple(maxChannelBitDepth, primaryType, holdsLuminance); | |
| }; | |
| auto [srcBits, srcType, srcLum] = summarize(src); | |
| auto [dstBits, dstType, dstLum] = summarize(dst); | |
| // Data bit representation has to go through raster pipeline | |
| if (srcBits != dstBits || srcType != dstType) { | |
| return true; | |
| } | |
| // Computing a luminance value has to go through raster pipeline | |
| if (dstLum && !srcLum && (src.size() != 1 || src[0].fName != 'a')) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| void validate_optimal_xfer_fn(skiatest::Reporter* r, | |
| const TextureFormatXferFn& xferFn, | |
| const SkColorSpaceXformSteps& cs, | |
| std::optional<char> ignoreChannel, | |
| const SkString& srcName, | |
| SkSpan<const Channel> srcChannels, | |
| const PixelData& srcData, | |
| const SkString& dstName, | |
| SkSpan<const Channel> dstChannels, | |
| const PixelData& dstData) { | |
| // If the transferred data remains unmodified, confirm that the transfer function detected that | |
| // it could have been an identity transfer. We skip that due to red herrings when there's a | |
| // colorspace (which would force raster pipeline always, but because of our RGB->BGR synthetic | |
| // space it can sometimes leave computed values unmodified). We skip when there's a channel | |
| // change (which can sometimes appear as an identity if certain channels remain 0). We skip the | |
| // case when it's an ignore-src + force-opaque, because TextureFormatXferfn will choose that | |
| // for alpha-only color types with unknown alpha but when the source data is alpha-only + opaque | |
| // it looks equivalent. | |
| const bool identityRedHerring = SkToBool(cs) || | |
| srcChannels.size() != dstChannels.size() || | |
| xferFn.isIgnoreSrcForceOpaque(); | |
| if (!identityRedHerring && memcmp(&srcData, &dstData, sizeof(PixelData)) == 0) { | |
| REPORTER_ASSERT(r, xferFn.isIdentity(), "Expected identity xfer fn between %s -> %s", | |
| srcName.c_str(), dstName.c_str()); | |
| } | |
| // The flip-side, if `xferFn` claims its the identity, then the data should match as well. | |
| if (xferFn.isIdentity()) { | |
| if (!compare_pixels(dstChannels, srcData, dstData, /*channelTolerance=*/0, ignoreChannel)) { | |
| dump_pixel_comparison(srcName, srcChannels, srcData, | |
| dstName, dstChannels, srcData, dstData); | |
| REPORTER_ASSERT(r, false, "Xfer fn between %s -> %s claimed identity but was not", | |
| srcName.c_str(), dstName.c_str()); | |
| STOP_ON_TRANSFER_FAILURE | |
| } | |
| } | |
| // Otherwise, check that the transfer function is only relying on the raster pipeline when | |
| // necessary. And if it is using raster pipeline, then it's not mixing the SIMD xfer ops. | |
| if (xferFn.usesRasterPipeline()) { | |
| REPORTER_ASSERT(r, raster_pipeline_allowed_in_transfer(srcChannels, dstChannels, cs), | |
| "Transfer between %s -> %s unexpected uses raster pipeline", | |
| srcName.c_str(), dstName.c_str()); | |
| if (xferFn.usesXferOps()) { | |
| // The only time it's okay to mix xfer ops and RP is for handling kDrop/kPadAlpha since | |
| // the format is not representable with raster pipeline. | |
| REPORTER_ASSERT(r, xferFn.isDropOrPadAlpha(), | |
| "Transfer between %s -> %s mixes transfer methods", | |
| srcName.c_str(), dstName.c_str()); | |
| } | |
| } | |
| } | |
| 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, just use RGBA. | |
| Swizzle writeSwizzle = textureCT.fWriteSwizzle.value_or(Swizzle::RGBA()); | |
| // Adjust the R8 channel to be 'G' for gray-storing textures so that gen_pixel_data includes | |
| // any conversion to or from luminance. | |
| const bool isRedOnly = textureFormat.fChannels.size() == 1 && | |
| textureFormat.fChannels[0].fName == 'r'; | |
| skia_private::TArray<Channel> expectedTextureChannels = textureFormat.fChannels; | |
| if (textureCT.fColorType == kGray_8_SkColorType) { | |
| SkASSERT(isRedOnly); | |
| 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) { | |
| // Adjust srcAT as if they had created a valid SkBitmap, which may mean skipping. Since we | |
| // test every srcAT possibility, if the srcAT changes, we skip to avoid running extra work. | |
| SkAlphaType validSrcAT; | |
| if (!SkColorTypeValidateAlphaType(src.fColorType, srcAT, &validSrcAT) || | |
| validSrcAT != srcAT) { | |
| continue; | |
| } | |
| // Adjust the read swizzle to account for unknown alpha type semantics | |
| Swizzle readSwizzle = adjust_swizzle_for_alphatype(textureCT.fReadSwizzle, dstAT); | |
| std::optional<TextureFormatXferFn> xferFn = | |
| TextureFormatXferFn::MakeCpuToGpu(src.fColorType, | |
| csSteps, | |
| textureFormat.fFormat, | |
| readSwizzle); | |
| REPORTER_ASSERT(r, textureFormat.fXferSwizzle.has_value() == xferFn.has_value()); | |
| if (textureFormat.fXferSwizzle.has_value() && xferFn.has_value()) { | |
| SkString ctLabel = | |
| SkStringPrintf("CPU colortype %s", ToolUtils::colortype_name(src.fColorType)); | |
| auto [cpuPixel, cpuPixelBits] = 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")); | |
| } | |
| auto [expectedGpuPixel, gpuPixelBits] = gen_pixel_data(expectedTextureChannels, | |
| writeSwizzle, | |
| dstAT, | |
| src.fChannels, | |
| loadSrc, | |
| validSrcAT); | |
| PixelData actualGpuPixel = transfer_data(*xferFn, cpuPixel, cpuPixelBits, gpuPixelBits); | |
| // The data transfer can skip alpha handling if we know that all samples/reads are going | |
| // to override it with 1.0 anyways. Testing the final readSwizzle includes both color | |
| // type semantics that induce the swizzle and requesting unknown alpha type. | |
| std::optional<char> ignoreChannel; | |
| if (readSwizzle[3] == '1') { | |
| // If the format stores alpha in r, we need to ignore the r value. | |
| ignoreChannel = isRedOnly ? 'r' : 'a'; | |
| } | |
| // Confirm the transfer is as optimized as possible | |
| validate_optimal_xfer_fn(r, *xferFn, csSteps, ignoreChannel, | |
| ctLabel, src.fChannels, cpuPixel, | |
| gpuLabel, expectedTextureChannels, expectedGpuPixel); | |
| const int tol = channel_tolerance(src.fChannels, validSrcAT, | |
| expectedTextureChannels, dstAT); | |
| if (!compare_pixels(expectedTextureChannels, expectedGpuPixel, actualGpuPixel, tol, | |
| ignoreChannel)) { | |
| 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(validSrcAT), | |
| 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) { | |
| // Adjust dstAT as if they had created a valid SkBitmap, which may mean skipping. Since we | |
| // test every dstAT possibility, if the dstAT changes, we skip to avoid running extra work. | |
| SkAlphaType validDstAT; | |
| if (!SkColorTypeValidateAlphaType(dst.fColorType, dstAT, &validDstAT) || | |
| validDstAT != dstAT) { | |
| continue; | |
| } | |
| // Adjust the read swizzle to account for unknown alpha type semantics | |
| Swizzle readSwizzle = adjust_swizzle_for_alphatype(textureCT.fReadSwizzle, srcAT); | |
| std::optional<TextureFormatXferFn> xferFn = | |
| TextureFormatXferFn::MakeGpuToCpu(textureFormat.fFormat, | |
| readSwizzle, | |
| csSteps, | |
| dst.fColorType); | |
| REPORTER_ASSERT(r, textureFormat.fXferSwizzle.has_value() == xferFn.has_value()); | |
| if (textureFormat.fXferSwizzle.has_value() && xferFn.has_value()) { | |
| SkString ctLabel = | |
| SkStringPrintf("CPU colortype %s", ToolUtils::colortype_name(dst.fColorType)); | |
| auto [gpuPixel, gpuPixelBits] = 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, readSwizzle); | |
| if (applyCS) { | |
| loadSrc = Swizzle::Concat(loadSrc, Swizzle("gbra")); // See above | |
| } | |
| auto [expectedCpuPixel, cpuPixelBits] = gen_pixel_data(dst.fChannels, | |
| Swizzle::RGBA(), | |
| dstAT, | |
| expectedTextureChannels, | |
| loadSrc, | |
| srcAT); | |
| // Confirm the transfer is as optimized as possible | |
| validate_optimal_xfer_fn(r, *xferFn, csSteps, /*ignoreChannel=*/std::nullopt, | |
| gpuLabel, expectedTextureChannels, gpuPixel, | |
| ctLabel, dst.fChannels, expectedCpuPixel); | |
| PixelData actualCpuPixel = transfer_data(*xferFn, gpuPixel, gpuPixelBits, cpuPixelBits); | |
| const int tol = channel_tolerance(expectedTextureChannels, srcAT, | |
| dst.fChannels, dstAT); | |
| if (!compare_pixels(dst.fChannels, expectedCpuPixel, actualCpuPixel, tol)) { | |
| 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 sampled and readable copy info | |
| TextureInfo sampledInfo = caps->getDefaultSampledTextureInfo( | |
| ct, Mipmapped::kNo, Protected::kNo, Renderable::kNo); | |
| if (sampledInfo.isValid() && | |
| TextureInfoPriv::ViewFormat(sampledInfo) == format) { | |
| REPORTER_ASSERT(r, caps->isTexturable(sampledInfo)); | |
| REPORTER_ASSERT(r, caps->isReadable(sampledInfo)); | |
| TextureInfo copyInfo = caps->getTextureInfoForSampledCopy( | |
| sampledInfo, Mipmapped::kNo); | |
| REPORTER_ASSERT(r, copyInfo.isValid()); | |
| } | |
| TextureInfo readableInfo = caps->getDefaultReadableTextureInfo( | |
| format, Protected::kNo); | |
| if (readableInfo.isValid() && | |
| TextureInfoPriv::ViewFormat(readableInfo) == format) { | |
| REPORTER_ASSERT(r, caps->isReadable(readableInfo)); | |
| TextureInfo copyInfo = caps->getTextureInfoForReadableCopy(readableInfo); | |
| REPORTER_ASSERT(r, copyInfo.isValid()); | |
| } | |
| TextureInfo storageInfo = caps->getDefaultReadableStorageTextureInfo( | |
| format, Protected::kNo); | |
| if (storageInfo.isValid() && | |
| TextureInfoPriv::ViewFormat(storageInfo) == format) { | |
| REPORTER_ASSERT(r, caps->isStorage(storageInfo)); | |
| REPORTER_ASSERT(r, caps->isReadable(storageInfo)); | |
| } | |
| // 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::kApiLevel_202604) { | |
| for (int i = 0; i < kTextureFormatCount; ++i) { | |
| run_texture_format_test(r, ctx->priv().caps(), static_cast<TextureFormat>(i)); | |
| } | |
| } | |
| } // namespace skgpu::graphite |