2022-09-26 15:12:13 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2022-10-01 02:06:09 -04:00
# include "MuCO/UnrealMutableImageProvider.h"
2022-09-26 15:12:13 -04:00
2022-10-01 02:04:57 -04:00
# include "MuCO/CustomizableObjectSystem.h"
2022-10-04 09:10:32 -04:00
# include "MuCO/CustomizableObject.h"
2022-10-26 12:57:32 -04:00
# include "TextureResource.h"
2023-05-19 06:11:26 -04:00
# include "MuR/Parameters.h"
2022-09-26 15:12:13 -04:00
//-------------------------------------------------------------------------------------------------
namespace
{
2023-01-05 07:05:06 -05:00
mu : : ImagePtr ConvertTextureUnrealToMutable ( UTexture2D * Texture , uint8 MipmapsToSkip )
2022-09-26 15:12:13 -04:00
{
mu : : ImagePtr pResult ;
# if WITH_EDITOR
int LODs = 1 ;
2023-01-05 07:05:06 -05:00
int SizeX = Texture - > Source . GetSizeX ( ) > > MipmapsToSkip ;
int SizeY = Texture - > Source . GetSizeY ( ) > > MipmapsToSkip ;
check ( SizeX > 0 & & SizeY > 0 ) ;
2022-09-26 15:12:13 -04:00
ETextureSourceFormat Format = Texture - > Source . GetFormat ( ) ;
mu : : EImageFormat MutableFormat = mu : : EImageFormat : : IF_NONE ;
switch ( Format )
{
case ETextureSourceFormat : : TSF_BGRA8 : MutableFormat = mu : : EImageFormat : : IF_BGRA_UBYTE ; break ;
2022-10-04 09:10:32 -04:00
// This format is deprecated and using the enum fails to compile in some cases.
2022-09-26 15:12:13 -04:00
//case ETextureSourceFormat::TSF_RGBA8: MutableFormat = mu::EImageFormat::IF_RGBA_UBYTE; break;
case ETextureSourceFormat : : TSF_G8 : MutableFormat = mu : : EImageFormat : : IF_L_UBYTE ; break ;
default :
break ;
}
// If not locked ReadOnly the Texture Source's FGuid can change, invalidating the texture's caching/shaders
// making shader compile and cook times increase
2023-01-05 07:05:06 -05:00
const uint8 * pSource = Texture - > Source . LockMipReadOnly ( MipmapsToSkip ) ;
2022-09-26 15:12:13 -04:00
if ( pSource )
{
2023-06-23 02:01:13 -04:00
pResult = new mu : : Image ( SizeX , SizeY , LODs , MutableFormat , mu : : EInitializationType : : NotInitialized ) ;
2022-09-26 15:12:13 -04:00
FMemory : : Memcpy ( pResult - > GetData ( ) , pSource , pResult - > GetDataSize ( ) ) ;
2023-01-05 07:05:06 -05:00
Texture - > Source . UnlockMip ( MipmapsToSkip ) ;
}
else
{
check ( false ) ;
2023-06-23 02:01:13 -04:00
pResult = new mu : : Image ( SizeX , SizeY , LODs , MutableFormat , mu : : EInitializationType : : Black ) ;
2022-09-26 15:12:13 -04:00
}
2023-01-05 07:05:06 -05:00
2022-10-04 09:10:32 -04:00
# else
2023-01-05 07:05:06 -05:00
check ( Texture - > GetPlatformData ( ) - > Mips [ MipmapsToSkip ] . BulkData . IsBulkDataLoaded ( ) ) ;
2022-10-04 09:10:32 -04:00
int32 LODs = 1 ;
2023-01-05 07:05:06 -05:00
int32 SizeX = Texture - > GetSizeX ( ) > > MipmapsToSkip ;
int32 SizeY = Texture - > GetSizeY ( ) > > MipmapsToSkip ;
check ( SizeX > 0 & & SizeY > 0 ) ;
2022-10-04 09:10:32 -04:00
EPixelFormat Format = Texture - > GetPlatformData ( ) - > PixelFormat ;
mu : : EImageFormat MutableFormat = mu : : EImageFormat : : IF_NONE ;
switch ( Format )
{
case EPixelFormat : : PF_B8G8R8A8 : MutableFormat = mu : : EImageFormat : : IF_BGRA_UBYTE ; break ;
// This format is deprecated and using the enum fails to compile in some cases.
//case ETextureSourceFormat::TSF_RGBA8: MutableFormat = mu::EImageFormat::IF_RGBA_UBYTE; break;
case EPixelFormat : : PF_G8 : MutableFormat = mu : : EImageFormat : : IF_L_UBYTE ; break ;
default :
break ;
}
// If not locked ReadOnly the Texture Source's FGuid can change, invalidating the texture's caching/shaders
// making shader compile and cook times increase
2023-01-05 07:05:06 -05:00
const void * pSource = Texture - > GetPlatformData ( ) - > Mips [ MipmapsToSkip ] . BulkData . LockReadOnly ( ) ;
2022-10-04 09:10:32 -04:00
if ( pSource )
{
2023-06-23 02:01:13 -04:00
pResult = new mu : : Image ( SizeX , SizeY , LODs , MutableFormat , mu : : EInitializationType : : NotInitialized ) ;
2022-10-04 09:10:32 -04:00
FMemory : : Memcpy ( pResult - > GetData ( ) , pSource , pResult - > GetDataSize ( ) ) ;
2023-01-05 07:05:06 -05:00
Texture - > GetPlatformData ( ) - > Mips [ MipmapsToSkip ] . BulkData . Unlock ( ) ;
}
else
{
check ( false ) ;
2023-06-23 02:01:13 -04:00
pResult = new mu : : Image ( SizeX , SizeY , LODs , MutableFormat , mu : : EInitializationType : : Black ) ;
2022-10-04 09:10:32 -04:00
}
2022-09-26 15:12:13 -04:00
# endif
return pResult ;
}
}
2022-12-16 05:05:50 -05:00
mu : : EImageFormat GetMutablePixelFormat ( EPixelFormat InTextureFormat )
{
switch ( InTextureFormat )
{
case PF_B8G8R8A8 : return mu : : EImageFormat : : IF_BGRA_UBYTE ;
case PF_R8G8B8A8 : return mu : : EImageFormat : : IF_RGBA_UBYTE ;
case PF_DXT1 : return mu : : EImageFormat : : IF_BC1 ;
case PF_DXT3 : return mu : : EImageFormat : : IF_BC2 ;
case PF_DXT5 : return mu : : EImageFormat : : IF_BC3 ;
case PF_BC4 : return mu : : EImageFormat : : IF_BC4 ;
case PF_BC5 : return mu : : EImageFormat : : IF_BC5 ;
case PF_G8 : return mu : : EImageFormat : : IF_L_UBYTE ;
case PF_ASTC_4x4 : return mu : : EImageFormat : : IF_ASTC_4x4_RGBA_LDR ;
2023-08-22 06:59:07 -04:00
case PF_ASTC_6x6 : return mu : : EImageFormat : : IF_ASTC_6x6_RGBA_LDR ;
2022-12-16 05:05:50 -05:00
case PF_ASTC_8x8 : return mu : : EImageFormat : : IF_ASTC_8x8_RGBA_LDR ;
2023-08-22 06:59:07 -04:00
case PF_ASTC_10x10 : return mu : : EImageFormat : : IF_ASTC_10x10_RGBA_LDR ;
2022-12-16 05:05:50 -05:00
case PF_ASTC_12x12 : return mu : : EImageFormat : : IF_ASTC_12x12_RGBA_LDR ;
default : return mu : : EImageFormat : : IF_NONE ;
}
}
2022-09-26 15:12:13 -04:00
//-------------------------------------------------------------------------------------------------
2023-02-08 04:20:33 -05:00
# ifdef MUTABLE_USE_NEW_TASKGRAPH
2023-07-31 03:24:06 -04:00
TTuple < UE : : Tasks : : FTask , TFunction < void ( ) > > FUnrealMutableImageProvider : : GetImageAsync ( FName Id , uint8 MipmapsToSkip , TFunction < void ( mu : : Ptr < mu : : Image > ) > & ResultCallback )
2023-02-08 04:20:33 -05:00
# else
2023-07-31 03:24:06 -04:00
TTuple < FGraphEventRef , TFunction < void ( ) > > FUnrealMutableImageProvider : : GetImageAsync ( FName Id , uint8 MipmapsToSkip , TFunction < void ( mu : : Ptr < mu : : Image > ) > & ResultCallback )
2023-02-08 04:20:33 -05:00
# endif
2022-09-26 15:12:13 -04:00
{
2023-01-03 10:26:42 -05:00
// Thread: worker
2022-12-16 05:05:50 -05:00
MUTABLE_CPUPROFILER_SCOPE ( FUnrealMutableImageProvider : : GetImage ) ;
// Out Texture
mu : : ImagePtr Image ;
2022-09-26 15:12:13 -04:00
2022-10-04 09:10:32 -04:00
// Some data that may have to be copied from the GlobalExternalImages while it's locked
2023-01-13 10:48:15 -05:00
IBulkDataIORequest * IORequest = nullptr ;
2022-12-16 05:05:50 -05:00
const int32 LODs = 1 ;
2022-10-04 09:10:32 -04:00
EPixelFormat Format = EPixelFormat : : PF_Unknown ;
int32 BulkDataSize = 0 ;
2022-12-16 05:05:50 -05:00
mu : : EImageFormat MutImageFormat = mu : : EImageFormat : : IF_NONE ;
int32 MutImageDataSize = 0 ;
2023-01-13 10:48:15 -05:00
2023-02-08 04:20:33 -05:00
# ifdef MUTABLE_USE_NEW_TASKGRAPH
auto TrivialReturn = [ ] ( ) - > TTuple < UE : : Tasks : : FTask , TFunction < void ( ) > >
{
UE : : Tasks : : FTaskEvent CompletionEvent ( TEXT ( " GetImageAsyncCompleted " ) ) ;
CompletionEvent . Trigger ( ) ;
return MakeTuple ( CompletionEvent , [ ] ( ) - > void { } ) ;
} ;
# else
2023-01-13 10:48:15 -05:00
auto TrivialReturn = [ ] ( ) - > TTuple < FGraphEventRef , TFunction < void ( ) > >
{
FGraphEventRef CompletionEvent = FGraphEvent : : CreateGraphEvent ( ) ;
CompletionEvent - > DispatchSubsequents ( ) ;
return MakeTuple ( CompletionEvent , [ ] ( ) - > void { } ) ;
} ;
2023-02-08 04:20:33 -05:00
# endif
2023-01-13 10:48:15 -05:00
2022-10-04 09:10:32 -04:00
{
FScopeLock Lock ( & ExternalImagesLock ) ;
// Inside this scope it's safe to access GlobalExternalImages
2023-01-13 10:48:15 -05:00
2023-07-31 03:24:06 -04:00
if ( ! GlobalExternalImages . Contains ( Id ) )
2022-10-04 09:10:32 -04:00
{
// Null case, no image was provided
2023-08-07 12:37:29 -04:00
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image [%s]. GlobalExternalImage not found. " ) , * Id . ToString ( ) ) ;
2023-01-26 05:27:35 -05:00
2023-01-13 10:48:15 -05:00
ResultCallback ( CreateDummy ( ) ) ;
return Invoke ( TrivialReturn ) ;
2022-10-04 09:10:32 -04:00
}
2023-07-31 03:24:06 -04:00
const FUnrealMutableImageInfo & ImageInfo = GlobalExternalImages [ Id ] ;
2022-10-04 09:10:32 -04:00
if ( ImageInfo . Image )
{
// Easy case where the image was directly provided
2023-01-13 10:48:15 -05:00
ResultCallback ( ImageInfo . Image ) ;
return Invoke ( TrivialReturn ) ;
2022-10-04 09:10:32 -04:00
}
else if ( UTexture2D * TextureToLoad = ImageInfo . TextureToLoad )
{
// It's safe to access TextureToLoad because ExternalImagesLock guarantees that the data in GlobalExternalImages is valid,
// not being modified by the game thread at the moment and the texture cannot be GCed because of the AddReferencedObjects
// in the FUnrealMutableImageProvider
2023-02-20 11:01:39 -05:00
# if WITH_EDITOR
if ( ! TextureToLoad - > IsAsyncCacheComplete ( ) )
{
// If the UE texture is being compiled/processed in a background process, it's not safe to access
FString TextureName ;
TextureToLoad - > GetName ( TextureName ) ;
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get the external texture [%s] because it's still being async cached. Wait until all background processed finish and retry again. " ) , * TextureName ) ;
ResultCallback ( CreateDummy ( ) ) ;
return Invoke ( TrivialReturn ) ;
}
# endif
2023-05-10 08:01:59 -04:00
int32 MipIndex = MipmapsToSkip < TextureToLoad - > GetPlatformData ( ) - > Mips . Num ( ) ? MipmapsToSkip : TextureToLoad - > GetPlatformData ( ) - > Mips . Num ( ) - 1 ;
2023-01-05 07:05:06 -05:00
// Mips in the mip tail are inlined and can't be streamed, find the smallest mip available.
for ( ; MipIndex > 0 ; - - MipIndex )
{
if ( TextureToLoad - > GetPlatformData ( ) - > Mips [ MipIndex ] . BulkData . CanLoadFromDisk ( ) )
{
break ;
}
}
2022-10-04 09:10:32 -04:00
# if WITH_EDITOR
// In the editor the src data can be directly accessed
2023-01-13 10:48:15 -05:00
ResultCallback ( ConvertTextureUnrealToMutable ( TextureToLoad , MipIndex ) ) ;
return Invoke ( TrivialReturn ) ;
2022-10-04 09:10:32 -04:00
# else
2022-12-16 05:05:50 -05:00
// Texture format and the equivalent mutable format
Format = TextureToLoad - > GetPlatformData ( ) - > PixelFormat ;
MutImageFormat = GetMutablePixelFormat ( Format ) ;
// Check if it's a format we support
if ( MutImageFormat = = mu : : EImageFormat : : IF_NONE )
{
2023-08-07 12:37:29 -04:00
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image [%s]. Unexpected image format. EImageFormat [%s]. " ) , * Id . ToString ( ) , GetPixelFormatString ( Format ) ) ;
2023-01-13 10:48:15 -05:00
ResultCallback ( CreateDummy ( ) ) ;
return Invoke ( TrivialReturn ) ;
2022-12-16 05:05:50 -05:00
}
2023-01-05 07:05:06 -05:00
int SizeX = TextureToLoad - > GetSizeX ( ) > > MipIndex ;
int SizeY = TextureToLoad - > GetSizeY ( ) > > MipIndex ;
2023-06-23 02:01:13 -04:00
Image = new mu : : Image ( SizeX , SizeY , LODs , MutImageFormat , mu : : EInitializationType : : NotInitialized ) ;
2022-12-16 05:05:50 -05:00
MutImageDataSize = Image - > GetDataSize ( ) ;
2022-10-04 09:10:32 -04:00
// In a packaged game the bulk data has to be loaded
// Get the actual file to read the mip 0 data, do not keep any reference to TextureToLoad because once outside of the lock
// it may be GCed or changed. Just keep the actual file handle and some sizes instead of the texture
2023-01-05 07:05:06 -05:00
FByteBulkData & BulkData = TextureToLoad - > GetPlatformData ( ) - > Mips [ MipIndex ] . BulkData ;
2022-10-04 09:10:32 -04:00
BulkDataSize = BulkData . GetBulkDataSize ( ) ;
2022-12-16 05:05:50 -05:00
check ( BulkDataSize > 0 ) ;
if ( BulkDataSize ! = MutImageDataSize )
{
2023-08-07 12:37:29 -04:00
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image [%s]. Bulk data size is different than the expected size. BulkData size [%d]. Mutable image data size [%d]. " ) ,
* Id . ToString ( ) , BulkDataSize , MutImageDataSize ) ;
2022-12-16 05:05:50 -05:00
2023-01-13 10:48:15 -05:00
ResultCallback ( CreateDummy ( ) ) ;
return Invoke ( TrivialReturn ) ;
2022-12-16 05:05:50 -05:00
}
// Create a streaming request if the data is not loaded or copy the mip data
if ( ! BulkData . IsBulkDataLoaded ( ) )
{
2023-02-08 04:20:33 -05:00
# ifdef MUTABLE_USE_NEW_TASKGRAPH
UE : : Tasks : : FTaskEvent IORequestCompletionEvent ( TEXT ( " Mutable_IORequestCompletionEvent " ) ) ;
# else
2023-01-13 10:48:15 -05:00
FGraphEventRef IORequestCompletionEvent = FGraphEvent : : CreateGraphEvent ( ) ;
2023-02-08 04:20:33 -05:00
# endif
2023-01-13 10:48:15 -05:00
TFunction < void ( bool , IBulkDataIORequest * ) > IOCallback =
[
MutImageDataSize ,
MutImageFormat ,
Format ,
Image ,
BulkDataSize ,
ResultCallback , // Notice ResultCallback is captured by copy
IORequestCompletionEvent
] ( bool bWasCancelled , IBulkDataIORequest * IORequest )
{
ON_SCOPE_EXIT
{
2023-02-08 04:20:33 -05:00
# ifdef MUTABLE_USE_NEW_TASKGRAPH
UE : : Tasks : : FTaskEvent EventCopy = IORequestCompletionEvent ;
EventCopy . Trigger ( ) ;
# else
2023-01-13 10:48:15 -05:00
if ( IORequestCompletionEvent . IsValid ( ) )
{
IORequestCompletionEvent - > DispatchSubsequents ( ) ;
}
2023-02-08 04:20:33 -05:00
# endif
2023-01-13 10:48:15 -05:00
} ;
// Should we do someting different than returning a dummy image if cancelled?
if ( bWasCancelled )
{
2023-01-26 05:27:35 -05:00
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image. Cancelled IO Request " ) ) ;
2023-01-13 10:48:15 -05:00
ResultCallback ( CreateDummy ( ) ) ;
return ;
}
uint8 * Results = IORequest - > GetReadResults ( ) ; // required?
if ( Results & & Image - > GetDataSize ( ) = = ( int32 ) IORequest - > GetSize ( ) )
{
check ( BulkDataSize = = ( int32 ) IORequest - > GetSize ( ) ) ;
check ( Results = = Image - > GetData ( ) ) ;
ResultCallback ( Image ) ;
return ;
}
if ( ! Results )
{
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image. IO Request failed. Request results [%hhd]. Format: [%s]. MutableFormat: [%d]. " ) ,
( Results ! = nullptr ) ,
GetPixelFormatString ( Format ) ,
( int32 ) MutImageFormat ) ;
}
else if ( MutImageDataSize ! = ( int32 ) IORequest - > GetSize ( ) )
{
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image. Requested size is different than the expected size. RequestSize: [%lld]. ExpectedSize: [%d]. Format: [%s]. MutableFormat: [%d]. " ) ,
IORequest - > GetSize ( ) ,
Image - > GetDataSize ( ) ,
GetPixelFormatString ( Format ) ,
( int32 ) MutImageFormat ) ;
}
else
{
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image. " ) ) ;
}
// Something failed when loading the bulk data, just return a dummy
ResultCallback ( CreateDummy ( ) ) ;
} ;
// Is the resposability of the CreateStreamingRequest caller to delete the IORequest.
// This can *not* be done in the IOCallback because it would cause a deadlock so it is deferred to the returned
// cleanup function. Another solution could be to spwan a new task that depends on the
// IORequestComplitionEvent which deletes it.
IORequest = BulkData . CreateStreamingRequest ( EAsyncIOPriorityAndFlags : : AIOP_High , & IOCallback , Image - > GetData ( ) ) ;
if ( IORequest )
{
// Make the lambda mutable and set the IORequest pointer to null when deleted so it is safer
// agains multiple calls.
const auto DeleteIORequest = [ IORequest ] ( ) mutable - > void
{
if ( IORequest )
{
delete IORequest ;
}
IORequest = nullptr ;
} ;
return MakeTuple ( IORequestCompletionEvent , DeleteIORequest ) ;
}
else
{
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to create an IORequest for a UTexture2D BulkData for an application-specific image parameter. " ) ) ;
2023-09-14 06:06:24 -04:00
# ifdef MUTABLE_USE_NEW_TASKGRAPH
IORequestCompletionEvent . Trigger ( ) ;
# else
if ( IORequestCompletionEvent . IsValid ( ) )
{
IORequestCompletionEvent - > DispatchSubsequents ( ) ;
}
# endif
2023-02-06 18:02:49 -05:00
2023-01-13 10:48:15 -05:00
ResultCallback ( CreateDummy ( ) ) ;
return Invoke ( TrivialReturn ) ;
}
2022-12-16 05:05:50 -05:00
}
else
{
// Bulk data already loaded
2023-04-04 06:06:06 -04:00
const void * Data = ( ! BulkData . IsLocked ( ) ) ? BulkData . LockReadOnly ( ) : nullptr ; // TODO: Retry if it fails?
2022-12-16 05:05:50 -05:00
if ( Data )
{
FMemory : : Memcpy ( Image - > GetData ( ) , Data , BulkDataSize ) ;
BulkData . Unlock ( ) ;
2023-01-13 10:48:15 -05:00
ResultCallback ( Image ) ;
return Invoke ( TrivialReturn ) ;
2022-12-16 05:05:50 -05:00
}
else
{
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image. Bulk data already locked or null. " ) ) ;
2023-01-13 10:48:15 -05:00
ResultCallback ( CreateDummy ( ) ) ;
return Invoke ( TrivialReturn ) ;
2022-12-16 05:05:50 -05:00
}
}
2022-10-04 09:10:32 -04:00
# endif
}
else
{
// No UTexture2D was provided, cannot do anything, just provide a dummy texture
UE_LOG ( LogMutable , Warning , TEXT ( " No UTexture2D was provided for an application-specific image parameter. " ) ) ;
2023-01-13 10:48:15 -05:00
ResultCallback ( CreateDummy ( ) ) ;
return Invoke ( TrivialReturn ) ;
2022-10-04 09:10:32 -04:00
}
}
2023-01-13 10:48:15 -05:00
// Make sure the returned event is dispatched at some point for all code paths,
// in this case returning Invoke(TrivialReturn) or through the IORequest callback.
}
2022-12-16 05:05:50 -05:00
2023-01-13 10:48:15 -05:00
// This should mantain parity with the descriptor of the images generated by GetImageAsync
2023-07-31 03:24:06 -04:00
mu : : FImageDesc FUnrealMutableImageProvider : : GetImageDesc ( FName Id , uint8 MipmapsToSkip )
2023-01-13 10:48:15 -05:00
{
MUTABLE_CPUPROFILER_SCOPE ( FUnrealMutableImageProvider : : GetImageDesc ) ;
mu : : FImageDesc Result ;
2022-10-04 09:10:32 -04:00
{
2023-01-13 10:48:15 -05:00
FScopeLock Lock ( & ExternalImagesLock ) ;
// Inside this scope it's safe to access GlobalExternalImages
2022-10-04 09:10:32 -04:00
2023-07-31 03:24:06 -04:00
if ( ! GlobalExternalImages . Contains ( Id ) )
2022-10-04 09:10:32 -04:00
{
2023-01-13 10:48:15 -05:00
// Null case, no image was provided
return CreateDummyDesc ( ) ;
2022-10-04 09:10:32 -04:00
}
2023-07-31 03:24:06 -04:00
const FUnrealMutableImageInfo & ImageInfo = GlobalExternalImages [ Id ] ;
2023-01-13 10:48:15 -05:00
if ( ImageInfo . Image )
2022-10-04 09:10:32 -04:00
{
2023-01-13 10:48:15 -05:00
// Easy case where the image was directly provided
Result = mu : : FImageDesc ( ImageInfo . Image - > GetSize ( ) , ImageInfo . Image - > GetFormat ( ) , ImageInfo . Image - > GetLODCount ( ) ) ;
2022-12-16 05:05:50 -05:00
}
2023-01-13 10:48:15 -05:00
else if ( UTexture2D * TextureToLoad = ImageInfo . TextureToLoad )
2022-12-16 05:05:50 -05:00
{
2023-01-13 10:48:15 -05:00
// It's safe to access TextureToLoad because ExternalImagesLock guarantees that the data in GlobalExternalImages is valid,
// not being modified by the game thread at the moment and the texture cannot be GCed because of the AddReferencedObjects
// in the FUnrealMutableImageProvider
2023-05-10 08:01:59 -04:00
int32 MipIndex = MipmapsToSkip < TextureToLoad - > GetPlatformData ( ) - > Mips . Num ( ) ? MipmapsToSkip : TextureToLoad - > GetPlatformData ( ) - > Mips . Num ( ) - 1 ;
2023-01-13 10:48:15 -05:00
// Mips in the mip tail are inlined and can't be streamed, find the smallest mip available.
for ( ; MipIndex > 0 ; - - MipIndex )
{
if ( TextureToLoad - > GetPlatformData ( ) - > Mips [ MipIndex ] . BulkData . CanLoadFromDisk ( ) )
{
break ;
}
}
// Texture format and the equivalent mutable format
const EPixelFormat Format = TextureToLoad - > GetPlatformData ( ) - > PixelFormat ;
const mu : : EImageFormat MutableFormat = GetMutablePixelFormat ( Format ) ;
// Check if it's a format we support
if ( MutableFormat = = mu : : EImageFormat : : IF_NONE )
{
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to get external image descriptor. Unexpected image format. EImageFormat [%s]. " ) , GetPixelFormatString ( Format ) ) ;
return CreateDummyDesc ( ) ;
}
const mu : : FImageSize ImageSize = mu : : FImageSize (
TextureToLoad - > GetSizeX ( ) > > MipIndex ,
TextureToLoad - > GetSizeY ( ) > > MipIndex ) ;
const int32 Lods = 1 ;
Result = mu : : FImageDesc ( ImageSize , MutableFormat , Lods ) ;
2022-12-16 05:05:50 -05:00
}
else
{
2023-01-13 10:48:15 -05:00
// No UTexture2D was provided, cannot do anything, just provide a dummy texture
UE_LOG ( LogMutable , Warning , TEXT ( " No UTexture2D was provided for an application-specific image parameter descriptor. " ) ) ;
return CreateDummyDesc ( ) ;
2022-10-04 09:10:32 -04:00
}
2023-01-13 10:48:15 -05:00
return Result ;
2022-10-04 09:10:32 -04:00
}
2022-09-26 15:12:13 -04:00
}
2023-07-31 03:24:06 -04:00
void FUnrealMutableImageProvider : : CacheImage ( FName Id , bool bUser )
2022-09-26 15:12:13 -04:00
{
2023-08-25 05:16:24 -04:00
if ( Id = = NAME_None )
2022-09-26 15:12:13 -04:00
{
2023-05-19 06:11:26 -04:00
return ;
}
2022-09-26 15:12:13 -04:00
2022-10-04 09:10:32 -04:00
{
FScopeLock Lock ( & ExternalImagesLock ) ;
2023-05-19 06:11:26 -04:00
if ( FUnrealMutableImageInfo * Result = GlobalExternalImages . Find ( Id ) )
2022-10-04 09:10:32 -04:00
{
2023-05-19 06:11:26 -04:00
if ( bUser )
2022-10-04 09:10:32 -04:00
{
2023-05-19 06:11:26 -04:00
Result - > ReferencesUser = true ;
2022-10-04 09:10:32 -04:00
}
2023-05-19 06:11:26 -04:00
else
{
Result - > ReferencesSystem + + ;
}
}
else
{
mu : : ImagePtr pResult ;
UTexture2D * UnrealDeferredTexture = nullptr ;
// See if any provider provides this id.
for ( int32 p = 0 ; ! pResult & & p < ImageProviders . Num ( ) ; + + p )
{
TWeakObjectPtr < UCustomizableSystemImageProvider > Provider = ImageProviders [ p ] ;
if ( Provider . IsValid ( ) )
{
2023-06-26 06:08:57 -04:00
// \TODO: all these queries could probably be optimized into a single call.
2023-07-31 03:24:06 -04:00
switch ( Provider - > HasTextureParameterValue ( Id ) )
2023-05-19 06:11:26 -04:00
{
case UCustomizableSystemImageProvider : : ValueType : : Raw :
{
2023-07-31 03:24:06 -04:00
FIntVector desc = Provider - > GetTextureParameterValueSize ( Id ) ;
2023-06-23 02:01:13 -04:00
pResult = new mu : : Image ( desc [ 0 ] , desc [ 1 ] , 1 , mu : : EImageFormat : : IF_RGBA_UBYTE , mu : : EInitializationType : : Black ) ;
2023-07-31 03:24:06 -04:00
Provider - > GetTextureParameterValueData ( Id , pResult - > GetData ( ) ) ;
2023-05-19 06:11:26 -04:00
break ;
}
case UCustomizableSystemImageProvider : : ValueType : : Unreal :
{
2023-07-31 03:24:06 -04:00
UTexture2D * UnrealTexture = Provider - > GetTextureParameterValue ( Id ) ;
2023-05-19 06:11:26 -04:00
pResult = ConvertTextureUnrealToMutable ( UnrealTexture , 0 ) ;
break ;
}
case UCustomizableSystemImageProvider : : ValueType : : Unreal_Deferred :
{
2023-07-31 03:24:06 -04:00
UnrealDeferredTexture = Provider - > GetTextureParameterValue ( Id ) ;
2023-05-19 06:11:26 -04:00
break ;
}
default :
break ;
}
}
}
if ( ! pResult & & ! UnrealDeferredTexture )
{
2023-08-07 12:37:29 -04:00
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to cache external image %s. Missing result or source texture. Result [%d]. Unreal texture [%d]. Num providers [%d] " ) ,
* Id . ToString ( ) ,
pResult ? 1 : 0 ,
UnrealDeferredTexture ? 1 : 0 ,
ImageProviders . Num ( ) ) ;
2023-08-10 14:14:20 -04:00
return ;
2023-05-19 06:11:26 -04:00
}
FUnrealMutableImageInfo ImageInfo ( pResult , UnrealDeferredTexture ) ;
if ( bUser )
{
ImageInfo . ReferencesUser = true ;
}
else
{
ImageInfo . ReferencesSystem + + ;
}
GlobalExternalImages . Add ( Id , ImageInfo ) ;
2022-10-04 09:10:32 -04:00
}
}
2022-09-26 15:12:13 -04:00
}
2023-07-31 03:24:06 -04:00
void FUnrealMutableImageProvider : : UnCacheImage ( FName Id , bool bUser )
2023-05-19 06:11:26 -04:00
{
2023-08-25 05:16:24 -04:00
if ( Id = = NAME_None )
2023-05-19 06:11:26 -04:00
{
return ;
}
FScopeLock Lock ( & ExternalImagesLock ) ;
if ( FUnrealMutableImageInfo * Result = GlobalExternalImages . Find ( Id ) )
{
if ( bUser )
{
Result - > ReferencesUser = false ;
}
else
{
2023-07-21 03:28:52 -04:00
- - Result - > ReferencesSystem ;
check ( Result - > ReferencesSystem > = 0 ) ; // Something went wrong. The image has been uncached more times than it has been cached.
2023-05-19 06:11:26 -04:00
}
if ( Result - > ReferencesUser + Result - > ReferencesSystem = = 0 )
{
GlobalExternalImages . Remove ( Id ) ;
}
}
2023-07-28 06:06:02 -04:00
else
{
2023-07-31 03:24:06 -04:00
UE_LOG ( LogMutable , Warning , TEXT ( " Failed to uncache external image %s. Possible double free! " ) , * Id . ToString ( ) ) ;
2023-07-28 06:06:02 -04:00
}
2023-05-19 06:11:26 -04:00
}
void FUnrealMutableImageProvider : : ClearCache ( bool bUser )
2022-09-26 15:12:13 -04:00
{
2022-10-04 09:10:32 -04:00
FScopeLock Lock ( & ExternalImagesLock ) ;
2023-07-31 03:24:06 -04:00
for ( TTuple < FName , FUnrealMutableImageInfo > Tuple : GlobalExternalImages )
2023-05-19 06:11:26 -04:00
{
UnCacheImage ( Tuple . Key , bUser ) ;
}
}
void FUnrealMutableImageProvider : : CacheImages ( const mu : : Parameters & Parameters )
{
const int32 NumParams = Parameters . GetCount ( ) ;
for ( int32 ParamIndex = 0 ; ParamIndex < NumParams ; + + ParamIndex )
{
if ( Parameters . GetType ( ParamIndex ) ! = mu : : PARAMETER_TYPE : : T_IMAGE )
{
continue ;
}
{
2023-07-31 03:24:06 -04:00
const FName TextureId = Parameters . GetImageValue ( ParamIndex ) ;
2023-05-19 06:11:26 -04:00
CacheImage ( TextureId , false ) ;
}
const int32 NumValues = Parameters . GetValueCount ( ParamIndex ) ;
for ( int32 ValueIndex = 0 ; ValueIndex < NumValues ; + + ValueIndex )
{
mu : : Ptr < const mu : : RangeIndex > Range = Parameters . GetValueIndex ( ParamIndex , ValueIndex ) ;
2023-07-31 03:24:06 -04:00
const FName TextureId = Parameters . GetImageValue ( ParamIndex , Range ) ;
2023-05-19 06:11:26 -04:00
CacheImage ( TextureId , false ) ;
}
}
}
void FUnrealMutableImageProvider : : UnCacheImages ( const mu : : Parameters & Parameters )
{
const int32 NumParams = Parameters . GetCount ( ) ;
for ( int32 ParamIndex = 0 ; ParamIndex < NumParams ; + + ParamIndex )
{
if ( Parameters . GetType ( ParamIndex ) ! = mu : : PARAMETER_TYPE : : T_IMAGE )
{
continue ;
}
{
2023-07-31 03:24:06 -04:00
const FName TextureId = Parameters . GetImageValue ( ParamIndex ) ;
2023-05-19 06:11:26 -04:00
UnCacheImage ( TextureId , false ) ;
}
const int32 NumValues = Parameters . GetValueCount ( ParamIndex ) ;
for ( int32 ValueIndex = 0 ; ValueIndex < NumValues ; + + ValueIndex )
{
mu : : Ptr < const mu : : RangeIndex > Range = Parameters . GetValueIndex ( ParamIndex , ValueIndex ) ;
2023-07-31 03:24:06 -04:00
const FName TextureId = Parameters . GetImageValue ( ParamIndex , Range ) ;
2023-05-19 06:11:26 -04:00
UnCacheImage ( TextureId , false ) ;
}
}
2022-09-26 15:12:13 -04:00
}
mu : : ImagePtr FUnrealMutableImageProvider : : CreateDummy ( )
{
// Create a dummy image
2023-01-13 10:48:15 -05:00
const int size = DUMMY_IMAGE_DESC . m_size [ 0 ] ;
2022-09-26 15:12:13 -04:00
const int checkerSize = 4 ;
constexpr int checkerTileCount = 2 ;
2023-01-31 12:07:56 -05:00
# if !UE_BUILD_SHIPPING
uint8_t colours [ checkerTileCount ] [ 4 ] = { { 255 , 255 , 0 , 255 } , { 0 , 0 , 255 , 255 } } ;
# else
uint8_t colours [ checkerTileCount ] [ 4 ] = { { 255 , 255 , 0 , 0 } , { 0 , 0 , 255 , 0 } } ;
# endif
2022-09-26 15:12:13 -04:00
2023-06-23 02:01:13 -04:00
mu : : ImagePtr pResult = new mu : : Image ( size , size , DUMMY_IMAGE_DESC . m_lods , DUMMY_IMAGE_DESC . m_format , mu : : EInitializationType : : NotInitialized ) ;
2022-09-26 15:12:13 -04:00
uint8_t * pData = pResult - > GetData ( ) ;
for ( int x = 0 ; x < size ; + + x )
{
for ( int y = 0 ; y < size ; + + y )
{
int checkerIndex = ( ( x / checkerSize ) + ( y / checkerSize ) ) % checkerTileCount ;
pData [ 0 ] = colours [ checkerIndex ] [ 0 ] ;
pData [ 1 ] = colours [ checkerIndex ] [ 1 ] ;
pData [ 2 ] = colours [ checkerIndex ] [ 2 ] ;
pData [ 3 ] = colours [ checkerIndex ] [ 3 ] ;
pData + = 4 ;
}
}
return pResult ;
}
2023-01-13 10:48:15 -05:00
mu : : FImageDesc FUnrealMutableImageProvider : : CreateDummyDesc ( )
{
return DUMMY_IMAGE_DESC ;
}
2023-08-31 14:28:14 -04:00
TAutoConsoleVariable < bool > CVarMutableLockExternalImagesDuringGC (
TEXT ( " Mutable.LockExternalImagesDuringGC " ) ,
true ,
TEXT ( " If true, GlobalExternalImages where all texture parameters are stored will be locked from concurrent access during the AddReferencedObjects phase of GC. " ) ,
ECVF_Default ) ;
2022-10-04 09:10:32 -04:00
void FUnrealMutableImageProvider : : AddReferencedObjects ( FReferenceCollector & Collector )
{
2023-08-31 14:28:14 -04:00
bool bDoLock = CVarMutableLockExternalImagesDuringGC . GetValueOnAnyThread ( ) ;
if ( bDoLock )
{
ExternalImagesLock . Lock ( ) ;
}
2023-07-31 03:24:06 -04:00
for ( TPair < FName , FUnrealMutableImageInfo > & Image : GlobalExternalImages )
2022-10-04 09:10:32 -04:00
{
if ( Image . Value . TextureToLoad )
{
Collector . AddReferencedObject ( Image . Value . TextureToLoad ) ;
}
}
2023-08-31 14:28:14 -04:00
if ( bDoLock )
{
ExternalImagesLock . Unlock ( ) ;
}
2022-10-04 09:10:32 -04:00
}