Files
UnrealEngine/Engine/Plugins/Interchange/Runtime/Source/Pipelines/Private/InterchangeGenericTexturePipeline.cpp
2025-05-18 13:04:45 +08:00

472 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "InterchangeGenericTexturePipeline.h"
#include "Engine/Texture.h"
#include "Engine/TextureCube.h"
#include "Engine/TextureLightProfile.h"
#include "InterchangePipelineLog.h"
#include "InterchangeTexture2DArrayFactoryNode.h"
#include "InterchangeTexture2DArrayNode.h"
#include "InterchangeTexture2DFactoryNode.h"
#include "InterchangeTexture2DNode.h"
#include "InterchangeTextureBlurNode.h"
#include "InterchangeTextureCubeArrayFactoryNode.h"
#include "InterchangeTextureCubeArrayNode.h"
#include "InterchangeTextureCubeFactoryNode.h"
#include "InterchangeTextureCubeNode.h"
#include "InterchangeTextureFactoryNode.h"
#include "InterchangeTextureLightProfileFactoryNode.h"
#include "InterchangeTextureLightProfileNode.h"
#include "InterchangeTextureNode.h"
#include "InterchangeVolumeTextureNode.h"
#include "InterchangeVolumeTextureFactoryNode.h"
#include "Misc/Paths.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(InterchangeGenericTexturePipeline)
#if WITH_EDITOR
#include "NormalMapIdentification.h"
#include "TextureCompiler.h"
#include "UDIMUtilities.h"
#endif //WITH_EDITOR
namespace UE::Interchange::Private
{
UClass* GetDefaultFactoryClassFromTextureNodeClass(UClass* NodeClass)
{
if (UInterchangeTexture2DNode::StaticClass() == NodeClass)
{
return UInterchangeTexture2DFactoryNode::StaticClass();
}
if (UInterchangeTextureCubeNode::StaticClass() == NodeClass)
{
return UInterchangeTextureCubeFactoryNode::StaticClass();
}
if (UInterchangeTextureCubeArrayNode::StaticClass() == NodeClass)
{
return UInterchangeTextureCubeArrayFactoryNode::StaticClass();
}
if (UInterchangeTexture2DArrayNode::StaticClass() == NodeClass)
{
return UInterchangeTexture2DArrayFactoryNode::StaticClass();
}
if (UInterchangeTextureLightProfileNode::StaticClass() == NodeClass)
{
return UInterchangeTextureLightProfileFactoryNode::StaticClass();
}
if (UInterchangeVolumeTextureNode::StaticClass() == NodeClass)
{
return UInterchangeVolumeTextureFactoryNode::StaticClass();
}
if(UInterchangeTextureBlurNode::StaticClass() == NodeClass)
{
return UInterchangeTexture2DFactoryNode::StaticClass();
}
return nullptr;
}
#if WITH_EDITOR
void AdjustTextureForNormalMap(UTexture* Texture, FImageView MipToAnalyze, bool bFlipNormalMapGreenChannel)
{
if (Texture)
{
if (UE::NormalMapIdentification::HandleAssetPostImport(Texture, MipToAnalyze))
{
UE_LOG(LogInterchangePipeline, Display, TEXT("Auto-detected normal map"));
if (bFlipNormalMapGreenChannel)
{
Texture->bFlipGreenChannel = true;
}
}
// this will rebuild the texture if it changed to normal map
}
}
#endif
TextureAddress ConvertWrap(const EInterchangeTextureWrapMode WrapMode)
{
switch (WrapMode)
{
case EInterchangeTextureWrapMode::Wrap:
return TA_Wrap;
case EInterchangeTextureWrapMode::Clamp:
return TA_Clamp;
case EInterchangeTextureWrapMode::Mirror:
return TA_Mirror;
default:
ensureMsgf(false, TEXT("Unkown Interchange Texture Wrap Mode"));
return TA_Wrap;
}
}
}
FString UInterchangeGenericTexturePipeline::GetPipelineCategory(UClass* AssetClass)
{
return TEXT("Textures");
}
void UInterchangeGenericTexturePipeline::AdjustSettingsForContext(const FInterchangePipelineContextParams& ContextParams)
{
Super::AdjustSettingsForContext(ContextParams);
#if WITH_EDITOR
TArray<FString> HideCategories;
bool bIsObjectATexture = !ContextParams.ReimportAsset ? false : ContextParams.ReimportAsset.IsA(UTexture::StaticClass());
if( (!bIsObjectATexture && ContextParams.ContextType == EInterchangePipelineContext::AssetReimport)
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomLODImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomLODReimport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetAlternateSkinningImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetAlternateSkinningReimport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomMorphTargetImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomMorphTargetReImport)
{
bImportTextures = false;
HideCategories.Add(UInterchangeGenericTexturePipeline::GetPipelineCategory(nullptr));
}
if (UInterchangePipelineBase* OuterMostPipeline = GetMostPipelineOuter())
{
for (const FString& HideCategoryName : HideCategories)
{
HidePropertiesOfCategory(OuterMostPipeline, this, HideCategoryName);
}
}
#endif //WITH_EDITOR
}
#if WITH_EDITOR
bool UInterchangeGenericTexturePipeline::IsPropertyChangeNeedRefresh(const FPropertyChangedEvent& PropertyChangedEvent) const
{
if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UInterchangeGenericTexturePipeline, bImportTextures))
{
return true;
}
return Super::IsPropertyChangeNeedRefresh(PropertyChangedEvent);
}
void UInterchangeGenericTexturePipeline::FilterPropertiesFromTranslatedData(UInterchangeBaseNodeContainer* InBaseNodeContainer)
{
Super::FilterPropertiesFromTranslatedData(InBaseNodeContainer);
//Filter all material pipeline properties if there is no translated material.
TArray<FString> TmpTextureNodes;
InBaseNodeContainer->GetNodes(UInterchangeTextureNode::StaticClass(), TmpTextureNodes);
if (TmpTextureNodes.Num() == 0)
{
//Filter out all Textures properties
if (UInterchangePipelineBase* OuterMostPipeline = GetMostPipelineOuter())
{
HidePropertiesOfCategory(OuterMostPipeline, this, UInterchangeGenericTexturePipeline::GetPipelineCategory(nullptr));
}
}
}
void UInterchangeGenericTexturePipeline::GetSupportAssetClasses(TArray<UClass*>& PipelineSupportAssetClasses) const
{
PipelineSupportAssetClasses.Add(UTexture::StaticClass());
}
#endif //WITH_EDITOR
void UInterchangeGenericTexturePipeline::ExecutePipeline(UInterchangeBaseNodeContainer* InBaseNodeContainer, const TArray<UInterchangeSourceData*>& InSourceDatas, const FString& ContentBasePath)
{
if (!InBaseNodeContainer)
{
UE_LOG(LogInterchangePipeline, Warning, TEXT("UInterchangeGenericTexturePipeline: Cannot execute pre-import pipeline because InBaseNodeContrainer is null"));
return;
}
BaseNodeContainer = InBaseNodeContainer;
SourceDatas.Empty(InSourceDatas.Num());
for (const UInterchangeSourceData* SourceData : InSourceDatas)
{
SourceDatas.Add(SourceData);
}
//Find all translated node we need for this pipeline
BaseNodeContainer->IterateNodes([this](const FString& NodeUid, UInterchangeBaseNode* Node)
{
switch(Node->GetNodeContainerType())
{
case EInterchangeNodeContainerType::TranslatedAsset:
{
if (UInterchangeTextureNode* TextureNode = Cast<UInterchangeTextureNode>(Node))
{
TextureNodes.Add(TextureNode);
}
}
break;
}
});
if (bImportTextures)
{
UInterchangeTextureFactoryNode* TextureFactoryNode = nullptr;
for (UInterchangeTextureNode* TextureNode : TextureNodes)
{
TextureFactoryNode = HandleCreationOfTextureFactoryNode(TextureNode);
}
//If we have a valid override name
FString OverrideAssetName = IsStandAlonePipeline() ? DestinationName : FString();
if (OverrideAssetName.IsEmpty() && IsStandAlonePipeline())
{
OverrideAssetName = AssetName;
}
const bool bOverrideAssetName = TextureNodes.Num() == 1 && IsStandAlonePipeline() && !OverrideAssetName.IsEmpty();
if (TextureFactoryNode && bOverrideAssetName)
{
TextureFactoryNode->SetAssetName(OverrideAssetName);
TextureFactoryNode->SetDisplayLabel(OverrideAssetName);
}
}
}
void UInterchangeGenericTexturePipeline::ExecutePostFactoryPipeline(const UInterchangeBaseNodeContainer* InBaseNodeContainer, const FString& NodeKey, UObject* CreatedAsset, bool bIsAReimport)
{
//We do not use the provided base container since ExecutePreImportPipeline cache it
//We just make sure the same one is pass in parameter
if (!InBaseNodeContainer || !ensure(BaseNodeContainer == InBaseNodeContainer) || !CreatedAsset)
{
return;
}
const UInterchangeFactoryBaseNode* Node = BaseNodeContainer->GetFactoryNode(NodeKey);
if (!Node)
{
return;
}
PostImportTextureAssetImport(CreatedAsset, bIsAReimport);
}
UInterchangeTextureFactoryNode* UInterchangeGenericTexturePipeline::HandleCreationOfTextureFactoryNode(const UInterchangeTextureNode* TextureNode)
{
UClass* FactoryClass = UE::Interchange::Private::GetDefaultFactoryClassFromTextureNodeClass(TextureNode->GetClass());
TOptional<FString> SourceFile = TextureNode->GetPayLoadKey();
#if WITH_EDITORONLY_DATA
if (FactoryClass == UInterchangeTexture2DFactoryNode::StaticClass())
{
if (SourceFile)
{
const FString Extension = FPaths::GetExtension(SourceFile.GetValue()).ToLower();
if (FileExtensionsToImportAsLongLatCubemap.Contains(Extension))
{
FactoryClass = UInterchangeTextureCubeFactoryNode::StaticClass();
}
}
}
#endif
UInterchangeTextureFactoryNode* InterchangeTextureFactoryNode = CreateTextureFactoryNode(TextureNode, FactoryClass);
if (FactoryClass == UInterchangeTexture2DFactoryNode::StaticClass() && InterchangeTextureFactoryNode)
{
// Forward the UDIM from the translator to the factory node
TMap<int32, FString> SourceBlocks;
UInterchangeTexture2DFactoryNode* Texture2DFactoryNode = static_cast<UInterchangeTexture2DFactoryNode*>(InterchangeTextureFactoryNode);
if (const UInterchangeTexture2DNode* Texture2DNode = Cast<UInterchangeTexture2DNode>(TextureNode))
{
SourceBlocks = Texture2DNode->GetSourceBlocks();
EInterchangeTextureWrapMode WrapU;
if (Texture2DNode->GetCustomWrapU(WrapU))
{
Texture2DFactoryNode->SetCustomAddressX(UE::Interchange::Private::ConvertWrap(WrapU));
}
EInterchangeTextureWrapMode WrapV;
if (Texture2DNode->GetCustomWrapV(WrapV))
{
Texture2DFactoryNode->SetCustomAddressY(UE::Interchange::Private::ConvertWrap(WrapV));
}
}
#if WITH_EDITOR
if (SourceBlocks.IsEmpty() && bImportUDIMs && SourceFile)
{
FString PrettyAssetName;
SourceBlocks = UE::TextureUtilitiesCommon::GetUDIMBlocksFromSourceFile(SourceFile.GetValue(), UE::TextureUtilitiesCommon::DefaultUdimRegexPattern, &PrettyAssetName);
if (!PrettyAssetName.IsEmpty())
{
InterchangeTextureFactoryNode->SetAssetName(PrettyAssetName);
}
}
#endif
if (!SourceBlocks.IsEmpty())
{
Texture2DFactoryNode->SetSourceBlocks(MoveTemp(SourceBlocks));
}
}
return InterchangeTextureFactoryNode;
}
UInterchangeTextureFactoryNode* UInterchangeGenericTexturePipeline::CreateTextureFactoryNode(const UInterchangeTextureNode* TextureNode, const TSubclassOf<UInterchangeTextureFactoryNode>& FactorySubclass)
{
FString DisplayLabel = TextureNode->GetDisplayLabel();
FString NodeUid = UInterchangeFactoryBaseNode::BuildFactoryNodeUid(TextureNode->GetUniqueID());
UInterchangeTextureFactoryNode* TextureFactoryNode = nullptr;
if (BaseNodeContainer->IsNodeUidValid(NodeUid))
{
TextureFactoryNode = Cast<UInterchangeTextureFactoryNode>(BaseNodeContainer->GetFactoryNode(NodeUid));
if (!ensure(TextureFactoryNode))
{
//Log an error
return nullptr;
}
}
else
{
UClass* FactoryClass = FactorySubclass.Get();
if (!ensure(FactoryClass))
{
// Log an error
return nullptr;
}
TextureFactoryNode = NewObject<UInterchangeTextureFactoryNode>(BaseNodeContainer, FactoryClass);
if (!ensure(TextureFactoryNode))
{
return nullptr;
}
//Creating a Texture
TextureFactoryNode->InitializeTextureNode(NodeUid, DisplayLabel, TextureNode->GetDisplayLabel(), BaseNodeContainer);
TextureFactoryNode->SetCustomTranslatedTextureNodeUid(TextureNode->GetUniqueID());
TextureFactoryNodes.Add(TextureFactoryNode);
TextureFactoryNode->AddTargetNodeUid(TextureNode->GetUniqueID());
TextureNode->AddTargetNodeUid(TextureFactoryNode->GetUniqueID());
if (bAllowNonPowerOfTwo)
{
TextureFactoryNode->SetCustomAllowNonPowerOfTwo(bAllowNonPowerOfTwo);
}
}
EInterchangeTextureColorSpace ColorSpace;
const bool bHasColorSpace = TextureNode->GetCustomColorSpace(ColorSpace);
if(bHasColorSpace)
{
TextureFactoryNode->SetCustomColorSpace(ETextureColorSpace(ColorSpace));
}
if (bool bSRGB; TextureNode->GetCustomSRGB(bSRGB))
{
TextureFactoryNode->SetCustomSRGB(bSRGB);
if(bSRGB && !bHasColorSpace)
{
TextureFactoryNode->SetCustomColorSpace(ETextureColorSpace::TCS_sRGB);
}
}
if (bool bFlipGreenChannel; TextureNode->GetCustombFlipGreenChannel(bFlipGreenChannel))
{
TextureFactoryNode->SetCustombFlipGreenChannel(bFlipGreenChannel);
}
using FInterchangeTextureFilterMode = std::underlying_type_t<EInterchangeTextureFilterMode>;
using FTextureFilter = std::underlying_type_t<TextureFilter>;
using FCommonTextureFilterModes = std::common_type_t<FInterchangeTextureFilterMode, FTextureFilter>;
static_assert(FCommonTextureFilterModes(EInterchangeTextureFilterMode::Nearest) == FCommonTextureFilterModes(TextureFilter::TF_Nearest), "EInterchangeTextureFilterMode::Nearest differs from TextureFilter::TF_Nearest");
static_assert(FCommonTextureFilterModes(EInterchangeTextureFilterMode::Bilinear) == FCommonTextureFilterModes(TextureFilter::TF_Bilinear), "EInterchangeTextureFilterMode::Bilinear differs from TextureFilter::TF_Bilinear");
static_assert(FCommonTextureFilterModes(EInterchangeTextureFilterMode::Trilinear) == FCommonTextureFilterModes(TextureFilter::TF_Trilinear), "EInterchangeTextureFilterMode::Trilinear differs from TextureFilter::TF_Trilinear");
static_assert(FCommonTextureFilterModes(EInterchangeTextureFilterMode::Default) == FCommonTextureFilterModes(TextureFilter::TF_Default), "EInterchangeTextureFilterMode::Default differs from TextureFilter::TF_Default");
if (EInterchangeTextureFilterMode TextureFilter; TextureNode->GetCustomFilter(TextureFilter))
{
TextureFactoryNode->SetCustomFilter(uint8(TextureFilter));
}
#if WITH_EDITORONLY_DATA
if (bPreferCompressedSourceData)
{
TextureFactoryNode->SetCustomPreferCompressedSourceData(true);
}
#endif // WITH_EDITORONLY_DATA
return TextureFactoryNode;
}
void UInterchangeGenericTexturePipeline::PostImportTextureAssetImport(UObject* CreatedAsset, bool bIsAReimport)
{
#if WITH_EDITOR
// this is run on main thread
check(IsInGameThread());
UTexture* Texture = Cast<UTexture>(CreatedAsset);
if (Texture == nullptr)
{
return;
}
// (Note - as part of the standard interchange import this is called during the object
// import iteration, _before_ the iteration to call to PostEditChange which is what starts the texture build via UpdateResource,
// so altering properties here should be safe!)
if (Texture->IsCompiling())
{
ensure(!bIsAReimport);
FTextureCompilingManager::Get().FinishCompilation(MakeArrayView(&Texture, 1));
}
if(bFlipNormalMapGreenChannel && Texture->IsNormalMap())
{
Texture->bFlipGreenChannel = true;
}
FTextureSource& Source = Texture->Source;
bool bRunNormapMapDetection = !bIsAReimport && bDetectNormalMapTexture && !Texture->IsNormalMap();
// we probably got the info via Init() - if we didn't it's because it's compressed. Here we can decompress, so do it if needed.
bool bRunChannelScan = !Source.HasLayerColorInfo();
bool bNeedLockedMip = bRunChannelScan || bRunNormapMapDetection;
if (!bNeedLockedMip)
{
return;
}
// Lock the mip outside of everything. We allow nested locks so this just makes sure
// that we don't decompress the mip data multiple times. Since the mips are all shared
// this is locking all of them even if it only exposes the one mip.
FTextureSource::FMipLock LockedMip0(FTextureSource::ELockState::ReadOnly, &Texture->Source, 0);
if (!LockedMip0.IsValid())
{
UE_LOG(LogInterchangePipeline, Error, TEXT("PostImport Texture failed to lock mip data, actions (like normal map detection) not performed on %s"), *Texture->GetPathName());
return;
}
if (bRunChannelScan)
{
Source.UpdateChannelLinearMinMax();
}
if (bRunNormapMapDetection)
{
// AdjustTextureForNormalMap technically only adjusts properties and doesn't kick a texture build, however
// if it guesses it's a normal map it pops a toast notification that can Revert the change, which does a whole
// Modify / PostEditChange which theoretically can kick a build before the PostEditChange in the outer interchange
// import chain if somehow the UI click chain routes before the outer loop calls PostEditChange.
UE::Interchange::Private::AdjustTextureForNormalMap(Texture, LockedMip0.Image, bFlipNormalMapGreenChannel);
}
#endif //WITH_EDITOR
}