| /* |
| * Copyright 2025 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/graphite/TextureFormat.h" |
| |
| #include "include/core/SkColor.h" |
| #include "include/core/SkColorType.h" |
| #include "src/core/SkImageInfoPriv.h" |
| |
| namespace skgpu::graphite { |
| |
| const char* TextureFormatName(TextureFormat format) { |
| switch (format) { |
| case TextureFormat::kUnsupported: return "Unsupported"; |
| case TextureFormat::kR8: return "R8"; |
| case TextureFormat::kR16: return "R16"; |
| case TextureFormat::kR16F: return "R16F"; |
| case TextureFormat::kR32F: return "R32F"; |
| case TextureFormat::kA8: return "A8"; |
| case TextureFormat::kRG8: return "RG8"; |
| case TextureFormat::kRG16: return "RG16"; |
| case TextureFormat::kRG16F: return "RG16F"; |
| case TextureFormat::kRG32F: return "RG32F"; |
| case TextureFormat::kRGB8: return "RGB8"; |
| case TextureFormat::kBGR8: return "BGR8"; |
| case TextureFormat::kB5_G6_R5: return "B5_G6_R5"; |
| case TextureFormat::kR5_G6_B5: return "R5_G6_B5"; |
| case TextureFormat::kRGB16: return "RGB16"; |
| case TextureFormat::kRGB16F: return "RGB16F"; |
| case TextureFormat::kRGB32F: return "RGB32F"; |
| case TextureFormat::kRGB8_sRGB: return "RGB8_sRGB"; |
| case TextureFormat::kBGR10_XR: return "BGR10_XR"; |
| case TextureFormat::kRGBA8: return "RGBA8"; |
| case TextureFormat::kRGBA16: return "RBGA16"; |
| case TextureFormat::kRGBA16F: return "RGBA16F"; |
| case TextureFormat::kRGBA32F: return "RGBA32F"; |
| case TextureFormat::kRGB10_A2: return "RGB10_A2"; |
| case TextureFormat::kRGBA10x6: return "RGBA10x6"; |
| case TextureFormat::kRGBA8_sRGB: return "RGBA8_sRGB"; |
| case TextureFormat::kBGRA8: return "BGRA8"; |
| case TextureFormat::kBGR10_A2: return "BGR10_A2"; |
| case TextureFormat::kBGRA8_sRGB: return "BGRA8_sRGB"; |
| case TextureFormat::kABGR4: return "ABGR4"; |
| case TextureFormat::kARGB4: return "ARGB4"; |
| case TextureFormat::kBGRA10x6_XR: return "BGRA10x6_XR"; |
| case TextureFormat::kRGB8_ETC2: return "RGB8_ETC2"; |
| case TextureFormat::kRGB8_ETC2_sRGB: return "RGB8_ETC2_sRGB"; |
| case TextureFormat::kRGB8_BC1: return "RGB8_BC1"; |
| case TextureFormat::kRGBA8_BC1: return "RGBA8_BC1"; |
| case TextureFormat::kRGBA8_BC1_sRGB: return "RGBA8_BC1_sRGB"; |
| case TextureFormat::kYUV8_P2_420: return "YUV8_P2_420"; |
| case TextureFormat::kYUV8_P3_420: return "YUV8_P3_420"; |
| case TextureFormat::kYUV10x6_P2_420: return "YUV10x6_P2_420"; |
| case TextureFormat::kExternal: return "External"; |
| case TextureFormat::kS8: return "S8"; |
| case TextureFormat::kD16: return "D16"; |
| case TextureFormat::kD32F: return "D32F"; |
| case TextureFormat::kD24_S8: return "D24_S8"; |
| case TextureFormat::kD32F_S8: return "D32F_S8"; |
| } |
| SkUNREACHABLE; |
| } |
| |
| SkTextureCompressionType TextureFormatCompressionType(TextureFormat format) { |
| switch (format) { |
| case TextureFormat::kRGB8_ETC2: [[fallthrough]]; |
| case TextureFormat::kRGB8_ETC2_sRGB: return SkTextureCompressionType::kETC2_RGB8_UNORM; |
| case TextureFormat::kRGB8_BC1: return SkTextureCompressionType::kBC1_RGB8_UNORM; |
| case TextureFormat::kRGBA8_BC1: [[fallthrough]]; |
| case TextureFormat::kRGBA8_BC1_sRGB: return SkTextureCompressionType::kBC1_RGBA8_UNORM; |
| default: return SkTextureCompressionType::kNone; |
| } |
| } |
| |
| TextureFormat CompressionTypeToTextureFormat(SkTextureCompressionType type) { |
| switch (type) { |
| case SkTextureCompressionType::kBC1_RGB8_UNORM: return TextureFormat::kRGB8_BC1; |
| case SkTextureCompressionType::kBC1_RGBA8_UNORM: return TextureFormat::kRGBA8_BC1; |
| case SkTextureCompressionType::kETC2_RGB8_UNORM: return TextureFormat::kRGB8_ETC2; |
| default: return TextureFormat::kUnsupported; |
| } |
| } |
| |
| size_t TextureFormatBytesPerBlock(TextureFormat format) { |
| switch (format) { |
| case TextureFormat::kUnsupported: return 0; |
| case TextureFormat::kR8: return 1; |
| case TextureFormat::kR16: return 2; |
| case TextureFormat::kR16F: return 2; |
| case TextureFormat::kR32F: return 4; |
| case TextureFormat::kA8: return 1; |
| case TextureFormat::kRG8: return 2; |
| case TextureFormat::kRG16: return 4; |
| case TextureFormat::kRG16F: return 4; |
| case TextureFormat::kRG32F: return 8; |
| case TextureFormat::kRGB8: return 3; |
| case TextureFormat::kBGR8: return 3; |
| case TextureFormat::kB5_G6_R5: return 2; |
| case TextureFormat::kR5_G6_B5: return 2; |
| case TextureFormat::kRGB16: return 6; |
| case TextureFormat::kRGB16F: return 6; |
| case TextureFormat::kRGB32F: return 12; |
| case TextureFormat::kRGB8_sRGB: return 3; |
| case TextureFormat::kBGR10_XR: return 4; |
| case TextureFormat::kRGBA8: return 4; |
| case TextureFormat::kRGBA16: return 8; |
| case TextureFormat::kRGBA16F: return 8; |
| case TextureFormat::kRGBA32F: return 16; |
| case TextureFormat::kRGB10_A2: return 4; |
| case TextureFormat::kRGBA10x6: return 8; |
| case TextureFormat::kRGBA8_sRGB: return 4; |
| case TextureFormat::kBGRA8: return 4; |
| case TextureFormat::kBGR10_A2: return 4; |
| case TextureFormat::kBGRA8_sRGB: return 4; |
| case TextureFormat::kABGR4: return 2; |
| case TextureFormat::kARGB4: return 2; |
| case TextureFormat::kBGRA10x6_XR: return 8; |
| case TextureFormat::kS8: return 1; |
| case TextureFormat::kD16: return 2; |
| case TextureFormat::kD32F: return 4; |
| case TextureFormat::kD24_S8: return 4; |
| case TextureFormat::kD32F_S8: return 5; // assuming it's multiplanar |
| // NOTE: For compressed formats, the block size refers to an actual compressed block of |
| // multiple texels, whereas with other formats the block size represents a single pixel. |
| case TextureFormat::kRGB8_ETC2: |
| case TextureFormat::kRGB8_ETC2_sRGB: |
| case TextureFormat::kRGB8_BC1: |
| case TextureFormat::kRGBA8_BC1: |
| case TextureFormat::kRGBA8_BC1_sRGB: |
| return 8; |
| // NOTE: We don't actually know the size of external formats, so this is an arbitrary value. |
| // We will see external formats only in wrapped SkImages, so this won't impact Skia's |
| // internal budgeting. |
| case TextureFormat::kExternal: |
| return 4; |
| // TODO(b/401016699): We are just over estimating this value to be used in gpu size |
| // calculations even though the actually size is probably less. We should instead treat |
| // planar formats similar to compressed textures that go through their own special query for |
| // calculating size. |
| case TextureFormat::kYUV8_P2_420: |
| case TextureFormat::kYUV8_P3_420: |
| return 3; |
| case TextureFormat::kYUV10x6_P2_420: |
| return 6; |
| } |
| SkUNREACHABLE; |
| } |
| |
| uint32_t TextureFormatChannelMask(TextureFormat format) { |
| switch (format) { |
| case TextureFormat::kA8: return kAlpha_SkColorChannelFlag; |
| |
| case TextureFormat::kR8: [[fallthrough]]; |
| case TextureFormat::kR16: |
| case TextureFormat::kR16F: |
| case TextureFormat::kR32F: return kRed_SkColorChannelFlag; |
| |
| case TextureFormat::kRG8: [[fallthrough]]; |
| case TextureFormat::kRG16: |
| case TextureFormat::kRG16F: |
| case TextureFormat::kRG32F: return kRG_SkColorChannelFlags; |
| |
| case TextureFormat::kRGB8: [[fallthrough]]; |
| case TextureFormat::kBGR8: |
| case TextureFormat::kB5_G6_R5: |
| case TextureFormat::kR5_G6_B5: |
| case TextureFormat::kRGB16: |
| case TextureFormat::kRGB16F: |
| case TextureFormat::kRGB32F: |
| case TextureFormat::kRGB8_sRGB: |
| case TextureFormat::kBGR10_XR: |
| case TextureFormat::kRGB8_ETC2: |
| case TextureFormat::kRGB8_ETC2_sRGB: |
| case TextureFormat::kRGB8_BC1: |
| case TextureFormat::kYUV8_P2_420: |
| case TextureFormat::kYUV8_P3_420: |
| case TextureFormat::kYUV10x6_P2_420: return kRGB_SkColorChannelFlags; |
| |
| case TextureFormat::kRGBA8: [[fallthrough]]; |
| case TextureFormat::kRGBA16: |
| case TextureFormat::kRGBA16F: |
| case TextureFormat::kRGBA32F: |
| case TextureFormat::kRGB10_A2: |
| case TextureFormat::kRGBA10x6: |
| case TextureFormat::kRGBA8_sRGB: |
| case TextureFormat::kBGRA8: |
| case TextureFormat::kBGR10_A2: |
| case TextureFormat::kBGRA8_sRGB: |
| case TextureFormat::kABGR4: |
| case TextureFormat::kARGB4: |
| case TextureFormat::kBGRA10x6_XR: |
| case TextureFormat::kRGBA8_BC1: |
| case TextureFormat::kRGBA8_BC1_sRGB: |
| case TextureFormat::kExternal: return kRGBA_SkColorChannelFlags; |
| |
| case TextureFormat::kS8: [[fallthrough]]; |
| case TextureFormat::kD16: |
| case TextureFormat::kD32F: |
| case TextureFormat::kD24_S8: |
| case TextureFormat::kD32F_S8: |
| case TextureFormat::kUnsupported: return 0; |
| } |
| SkUNREACHABLE; |
| } |
| |
| bool TextureFormatAutoClamps(TextureFormat format) { |
| // Floating point formats, extended range formats, and non-normalized integer formats do not |
| // auto-clamp. Everything behaves like an unsigned normalized number. |
| return !(TextureFormatIsFloatingPoint(format) || |
| format == TextureFormat::kBGR10_XR || |
| format == TextureFormat::kBGRA10x6_XR || |
| format == TextureFormat::kS8); |
| } |
| |
| bool TextureFormatIsFloatingPoint(TextureFormat format) { |
| switch (format) { |
| // Floating point formats |
| case TextureFormat::kR16F: [[fallthrough]]; |
| case TextureFormat::kR32F: |
| case TextureFormat::kRG16F: |
| case TextureFormat::kRG32F: |
| case TextureFormat::kRGB16F: |
| case TextureFormat::kRGB32F: |
| case TextureFormat::kRGBA16F: |
| case TextureFormat::kRGBA32F: |
| case TextureFormat::kD32F: |
| case TextureFormat::kD32F_S8: return true; |
| |
| // Everything else is unorm, unorm-srgb, fixed point, or integral |
| case TextureFormat::kUnsupported: [[fallthrough]]; |
| case TextureFormat::kR8: |
| case TextureFormat::kR16: |
| case TextureFormat::kA8: |
| case TextureFormat::kRG8: |
| case TextureFormat::kRG16: |
| case TextureFormat::kRGB8: |
| case TextureFormat::kBGR8: |
| case TextureFormat::kB5_G6_R5: |
| case TextureFormat::kR5_G6_B5: |
| case TextureFormat::kRGB16: |
| case TextureFormat::kRGB8_sRGB: |
| case TextureFormat::kBGR10_XR: |
| case TextureFormat::kRGBA8: |
| case TextureFormat::kRGBA16: |
| case TextureFormat::kRGB10_A2: |
| case TextureFormat::kRGBA10x6: |
| case TextureFormat::kRGBA8_sRGB: |
| case TextureFormat::kBGRA8: |
| case TextureFormat::kBGR10_A2: |
| case TextureFormat::kBGRA8_sRGB: |
| case TextureFormat::kABGR4: |
| case TextureFormat::kARGB4: |
| case TextureFormat::kBGRA10x6_XR: |
| case TextureFormat::kRGB8_ETC2: |
| case TextureFormat::kRGB8_ETC2_sRGB: |
| case TextureFormat::kRGB8_BC1: |
| case TextureFormat::kRGBA8_BC1: |
| case TextureFormat::kRGBA8_BC1_sRGB: |
| case TextureFormat::kYUV8_P2_420: |
| case TextureFormat::kYUV8_P3_420: |
| case TextureFormat::kYUV10x6_P2_420: |
| case TextureFormat::kExternal: |
| case TextureFormat::kS8: |
| case TextureFormat::kD16: |
| case TextureFormat::kD24_S8: return false; |
| } |
| SkUNREACHABLE; |
| } |
| |
| bool TextureFormatIsDepthOrStencil(TextureFormat format) { |
| switch (format) { |
| case TextureFormat::kS8: [[fallthrough]]; |
| case TextureFormat::kD16: |
| case TextureFormat::kD32F: |
| case TextureFormat::kD24_S8: |
| case TextureFormat::kD32F_S8: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool TextureFormatHasDepth(TextureFormat format) { |
| switch (format) { |
| case TextureFormat::kD16: [[fallthrough]]; |
| case TextureFormat::kD32F: |
| case TextureFormat::kD24_S8: |
| case TextureFormat::kD32F_S8: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool TextureFormatHasStencil(TextureFormat format) { |
| switch (format) { |
| case TextureFormat::kS8: [[fallthrough]]; |
| case TextureFormat::kD24_S8: |
| case TextureFormat::kD32F_S8: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool TextureFormatIsMultiplanar(TextureFormat format) { |
| switch (format) { |
| case TextureFormat::kYUV8_P2_420: [[fallthrough]]; |
| case TextureFormat::kYUV8_P3_420: |
| case TextureFormat::kYUV10x6_P2_420: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // Supporting implementation details for TextureFormat and SkColorType conversions |
| // ------------------------------------------------------------------------------------------------ |
| |
| Swizzle ReadSwizzleForColorType(SkColorType ct, TextureFormat format) { |
| // TODO(b/390473370): When data transfers can apply an RG swizzle outside of the |
| // SkColorType representation, we should instead apply the swizzle on upload and |
| // preserve the expected order for any GPU use. |
| if (ct == kARGB_4444_SkColorType && format == TextureFormat::kARGB4) { |
| return Swizzle::BGRA(); |
| } |
| |
| uint32_t colorChannels = SkColorTypeChannelFlags(ct); |
| uint32_t formatChannels = TextureFormatChannelMask(format); |
| |
| // Read swizzles only have to handle a few semantics around the sampled values, as any sort of |
| // channel ordering for RGB vs BGR is handled by hardware. All we have to handle is mapping to |
| // "gray", red-vs-alpha, and forcing to opaque. |
| if (SkColorTypeIsAlphaOnly(ct)) { |
| // If the format isn't just an alpha channel (e.g. TextureFormat::kA8), we need to adjust |
| if (formatChannels != kAlpha_SkColorChannelFlag) { |
| // If the format has an alpha channel, mask every other channel to 0 |
| if (formatChannels & kAlpha_SkColorChannelFlag) { |
| return Swizzle("000a"); |
| } else { |
| // Otherwise move the red channel to alpha |
| SkASSERT(formatChannels & kRed_SkColorChannelFlag); |
| return Swizzle("000r"); |
| } |
| } else { |
| // otherwise leave as "rgba" and let hardware do the right thing |
| return Swizzle::RGBA(); |
| } |
| } else { |
| // First map gray to rrra; if this is just gray and not gray+alpha, it will also be forced |
| // to opaque below and become rrr1. |
| Swizzle swizzle; |
| if (colorChannels & kGray_SkColorChannelFlag) { |
| SkASSERT(formatChannels & kRed_SkColorChannelFlag); |
| swizzle = Swizzle::RRRA(); |
| } else { |
| swizzle = Swizzle::RGBA(); |
| } |
| |
| // Last, force the alpha to opaque if the color type masks it off but is present in the |
| // texture format. |
| if (!(colorChannels & kAlpha_SkColorChannelFlag) && |
| (formatChannels & kAlpha_SkColorChannelFlag)) { |
| swizzle = Swizzle::Concat(swizzle, Swizzle::RGB1()); |
| } |
| |
| return swizzle; |
| } |
| } |
| |
| std::optional<skgpu::Swizzle> WriteSwizzleForColorType(SkColorType ct, TextureFormat format) { |
| // D/S, compressed, external, and multiplanar formats aren't renderable with a color type. |
| // Format support would mean we never really try to get here in practice, but keep consistent. |
| if (format == TextureFormat::kExternal || |
| TextureFormatIsDepthOrStencil(format) || |
| TextureFormatIsMultiplanar(format) || |
| TextureFormatCompressionType(format) != SkTextureCompressionType::kNone) { |
| return std::nullopt; |
| } |
| |
| // TODO(b/390473370): When data transfers can apply an RG swizzle outside of the |
| // SkColorType representation, we should instead apply the swizzle on upload and |
| // preserve the expected order for any GPU use. |
| if (ct == kARGB_4444_SkColorType && format == TextureFormat::kARGB4) { |
| return Swizzle::BGRA(); |
| } |
| |
| uint32_t colorChannels = SkColorTypeChannelFlags(ct); |
| uint32_t formatChannels = TextureFormatChannelMask(format); |
| |
| // Write swizzles only have to handle a few semantics around the sampled values, as any sort of |
| // channel ordering for RGB vs BGR is handled by hardware. This reduces to just handling red |
| // vs alpha. The other cases for read swizzles do not apply: |
| // - We disallow gray since computing luminance is beyond a swizzle. |
| // - We disallow forcing to opaque since in all cases where we'd do that we have no guarantee |
| // of what the dst pixel's alpha was. In the future, we could support forced-opaque |
| // rendering by always using shader-based blending or by guaranteeing a one-time initialize |
| // draw that forced any alpha channel to 1 (b/489785214). |
| if (SkColorTypeIsAlphaOnly(ct)) { |
| // If the format isn't just an alpha channel (e.g. TextureFormat::kA8), we need to adjust |
| if (formatChannels != kAlpha_SkColorChannelFlag) { |
| // If the format has an alpha channel, mask every other channel to 0 |
| if (formatChannels & kAlpha_SkColorChannelFlag) { |
| return Swizzle("000a"); |
| } else { |
| // Otherwise move the alpha channel to red |
| SkASSERT(formatChannels & kRed_SkColorChannelFlag); |
| return Swizzle("a000"); |
| } |
| } else { |
| // otherwise leave as "rgba" and let hardware do the right thing |
| return Swizzle::RGBA(); |
| } |
| } else { |
| if ((colorChannels != formatChannels) || (colorChannels & kGray_SkColorChannelFlag)) { |
| return std::nullopt; |
| } |
| return Swizzle::RGBA(); |
| } |
| } |
| |
| SkSpan<const TextureFormat> PreferredTextureFormats(SkColorType ct) { |
| #define N(...) std::size({__VA_ARGS__}) |
| #define CASE(C, ...) case C: { \ |
| static const std::array<TextureFormat, N(__VA_ARGS__)> kFormats{{__VA_ARGS__}}; \ |
| return SkSpan(kFormats); } |
| |
| switch (ct) { |
| case kUnknown_SkColorType: return {}; |
| // NOTE: Not all backends support all TextureFormats. Some of the more advanced formats |
| // may not be supported at all and have no viable fallback. For color types that have |
| // equivalent texture formats differing only in RGB vs. BGR swizzle, we allow both |
| // format variations to maximize color types that have some format. For alpha-only color |
| // types, we only match to red-channel formats as they have the broadest support. |
| |
| CASE(kAlpha_8_SkColorType, TextureFormat::kR8) |
| // NOTE: kRGB_565_SkColorType is misnamed and natively matches B5_G6_R5 |
| CASE(kRGB_565_SkColorType, TextureFormat::kB5_G6_R5, TextureFormat::kR5_G6_B5) |
| // NOTE: kARGB_4444_SkColorType is misnamed and natively matches ABGR4 |
| CASE(kARGB_4444_SkColorType, TextureFormat::kABGR4, TextureFormat::kARGB4) |
| CASE(kRGBA_8888_SkColorType, TextureFormat::kRGBA8, TextureFormat::kBGRA8) |
| CASE(kRGB_888x_SkColorType, TextureFormat::kRGB8, |
| TextureFormat::kRGBA8, |
| TextureFormat::kBGRA8) |
| CASE(kBGRA_8888_SkColorType, TextureFormat::kBGRA8, TextureFormat::kRGBA8) |
| CASE(kRGBA_1010102_SkColorType, TextureFormat::kRGB10_A2, TextureFormat::kBGR10_A2) |
| CASE(kBGRA_1010102_SkColorType, TextureFormat::kBGR10_A2, TextureFormat::kRGB10_A2) |
| CASE(kRGB_101010x_SkColorType, TextureFormat::kRGB10_A2, TextureFormat::kBGR10_A2) |
| CASE(kBGR_101010x_SkColorType, TextureFormat::kBGR10_A2, TextureFormat::kRGB10_A2) |
| CASE(kBGR_101010x_XR_SkColorType, TextureFormat::kBGR10_XR) |
| CASE(kBGRA_10101010_XR_SkColorType, TextureFormat::kBGRA10x6_XR) |
| CASE(kRGBA_10x6_SkColorType, TextureFormat::kRGBA10x6) |
| CASE(kGray_8_SkColorType, TextureFormat::kR8) |
| CASE(kRGBA_F16Norm_SkColorType, TextureFormat::kRGBA16F) |
| CASE(kRGBA_F16_SkColorType, TextureFormat::kRGBA16F) |
| CASE(kRGB_F16F16F16x_SkColorType, TextureFormat::kRGBA16F) |
| CASE(kRGBA_F32_SkColorType, TextureFormat::kRGBA32F) |
| CASE(kR8G8_unorm_SkColorType, TextureFormat::kRG8) |
| CASE(kA16_float_SkColorType, TextureFormat::kR16F) |
| CASE(kR16G16_float_SkColorType, TextureFormat::kRG16F) |
| CASE(kA16_unorm_SkColorType, TextureFormat::kR16) |
| CASE(kR16_unorm_SkColorType, TextureFormat::kR16) |
| CASE(kR16G16_unorm_SkColorType, TextureFormat::kRG16) |
| CASE(kR16G16B16A16_unorm_SkColorType, TextureFormat::kRGBA16) |
| CASE(kSRGBA_8888_SkColorType, TextureFormat::kRGBA8_sRGB, |
| TextureFormat::kBGRA8_sRGB) |
| CASE(kR8_unorm_SkColorType, TextureFormat::kR8) |
| } |
| |
| SkUNREACHABLE; |
| #undef CASE |
| #undef N |
| } |
| |
| } // namespace skgpu::graphite |