// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "GenericPlatform/GenericPlatformStackWalk.h" #include "Misc/Paths.h" #include "ImageCore.h" #include "Modules/ModuleManager.h" #include "Interfaces/ITextureFormat.h" #include "Interfaces/ITextureFormatModule.h" #include "TextureCompressorModule.h" #include "PixelFormat.h" #include "HAL/PlatformProcess.h" #include "TextureBuildFunction.h" #include "DerivedDataBuildFunctionFactory.h" #include "DerivedDataSharedString.h" #ifndef __APPLE__ #define __APPLE__ 0 #endif #ifndef __unix__ #define __unix__ 0 #endif #include "Etc.h" #include "EtcErrorMetric.h" #include "EtcImage.h" DEFINE_LOG_CATEGORY_STATIC(LogTextureFormatETC2, Log, All); class FETC2TextureBuildFunction final : public FTextureBuildFunction { const UE::DerivedData::FUtf8SharedString& GetName() const final { static const UE::DerivedData::FUtf8SharedString Name(UTF8TEXTVIEW("ETC2Texture")); return Name; } void GetVersion(UE::DerivedData::FBuildVersionBuilder& Builder, ITextureFormat*& OutTextureFormatVersioning) const final { static FGuid Version(TEXT("af5192f4-351f-422f-b539-f6bd4abadfae")); Builder << Version; OutTextureFormatVersioning = FModuleManager::GetModuleChecked(TEXT("TextureFormatETC2")).GetTextureFormat(); } }; /** * Macro trickery for supported format names. */ #define ENUM_SUPPORTED_FORMATS(op) \ op(ETC2_RGB) \ op(ETC2_RGBA) \ op(ETC2_R11) \ op(AutoETC2) #define DECL_FORMAT_NAME(FormatName) static FName GTextureFormatName##FormatName = FName(TEXT(#FormatName)); ENUM_SUPPORTED_FORMATS(DECL_FORMAT_NAME); #undef DECL_FORMAT_NAME #define DECL_FORMAT_NAME_ENTRY(FormatName) GTextureFormatName##FormatName , static FName GSupportedTextureFormatNames[] = { ENUM_SUPPORTED_FORMATS(DECL_FORMAT_NAME_ENTRY) }; #undef DECL_FORMAT_NAME_ENTRY #undef ENUM_SUPPORTED_FORMATS static bool CompressImageUsingEtc2comp( const void* InSourceData, EPixelFormat PixelFormat, int32 SizeX, int32 SizeY, EGammaSpace TargetGammaSpace, TArray64& OutCompressedData) { using namespace Etc; Image::Format EtcFormat = Image::Format::UNKNOWN; switch (PixelFormat) { case PF_ETC2_RGB: EtcFormat = Image::Format::RGB8; break; case PF_ETC2_RGBA: EtcFormat = Image::Format::RGBA8; break; case PF_ETC2_R11_EAC: EtcFormat = Image::Format::R11; break; case PF_ETC2_RG11_EAC: EtcFormat = Image::Format::RG11; break; default: UE_LOG(LogTextureFormatETC2, Fatal, TEXT("Unsupported EPixelFormat for compression: %u"), (uint32)PixelFormat); return false; } // RGBA, REC709, NUMERIC will set RGB to 0 if all pixels in the block are transparent (A=0) const Etc::ErrorMetric EtcErrorMetric = Etc::RGBX; const float EtcEffort = ETCCOMP_DEFAULT_EFFORT_LEVEL; const unsigned int MAX_JOBS = 8; const unsigned int NUM_JOBS = 8; unsigned char* paucEncodingBits = nullptr; unsigned int uiEncodingBitsBytes = 0; unsigned int uiExtendedWidth = 0; unsigned int uiExtendedHeight = 0; int iEncodingTime_ms = 0; float* SourceData = (float*)InSourceData; // InSourceData is a linear color, we need to feed float* data to the codec in a target color space TArray64 IntermediateData; if (TargetGammaSpace == EGammaSpace::sRGB) { int64 NumPixels = SizeX * SizeY; IntermediateData.Reserve(NumPixels * 4); IntermediateData.AddUninitialized(NumPixels * 4); for (int64 Idx = 0; Idx < IntermediateData.Num(); Idx += 4) { const FLinearColor& LinColor = *(FLinearColor*)(SourceData + Idx); FColor Color = LinColor.ToFColorSRGB(); IntermediateData[Idx + 0] = Color.R / 255.f; IntermediateData[Idx + 1] = Color.G / 255.f; IntermediateData[Idx + 2] = Color.B / 255.f; IntermediateData[Idx + 3] = Color.A / 255.f; } SourceData = IntermediateData.GetData(); } Encode( SourceData, SizeX, SizeY, EtcFormat, EtcErrorMetric, EtcEffort, NUM_JOBS, MAX_JOBS, &paucEncodingBits, &uiEncodingBitsBytes, &uiExtendedWidth, &uiExtendedHeight, &iEncodingTime_ms ); OutCompressedData.SetNumUninitialized(uiEncodingBitsBytes); FMemory::Memcpy(OutCompressedData.GetData(), paucEncodingBits, uiEncodingBitsBytes); delete[] paucEncodingBits; return true; } /** * ETC2 texture format handler. */ class FTextureFormatETC2 : public ITextureFormat { public: virtual bool AllowParallelBuild() const override { return true; } virtual uint16 GetVersion( FName Format, const struct FTextureBuildSettings* BuildSettings = nullptr ) const override { return 2; } virtual FName GetEncoderName(FName Format) const override { static const FName ETC2Name("ETC2"); return ETC2Name; } virtual void GetSupportedFormats(TArray& OutFormats) const override { OutFormats.Append(GSupportedTextureFormatNames, UE_ARRAY_COUNT(GSupportedTextureFormatNames)); } virtual FTextureFormatCompressorCaps GetFormatCapabilities() const override { return FTextureFormatCompressorCaps(); // Default capabilities. } virtual EPixelFormat GetPixelFormatForImage(const struct FTextureBuildSettings& BuildSettings, const struct FImage& Image, bool bImageHasAlphaChannel) const override { if (BuildSettings.TextureFormatName == GTextureFormatNameETC2_RGB || (BuildSettings.TextureFormatName == GTextureFormatNameAutoETC2 && !bImageHasAlphaChannel)) { return PF_ETC2_RGB; } if (BuildSettings.TextureFormatName == GTextureFormatNameETC2_RGBA || (BuildSettings.TextureFormatName == GTextureFormatNameAutoETC2 && bImageHasAlphaChannel)) { return PF_ETC2_RGBA; } if (BuildSettings.TextureFormatName == GTextureFormatNameETC2_R11) { return PF_ETC2_R11_EAC; } UE_LOG(LogTextureFormatETC2, Fatal, TEXT("Unhandled texture format '%s' given to FTextureFormatAndroid::GetPixelFormatForImage()"), *BuildSettings.TextureFormatName.ToString()); return PF_Unknown; } virtual bool CompressImage( const FImage& InImage, const struct FTextureBuildSettings& BuildSettings, FStringView DebugTexturePathName, bool bImageHasAlphaChannel, FCompressedImage2D& OutCompressedImage ) const override { const FImage& Image = InImage; // Source is expected to be F32 linear color check(Image.Format == ERawImageFormat::RGBA32F); EPixelFormat CompressedPixelFormat = GetPixelFormatForImage(BuildSettings, Image, bImageHasAlphaChannel); bool bCompressionSucceeded = true; int32 SliceSize = Image.SizeX * Image.SizeY; for (int32 SliceIndex = 0; SliceIndex < Image.NumSlices && bCompressionSucceeded; ++SliceIndex) { TArray64 CompressedSliceData; bCompressionSucceeded = CompressImageUsingEtc2comp( Image.AsRGBA32F().GetData() + SliceIndex * SliceSize, CompressedPixelFormat, Image.SizeX, Image.SizeY, BuildSettings.GetDestGammaSpace(), CompressedSliceData ); OutCompressedImage.RawData.Append(CompressedSliceData); } if (bCompressionSucceeded) { OutCompressedImage.SizeX = Image.SizeX; OutCompressedImage.SizeY = Image.SizeY; OutCompressedImage.SizeZ = (BuildSettings.bVolume || BuildSettings.bTextureArray) ? Image.NumSlices : 1; OutCompressedImage.PixelFormat = CompressedPixelFormat; } return bCompressionSucceeded; } }; class FTextureFormatETC2Module : public ITextureFormatModule { public: ITextureFormat* Singleton = NULL; FTextureFormatETC2Module() { } virtual ~FTextureFormatETC2Module() { if ( Singleton ) { delete Singleton; Singleton = nullptr; } } virtual void StartupModule() override { } virtual bool CanCallGetTextureFormats() override { return false; } virtual ITextureFormat* GetTextureFormat() { if ( Singleton == nullptr ) // not thread safe { FTextureFormatETC2* ptr = new FTextureFormatETC2(); Singleton = ptr; } return Singleton; } static inline UE::DerivedData::TBuildFunctionFactory BuildFunctionFactory; }; IMPLEMENT_MODULE(FTextureFormatETC2Module, TextureFormatETC2);