Files
UnrealEngine/Engine/Plugins/TextureGraph/Source/TextureGraphEngine/Export/TextureExporter.cpp
2025-05-18 13:04:45 +08:00

302 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TextureExporter.h"
#include "2D/Tex.h"
#include "Async/ParallelFor.h"
#include "Data/TiledBlob.h"
#include "Job/JobBatch.h"
#include "Job/Scheduler.h"
#include "Misc/Paths.h"
#include "TextureGraphEngine.h"
#include "Model/Mix/MixSettings.h"
#include "Model/Mix/MixUpdateCycle.h"
#include "T_ExportAsUAsset.h"
#include "UObject/SavePackage.h"
#include "Device/FX/DeviceBuffer_FX.h"
#include <AssetRegistry/AssetRegistryModule.h>
#include <Engine/Texture2D.h>
#include "2D/TextureHelper.h"
#include "TextureResource.h"
#if WITH_EDITOR
#include "FileHelpers.h"
#include "AssetToolsModule.h"
#include "AssetViewUtils.h"
#endif
#include <Misc/PackageName.h>
#include "Misc/FileHelper.h"
#include "TextureCompiler.h"
DEFINE_LOG_CATEGORY(ExportLogs);
bool TextureExporter::IsPackageNameValid(FString Path, FString AssetName)
{
return FPackageName::TryConvertLongPackageNameToFilename(Path, AssetName);
}
bool TextureExporter::IsFileNameValid(FName FileName, FText& Reason)
{
FileName.IsValidObjectName(Reason);
FFileHelper::IsFilenameValidForSaving(FileName.ToString(), Reason);
return Reason.IsEmpty();
}
bool TextureExporter::IsFolderPathValid(FString FolderPath, FText& Reason)
{
FName::IsValidXName(FolderPath, INVALID_OBJECTPATH_CHARACTERS INVALID_LONGPACKAGE_CHARACTERS, &Reason);
if (!Reason.IsEmpty())
{
Reason = FText::FromString(Reason.ToString().Replace(TEXT("Name"), TEXT("Path")));
}
FPaths::ValidatePath(FolderPath, &Reason);
return Reason.IsEmpty();
}
bool TextureExporter::IsFilePathValid(const FName InFileName, const FName InFolderPath, FString& OutErrors)
{
FText FileNameError;
IsFileNameValid(InFileName, FileNameError);
if(!FileNameError.IsEmpty())
{
FString Message = "Invalid file name. " + FileNameError.ToString();
OutErrors += Message;
}
FText FullPathError;
const FString AssetName = FString::Printf(TEXT("/%s.%s"), *InFileName.ToString(), *InFileName.ToString());
const FString FullPath = FPaths::Combine(InFolderPath.ToString(), AssetName);
#if WITH_EDITOR
if(!AssetViewUtils::IsValidObjectPathForCreate(FullPath, FullPathError, true))
{
FString Message = "Invalid file path. " + FullPathError.ToString();
OutErrors += Message;
}
#endif
FText PathValidError;
IsFolderPathValid(InFolderPath.ToString(), PathValidError);
if(!PathValidError.IsEmpty())
{
FString Message = "Invalid folder path Error: " + PathValidError.ToString();
OutErrors += Message;
}
return OutErrors.IsEmpty();
}
AsyncBool TextureExporter::ExportAllMapsAsUAsset(UMixInterface* MixObj, const FString& OutputFolder, const FExportSettings& ExportSettings)
{
FInvalidationDetails Details;
Details.Mix = MixObj;
Details.All();
JobBatchPtr Batch = JobBatch::Create(Details);
MixUpdateCyclePtr Cycle = Batch->GetCycle();
const ExportMaps Maps = ExportSettings.ExportPreset;
for (const auto& ExportMap : Maps)
{
auto& MapSettings = ExportMap.second;
FString Path = MapSettings.Path;
if (ExportMap.second.UseOverridePath)
{
Path = OutputFolder;
}
T_ExportAsUAsset::ExportAsUAsset(Cycle, 0, ExportMap.second, Path);
}
TextureGraphEngine::GetScheduler()->AddBatch(Batch);
return cti::make_continuable<bool>([=](auto&& Promise) mutable
{
Batch->OnDone([=, FWD_PROMISE(Promise)](JobBatch*) mutable
{
Promise.set_value(true);
});
});
}
AsyncInt TextureExporter::ExportRawAsUAsset(RawBufferPtr RawObj,const FExportMapSettings& Setting, const FString& CompletePath, const FName& TextureName)
{
return cti::make_continuable<int32>([RawObj,Setting, CompletePath, TextureName](auto Promise) mutable {
FText PathError;
FPaths::ValidatePath(CompletePath, &PathError);
if (CompletePath.IsEmpty())
{
UE_LOG(LogTexture, Warning, TEXT("Provide path is empty!"));
Promise.set_value(-1);
}
else if (!PathError.IsEmpty())
{
UE_LOG(LogTexture, Warning, TEXT("Invalid file path provided: %s"), *(PathError.ToString()));
Promise.set_value(-1);
}
if (!RawObj || RawObj->GetData() == nullptr)
{
UE_LOG(LogTexture, Warning, TEXT("Can't export with invalid RawBuffer: %s"), *(RawObj->GetName()));
Promise.set_value(-1);
}
const EPixelFormat PixelFormat = BufferDescriptor::BufferPixelFormat(RawObj->GetDescriptor().Format, RawObj->GetDescriptor().ItemsPerPoint);
const ETextureSourceFormat SourceFormat = TextureHelper::GetTextureSourceFormat(RawObj->GetDescriptor().Format, RawObj->GetDescriptor().ItemsPerPoint);
FString PackageName = FPaths::Combine(CompletePath, TextureName.ToString());
FString SuggestedName = TextureName.ToString();
FPackagePath PackagePath;
#if WITH_EDITOR
bool bCanOverriteTexture = true;
// Get the Asset Registry Module
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
// Get all asset data in the Content Browser
TArray<FAssetData> AssetData;
AssetRegistryModule.Get().GetAssetsByPackageName(FName(*PackageName), AssetData);
if (AssetData.Num() > 0)
{
bool bIsTextureAsset = false;
FString AssetType = "";
// Iterate through the asset data
for (const FAssetData& Asset : AssetData)
{
if (Asset.IsInstanceOf(UTexture::StaticClass()))
{
bIsTextureAsset = true;
break;
}
}
// It is an error if we are trying to replace an object of a different class
if (!bIsTextureAsset)
{
bCanOverriteTexture = false;
UE_LOG(LogTexture, Warning, TEXT("Creating a Unique name for the output texture as another asset of different type %s exists with same name %s"), *AssetType, *PackageName);
}
}
//Using override paths when exporting from blueprints
if (!Setting.OverwriteTextures || !bCanOverriteTexture)
{
//Helps creating a unique Name if a file with same name exists
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateUniqueAssetName(PackageName, TEXT(""), PackageName, SuggestedName);
}
const uint8* RawBufferData = RawObj->GetData();
size_t RawBufferSize = RawObj->GetUnpaddedSize();
UPackage* Package = CreatePackage( *PackageName);
Package->FullyLoad();
UTexture2D* NewTexture = nullptr;
int32 WidthToUse = Setting.Width == 0 ? RawObj->Width() : Setting.Width;
int32 HeightToUse = Setting.Height == 0 ? RawObj->Height() : Setting.Height;
const int32 NumBlocksX = WidthToUse / GPixelFormats[PixelFormat].BlockSizeX;
const int32 NumBlocksY = HeightToUse / GPixelFormats[PixelFormat].BlockSizeY;
uint32 BufferSizeRequired = NumBlocksX * GPixelFormats[PixelFormat].BlockBytes * NumBlocksY;
if (BufferSizeRequired > RawBufferSize)
{
UE_LOG(LogTexture, Warning, TEXT("Export dimensions are invalid and require buffer size larger than is available. Expected to be less than: %d [Asked: %d, %dx%d]. Clamping ..."),
RawBufferSize, BufferSizeRequired, WidthToUse, HeightToUse);
WidthToUse = RawObj->Width();
HeightToUse = RawObj->Height();
}
if ((WidthToUse % GPixelFormats[PixelFormat].BlockSizeX) == 0 &&
(HeightToUse % GPixelFormats[PixelFormat].BlockSizeY) == 0)
{
NewTexture = NewObject<UTexture2D>(Package, FName(SuggestedName), RF_Public | RF_Standalone | RF_MarkAsRootSet);
// if there is padding, we need to remove that
if (RawObj->IsPadded())
{
FTexturePlatformData* PlatformData = NewTexture->GetPlatformData();
if (!PlatformData)
{
PlatformData = new FTexturePlatformData();
NewTexture->SetPlatformData(PlatformData);
}
PlatformData->SizeX = WidthToUse;
PlatformData->SizeY = HeightToUse;
PlatformData->SetNumSlices(1);
PlatformData->PixelFormat = PixelFormat;
// Get First MipMap
//
FTexture2DMipMap* Mip;
if (PlatformData->Mips.IsEmpty())
{
Mip = new FTexture2DMipMap(0, 0);
PlatformData->Mips.Add(Mip);
}
else
{
Mip = &PlatformData->Mips[0];
}
Mip->SizeX = WidthToUse;
Mip->SizeY = HeightToUse;
Mip->SizeZ = 1;
const size_t DestSize = RawObj->GetUnpaddedSize();
Mip->BulkData.Lock(LOCK_READ_WRITE);
uint8* TextureData = Mip->BulkData.Realloc(DestSize);
RawObj->CopyUnpaddedBytes(TextureData);
Mip->BulkData.Unlock();
RawBufferData = TextureData;
}
}
else
{
UE_LOG(LogTexture, Warning, TEXT("Cannot Export specified Pixel Format %d as UTexture2D"), PixelFormat);
Promise.set_value(-1);
return;
}
NewTexture->SRGB = Setting.IsSRGB;
NewTexture->LODGroup = Setting.LODGroup;
NewTexture->CompressionSettings = Setting.Compression;
NewTexture->Source.Init(WidthToUse, HeightToUse, 1,1, SourceFormat, RawBufferData);
NewTexture->UpdateResource();
FAssetRegistryModule::AssetCreated(NewTexture);
Package->FullyLoad();
FTextureCompilingManager::Get().FinishCompilation({NewTexture});
bool Success = true;
if (Setting.bSave)
{
Success = UEditorLoadingAndSavingUtils::SavePackages({ Package }, false);
}
else
{
Package->SetDirtyFlag(true);
}
Promise.set_value(Success);
#else
Promise.set_value(-1);
#endif
});
}
AsyncBool TextureExporter::ExportAsUAsset(UMixInterface* MixObj, const FExportSettings& ExportSettings, FString CurrentPath)
{
return ExportAllMapsAsUAsset(MixObj, CurrentPath, ExportSettings);
}
FExportMapSettings TextureExporter::GetExportSettingsForTarget(FExportSettings& ExportSettings, TiledBlobPtr BlobObj, FName Name)
{
if (!FSlateApplication::IsInitialized() || !FApp::CanEverRender())
return FExportMapSettings();
ExportSettings.PresetName = "ExportAsUAssetPreset";
FExportMapSettings TargetMap;
TargetMap.Name = Name;
TargetMap.IsSRGB = BlobObj->GetDescriptor().bIsSRGB;
TargetMap.Map = BlobObj;
TargetMap.Width = TargetMap.Map->GetWidth();
TargetMap.Height = TargetMap.Map->GetHeight();
TargetMap.Compression = TextureCompressionSettings::TC_Default;
TargetMap.OnDone.BindRaw(&ExportSettings, &FExportSettings::OnMapExportDone);
return TargetMap;
}
void TextureExporter::ExportAsUAsset(UMixInterface* MixObj, FName AssetName, FString CurrentPath, const FExportSettings& Setting)
{
ExportAllMapsAsUAsset(MixObj, CurrentPath, Setting);
}