使用 ModularFeature 集成第三方压缩算法,以 ZSTD 为例。
在 UE 中如果想要自己添加一个压缩算法的实现则需要自己实现一个继承自 ICompressionFormat
的类,然后注册给IModulelarFeatures
,那么以 ZSTD 为例,来示范一下怎么真实地添加一个压缩算法。
首先还是要来介绍一下 ICompressionFormat
中提供的四个接口函数的语义要求:
1 2 3 4 5 6 7 8 9 10 11
| struct ICompressionFormat : public IModularFeature, public IModuleInterface { virtual FName GetCompressionFormatName() = 0; virtual bool Compress(void* CompressedBuffer, int32& CompressedSize, const void* UncompressedBuffer, int32 UncompressedSize, int32 CompressionData) = 0; virtual bool Uncompress(void* UncompressedBuffer, int32& UncompressedSize, const void* CompressedBuffer, int32 CompressedSize, int32 CompressionData) = 0; virtual int32 GetCompressedBufferSize(int32 UncompressedSize, int32 CompressionData) = 0; };
|
然后开干,集成 ZSTD 首先需要去 facebook/zstd 上把代码拉取下来,然后把代码提取出来(Lib 目录下除了 dll
目录外都可以拷贝过来),放到插件的 Souce/ThirdParty
下,并在插件的 *.build.cs
中将其添加至 PublicIncludePaths
中。
首先先要对 ZSTD
的代码进行修改,因为 UE 的编译环境和警告等级的关系是没办法把代码拷过来就可以直接编译过的,常见的操作为忽略某些警告,但是 ZSTD
有一点特别的地方在于它里面具有 XXHash
的代码在编译时会与 LiveCoding
中的有冲突会有重定义错误,所以需要对 ZSTD
代码中的 XXHash
进行改名。
当把 ZSTD 的代码在 UE 中能够顺利的编译过的时候可以进入下一个流程,创建并实现 ZSTD
的ICompressionFormat
实现。
1 2 3 4 5 6 7 8 9
| struct FZstdCompressionFormat : public ICompressionFormat { virtual FName GetCompressionFormatName()override; virtual bool Compress(void* CompressedBuffer, int32& CompressedSize, const void* UncompressedBuffer, int32 UncompressedSize, int32 CompressionData)override; virtual bool Uncompress(void* UncompressedBuffer, int32& UncompressedSize, const void* CompressedBuffer, int32 CompressedSize, int32 CompressionData)override; virtual int32 GetCompressedBufferSize(int32 UncompressedSize, int32 CompressionData)override;
static int32 Level; };
|
只是继承了 ICompressionFormat
然后添加了一个 Level
的 static 数据成员,用于记录在 ZSTD 中使用哪个压缩级别。
剩下的事情就是从 ZSTD 的代码里找到能够实现 ICompressionFormat
接口语义的函数:
1 2 3
| ZSTDLIB_API size_t ZSTD_compress(void* dst, size_t dstCapacity,const void* src, size_t srcSize,int compressionLevel); ZSTDLIB_API size_t ZSTD_decompress(void* dst, size_t dstCapacity,const void* src, size_t compressedSize); ZSTDLIB_API size_t ZSTD_compressBound(size_t srcSize);
|
通过查看 ZSTD 的代码可以发现这三个函数组合起来就可以实现 ICompressionFormat
中的所有功能,比较简单,都是转发调用:
PS: 通过实现 GetCompressionFormatName
来指定该压缩 Feature 的名字,我这里给了zstd
,在项目设置里指定的时候就要使用这个名字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| FName FZstdCompressionFormat::GetCompressionFormatName() { return TEXT("zstd"); }
bool FZstdCompressionFormat::Compress(void* CompressedBuffer, int32& CompressedSize, const void* UncompressedBuffer, int32 UncompressedSize, int32 CompressionData) { UE_LOG(LogTemp, Log, TEXT("FZstdCompressionFormat::Compress level is %d"), FZstdCompressionFormat::Level); int32 Result = ZSTD_compress(CompressedBuffer, CompressedSize, UncompressedBuffer, UncompressedSize, FZstdCompressionFormat::Level); if (Result > 0) { if (Result > GetCompressedBufferSize(UncompressedSize, CompressionData)) { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("%d < %d"), Result, GetCompressedBufferSize(UncompressedSize, CompressionData)); return false; } CompressedSize = Result; return true; } return false; } bool FZstdCompressionFormat::Uncompress(void* UncompressedBuffer, int32& UncompressedSize, const void* CompressedBuffer, int32 CompressedSize, int32 CompressionData) { int32 Result = ZSTD_decompress(UncompressedBuffer, UncompressedSize, CompressedBuffer, CompressedSize); if (Result > 0) { UncompressedSize = Result; return true; } return false; } int32 FZstdCompressionFormat::GetCompressedBufferSize(int32 UncompressedSize, int32 CompressionData) { return ZSTD_compressBound(UncompressedSize); }
|
到这里 ZSTD
的的集成工作就完毕了,只剩下最后一步,那就是把这个 Feature 添加到 IModularFeatures
中,可以供引擎使用。
因为我是创建了一个插件,所以可以把注册的逻辑写到模块的 StartupModule
中,反之卸载模块时取消注册。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #define ZSTD_LEVEL_OPTION_STRING TEXT("-ZstdLevel=") void FlibzstdModule::StartupModule() { FString CommandLine = FCommandLine::Get(); if (CommandLine.Contains(ZSTD_LEVEL_OPTION_STRING, ESearchCase::IgnoreCase)) { int32 level; FParse::Value(FCommandLine::Get(), *FString(ZSTD_LEVEL_OPTION_STRING).ToLower(), level); FZstdCompressionFormat::Level = FMath::Clamp(level, ZSTD_minCLevel(),ZSTD_maxCLevel()); }
ZstdCompressionFormat = new FZstdCompressionFormat(); IModularFeatures::Get().RegisterModularFeature(COMPRESSION_FORMAT_FEATURE_NAME, ZstdCompressionFormat);
}
void FlibzstdModule::ShutdownModule() { IModularFeatures::Get().UnregisterModularFeature(COMPRESSION_FORMAT_FEATURE_NAME, ZstdCompressionFormat); delete ZstdCompressionFormat;
}
|
与前面讲的注册方法一致,我这里还添加了一个引擎启动时的命令行参数 -ZstdLevel=
可以用来传递使用 ZSTD
进行压缩的的压缩等级。
打包 Pak 使用 ZSTD
算法,使用我的 HotPatcher 可以在 UnrealPakOptions
中添加 -compressionformats=zstd,zlib
参数:
使用 UnrealPak
检测压缩格式:
注意,因为没有编译引擎,所以是不能直接通过 UnrealPak.exe
来解压使用 ZSTD
压缩的 Pak 的。