diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7849097d53..cc7cfeb2a5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,6 +8,7 @@ A new header is inserted each time a *tag* is created. - Fixed morph shapes not rendering in WebGL. - Added support for the latest version of emscripten. - gltfio: fixed blackness seen with default material. +- Added ETC2 and BC compressed texture support to Metal backend. ## v1.4.2 diff --git a/filament/backend/include/private/backend/BackendUtils.h b/filament/backend/include/private/backend/BackendUtils.h index 02876f8896..1b16c07ab6 100644 --- a/filament/backend/include/private/backend/BackendUtils.h +++ b/filament/backend/include/private/backend/BackendUtils.h @@ -22,9 +22,16 @@ namespace filament { namespace backend { /** - * Returns the number of bytes per pixel for the given format. + * Returns the number of bytes per pixel for the given format. For compressed texture formats, + * returns the number of bytes per block. */ size_t getFormatSize(TextureFormat format) noexcept; +/** + * For compressed texture formats, returns the number of horizontal pixels per block. Otherwise + * returns 0. + */ +size_t getBlockWidth(TextureFormat format) noexcept; + } // namespace backend } // namespace filament diff --git a/filament/backend/src/BackendUtils.cpp b/filament/backend/src/BackendUtils.cpp index 0c60f3c821..6be6ae043a 100644 --- a/filament/backend/src/BackendUtils.cpp +++ b/filament/backend/src/BackendUtils.cpp @@ -99,10 +99,130 @@ size_t getFormatSize(TextureFormat format) noexcept { case TextureFormat::RGBA32I: return 16; + // Compressed formats --------------------------------------------------------------------- + + case TextureFormat::EAC_RG11: + case TextureFormat::EAC_RG11_SIGNED: + case TextureFormat::ETC2_EAC_RGBA8: + case TextureFormat::ETC2_EAC_SRGBA8: + return 16; + + case TextureFormat::EAC_R11: + case TextureFormat::EAC_R11_SIGNED: + case TextureFormat::ETC2_RGB8: + case TextureFormat::ETC2_SRGB8: + case TextureFormat::ETC2_RGB8_A1: + case TextureFormat::ETC2_SRGB8_A1: + return 8; + + case TextureFormat::DXT1_RGB: + return 8; + + case TextureFormat::DXT3_RGBA: + case TextureFormat::DXT5_RGBA: + return 16; + + // The block size for ASTC compression is always 16 bytes. + case TextureFormat::RGBA_ASTC_4x4: + case TextureFormat::RGBA_ASTC_5x4: + case TextureFormat::RGBA_ASTC_5x5: + case TextureFormat::RGBA_ASTC_6x5: + case TextureFormat::RGBA_ASTC_6x6: + case TextureFormat::RGBA_ASTC_8x5: + case TextureFormat::RGBA_ASTC_8x6: + case TextureFormat::RGBA_ASTC_8x8: + case TextureFormat::RGBA_ASTC_10x5: + case TextureFormat::RGBA_ASTC_10x6: + case TextureFormat::RGBA_ASTC_10x8: + case TextureFormat::RGBA_ASTC_10x10: + case TextureFormat::RGBA_ASTC_12x10: + case TextureFormat::RGBA_ASTC_12x12: + case TextureFormat::SRGB8_ALPHA8_ASTC_4x4: + case TextureFormat::SRGB8_ALPHA8_ASTC_5x4: + case TextureFormat::SRGB8_ALPHA8_ASTC_5x5: + case TextureFormat::SRGB8_ALPHA8_ASTC_6x5: + case TextureFormat::SRGB8_ALPHA8_ASTC_6x6: + case TextureFormat::SRGB8_ALPHA8_ASTC_8x5: + case TextureFormat::SRGB8_ALPHA8_ASTC_8x6: + case TextureFormat::SRGB8_ALPHA8_ASTC_8x8: + case TextureFormat::SRGB8_ALPHA8_ASTC_10x5: + case TextureFormat::SRGB8_ALPHA8_ASTC_10x6: + case TextureFormat::SRGB8_ALPHA8_ASTC_10x8: + case TextureFormat::SRGB8_ALPHA8_ASTC_10x10: + case TextureFormat::SRGB8_ALPHA8_ASTC_12x10: + case TextureFormat::SRGB8_ALPHA8_ASTC_12x12: + return 16; + default: return 0; } } +size_t getBlockWidth(TextureFormat format) noexcept { + switch (format) { + case TextureFormat::EAC_RG11: + case TextureFormat::EAC_RG11_SIGNED: + case TextureFormat::ETC2_EAC_RGBA8: + case TextureFormat::ETC2_EAC_SRGBA8: + case TextureFormat::EAC_R11: + case TextureFormat::EAC_R11_SIGNED: + case TextureFormat::ETC2_RGB8: + case TextureFormat::ETC2_SRGB8: + case TextureFormat::ETC2_RGB8_A1: + case TextureFormat::ETC2_SRGB8_A1: + return 4; + + case TextureFormat::DXT1_RGB: + case TextureFormat::DXT3_RGBA: + case TextureFormat::DXT5_RGBA: + return 4; + + case TextureFormat::RGBA_ASTC_4x4: + case TextureFormat::SRGB8_ALPHA8_ASTC_4x4: + return 4; + + case TextureFormat::RGBA_ASTC_5x4: + case TextureFormat::RGBA_ASTC_5x5: + case TextureFormat::SRGB8_ALPHA8_ASTC_5x4: + case TextureFormat::SRGB8_ALPHA8_ASTC_5x5: + return 5; + return 5; + + case TextureFormat::RGBA_ASTC_6x5: + case TextureFormat::RGBA_ASTC_6x6: + case TextureFormat::SRGB8_ALPHA8_ASTC_6x5: + case TextureFormat::SRGB8_ALPHA8_ASTC_6x6: + return 6; + + case TextureFormat::RGBA_ASTC_8x5: + case TextureFormat::RGBA_ASTC_8x6: + case TextureFormat::RGBA_ASTC_8x8: + case TextureFormat::SRGB8_ALPHA8_ASTC_8x5: + case TextureFormat::SRGB8_ALPHA8_ASTC_8x6: + case TextureFormat::SRGB8_ALPHA8_ASTC_8x8: + return 8; + + case TextureFormat::RGBA_ASTC_10x5: + case TextureFormat::RGBA_ASTC_10x6: + case TextureFormat::RGBA_ASTC_10x8: + case TextureFormat::RGBA_ASTC_10x10: + case TextureFormat::SRGB8_ALPHA8_ASTC_10x5: + case TextureFormat::SRGB8_ALPHA8_ASTC_10x6: + case TextureFormat::SRGB8_ALPHA8_ASTC_10x8: + case TextureFormat::SRGB8_ALPHA8_ASTC_10x10: + return 10; + + case TextureFormat::RGBA_ASTC_12x10: + case TextureFormat::RGBA_ASTC_12x12: + case TextureFormat::SRGB8_ALPHA8_ASTC_12x10: + case TextureFormat::SRGB8_ALPHA8_ASTC_12x12: + return 12; + + default: + return 0; + } +} + + } // namespace backend } // namespace filament diff --git a/filament/backend/src/metal/MetalEnums.h b/filament/backend/src/metal/MetalEnums.h index 340e1bffcc..56eca31302 100644 --- a/filament/backend/src/metal/MetalEnums.h +++ b/filament/backend/src/metal/MetalEnums.h @@ -215,22 +215,29 @@ constexpr inline MTLPixelFormat getMetalFormat(TextureFormat format) noexcept { case TextureFormat::RGBA32UI: return MTLPixelFormatRGBA32Uint; case TextureFormat::RGBA32I: return MTLPixelFormatRGBA32Sint; - // TODO: add compressed formats - case TextureFormat::EAC_R11: - case TextureFormat::EAC_R11_SIGNED: - case TextureFormat::EAC_RG11: - case TextureFormat::EAC_RG11_SIGNED: - case TextureFormat::ETC2_RGB8: - case TextureFormat::ETC2_SRGB8: - case TextureFormat::ETC2_RGB8_A1: - case TextureFormat::ETC2_SRGB8_A1: - case TextureFormat::ETC2_EAC_RGBA8: - case TextureFormat::ETC2_EAC_SRGBA8: +#if defined(IOS) + // EAC / ETC2 formats are only available on iPhone. + case TextureFormat::EAC_R11: return MTLPixelFormatEAC_R11Unorm; + case TextureFormat::EAC_R11_SIGNED: return MTLPixelFormatEAC_R11Snorm; + case TextureFormat::EAC_RG11: return MTLPixelFormatEAC_RG11Unorm; + case TextureFormat::EAC_RG11_SIGNED: return MTLPixelFormatEAC_RG11Snorm; + case TextureFormat::ETC2_RGB8: return MTLPixelFormatETC2_RGB8; + case TextureFormat::ETC2_SRGB8: return MTLPixelFormatETC2_RGB8_sRGB; + case TextureFormat::ETC2_RGB8_A1: return MTLPixelFormatETC2_RGB8A1; + case TextureFormat::ETC2_SRGB8_A1: return MTLPixelFormatETC2_RGB8A1_sRGB; + case TextureFormat::ETC2_EAC_RGBA8: return MTLPixelFormatEAC_RGBA8; + case TextureFormat::ETC2_EAC_SRGBA8: return MTLPixelFormatEAC_RGBA8_sRGB; +#endif - case TextureFormat::DXT1_RGB: - case TextureFormat::DXT1_RGBA: - case TextureFormat::DXT3_RGBA: - case TextureFormat::DXT5_RGBA: +#if !defined(IOS) + // DXT (BC) formats are only available on macOS desktkop. + // See https://en.wikipedia.org/wiki/S3_Texture_Compression#S3TC_format_comparison + case TextureFormat::DXT1_RGBA: return MTLPixelFormatBC1_RGBA; + case TextureFormat::DXT3_RGBA: return MTLPixelFormatBC2_RGBA; + case TextureFormat::DXT5_RGBA: return MTLPixelFormatBC3_RGBA; + + case TextureFormat::DXT1_RGB: return MTLPixelFormatInvalid; +#endif case TextureFormat::RGBA_ASTC_4x4: case TextureFormat::RGBA_ASTC_5x4: diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index 5d23d65974..42536085e1 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -129,10 +129,13 @@ struct MetalTexture : public HwTexture { void loadCubeImage(const PixelBufferDescriptor& data, const FaceOffsets& faceOffsets, int miplevel); + NSUInteger getBytesPerRow(PixelDataType type, NSUInteger width) const noexcept; + MetalContext& context; MetalExternalImage externalImage; id texture = nil; - uint8_t bytesPerPixel; + uint8_t bytesPerElement; // The number of bytes per pixel, or block (for compressed texture formats). + uint8_t blockWidth; // The number of horizontal pixels per block (only for compressed texture formats). TextureReshaper reshaper; }; diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 749f566348..31bc77d51a 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -23,6 +23,8 @@ #include #include +#include + namespace filament { namespace backend { namespace metal { @@ -289,7 +291,9 @@ MetalTexture::MetalTexture(MetalContext& context, backend::SamplerType target, u const TextureFormat reshapedFormat = reshaper.getReshapedFormat(); const MTLPixelFormat pixelFormat = decidePixelFormat(context.device, reshapedFormat); - bytesPerPixel = static_cast(getFormatSize(reshapedFormat)); + bytesPerElement = static_cast(getFormatSize(reshapedFormat)); + assert(bytesPerElement > 0); + blockWidth = static_cast(getBlockWidth(reshapedFormat)); ASSERT_POSTCONDITION(pixelFormat != MTLPixelFormatInvalid, "Pixel format not supported."); @@ -349,7 +353,7 @@ void MetalTexture::load2DImage(uint32_t level, uint32_t xoffset, uint32_t yoffse .depth = 1 } }; - NSUInteger bytesPerRow = bytesPerPixel * width; + const NSUInteger bytesPerRow = getBytesPerRow(data.type, width); [texture replaceRegion:region mipmapLevel:level slice:0 @@ -362,8 +366,9 @@ void MetalTexture::load2DImage(uint32_t level, uint32_t xoffset, uint32_t yoffse void MetalTexture::loadCubeImage(const PixelBufferDescriptor& data, const FaceOffsets& faceOffsets, int miplevel) { - NSUInteger faceWidth = width >> miplevel; - NSUInteger bytesPerRow = bytesPerPixel * faceWidth; + const NSUInteger faceWidth = width >> miplevel; + const NSUInteger bytesPerRow = getBytesPerRow(data.type, faceWidth); + MTLRegion region = MTLRegionMake2D(0, 0, faceWidth, faceWidth); for (NSUInteger slice = 0; slice < 6; slice++) { FaceOffsets::size_type faceOffset = faceOffsets.offsets[slice]; @@ -376,6 +381,20 @@ void MetalTexture::loadCubeImage(const PixelBufferDescriptor& data, const FaceOf } } +NSUInteger MetalTexture::getBytesPerRow(PixelDataType type, NSUInteger width) const noexcept { + // From https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion: + // For an ordinary or packed pixel format, the stride, in bytes, between rows of source data. + // For a compressed pixel format, the stride is the number of bytes from the beginning of one + // row of blocks to the beginning of the next. + if (type == PixelDataType::COMPRESSED) { + assert(blockWidth > 0); + const NSUInteger blocksPerRow = std::ceil(width / (float) blockWidth); + return bytesPerElement * blocksPerRow; + } else { + return bytesPerElement * width; + } +} + MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples, id color, id depth, uint8_t colorLevel, uint8_t depthLevel) : HwRenderTarget(width, height), context(context), samples(samples),