Files
2025-05-18 13:04:45 +08:00

426 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DatasmithRuntimeAuxiliaryData.h"
#include "DatasmithRuntimeUtils.h"
#include "LogCategory.h"
#include "DatasmithUtils.h"
#include "IDatasmithSceneElements.h"
#include "Async/Async.h"
#include "Engine/TextureLightProfile.h"
#include "IESConverter.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "RHI.h"
#if WITH_EDITORONLY_DATA
#include "EditorFramework/AssetImportData.h"
#endif
namespace DatasmithRuntime
{
using FDataCleanupFunc = TFunction<void(uint8*, const FUpdateTextureRegion2D*)>;
bool GetTextureDataForIes(const TCHAR* Filename, FTextureData& TextureData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(DatasmithRuntime::GetTextureDataForIes);
TArray<uint8> Buffer;
if (!(FFileHelper::LoadFileToArray(Buffer, Filename) && Buffer.Num() > 0))
{
return false;
}
// checks for .IES extension to avoid wasting loading large assets just to reject them during header parsing
FIESConverter IESConverter(Buffer.GetData(), Buffer.Num());
if(IESConverter.IsValid())
{
TextureData.Width = IESConverter.GetWidth();
TextureData.Height = IESConverter.GetHeight();
TextureData.Brightness = IESConverter.GetBrightness();
TextureData.BytesPerPixel = 8; // RGBA16F
TextureData.Pitch = TextureData.Width * TextureData.BytesPerPixel;
TextureData.TextureMultiplier = IESConverter.GetMultiplier();
const TArray<uint8>& RAWData = IESConverter.GetRawData();
TextureData.ImageData = (uint8*)FMemory::Malloc(RAWData.Num() * sizeof(uint8), 0x20);
FMemory::Memcpy(TextureData.ImageData, RAWData.GetData(), RAWData.Num() * sizeof(uint8));
return true;
}
return false;
}
UTexture2D* CreateImageTexture(UTexture2D* Texture2D, FTextureData& TextureData, IDatasmithTextureElement* TextureElement, FDataCleanupFunc& DataCleanupFunc)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::CreateImageTexture);
if (Texture2D == nullptr)
{
Texture2D = UTexture2D::CreateTransient(TextureData.Width, TextureData.Height, TextureData.PixelFormat);
if (!Texture2D)
{
return nullptr;
}
FString TextureName = FString::Printf(TEXT("T_%s_%d"), TextureElement->GetName(), TextureElement->GetNodeId());
#ifdef ASSET_DEBUG
UPackage* Package = CreatePackage(*FPaths::Combine( TEXT("/Game/Runtime/Textures"), TextureName));
RenameObject(Texture2D, *TextureName, Package);
Texture2D->SetFlags(RF_Public);
#else
RenameObject(Texture2D, *TextureName);
#endif
}
#if WITH_EDITORONLY_DATA
FAssetImportInfo Info;
Info.Insert(FAssetImportInfo::FSourceFile(TextureElement->GetFile()));
Texture2D->AssetImportData->SourceData = MoveTemp(Info);
const float RGBCurve = TextureElement->GetRGBCurve();
if (FMath::IsNearlyEqual(RGBCurve, 1.0f) == false && RGBCurve > 0.f)
{
Texture2D->AdjustRGBCurve = RGBCurve;
}
#endif
Texture2D->SRGB = TextureElement->GetSRGB() == EDatasmithColorSpace::sRGB;
// Ensure there's no compression (we're editing pixel-by-pixel)
Texture2D->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
// Update the texture with these new settings
Texture2D->UpdateResource();
// The content of the texture has changed, update it
if (TextureData.ImageData != nullptr)
{
TextureData.Region = FUpdateTextureRegion2D(0, 0, 0, 0, TextureData.Width, TextureData.Height);
Texture2D->UpdateTextureRegions(0, 1, &TextureData.Region, TextureData.Pitch, TextureData.BytesPerPixel, TextureData.ImageData, DataCleanupFunc );
}
return Texture2D;
}
UTextureLightProfile* CreateIESTexture(UTextureLightProfile* Texture, FTextureData& TextureData, IDatasmithTextureElement* TextureElement, FDataCleanupFunc& DataCleanupFunc)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::CreateIESTexture);
if (Texture == nullptr)
{
Texture = NewObject<UTextureLightProfile>();
if (!Texture)
{
return nullptr;
}
FString TextureName = FString::Printf(TEXT("T_%s_%d"), TextureElement->GetName(), TextureElement->GetNodeId());
TextureName = FDatasmithUtils::SanitizeObjectName(TextureName);
#ifdef ASSET_DEBUG
UPackage* Package = CreatePackage(*FPaths::Combine( TEXT("/Game/Runtime/Textures"), TextureName));
RenameObject(Texture, *TextureName, Package);
Texture->SetFlags(RF_Public);
#else
RenameObject(Texture, *TextureName);
#endif
}
// TextureData.ImageData should not be null
ensure(TextureData.ImageData);
#if WITH_EDITORONLY_DATA
FAssetImportInfo Info;
Info.Insert(FAssetImportInfo::FSourceFile(TextureElement->GetFile()));
Texture->AssetImportData->SourceData = MoveTemp(Info);
#endif
#if WITH_EDITOR
Texture->Source.Init(
TextureData.Width,
TextureData.Height,
/*NumSlices=*/ 1,
1,
TSF_RGBA16F,
TextureData.ImageData
);
DataCleanupFunc(nullptr, nullptr);
#endif
Texture->LODGroup = TEXTUREGROUP_IESLightProfile;
Texture->AddressX = TA_Clamp;
Texture->AddressY = TA_Clamp;
Texture->CompressionSettings = TC_HDR;
#if WITH_EDITORONLY_DATA
Texture->MipGenSettings = TMGS_NoMipmaps;
#endif
Texture->Brightness = TextureData.Brightness;
Texture->TextureMultiplier = TextureData.TextureMultiplier;
// Update the texture with these new settings
Texture->UpdateResource();
#if !WITH_EDITOR
TextureData.Region = FUpdateTextureRegion2D(0, 0, 0, 0, TextureData.Width, TextureData.Height);
Texture->UpdateTextureRegions(0, 1, &TextureData.Region, TextureData.Pitch, TextureData.BytesPerPixel, TextureData.ImageData, DataCleanupFunc );
#endif
return Texture;
}
EActionResult::Type FSceneImporter::CreateTexture(FSceneGraphId ElementId)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::CreateTexture);
FAssetData& AssetData = AssetDataList[ElementId];
FTextureData& TextureData = TextureDataList[ElementId];
IDatasmithTextureElement* TextureElement = static_cast<IDatasmithTextureElement*>(Elements[ AssetData.ElementId ].Get());
// If the load of the image has failed, cleanup the TextureData and return
if (TextureData.Width == 0 || TextureData.Height == 0 || TextureData.ImageData == nullptr)
{
if (UObject* THelper = AssetData.GetObject<>())
{
FAssetRegistry::UnregisteredAssetsData(THelper, SceneKey, [](FAssetData& AssetData) -> void
{
AssetData.AddState(EAssetState::Completed);
AssetData.Object.Reset();
});
}
UE_LOG(LogDatasmithRuntime, Warning, TEXT("Failed to create texture %s"), TextureElement->GetName());
return EActionResult::Failed;
}
FDataCleanupFunc DataCleanupFunc;
DataCleanupFunc = [this, ElementId](uint8* SrcData, const FUpdateTextureRegion2D* Regions) -> void
{
FTextureData& TextureData = this->TextureDataList[ElementId];
FMemory::Free(TextureData.ImageData);
TextureData.ImageData = nullptr;
};
UObject* THelper = FAssetRegistry::FindObjectFromHash(AssetData.Hash);
ensure(THelper);
UTexture* Texture = nullptr;
if (TextureElement->GetTextureMode() == EDatasmithTextureMode::Ies)
{
Texture = CreateIESTexture(AssetData.GetObject<UTextureLightProfile>(), TextureData, TextureElement, DataCleanupFunc);
}
else
{
Texture = CreateImageTexture(AssetData.GetObject<UTexture2D>(), TextureData, TextureElement, DataCleanupFunc);
}
if (Texture)
{
// Apply metadata on newly created texture if any
ApplyMetadata(AssetData.MetadataId, Texture);
uint32 TextureHash = GetTypeHash(TextureElement->CalculateElementHash(true), EDataType::Texture);
FAssetRegistry::UnregisteredAssetsData(THelper, SceneKey, [this, &Texture, TextureHash](FAssetData& AssetData) -> void
{
AssetData.Object = Texture;
AssetData.Hash = TextureHash;
FAssetRegistry::RegisterAssetData(Texture, this->SceneKey, AssetData);
});
FAssetRegistry::SetObjectCompletion(Texture, true);
}
else
{
FAssetRegistry::UnregisteredAssetsData(THelper, SceneKey, [this](FAssetData& AssetData) -> void
{
AssetData.AddState(EAssetState::Completed);
AssetData.Object.Reset();
});
UE_LOG(LogDatasmithRuntime, Warning, TEXT("Failed to create texture %s"), TextureElement->GetName());
}
ActionCounter.Increment();
return Texture ? EActionResult::Succeeded : EActionResult::Failed;
}
bool FSceneImporter::LoadTexture(FSceneGraphId ElementId)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::LoadTexture);
FTextureData& TextureData = TextureDataList[ElementId];
IDatasmithTextureElement* TextureElement = static_cast<IDatasmithTextureElement*>(Elements[ ElementId ].Get());
// If image file does not exist, add scene's resource path if valid
if (!FPaths::FileExists(TextureElement->GetFile()) && FPaths::DirectoryExists(SceneElement->GetResourcePath()))
{
TextureElement->SetFile( *FPaths::Combine(SceneElement->GetResourcePath(), TextureElement->GetFile()) );
}
bool bSuccessfulLoad = false;
FString TextureName(TextureElement->GetName());
if (TextureElement->GetTextureMode() == EDatasmithTextureMode::Ies)
{
bSuccessfulLoad = GetTextureDataForIes(TextureElement->GetFile(), TextureData);
}
else
{
const bool bCreateNormal = ( TextureElement->GetTextureMode() == EDatasmithTextureMode::Bump );
bSuccessfulLoad = GetTextureDataFromFile(TextureElement->GetFile(), EDSResizeTextureMode::NearestPowerOfTwo, GMaxTextureDimensions, bCreateNormal, TextureData);
if (!bSuccessfulLoad)
{
EDatasmithTextureFormat TextureFormat;
uint32 ByteCount;
const uint8* Bytes = TextureElement->GetData(ByteCount, TextureFormat);
if (Bytes != nullptr && ByteCount > 0)
{
TArray<uint8> ByteArray;
ByteArray.SetNumUninitialized(ByteCount);
FPlatformMemory::Memcpy(ByteArray.GetData(), Bytes, ByteCount);
bSuccessfulLoad = GetTextureDataFromBuffer(ByteArray, TextureFormat, EDSResizeTextureMode::NearestPowerOfTwo, GMaxTextureDimensions, bCreateNormal, TextureData);
}
}
}
if (!bSuccessfulLoad)
{
if (TextureData.ImageData)
{
FMemory::Free(TextureData.ImageData);
}
TextureData.Width = 0;
TextureData.Height = 0;
TextureData.ImageData = nullptr;
UE_LOG(LogDatasmithRuntime, Warning, TEXT("Cannot load image file %s for texture %s"), TextureElement->GetFile(), TextureElement->GetLabel());
}
else
{
TasksToComplete |= EWorkerTask::TextureAssign;
}
FActionTaskFunction CreateTaskFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
return this->CreateTexture(Referencer.GetId());
};
AddToQueue(EQueueTask::NonAsyncQueue, { CreateTaskFunc, {EDataType::Texture, ElementId, 0 } });
ActionCounter.Increment();
return true;
}
void FSceneImporter::ProcessTextureData(FSceneGraphId TextureId)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSceneImporter::ProcessTextureData);
FAssetData& AssetData = AssetDataList[TextureId];
// Something is wrong. Do not go any further
if (AssetData.HasState(EAssetState::PendingDelete))
{
UE_LOG(LogDatasmithRuntime, Error, TEXT("A texture marked for deletion is actually used by the scene"));
return;
}
if (AssetData.HasState(EAssetState::Processed))
{
return;
}
IDatasmithTextureElement* TextureElement = static_cast<IDatasmithTextureElement*>(Elements[ TextureId ].Get());
uint32 TextureHash = GetTypeHash(TextureElement->CalculateElementHash(true), EDataType::Texture);
if (UObject* Asset = FAssetRegistry::FindObjectFromHash(TextureHash))
{
AssetData.SetState(EAssetState::Processed);
AssetData.Hash = TextureHash;
AssetData.Object = TWeakObjectPtr<UObject>(Asset);
FAssetRegistry::RegisterAssetData(Asset, SceneKey, AssetData);
return;
}
// The final texture has not been created yet, track texture data with temporary hash
AssetData.Hash = HashCombine(SceneKey, TextureHash);
if (UObject* Asset = FAssetRegistry::FindObjectFromHash(AssetData.Hash))
{
AssetData.SetState(EAssetState::Processed);
AssetData.Object = TWeakObjectPtr<UObject>(Asset);
FAssetRegistry::RegisterAssetData(Asset, SceneKey, AssetData);
return;
}
FActionTaskFunction LoadTaskFunc = [this](UObject* Object, const FReferencer& Referencer) -> EActionResult::Type
{
OnGoingTasks.Emplace( Async(
#if WITH_EDITOR
EAsyncExecution::LargeThreadPool,
#else
EAsyncExecution::ThreadPool,
#endif
[this, ElementId = Referencer.GetId()]()->bool
{
return this->LoadTexture(ElementId);
},
[this]()->void
{
this->ActionCounter.Increment();
}
));
return EActionResult::Succeeded;
};
// Textures are added in two steps. Make sure the associated FTextureData is created
if (!TextureDataList.Contains(TextureId))
{
TextureDataList.Add(TextureId);
}
AddToQueue(EQueueTask::TextureQueue, { LoadTaskFunc, {EDataType::Texture, TextureId, 0 } });
TasksToComplete |= EWorkerTask::TextureLoad;
// Create texture helper to leverage registration mechanism
UDatasmithRuntimeTHelper* TextureHelper = NewObject< UDatasmithRuntimeTHelper >();
AssetData.Object = TWeakObjectPtr<UObject>(TextureHelper);
AssetData.SetState(EAssetState::Processed);
FAssetRegistry::RegisterAssetData(TextureHelper, SceneKey, AssetData);
TextureElementSet.Add(TextureId);
}
} // End of namespace DatasmithRuntime