You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
----------------------------------------------------------------- Texture: fix many issues with Required Memory Estimate was wrong for latlong cubes, astc, MaxTextureSize factor out ComputeLongLatCubemapExtents to remove duplication of cube size logic fix GetOutputMipInfo was wrong on latlong cubes with pow2 options fix "VT has blocks with mismatched aspect ratios" warning could be thrown incorrectly on non-VT non-UDIM textures with "resize to specific" set [CL 34055640 by charles bloom in ue5-main branch]
775 lines
21 KiB
C++
775 lines
21 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "TextureSourceDataUtils.h"
|
|
|
|
#if WITH_EDITOR
|
|
|
|
// TextureSourceDataUtils is *incorrectly* in /Runtime/
|
|
// then the whole file is wrapped in WITH_EDITOR
|
|
|
|
#include "ImageCoreUtils.h"
|
|
#include "Engine/Texture.h"
|
|
#include "HAL/UnrealMemory.h"
|
|
#include "EngineLogs.h"
|
|
#include "TextureImportSettings.h"
|
|
#include "TextureBuildUtilities.h"
|
|
#include "ImageUtils.h"
|
|
|
|
namespace UE::TextureUtilitiesCommon::Experimental
|
|
{
|
|
|
|
namespace Private
|
|
{
|
|
|
|
// adds wrap/clamp flags for X/Y on the filter as appropriate for this texture
|
|
static FImageCore::EResizeImageFilter SetResizeImageFilterEdgeModes(UTexture * Texture,FImageCore::EResizeImageFilter Filter)
|
|
{
|
|
using namespace FImageCore;
|
|
|
|
// default Filter with no flags will clamp at edges
|
|
|
|
if ( Texture->GetTextureClass() == ETextureClass::Cube ||
|
|
Texture->GetTextureClass() == ETextureClass::CubeArray )
|
|
{
|
|
if ( Texture->Source.IsLongLatCubemap() )
|
|
{
|
|
// wrap X, clamp Y
|
|
return Filter | EResizeImageFilter::Flag_WrapX;
|
|
}
|
|
else
|
|
{
|
|
// clamp cube faces
|
|
return Filter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// see ComputeAddressMode
|
|
|
|
if ( Texture->bPreserveBorder )
|
|
{
|
|
// clamp
|
|
return Filter;
|
|
}
|
|
|
|
// @@ nonpow2, UI, NoMipMaps -> clamp ?
|
|
|
|
if ( Texture->GetTextureAddressX() == TA_Wrap )
|
|
{
|
|
Filter |= EResizeImageFilter::Flag_WrapX;
|
|
}
|
|
if ( Texture->GetTextureAddressY() == TA_Wrap )
|
|
{
|
|
Filter |= EResizeImageFilter::Flag_WrapY;
|
|
}
|
|
|
|
return Filter;
|
|
}
|
|
}
|
|
|
|
// use ResizeImage instead of DownsizeImageUsingTextureSettings ?
|
|
static bool UseResizeImageInsteadOfTextureSettings(UTexture * Texture)
|
|
{
|
|
if ( Texture->Source.IsLongLatCubemap() )
|
|
{
|
|
// longlat needs to wrap X and clamp Y which the TextureSettings resize can't do
|
|
return true;
|
|
}
|
|
|
|
if ( Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::StretchToPowerOfTwo ||
|
|
Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::StretchToSquarePowerOfTwo )
|
|
{
|
|
if ( ! Texture->Source.AreAllBlocksPowerOfTwo() )
|
|
{
|
|
// stretching required, ResizeImage instead
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( Texture->MipGenSettings == TMGS_NoMipmaps ||
|
|
Texture->MipGenSettings == TMGS_LeaveExistingMips ||
|
|
Texture->MipGenSettings == TMGS_Angular )
|
|
{
|
|
// TextureSettings mip gen is ill defined in this case, don't use it
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// resize so that the largest dimension is <= MaxSize
|
|
static bool ResizeTexture2D(UTexture* Texture, int32 MaxSize, const ITargetPlatform* TargetPlatform)
|
|
{
|
|
check( Texture->Source.GetNumLayers() == 1 );
|
|
check( Texture->Source.GetNumBlocks() == 1 );
|
|
const int32 LayerIndex = 0;
|
|
|
|
// We want to reduce the asset size so ignore the imported mip(s) (??)
|
|
const int32 MipIndex = 0;
|
|
FImage Image;
|
|
if (!Texture->Source.GetMipImage(Image, MipIndex))
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2D: Texture GetMipImage failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
if ( UseResizeImageInsteadOfTextureSettings(Texture) )
|
|
{
|
|
int32 TargetSizeX = Image.SizeX;
|
|
int32 TargetSizeY = Image.SizeY;
|
|
|
|
// stretch to pow2 like TextureCompressorModule
|
|
if ( Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::StretchToPowerOfTwo )
|
|
{
|
|
TargetSizeX = FMath::RoundUpToPowerOfTwo(Image.SizeX);
|
|
TargetSizeY = FMath::RoundUpToPowerOfTwo(Image.SizeY);
|
|
}
|
|
else if ( Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::StretchToSquarePowerOfTwo )
|
|
{
|
|
TargetSizeX = TargetSizeY = FMath::RoundUpToPowerOfTwo( FMath::Max(Image.SizeX,Image.SizeY) );
|
|
}
|
|
|
|
// do halving steps to get <= MaxSize
|
|
while( TargetSizeX > MaxSize || TargetSizeY > MaxSize )
|
|
{
|
|
TargetSizeX = FMath::Max(1,TargetSizeX>>1);
|
|
TargetSizeY = FMath::Max(1,TargetSizeY>>1);
|
|
}
|
|
|
|
if ( TargetSizeX >= Image.SizeX && TargetSizeY >= Image.SizeY )
|
|
{
|
|
// nothing to do
|
|
return false;
|
|
}
|
|
|
|
FImageCore::EResizeImageFilter Filter = SetResizeImageFilterEdgeModes(Texture,FImageCore::EResizeImageFilter::Default);
|
|
|
|
FImageCore::ResizeImageInPlace(Image,TargetSizeX,TargetSizeY,Filter);
|
|
}
|
|
else
|
|
{
|
|
bool MadeChanges = false;
|
|
if ( ! Texture->DownsizeImageUsingTextureSettings(TargetPlatform, Image, MaxSize, LayerIndex, MadeChanges) )
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2D: Texture DownsizeImageUsingTextureSettings failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
if ( ! MadeChanges )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Texture->PreEditChange(nullptr);
|
|
|
|
Texture->Source.Init(Image);
|
|
|
|
// if gamma was Pow22 it is now sRGB
|
|
Texture->bUseLegacyGamma = false;
|
|
|
|
// PostEditChange is called outside by our caller
|
|
|
|
return true;
|
|
}
|
|
|
|
static int32 RoundToNearestInt32PowerOfTwo(double X)
|
|
{
|
|
double Log2X = FMath::Log2(X);
|
|
int32 IntLog2X = FMath::RoundToInt32(Log2X);
|
|
|
|
return 1 << FMath::Clamp(IntLog2X,0,30);
|
|
}
|
|
|
|
// resize so that the largest dimension is == MaxSize
|
|
static bool ResizeTextureToNearestPow2(UTexture* Texture)
|
|
{
|
|
check( Texture->Source.GetNumLayers() == 1 );
|
|
check( Texture->Source.GetNumBlocks() == 1 );
|
|
const int32 LayerIndex = 0;
|
|
|
|
// discard imported mips :
|
|
const int32 MipIndex = 0;
|
|
|
|
FImage Image;
|
|
if (!Texture->Source.GetMipImage(Image, MipIndex))
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2D: Texture GetMipImage failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
check( Image.SizeX == Texture->Source.GetSizeX() );
|
|
check( Image.SizeY == Texture->Source.GetSizeY() );
|
|
|
|
int32 TargetSizeX,TargetSizeY;
|
|
|
|
// make the larger dimension go to nearest pow2 first
|
|
// then fix the smaller dimension for aspect ratio
|
|
if ( Image.SizeX >= Image.SizeY )
|
|
{
|
|
TargetSizeX = RoundToNearestInt32PowerOfTwo(Image.SizeX);
|
|
TargetSizeY = RoundToNearestInt32PowerOfTwo( (double) TargetSizeX * Image.SizeY / Image.SizeX );
|
|
}
|
|
else
|
|
{
|
|
TargetSizeY = RoundToNearestInt32PowerOfTwo(Image.SizeY);
|
|
TargetSizeX = RoundToNearestInt32PowerOfTwo( (double) TargetSizeY * Image.SizeX / Image.SizeY );
|
|
}
|
|
|
|
FImageCore::EResizeImageFilter Filter = SetResizeImageFilterEdgeModes(Texture,FImageCore::EResizeImageFilter::Default);
|
|
|
|
FImageCore::ResizeImageInPlace(Image,TargetSizeX,TargetSizeY,Filter);
|
|
|
|
Texture->PreEditChange(nullptr);
|
|
|
|
Texture->Source.Init(Image);
|
|
|
|
// if gamma was Pow22 it is now sRGB
|
|
Texture->bUseLegacyGamma = false;
|
|
|
|
// PostEditChange is called outside by our caller
|
|
|
|
return true;
|
|
}
|
|
|
|
// concatenate all the image payloads into one bulkdata, eg. for mips or blocks
|
|
static UE::Serialization::FEditorBulkData::FSharedBufferWithID MakeSharedBufferForImageDatas(const TArray<FImage> & InImages)
|
|
{
|
|
int64 SizeNeededInBytes = 0;
|
|
for (const FImage& Im : InImages)
|
|
{
|
|
check( Im.RawData.Num() == Im.GetImageSizeBytes() );
|
|
SizeNeededInBytes += Im.RawData.Num();
|
|
}
|
|
FUniqueBuffer WriteImageBuffer = FUniqueBuffer::Alloc(SizeNeededInBytes);
|
|
|
|
uint8* CurrentAddress = static_cast<uint8*>(WriteImageBuffer.GetData());
|
|
for (const FImage& Im : InImages)
|
|
{
|
|
FMemory::Memcpy(CurrentAddress, Im.RawData.GetData(), Im.RawData.Num());
|
|
CurrentAddress += Im.RawData.Num();
|
|
}
|
|
|
|
return WriteImageBuffer.MoveToShared();
|
|
}
|
|
|
|
static bool ResizeTexture2DBlocked(UTexture* Texture, int32 TotalMaxSize, const ITargetPlatform* TargetPlatform)
|
|
{
|
|
// does not support layers
|
|
check( Texture->Source.GetNumLayers() == 1 );
|
|
const int32 NumLayers = 1;
|
|
const int32 LayerIndex = 0;
|
|
|
|
check( Texture->Source.GetNumBlocks() > 1 );
|
|
|
|
// MaxSize is applied to the total UDIM size
|
|
|
|
FIntPoint LogicalSourceSize = Texture->Source.GetLogicalSize();
|
|
check( LogicalSourceSize.X > TotalMaxSize || LogicalSourceSize.Y > TotalMaxSize );
|
|
|
|
double ResizeRatio = double(TotalMaxSize) / FMath::Max(LogicalSourceSize.X,LogicalSourceSize.Y);
|
|
check( ResizeRatio < 1.0 );
|
|
|
|
TArray<FTextureSourceBlock> ResizedSourceBlocks;
|
|
ResizedSourceBlocks.Reserve(Texture->Source.GetNumBlocks());
|
|
|
|
TArray<FImage> ResizedBlocks;
|
|
ResizedBlocks.Reserve(Texture->Source.GetNumBlocks());
|
|
|
|
bool MadeAnyChanges = false;
|
|
|
|
for (int32 BlockIndex = 0; BlockIndex < Texture->Source.GetNumBlocks(); ++BlockIndex)
|
|
{
|
|
// We want to reduce the asset size so ignore the imported mip(s)
|
|
FImage SourceMip0;
|
|
const int32 MipIndex = 0;
|
|
if (!Texture->Source.GetMipImage(SourceMip0, BlockIndex, LayerIndex, MipIndex))
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2DBlocked: Texture GetMipImage failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
int32 NewSizeX = FMath::RoundToInt32( ResizeRatio * SourceMip0.SizeX );
|
|
int32 NewSizeY = FMath::RoundToInt32( ResizeRatio * SourceMip0.SizeY );
|
|
int32 BlockMaxSize = FMath::Max(NewSizeX,NewSizeY);
|
|
|
|
bool MadeChanges;
|
|
if ( ! Texture->DownsizeImageUsingTextureSettings(TargetPlatform, SourceMip0, BlockMaxSize, LayerIndex, MadeChanges) )
|
|
{
|
|
// critical error
|
|
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2DBlocked: Texture DownsizeImageUsingTextureSettings failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
MadeAnyChanges = MadeAnyChanges || MadeChanges;
|
|
|
|
FTextureSourceBlock& ResizedSourceBlock = ResizedSourceBlocks.AddDefaulted_GetRef();
|
|
Texture->Source.GetBlock(BlockIndex, ResizedSourceBlock);
|
|
|
|
FImage& ResizedBlock = ResizedBlocks.AddDefaulted_GetRef();
|
|
ResizedBlock = MoveTemp(SourceMip0);
|
|
|
|
ResizedSourceBlock.SizeX = ResizedBlock.SizeX;
|
|
ResizedSourceBlock.SizeY = ResizedBlock.SizeY;
|
|
ResizedSourceBlock.NumMips = 1;
|
|
}
|
|
|
|
if ( ! MadeAnyChanges )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Protect the code from an async build of the texture
|
|
Texture->PreEditChange(nullptr);
|
|
|
|
UE::Serialization::FEditorBulkData::FSharedBufferWithID ResizedImageBufferWithID = MakeSharedBufferForImageDatas(ResizedBlocks);
|
|
|
|
const ETextureSourceFormat SourceFormat = Texture->Source.GetFormat();
|
|
Texture->Source.InitBlocked(
|
|
&SourceFormat, // array of formats per layer
|
|
ResizedSourceBlocks.GetData(),
|
|
NumLayers,
|
|
ResizedSourceBlocks.Num(),
|
|
MoveTemp(ResizedImageBufferWithID)
|
|
);
|
|
|
|
// if gamma was Pow22 it is now sRGB
|
|
Texture->bUseLegacyGamma = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ResizeTextureToNearestPow2Blocked(UTexture* Texture)
|
|
{
|
|
// does not support layers
|
|
check( Texture->Source.GetNumLayers() == 1 );
|
|
const int32 NumLayers = 1;
|
|
const int32 LayerIndex = 0;
|
|
|
|
const int32 NumBlocks = Texture->Source.GetNumBlocks();
|
|
check( NumBlocks > 1 );
|
|
|
|
// Resize to Pow2 acts on each UDIM block, not the net size
|
|
|
|
TArray<FTextureSourceBlock> SourceBlocks;
|
|
SourceBlocks.SetNum(NumBlocks);
|
|
|
|
int32 BlockMaxSizeX = 0;
|
|
int32 BlockMaxSizeY = 0;
|
|
|
|
// get the largest of all blocks (same as VT builder)
|
|
for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex)
|
|
{
|
|
Texture->Source.GetBlock(BlockIndex, SourceBlocks[BlockIndex]);
|
|
|
|
BlockMaxSizeX = FMath::Max(BlockMaxSizeX, SourceBlocks[BlockIndex].SizeX );
|
|
BlockMaxSizeY = FMath::Max(BlockMaxSizeY, SourceBlocks[BlockIndex].SizeY );
|
|
}
|
|
|
|
int32 TargetSizeX,TargetSizeY;
|
|
|
|
// make the larger dimension go to nearest pow2 first
|
|
// then fix the smaller dimension for aspect ratio
|
|
if ( BlockMaxSizeX >= BlockMaxSizeY )
|
|
{
|
|
TargetSizeX = RoundToNearestInt32PowerOfTwo(BlockMaxSizeX);
|
|
TargetSizeY = RoundToNearestInt32PowerOfTwo( (double) TargetSizeX * BlockMaxSizeY / BlockMaxSizeX );
|
|
}
|
|
else
|
|
{
|
|
TargetSizeY = RoundToNearestInt32PowerOfTwo(BlockMaxSizeY);
|
|
TargetSizeX = RoundToNearestInt32PowerOfTwo( (double) TargetSizeY * BlockMaxSizeX / BlockMaxSizeY );
|
|
}
|
|
|
|
TArray<FImage> ResizedBlocks;
|
|
ResizedBlocks.SetNum(NumBlocks);
|
|
|
|
for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex)
|
|
{
|
|
FImage & SourceMip0 = ResizedBlocks[BlockIndex];
|
|
|
|
// Drops any imported mip data (yuck?)
|
|
const int32 MipIndex = 0;
|
|
if (!Texture->Source.GetMipImage(SourceMip0, BlockIndex, LayerIndex, MipIndex))
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2DBlocked: Texture GetMipImage failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
// all blocks have to be pow2 and the same aspect ratio
|
|
// but they don't have to all be the same size
|
|
// if they were different sizes, we could use a mip of TargetSize here; eg. (TargetSizeX>>1)
|
|
// if that was closer to the original size
|
|
// don't bother for now, just make them all the same size
|
|
|
|
// if this block is == TargetSize, this is a nop
|
|
FImageCore::ResizeImageInPlace(SourceMip0,TargetSizeX,TargetSizeY);
|
|
|
|
SourceBlocks[BlockIndex].SizeX = TargetSizeX;
|
|
SourceBlocks[BlockIndex].SizeY = TargetSizeY;
|
|
SourceBlocks[BlockIndex].NumMips = 1;
|
|
}
|
|
|
|
// Protect the code from an async build of the texture
|
|
Texture->PreEditChange(nullptr);
|
|
|
|
UE::Serialization::FEditorBulkData::FSharedBufferWithID ResizedImageBufferWithID = MakeSharedBufferForImageDatas(ResizedBlocks);
|
|
|
|
const ETextureSourceFormat SourceFormat = Texture->Source.GetFormat();
|
|
Texture->Source.InitBlocked(
|
|
&SourceFormat, // array of formats per layer
|
|
SourceBlocks.GetData(),
|
|
NumLayers,
|
|
NumBlocks,
|
|
MoveTemp(ResizedImageBufferWithID)
|
|
);
|
|
|
|
// if gamma was Pow22 it is now sRGB
|
|
Texture->bUseLegacyGamma = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace Private
|
|
|
|
static bool IsTextureResizeSupported(UTexture * Texture)
|
|
{
|
|
if ( ! ensure( Texture->Source.IsValid() ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check if we don't know how to resize that texture
|
|
|
|
if ( Texture->Source.GetNumMips() > 1 && Texture->MipGenSettings == TMGS_LeaveExistingMips )
|
|
{
|
|
// should not do this if MipGen == LeaveExisting ; or maybe warn?
|
|
// return false;
|
|
|
|
// go ahead and do it, but warn:
|
|
|
|
UE_LOG(LogTexture,Warning,TEXT("DownsizeTextureSourceData: Texture has LeaveExistingMips ; they will be discarded! [%s]"),
|
|
*Texture->GetFullName());
|
|
}
|
|
|
|
// we only support 1 layer currently
|
|
if (Texture->Source.GetNumLayers() != 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!(Texture->Source.GetTextureClass() == ETextureClass::Cube || Texture->Source.GetTextureClass() == ETextureClass::TwoD))
|
|
{
|
|
// array, cubearray, volume, not supported
|
|
return false;
|
|
}
|
|
|
|
if (Texture->Source.GetTextureClass() == ETextureClass::Cube)
|
|
{
|
|
if (Texture->Source.IsLongLatCubemap())
|
|
{
|
|
if (Texture->Source.GetNumSlices() != 1)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (Texture->Source.GetNumSlices() != 6)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TEXTUREUTILITIESCOMMON_API bool ResizeTextureSourceDataToNearestPowerOfTwo(UTexture* Texture)
|
|
{
|
|
if ( ! IsTextureResizeSupported(Texture) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( Texture->Source.AreAllBlocksPowerOfTwo() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Texture->Source.GetNumBlocks() == 1)
|
|
{
|
|
return Private::ResizeTextureToNearestPow2(Texture);
|
|
}
|
|
else
|
|
{
|
|
// UDIM VT
|
|
return Private::ResizeTextureToNearestPow2Blocked(Texture);
|
|
}
|
|
}
|
|
|
|
TEXTUREUTILITIESCOMMON_API bool DownsizeTextureSourceData(UTexture* Texture, int32 TargetSourceSize, const ITargetPlatform* TargetPlatform)
|
|
{
|
|
if ( ! IsTextureResizeSupported(Texture) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FIntPoint SourceSize = Texture->Source.GetLogicalSize();
|
|
if (SourceSize.X <= TargetSourceSize && SourceSize.Y <= TargetSourceSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Texture->Source.GetNumBlocks() == 1)
|
|
{
|
|
return Private::ResizeTexture2D(Texture, TargetSourceSize, TargetPlatform);
|
|
}
|
|
else
|
|
{
|
|
// UDIM VT
|
|
return Private::ResizeTexture2DBlocked(Texture, TargetSourceSize, TargetPlatform);
|
|
}
|
|
}
|
|
|
|
TEXTUREUTILITIESCOMMON_API bool DownsizeTextureSourceDataNearRenderingSize(UTexture* Texture, const ITargetPlatform* TargetPlatform, int32 AdditionalSourceSizeLimit)
|
|
{
|
|
if ( ! Texture->Source.IsValid() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32 BeforeSizeX;
|
|
int32 BeforeSizeY;
|
|
Texture->GetBuiltTextureSize(TargetPlatform, BeforeSizeX, BeforeSizeY);
|
|
|
|
int32 TargetSizeInGame = FMath::Max(BeforeSizeX, BeforeSizeY);
|
|
|
|
int32 TargetSourceSize = TargetSizeInGame;
|
|
if (Texture->Source.IsLongLatCubemap())
|
|
{
|
|
// The function return the max size of the generated cube from the source long lat
|
|
TargetSourceSize = UE::TextureBuildUtilities::ComputeLongLatCubemapExtents(TargetSizeInGame,0);
|
|
}
|
|
|
|
TargetSourceSize = FMath::Min(TargetSourceSize,AdditionalSourceSizeLimit);
|
|
|
|
if (DownsizeTextureSourceData(Texture, TargetSourceSize, TargetPlatform))
|
|
{
|
|
Texture->LODBias = 0;
|
|
|
|
// this counts as a reimport :
|
|
UE::TextureUtilitiesCommon::ApplyDefaultsForNewlyImportedTextures(Texture,true);
|
|
|
|
Texture->PostEditChange();
|
|
|
|
// check that GetBuiltTextureSize was preserved :
|
|
int32 AfterSizeX;
|
|
int32 AfterSizeY;
|
|
Texture->GetBuiltTextureSize(TargetPlatform, AfterSizeX, AfterSizeY);
|
|
|
|
if ( BeforeSizeX != AfterSizeX ||
|
|
BeforeSizeY != AfterSizeY )
|
|
{
|
|
UE_LOG(LogTexture,Warning,TEXT("DownsizeTextureSourceDataNearRenderingSize failed to preserve built size; was: %dx%d now: %dx%d on [%s]"),
|
|
BeforeSizeX,BeforeSizeY,
|
|
AfterSizeX,AfterSizeY,
|
|
*Texture->GetFullName());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// PreEditChange may have been called even if DownsizeTextureSourceData return false
|
|
// we don't PostEditChange here
|
|
// that's okay but not great
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
TEXTUREUTILITIESCOMMON_API bool ChangeTextureSourceFormat(UTexture* Texture, ETextureSourceFormat NewFormat)
|
|
{
|
|
if ( ! Texture->Source.IsValid() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// we only support 1 layer currently
|
|
if (Texture->Source.GetNumLayers() != 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ETextureSourceFormat OldFormat = Texture->Source.GetFormat(0);
|
|
if ( OldFormat == NewFormat )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ERawImageFormat::Type NewRIF = FImageCoreUtils::ConvertToRawImageFormat(NewFormat);
|
|
EGammaSpace NewGamma = ( Texture->SRGB && ERawImageFormat::GetFormatNeedsGammaSpace(NewRIF) ) ? EGammaSpace::sRGB : EGammaSpace::Linear;
|
|
|
|
if ( Texture->Source.GetNumBlocks() == 1 && Texture->Source.GetNumMips() == 1 )
|
|
{
|
|
const int32 MipIndex = 0;
|
|
|
|
FImage SourceMip;
|
|
if (!Texture->Source.GetMipImage(SourceMip, MipIndex))
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("ChangeTextureSourceFormat: Texture GetMipImage failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
FImage NewMip;
|
|
SourceMip.CopyTo(NewMip,NewRIF,NewGamma);
|
|
|
|
Texture->PreEditChange(nullptr);
|
|
|
|
Texture->Source.Init(NewMip);
|
|
}
|
|
else // blocks and/or mips
|
|
{
|
|
// all blocks of a UDIM have the same format; Layers do not
|
|
int32 NumLayers = 1;
|
|
int32 LayerIndex = 0;
|
|
|
|
int32 NumBlocks = Texture->Source.GetNumBlocks();
|
|
check( NumBlocks >= 1 );
|
|
|
|
TArray<FTextureSourceBlock> NewBlocks;
|
|
NewBlocks.Reserve(NumBlocks);
|
|
|
|
TArray<FImage> NewImages;
|
|
NewImages.Reserve(NumBlocks*16); // *16 for mips
|
|
|
|
for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex)
|
|
{
|
|
FTextureSourceBlock Block;
|
|
Texture->Source.GetBlock(BlockIndex,Block);
|
|
|
|
// NewBlocks has sizes, they don't change
|
|
NewBlocks.Add(Block);
|
|
|
|
for(int32 MipIndex=0; MipIndex < Block.NumMips;MipIndex++)
|
|
{
|
|
FImage SourceMip;
|
|
if ( ! Texture->Source.GetMipImage(SourceMip, BlockIndex, LayerIndex, MipIndex) )
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("ChangeTextureSourceFormat: Texture GetMipImage failed [%s]"),
|
|
*Texture->GetFullName());
|
|
|
|
return false;
|
|
}
|
|
|
|
FImage & NewMip = NewImages.AddDefaulted_GetRef();
|
|
|
|
SourceMip.CopyTo(NewMip,NewRIF,NewGamma);
|
|
}
|
|
}
|
|
|
|
UE::Serialization::FEditorBulkData::FSharedBufferWithID ResizedImageBufferWithID = Private::MakeSharedBufferForImageDatas(NewImages);
|
|
|
|
Texture->PreEditChange(nullptr);
|
|
|
|
Texture->Source.InitBlocked(
|
|
&NewFormat, // array of formats per layer
|
|
NewBlocks.GetData(),
|
|
NumLayers,
|
|
NewBlocks.Num(),
|
|
MoveTemp(ResizedImageBufferWithID)
|
|
);
|
|
}
|
|
|
|
// if gamma was Pow22 it is now sRGB
|
|
Texture->bUseLegacyGamma = false;
|
|
|
|
// this counts as a reimport :
|
|
UE::TextureUtilitiesCommon::ApplyDefaultsForNewlyImportedTextures(Texture,true);
|
|
|
|
Texture->PostEditChange();
|
|
|
|
check( Texture->Source.GetGammaSpace(0) == NewGamma );
|
|
|
|
return true;
|
|
}
|
|
|
|
// calls Pre/PostEditChange :
|
|
TEXTUREUTILITIESCOMMON_API bool CompressTextureSourceWithJPEG(UTexture* Texture,int32 Quality)
|
|
{
|
|
if ( ! Texture->Source.IsValid() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// we only support 1 layer currently
|
|
if (Texture->Source.GetNumLayers() != 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Texture->Source.GetNumBlocks() != 1 )
|
|
{
|
|
// JPEG does not support UDIM/blocks ; fix me?
|
|
return false;
|
|
}
|
|
|
|
if (Texture->Source.GetSourceCompression() == ETextureSourceCompressionFormat::TSCF_JPEG )
|
|
{
|
|
// already JPEG
|
|
return false;
|
|
}
|
|
|
|
if ( Texture->Source.GetNumSlices() != 1 )
|
|
{
|
|
// 1 mip, 1 slice only
|
|
return false;
|
|
}
|
|
|
|
ETextureSourceFormat Format = Texture->Source.GetFormat(0);
|
|
if ( Format != TSF_G8 && Format != TSF_BGRA8 )
|
|
{
|
|
// must be 8 bit
|
|
return false;
|
|
}
|
|
|
|
// we do kill existing mips to match the behavior of the other conversions in here
|
|
|
|
// okay, looks good, do it!
|
|
|
|
FImage Image;
|
|
if ( ! Texture->Source.GetMipImage(Image,0) )
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("CompressTextureSourceWithJPEG: Texture GetMipImage failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
// JPEG it :
|
|
|
|
TArray64<uint8> JPEGData;
|
|
if ( ! FImageUtils::CompressImage(JPEGData,TEXT(".jpg"),Image,Quality) )
|
|
{
|
|
UE_LOG(LogTexture,Error,TEXT("CompressTextureSourceWithJPEG: Texture CompressImage failed [%s]"),
|
|
*Texture->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
Texture->PreEditChange(nullptr);
|
|
|
|
// Format stays BGRA8 or G8
|
|
const int32 NumMips = 1;
|
|
Texture->Source.InitWithCompressedSourceData(Image.SizeX,Image.SizeY,NumMips,Format,
|
|
JPEGData,
|
|
ETextureSourceCompressionFormat::TSCF_JPEG);
|
|
|
|
Texture->PostEditChange();
|
|
|
|
return true;
|
|
}
|
|
|
|
} // End namespace UE::TextureUtilitiesCommon::Experimental
|
|
|
|
#endif //WITH_EDITOR
|