// Copyright Epic Games, Inc. All Rights Reserved. #include "TexturesCache.h" #include "Utils/LibPartInfo.h" #include "ModelMaterial.hpp" #include "Texture.hpp" #include "AttributeIndex.hpp" #include "GXImage.hpp" #include "Graphics2D.h" #include "Folder.hpp" #include "FileSystem.hpp" #include "Paths.h" #include "DatasmithUtils.h" BEGIN_NAMESPACE_UE_AC FTexturesCache::FTexturesCache(const GS::UniString& InAssetsCache) : AssetsCache(InAssetsCache) { AbsolutePath = AssetsCache + UE_AC_DirSep + GS::UniString("Textures") + UE_AC_DirSep; IESTexturesPath = AssetsCache + UE_AC_DirSep + GS::UniString("IESTextures") + UE_AC_DirSep; } const FTexturesCache::FTexturesCacheElem& FTexturesCache::GetTexture(const FSyncContext& InSyncContext, GS::Int32 InTextureIndex) { FTexturesCacheElem* ExistingTexture = Textures.Find(InTextureIndex); if (ExistingTexture != nullptr) { return *ExistingTexture; } UE_AC_Assert(InTextureIndex > 0 && InTextureIndex <= InSyncContext.GetModel().GetTextureCount()); // Create an new texture element FTexturesCacheElem& Texture = Textures.FindOrAdd(InTextureIndex); ModelerAPI::Texture AcTexture; ModelerAPI::AttributeIndex IndexTextureIndex(ModelerAPI::AttributeIndex::TextureIndex, InTextureIndex); InSyncContext.GetModel().GetTexture(IndexTextureIndex, &AcTexture); if (AcTexture.GetXSize() > 0) { Texture.InvXSize = 1 / AcTexture.GetXSize(); } if (AcTexture.GetYSize() > 0) { Texture.InvYSize = 1 / AcTexture.GetYSize(); } Texture.bHasAlpha = AcTexture.HasAlphaChannel(); Texture.bMirrorX = AcTexture.IsMirroredInX(); Texture.bMirrorY = AcTexture.IsMirroredInY(); Texture.bAlphaIsTransparence = AcTexture.IsTransparentPattern(); Texture.bIsAvailable = AcTexture.IsAvailable(); Texture.bUsed = false; if (Texture.bIsAvailable) { if (InSyncContext.bUseFingerPrint) { #if 0 // Compute pixel fingerprint to be redesing Texture.TextureLabel = AcTexture.GetPixelMapCheckSum(); Texture.Fingerprint = GSGuid2APIGuid(GS::Guid(Texture.TextureLabel)); #endif // Compute fingerprint with content and used texture informations char Tmp[256]; AcTexture.GetPixelMapCheckSum(Tmp, sizeof(Tmp)); size_t FingerprintLen = strnlen(Tmp, sizeof(Tmp)); UE_AC_Assert(FingerprintLen == 32); MD5::Generator MD5Generator; MD5Generator.Update(Tmp, (unsigned short)FingerprintLen); MD5Generator.Update(&Texture.InvXSize, sizeof(Texture.InvXSize)); MD5Generator.Update(&Texture.InvYSize, sizeof(Texture.InvYSize)); MD5Generator.Update(&Texture.bAlphaIsTransparence, Texture.bAlphaIsTransparence); MD5::FingerPrint FingerPrint; MD5Generator.Finish(FingerPrint); UE_AC_Assert(FingerPrint.GetAsString(Tmp) == NoError); Texture.TextureLabel = Tmp; Texture.Fingerprint = Fingerprint2API_Guid(FingerPrint); UE_AC_VerboseF("Texture name=\"%s\": TMFingerPrint=\"%s\"\n", AcTexture.GetName().ToUtf8(), Tmp); } else { // Create a unique name Texture.TextureLabel = AcTexture.GetName(); unsigned int SequencialNumber = 0; while (TexturesNameSet.Contains(&Texture.TextureLabel)) { Texture.TextureLabel = AcTexture.GetName() + GS::UniString::Printf(" %d", ++SequencialNumber); } TexturesNameSet.Add(&Texture.TextureLabel); GS::UniString fp(AcTexture.GetFingerprint()); Texture.Fingerprint = GSGuid2APIGuid(GS::Guid(fp)); UE_AC_VerboseF("Texture name=\"%s\": ACFingerprint=\"%s\"\n", Texture.TextureLabel.ToUtf8(), fp.ToUtf8()); } CreateCacheFolders(); Texture.TexturePath = AbsolutePath + Texture.TextureLabel + GetGSName(kName_TextureExtension); WriteTexture(AcTexture, Texture.TexturePath, InSyncContext.bUseFingerPrint); } else { GS::UniString fp(AcTexture.GetFingerprint()); Texture.Fingerprint = GSGuid2APIGuid(GS::Guid(fp)); UE_AC_ReportF("FTexturesCache::GetTexture - Texture name \"%s\" missing: ACFingerprint=%s\n", AcTexture.GetName().ToUtf8(), fp.ToUtf8()); } GS::UniString Fingerprint = APIGuidToString(Texture.Fingerprint); FString TextureId = GSStringToUE(Fingerprint); bool bTextureIdAlreadyInSet = false; TexturesIdsSet.Add(TextureId, &bTextureIdAlreadyInSet); if (!bTextureIdAlreadyInSet) { TSharedRef< IDatasmithTextureElement > BaseTexture = FDatasmithSceneFactory::CreateTexture(GSStringToUE(Fingerprint)); BaseTexture->SetLabel(GSStringToUE(AcTexture.GetName())); BaseTexture->SetFile(GSStringToUE(Texture.TexturePath)); if (*BaseTexture->GetFile() != 0) { FMD5Hash FileHash = FMD5Hash::HashFile(BaseTexture->GetFile()); BaseTexture->SetFileHash(FileHash); } else { BaseTexture->SetFile(TEXT("Missing_Texture_File")); } BaseTexture->SetSRGB(EDatasmithColorSpace::sRGB); InSyncContext.GetScene().AddTexture(BaseTexture); } return Texture; } /* Tool class to search file in Folder and Sub folder of a Parent folder. * If found, copy the file to destination folder */ class FSearchAndCopyFile { public: // Constructor FSearchAndCopyFile(IO::Folder& InDestination, const IO::Name& InFileName) : Destination(InDestination) , FileName(InFileName) , bFileInDestination(false) { // Check if the file is already in the destination folder GSErrCode GSErr = InDestination.Contains(InFileName, &bFileInDestination); if (GSErr != NoError) { UE_AC_DebugF("FSearchAndCopyFile::FSearchAndCopyFile - IESTexturesFolder.Contains return error %s\n", GetErrorName(GSErr)); } } // Search in the folder tree starting at parent folder. bool DoSearchIn(const IO::Folder& Parent) { return Parent.Enumerate(EnumCallBack, this) == NoError; } // Callback function called for each element of the parent specified bool EnumCallBack(const IO::Folder& Parent, const IO::Name& EntryName, bool bIsFolder); // Return true if the file is in the destination folder bool IsFileInDestination() const { return bFileInDestination; } private: // Callback to folder enumerate function static bool CCALL EnumCallBack(const IO::Folder& Parent, const IO::Name& EntryName, bool bIsFolder, void* UserData); // Destination where we must copy the file IO::Folder& Destination; // File's name to be copied const IO::Name& FileName; // True if the file is in the destination folder bool bFileInDestination; }; // Callback to folder enumerate function bool CCALL FSearchAndCopyFile::EnumCallBack(const IO::Folder& Parent, const IO::Name& EntryName, bool bIsFolder, void* UserData) { return reinterpret_cast< FSearchAndCopyFile* >(UserData)->EnumCallBack(Parent, EntryName, bIsFolder); } // Callback function called for each element of the parent specified bool FSearchAndCopyFile::EnumCallBack(const IO::Folder& Parent, const IO::Name& EntryName, bool bIsFolder) { if (bIsFolder) { // Search in sub folder DoSearchIn(IO::Folder(Parent, EntryName)); } else { if (EntryName == FileName) { // Try to copy the file GSErrCode GSErr = Parent.Copy(EntryName, Destination, FileName); if (GSErr == NoError) { bFileInDestination = true; } else { UE_AC_DebugF("FSearchAndCopyFile::EnumCallBack - IO::Folder::Copy returned error %s\n", GetErrorName(GSErr)); } } } return !bFileInDestination; // Stop if copy is done } // Insure we have a copy of the IES file in the cache folder GS::UniString FTexturesCache::CopyIESFile(const GS::UniString& InIESFileName) { IO::Location IESTexturesLocation(IESTexturesPath); IO::Name IESFileName(InIESFileName); // Create the cached texture path IO::Location IESTextureLocation(IESTexturesLocation, IESFileName); GS::UniString IESTexturePath; IESTextureLocation.ToPath(&IESTexturePath); // Create the texture folder if it's not present IO::Folder IESTexturesFolder(IESTexturesLocation, IO::Folder::Create); #if 1 // Check if the file is already in the destination folder bool bFileInDestination = false; GSErrCode GSErr = IESTexturesFolder.Contains(IESFileName, &bFileInDestination); if (GSErr != NoError) { UE_AC_DebugF("FTexturesCache::CopyIESFile - IESTexturesFolder.Contains return error %s\n", GetErrorName(GSErr)); } if (!bFileInDestination) { FAuto_API_LibPart LibPart; USize Lenght = InIESFileName.GetLength() + 1; if (Lenght > API_UniLongNameLen) { Lenght = API_UniLongNameLen; } memcpy(LibPart.file_UName, InIESFileName.ToUStr().Get(), Lenght * sizeof(GS::uchar_t)); GSErr = ACAPI_LibPart_Search(&LibPart, false); if (GSErr == NoError && LibPart.location != nullptr) { // Try to copy the file GSErr = IO::fileSystem.Copy(*LibPart.location, IESTextureLocation); if (GSErr != NoError) { UE_AC_ReportF("FTexturesCache::CopyIESFile - Cannot copy IES File \"%s\"", InIESFileName.ToUtf8()); UE_AC_DebugF("FTexturesCache::CopyIESFile - Cannot copy IES File \"%s\", error=%s\n", InIESFileName.ToUtf8(), GetErrorName(GSErr)); } } else { UE_AC_ReportF("FTexturesCache::CopyIESFile - Cannot find IES File \"%s\"\n", InIESFileName.ToUtf8()); if (GSErr != NoError) { UE_AC_DebugF("FTexturesCache::CopyIESFile - ACAPI_LibPart_Search error %s for IES File \"%s\"\n", GetErrorName(GSErr), InIESFileName.ToUtf8()); } } } #else // Search the IES file and copy it in the cache FSearchAndCopyFile SearchAndCopyIESFile(IESTexturesFolder, IESFileName); if (!SearchAndCopyIESFile.IsFileInDestination()) { GS::Array< API_LibraryInfo > LibInfoArray; GSErrCode GSErr = ACAPI_Environment(APIEnv_GetLibrariesID, &LibInfoArray); if (GSErr == NoError) { for (UInt32 IndexLibrary = 0; IndexLibrary < LibInfoArray.GetSize() && !SearchAndCopyIESFile.IsFileInDestination(); IndexLibrary++) { const API_LibraryInfo& LibInfo = LibInfoArray[IndexLibrary]; if (LibInfo.libraryType == API_LocalLibrary || LibInfo.libraryType == API_EmbeddedLibrary || LibInfo.libraryType == API_ServerLibrary) { IO::Folder LibraryFolder(LibInfo.location, IO::Folder::Ignore); SearchAndCopyIESFile.DoSearchIn(LibraryFolder); } } } } if (!SearchAndCopyIESFile.IsFileInDestination()) { UE_AC_ReportF("FTexturesCache::CopyIESFile - Cannot find IES File \"%s\"\n", InIESFileName.ToUtf8()); } #endif return IESTexturePath; } // Create IES texture const FTexturesCache::FIESTexturesCacheElem& FTexturesCache::GetIESTexture(const FSyncContext& InSyncContext, const FString& InIESFileName) { CreateCacheFolders(); FTexturesCache::FIESTexturesCacheElem* found = IESTextures.Find(InIESFileName); if (found == nullptr) { // Find and copy the texture in the cache. FString IESFilePath(GSStringToUE(CopyIESFile(UEToGSString(*InIESFileName)))); // Create IES texture const FString BaseFilename = FPaths::GetBaseFilename(IESFilePath); FString TextureName = FDatasmithUtils::SanitizeObjectName(BaseFilename + TEXT("_IES")); TSharedPtr< IDatasmithTextureElement > Texture = FDatasmithSceneFactory::CreateTexture(*TextureName); Texture->SetTextureMode(EDatasmithTextureMode::Ies); Texture->SetLabel(*BaseFilename); Texture->SetFile(*IESFilePath); InSyncContext.GetScene().AddTexture(Texture); found = &IESTextures.Add(InIESFileName, TextureName); } return *found; } void FTexturesCache::CreateCacheFolders() { if (bCacheFoldersCreated == false) { { // Create the asset folder if it's not present IO::Location AssetsFolderLocation(AssetsCache); IO::Folder AssetsFolder(AssetsFolderLocation, IO::Folder::Create); if (AssetsFolder.GetStatus() != NoError) { UE_AC_ReportF("Unable to create/access assets cache folder: \"%s\"", AssetsCache.ToUtf8()); UE_AC::ThrowGSError(AssetsFolder.GetStatus(), __FILE__, __LINE__); } } { // Create the textures folder if it's not present IO::Location TexturesLocation(AbsolutePath); IO::Folder TexturesFolder(TexturesLocation, IO::Folder::Create); if (TexturesFolder.GetStatus() != NoError) { UE_AC_ReportF("Unable to create/access cache textures cache folder: \"%s\"", AbsolutePath.ToUtf8()); UE_AC::ThrowGSError(TexturesFolder.GetStatus(), __FILE__, __LINE__); } } bCacheFoldersCreated = true; } } // Write the texture to the cache void FTexturesCache::WriteTexture(const ModelerAPI::Texture& InACTexture, const GS::UniString& InPath, bool InIsFingerprint) const { IO::Location TextureLoc(InPath); // If texture already exist, we do nothing if (InIsFingerprint) { IO::File TextureFile(TextureLoc); if ((TextureFile.GetStatus() == NoError) && (TextureFile.IsOpen() || (TextureFile.Open(IO::File::ReadMode) == NoError))) { return; } } // Create a pixmap of the same size of the texture GSPixMapHandle PixMap = GXCreateGSPixMap(InACTexture.GetPixelMapXSize(), InACTexture.GetPixelMapYSize()); UE_AC_TestPtr(PixMap); try { // Test the invariant UE_AC_Assert(InACTexture.GetPixelMapSize() * sizeof(ModelerAPI::Texture::Pixel) == GXGetGSPixMapBytesPerRow(PixMap) * InACTexture.GetPixelMapYSize()); // Copy the pixels from the texture to the PixMap. GSPtr Pixels = GXGetGSPixMapBaseAddr(PixMap); UE_AC_TestPtr(Pixels); InACTexture.GetPixelMap(reinterpret_cast< ModelerAPI::Texture::Pixel* >(Pixels)); GX::ImageSaveOptions ImgSaveOpt(GX::PixelBits_MillionsWithAlpha); GX::ImageSaveOptions* PixelBitSize = &ImgSaveOpt; GX::Image Img(PixMap); UE_AC_TestGSError(Img.WriteToFile( TextureLoc, FTM::FileTypeManager::SearchForMime(GetStdName(kName_TextureMime), NULL), PixelBitSize)); } catch (...) { GXDeleteGSPixMap(PixMap); throw; } // Delete the pixmap GXDeleteGSPixMap(PixMap); } END_NAMESPACE_UE_AC