2022-05-31 15:49:40 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "CoreMinimal.h"
# include "ImageCore.h"
# include "DDSFile.h"
# include "Modules/ModuleManager.h"
# include "TextureCompressorModule.h"
# include "Interfaces/ITextureFormat.h"
# include "Interfaces/ITextureFormatModule.h"
# include "PixelFormat.h"
# include "Engine/TextureDefines.h"
# include "Misc/ConfigCacheIni.h"
# include "Misc/ScopeLock.h"
# include "Misc/SecureHash.h"
# include "Async/ParallelFor.h"
# include "Async/TaskGraphInterfaces.h"
# include "IImageWrapper.h"
# include "IImageWrapperModule.h"
# include "Misc/FileHelper.h"
2022-08-26 01:46:28 -04:00
# include "Runtime/Launch/Resources/Version.h"
2022-05-31 15:49:40 -04:00
# include "Serialization/CompactBinary.h"
# include "Serialization/CompactBinaryWriter.h"
# include "DerivedDataBuildFunctionFactory.h"
# include "DerivedDataSharedString.h"
# include "Tasks/Task.h"
# include "TextureBuildFunction.h"
# include "HAL/FileManager.h"
2022-10-22 12:11:23 -04:00
# include "HAL/LowLevelMemTracker.h"
2022-05-31 15:49:40 -04:00
# include "Misc/WildcardString.h"
# include "Misc/CommandLine.h"
# include "oodle2tex.h"
// Alternate job system - can set UseOodleExampleJobify in engine ini to enable.
2022-09-10 00:03:16 -04:00
# include "Jobify/example_jobify.h"
2022-05-31 15:49:40 -04:00
/**********
Oodle Texture can do both RDO ( rate distortion optimization ) and non - RDO encoding to BC1 - 7.
This is controlled using the project texture compression settings and the corresponding
Compress Speed .
The texture property Lossy Compression Amount is converted to an RDO Lambda to use .
This property can be adjusted via LODGroup or per texture . If not set in either place ,
the project settings provide a default value .
Oodle Texture can encode BC1 - 7. It does not currently encode ASTC or other mobile formats .
= = = = = = = = = = = = = = = = = = = = =
TextureFormatOodle handles formats TFO_DXT1 , etc .
Use of this format ( instead of DXT1 ) is enabled with TextureFormatPrefix in config , such as :
\ Engine \ Config \ BaseEngine . ini
[ AlternateTextureCompression ]
TextureCompressionFormat = " TextureFormatOodle "
TextureFormatPrefix = " TFO_ "
When this is enabled , the formats like " DXT1 " are renamed to " TFO_DXT1 " and are handled by this encoder .
Oodle Texture RDO encoding can be slow , but is cached in the DDC so should only be slow the first time .
A fast local network shared DDC is recommended .
RDO encoding and compression level can be enabled separately in the editor vs cooks using settings described
below .
= = = = = = = = = = = = = = = = = = = = = = = =
Oodle Texture Settings
- - - - - - - - - - - - - - - - - - - - - -
TextureFormatOodle reads settings from Engine . ini ; they ' re created by default
when not found . Note they are created in per - platform Engine . ini , you can
find them and move them up to DefaultEngine if you want them to be global .
The INI settings block looks like :
[ TextureFormatOodleSettings ]
bDebugColor = False
GlobalLambdaMultiplier = 1.0
The sense of the bools is set so that all - false is default behavior .
bDebugColor :
Fills the encoded texture with a solid color depending on their BCN format .
This is a handy way to see that you are in fact getting Oodle Texture in your game .
It ' s also an easy way to spot textures that aren ' t BCN compressed , since they will not
be solid color . ( for example I found that lots of the Unreal demo content uses " HDR "
which is an uncompressed format , instead of " HDRCompressed " ( BC6 ) ) The color indicates
the actual compressed format output ( BC1 - 7 ) .
GlobalLambdaMultiplier :
Takes all lambdas and scales them by this multiplier , so it affects the global default
and the per - texture lambdas .
It is recommended to leave this at 1.0 until you get near shipping your final game , at
which point you could tweak it to 0.9 or 1.1 to adjust your package size without having
to edit lots of per - texture lambdas .
Oodle Texture lambda
- - - - - - - - - - - - - - - - - - - - - -
The " lambda " parameter is the most important way of controlling Oodle Texture RDO .
" lambda " controls the tradeoff of size vs quality in the Rate Distortion Optimization .
Finding the right lambda settings will be a collaboration between artists and
programmers . Programmers and technical artists may wish to find a global lambda
that meets your goals . Individual texture artists may wish to tweak the lambda
per - texture when needed , but this should be rare - for the most part Oodle Texture
quality is very predictable and good on most textures .
Lambda first of all can be overridden per texture with the " LossyCompressionAmount "
setting . This is a slider in the GUI in the editor that goes from Lowest to Highest .
The default value is " Default " and we recommend leaving that there most of the time .
If the per - texture LossyCompressionAmount is " Default " , that means " inherit from LODGroup " .
The LODGroup gives you a logical group of textures where you can adjust the lambda on that
whole set of textures rather than per - texture .
For example here I have changed " World " LossyCompressionAmount to TLCA_High , and
" WorldNormalMap " to TLCA_Low :
2022-10-07 18:10:19 -04:00
[ GlobalDefaults DeviceProfile ]
2022-05-31 15:49:40 -04:00
@ TextureLODGroups = Group
TextureLODGroups = ( Group = TEXTUREGROUP_World , MinLODSize = 1 , MaxLODSize = 8192 , LODBias = 0 , MinMagFilter = aniso , MipFilter = point , MipGenSettings = TMGS_SimpleAverage , LossyCompressionAmount = TLCA_High )
+ TextureLODGroups = ( Group = TEXTUREGROUP_WorldNormalMap , MinLODSize = 1 , MaxLODSize = 8192 , LODBias = 0 , MinMagFilter = aniso , MipFilter = point , MipGenSettings = TMGS_SimpleAverage , LossyCompressionAmount = TLCA_Low )
+ TextureLODGroups = ( Group = TEXTUREGROUP_WorldSpecular , MinLODSize = 1 , MaxLODSize = 8192 , LODBias = 0 , MinMagFilter = aniso , MipFilter = point , MipGenSettings = TMGS_SimpleAverage )
If the LossyCompressionAmount is not set on the LODGroup ( which is the default ) ,
then it falls through to the global default , which is set in the texture compression
project settings .
At each stage , TLCA_Default means " inherit from parent " .
TLCA_None means disable RDO entirely . We do not recommend this , use TLCA_Lowest
instead when you need very high quality .
Note that the Unreal Editor texture dialog shows live compression results .
When you ' re in the editor and you adjust the LossyCompressionAmount or import a
new texture , it shows the Oodle Texture encoded result in the texture preview .
* * * * * * * * */
DEFINE_LOG_CATEGORY_STATIC ( LogTextureFormatOodle , Log , All ) ;
2022-10-22 12:11:23 -04:00
LLM_DEFINE_TAG ( OodleTexture ) ;
2022-05-31 15:49:40 -04:00
/*****************
*
* Function pointer types for the Oodle Texture functions we need to import :
*
* * * * * * * * * * * * * * * * * * * */
OODEFFUNC typedef OodleTex_Err ( OOEXPLINK t_fp_OodleTex_EncodeBCN_RDO_Ex ) (
OodleTex_BC to_bcn , void * to_bcn_blocks , OO_SINTa num_blocks ,
const OodleTex_Surface * from_surfaces , OO_SINTa num_from_surfaces , OodleTex_PixelFormat from_format ,
const OodleTex_Layout * layout ,
int rdo_lagrange_lambda ,
const OodleTex_RDO_Options * options ,
int num_job_threads , void * jobify_user_ptr ) ;
OODEFFUNC typedef void ( OOEXPLINK t_fp_OodleTex_Plugins_SetAllocators ) (
t_fp_OodleTex_Plugin_MallocAligned * fp_OodleMallocAligned ,
t_fp_OodleTex_Plugin_Free * fp_OodleFree ) ;
OODEFFUNC typedef void ( OOEXPLINK t_fp_OodleTex_Plugins_SetJobSystemAndCount ) (
t_fp_OodleTex_Plugin_RunJob * fp_RunJob ,
t_fp_OodleTex_Plugin_WaitJob * fp_WaitJob ,
int target_parallelism ) ;
OODEFFUNC typedef t_fp_OodleTex_Plugin_Printf * ( OOEXPLINK t_fp_OodleTex_Plugins_SetPrintf ) ( t_fp_OodleTex_Plugin_Printf * fp_rrRawPrintf ) ;
OODEFFUNC typedef t_fp_OodleTex_Plugin_DisplayAssertion * ( OOEXPLINK t_fp_OodleTex_Plugins_SetAssertion ) ( t_fp_OodleTex_Plugin_DisplayAssertion * fp_rrDisplayAssertion ) ;
OODEFFUNC typedef const char * ( OOEXPLINK t_fp_OodleTex_Err_GetName ) ( OodleTex_Err error ) ;
OODEFFUNC typedef const char * ( OOEXPLINK t_fp_OodleTex_PixelFormat_GetName ) ( OodleTex_PixelFormat pf ) ;
OODEFFUNC typedef const char * ( OOEXPLINK t_fp_OodleTex_BC_GetName ) ( OodleTex_BC bcn ) ;
OODEFFUNC typedef const char * ( OOEXPLINK t_fp_OodleTex_RDO_UniversalTiling_GetName ) ( OodleTex_RDO_UniversalTiling tiling ) ;
OODEFFUNC typedef OO_S32 ( OOEXPLINK t_fp_OodleTex_BC_BytesPerBlock ) ( OodleTex_BC bcn ) ;
OODEFFUNC typedef OO_S32 ( OOEXPLINK t_fp_OodleTex_PixelFormat_BytesPerPixel ) ( OodleTex_PixelFormat pf ) ;
2022-06-29 22:43:01 -04:00
OODEFFUNC typedef OodleTex_Err ( OOEXPLINK t_fp_OodleTex_LogVersion ) ( void ) ;
2022-10-22 12:11:23 -04:00
/**
* DebugInfo passed to the Jobify callbacks for tracing
*/
struct FOodleJobDebugInfo
{
FStringView DebugTexturePathName ;
int32 SizeX ;
int32 SizeY ;
OodleTex_BC OodleBCN ;
int RDOLambda ;
} ;
2022-05-31 15:49:40 -04:00
/**
*
* FOodleTextureVTable provides function calls to a specific version of the Oodle Texture dynamic lib
* multiple FOodleTextureVTables may be loaded to support multi - version encoding
*
* */
struct FOodleTextureVTable
{
FName Version ;
void * DynamicLib = nullptr ;
t_fp_OodleTex_EncodeBCN_RDO_Ex * fp_OodleTex_EncodeBCN_RDO_Ex = nullptr ;
t_fp_OodleTex_Plugins_SetAllocators * fp_OodleTex_Plugins_SetAllocators = nullptr ;
t_fp_OodleTex_Plugins_SetJobSystemAndCount * fp_OodleTex_Plugins_SetJobSystemAndCount = nullptr ;
t_fp_OodleTex_Plugins_SetPrintf * fp_OodleTex_Plugins_SetPrintf = nullptr ;
t_fp_OodleTex_Plugins_SetAssertion * fp_OodleTex_Plugins_SetAssertion = nullptr ;
t_fp_OodleTex_Err_GetName * fp_OodleTex_Err_GetName = nullptr ;
t_fp_OodleTex_PixelFormat_GetName * fp_OodleTex_PixelFormat_GetName = nullptr ;
t_fp_OodleTex_BC_GetName * fp_OodleTex_BC_GetName = nullptr ;
t_fp_OodleTex_RDO_UniversalTiling_GetName * fp_OodleTex_RDO_UniversalTiling_GetName = nullptr ;
t_fp_OodleTex_BC_BytesPerBlock * fp_OodleTex_BC_BytesPerBlock = nullptr ;
t_fp_OodleTex_PixelFormat_BytesPerPixel * fp_OodleTex_PixelFormat_BytesPerPixel = nullptr ;
FOodleTextureVTable ( )
{
}
bool LoadDynamicLib ( FString InVersionString )
{
Version = FName ( InVersionString ) ;
// TFO_DLL_PREFIX/SUFFIX is set by the build.cs with the right names for this platform
FString DynamicLibName = FString ( TFO_DLL_PREFIX ) + InVersionString + FString ( TFO_DLL_SUFFIX ) ;
// I want to see this log by default in Cook+Editor , but not in TBW
# ifndef VerboseIfNotEditor
# if WITH_EDITOR
# define VerboseIfNotEditor Display
# else
# define VerboseIfNotEditor Verbose
# endif
# endif
UE_LOG ( LogTextureFormatOodle , VerboseIfNotEditor , TEXT ( " Oodle Texture loading DLL: %s " ) , * DynamicLibName ) ;
DynamicLib = FPlatformProcess : : GetDllHandle ( * DynamicLibName ) ;
if ( DynamicLib = = nullptr )
{
UE_LOG ( LogTextureFormatOodle , Warning , TEXT ( " Oodle Texture %s requested but could not be loaded " ) , * DynamicLibName ) ;
2022-06-29 22:43:01 -04:00
2022-05-31 15:49:40 -04:00
Version = FName ( " invalid " ) ; // so we can't be found
return false ;
}
2022-06-29 22:43:01 -04:00
fp_OodleTex_Err_GetName = ( t_fp_OodleTex_Err_GetName * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_Err_GetName " ) ) ;
check ( fp_OodleTex_Err_GetName ! = nullptr ) ;
fp_OodleTex_Plugins_SetPrintf = ( t_fp_OodleTex_Plugins_SetPrintf * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_Plugins_SetPrintf " ) ) ;
check ( fp_OodleTex_Plugins_SetPrintf ! = nullptr ) ;
t_fp_OodleTex_LogVersion * fp_OodleTex_LogVersion = ( t_fp_OodleTex_LogVersion * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_LogVersion " ) ) ;
check ( fp_OodleTex_LogVersion ! = nullptr ) ;
// make Printf go to NULL for LogVersion :
( * fp_OodleTex_Plugins_SetPrintf ) ( nullptr ) ;
// Get LogVersion so we can get an error code to check the DLL is okay :
OodleTex_Err OodleErr = ( * fp_OodleTex_LogVersion ) ( ) ;
if ( OodleErr ! = OodleTex_Err_OK )
{
const char * OodleErrStr = ( * fp_OodleTex_Err_GetName ) ( OodleErr ) ;
UE_LOG ( LogTextureFormatOodle , Warning , TEXT ( " Oodle Texture %s loaded but failed in LogVersion with error %d=%s " ) , * DynamicLibName ,
( int ) OodleErr , ANSI_TO_TCHAR ( OodleErrStr ) ) ;
Version = FName ( " invalid " ) ; // so we can't be found
return false ;
}
2022-05-31 15:49:40 -04:00
fp_OodleTex_EncodeBCN_RDO_Ex = ( t_fp_OodleTex_EncodeBCN_RDO_Ex * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_EncodeBCN_RDO_Ex " ) ) ;
check ( fp_OodleTex_EncodeBCN_RDO_Ex ! = nullptr ) ;
fp_OodleTex_Plugins_SetAllocators = ( t_fp_OodleTex_Plugins_SetAllocators * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_Plugins_SetAllocators " ) ) ;
check ( fp_OodleTex_Plugins_SetAllocators ! = nullptr ) ;
fp_OodleTex_Plugins_SetJobSystemAndCount = ( t_fp_OodleTex_Plugins_SetJobSystemAndCount * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_Plugins_SetJobSystemAndCount " ) ) ;
check ( fp_OodleTex_Plugins_SetJobSystemAndCount ! = nullptr ) ;
fp_OodleTex_Plugins_SetAssertion = ( t_fp_OodleTex_Plugins_SetAssertion * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_Plugins_SetAssertion " ) ) ;
check ( fp_OodleTex_Plugins_SetAssertion ! = nullptr ) ;
fp_OodleTex_PixelFormat_GetName = ( t_fp_OodleTex_PixelFormat_GetName * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_PixelFormat_GetName " ) ) ;
check ( fp_OodleTex_PixelFormat_GetName ! = nullptr ) ;
fp_OodleTex_BC_GetName = ( t_fp_OodleTex_BC_GetName * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_BC_GetName " ) ) ;
check ( fp_OodleTex_BC_GetName ! = nullptr ) ;
fp_OodleTex_RDO_UniversalTiling_GetName = ( t_fp_OodleTex_RDO_UniversalTiling_GetName * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_RDO_UniversalTiling_GetName " ) ) ;
check ( fp_OodleTex_RDO_UniversalTiling_GetName ! = nullptr ) ;
fp_OodleTex_BC_BytesPerBlock = ( t_fp_OodleTex_BC_BytesPerBlock * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_BC_BytesPerBlock " ) ) ;
check ( fp_OodleTex_BC_BytesPerBlock ! = nullptr ) ;
fp_OodleTex_PixelFormat_BytesPerPixel = ( t_fp_OodleTex_PixelFormat_BytesPerPixel * ) FPlatformProcess : : GetDllExport ( DynamicLib , TEXT ( " OodleTex_PixelFormat_BytesPerPixel " ) ) ;
check ( fp_OodleTex_PixelFormat_BytesPerPixel ! = nullptr ) ;
return true ;
}
~ FOodleTextureVTable ( )
{
if ( DynamicLib )
{
FPlatformProcess : : FreeDllHandle ( DynamicLib ) ;
DynamicLib = nullptr ;
}
}
} ;
class FOodleTextureBuildFunction final : public FTextureBuildFunction
{
const UE : : DerivedData : : FUtf8SharedString & GetName ( ) const final
{
static const UE : : DerivedData : : FUtf8SharedString Name ( UTF8TEXTVIEW ( " OodleTexture " ) ) ;
return Name ;
}
void GetVersion ( UE : : DerivedData : : FBuildVersionBuilder & Builder , ITextureFormat * & OutTextureFormatVersioning ) const final
{
static FGuid Version ( TEXT ( " e6b8884f-923a-44a1-8da1-298fb48865b2 " ) ) ;
Builder < < Version ;
OutTextureFormatVersioning = FModuleManager : : GetModuleChecked < ITextureFormatModule > ( TEXT ( " TextureFormatOodle " ) ) . GetTextureFormat ( ) ;
}
} ;
struct FOodlePixelFormatMapping
{
UE : : DDS : : EDXGIFormat DXGIFormat ;
OodleTex_PixelFormat OodlePF ;
bool bHasAlpha ;
} ;
// mapping from/to UNORM formats; sRGB-ness is handled separately.
// when there are multiple dxgi formats mapping to the same Oodle format, the first one is used
// for conversions from Oodle to DXGI
static FOodlePixelFormatMapping PixelFormatMap [ ] =
{
// dxgi ootex has_alpha
{ UE : : DDS : : EDXGIFormat : : R32G32B32A32_FLOAT , OodleTex_PixelFormat_4_F32_RGBA , true } ,
{ UE : : DDS : : EDXGIFormat : : R32G32B32_FLOAT , OodleTex_PixelFormat_3_F32_RGB , true } ,
{ UE : : DDS : : EDXGIFormat : : R16G16B16A16_FLOAT , OodleTex_PixelFormat_4_F16_RGBA , true } ,
{ UE : : DDS : : EDXGIFormat : : R8G8B8A8_UNORM , OodleTex_PixelFormat_4_U8_RGBA , true } ,
{ UE : : DDS : : EDXGIFormat : : R16G16B16A16_UNORM , OodleTex_PixelFormat_4_U16 , true } ,
{ UE : : DDS : : EDXGIFormat : : R16G16_UNORM , OodleTex_PixelFormat_2_U16 , false } ,
{ UE : : DDS : : EDXGIFormat : : R16G16_SNORM , OodleTex_PixelFormat_2_S16 , false } ,
{ UE : : DDS : : EDXGIFormat : : R8G8_UNORM , OodleTex_PixelFormat_2_U8 , false } ,
{ UE : : DDS : : EDXGIFormat : : R8G8_SNORM , OodleTex_PixelFormat_2_S8 , false } ,
{ UE : : DDS : : EDXGIFormat : : R16_UNORM , OodleTex_PixelFormat_1_U16 , false } ,
{ UE : : DDS : : EDXGIFormat : : R16_SNORM , OodleTex_PixelFormat_1_S16 , false } ,
{ UE : : DDS : : EDXGIFormat : : R8_UNORM , OodleTex_PixelFormat_1_U8 , false } ,
{ UE : : DDS : : EDXGIFormat : : R8_SNORM , OodleTex_PixelFormat_1_S8 , false } ,
{ UE : : DDS : : EDXGIFormat : : B8G8R8A8_UNORM , OodleTex_PixelFormat_4_U8_BGRA , true } ,
{ UE : : DDS : : EDXGIFormat : : B8G8R8X8_UNORM , OodleTex_PixelFormat_4_U8_BGRx , false } ,
} ;
static OodleTex_PixelFormat OodlePFFromDXGIFormat ( UE : : DDS : : EDXGIFormat InFormat )
{
InFormat = UE : : DDS : : DXGIFormatRemoveSRGB ( InFormat ) ;
for ( size_t i = 0 ; i < sizeof ( PixelFormatMap ) / sizeof ( * PixelFormatMap ) ; + + i )
{
if ( PixelFormatMap [ i ] . DXGIFormat = = InFormat )
{
return PixelFormatMap [ i ] . OodlePF ;
}
}
return OodleTex_PixelFormat_Invalid ;
}
// don't need this for all DXGI formats, just the ones we can translate to Oodle Texture formats
static bool DXGIFormatHasAlpha ( UE : : DDS : : EDXGIFormat InFormat )
{
InFormat = UE : : DDS : : DXGIFormatRemoveSRGB ( InFormat ) ;
for ( size_t i = 0 ; i < sizeof ( PixelFormatMap ) / sizeof ( * PixelFormatMap ) ; + + i )
{
if ( PixelFormatMap [ i ] . DXGIFormat = = InFormat )
{
return PixelFormatMap [ i ] . bHasAlpha ;
}
}
// when we don't know the format, the answer doesn't really matter; let's just say "yes"
return true ;
}
static UE : : DDS : : EDXGIFormat DXGIFormatFromOodlePF ( OodleTex_PixelFormat pf )
{
for ( size_t i = 0 ; i < sizeof ( PixelFormatMap ) / sizeof ( * PixelFormatMap ) ; + + i )
{
if ( PixelFormatMap [ i ] . OodlePF = = pf )
{
return PixelFormatMap [ i ] . DXGIFormat ;
}
}
return UE : : DDS : : EDXGIFormat : : UNKNOWN ;
}
struct FOodleBCMapping
{
UE : : DDS : : EDXGIFormat DXGIFormat ;
OodleTex_BC OodleBC ;
} ;
static FOodleBCMapping BCFormatMap [ ] =
{
{ UE : : DDS : : EDXGIFormat : : BC1_UNORM , OodleTex_BC1 } ,
{ UE : : DDS : : EDXGIFormat : : BC1_UNORM , OodleTex_BC1_WithTransparency } ,
{ UE : : DDS : : EDXGIFormat : : BC2_UNORM , OodleTex_BC2 } ,
{ UE : : DDS : : EDXGIFormat : : BC3_UNORM , OodleTex_BC3 } ,
{ UE : : DDS : : EDXGIFormat : : BC4_UNORM , OodleTex_BC4U } ,
{ UE : : DDS : : EDXGIFormat : : BC4_SNORM , OodleTex_BC4S } ,
{ UE : : DDS : : EDXGIFormat : : BC5_UNORM , OodleTex_BC5U } ,
{ UE : : DDS : : EDXGIFormat : : BC5_SNORM , OodleTex_BC5S } ,
{ UE : : DDS : : EDXGIFormat : : BC6H_UF16 , OodleTex_BC6U } ,
{ UE : : DDS : : EDXGIFormat : : BC6H_SF16 , OodleTex_BC6S } ,
{ UE : : DDS : : EDXGIFormat : : BC7_UNORM , OodleTex_BC7RGBA } ,
{ UE : : DDS : : EDXGIFormat : : BC7_UNORM , OodleTex_BC7RGB } ,
} ;
static OodleTex_BC OodleBCFromDXGIFormat ( UE : : DDS : : EDXGIFormat InFormat )
{
InFormat = UE : : DDS : : DXGIFormatRemoveSRGB ( InFormat ) ;
for ( size_t i = 0 ; i < sizeof ( BCFormatMap ) / sizeof ( * BCFormatMap ) ; + + i )
{
if ( BCFormatMap [ i ] . DXGIFormat = = InFormat )
{
return BCFormatMap [ i ] . OodleBC ;
}
}
return OodleTex_BC_Invalid ;
}
static UE : : DDS : : EDXGIFormat DXGIFormatFromOodleBC ( OodleTex_BC InBC )
{
for ( size_t i = 0 ; i < sizeof ( BCFormatMap ) / sizeof ( * BCFormatMap ) ; + + i )
{
if ( BCFormatMap [ i ] . OodleBC = = InBC )
{
return BCFormatMap [ i ] . DXGIFormat ;
}
}
return UE : : DDS : : EDXGIFormat : : UNKNOWN ;
}
static void TFO_Plugins_Init ( ) ;
static void TFO_Plugins_Install ( const FOodleTextureVTable * VTable ) ;
// user data passed to Oodle Jobify system
static int OodleJobifyNumThreads = 0 ;
static void * OodleJobifyUserPointer = nullptr ;
static bool OodleJobifyUseExampleJobify = false ;
// enable this to make the DDC key unique (per build) for testing
//#define DO_FORCE_UNIQUE_DDC_KEY_PER_BUILD
# define ENUSUPPORTED_FORMATS(op) \
op ( DXT1 ) \
op ( DXT3 ) \
op ( DXT5 ) \
op ( DXT5n ) \
op ( AutoDXT ) \
op ( BC4 ) \
op ( BC5 ) \
op ( BC6H ) \
op ( BC7 )
// register support for TFO_ prefixed names like "TFO_DXT1"
# define TEXTURE_FORMAT_PREFIX "TFO_"
# define DECL_FORMAT_NAME(FormatName) static FName GTextureFormatName##FormatName = FName(TEXT(TEXTURE_FORMAT_PREFIX #FormatName));
ENUSUPPORTED_FORMATS ( DECL_FORMAT_NAME ) ;
# undef DECL_FORMAT_NAME
# define DECL_FORMAT_NAME_ENTRY(FormatName) GTextureFormatName##FormatName ,
static FName GSupportedTextureFormatNames [ ] =
{
ENUSUPPORTED_FORMATS ( DECL_FORMAT_NAME_ENTRY )
} ;
# undef DECL_FORMAT_NAME_ENTRY
# undef ENUSUPPORTED_FORMATS
class FTextureFormatOodleConfig
{
public :
struct FLocalDebugConfig
{
FLocalDebugConfig ( ) :
LogVerbosity ( 0 )
{
}
FString DebugDumpFilter ; // dump textures that were encoded
int LogVerbosity ; // 0-2 ; 0=never, 1=large only, 2=always
} ;
FTextureFormatOodleConfig ( ) :
bDebugColor ( false ) ,
GlobalLambdaMultiplier ( 1.f )
{
}
const FLocalDebugConfig & GetLocalDebugConfig ( ) const
{
return LocalDebugConfig ;
}
void ImportFromConfigCache ( )
{
const TCHAR * IniSection = TEXT ( " TextureFormatOodleSettings " ) ;
//
// Note that while this gets called during singleton init for the module,
// the INIs don't exist when we're being run as a texture build worker,
// so all of these GConfig calls do nothing.
//
// Class config variables
GConfig - > GetBool ( IniSection , TEXT ( " bDebugColor " ) , bDebugColor , GEngineIni ) ;
GConfig - > GetString ( IniSection , TEXT ( " DebugDumpFilter " ) , LocalDebugConfig . DebugDumpFilter , GEngineIni ) ;
GConfig - > GetInt ( IniSection , TEXT ( " LogVerbosity " ) , LocalDebugConfig . LogVerbosity , GEngineIni ) ;
GConfig - > GetFloat ( IniSection , TEXT ( " GlobalLambdaMultiplier " ) , GlobalLambdaMultiplier , GEngineIni ) ;
FString CmdLineString ;
if ( FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " -OodleDebugDumpFilter= " ) , CmdLineString ) )
{
UE_LOG ( LogTextureFormatOodle , Display , TEXT ( " Enabling debug dump from command line: -OodleDebugDumpFilter=%s " ) , * CmdLineString ) ;
LocalDebugConfig . DebugDumpFilter = CmdLineString ;
}
if ( FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " OodleDebugColor " ) ) )
{
UE_LOG ( LogTextureFormatOodle , Display , TEXT ( " Enabling debug color encoding from command line (-OodleDebugColor) " ) ) ;
bDebugColor = true ;
}
// sanitize config values :
if ( GlobalLambdaMultiplier < = 0.f )
{
GlobalLambdaMultiplier = 1.f ;
}
UE_LOG ( LogTextureFormatOodle , VerboseIfNotEditor ,
TEXT ( " Oodle Texture TFO init; latest sdk version = %s " ) , TEXT ( OodleTextureVersion )
) ;
# ifdef DO_FORCE_UNIQUE_DDC_KEY_PER_BUILD
UE_LOG ( LogTextureFormatOodle , Display , TEXT ( " Oodle Texture DO_FORCE_UNIQUE_DDC_KEY_PER_BUILD " ) ) ;
# endif
}
FCbObject ExportToCb ( const FTextureBuildSettings & BuildSettings ) const
{
//
// Here we write config stuff to the packet that gets sent to the build
// workers.
//
// This is only for stuff that isn't already part of the build settings.
//
FCbWriter Writer ;
Writer . BeginObject ( " TextureFormatOodleSettings " ) ;
if ( bDebugColor )
{
Writer . AddBool ( " bDebugColor " , bDebugColor ) ;
}
if ( GlobalLambdaMultiplier ! = 1.f )
{
Writer . AddFloat ( " GlobalLambdaMultipler " , GlobalLambdaMultiplier ) ;
}
Writer . EndObject ( ) ;
return Writer . Save ( ) . AsObject ( ) ;
}
2023-09-01 19:25:25 -04:00
void GetOodleCompressParameters ( EPixelFormat * OutCompressedPixelFormat , int * OutRDOLambda , OodleTex_EncodeEffortLevel * OutEffortLevel , bool * bOutDebugColor , OodleTex_RDO_UniversalTiling * OutRDOUniversalTiling , OodleTex_BCNFlags * OutBCNFlags , const struct FTextureBuildSettings & InBuildSettings , bool bHasAlpha ) const
2022-05-31 15:49:40 -04:00
{
2023-04-19 17:29:39 -04:00
//TRACE_CPUPROFILER_EVENT_SCOPE(Texture.GetOodleCompressParameters);
2022-05-31 15:49:40 -04:00
FName TextureFormatName = InBuildSettings . TextureFormatName ;
EPixelFormat CompressedPixelFormat = PF_Unknown ;
if ( TextureFormatName = = GTextureFormatNameDXT1 )
{
CompressedPixelFormat = PF_DXT1 ;
}
else if ( TextureFormatName = = GTextureFormatNameDXT3 )
{
CompressedPixelFormat = PF_DXT3 ;
}
else if ( TextureFormatName = = GTextureFormatNameDXT5 )
{
CompressedPixelFormat = PF_DXT5 ;
}
else if ( TextureFormatName = = GTextureFormatNameAutoDXT )
{
//not all "AutoDXT" comes in here
// some AutoDXT is converted to "DXT1" before it gets here
// (by GetDefaultTextureFormatName if "compress no alpha" is set)
CompressedPixelFormat = bHasAlpha ? PF_DXT5 : PF_DXT1 ;
}
else if ( TextureFormatName = = GTextureFormatNameDXT5n )
{
// Unreal already has global UseDXT5NormalMap config option
// EngineSettings.GetString(TEXT("SystemSettings"), TEXT("Compat.UseDXT5NormalMaps")
// if that is false (which is the default) they use BC5
// so this should be rarely use
// (we prefer BC5 over DXT5n)
CompressedPixelFormat = PF_DXT5 ;
}
else if ( TextureFormatName = = GTextureFormatNameBC4 )
{
CompressedPixelFormat = PF_BC4 ;
}
else if ( TextureFormatName = = GTextureFormatNameBC5 )
{
CompressedPixelFormat = PF_BC5 ;
}
else if ( TextureFormatName = = GTextureFormatNameBC6H )
{
CompressedPixelFormat = PF_BC6H ;
}
else if ( TextureFormatName = = GTextureFormatNameBC7 )
{
CompressedPixelFormat = PF_BC7 ;
}
else
{
UE_LOG ( LogTextureFormatOodle , Fatal ,
TEXT ( " Unsupported TextureFormatName for compression: %s " ) ,
* TextureFormatName . ToString ( )
) ;
}
* OutCompressedPixelFormat = CompressedPixelFormat ;
// Use the DDC2 provided value if it exists.
bool bUseDebugColor = InBuildSettings . FormatConfigOverride . FindView ( " bDebugColor " ) . AsBool ( bDebugColor ) ;
float UseGlobalLambdaMultiplier = InBuildSettings . FormatConfigOverride . FindView ( " GlobalLambdaMultipler " ) . AsFloat ( GlobalLambdaMultiplier ) ;
//
// Convert general build settings in to oodle relevant values.
//
int RDOLambda = InBuildSettings . OodleRDO ;
if ( RDOLambda > 0 & & UseGlobalLambdaMultiplier ! = 1.f )
{
RDOLambda = ( int ) ( UseGlobalLambdaMultiplier * RDOLambda + 0.5f ) ;
// don't let it change to 0 :
if ( RDOLambda < = 0 )
{
RDOLambda = 1 ;
}
}
RDOLambda = FMath : : Clamp ( RDOLambda , 0 , 100 ) ;
// EffortLevel might be set to faster modes for previewing vs cooking or something
// but I don't see people setting that per-Texture or in lod groups or any of that
// it's more about cook mode (fast vs final bake)
// Note InBuildSettings.OodleEncodeEffort is an ETextureEncodeEffort
// we cast directly to OodleTex_EncodeEffortLevel
// the enum values must match exactly
OodleTex_EncodeEffortLevel EffortLevel = ( OodleTex_EncodeEffortLevel ) InBuildSettings . OodleEncodeEffort ;
if ( EffortLevel ! = OodleTex_EncodeEffortLevel_Default & &
EffortLevel ! = OodleTex_EncodeEffortLevel_Low & &
EffortLevel ! = OodleTex_EncodeEffortLevel_Normal & &
EffortLevel ! = OodleTex_EncodeEffortLevel_High )
{
UE_LOG ( LogTextureFormatOodle , Warning , TEXT ( " Invalid effort level passed to texture format oodle: %d is invalid, using default " ) , ( uint32 ) EffortLevel ) ;
EffortLevel = OodleTex_EncodeEffortLevel_Default ;
}
// map Unreal ETextureUniversalTiling to OodleTex_RDO_UniversalTiling
// enum values must match exactly
OodleTex_RDO_UniversalTiling UniversalTiling = ( OodleTex_RDO_UniversalTiling ) InBuildSettings . OodleUniversalTiling ;
if ( UniversalTiling ! = OodleTex_RDO_UniversalTiling_Disable & &
UniversalTiling ! = OodleTex_RDO_UniversalTiling_256KB & &
UniversalTiling ! = OodleTex_RDO_UniversalTiling_64KB )
{
UE_LOG ( LogTextureFormatOodle , Warning , TEXT ( " Invalid universal tiling value passed to texture format oodle: %d is invalid, disabling " ) , ( uint32 ) UniversalTiling ) ;
UniversalTiling = OodleTex_RDO_UniversalTiling_Disable ;
}
2023-09-01 19:25:25 -04:00
OodleTex_BCNFlags BCNFlags = OodleTex_BCNFlags_None ;
if ( InBuildSettings . bOodlePreserveExtremes )
{
BCNFlags = ( OodleTex_BCNFlags ) ( ( uint32 ) BCNFlags | ( uint32 ) OodleTex_BCNFlag_PreserveExtremes_BC3457 ) ;
}
2022-05-31 15:49:40 -04:00
if ( RDOLambda = = 0 )
{
// Universal tiling doesn't make sense without RDO.
UniversalTiling = OodleTex_RDO_UniversalTiling_Disable ;
}
#if 0
// leave this if 0 block for developers to toggle for debugging
// Debug Color any non-RDO
// easy way to make sure you're seeing RDO textures
if ( RDOLambda = = 0 )
{
bUseDebugColor = true ;
}
# endif
* bOutDebugColor = bUseDebugColor ;
* OutRDOLambda = RDOLambda ;
* OutEffortLevel = EffortLevel ;
* OutRDOUniversalTiling = UniversalTiling ;
2023-09-01 19:25:25 -04:00
* OutBCNFlags = BCNFlags ;
2022-05-31 15:49:40 -04:00
}
private :
// the sense of these bools is set so that default behavior = all false
bool bDebugColor ; // color textures by their BCN, for data discovery
// after lambda is set, multiply by this scale factor :
// (multiplies the default and per-Texture overrides)
// is intended to let you do last minute whole-game adjustment
float GlobalLambdaMultiplier ;
FLocalDebugConfig LocalDebugConfig ;
} ;
class FTextureFormatOodle : public ITextureFormat
{
public :
FTextureFormatOodleConfig GlobalFormatConfig ;
TArray < FOodleTextureVTable > VTables ;
FName OodleTextureVersionLatest ;
FName OodleTextureSdkVersionToUseIfNone ;
FTextureFormatOodle ( ) :
OodleTextureVersionLatest ( OodleTextureVersion ) ,
OodleTextureSdkVersionToUseIfNone ( " 2.9.5 " )
{
// OodleTextureSdkVersionToUseIfNone is the fallback version to use if none is in the Texture uasset
// and also no remap pref is set
// it should not be latest; it should be oldest (2.9.5)
// OodleTextureSdkVersionToUseIfNone should never be changed
// if you want to map none to a newer version use config ini option AlternateTextureCompression/OodleTextureSdkVersionToUseIfNone
}
virtual ~ FTextureFormatOodle ( )
{
}
virtual bool AllowParallelBuild ( ) const override
{
return true ;
}
virtual bool SupportsEncodeSpeed ( FName Format ) const override
{
return true ;
}
virtual FName GetEncoderName ( FName Format ) const override
{
2023-02-01 13:24:48 -05:00
static const FName OodleName ( " UE5 Oodle Texture " ) ;
2022-05-31 15:49:40 -04:00
return OodleName ;
}
virtual FCbObject ExportGlobalFormatConfig ( const FTextureBuildSettings & BuildSettings ) const override
{
return GlobalFormatConfig . ExportToCb ( BuildSettings ) ;
}
2022-06-29 22:43:01 -04:00
bool Init ( )
2022-05-31 15:49:40 -04:00
{
TFO_Plugins_Init ( ) ;
// this is done at Singleton init time, the first time GetTextureFormat() is called
GlobalFormatConfig . ImportFromConfigCache ( ) ;
// load ALL Oodle DLL versions we support :
// !! add new versions of Oodle here !!
const TCHAR * OodleTextureVersions [ ] =
{
TEXT ( " 2.9.5 " ) ,
2022-06-30 15:41:13 -04:00
TEXT ( " 2.9.6 " ) ,
2022-10-03 20:37:45 -04:00
TEXT ( " 2.9.7 " ) ,
2023-02-08 17:07:57 -05:00
TEXT ( " 2.9.8 " ) ,
2023-04-19 19:53:52 -04:00
TEXT ( " 2.9.9 " ) ,
TEXT ( " 2.9.10 " )
2022-05-31 15:49:40 -04:00
} ;
const int32 OodleTextureVersionsCount = ( int32 ) ( sizeof ( OodleTextureVersions ) / sizeof ( OodleTextureVersions [ 0 ] ) ) ;
VTables . SetNum ( OodleTextureVersionsCount ) ;
for ( int32 i = 0 ; i < OodleTextureVersionsCount ; i + + )
{
if ( VTables [ i ] . LoadDynamicLib ( FString ( OodleTextureVersions [ i ] ) ) )
{
TFO_Plugins_Install ( & ( VTables [ i ] ) ) ;
}
}
// verify the latest and oldest can be found :
2022-06-29 22:43:01 -04:00
if ( GetOodleTextureVTable ( OodleTextureVersionLatest ) = = nullptr | |
GetOodleTextureVTable ( OodleTextureSdkVersionToUseIfNone ) = = nullptr )
{
UE_LOG ( LogTextureFormatOodle , Warning ,
TEXT ( " Required Oodle Texture versions not available : (%s and %s), will be disabled. " ) ,
* OodleTextureVersionLatest . ToString ( ) ,
* OodleTextureSdkVersionToUseIfNone . ToString ( )
) ;
return false ;
}
return true ;
2022-05-31 15:49:40 -04:00
}
const FOodleTextureVTable * GetOodleTextureVTable ( const FName & InVersion ) const
{
for ( int i = 0 ; i < VTables . Num ( ) ; i + + )
{
if ( VTables [ i ] . Version = = InVersion )
{
return & ( VTables [ i ] ) ;
}
}
return nullptr ;
}
// increment this to invalidate Derived Data Cache to recompress everything
2022-12-06 18:20:21 -05:00
// 16->17 incremented due to incorrect routing of CanAcceptNonF32Source. We are the only thing it's true for.
# define DDC_OODLE_TEXTURE_VERSION 17
2022-05-31 15:49:40 -04:00
virtual uint16 GetVersion ( FName Format , const FTextureBuildSettings * InBuildSettings ) const override
{
// note: InBuildSettings == NULL is used by GetVersionFormatNumbersForIniVersionStrings
// just to get a displayable version number
return DDC_OODLE_TEXTURE_VERSION ;
}
virtual FString GetAlternateTextureFormatPrefix ( ) const override
{
static const FString Prefix ( TEXT ( TEXTURE_FORMAT_PREFIX ) ) ;
return Prefix ;
}
virtual FName GetLatestSdkVersion ( ) const override
{
return OodleTextureVersionLatest ;
}
2023-03-27 16:39:53 -04:00
virtual FString GetDerivedDataKeyString ( const FTextureBuildSettings & InBuildSettings , int32 InMipCount , const FIntVector3 & InMip0Dimensions ) const override
2022-05-31 15:49:40 -04:00
{
// return all parameters that affect our output Texture
// so if any of them change, we rebuild
int RDOLambda ;
OodleTex_EncodeEffortLevel EffortLevel ;
OodleTex_RDO_UniversalTiling RDOUniversalTiling ;
2023-09-01 19:25:25 -04:00
OodleTex_BCNFlags BCNFlags ;
2022-05-31 15:49:40 -04:00
EPixelFormat CompressedPixelFormat ;
bool bDebugColor ;
// @todo Oodle this is not quite the same "bHasAlpha" that Compress will see
// bHasAlpha is used for AutoDXT -> DXT1/5
// we do have Texture.bForceNoAlphaChannel/CompressionNoAlpha but that's not quite what we want
// do go ahead and read bForceNoAlphaChannel/CompressionNoAlpha so that we invalidate DDC when that changes
bool bHasAlpha = ! InBuildSettings . bForceNoAlphaChannel ;
2023-09-01 19:25:25 -04:00
GlobalFormatConfig . GetOodleCompressParameters ( & CompressedPixelFormat , & RDOLambda , & EffortLevel , & bDebugColor , & RDOUniversalTiling , & BCNFlags , InBuildSettings , bHasAlpha ) ;
2022-05-31 15:49:40 -04:00
int icpf = ( int ) CompressedPixelFormat ;
check ( RDOLambda < 256 ) ;
if ( bDebugColor )
{
// Debug Color is solid or check for RDO/ no RDO
// so make different DDC keys :
if ( RDOLambda = = 0 )
RDOLambda = 256 ;
else
RDOLambda = 257 ;
EffortLevel = OodleTex_EncodeEffortLevel_Default ;
}
FString DDCString = FString : : Printf ( TEXT ( " Oodle_CPF%d_L%d_E%d " ) , icpf , ( int ) RDOLambda , ( int ) EffortLevel ) ;
if ( RDOUniversalTiling ! = OodleTex_RDO_UniversalTiling_Disable )
{
DDCString + = FString : : Printf ( TEXT ( " _UT%d " ) , ( int ) RDOUniversalTiling ) ;
}
2023-09-01 19:25:25 -04:00
if ( BCNFlags ! = OodleTex_BCNFlags_None )
{
DDCString + = FString : : Printf ( TEXT ( " _BCNF%ud " ) , ( uint32 ) BCNFlags ) ;
}
2022-05-31 15:49:40 -04:00
// OodleTextureSdkVersion was added ; keys where OodleTextureSdkVersion is none are unchanged
if ( ! InBuildSettings . OodleTextureSdkVersion . IsNone ( ) )
{
DDCString + = TEXT ( " _V " ) ;
// concatenate VersionString without . characters which are illegal in DDC
// version is something like "2.9.5" , we'll add something like "_V295"
2023-04-19 20:28:52 -04:00
FName UseOodleTextureSdkVersion = ValidateOodleTextureSdkVersion ( InBuildSettings . OodleTextureSdkVersion ) ;
FString VersionString = UseOodleTextureSdkVersion . ToString ( ) ;
2022-05-31 15:49:40 -04:00
for ( int32 i = 0 ; i < VersionString . Len ( ) ; i + + )
{
if ( VersionString [ i ] ! = TEXT ( ' . ' ) )
{
DDCString + = VersionString [ i ] ;
}
}
}
# ifdef DO_FORCE_UNIQUE_DDC_KEY_PER_BUILD
DDCString + = TEXT ( __DATE__ ) ;
DDCString + = TEXT ( __TIME__ ) ;
# endif
return DDCString ;
}
2023-04-19 20:28:52 -04:00
FName ValidateOodleTextureSdkVersion ( FName DesiredOodleTextureSdkVersion ) const
{
const FOodleTextureVTable * VTable = GetOodleTextureVTable ( DesiredOodleTextureSdkVersion ) ;
if ( VTable = = nullptr )
{
UE_LOG ( LogTextureFormatOodle , Warning ,
TEXT ( " Unsupported OodleTextureSdkVersion: %s ; instead using: %s " ) ,
* DesiredOodleTextureSdkVersion . ToString ( ) ,
* OodleTextureVersionLatest . ToString ( )
) ;
return OodleTextureVersionLatest ;
}
else
{
return DesiredOodleTextureSdkVersion ;
}
}
2022-05-31 15:49:40 -04:00
virtual void GetSupportedFormats ( TArray < FName > & OutFormats ) const override
{
OutFormats . Append ( GSupportedTextureFormatNames , sizeof ( GSupportedTextureFormatNames ) / sizeof ( GSupportedTextureFormatNames [ 0 ] ) ) ;
}
2022-06-14 19:00:42 -04:00
virtual EPixelFormat GetEncodedPixelFormat ( const FTextureBuildSettings & InBuildSettings , bool bImageHasAlphaChannel ) const
2022-05-31 15:49:40 -04:00
{
int RDOLambda ;
OodleTex_EncodeEffortLevel EffortLevel ;
OodleTex_RDO_UniversalTiling RDOUniversalTiling ;
2023-09-01 19:25:25 -04:00
OodleTex_BCNFlags BCNFlags ;
2022-05-31 15:49:40 -04:00
EPixelFormat CompressedPixelFormat ;
bool bDebugColor ;
2023-09-01 19:25:25 -04:00
GlobalFormatConfig . GetOodleCompressParameters ( & CompressedPixelFormat , & RDOLambda , & EffortLevel , & bDebugColor , & RDOUniversalTiling , & BCNFlags , InBuildSettings , bImageHasAlphaChannel ) ;
2022-05-31 15:49:40 -04:00
return CompressedPixelFormat ;
}
static void DebugDumpDDS ( const FStringView & DebugTexturePathName ,
int32 SizeX , int32 SizeY , int32 Slice , UE : : DDS : : EDXGIFormat DebugFormat , const TCHAR * InOrOut ,
const void * PixelData , size_t PixelDataSize )
{
if ( DebugFormat ! = UE : : DDS : : EDXGIFormat : : UNKNOWN )
{
UE : : DDS : : FDDSFile * DDS = UE : : DDS : : FDDSFile : : CreateEmpty2D ( SizeX , SizeY , 1 , DebugFormat , UE : : DDS : : FDDSFile : : CREATE_FLAG_NONE ) ;
if ( DDS - > Mips [ 0 ] . DataSize ! = PixelDataSize )
{
UE_LOG ( LogTextureFormatOodle , Warning , TEXT ( " DebugDump mip sizes don't match %dx%d: %lld != %lld " ) , SizeX , SizeY , DDS - > Mips [ 0 ] . DataSize , PixelDataSize ) ;
}
size_t MipCopySize = FMath : : Min ( PixelDataSize , ( size_t ) DDS - > Mips [ 0 ] . DataSize ) ;
FMemory : : Memcpy ( DDS - > Mips [ 0 ] . Data , PixelData , MipCopySize ) ;
const TCHAR * FormatStr = DXGIFormatGetName ( DebugFormat ) ;
FString FileName = FString : : Printf ( TEXT ( " %.*s_%s_%dx%d_S%d_%s.dds " ) , DebugTexturePathName . Len ( ) , DebugTexturePathName . GetData ( ) , FormatStr , SizeX , SizeY , Slice , InOrOut ) ;
// Object paths a) can contain slashes as its a path, and we dont want a hierarchy and b) can have random characters we don't want
FileName = FPaths : : MakeValidFileName ( FileName , TEXT ( ' _ ' ) ) ;
// limit file name len
// full path will still likely be longer than _MAX_PATH which breaks many programs
if ( FileName . Len ( ) > = 256 )
{
FileName = FileName . Right ( 255 ) ;
}
FileName = FPaths : : ProjectSavedDir ( ) + TEXT ( " OodleDebugImages/ " ) + FileName ;
FArchive * Ar = IFileManager : : Get ( ) . CreateFileWriter ( * FileName ) ;
if ( Ar ! = nullptr )
{
TArray64 < uint8 > DdsBytes ;
if ( DDS - > WriteDDS ( DdsBytes ) = = UE : : DDS : : EDDSError : : OK )
{
Ar - > Serialize ( DdsBytes . GetData ( ) , DdsBytes . Num ( ) ) ;
}
Ar - > Close ( ) ;
delete Ar ;
}
else
{
UE_LOG ( LogTextureFormatOodle , Error , TEXT ( " Failed to open DDS debug file: %s " ) , * FileName ) ;
}
delete DDS ;
}
}
2022-12-06 18:20:21 -05:00
bool CanAcceptNonF32Source ( FName Format ) const override
2022-05-31 15:49:40 -04:00
{
return true ;
}
2023-04-19 17:29:39 -04:00
virtual bool CompressImage ( const FImage & InImage , const FTextureBuildSettings & InBuildSettings , const FIntVector3 & InMip0Dimensions ,
2023-03-07 15:15:48 -05:00
int32 InMip0NumSlicesNoDepth , int32 InMipIndex , int32 InMipCount , FStringView DebugTexturePathName , const bool bInHasAlpha , FCompressedImage2D & OutImage ) const override
2022-05-31 15:49:40 -04:00
{
2023-02-13 20:10:26 -05:00
TRACE_CPUPROFILER_EVENT_SCOPE ( TFOodle_CompressImage ) ;
2022-05-31 15:49:40 -04:00
check ( InImage . SizeX > 0 ) ;
check ( InImage . SizeY > 0 ) ;
check ( InImage . NumSlices > 0 ) ;
if ( InImage . SizeX > OODLETEX_MAX_SURFACE_DIMENSION | | InImage . SizeY > OODLETEX_MAX_SURFACE_DIMENSION )
{
UE_LOG ( LogTextureFormatOodle , Error ,
TEXT ( " Image larger than OODLETEX_MAX_SURFACE_DIMENSION : %dx%d > %d " ) ,
InImage . SizeX , InImage . SizeY ,
OODLETEX_MAX_SURFACE_DIMENSION
) ;
return false ;
}
2023-04-19 20:28:52 -04:00
FName CompressOodleTextureVersion ;
2022-05-31 15:49:40 -04:00
2023-04-19 20:28:52 -04:00
if ( InBuildSettings . OodleTextureSdkVersion . IsNone ( ) )
2022-05-31 15:49:40 -04:00
{
// legacy texture without version, and no remap is set up in prefs
// use default:
CompressOodleTextureVersion = OodleTextureSdkVersionToUseIfNone ;
}
2023-04-19 20:28:52 -04:00
else
{
CompressOodleTextureVersion = ValidateOodleTextureSdkVersion ( InBuildSettings . OodleTextureSdkVersion ) ;
}
2022-06-29 22:43:01 -04:00
2022-05-31 15:49:40 -04:00
const FOodleTextureVTable * VTable = GetOodleTextureVTable ( CompressOodleTextureVersion ) ;
if ( VTable = = nullptr )
{
UE_LOG ( LogTextureFormatOodle , Error ,
TEXT ( " Unsupported OodleTextureSdkVersion: %s " ) ,
* CompressOodleTextureVersion . ToString ( )
) ;
return false ;
}
2022-06-27 10:44:18 -04:00
// InImage usually comes in as F32 in linear light
2022-05-31 15:49:40 -04:00
// (Unreal has just made mips in that format)
2022-06-27 10:44:18 -04:00
// (when no processing is needed, eg for VT tiles, it can come in different formats now)
2022-05-31 15:49:40 -04:00
// we are run simultaneously on all mips or VT tiles
// bHasAlpha = DetectAlphaChannel , scans the A's for non-opaque , in in CompressMipChain
// used by AutoDXT
bool bHasAlpha = bInHasAlpha ;
int RDOLambda ;
OodleTex_EncodeEffortLevel EffortLevel ;
OodleTex_RDO_UniversalTiling RDOUniversalTiling ;
2023-09-01 19:25:25 -04:00
OodleTex_BCNFlags BCNFlags ;
2022-05-31 15:49:40 -04:00
EPixelFormat CompressedPixelFormat ;
bool bDebugColor ;
2023-09-01 19:25:25 -04:00
GlobalFormatConfig . GetOodleCompressParameters ( & CompressedPixelFormat , & RDOLambda , & EffortLevel , & bDebugColor , & RDOUniversalTiling , & BCNFlags , InBuildSettings , bHasAlpha ) ;
2022-05-31 15:49:40 -04:00
OodleTex_BC OodleBCN = OodleTex_BC_Invalid ;
if ( CompressedPixelFormat = = PF_DXT1 ) { OodleBCN = OodleTex_BC1_WithTransparency ; bHasAlpha = false ; }
else if ( CompressedPixelFormat = = PF_DXT3 ) { OodleBCN = OodleTex_BC2 ; }
else if ( CompressedPixelFormat = = PF_DXT5 ) { OodleBCN = OodleTex_BC3 ; }
else if ( CompressedPixelFormat = = PF_BC4 ) { OodleBCN = OodleTex_BC4U ; }
else if ( CompressedPixelFormat = = PF_BC5 ) { OodleBCN = OodleTex_BC5U ; }
else if ( CompressedPixelFormat = = PF_BC6H ) { OodleBCN = OodleTex_BC6U ; }
else if ( CompressedPixelFormat = = PF_BC7 ) { OodleBCN = OodleTex_BC7RGBA ; }
else
{
UE_LOG ( LogTextureFormatOodle , Fatal ,
TEXT ( " Unsupported CompressedPixelFormat for compression: %d " ) ,
( int ) CompressedPixelFormat
) ;
}
FName TextureFormatName = InBuildSettings . TextureFormatName ;
bool bIsVT = InBuildSettings . bVirtualStreamable ;
// LogVerbosity 0 : never
// LogVerbosity 1 : only large mips
// LogVerbosity 2 : always
bool bIsLargeMip = InImage . SizeX > = 1024 | | InImage . SizeY > = 1024 ;
if ( GlobalFormatConfig . GetLocalDebugConfig ( ) . LogVerbosity > = 2 | | ( GlobalFormatConfig . GetLocalDebugConfig ( ) . LogVerbosity & & bIsLargeMip ) )
{
2022-06-30 15:41:13 -04:00
UE_LOG ( LogTextureFormatOodle , Display , TEXT ( " Oodle%s %s encode %i x %i x %i to format %s%s (%s) lambda=%i effort=%i " ) ,
2022-06-07 10:44:04 -04:00
* CompressOodleTextureVersion . ToString ( ) ,
2022-05-31 15:49:40 -04:00
RDOLambda ? TEXT ( " RDO " ) : TEXT ( " non-RDO " ) , InImage . SizeX , InImage . SizeY , InImage . NumSlices ,
* TextureFormatName . ToString ( ) ,
bIsVT ? TEXT ( " VT " ) : TEXT ( " " ) ,
* FString ( ( VTable - > fp_OodleTex_BC_GetName ) ( OodleBCN ) ) ,
RDOLambda , ( int ) EffortLevel ) ;
}
// input Image comes in as F32 in linear light
// for BC6 we just leave that alone
// for all others we must convert to 8 bit to get Gamma correction
2022-09-14 14:55:36 -04:00
// Dest Gamma is either sRGB or Linear, never Pow22
EGammaSpace Gamma = InBuildSettings . GetDestGammaSpace ( ) ;
2022-05-31 15:49:40 -04:00
if ( ( OodleBCN = = OodleTex_BC4U | | OodleBCN = = OodleTex_BC5U | | OodleBCN = = OodleTex_BC6U ) & &
Gamma ! = EGammaSpace : : Linear )
{
// BC4,5,6 should always be encoded to linear gamma
UE_LOG ( LogTextureFormatOodle , Display , TEXT ( " Image format %s (Oodle %s) encoded with non-Linear Gamma " ) , \
* TextureFormatName . ToString ( ) , * FString ( ( VTable - > fp_OodleTex_BC_GetName ) ( OodleBCN ) ) ) ;
}
ERawImageFormat : : Type ImageFormat ;
OodleTex_PixelFormat OodlePF ;
if ( OodleBCN = = OodleTex_BC6U )
{
ImageFormat = ERawImageFormat : : RGBA32F ;
OodlePF = OodleTex_PixelFormat_4_F32_RGBA ;
// BC6 is assumed to be a linear-light HDR Image by default
// use OodleTex_BCNFlag_BC6_NonRGBData if it is some other kind of data
Gamma = EGammaSpace : : Linear ;
// TFO just passes the F32 to Oodle
// FImageCore::SanitizeFloat16AndSetAlphaOpaqueForBC6H is not needed here
// Oodle will convert the F32 to F16 and also clamp in [0,F16_max] (no negatives, no +inf)
}
else if ( ( OodleBCN = = OodleTex_BC4U | | OodleBCN = = OodleTex_BC5U ) & &
Gamma = = EGammaSpace : : Linear & &
! bDebugColor )
{
2023-04-06 14:21:41 -04:00
// for BC4/5 use 16-bit integer U16 pixels :
2022-05-31 15:49:40 -04:00
// BC4/5 should always have linear gamma
2023-04-06 14:21:41 -04:00
2022-06-22 20:21:16 -04:00
// input image format now can be BGRA8 (used to always be RGBA32F)
// but to maintain matching output with previous RGBA32F format, still do convert to RGBA16
// ideally should pass BGRA8 directly to Oodle, but that changes output bits
2023-04-20 12:32:12 -04:00
/*
// -> need DDC key bump for this
if ( InImage . Format = = ERawImageFormat : : BGRA8 & & InBuildSettings . bUseNewMipFilter )
{
ImageFormat = ERawImageFormat : : BGRA8 ;
OodlePF = OodleTex_PixelFormat_4_U8_BGRA ;
}
*/
if ( InImage . Format = = ERawImageFormat : : RGBA16 )
{
ImageFormat = ERawImageFormat : : RGBA16 ;
OodlePF = OodleTex_PixelFormat_4_U16 ;
}
else
{
ImageFormat = ERawImageFormat : : BGRA8 ; // <- not really, 2_U16 in disguise!
OodlePF = OodleTex_PixelFormat_2_U16 ;
}
2022-05-31 15:49:40 -04:00
}
else
{
ImageFormat = ERawImageFormat : : BGRA8 ;
// if bHasAlpha is off due to bForceNoAlphaChannel, we could still have transparent pixels
// force Oodle to read input alpha as opaque :
OodlePF = bHasAlpha ? OodleTex_PixelFormat_4_U8_BGRA : OodleTex_PixelFormat_4_U8_BGRx ;
}
2023-04-20 12:32:12 -04:00
bool bIsSpecial2U16 = ( OodlePF = = OodleTex_PixelFormat_2_U16 ) ;
2022-05-31 15:49:40 -04:00
bool bNeedsImageCopy = ImageFormat ! = InImage . Format | |
Gamma ! = InImage . GammaSpace | |
( CompressedPixelFormat = = PF_DXT5 & & TextureFormatName = = GTextureFormatNameDXT5n ) | |
2023-04-20 12:32:12 -04:00
bDebugColor | | bIsSpecial2U16 ;
2022-05-31 15:49:40 -04:00
FImage ImageCopy ;
if ( bNeedsImageCopy )
{
2023-04-19 17:29:39 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( Texture . Oodle_FormatChange ) ;
2022-10-22 12:11:23 -04:00
//not sure if we should bill this alloc to OodleTexture or the calling context (TextureCompressor)
//we are freeing the previous Image alloc to replace it with a changed format
//LLM_SCOPE_BYTAG(OodleTexture);
2023-04-20 12:32:12 -04:00
if ( bIsSpecial2U16 )
{
ImageCopy . Init ( InImage . SizeX , InImage . SizeY , InImage . NumSlices , ImageFormat , EGammaSpace : : Linear ) ;
FImageCore : : CopyImageTo2U16 ( InImage , ImageCopy ) ;
// everything past this point only uses OodlePF
// so the fact that ImageCopy.Format is wrong is okay
}
else
{
InImage . CopyTo ( ImageCopy , ImageFormat , Gamma ) ;
}
2023-04-19 17:29:39 -04:00
2022-06-27 10:44:18 -04:00
// after we copy the image, we can free the source
// can reduce peak mem use to do so immediately
// (source is usually/often F32 RGBA (when not VT) so quite fat)
2023-04-19 17:29:39 -04:00
2023-04-20 12:32:12 -04:00
// InImage.RawData.Empty();
2023-04-19 20:28:52 -04:00
// -> no longer possible because Hashing Source is on a thread
// needs a refcount on the source Image to make that work again
2023-04-20 12:32:12 -04:00
// @todo Oodle : peak memory use is a lot higher if we don't free the float temp image here
2023-04-19 17:29:39 -04:00
2022-05-31 15:49:40 -04:00
}
const FImage & Image = bNeedsImageCopy ? ImageCopy : InImage ;
// verify OodlePF matches Image :
check ( Image . GetBytesPerPixel ( ) = = ( VTable - > fp_OodleTex_PixelFormat_BytesPerPixel ) ( OodlePF ) ) ;
SSIZE_T InRowStrideBytes = Image . GetBytesPerPixel ( ) * Image . SizeX ;
SSIZE_T InBytesPerSlice = InRowStrideBytes * Image . SizeY ;
uint8 * ImageBasePtr = ( uint8 * ) & ( Image . RawData [ 0 ] ) ;
SSIZE_T InBytesTotal = InBytesPerSlice * Image . NumSlices ;
check ( Image . RawData . Num ( ) = = InBytesTotal ) ;
if ( CompressedPixelFormat = = PF_DXT5 & &
TextureFormatName = = GTextureFormatNameDXT5n )
{
// this is only used if Compat.UseDXT5NormalMaps
// normal map comes in as RG , B&A can be ignored
// in the optional use BC5 path, only the source RG pass through
// normal was in RG , move to GA
if ( OodlePF = = OodleTex_PixelFormat_4_U8_BGRx )
{
OodlePF = OodleTex_PixelFormat_4_U8_BGRA ;
}
check ( OodlePF = = OodleTex_PixelFormat_4_U8_BGRA ) ;
for ( uint8 * ptr = ImageBasePtr ; ptr < ( ImageBasePtr + InBytesTotal ) ; ptr + = 4 )
{
// ptr is BGRA
ptr [ 3 ] = ptr [ 2 ] ;
// match what NVTT does, it sets R=FF and B=0
// NVTT also sets weight=0 for B so output B is undefined
// but output R is preserved at 1.f
ptr [ 0 ] = 0xFF ;
ptr [ 2 ] = 0 ;
}
}
if ( bDebugColor )
{
// fill Texture with solid color based on which BCN we would have output
// checker board if RDO
// lets you visually identify BCN textures in the Editor or game
const bool IsRDO = RDOLambda ! = 0 ;
constexpr SSIZE_T CheckerSizeBits = 4 ;
const SSIZE_T NumSlices = Image . NumSlices ;
const SSIZE_T SizeY = Image . SizeY ;
const SSIZE_T SizeX = Image . SizeX ;
// use fast encoding settings for debug color :
RDOLambda = 0 ;
EffortLevel = OodleTex_EncodeEffortLevel_Low ;
if ( OodlePF = = OodleTex_PixelFormat_4_F32_RGBA )
{
//BC6 = purple
check ( OodleBCN = = OodleTex_BC6U ) ;
if ( IsRDO )
{
// debug color with checker board
for ( SSIZE_T Slice = 0 ; Slice < NumSlices ; Slice + + )
{
float * LineBase = ( float * ) ( ImageBasePtr + InBytesPerSlice * Slice ) ;
for ( SSIZE_T Y = 0 ; Y < SizeY ; Y + + , LineBase + = ( InRowStrideBytes / sizeof ( float ) ) )
{
for ( SSIZE_T X = 0 ; X < SizeX ; X + + )
{
float * Pixel = LineBase + 4 * X ;
SSIZE_T GridOnY = Y & ( 1 < < CheckerSizeBits ) ;
SSIZE_T GridOnX = X & ( 1 < < CheckerSizeBits ) ;
SSIZE_T GridOn = GridOnX ^ GridOnY ;
if ( GridOn )
{
Pixel [ 0 ] = 0.5f ;
Pixel [ 1 ] = 0 ;
Pixel [ 2 ] = 0.8f ;
Pixel [ 3 ] = 1.f ;
}
else
{
Pixel [ 0 ] = 1.0f ;
Pixel [ 1 ] = 1.0f ;
Pixel [ 2 ] = 1.0f ;
Pixel [ 3 ] = 1.f ;
}
}
} // each line
} // each slice
} // end if RDO
else
{
for ( float * ptr = ( float * ) ImageBasePtr ; ptr < ( float * ) ( ImageBasePtr + InBytesTotal ) ; ptr + = 4 )
{
// RGBA floats
ptr [ 0 ] = 0.5f ;
ptr [ 1 ] = 0 ;
ptr [ 2 ] = 0.8f ;
ptr [ 3 ] = 1.f ;
}
}
}
else
{
check ( OodlePF = = OodleTex_PixelFormat_4_U8_BGRA | | OodlePF = = OodleTex_PixelFormat_4_U8_BGRx ) ;
// BGRA in bytes
uint32 DebugColor = 0xFF000000U ; // alpha
switch ( OodleBCN )
{
case OodleTex_BC1_WithTransparency :
case OodleTex_BC1 : DebugColor | = 0xFF0000 ; break ; // BC1 = red
case OodleTex_BC2 : DebugColor | = 0x008000 ; break ; // BC2/3 = greens
case OodleTex_BC3 : DebugColor | = 0x00FF00 ; break ;
case OodleTex_BC4S :
case OodleTex_BC4U : DebugColor | = 0x808000 ; break ; // BC4/5 = yellows
case OodleTex_BC5S :
case OodleTex_BC5U : DebugColor | = 0xFFFF00 ; break ;
case OodleTex_BC7RGB : DebugColor | = 0x8080FF ; break ; // BC7 = blues
case OodleTex_BC7RGBA : DebugColor | = 0x0000FF ; break ;
default : break ;
}
if ( IsRDO )
{
// debug color with checker board
for ( SSIZE_T Slice = 0 ; Slice < NumSlices ; Slice + + )
{
uint8 * LineBase = ImageBasePtr + InBytesPerSlice * Slice ; ;
for ( SSIZE_T Y = 0 ; Y < SizeY ; Y + + , LineBase + = InRowStrideBytes )
{
for ( SSIZE_T X = 0 ; X < SizeX ; X + + )
{
uint8 * Pixel = LineBase + 4 * X ;
SSIZE_T GridOnY = Y & ( 1 < < CheckerSizeBits ) ;
SSIZE_T GridOnX = X & ( 1 < < CheckerSizeBits ) ;
SSIZE_T GridOn = GridOnX ^ GridOnY ;
if ( GridOn )
{
* ( ( uint32 * ) Pixel ) = DebugColor ;
}
else
{
* ( ( uint32 * ) Pixel ) = 0xFF000000 ;
}
}
} // each line
} // each slice
} // end if RDO
else
{
for ( uint8 * ptr = ImageBasePtr ; ptr < ( ImageBasePtr + InBytesTotal ) ; ptr + = 4 )
{
* ( ( uint32 * ) ptr ) = DebugColor ;
}
}
}
}
/*
UE_LOG ( LogTextureFormatOodle , Display , TEXT ( " bHasAlpha=%d OodlePF=%d=%s OodleBCN=%d=%s " ) ,
( int ) bHasAlpha ,
( int ) OodlePF ,
* FString ( ( VTable - > fp_OodleTex_PixelFormat_GetName ) ( OodlePF ) ) ,
( int ) OodleBCN ,
* FString ( ( VTable - > fp_OodleTex_BC_GetName ) ( OodleBCN ) )
) ;
*/
int BytesPerBlock = ( VTable - > fp_OodleTex_BC_BytesPerBlock ) ( OodleBCN ) ;
int NumBlocksX = ( Image . SizeX + 3 ) / 4 ;
int NumBlocksY = ( Image . SizeY + 3 ) / 4 ;
OO_SINTa NumBlocksPerSlice = NumBlocksX * NumBlocksY ;
OO_SINTa OutBytesPerSlice = NumBlocksPerSlice * BytesPerBlock ;
OO_SINTa OutBytesTotal = OutBytesPerSlice * Image . NumSlices ;
OutImage . PixelFormat = CompressedPixelFormat ;
// old behavior :
//OutImage.SizeX = NumBlocksX*4;
//OutImage.SizeY = NumBlocksY*4;
OutImage . SizeX = Image . SizeX ;
OutImage . SizeY = Image . SizeY ;
// note: cubes come in as 6 slices and go out as 1
OutImage . SizeZ = ( InBuildSettings . bVolume | | InBuildSettings . bTextureArray ) ? Image . NumSlices : 1 ;
2023-04-19 17:29:39 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( Texture . Oodle_Alloc ) ;
2022-05-31 15:49:40 -04:00
OutImage . RawData . AddUninitialized ( OutBytesTotal ) ;
2023-04-19 17:29:39 -04:00
}
2022-05-31 15:49:40 -04:00
UE_LOG ( LogTextureFormatOodle , Verbose , TEXT ( " TFO out size=%dx%d stride=%d total=%d " ) ,
OutImage . SizeX , OutImage . SizeY ,
NumBlocksX * BytesPerBlock ,
OutBytesTotal ) ;
uint8 * OutBlocksBasePtr = ( uint8 * ) & OutImage . RawData [ 0 ] ;
// Check if we want to dump the before/after images out.
bool bImageDump = false ;
if ( GlobalFormatConfig . GetLocalDebugConfig ( ) . DebugDumpFilter . Len ( ) & &
! bDebugColor & & // don't bother if they are solid color
( Image . SizeX > = 4 | | Image . SizeY > = 4 ) ) // don't bother if they are too small.
{
if ( FWildcardString : : IsMatchSubstring ( * GlobalFormatConfig . GetLocalDebugConfig ( ) . DebugDumpFilter , DebugTexturePathName . GetData ( ) , DebugTexturePathName . GetData ( ) + DebugTexturePathName . Len ( ) , ESearchCase : : IgnoreCase ) )
{
bImageDump = true ;
}
}
int CurJobifyNumThreads = OodleJobifyNumThreads ;
void * CurJobifyUserPointer = OodleJobifyUserPointer ;
2022-10-22 12:11:23 -04:00
// CurJobifyUserPointer is not used in the Unreal TaskGraph
// so we can use it to pass a pointer to a debug info struct:
FOodleJobDebugInfo LocalDebugInfo ;
LocalDebugInfo . DebugTexturePathName = DebugTexturePathName ;
LocalDebugInfo . SizeX = Image . SizeX ;
LocalDebugInfo . SizeY = Image . SizeY ;
LocalDebugInfo . OodleBCN = OodleBCN ;
LocalDebugInfo . RDOLambda = RDOLambda ;
if ( ! OodleJobifyUseExampleJobify )
{
check ( CurJobifyUserPointer = = nullptr ) ;
CurJobifyUserPointer = ( void * ) & LocalDebugInfo ;
}
2022-05-31 15:49:40 -04:00
// Have a target number of pixels per job, and clamp the num threads
// to avoid generating lots of tiny jobs
const int64 TargetPixelsPerJobThread = 128 * 128 ;
const int TargetJobThreads = ( int ) ( int64 ( Image . SizeX ) * Image . SizeY / TargetPixelsPerJobThread ) ;
if ( bIsVT | | TargetJobThreads < = 1 )
{
// VT runs its tiles in a ParallelFor on the TaskGraph
// We internally also make tasks on TaskGraph
// it should not deadlock to do tasks from tasks, but it's not handled well
// parallelism at the VT tile level only works better
// disable our own internal threading for VT tiles :
CurJobifyNumThreads = OODLETEX_JOBS_DISABLE ;
CurJobifyUserPointer = nullptr ;
}
else
{
CurJobifyNumThreads = FMath : : Min ( CurJobifyNumThreads , TargetJobThreads ) ;
}
// encode each slice
std : : atomic_bool bCompressionSucceeded { true } ;
ParallelFor (
TEXT ( " ProcessSlice " ) ,
Image . NumSlices , 1 ,
[ & ] ( int32 Slice )
{
// Early out in case another task failed
if ( ! bCompressionSucceeded )
{
return ;
}
OodleTex_Surface InSurf = { } ;
InSurf . pixels = ImageBasePtr + Slice * InBytesPerSlice ;
InSurf . rowStrideBytes = InRowStrideBytes ;
InSurf . width = Image . SizeX ;
InSurf . height = Image . SizeY ;
uint8 * OutSlicePtr = OutBlocksBasePtr + Slice * OutBytesPerSlice ;
2022-10-13 11:39:06 -04:00
// verify that the surface memory ranges given to us by Unreal are valid before calling into Oodle Texture:
CheckMemoryIsReadable ( InSurf . pixels , InBytesPerSlice ) ;
CheckMemoryIsReadable ( OutSlicePtr , OutBytesPerSlice ) ;
2022-05-31 15:49:40 -04:00
OodleTex_RDO_Options OodleOptions = { } ;
OodleOptions . effort = EffortLevel ;
OodleOptions . metric = OodleTex_RDO_ErrorMetric_Default ;
2023-09-01 19:25:25 -04:00
OodleOptions . bcn_flags = BCNFlags ;
2022-05-31 15:49:40 -04:00
OodleOptions . universal_tiling = RDOUniversalTiling ;
if ( bImageDump )
{
DebugDumpDDS ( DebugTexturePathName , InSurf . width , InSurf . height , Slice ,
DXGIFormatFromOodlePF ( OodlePF ) , TEXT ( " IN " ) ,
InSurf . pixels , InBytesPerSlice ) ;
}
{
2023-02-13 20:10:26 -05:00
TRACE_CPUPROFILER_EVENT_SCOPE ( TFOodle_EncodeBCN ) ;
2022-05-31 15:49:40 -04:00
// if RDOLambda == 0, does non-RDO encode :
OodleTex_Err OodleErr = ( VTable - > fp_OodleTex_EncodeBCN_RDO_Ex ) ( OodleBCN , OutSlicePtr , NumBlocksPerSlice ,
& InSurf , 1 , OodlePF , NULL , RDOLambda ,
& OodleOptions , CurJobifyNumThreads , CurJobifyUserPointer ) ;
if ( OodleErr ! = OodleTex_Err_OK )
{
const char * OodleErrStr = ( VTable - > fp_OodleTex_Err_GetName ) ( OodleErr ) ;
UE_LOG ( LogTextureFormatOodle , Warning , TEXT ( " Oodle Texture encode failed!? %d=%s " ) , ( int ) OodleErr , ANSI_TO_TCHAR ( OodleErrStr ) ) ;
bCompressionSucceeded = false ;
return ;
}
}
if ( bImageDump )
{
// put RDO lambda on the debug name :
FStringView DebugNameOut = DebugTexturePathName ;
FString Scratch ;
if ( RDOLambda ! = 0 )
{
Scratch = FString : : Printf ( TEXT ( " %.*s_RDO%d " ) , DebugTexturePathName . Len ( ) , DebugTexturePathName . GetData ( ) , RDOLambda ) ;
DebugNameOut = Scratch ;
}
DebugDumpDDS ( DebugNameOut , InSurf . width , InSurf . height , Slice ,
DXGIFormatFromOodleBC ( OodleBCN ) , TEXT ( " OUT " ) ,
OutSlicePtr , OutBytesPerSlice ) ;
}
} ,
EParallelForFlags : : Unbalanced
) ;
return bCompressionSucceeded ;
}
2022-10-13 11:39:06 -04:00
// noinline so we see it on the call stack :
FORCENOINLINE void CheckMemoryIsReadable ( const void * Buffer , int64 Size ) const
{
check ( Size > 0 ) ;
const uint8 * Start = ( const uint8 * ) Buffer ;
// volatile to ensure it's not optimized out
volatile uint8 Byte ;
Byte = Start [ 0 ] ;
Byte = Start [ Size - 1 ] ;
}
2022-05-31 15:49:40 -04:00
} ;
//===============================================================
// TFO_ plugins to Oodle to run Oodle system services in Unreal
// @todo Oodle : factor this out and share for Core & Net some day
static OO_U64 OODLE_CALLBACK TFO_RunJob ( t_fp_Oodle_Job * JobFunction , void * JobData , OO_U64 * Dependencies , int NumDependencies , void * UserPtr )
{
using namespace UE : : Tasks ;
2022-10-22 12:11:23 -04:00
// DebugInfo to inspect:
const FOodleJobDebugInfo * DebugInfo = ( FOodleJobDebugInfo * ) UserPtr ;
2022-05-31 15:49:40 -04:00
FTask * Task = new FTask ;
Task - > Launch (
2023-02-13 20:10:26 -05:00
TEXT ( " TFOodle_EncodeBCN_Task " ) ,
2022-05-31 15:49:40 -04:00
[ JobFunction , JobData ]
{
2023-02-13 20:10:26 -05:00
TRACE_CPUPROFILER_EVENT_SCOPE ( TFOodle_EncodeBCN_Task ) ;
2022-05-31 15:49:40 -04:00
JobFunction ( JobData ) ;
} ,
TArrayView < FTask * > { reinterpret_cast < FTask * * > ( Dependencies ) , NumDependencies } ,
// Use Background priority so we don't use Foreground time in the Editor
// @todo maybe it's better to inherit so the outer caller can tell us if we are high priority or not?
IsInGameThread ( ) ? ETaskPriority : : Normal : ETaskPriority : : BackgroundNormal
) ;
return reinterpret_cast < OO_U64 > ( Task ) ;
}
static void OODLE_CALLBACK TFO_WaitJob ( OO_U64 JobHandle , void * UserPtr )
{
using namespace UE : : Tasks ;
2022-10-22 12:11:23 -04:00
// DebugInfo to inspect:
const FOodleJobDebugInfo * DebugInfo = ( FOodleJobDebugInfo * ) UserPtr ;
2022-05-31 15:49:40 -04:00
2023-02-13 20:10:26 -05:00
TRACE_CPUPROFILER_EVENT_SCOPE ( TFOodle_WaitJob ) ;
2022-05-31 15:49:40 -04:00
FTask * Task = reinterpret_cast < FTask * > ( JobHandle ) ;
Task - > Wait ( ) ;
delete Task ;
}
static OO_BOOL OODLE_CALLBACK TFO_OodleAssert ( const char * file , const int line , const char * function , const char * message )
{
// AssertFailed exits the program
FDebug : : AssertFailed ( message , file , line ) ;
// return true to issue a debug break at the execution site
return true ;
}
static void OODLE_CALLBACK TFO_OodleLog ( int verboseLevel , const char * file , int line , const char * InFormat , . . . )
{
ANSICHAR TempString [ 1024 ] ;
va_list Args ;
va_start ( Args , InFormat ) ;
FCStringAnsi : : GetVarArgs ( TempString , UE_ARRAY_COUNT ( TempString ) , InFormat , Args ) ;
va_end ( Args ) ;
UE_LOG_CLINKAGE ( LogTextureFormatOodle , Display , TEXT ( " Oodle Log: %s " ) , ANSI_TO_TCHAR ( TempString ) ) ;
}
static void * OODLE_CALLBACK TFO_OodleMallocAligned ( OO_SINTa Bytes , OO_S32 Alignment )
{
2022-10-22 12:11:23 -04:00
LLM_SCOPE_BYTAG ( OodleTexture ) ;
2022-05-31 15:49:40 -04:00
void * Ret = FMemory : : Malloc ( Bytes , Alignment ) ;
check ( Ret ! = nullptr ) ;
return Ret ;
}
static void OODLE_CALLBACK TFO_OodleFree ( void * ptr )
{
FMemory : : Free ( ptr ) ;
}
// Init is only done once for all versions
static void TFO_Plugins_Init ( )
{
// Install Unreal system plugins to OodleTex
// this should only be done once
// and should be done before any other Oodle calls
// plugins to Core/Tex/Net are independent
GConfig - > GetBool ( TEXT ( " TextureFormatOodleSettings " ) , TEXT ( " UseOodleExampleJobify " ) , OodleJobifyUseExampleJobify , GEngineIni ) ;
if ( OodleJobifyUseExampleJobify )
{
UE_LOG ( LogTextureFormatOodle , Display , TEXT ( " Using Oodle Example Jobify " ) ) ;
// Optionally we allow for users to use the internal Oodle job system instead of
// thunking to the Unreal task graph.
OodleJobifyUserPointer = example_jobify_init ( ) ;
OodleJobifyNumThreads = example_jobify_target_parallelism ;
}
else
{
OodleJobifyUserPointer = nullptr ;
OodleJobifyNumThreads = FMath : : Max ( 1 , FTaskGraphInterface : : Get ( ) . GetNumWorkerThreads ( ) ) ;
}
}
// Install is done for each Oodle DLL
static void TFO_Plugins_Install ( const FOodleTextureVTable * VTable )
{
if ( OodleJobifyUseExampleJobify )
{
( VTable - > fp_OodleTex_Plugins_SetJobSystemAndCount ) ( example_jobify_run_job_fptr , example_jobify_wait_job_fptr , example_jobify_target_parallelism ) ;
}
else
{
( VTable - > fp_OodleTex_Plugins_SetJobSystemAndCount ) ( TFO_RunJob , TFO_WaitJob , OodleJobifyNumThreads ) ;
}
( VTable - > fp_OodleTex_Plugins_SetAssertion ) ( TFO_OodleAssert ) ;
( VTable - > fp_OodleTex_Plugins_SetPrintf ) ( TFO_OodleLog ) ;
( VTable - > fp_OodleTex_Plugins_SetAllocators ) ( TFO_OodleMallocAligned , TFO_OodleFree ) ;
}
//=========================================================================
class FTextureFormatOodleModule : public ITextureFormatModule
{
public :
2022-06-29 22:43:01 -04:00
ITextureFormat * TextureFormat = nullptr ;
2022-05-31 15:49:40 -04:00
FTextureFormatOodleModule ( ) { }
virtual ~ FTextureFormatOodleModule ( )
{
2022-06-29 22:43:01 -04:00
if ( TextureFormat )
{
delete TextureFormat ;
TextureFormat = nullptr ;
}
2022-05-31 15:49:40 -04:00
}
virtual void StartupModule ( ) override
{
}
virtual bool CanCallGetTextureFormats ( ) override { return false ; }
virtual ITextureFormat * GetTextureFormat ( )
{
2022-06-29 22:43:01 -04:00
// this is called several times during normal init
2022-05-31 15:49:40 -04:00
2022-06-29 22:43:01 -04:00
auto MakeTextureFormat = [ & ] ( )
2022-05-31 15:49:40 -04:00
{
2022-06-29 22:43:01 -04:00
check ( TextureFormat = = nullptr ) ;
2022-05-31 15:49:40 -04:00
FTextureFormatOodle * ptr = new FTextureFormatOodle ( ) ;
2022-06-29 22:43:01 -04:00
if ( ptr - > Init ( ) )
{
TextureFormat = ptr ;
}
else
{
UE_LOG ( LogTextureFormatOodle , Warning , TEXT ( " Oodle Texture Init failed, not installed " ) ) ;
delete ptr ;
2022-05-31 15:49:40 -04:00
}
2022-06-29 22:43:01 -04:00
} ;
UE_CALL_ONCE ( MakeTextureFormat ) ;
return TextureFormat ;
2022-05-31 15:49:40 -04:00
}
static inline UE : : DerivedData : : TBuildFunctionFactory < FOodleTextureBuildFunction > BuildFunctionFactory ;
} ;
IMPLEMENT_MODULE ( FTextureFormatOodleModule , TextureFormatOodle ) ;