Files
UnrealEngineUWP/Engine/Plugins/Developer/TextureFormatOodle/Source/Private/TextureFormatOodle.cpp
Dan Thompson 936df062d1 allegedly a temporary fix for native mac/linux editor while I set up a proper path.
[CL 16779185 by Dan Thompson in ue5-main branch]
2021-06-24 17:57:05 -04:00

1313 lines
46 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TextureFormatOodlePCH.h"
#include "CoreMinimal.h"
#include "ImageCore.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/TaskGraphInterfaces.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "Misc/FileHelper.h"
#include "Serialization/CompactBinary.h"
#include "Serialization/CompactBinaryWriter.h"
#include "TextureBuildFunction.h"
#include "DerivedDataBuildFunctionFactory.h"
#include "oodle2tex.h"
// Alternate job system - can set UseOodleExampleJobify in engine ini to enable.
#include "../Jobify/example_jobify.h"
/**********
Oodle Texture can do both RDO (rate distortion optimization) and non-RDO encoding to BC1-7
by default this plugin enables RDO with a moderate quality level (lambda=30).
Set DefaultRDOLambda=40 for smaller compressed sizes.
quality can be controlled at three levels :
1. Each Texture can choose an individual setting with LossyCompressionAmount
2. If that is "Default", the setting is looked up in the LODGroup
3. If that is not set, the global default is used (DefaultRDOLambda)
Lambda up to 40 usually produces very high quality. The need to for per-Texture adjustment should be rare
and is mainly for when textures are used in unusual ways (not just diffuse color or normal maps).
Oodle Texture can encode BC1-7. It does not currently encode ASTC or other mobile formats.
=====================
TextureFormatOodle handles formats OODLE_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="OODLE_"
When this is enabled, the formats like "DXT1" are renamed to "OODLE_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]
bForceAllBC23ToBC7=False
bForceRDOOff_Editor=False
bForceRDOOff_NoEditor=False
bDebugColor=False
DefaultRDOLambda=30
GlobalLambdaMultiplier=1.0
CompressEffortLevel_NoEditor=High
CompressEffortLevel_Editor=Normal
The sense of the bools is set so that all-false is default behavior.
TextureFormatOodle by default tries to exactly reproduce the legacy behavior of
TextureFormatDXT+TextureFormatISPC , just with Oodle Texture RDO encoding.
The behavior of the options is :
CompressEffortLevel_NoEditor :
CompressEffortLevel_Editor :
Sets how much time Oodle should spend finding good results. Values are
from the OodleTex_EncodeEffortLevel enum - Default, Low, Normal, High.
bForceAllBC23ToBC7 :
If true, all BC2 & 3 (DXT3 and DXT5) is encoded to BC7 instead.
On DX11 games, BC7 usualy has higher quality and takes the same space in memory as BC3.
For example in Unreal, "AutoDXT" selects DXT1 (BC1) for opaque textures and DXT5 (BC3)
for textures with alpha. If you turn on this option, the BC3 will change to BC7, so
"AutoDXT" will now select BC1 for opaque and BC7 for alpha.
It is off by default to make default behavior match the old encoders.
bForceRDOOff_Editor :
bForceRDOOff_NoEditor :
Force Oodle Texture to use non-RDO encoding. This sets lambda to 0 for all encodes.
(this is different than setting DefaultRDOLambda=0 because it also applies to textures
that have per-texture lambda overrides set). When EditorOnly data is present _Editor
is used to facilitate iteration times.
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).
DefaultRDOLambda :
global default lambda value that is used if no per-texture lambda is set.
(see next section)
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 :
[/Script/Engine.TextureLODSettings]
@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 "DefaultRDOLambda" from our
INI block. eg. for "WorldSpecular" above it would use the DefaultRDOLambda setting.
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 (if bEnableInEditor is true).
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);
// user data passed to Oodle Jobify system
static int OodleJobifyNumThreads = 0;
static void *OodleJobifyUserPointer = nullptr;
// 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 OODLE_ prefixed names like "OODLE_DXT1"
#define DECL_FORMAT_NAME(FormatName) static FName GTextureFormatName##FormatName = FName(TEXT("OODLE_" #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 FImageDumper
{
public:
FImageDumper()
: ImageWrapperModule(nullptr)
, ImageFormat(EImageFormat::Invalid)
, RGBFormat(ERGBFormat::Invalid)
, BytesPerPixel(0)
, BitDepth(0)
, Extension(nullptr)
{ }
bool Initialize(const ERawImageFormat::Type InImageFormat)
{
ImageWrapper.Reset();
switch (InImageFormat)
{
case ERawImageFormat::RGBA32F:
ImageFormat = EImageFormat::EXR;
RGBFormat = ERGBFormat::RGBAF;
BytesPerPixel = 16;
BitDepth = 32;
Extension = TEXT(".exr");
break;
case ERawImageFormat::RGBA16:
ImageFormat = EImageFormat::PNG;
RGBFormat = ERGBFormat::RGBA;
BytesPerPixel = 8;
BitDepth = 16;
Extension = TEXT(".png");
break;
case ERawImageFormat::BGRA8:
ImageFormat = EImageFormat::PNG;
RGBFormat = ERGBFormat::BGRA;
BytesPerPixel = 4;
BitDepth = 8;
Extension = TEXT(".png");
break;
default:
return false;
}
if (!ImageWrapperModule)
{
ImageWrapperModule = FModuleManager::GetModulePtr<IImageWrapperModule>("ImageWrapper");
}
if (ImageWrapperModule)
{
ImageWrapper = ImageWrapperModule->CreateImageWrapper(ImageFormat);
}
return ImageWrapper.IsValid();
}
bool DumpImage(const void* InRawData, int64 InRawSize, const int32 InWidth, const int32 InHeight, const int32 InSlice, const int32 InRDOLambda, const OodleTex_BC InOodleBCN)
{
check(InRawData);
check(InWidth > 0);
check(InHeight > 0);
check(InRawSize == (int64)BytesPerPixel * InWidth * InHeight);
if (!ImageWrapper.IsValid() || !ImageWrapper->SetRaw(InRawData, InRawSize, InWidth, InHeight, RGBFormat, BitDepth))
{
return false;
}
FMD5 MD5;
FString ImageHash = MD5.HashBytes(static_cast<const uint8*>(InRawData), InRawSize);
FString OodleBCName(OodleTex_BC_GetName(InOodleBCN));
FString Filename = FString::Printf(TEXT("%s.w%d.h%d.s%d.rdo%d.%s%s"), *ImageHash, InWidth, InHeight, InSlice, InRDOLambda, *OodleBCName, Extension);
// put in subdir by format and size
// helps reduce the count of files in a single dir, which stresses the file system
FString Subdir = FString::Printf(TEXT("%s.w%d.h%d"), *OodleBCName, InWidth, InHeight);
FString Path = FPaths::ProjectSavedDir() / TEXT("Oodle") / TEXT("DebugDump") / Subdir / Filename;
//UE_LOG(LogTextureFormatOodle, Display, TEXT("DumpImage : %s"), *Filename );
const TArray64<uint8>& CompressedImage = ImageWrapper->GetCompressed((int32)EImageCompressionQuality::Uncompressed);
return FFileHelper::SaveArrayToFile(CompressedImage, *Path);
}
private:
IImageWrapperModule* ImageWrapperModule;
TSharedPtr<IImageWrapper> ImageWrapper;
EImageFormat ImageFormat;
ERGBFormat RGBFormat;
int32 BytesPerPixel;
int32 BitDepth;
const TCHAR* Extension;
};
class FTextureFormatOodleConfig
{
public:
struct FLocalDebugConfig
{
FLocalDebugConfig() :
bDebugDump(false),
LogVerbosity(0)
{
}
bool bDebugDump; // dump textures that were encoded
int LogVerbosity; // 0-2 ; 0=never, 1=large only, 2=always
};
FTextureFormatOodleConfig() :
bForceAllBC23ToBC7(false),
bForceRDOOff_NoEditor(true),
bForceRDOOff_Editor(true),
CompressEffortLevel_NoEditor(OodleTex_EncodeEffortLevel_High),
CompressEffortLevel_Editor(OodleTex_EncodeEffortLevel_Normal),
RDOUniversalTiling(OodleTex_RDO_UniversalTiling_Disable),
bDebugColor(false),
DefaultRDOLambda(OodleTex_RDOLagrangeLambda_Default),
GlobalLambdaMultiplier(1.f)
{
}
const FLocalDebugConfig& GetLocalDebugConfig() const
{
return LocalDebugConfig;
}
static const TCHAR* EffortLevelToString(OodleTex_EncodeEffortLevel InEffortLevel)
{
switch (InEffortLevel)
{
case OodleTex_EncodeEffortLevel_Default:
{
return TEXT("Default");
}
case OodleTex_EncodeEffortLevel_Low:
{
return TEXT("Low");
}
case OodleTex_EncodeEffortLevel_Normal:
{
return TEXT("Normal");
}
case OodleTex_EncodeEffortLevel_High:
{
return TEXT("High");
}
}
UE_LOG(LogTextureFormatOodle, Warning, TEXT("Invalid Oodle effort level passed to ToString: %d -- returning \"Default\""), InEffortLevel);
return TEXT("Default");
}
static bool EffortLevelFromConfig(const TCHAR* InSection, const TCHAR* InKey, OodleTex_EncodeEffortLevel& OutEffortLevel)
{
FString EffortLevel_String;
if (GConfig->GetString(InSection, InKey, EffortLevel_String, GEngineIni))
{
OodleTex_EncodeEffortLevel Result;
if (EffortLevel_String.Compare(TEXT("Default"), ESearchCase::IgnoreCase) == 0)
{
Result = OodleTex_EncodeEffortLevel_Default;
}
else if (EffortLevel_String.Compare(TEXT("Low"), ESearchCase::IgnoreCase) == 0)
{
Result = OodleTex_EncodeEffortLevel_Low;
}
else if (EffortLevel_String.Compare(TEXT("Normal"), ESearchCase::IgnoreCase) == 0)
{
Result = OodleTex_EncodeEffortLevel_Normal;
}
else if (EffortLevel_String.Compare(TEXT("High"), ESearchCase::IgnoreCase) == 0)
{
Result = OodleTex_EncodeEffortLevel_High;
}
else
{
UE_LOG(LogTextureFormatOodle, Warning, TEXT("Invalid %s specified: %s, using defaults."), InKey, *EffortLevel_String);
return false;
}
OutEffortLevel = Result;
return true;
}
return false;
}
void ImportFromConfigCache()
{
const TCHAR* IniSection = TEXT("TextureFormatOodleSettings");
#if 0
// Check that our config section exists, and if not, init with defaults
// this will add it to your per-user "Saved" Engine.ini
// eg: C:\UnrealEngine\Games\oodletest\Saved\Config\Windows\Engine.ini
// you can then move or copy it to DefaultEngine.ini if you like
if (!GConfig->DoesSectionExist(OODLETEXTURE_INI_SECTION, GEngineIni))
{
GConfig->SetBool(OODLETEXTURE_INI_SECTION, TEXT("bForceAllBC23ToBC7"), bForceAllBC23ToBC7, GEngineIni);
GConfig->SetBool(OODLETEXTURE_INI_SECTION, TEXT("bForceRDOOff_NoEditor"), bForceRDOOff_NoEditor, GEngineIni);
GConfig->SetBool(OODLETEXTURE_INI_SECTION, TEXT("bForceRDOOff_Editor"), bForceRDOOff_Editor, GEngineIni);
GConfig->SetString(OODLETEXTURE_INI_SECTION, TEXT("CompressEffortLevel_NoEditor"), EffortLevelToString(CompressEffortLevel_NoEditor), GEngineIni);
GConfig->SetString(OODLETEXTURE_INI_SECTION, TEXT("CompressEffortLevel_Editor"), EffortLevelToString(CompressEffortLevel_Editor), GEngineIni);
GConfig->SetBool(OODLETEXTURE_INI_SECTION, TEXT("bDebugColor"), bDebugColor, GEngineIni);
GConfig->SetBool(OODLETEXTURE_INI_SECTION, TEXT("bDebugDump"), bDebugDump, GEngineIni);
GConfig->SetInt(OODLETEXTURE_INI_SECTION, TEXT("LogVerbosity"), LogVerbosity, GEngineIni);
GConfig->SetFloat(OODLETEXTURE_INI_SECTION, TEXT("GlobalLambdaMultiplier"), GlobalLambdaMultiplier, GEngineIni);
GConfig->SetInt(OODLETEXTURE_INI_SECTION, TEXT("DefaultRDOLambda"), DefaultRDOLambda, GEngineIni);
GConfig->SetInt(OODLETEXTURE_INI_SECTION, TEXT("RDOUniversalTiling"), (int32)RDOUniversalTiling, GEngineIni);
GConfig->Flush(false);
}
#endif
//
// 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("bForceAllBC23ToBC7"), bForceAllBC23ToBC7, GEngineIni);
GConfig->GetBool(IniSection, TEXT("bForceRDOOff_NoEditor"), bForceRDOOff_NoEditor, GEngineIni);
GConfig->GetBool(IniSection, TEXT("bForceRDOOff_Editor"), bForceRDOOff_Editor, GEngineIni);
GConfig->GetBool(IniSection, TEXT("bDebugColor"), bDebugColor, GEngineIni);
GConfig->GetBool(IniSection, TEXT("bDebugDump"), LocalDebugConfig.bDebugDump, GEngineIni);
GConfig->GetInt(IniSection, TEXT("LogVerbosity"), LocalDebugConfig.LogVerbosity, GEngineIni);
GConfig->GetFloat(IniSection, TEXT("GlobalLambdaMultiplier"), GlobalLambdaMultiplier, GEngineIni);
GConfig->GetInt(IniSection, TEXT("DefaultRDOLambda"), DefaultRDOLambda, GEngineIni);
GConfig->GetInt(IniSection, TEXT("RDOUniversalTiling"), (int32&)RDOUniversalTiling, GEngineIni);
EffortLevelFromConfig(IniSection, TEXT("CompressEffortLevel_NoEditor"), CompressEffortLevel_NoEditor);
EffortLevelFromConfig(IniSection, TEXT("CompressEffortLevel_Editor"), CompressEffortLevel_Editor);
// sanitize config values :
DefaultRDOLambda = FMath::Clamp(DefaultRDOLambda,0,100);
if ( GlobalLambdaMultiplier <= 0.f )
{
GlobalLambdaMultiplier = 1.f;
}
if (RDOUniversalTiling < 0 ||
RDOUniversalTiling > OodleTex_RDO_UniversalTiling_Max)
{
UE_LOG(LogTextureFormatOodle, Warning, TEXT("Invalid RDOUniversalTiling value supplied: %d, using 0 (Disabled)"), RDOUniversalTiling);
RDOUniversalTiling = OodleTex_RDO_UniversalTiling_Disable;
}
UE_LOG(LogTextureFormatOodle, Display, TEXT("Oodle Texture %s init {cook RDO %s %s, Editor RDO %s %s} with DefaultRDOLambda=%d, RDOUniversalTiling=%d"),
TEXT(OodleTextureVersion),
bForceRDOOff_NoEditor ? TEXT("Off") : TEXT("On"), EffortLevelToString(CompressEffortLevel_NoEditor),
bForceRDOOff_Editor ? TEXT("Off") : TEXT("On"), EffortLevelToString(CompressEffortLevel_Editor),
DefaultRDOLambda,
(int32)RDOUniversalTiling
);
#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
{
int RDOLambda;
OodleTex_EncodeEffortLevel EffortLevel;
OodleTex_RDO_UniversalTiling LocalRDOUniversalTiling;
EPixelFormat CompressedPixelFormat;
bool bLocalDebugColor;
const bool bHasAlpha = !BuildSettings.bForceNoAlphaChannel;
GetOodleCompressParameters(&CompressedPixelFormat,&RDOLambda,&EffortLevel, &bLocalDebugColor,&LocalRDOUniversalTiling,BuildSettings,bHasAlpha);
FCbWriter Writer;
Writer.BeginObject("TextureFormatOodleSettings");
if ((BuildSettings.TextureFormatName == GTextureFormatNameDXT3) ||
(BuildSettings.TextureFormatName == GTextureFormatNameDXT5) ||
(BuildSettings.TextureFormatName == GTextureFormatNameDXT5n) ||
(BuildSettings.TextureFormatName == GTextureFormatNameAutoDXT) )
{
Writer.AddBool("bForceAllBC23ToBC7", bForceAllBC23ToBC7);
}
Writer.AddInteger("RDOLambda", RDOLambda);
Writer.AddInteger("EffortLevel", EffortLevel);
Writer.AddBool("bDebugColor", bLocalDebugColor);
// Don't write if default to maintain compat with any outstanding texture
// build workers.
if (LocalRDOUniversalTiling != OodleTex_RDO_UniversalTiling_Disable)
{
Writer.AddInteger("RDOUniversalTiling", LocalRDOUniversalTiling);
}
Writer.EndObject();
return Writer.Save().AsObject();
}
void GetOodleCompressParameters(EPixelFormat * OutCompressedPixelFormat,int * OutRDOLambda, OodleTex_EncodeEffortLevel * OutEffortLevel, bool * bOutDebugColor, OodleTex_RDO_UniversalTiling* OutRDOUniversalTiling, const struct FTextureBuildSettings& InBuildSettings, bool bHasAlpha) const
{
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)
// if you set bForceAllBC23ToBC7, the DXT5 will change to BC7
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()
);
}
// BC7 is just always better than BC2 & BC3
// so anything that came through as BC23, force to BC7 : (AutoDXT-alpha and Normals)
// Note that we are using the value from the FormatConfigOverride if we have one, otherwise the default will be the value we have locally
if ( InBuildSettings.FormatConfigOverride.FindView("bForceAllBC23ToBC7").AsBool(bForceAllBC23ToBC7) &&
(CompressedPixelFormat == PF_DXT3 || CompressedPixelFormat == PF_DXT5 ) )
{
CompressedPixelFormat = PF_BC7;
}
*OutCompressedPixelFormat = CompressedPixelFormat;
if (InBuildSettings.FormatConfigOverride)
{
// If we have an explicit format config, then use it directly
FCbFieldView FieldView;
// RDOUniversalTiling is only set if not default, so fall back to defaults if it doesn't exist.
// Note that in this case, we're a texture build worker, so our defaults are not
// changed by GConfig.
*OutRDOUniversalTiling = (OodleTex_RDO_UniversalTiling)InBuildSettings.FormatConfigOverride.FindView("RDOUniversalTiling").AsUInt32(RDOUniversalTiling);
FieldView = InBuildSettings.FormatConfigOverride.FindView("RDOLambda");
checkf(FieldView.HasValue(), TEXT("Missing RDOLambda key from FormatConfigOverride"));
*OutRDOLambda = FieldView.AsInt32();
checkf(!FieldView.HasError(), TEXT("Failed to parse RDOLambda value from FormatConfigOverride"));
FieldView = InBuildSettings.FormatConfigOverride.FindView("EffortLevel");
checkf(FieldView.HasValue(), TEXT("Missing EffortLevel key from FormatConfigOverride"));
*OutEffortLevel = (OodleTex_EncodeEffortLevel)FieldView.AsUInt32();
checkf(!FieldView.HasError(), TEXT("Failed to parse EffortLevel value from FormatConfigOverride"));
FieldView = InBuildSettings.FormatConfigOverride.FindView("bDebugColor");
checkf(FieldView.HasValue(), TEXT("Missing bDebugColor key from FormatConfigOverride"));
*bOutDebugColor = FieldView.AsBool();
checkf(!FieldView.HasError(), TEXT("Failed to parse bDebugColor value from FormatConfigOverride"));
return;
}
*bOutDebugColor = bDebugColor;
int RDOLambda = -1;
// LossyCompressionAmount for per-Texture override
// also inherits from LODGroup if not set per-Texture
int32 LossyCompressionAmount = InBuildSettings.LossyCompressionAmount;
switch (LossyCompressionAmount)
{
default:
case TLCA_Default: break; // "Default"
case TLCA_None: RDOLambda = 0; break; // "No lossy compression"
case TLCA_Lowest: RDOLambda = 5; break; // "Lowest (Best Image quality, largest filesize)"
case TLCA_Low: RDOLambda = 15; break; // "Low"
case TLCA_Medium: RDOLambda = 30; break; // "Medium"
case TLCA_High: RDOLambda = 40; break; // "High"
case TLCA_Highest: RDOLambda = 60; break; // "Highest (Worst Image quality, smallest filesize)"
}
if ( RDOLambda == -1 )
{
// not set
// get global default from config
RDOLambda = DefaultRDOLambda;
}
if ( RDOLambda > 0 && GlobalLambdaMultiplier != 1.f )
{
RDOLambda = (int)( GlobalLambdaMultiplier * RDOLambda + 0.5f );
// don't let it change to 0 :
if ( RDOLambda <= 0 )
{
RDOLambda = 1;
}
}
RDOLambda = FMath::Clamp(RDOLambda,0,100);
// ini option to force non-RDO encoding :
bool bForceRDOOff = InBuildSettings.bHasEditorOnlyData ? bForceRDOOff_Editor : bForceRDOOff_NoEditor;
if ( bForceRDOOff )
{
RDOLambda = 0;
}
// "Normal" is medium quality/speed
OodleTex_EncodeEffortLevel EffortLevel = InBuildSettings.bHasEditorOnlyData ? CompressEffortLevel_Editor : CompressEffortLevel_NoEditor;
// 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)
*OutRDOLambda = RDOLambda;
*OutEffortLevel = EffortLevel;
*OutRDOUniversalTiling = RDOUniversalTiling;
}
private:
// the sense of these bools is set so that default behavior = all false
bool bForceAllBC23ToBC7; // change BC2 & 3 (aka DXT3 and DXT5) to BC7
bool bForceRDOOff_NoEditor; // use Oodle Texture but without RDO ; for debugging/testing , use LossyCompresionAmount to do this per-Texture
bool bForceRDOOff_Editor; // bForceRDOOff in Editor
OodleTex_EncodeEffortLevel CompressEffortLevel_NoEditor; // how much time to spend encoding to get higher quality
OodleTex_EncodeEffortLevel CompressEffortLevel_Editor; // CompressEffortLevel in Editor
OodleTex_RDO_UniversalTiling RDOUniversalTiling; // whether to use universal tiling, and at what block size.
bool bDebugColor; // color textures by their BCN, for data discovery
// if no lambda is set on Texture or lodgroup, fall through to this global default :
int DefaultRDOLambda;
// 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;
FTextureFormatOodle()
{
}
virtual ~FTextureFormatOodle()
{
}
virtual bool AllowParallelBuild() const override
{
return true;
}
virtual bool UsesTaskGraph() const override
{
// @todo the UsesTaskGraph function should go away entirely from ITextureFormat
// it's only being used by VirtualTextureDataBuilder
// it's none of his business
// if that's a deadlock, there should be a better solution
// like let me ask if I'm being called from a ParallelFor
return true;
}
virtual FCbObject ExportGlobalFormatConfig(const FTextureBuildSettings& BuildSettings) const override
{
return GlobalFormatConfig.ExportToCb(BuildSettings);
}
void Init()
{
// this is done at Singleton init time, the first time GetTextureFormat() is called
GlobalFormatConfig.ImportFromConfigCache();
}
// increment this to invalidate Derived Data Cache to recompress everything
#define DDC_OODLE_TEXTURE_VERSION 12
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 GetDerivedDataKeyString(const FTextureBuildSettings& InBuildSettings) const override
{
// 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;
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;
GlobalFormatConfig.GetOodleCompressParameters(&CompressedPixelFormat,&RDOLambda,&EffortLevel,&bDebugColor,&RDOUniversalTiling,InBuildSettings,bHasAlpha);
// store the actual lambda in DDC key (rather than "LossyCompressionAmount")
// that way any changes in how LossyCompressionAmount maps to lambda get rebuilt
int icpf = (int)CompressedPixelFormat;
check(RDOLambda<256);
if (bDebugColor)
{
RDOLambda = 256;
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);
}
#ifdef DO_FORCE_UNIQUE_DDC_KEY_PER_BUILD
DDCString += TEXT(__DATE__);
DDCString += TEXT(__TIME__);
#endif
return DDCString;
}
virtual void GetSupportedFormats(TArray<FName>& OutFormats) const override
{
OutFormats.Append(GSupportedTextureFormatNames, sizeof(GSupportedTextureFormatNames)/sizeof(GSupportedTextureFormatNames[0]) );
}
virtual FTextureFormatCompressorCaps GetFormatCapabilities() const override
{
return FTextureFormatCompressorCaps(); // Default capabilities.
}
virtual EPixelFormat GetPixelFormatForImage(const FTextureBuildSettings& InBuildSettings, const struct FImage& Image, bool bHasAlpha) const override
{
int RDOLambda;
OodleTex_EncodeEffortLevel EffortLevel;
OodleTex_RDO_UniversalTiling RDOUniversalTiling;
EPixelFormat CompressedPixelFormat;
bool bDebugColor;
GlobalFormatConfig.GetOodleCompressParameters(&CompressedPixelFormat,&RDOLambda,&EffortLevel,&bDebugColor,&RDOUniversalTiling,InBuildSettings,bHasAlpha);
return CompressedPixelFormat;
}
virtual bool CompressImage(const FImage& InImage, const FTextureBuildSettings& InBuildSettings, const bool bInHasAlpha, FCompressedImage2D& OutImage) const override
{
check(InImage.SizeX > 0);
check(InImage.SizeY > 0);
check(InImage.NumSlices > 0);
// InImage always comes in as F32 in linear light
// (Unreal has just made mips in that format)
// we are run simultaneously on all mips using the GLargeThreadPool
// 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;
EPixelFormat CompressedPixelFormat;
bool bDebugColor;
GlobalFormatConfig.GetOodleCompressParameters(&CompressedPixelFormat,&RDOLambda,&EffortLevel,&bDebugColor,&RDOUniversalTiling,InBuildSettings,bHasAlpha);
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;
// 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) )
{
UE_LOG(LogTextureFormatOodle, Display, TEXT("%s encode %i x %i x %i to format %s (Oodle %s) lambda=%i effort=%i "), \
RDOLambda ? TEXT("RDO") : TEXT("non-RDO"), InImage.SizeX, InImage.SizeY, InImage.NumSlices,
*TextureFormatName.ToString(), *FString(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
// because Unreal only does Gamma correction on the 8 bit conversion
// (this loses precision for BC4,5 which would like 16 bit input)
EGammaSpace Gamma = InBuildSettings.GetGammaSpace();
// note in unreal if Gamma == Pow22 due to legacy Gamma,
// we still want to encode to sRGB
// (CopyTo does that even without this change, but let's make it explicit)
if ( Gamma == EGammaSpace::Pow22 ) Gamma = EGammaSpace::sRGB;
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(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;
}
else if ((OodleBCN == OodleTex_BC4U || OodleBCN == OodleTex_BC5U) &&
Gamma == EGammaSpace::Linear &&
!bDebugColor)
{
// for BC4/5 use 16-bit :
// BC4/5 should always have linear gamma
// @todo we only need 1 or 2 channel 16-bit, not all 4; use our own converter
// or just let our encoder take F32 input?
ImageFormat = ERawImageFormat::RGBA16;
OodlePF = OodleTex_PixelFormat_4_U16;
}
else
{
ImageFormat = ERawImageFormat::BGRA8;
// if requested format was DXT1
// Unreal assumes that will not encode any alpha channel in the source
// (Unreal's "compress without alpha" just selects DXT1)
// the legacy NVTT behavior for DXT1 was to always encode opaque pixels
// for DXT1 we use BC1_WithTransparency which will preserve the input A transparency bit
// so we need to force the A's to be 255 coming into Oodle
// so for DXT1 we force bHasAlpha = false
// force Oodle to ignore input alpha :
OodlePF = bHasAlpha ? OodleTex_PixelFormat_4_U8_BGRA : OodleTex_PixelFormat_4_U8_BGRx;
}
bool bNeedsImageCopy = ImageFormat != InImage.Format ||
Gamma != InImage.GammaSpace ||
(CompressedPixelFormat == PF_DXT5 && TextureFormatName == GTextureFormatNameDXT5n) ||
bDebugColor;
FImage ImageCopy;
if (bNeedsImageCopy)
{
InImage.CopyTo(ImageCopy, ImageFormat, Gamma);
}
const FImage& Image = bNeedsImageCopy ? ImageCopy : InImage;
// verify OodlePF matches Image :
check( Image.GetBytesPerPixel() == OodleTex_PixelFormat_BytesPerPixel(OodlePF) );
OodleTex_Surface InSurf = { 0 };
InSurf.width = Image.SizeX;
InSurf.height = Image.SizeY;
InSurf.pixels = 0;
InSurf.rowStrideBytes = Image.GetBytesPerPixel() * Image.SizeX;
SSIZE_T InBytesPerSlice = InSurf.rowStrideBytes * 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
// lets you visually identify BCN textures in the Editor or game
// 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);
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;
}
for(uint8 * ptr = ImageBasePtr; ptr < (ImageBasePtr + InBytesTotal); ptr += 4)
{
*((uint32 *)ptr) = DebugColor;
}
}
}
int BytesPerBlock = 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;
OutImage.SizeX = NumBlocksX*4;
OutImage.SizeY = NumBlocksY*4;
// note: cubes come in as 6 slices and go out as 1
OutImage.SizeZ = (InBuildSettings.bVolume || InBuildSettings.bTextureArray) ? Image.NumSlices : 1;
OutImage.RawData.AddUninitialized(OutBytesTotal);
uint8 * OutBlocksBasePtr = (uint8 *) &OutImage.RawData[0];
FImageDumper ImageDumper;
bool bImageDump = false;
if (GlobalFormatConfig.GetLocalDebugConfig().bDebugDump && !bDebugColor)
{
if (ImageDumper.Initialize(ImageFormat))
{
bImageDump = true;
}
else
{
UE_LOG(LogTextureFormatOodle, Display, TEXT("Oodle Texture debug dump initialization failed!"));
}
}
int CurJobifyNumThreads = OodleJobifyNumThreads;
void* CurJobifyUserPointer = OodleJobifyUserPointer;
// @todo check its safe to do TaskGraph waits from inside TaskGraph threads?
// see also VirtualTextureDataBuilder.cpp UsesTaskGraph
//const bool bVTDisableInternalThreading = false; // false = DO use internal threads on VT
const bool bVTDisableInternalThreading = true; // true = DO NOT use internal threads on VT
bool bIsVT = InBuildSettings.bVirtualStreamable;
if (bIsVT && bVTDisableInternalThreading)
{
// VT runs its tiles in a ParallelFor on the TaskGraph
// if we use TaskGraph internally there's a chance of deadlock (?)
// disable our own internal threading for VT tiles :
CurJobifyNumThreads = OODLETEX_JOBS_DISABLE;
CurJobifyUserPointer = nullptr;
}
// encode each slice
// @todo Oodle alternatively could do [Image.NumSlices] array of OodleTex_Surface
// and call OodleTex_Encode with the array
// would be slightly better for parallelism with multi-slice images & cube maps
// that's a rare case so don't bother for now
// (the main parallelism is from running many mips or VT tiles at once which is done by our caller)
bool bCompressionSucceeded = true;
for (int Slice = 0; Slice < Image.NumSlices; ++Slice)
{
InSurf.pixels = ImageBasePtr + Slice * InBytesPerSlice;
uint8 * OutSlicePtr = OutBlocksBasePtr + Slice * OutBytesPerSlice;
if (bImageDump && !ImageDumper.DumpImage(InSurf.pixels, (int64)Image.GetBytesPerPixel() * Image.SizeX * Image.SizeY, Image.SizeX, Image.SizeY, Slice, RDOLambda, OodleBCN))
{
UE_LOG(LogTextureFormatOodle, Display, TEXT("Oodle Texture debug dump failed!"));
}
OodleTex_RDO_Options OodleOptions = { };
OodleOptions.effort = EffortLevel;
OodleOptions.metric = OodleTex_RDO_ErrorMetric_Default;
OodleOptions.bcn_flags = OodleTex_BCNFlags_None;
OodleOptions.universal_tiling = RDOUniversalTiling;
// if RDOLambda == 0, does non-RDO encode :
OodleTex_Err OodleErr = OodleTex_EncodeBCN_RDO_Ex(OodleBCN, OutSlicePtr, NumBlocksPerSlice,
&InSurf, 1, OodlePF, NULL, RDOLambda,
&OodleOptions, CurJobifyNumThreads, CurJobifyUserPointer);
if (OodleErr != OodleTex_Err_OK)
{
const char * OodleErrStr = OodleTex_Err_GetName(OodleErr);
UE_LOG(LogTextureFormatOodle, Display, TEXT("Oodle Texture encode failed!? %s"), OodleErrStr );
bCompressionSucceeded = false;
break;
}
}
return bCompressionSucceeded;
}
};
//===============================================================
static ITextureFormat* Singleton = NULL;
// TFO_ plugins to Oodle to run Oodle system services in Unreal
// @todo Oodle : factor this out and share for Core & Net some day
// global map of TaskGraph references to uint64 for Oodle Jobify system
// protected by TaskIdMapLock
static uint8 PadToCacheLine1[64];
static FCriticalSection TaskIdMapLock;
// would be more efficient to split task ids into bins to reduce contention
static uint64 NextTaskId = 1;
static TMap<uint64, FGraphEventRef> TaskIdMap;
static uint8 PadToCacheLine2[64];
static OO_U64 OODLE_CALLBACK TFO_RunJob(t_fp_Oodle_Job* JobFunction, void* JobData, OO_U64* Dependencies, int NumDependencies, void* UserPtr)
{
FGraphEventArray Prerequisites;
if ( NumDependencies > 0 )
{
// map uint64 dependencies to TaskGraph refs
Prerequisites.Reserve(NumDependencies);
FScopeLock Lock(&TaskIdMapLock);
for (int DependencyIndex = 0; DependencyIndex < NumDependencies; DependencyIndex++)
{
uint64 Id = Dependencies[DependencyIndex];
FGraphEventRef Task = TaskIdMap[Id];
// operator [] does a check that Task was found
Prerequisites.Add(Task);
}
}
// don't hold TaskIdMapLock while dispatching task
// Use AnyBackgroundThreadNormalTask 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?
FGraphEventRef Task = FFunctionGraphTask::CreateAndDispatchWhenReady(
[JobFunction, JobData]()
{
JobFunction(JobData);
}, TStatId(), &Prerequisites, IsInGameThread() ? ENamedThreads::AnyThread : ENamedThreads::AnyBackgroundThreadNormalTask);
// scope lock for NextTaskId and TaskIdMap
TaskIdMapLock.Lock();
uint64 Id = NextTaskId++;
TaskIdMap.Add(Id, MoveTemp(Task));
TaskIdMapLock.Unlock();
return Id;
}
static void OODLE_CALLBACK TFO_WaitJob(OO_U64 JobHandle, void* UserPtr)
{
TaskIdMapLock.Lock();
FGraphEventRef Task = TaskIdMap[JobHandle];
// TMap operator [] checks that value is found
// can remove immediately (task may still be running)
// because once WaitJob is called this handle can never be referred to by calling code
TaskIdMap.Remove(JobHandle);
TaskIdMapLock.Unlock();
// don't hold TaskIdMapLock while waiting
FTaskGraphInterface::Get().WaitUntilTaskCompletes(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)
{
void * Ret = FMemory::Malloc(Bytes, Alignment);
check( Ret != nullptr );
return Ret;
}
static void OODLE_CALLBACK TFO_OodleFree(void* ptr)
{
FMemory::Free(ptr);
}
static void TFO_InstallPlugins()
{
// 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
const TCHAR* IniSection = TEXT("TextureFormatOodleSettings");
bool UseOodleJobify = false;
GConfig->GetBool(IniSection, TEXT("UseOodleExampleJobify"), UseOodleJobify, GEngineIni);
if (UseOodleJobify)
{
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;
OodleTex_Plugins_SetJobSystemAndCount(example_jobify_run_job_fptr, example_jobify_wait_job_fptr, example_jobify_target_parallelism);
}
else
{
OodleJobifyUserPointer = (void *)1; //anything non-null
OodleJobifyNumThreads = FTaskGraphInterface::Get().GetNumWorkerThreads();
OodleTex_Plugins_SetJobSystemAndCount(TFO_RunJob, TFO_WaitJob, OodleJobifyNumThreads);
}
OodleTex_Plugins_SetAssertion(TFO_OodleAssert);
OodleTex_Plugins_SetPrintf(TFO_OodleLog);
OodleTex_Plugins_SetAllocators(TFO_OodleMallocAligned, TFO_OodleFree);
}
class FOodleTextureBuildFunction final : public FTextureBuildFunction
{
FStringView GetName() const final { return TEXT("OodleTexture"); }
FGuid GetVersion() const final { return FGuid(TEXT("e6b8884f-923a-44a1-8da1-298fb48865b2")); }
};
class FTextureFormatOodleModule : public ITextureFormatModule
{
public:
FTextureFormatOodleModule() { }
virtual ~FTextureFormatOodleModule()
{
ITextureFormat * p = Singleton;
Singleton = NULL;
if ( p )
delete p;
}
virtual void StartupModule() override
{
}
virtual ITextureFormat* GetTextureFormat()
{
// this is called twice
if (!Singleton) // not thread safe
{
TFO_InstallPlugins();
FTextureFormatOodle * ptr = new FTextureFormatOodle();
ptr->Init();
Singleton = ptr;
}
return Singleton;
}
static UE::DerivedData::TBuildFunctionFactory<FOodleTextureBuildFunction> BuildFunctionFactory;
};
UE::DerivedData::TBuildFunctionFactory<FOodleTextureBuildFunction> FTextureFormatOodleModule::BuildFunctionFactory;
IMPLEMENT_MODULE(FTextureFormatOodleModule, TextureFormatOodle);