You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
In a future version, additional logical tile data textures may be allocated to handle cases where more mip levels are being requested than can physically fit into a single texture. In addition, page table textures should also be resized so that streamed out frames take up less GPU memory. It might also be necessary to implement a blocking streaming option for MRQ and similar use cases. This feature probably depends on being able to spill to additional physical page table textures. Also moved all of the SVT runtime classes into a shared namespace (UE::SVT), which is why this CL ended up touching almost all SVT related files. #rb Sebastien.Hillaire, Rune.Stubbe, Devon.Penney, Patrick.Kelly #rnx #preflight 64772ef20d55081f54759f0b [CL 25699866 by tim doerries in ue5-main branch]
752 lines
25 KiB
C++
752 lines
25 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SparseVolumeTextureFactory.h"
|
|
|
|
#include "SparseVolumeTexture/SparseVolumeTexture.h"
|
|
#include "SparseVolumeTexture/SparseVolumeTextureData.h"
|
|
|
|
#if WITH_EDITOR
|
|
|
|
#include "SparseVolumeTextureOpenVDB.h"
|
|
#include "SparseVolumeTextureOpenVDBUtility.h"
|
|
#include "OpenVDBImportOptions.h"
|
|
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Async/Async.h"
|
|
#include "Async/ParallelFor.h"
|
|
|
|
#include "Editor.h"
|
|
|
|
#include "OpenVDBImportWindow.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "HAL/Event.h"
|
|
#include "HAL/PlatformProcess.h"
|
|
#include "Interfaces/IMainFrameModule.h"
|
|
|
|
#include <atomic>
|
|
#include <mutex>
|
|
|
|
#define LOCTEXT_NAMESPACE "USparseVolumeTextureFactory"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogSparseVolumeTextureFactory, Log, All);
|
|
|
|
static void ComputeDefaultOpenVDBGridAssignment(const TArray<TSharedPtr<FOpenVDBGridComponentInfo>>& GridComponentInfo, int32 NumFiles, FOpenVDBImportOptions* ImportOptions)
|
|
{
|
|
for (FOpenVDBSparseVolumeAttributesDesc& AttributesDesc : ImportOptions->Attributes)
|
|
{
|
|
for (FOpenVDBSparseVolumeComponentMapping& Mapping : AttributesDesc.Mappings)
|
|
{
|
|
Mapping.SourceGridIndex = INDEX_NONE;
|
|
Mapping.SourceComponentIndex = INDEX_NONE;
|
|
}
|
|
AttributesDesc.Format = ESparseVolumeAttributesFormat::Float32;
|
|
}
|
|
|
|
// Assign the components of the input grids to the components of the output SVT.
|
|
uint32 DstAttributesIdx = 0;
|
|
uint32 DstComponentIdx = 0;
|
|
for (const TSharedPtr<FOpenVDBGridComponentInfo>& GridComponent : GridComponentInfo)
|
|
{
|
|
if (GridComponent->Index == INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
ImportOptions->Attributes[DstAttributesIdx].Mappings[DstComponentIdx].SourceGridIndex = GridComponent->Index;
|
|
ImportOptions->Attributes[DstAttributesIdx].Mappings[DstComponentIdx].SourceComponentIndex = GridComponent->ComponentIndex;
|
|
++DstComponentIdx;
|
|
if (DstComponentIdx == 4)
|
|
{
|
|
DstComponentIdx = 0;
|
|
++DstAttributesIdx;
|
|
if (DstAttributesIdx == 2)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ImportOptions->bIsSequence = NumFiles > 1;
|
|
}
|
|
|
|
static TArray<FString> FindOpenVDBSequenceFileNames(const FString& Filename)
|
|
{
|
|
TArray<FString> SequenceFilenames;
|
|
|
|
// The file is potentially a sequence if the character before the `.vdb` is a number.
|
|
const bool bIsFilePotentiallyPartOfASequence = FChar::IsDigit(Filename[Filename.Len() - 5]);
|
|
|
|
if (!bIsFilePotentiallyPartOfASequence)
|
|
{
|
|
SequenceFilenames.Add(Filename);
|
|
}
|
|
else
|
|
{
|
|
const FString Path = FPaths::GetPath(Filename);
|
|
const FString CleanFilename = FPaths::GetCleanFilename(Filename);
|
|
FString CleanFilenameWithoutSuffix;
|
|
{
|
|
const FString CleanFilenameWithoutExt = CleanFilename.LeftChop(4);
|
|
const int32 LastNonDigitIndex = CleanFilenameWithoutExt.FindLastCharByPredicate([](TCHAR Letter) { return !FChar::IsDigit(Letter); }) + 1;
|
|
const int32 DigitCount = CleanFilenameWithoutExt.Len() - LastNonDigitIndex;
|
|
CleanFilenameWithoutSuffix = CleanFilenameWithoutExt.LeftChop(CleanFilenameWithoutExt.Len() - LastNonDigitIndex);
|
|
}
|
|
|
|
// Find all files potentially part of the sequence
|
|
TArray<FString> PotentialSequenceFilenames;
|
|
IFileManager::Get().FindFiles(PotentialSequenceFilenames, *Path, TEXT("*.vdb"));
|
|
PotentialSequenceFilenames = PotentialSequenceFilenames.FilterByPredicate([CleanFilenameWithoutSuffix](const FString& Str) { return Str.StartsWith(CleanFilenameWithoutSuffix); });
|
|
|
|
auto GetFilenameNumberSuffix = [](const FString& Filename) -> int32
|
|
{
|
|
const FString FilenameWithoutExt = Filename.LeftChop(4);
|
|
const int32 LastNonDigitIndex = FilenameWithoutExt.FindLastCharByPredicate([](TCHAR Letter) { return !FChar::IsDigit(Letter); }) + 1;
|
|
const FString NumberSuffixStr = FilenameWithoutExt.RightChop(LastNonDigitIndex);
|
|
|
|
int32 Number = INDEX_NONE;
|
|
if (NumberSuffixStr.IsNumeric())
|
|
{
|
|
TTypeFromString<int32>::FromString(Number, *NumberSuffixStr);
|
|
}
|
|
return Number;
|
|
};
|
|
|
|
// Find range of number suffixes
|
|
int32 LowestIndex = INT32_MAX;
|
|
int32 HighestIndex = INT32_MIN;
|
|
for (FString& ItemFilename : PotentialSequenceFilenames)
|
|
{
|
|
const int32 Index = GetFilenameNumberSuffix(ItemFilename);
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
ItemFilename.Empty();
|
|
continue;
|
|
}
|
|
LowestIndex = FMath::Min(LowestIndex, Index);
|
|
HighestIndex = FMath::Max(HighestIndex, Index);
|
|
}
|
|
|
|
check(HighestIndex >= LowestIndex);
|
|
|
|
// Sort the filenames into the result array
|
|
SequenceFilenames.SetNum(HighestIndex - LowestIndex + 1);
|
|
for (const FString& ItemFilename : PotentialSequenceFilenames)
|
|
{
|
|
const int32 Index = ItemFilename.IsEmpty() ? INDEX_NONE : GetFilenameNumberSuffix(ItemFilename);
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
SequenceFilenames[Index - LowestIndex] = Path / ItemFilename;
|
|
}
|
|
|
|
// Chop off any items after finding the first gap
|
|
for (int32 i = 0; i < SequenceFilenames.Num(); ++i)
|
|
{
|
|
if (SequenceFilenames[i].IsEmpty())
|
|
{
|
|
SequenceFilenames.SetNum(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
check(!SequenceFilenames.IsEmpty());
|
|
|
|
return SequenceFilenames;
|
|
}
|
|
|
|
struct FOpenVDBPreviewData
|
|
{
|
|
TArray64<uint8> LoadedFile;
|
|
TArray<FOpenVDBGridInfo> GridInfo;
|
|
TArray<TSharedPtr<FOpenVDBGridInfo>> GridInfoPtrs;
|
|
TArray<TSharedPtr<FOpenVDBGridComponentInfo>> GridComponentInfoPtrs;
|
|
TArray<FString> SequenceFilenames;
|
|
FOpenVDBImportOptions DefaultImportOptions;
|
|
};
|
|
|
|
static bool LoadOpenVDBPreviewData(const FString& Filename, FOpenVDBPreviewData* OutPreviewData)
|
|
{
|
|
FOpenVDBPreviewData& Result = *OutPreviewData;
|
|
check(Result.LoadedFile.IsEmpty());
|
|
check(Result.GridInfo.IsEmpty());
|
|
check(Result.GridInfoPtrs.IsEmpty());
|
|
check(Result.GridComponentInfoPtrs.IsEmpty());
|
|
check(Result.SequenceFilenames.IsEmpty());
|
|
|
|
if (!FFileHelper::LoadFileToArray(Result.LoadedFile, *Filename))
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("OpenVDB file could not be loaded: %s"), *Filename);
|
|
return false;
|
|
}
|
|
if (!GetOpenVDBGridInfo(Result.LoadedFile, true /*bCreateStrings*/, &Result.GridInfo))
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("Failed to read OpenVDB file: %s"), *Filename);
|
|
return false;
|
|
}
|
|
if (Result.GridInfo.IsEmpty())
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("OpenVDB file contains no grids: %s"), *Filename);
|
|
return false;
|
|
}
|
|
|
|
// We need a <None> option to leave channels empty
|
|
FOpenVDBGridComponentInfo NoneGridComponentInfo;
|
|
NoneGridComponentInfo.Index = INDEX_NONE;
|
|
NoneGridComponentInfo.ComponentIndex = INDEX_NONE;
|
|
NoneGridComponentInfo.Name = TEXT("<None>");
|
|
NoneGridComponentInfo.DisplayString = TEXT("<None>");
|
|
|
|
Result.GridComponentInfoPtrs.Add(MakeShared<FOpenVDBGridComponentInfo>(NoneGridComponentInfo));
|
|
|
|
// Create individual entries for each component of all valid source grids.
|
|
// This is an array of TSharedPtr because SComboBox requires its input to be wrapped in TSharedPtr.
|
|
bool bFoundSupportedGridType = false;
|
|
for (const FOpenVDBGridInfo& Grid : Result.GridInfo)
|
|
{
|
|
// Append all grids, even if we don't actually support them
|
|
Result.GridInfoPtrs.Add(MakeShared<FOpenVDBGridInfo>(Grid));
|
|
|
|
if (Grid.Type == EOpenVDBGridType::Unknown || !IsOpenVDBGridValid(Grid, Filename))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bFoundSupportedGridType = true;
|
|
|
|
// Create one entry per component
|
|
for (uint32 ComponentIdx = 0; ComponentIdx < Grid.NumComponents; ++ComponentIdx)
|
|
{
|
|
FOpenVDBGridComponentInfo ComponentInfo;
|
|
ComponentInfo.Index = Grid.Index;
|
|
ComponentInfo.ComponentIndex = ComponentIdx;
|
|
ComponentInfo.Name = Grid.Name;
|
|
|
|
const TCHAR* ComponentNames[] = { TEXT(".X"), TEXT(".Y"),TEXT(".Z"),TEXT(".W") };
|
|
FStringFormatOrderedArguments FormatArgs;
|
|
FormatArgs.Add(ComponentInfo.Index);
|
|
FormatArgs.Add(ComponentInfo.Name);
|
|
FormatArgs.Add(Grid.NumComponents == 1 ? TEXT("") : ComponentNames[ComponentIdx]);
|
|
|
|
ComponentInfo.DisplayString = FString::Format(TEXT("{0}. {1}{2}"), FormatArgs);
|
|
|
|
Result.GridComponentInfoPtrs.Add(MakeShared<FOpenVDBGridComponentInfo>(MoveTemp(ComponentInfo)));
|
|
}
|
|
}
|
|
|
|
if (!bFoundSupportedGridType)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("OpenVDB file contains no grids of supported type: %s"), *Filename);
|
|
return false;
|
|
}
|
|
|
|
Result.SequenceFilenames = FindOpenVDBSequenceFileNames(Filename);
|
|
|
|
ComputeDefaultOpenVDBGridAssignment(Result.GridComponentInfoPtrs, Result.SequenceFilenames.Num(), &Result.DefaultImportOptions);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ShowOpenVDBImportWindow(const FString& Filename, const FOpenVDBPreviewData& PreviewData, FOpenVDBImportOptions* OutImportOptions)
|
|
{
|
|
TSharedPtr<SWindow> ParentWindow;
|
|
|
|
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
|
|
{
|
|
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
|
|
ParentWindow = MainFrame.GetParentWindow();
|
|
}
|
|
|
|
// Compute centered window position based on max window size, which include when all categories are expanded
|
|
const float ImportWindowWidth = 450.0f;
|
|
const float ImportWindowHeight = 750.0f;
|
|
FVector2D ImportWindowSize = FVector2D(ImportWindowWidth, ImportWindowHeight); // Max window size it can get based on current slate
|
|
|
|
|
|
FSlateRect WorkAreaRect = FSlateApplicationBase::Get().GetPreferredWorkArea();
|
|
FVector2D DisplayTopLeft(WorkAreaRect.Left, WorkAreaRect.Top);
|
|
FVector2D DisplaySize(WorkAreaRect.Right - WorkAreaRect.Left, WorkAreaRect.Bottom - WorkAreaRect.Top);
|
|
|
|
float ScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(DisplayTopLeft.X, DisplayTopLeft.Y);
|
|
ImportWindowSize *= ScaleFactor;
|
|
|
|
FVector2D WindowPosition = (DisplayTopLeft + (DisplaySize - ImportWindowSize) / 2.0f) / ScaleFactor;
|
|
|
|
TSharedRef<SWindow> Window = SNew(SWindow)
|
|
.Title(NSLOCTEXT("UnrealEd", "OpenVDBImportOptionsTitle", "OpenVDB Import Options"))
|
|
.SizingRule(ESizingRule::Autosized)
|
|
.AutoCenter(EAutoCenter::None)
|
|
.ClientSize(ImportWindowSize)
|
|
.ScreenPosition(WindowPosition);
|
|
|
|
TArray<TSharedPtr<ESparseVolumeAttributesFormat>> SupportedFormats =
|
|
{
|
|
MakeShared<ESparseVolumeAttributesFormat>(ESparseVolumeAttributesFormat::Float32),
|
|
MakeShared<ESparseVolumeAttributesFormat>(ESparseVolumeAttributesFormat::Float16),
|
|
MakeShared<ESparseVolumeAttributesFormat>(ESparseVolumeAttributesFormat::Unorm8)
|
|
};
|
|
|
|
TSharedPtr<SOpenVDBImportWindow> OpenVDBOptionWindow;
|
|
Window->SetContent
|
|
(
|
|
SAssignNew(OpenVDBOptionWindow, SOpenVDBImportWindow)
|
|
.ImportOptions(OutImportOptions)
|
|
.DefaultImportOptions(&PreviewData.DefaultImportOptions)
|
|
.NumFoundFiles(PreviewData.SequenceFilenames.Num())
|
|
.OpenVDBGridInfo(&PreviewData.GridInfoPtrs)
|
|
.OpenVDBGridComponentInfo(&PreviewData.GridComponentInfoPtrs)
|
|
.OpenVDBSupportedTargetFormats(&SupportedFormats)
|
|
.WidgetWindow(Window)
|
|
.FullPath(FText::FromString(Filename))
|
|
.MaxWindowHeight(ImportWindowHeight)
|
|
.MaxWindowWidth(ImportWindowWidth)
|
|
);
|
|
|
|
FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false);
|
|
|
|
OutImportOptions->bIsSequence = OpenVDBOptionWindow->ShouldImportAsSequence();
|
|
|
|
return OpenVDBOptionWindow->ShouldImport();
|
|
}
|
|
|
|
static bool ValidateImportOptions(const FOpenVDBImportOptions& ImportOptions, const TArray<FOpenVDBGridInfo>& GridInfo)
|
|
{
|
|
const int32 NumGrids = GridInfo.Num();
|
|
|
|
for (const FOpenVDBSparseVolumeAttributesDesc& AttributesDesc : ImportOptions.Attributes)
|
|
{
|
|
for (const FOpenVDBSparseVolumeComponentMapping& Mapping : AttributesDesc.Mappings)
|
|
{
|
|
const int32 SourceGridIndex = Mapping.SourceGridIndex;
|
|
const int32 SourceComponentIndex = Mapping.SourceComponentIndex;
|
|
if (Mapping.SourceGridIndex != INDEX_NONE)
|
|
{
|
|
if (SourceGridIndex >= NumGrids)
|
|
{
|
|
return false; // Invalid grid index
|
|
}
|
|
if (SourceComponentIndex == INDEX_NONE || SourceComponentIndex >= (int32)GridInfo[SourceGridIndex].NumComponents)
|
|
{
|
|
return false; // Invalid component index
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
USparseVolumeTextureFactory::USparseVolumeTextureFactory(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
bCreateNew = true;
|
|
bEditAfterNew = true;
|
|
bEditorImport = true;
|
|
SupportedClass = USparseVolumeTexture::StaticClass();
|
|
|
|
Formats.Add(TEXT("vdb;OpenVDB Format"));
|
|
}
|
|
|
|
FText USparseVolumeTextureFactory::GetDisplayName() const
|
|
{
|
|
return LOCTEXT("SparseVolumeTextureFactoryDescription", "Sparse Volume Texture");
|
|
}
|
|
|
|
bool USparseVolumeTextureFactory::ConfigureProperties()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool USparseVolumeTextureFactory::ShouldShowInNewMenu() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Create asset
|
|
|
|
|
|
bool USparseVolumeTextureFactory::CanCreateNew() const
|
|
{
|
|
return false; // To be able to import files and call
|
|
}
|
|
|
|
UObject* USparseVolumeTextureFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
|
{
|
|
USparseVolumeTexture* Object = NewObject<USparseVolumeTexture>(InParent, InClass, InName, Flags);
|
|
|
|
// SVT_TODO initialize similarly to UTexture2DFactoryNew
|
|
|
|
return Object;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Import asset
|
|
|
|
|
|
bool USparseVolumeTextureFactory::DoesSupportClass(UClass* Class)
|
|
{
|
|
return Class == USparseVolumeTexture::StaticClass();
|
|
}
|
|
|
|
UClass* USparseVolumeTextureFactory::ResolveSupportedClass()
|
|
{
|
|
return USparseVolumeTexture::StaticClass();
|
|
}
|
|
|
|
bool USparseVolumeTextureFactory::FactoryCanImport(const FString& Filename)
|
|
{
|
|
const FString Extension = FPaths::GetExtension(Filename);
|
|
if (Extension == TEXT("vdb"))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void USparseVolumeTextureFactory::CleanUp()
|
|
{
|
|
Super::CleanUp();
|
|
}
|
|
|
|
UObject* USparseVolumeTextureFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename,
|
|
const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled)
|
|
{
|
|
#if OPENVDB_AVAILABLE
|
|
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPreImport(this, InClass, InParent, InName, Parms);
|
|
TArray<UObject*> ResultAssets;
|
|
|
|
bOutOperationCanceled = false;
|
|
|
|
const bool bIsUnattended = (IsAutomatedImport()
|
|
|| FApp::IsUnattended()
|
|
|| IsRunningCommandlet()
|
|
|| GIsRunningUnattendedScript);
|
|
|
|
// Load file and get info about each contained grid
|
|
FOpenVDBPreviewData PreviewData;
|
|
if (!LoadOpenVDBPreviewData(Filename, &PreviewData))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FOpenVDBImportOptions ImportOptions = PreviewData.DefaultImportOptions;
|
|
|
|
if (!bIsUnattended)
|
|
{
|
|
// Show dialog for import options
|
|
if (!ShowOpenVDBImportWindow(Filename, PreviewData, &ImportOptions))
|
|
{
|
|
bOutOperationCanceled = true;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (!ValidateImportOptions(ImportOptions, PreviewData.GridInfo))
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("Import options are invalid! This is likely due to invalid/out-of-bounds grid or component indices."));
|
|
return nullptr;
|
|
}
|
|
|
|
// Utility function for computing the bounding box encompassing the bounds of all frames in the SVT.
|
|
auto ExpandVolumeBounds = [](const FOpenVDBImportOptions& ImportOptions, const TArray<FOpenVDBGridInfo>& GridInfoArray, FIntVector3& VolumeBoundsMin, FIntVector3& VolumeBoundsMax)
|
|
{
|
|
for (const FOpenVDBSparseVolumeAttributesDesc& Attributes : ImportOptions.Attributes)
|
|
{
|
|
for (const FOpenVDBSparseVolumeComponentMapping& Mapping : Attributes.Mappings)
|
|
{
|
|
if (Mapping.SourceGridIndex != INDEX_NONE)
|
|
{
|
|
const FOpenVDBGridInfo& GridInfo = GridInfoArray[Mapping.SourceGridIndex];
|
|
VolumeBoundsMin.X = FMath::Min(VolumeBoundsMin.X, GridInfo.VolumeActiveAABBMin.X);
|
|
VolumeBoundsMin.Y = FMath::Min(VolumeBoundsMin.Y, GridInfo.VolumeActiveAABBMin.Y);
|
|
VolumeBoundsMin.Z = FMath::Min(VolumeBoundsMin.Z, GridInfo.VolumeActiveAABBMin.Z);
|
|
|
|
VolumeBoundsMax.X = FMath::Max(VolumeBoundsMax.X, GridInfo.VolumeActiveAABBMax.X);
|
|
VolumeBoundsMax.Y = FMath::Max(VolumeBoundsMax.Y, GridInfo.VolumeActiveAABBMax.Y);
|
|
VolumeBoundsMax.Z = FMath::Max(VolumeBoundsMax.Z, GridInfo.VolumeActiveAABBMax.Z);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
auto ComputeNumMipLevels = [](const FIntVector3& VolumeBoundsMin, const FIntVector3& VolumeBoundsMax)
|
|
{
|
|
int32 Levels = 1;
|
|
FIntVector3 Resolution = VolumeBoundsMax - VolumeBoundsMin;
|
|
while (Resolution.X > SPARSE_VOLUME_TILE_RES || Resolution.Y > SPARSE_VOLUME_TILE_RES || Resolution.Z > SPARSE_VOLUME_TILE_RES)
|
|
{
|
|
Resolution /= 2;
|
|
++Levels;
|
|
}
|
|
return Levels;
|
|
};
|
|
|
|
FIntVector3 VolumeBoundsMin = FIntVector3(INT32_MAX, INT32_MAX, INT32_MAX);
|
|
FIntVector3 VolumeBoundsMax = FIntVector3(INT32_MIN, INT32_MIN, INT32_MIN);
|
|
|
|
// Import as either single static SVT or a sequence of frames, making up an animated SVT
|
|
if (!ImportOptions.bIsSequence)
|
|
{
|
|
// Import as a static sparse volume texture asset.
|
|
|
|
FScopedSlowTask ImportTask(1.0f, LOCTEXT("ImportingVDBStatic", "Importing static OpenVDB"));
|
|
ImportTask.MakeDialog(true);
|
|
|
|
ExpandVolumeBounds(ImportOptions, PreviewData.GridInfo, VolumeBoundsMin, VolumeBoundsMax);
|
|
|
|
UE::SVT::FTextureData TextureData{};
|
|
const bool bConversionSuccess = ConvertOpenVDBToSparseVolumeTexture(PreviewData.LoadedFile, ImportOptions, VolumeBoundsMin, TextureData);
|
|
|
|
if (!bConversionSuccess)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("Failed to convert OpenVDB file to SparseVolumeTexture: %s"), *Filename);
|
|
return nullptr;
|
|
}
|
|
|
|
UStaticSparseVolumeTexture* StaticSVTexture = NewObject<UStaticSparseVolumeTexture>(InParent, UStaticSparseVolumeTexture::StaticClass(), InName, Flags);
|
|
const bool bInitSuccess = StaticSVTexture->Initialize(MakeArrayView<UE::SVT::FTextureData>(&TextureData, 1));
|
|
if (!bInitSuccess)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("Failed to initialize SparseVolumeTexture: %s"), *Filename);
|
|
return nullptr;
|
|
}
|
|
|
|
if (ImportTask.ShouldCancel())
|
|
{
|
|
bOutOperationCanceled = true;
|
|
return nullptr;
|
|
}
|
|
ImportTask.EnterProgressFrame(1.0f, LOCTEXT("ConvertingVDBStatic", "Converting static OpenVDB"));
|
|
|
|
ResultAssets.Add(StaticSVTexture);
|
|
}
|
|
else
|
|
{
|
|
// Import as an animated sparse volume texture asset.
|
|
|
|
// Data from original file is no longer needed; we iterate over all frames later
|
|
PreviewData.LoadedFile.Empty();
|
|
|
|
const int32 NumFrames = PreviewData.SequenceFilenames.Num();
|
|
|
|
FScopedSlowTask ImportTask(NumFrames + 1, LOCTEXT("ImportingVDBAnim", "Importing OpenVDB animation"));
|
|
ImportTask.MakeDialog(true);
|
|
|
|
// Allocate space for each frame
|
|
TArray<UE::SVT::FTextureData> UncookedFramesData;
|
|
UncookedFramesData.SetNum(NumFrames);
|
|
|
|
std::atomic_bool bErrored = false;
|
|
std::atomic_bool bCanceled = false;
|
|
|
|
// Compute volume bounds and check sequence files for compatiblity
|
|
std::mutex VolumeBoundsMutex;
|
|
ParallelFor(NumFrames, [&bErrored, &VolumeBoundsMutex, &VolumeBoundsMin, &VolumeBoundsMax, &ExpandVolumeBounds, &PreviewData, &ImportOptions](int32 FrameIdx)
|
|
{
|
|
if (bErrored.load())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Load file and get info about each contained grid
|
|
const FString& FrameFilename = PreviewData.SequenceFilenames[FrameIdx];
|
|
TArray64<uint8> LoadedFrameFile;
|
|
if (!FFileHelper::LoadFileToArray(LoadedFrameFile, *FrameFilename))
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("OpenVDB file could not be loaded: %s"), *FrameFilename);
|
|
bErrored.store(true);
|
|
return;
|
|
}
|
|
|
|
TArray<FOpenVDBGridInfo> FrameGridInfo;
|
|
if (!GetOpenVDBGridInfo(LoadedFrameFile, true, &FrameGridInfo))
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("Failed to read OpenVDB file: %s"), *FrameFilename);
|
|
bErrored.store(true);
|
|
return;
|
|
}
|
|
|
|
// Sanity check for compatibility
|
|
for (const FOpenVDBSparseVolumeAttributesDesc& AttributesDesc : ImportOptions.Attributes)
|
|
{
|
|
for (const FOpenVDBSparseVolumeComponentMapping& Mapping : AttributesDesc.Mappings)
|
|
{
|
|
const uint32 SourceGridIndex = Mapping.SourceGridIndex;
|
|
if (SourceGridIndex != INDEX_NONE)
|
|
{
|
|
if ((int32)SourceGridIndex >= FrameGridInfo.Num())
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("OpenVDB file is incompatible with other frames in the sequence: %s"), *FrameFilename);
|
|
bErrored.store(true);
|
|
return;
|
|
}
|
|
const FOpenVDBGridInfo& OrigSourceGrid = PreviewData.GridInfo[SourceGridIndex];
|
|
const FOpenVDBGridInfo& FrameSourceGrid = FrameGridInfo[SourceGridIndex];
|
|
if (OrigSourceGrid.Type != FrameSourceGrid.Type || OrigSourceGrid.Name != FrameSourceGrid.Name)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("OpenVDB file is incompatible with other frames in the sequence: %s"), *FrameFilename);
|
|
bErrored.store(true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update sequence volume bounds and increment ProcessedFramesCounter
|
|
{
|
|
std::lock_guard<std::mutex> Lock(VolumeBoundsMutex);
|
|
ExpandVolumeBounds(ImportOptions, FrameGridInfo, VolumeBoundsMin, VolumeBoundsMax);
|
|
}
|
|
});
|
|
|
|
if (bErrored.load())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
ImportTask.EnterProgressFrame(1.0f, LOCTEXT("ConvertingVDBAnim", "Converting OpenVDB animation"));
|
|
|
|
FEvent* AllTasksFinishedEvent = FPlatformProcess::GetSynchEventFromPool();
|
|
std::atomic_int FinishedTasksCounter = 0; // Will be incremented even if frame processing failed
|
|
std::atomic_int ProcessedFramesCounter = 0;
|
|
|
|
// Load individual frames, process/convert them and append them to the resulting asset
|
|
for (int32 FrameIdx = 0; FrameIdx < NumFrames; ++FrameIdx)
|
|
{
|
|
// Increments the atomic counter when going out of scope. Triggers an event once the counter reaches a given value.
|
|
struct FScopedIncrementer
|
|
{
|
|
std::atomic_int& Counter;
|
|
int32 MaxValue;
|
|
FEvent* Event;
|
|
explicit FScopedIncrementer(std::atomic_int& InCounter, int32 InMaxValue, FEvent* InEvent)
|
|
: Counter(InCounter), MaxValue(InMaxValue), Event(InEvent) {}
|
|
~FScopedIncrementer()
|
|
{
|
|
if ((Counter.fetch_add(1) + 1) == MaxValue)
|
|
{
|
|
Event->Trigger();
|
|
}
|
|
}
|
|
};
|
|
|
|
AsyncTask(ENamedThreads::AnyNormalThreadNormalTask,
|
|
[FrameIdx, NumFrames, &PreviewData, &ImportOptions, &UncookedFramesData,
|
|
AllTasksFinishedEvent, &bErrored, &bCanceled, &FinishedTasksCounter, &ProcessedFramesCounter, &VolumeBoundsMin]()
|
|
{
|
|
// Ensure the FinishedTasksCounter will be incremented in all cases
|
|
FScopedIncrementer Incremeter(FinishedTasksCounter, NumFrames, AllTasksFinishedEvent);
|
|
|
|
if (bErrored.load() || bCanceled.load())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FString& FrameFilename = PreviewData.SequenceFilenames[FrameIdx];
|
|
|
|
UE_LOG(LogSparseVolumeTextureFactory, Display, TEXT("Loading OpenVDB sequence frame #%i %s."), FrameIdx, *FrameFilename);
|
|
|
|
// Load file
|
|
TArray64<uint8> LoadedFrameFile;
|
|
if (!FFileHelper::LoadFileToArray(LoadedFrameFile, *FrameFilename))
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("OpenVDB file could not be loaded: %s"), *FrameFilename);
|
|
bErrored.store(true);
|
|
return;
|
|
}
|
|
|
|
UE::SVT::FTextureData TextureData{};
|
|
const bool bConversionSuccess = ConvertOpenVDBToSparseVolumeTexture(LoadedFrameFile, ImportOptions, VolumeBoundsMin, TextureData);
|
|
|
|
if (!bConversionSuccess)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("Failed to convert OpenVDB file to SparseVolumeTexture: %s"), *FrameFilename);
|
|
bErrored.store(true);
|
|
return;
|
|
}
|
|
|
|
UncookedFramesData[FrameIdx] = MoveTemp(TextureData);
|
|
|
|
// Increment ProcessedFramesCounter
|
|
ProcessedFramesCounter.fetch_add(1);
|
|
});
|
|
}
|
|
|
|
// Wait for frames to be processed
|
|
{
|
|
int NumFinishedTasks = 0;
|
|
int NumProcessedFrames = 0;
|
|
|
|
while (NumFinishedTasks < NumFrames)
|
|
{
|
|
// We can't block here because we want to regularly update the progress bar and check for user input.
|
|
const uint32 WaitTimeMS = 2;
|
|
AllTasksFinishedEvent->Wait(WaitTimeMS);
|
|
|
|
if (!bCanceled.load() && !bErrored.load() && ImportTask.ShouldCancel())
|
|
{
|
|
bCanceled.store(true);
|
|
}
|
|
|
|
const int NewNumFinishedTasks = FinishedTasksCounter.load();
|
|
if (NewNumFinishedTasks > NumFinishedTasks)
|
|
{
|
|
const int NewNumProcessedFrames = ProcessedFramesCounter.load();
|
|
if (NewNumProcessedFrames > NumProcessedFrames && !bErrored.load())
|
|
{
|
|
const float Progress = float(NewNumProcessedFrames - NumProcessedFrames);
|
|
ImportTask.EnterProgressFrame(Progress, LOCTEXT("ConvertingVDBAnim", "Converting OpenVDB animation"));
|
|
}
|
|
|
|
NumFinishedTasks = NewNumFinishedTasks;
|
|
NumProcessedFrames = NewNumProcessedFrames;
|
|
}
|
|
}
|
|
}
|
|
FPlatformProcess::ReturnSynchEventToPool(AllTasksFinishedEvent);
|
|
|
|
if (bCanceled.load())
|
|
{
|
|
bOutOperationCanceled = true;
|
|
return nullptr;
|
|
}
|
|
if (bErrored.load())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UAnimatedSparseVolumeTexture* AnimatedSVTexture = NewObject<UAnimatedSparseVolumeTexture>(InParent, UAnimatedSparseVolumeTexture::StaticClass(), InName, Flags);
|
|
const bool bInitSuccess = AnimatedSVTexture->Initialize(UncookedFramesData);
|
|
if (!bInitSuccess)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("Failed to initialize SparseVolumeTexture: %s"), *Filename);
|
|
return nullptr;
|
|
}
|
|
|
|
ResultAssets.Add(AnimatedSVTexture);
|
|
}
|
|
|
|
// Now notify the system about the imported/updated/created assets
|
|
check(ResultAssets.Num() == 1);
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostImport(this, ResultAssets[0]);
|
|
|
|
return ResultAssets[0];
|
|
|
|
#else // OPENVDB_AVAILABLE
|
|
|
|
// SVT_TODO Make sure we can also import on more platforms such as Linux. See SparseVolumeTextureOpenVDB.h
|
|
UE_LOG(LogSparseVolumeTextureFactory, Error, TEXT("Cannot import OpenVDB asset any platform other than Windows."));
|
|
return nullptr;
|
|
|
|
#endif // OPENVDB_AVAILABLE
|
|
|
|
}
|
|
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
#undef LOCTEXT_NAMESPACE
|