// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "HAL/PlatformProcess.h" #include "HAL/FileManager.h" #include "Misc/CommandLine.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Misc/Guid.h" #include "Misc/ConfigCacheIni.h" #include "ImageCore.h" #include "IImageWrapper.h" #include "IImageWrapperModule.h" #include "Modules/ModuleManager.h" #include "Interfaces/ITextureFormat.h" #include "Interfaces/ITextureFormatModule.h" #include "TextureCompressorModule.h" #include "PixelFormat.h" #include "Serialization/CompactBinary.h" #include "Serialization/CompactBinaryWriter.h" #include "TextureBuildFunction.h" #include "DerivedDataBuildFunctionFactory.h" #include "DerivedDataSharedString.h" #include "HAL/IConsoleManager.h" /**** * * TextureFormatASTC runs the ARM astcenc.exe command line tool * * or (by default) redirects to Intel ISPC texcomp* * *****/ // when GASTCCompressor == 0 ,use TextureFormatIntelISPCTexComp instead of this // @@!! does this break DDC2 ? can TBW see GASTCCompressor ? @todo Oodle fix me int32 GASTCCompressor = 0; static FAutoConsoleVariableRef CVarASTCCompressor( TEXT("cook.ASTCTextureCompressor"), GASTCCompressor, TEXT("0: IntelISPC, 1: Arm"), ECVF_Default | ECVF_ReadOnly ); #if PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC #define SUPPORTS_ISPC_ASTC 1 #else #define SUPPORTS_ISPC_ASTC 0 #endif // increment this if you change anything that will affect compression in this file #define BASE_ASTC_FORMAT_VERSION 41 #define MAX_QUALITY_BY_SIZE 4 #define MAX_QUALITY_BY_SPEED 3 DEFINE_LOG_CATEGORY_STATIC(LogTextureFormatASTC, Log, All); class FASTCTextureBuildFunction final : public FTextureBuildFunction { const UE::DerivedData::FUtf8SharedString& GetName() const final { static const UE::DerivedData::FUtf8SharedString Name(UTF8TEXTVIEW("ASTCTexture")); return Name; } void GetVersion(UE::DerivedData::FBuildVersionBuilder& Builder, ITextureFormat*& OutTextureFormatVersioning) const final { static FGuid Version(TEXT("4788dab5-b99c-479f-bc34-6d7df1cf30e5")); Builder << Version; OutTextureFormatVersioning = FModuleManager::GetModuleChecked(TEXT("TextureFormatASTC")).GetTextureFormat(); } }; /** * Macro trickery for supported format names. */ #define ENUM_SUPPORTED_FORMATS(op) \ op(ASTC_RGB) \ op(ASTC_RGBA) \ op(ASTC_RGBAuto) \ op(ASTC_RGBA_HQ) \ op(ASTC_RGB_HDR) \ op(ASTC_NormalAG) \ op(ASTC_NormalRG) #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 // ASTC file header format #if PLATFORM_SUPPORTS_PRAGMA_PACK #pragma pack(push, 4) #endif #define ASTC_MAGIC_CONSTANT 0x5CA1AB13 struct FASTCHeader { uint32 Magic; uint8 BlockSizeX; uint8 BlockSizeY; uint8 BlockSizeZ; uint8 TexelCountX[3]; uint8 TexelCountY[3]; uint8 TexelCountZ[3]; }; #if PLATFORM_SUPPORTS_PRAGMA_PACK #pragma pack(pop) #endif static int32 GetDefaultCompressionBySizeValue(FCbObjectView InFormatConfigOverride) { // this is code duped between TextureFormatASTC and TextureFormatISPC if (InFormatConfigOverride) { // If we have an explicit format config, then use it directly FCbFieldView FieldView = InFormatConfigOverride.FindView("DefaultASTCQualityBySize"); checkf(FieldView.HasValue(), TEXT("Missing DefaultASTCQualityBySize key from FormatConfigOverride")); int32 CompressionModeValue = FieldView.AsInt32(); checkf(!FieldView.HasError(), TEXT("Failed to parse DefaultASTCQualityBySize value from FormatConfigOverride")); return CompressionModeValue; } else { // default of 0 == 12x12 ? // BaseEngine.ini sets DefaultASTCQualityBySize to 3 == 6x6 auto GetCompressionModeValue = []() { // start at default quality, then lookup in .ini file int32 CompressionModeValue = 0; GConfig->GetInt(TEXT("/Script/UnrealEd.CookerSettings"), TEXT("DefaultASTCQualityBySize"), CompressionModeValue, GEngineIni); FParse::Value(FCommandLine::Get(), TEXT("-astcqualitybysize="), CompressionModeValue); return FMath::Min(CompressionModeValue, MAX_QUALITY_BY_SIZE); }; static int32 CompressionModeValue = GetCompressionModeValue(); return CompressionModeValue; } } static int32 GetDefaultCompressionBySpeedValue(FCbObjectView InFormatConfigOverride) { if (InFormatConfigOverride) { // If we have an explicit format config, then use it directly FCbFieldView FieldView = InFormatConfigOverride.FindView("DefaultASTCQualityBySpeed"); checkf(FieldView.HasValue(), TEXT("Missing DefaultASTCQualityBySpeed key from FormatConfigOverride")); int32 CompressionModeValue = FieldView.AsInt32(); checkf(!FieldView.HasError(), TEXT("Failed to parse DefaultASTCQualityBySpeed value from FormatConfigOverride")); return CompressionModeValue; } else { // default of 0 == "fastest" auto GetCompressionModeValue = []() { // start at default quality, then lookup in .ini file int32 CompressionModeValue = 0; GConfig->GetInt(TEXT("/Script/UnrealEd.CookerSettings"), TEXT("DefaultASTCQualityBySpeed"), CompressionModeValue, GEngineIni); FParse::Value(FCommandLine::Get(), TEXT("-astcqualitybyspeed="), CompressionModeValue); return FMath::Min(CompressionModeValue, MAX_QUALITY_BY_SPEED); }; static int32 CompressionModeValue = GetCompressionModeValue(); return CompressionModeValue; } } static FString GetQualityString(EPixelFormat PixelFormat,const FCbObjectView& InFormatConfigOverride) { // convert to a string FString CompressionMode; switch (PixelFormat) { case PF_ASTC_12x12: case PF_ASTC_12x12_HDR: CompressionMode = TEXT("12x12"); break; case PF_ASTC_10x10: case PF_ASTC_10x10_HDR: CompressionMode = TEXT("10x10"); break; case PF_ASTC_8x8: case PF_ASTC_8x8_HDR: CompressionMode = TEXT("8x8"); break; case PF_ASTC_6x6: case PF_ASTC_6x6_HDR: CompressionMode = TEXT("6x6"); break; case PF_ASTC_4x4: case PF_ASTC_4x4_HDR: CompressionMode = TEXT("4x4"); break; default: UE_LOG(LogTextureFormatASTC, Fatal, TEXT("ASTC size quality higher than expected")); } switch ( GetDefaultCompressionBySpeedValue(InFormatConfigOverride) ) { case 0: CompressionMode += TEXT(" -fastest"); break; case 1: CompressionMode += TEXT(" -fast"); break; case 2: CompressionMode += TEXT(" -medium"); break; case 3: CompressionMode += TEXT(" -thorough"); break; default: UE_LOG(LogTextureFormatASTC, Fatal, TEXT("ASTC speed quality higher than expected")); } return CompressionMode; } static EPixelFormat GetQualityFormat(const FTextureBuildSettings& BuildSettings) { // code dupe between TextureFormatASTC and TextureFormatISPC const FCbObjectView& InFormatConfigOverride = BuildSettings.FormatConfigOverride; int32 OverrideSizeValue= BuildSettings.CompressionQuality; bool bIsNormalMap = (BuildSettings.TextureFormatName == GTextureFormatNameASTC_NormalAG || BuildSettings.TextureFormatName == GTextureFormatNameASTC_NormalRG); bool bIsHQ = BuildSettings.TextureFormatName == GTextureFormatNameASTC_RGBA_HQ; bool bHDRFormat = BuildSettings.TextureFormatName == GTextureFormatNameASTC_RGB_HDR; if ( bIsNormalMap ) { return PF_ASTC_6x6; } else if ( bIsHQ ) { return PF_ASTC_4x4; } else if (BuildSettings.bVirtualStreamable) { return PF_ASTC_4x4; } // CompressionQuality value here is ETextureCompressionQuality minus 1 // convert to a string EPixelFormat Format = PF_Unknown; if (bHDRFormat) { switch (OverrideSizeValue >= 0 ? OverrideSizeValue : GetDefaultCompressionBySizeValue(InFormatConfigOverride)) { case 0: Format = PF_ASTC_12x12_HDR; break; case 1: Format = PF_ASTC_10x10_HDR; break; case 2: Format = PF_ASTC_8x8_HDR; break; case 3: Format = PF_ASTC_6x6_HDR; break; case 4: Format = PF_ASTC_4x4_HDR; break; default: UE_LOG(LogTextureFormatASTC, Fatal, TEXT("Max quality higher than expected")); } } else { switch (OverrideSizeValue >= 0 ? OverrideSizeValue : GetDefaultCompressionBySizeValue(InFormatConfigOverride)) { case 0: Format = PF_ASTC_12x12; break; case 1: Format = PF_ASTC_10x10; break; case 2: Format = PF_ASTC_8x8; break; case 3: Format = PF_ASTC_6x6; break; case 4: Format = PF_ASTC_4x4; break; default: UE_LOG(LogTextureFormatASTC, Fatal, TEXT("Max quality higher than expected")); } } return Format; } static bool CompressSliceToASTC( const FImageView & SourceImage, FString CompressionParameters, TArray64& OutCompressedData, IImageWrapperModule& ImageWrapperModule ) { // at this point, SourceImage has been converted to RGBA8 or RGBA16F based on whether // the request TextureFormatName is "ASTC_RGB_HDR" or not, so we can ask if "source" is HDR : bool bHDR = ERawImageFormat::IsHDR(SourceImage.Format); EImageFormat FileFormat = bHDR ? EImageFormat::EXR : EImageFormat::PNG; TArray64 FileData; bool bCompressSucceeded = ImageWrapperModule.CompressImage(FileData,FileFormat,SourceImage,(int32)EImageCompressionQuality::Uncompressed); if ( ! bCompressSucceeded ) { UE_LOG(LogTextureFormatASTC, Error, TEXT("CompressSliceToASTC CompressImage failed")); return false; } int64 FileDataSize = FileData.Num(); // make a random file name to write the image : FGuid Guid; FPlatformMisc::CreateGuid(Guid); FString InputFilePath = FString::Printf(TEXT("Cache/%08x-%08x-%08x-%08x-RGBToASTCIn"), Guid.A, Guid.B, Guid.C, Guid.D) + TEXT(".") + ImageWrapperModule.GetExtension(FileFormat); FString OutputFilePath = FString::Printf(TEXT("Cache/%08x-%08x-%08x-%08x-RGBToASTCOut.astc"), Guid.A, Guid.B, Guid.C, Guid.D); InputFilePath = FPaths::ProjectIntermediateDir() + InputFilePath; OutputFilePath = FPaths::ProjectIntermediateDir() + OutputFilePath; // write to InputFilePath : { FArchive* PNGFile = IFileManager::Get().CreateFileWriter(*InputFilePath); while (!PNGFile) { // CreateFileWriter occasionally returns NULL due to error code ERROR_SHARING_VIOLATION // ... no choice but to wait for the file to become free to access UE_LOG(LogTextureFormatASTC, Display, TEXT("CreateFileWriter for %s failed, trying again..."), *InputFilePath); FPlatformProcess::Sleep(0.01f); PNGFile = IFileManager::Get().CreateFileWriter(*InputFilePath); } PNGFile->Serialize((void*)&FileData[0], FileDataSize); delete PNGFile; } // FileData written, can free now : FileData.Reset(); // Compress PNG file to ASTC (using the reference astcenc.exe from ARM) FString Params = (bHDR ? TEXT("-ch ") : TEXT("-cl ")) + FString::Printf(TEXT("\"%s\" \"%s\" %s"), *InputFilePath, *OutputFilePath, *CompressionParameters ); UE_LOG(LogTextureFormatASTC, Display, TEXT("Compressing to ASTC (options = '%s')..."), *CompressionParameters); // Start Compressor #if PLATFORM_MAC_X86 FString CompressorPath(FPaths::EngineDir() + TEXT("Binaries/ThirdParty/ARM/Mac/astcenc-sse2")); #elif PLATFORM_MAC_ARM64 FString CompressorPath(FPaths::EngineDir() + TEXT("Binaries/ThirdParty/ARM/Mac/astcenc-neon")); #elif PLATFORM_LINUX FString CompressorPath(FPaths::EngineDir() + TEXT("Binaries/ThirdParty/ARM/Linux64/astcenc-sse2")); #elif PLATFORM_WINDOWS FString CompressorPath(FPaths::EngineDir() + TEXT("Binaries/ThirdParty/ARM/Win64/astcenc-sse2.exe")); #else #error Unsupported platform #endif // run the astcenc process : { FProcHandle Proc = FPlatformProcess::CreateProc(*CompressorPath, *Params, true, false, false, NULL, -1, NULL, NULL); // Failed to start the compressor process if (!Proc.IsValid()) { UE_LOG(LogTextureFormatASTC, Error, TEXT("Failed to start astcenc for compressing images (%s)"), *CompressorPath); return false; } // Wait for the process to complete FPlatformProcess::WaitForProc(Proc); int ReturnCode = -1; FPlatformProcess::GetProcReturnCode(Proc, &ReturnCode); FPlatformProcess::CloseProc(Proc); // Did it work? if ( ReturnCode != 0) { UE_LOG(LogTextureFormatASTC, Error, TEXT("ASTC encoder failed with return code %d, mip size (%d, %d). Leaving '%s' for testing."), ReturnCode, SourceImage.SizeX, SourceImage.SizeY, *InputFilePath); return false; } } // Open compressed file and put the data in OutCompressedImage { // Get raw file data TArray64 ASTCData; if ( ! FFileHelper::LoadFileToArray(ASTCData, *OutputFilePath) ) { UE_LOG(LogTextureFormatASTC, Error, TEXT("Failed load output of astcenc (%s -> %s)"),*InputFilePath,*OutputFilePath); return false; } // Process it FASTCHeader* Header = (FASTCHeader*)ASTCData.GetData(); // Fiddle with the texel count data to get the right value uint32 TexelCountX = (Header->TexelCountX[0] << 0) + (Header->TexelCountX[1] << 8) + (Header->TexelCountX[2] << 16); uint32 TexelCountY = (Header->TexelCountY[0] << 0) + (Header->TexelCountY[1] << 8) + (Header->TexelCountY[2] << 16); uint32 TexelCountZ = (Header->TexelCountZ[0] << 0) + (Header->TexelCountZ[1] << 8) + (Header->TexelCountZ[2] << 16); if ( TexelCountX != SourceImage.SizeX || TexelCountY != SourceImage.SizeY ) { UE_LOG(LogTextureFormatASTC, Warning, TEXT("Unexpected image size mismatch : %d x %d != %d x %d"), TexelCountX,TexelCountY,SourceImage.SizeX,SourceImage.SizeY); } // Calculate size of this mip in blocks uint32 MipSizeX = (TexelCountX + Header->BlockSizeX - 1) / Header->BlockSizeX; uint32 MipSizeY = (TexelCountY + Header->BlockSizeY - 1) / Header->BlockSizeY; // TexelCountZ ignored // A block is always 16 bytes uint64 MipSize = (uint64)MipSizeX * MipSizeY * 16; // Copy the compressed data OutCompressedData.Empty(MipSize); OutCompressedData.AddUninitialized(MipSize); void* MipData = OutCompressedData.GetData(); // Calculate the offset to get to the mip data check(sizeof(FASTCHeader) == 16); check(ASTCData.Num() == (sizeof(FASTCHeader) + MipSize)); FMemory::Memcpy(MipData, ASTCData.GetData() + sizeof(FASTCHeader), MipSize); } // Delete intermediate files IFileManager::Get().Delete(*InputFilePath); IFileManager::Get().Delete(*OutputFilePath); return true; } /** * ASTC texture format handler. */ class FTextureFormatASTC : public ITextureFormat { public: FTextureFormatASTC() : IntelISPCTexCompFormat(*FModuleManager::LoadModuleChecked(TEXT("TextureFormatIntelISPCTexComp")).GetTextureFormat()), ImageWrapperModule(FModuleManager::LoadModuleChecked(FName("ImageWrapper"))) { // LoadModule has to be done on Main thread // can't be done on-demand in the Compress call } virtual bool AllowParallelBuild() const override { #if SUPPORTS_ISPC_ASTC if(GASTCCompressor == 0) { return IntelISPCTexCompFormat.AllowParallelBuild(); } #endif return true; } virtual FName GetEncoderName(FName Format) const override { #if SUPPORTS_ISPC_ASTC if (GASTCCompressor == 0) { return IntelISPCTexCompFormat.GetEncoderName(Format); } #endif static const FName ASTCName("ArmASTC"); return ASTCName; } virtual FCbObject ExportGlobalFormatConfig(const FTextureBuildSettings& BuildSettings) const override { #if SUPPORTS_ISPC_ASTC if(GASTCCompressor == 0) { return IntelISPCTexCompFormat.ExportGlobalFormatConfig(BuildSettings); } #endif FCbWriter Writer; Writer.BeginObject("TextureFormatASTCSettings"); Writer.AddInteger("DefaultASTCQualityBySize", GetDefaultCompressionBySizeValue(FCbObjectView())); Writer.AddInteger("DefaultASTCQualityBySpeed", GetDefaultCompressionBySpeedValue(FCbObjectView())); Writer.EndObject(); return Writer.Save().AsObject(); } // Version for all ASTC textures, whether it's handled by the ARM encoder or the ISPC encoder. virtual uint16 GetVersion( FName Format, const FTextureBuildSettings* BuildSettings = nullptr ) const override { #if SUPPORTS_ISPC_ASTC if(GASTCCompressor == 0) { // set high bit so version numbers of ISPC and ASTC don't overlap : check( BASE_ASTC_FORMAT_VERSION < 0x80 ); return 0x80 | IntelISPCTexCompFormat.GetVersion(Format,BuildSettings); } #endif return BASE_ASTC_FORMAT_VERSION; } virtual FString GetDerivedDataKeyString(const FTextureBuildSettings& BuildSettings) const override { #if SUPPORTS_ISPC_ASTC if(GASTCCompressor == 0) { return IntelISPCTexCompFormat.GetDerivedDataKeyString(BuildSettings); } #endif // ASTC block size chosen is in PixelFormat EPixelFormat PixelFormat = GetQualityFormat(BuildSettings); int Speed = GetDefaultCompressionBySpeedValue(BuildSettings.FormatConfigOverride); return FString::Printf(TEXT("ASTC_%d_%d"), (int)PixelFormat,Speed); } virtual FTextureFormatCompressorCaps GetFormatCapabilities() const override { return FTextureFormatCompressorCaps(); // Default capabilities. } virtual void GetSupportedFormats(TArray& OutFormats) const override { OutFormats.Append(GSupportedTextureFormatNames, sizeof(GSupportedTextureFormatNames)/sizeof(GSupportedTextureFormatNames[0]) ); } virtual EPixelFormat GetPixelFormatForImage(const FTextureBuildSettings& BuildSettings, const struct FImage& Image, bool bImageHasAlphaChannel) const override { return GetQualityFormat(BuildSettings); } virtual bool CompressImage( const FImage& InImage, const FTextureBuildSettings& BuildSettings, FStringView DebugTexturePathName, bool bImageHasAlphaChannel, FCompressedImage2D& OutCompressedImage ) const override { #if SUPPORTS_ISPC_ASTC if(GASTCCompressor == 0) { UE_CALL_ONCE( [&](){ UE_LOG(LogTextureFormatASTC, Display, TEXT("TextureFormatASTC using ISPC")) } ); // Route ASTC compression work to the ISPC module instead. // note: ISPC can't do HDR, will throw an error return IntelISPCTexCompFormat.CompressImage(InImage, BuildSettings, DebugTexturePathName, bImageHasAlphaChannel, OutCompressedImage); } #endif UE_CALL_ONCE( [&](){ UE_LOG(LogTextureFormatASTC, Display, TEXT("TextureFormatASTC using astcenc")) } ); bool bHDRImage = BuildSettings.TextureFormatName == GTextureFormatNameASTC_RGB_HDR; // Get Raw Image Data from passed in FImage & convert to BGRA8 or RGBA16F // note: wasteful, often copies image to same format FImage Image; InImage.CopyTo(Image, bHDRImage ? ERawImageFormat::RGBA16F : ERawImageFormat::BGRA8, BuildSettings.GetDestGammaSpace()); // @todo Oodle : for HDR need to clamp the F16 image in [0,F16_max] (like Oodle Texture does) // maybe also set A to 1.f to match BC6 ? // Determine the compressed pixel format and compression parameters EPixelFormat CompressedPixelFormat = GetPixelFormatForImage(BuildSettings, Image, bImageHasAlphaChannel); FString CompressionParameters = TEXT(""); FString QualityString = GetQualityString(CompressedPixelFormat,BuildSettings.FormatConfigOverride); /* The modes available are: -cl : use the linear LDR color profile. -cs : use the sRGB LDR color profile. -ch : use the HDR color profile, tuned for HDR RGB and LDR A. -cH : use the HDR color profile, tuned for HDR RGBA. -ch or -cl is added later in CompressSlice */ if (bHDRImage) { CompressionParameters = FString::Printf(TEXT("%s"), *QualityString ); // ASTC can encode floats that BC6H can't // but still clamp as if we were BC6H, so that the same output is made // (eg. ASTC can encode A but BC6 can't; we stuff 1 in A here) FImageCore::SanitizeFloat16AndSetAlphaOpaqueForBC6H(Image); } else if ( BuildSettings.TextureFormatName == GTextureFormatNameASTC_RGB || BuildSettings.TextureFormatName == GTextureFormatNameASTC_RGBA || BuildSettings.TextureFormatName == GTextureFormatNameASTC_RGBAuto || BuildSettings.TextureFormatName == GTextureFormatNameASTC_RGBA_HQ ) { if ( BuildSettings.TextureFormatName == GTextureFormatNameASTC_RGB || ! bImageHasAlphaChannel ) { // even if Name was RGBA we still use the RGB profile if !bImageHasAlphaChannel // so that "Compress Without Alpha" can force us to opaque // we need to set alpha to opaque here // can do it using "1" in the bgra swizzle to astcenc CompressionParameters = FString::Printf(TEXT("%s -esw bgr1"), *QualityString ); } else { CompressionParameters = FString::Printf(TEXT("%s -esw bgra"), *QualityString ); } } else if (BuildSettings.TextureFormatName == GTextureFormatNameASTC_NormalAG) { CompressionParameters = FString::Printf(TEXT("%s -esw 0g0b -cw 0 1 0 1 -dblimit 60 -b 2.5 -v 3 1 1 0 50 0 -va 1 1 0 50"), *QualityString); } else if (BuildSettings.TextureFormatName == GTextureFormatNameASTC_NormalRG) { CompressionParameters = FString::Printf(TEXT("%s -esw bg00 -cw 1 1 0 0 -dblimit 60 -b 2.5 -v 3 1 1 0 50 0 -va 1 1 0 50"), *QualityString); } else { check(false); } // Compress the image, slice by slice bool bCompressionSucceeded = true; for (int32 SliceIndex = 0; SliceIndex < Image.NumSlices; ++SliceIndex) { TArray64 CompressedSliceData; FImageView Slice = Image.GetSlice(SliceIndex); bCompressionSucceeded = CompressSliceToASTC(Slice,CompressionParameters,CompressedSliceData,ImageWrapperModule); if ( ! bCompressionSucceeded ) { return false; } 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; } private: const ITextureFormat& IntelISPCTexCompFormat; IImageWrapperModule& ImageWrapperModule; }; /** * Module for ASTC texture compression. */ static ITextureFormat* Singleton = NULL; class FTextureFormatASTCModule : public ITextureFormatModule { public: FTextureFormatASTCModule() { } virtual ~FTextureFormatASTCModule() { delete Singleton; Singleton = NULL; } virtual void StartupModule() override { } virtual bool CanCallGetTextureFormats() override { return false; } virtual ITextureFormat* GetTextureFormat() { if (!Singleton) { Singleton = new FTextureFormatASTC(); } return Singleton; } static inline UE::DerivedData::TBuildFunctionFactory BuildFunctionFactory; }; IMPLEMENT_MODULE(FTextureFormatASTCModule, TextureFormatASTC);