You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rnx #preflight 63d7f61031334253e5ff28c4 [CL 23923279 by tim doerries in ue5-main branch]
637 lines
24 KiB
C++
637 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#if WITH_EDITOR
|
|
|
|
#include "SparseVolumeTextureOpenVDBUtility.h"
|
|
#include "SparseVolumeTextureOpenVDB.h"
|
|
#include "SparseVolumeTexture/SparseVolumeTexture.h"
|
|
#include "OpenVDBGridAdapter.h"
|
|
#include "OpenVDBImportOptions.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogSparseVolumeTextureOpenVDBUtility, Log, All);
|
|
|
|
#if OPENVDB_AVAILABLE
|
|
|
|
// We are using this instead of GMaxVolumeTextureDimensions to be independent of the platform that
|
|
// the asset is imported on. 2048 should be a safe value that should be supported by all our platforms.
|
|
static constexpr int32 SVTMaxVolumeTextureDim = 2048;
|
|
|
|
namespace
|
|
{
|
|
// Utility class acting as adapter between TArray<uint8> and std::istream
|
|
class FArrayUint8StreamBuf : public std::streambuf
|
|
{
|
|
public:
|
|
explicit FArrayUint8StreamBuf(TArray<uint8>& Array)
|
|
{
|
|
char* Data = (char*)Array.GetData();
|
|
setg(Data, Data, Data + Array.Num());
|
|
}
|
|
};
|
|
}
|
|
|
|
static FOpenVDBGridInfo GetOpenVDBGridInfo(openvdb::GridBase::Ptr Grid, uint32 GridIndex, bool bCreateStrings)
|
|
{
|
|
openvdb::CoordBBox VolumeActiveAABB = Grid->evalActiveVoxelBoundingBox();
|
|
openvdb::Coord VolumeActiveDim = Grid->evalActiveVoxelDim();
|
|
openvdb::Vec3d VolumeVoxelSize = Grid->voxelSize();
|
|
openvdb::math::MapBase::ConstPtr MapBase = Grid->constTransform().baseMap();
|
|
openvdb::Vec3d VoxelSize = MapBase->voxelSize();
|
|
openvdb::Mat4d GridTransformVDB = MapBase->getAffineMap()->getConstMat4();
|
|
|
|
FOpenVDBGridInfo GridInfo;
|
|
GridInfo.Index = GridIndex;
|
|
GridInfo.NumComponents = 0;
|
|
GridInfo.Type = EOpenVDBGridType::Unknown;
|
|
GridInfo.VolumeActiveAABBMin = FVector(VolumeActiveAABB.min().x(), VolumeActiveAABB.min().y(), VolumeActiveAABB.min().z());
|
|
GridInfo.VolumeActiveAABBMax = FVector(VolumeActiveAABB.max().x(), VolumeActiveAABB.max().y(), VolumeActiveAABB.max().z());
|
|
GridInfo.VolumeActiveDim = FVector(VolumeActiveDim.x(), VolumeActiveDim.y(), VolumeActiveDim.z());
|
|
GridInfo.VolumeVoxelSize = FVector(VolumeVoxelSize.x(), VolumeVoxelSize.y(), VolumeVoxelSize.z());
|
|
GridInfo.bIsInWorldSpace = Grid->isInWorldSpace();
|
|
GridInfo.bHasUniformVoxels = Grid->hasUniformVoxels();
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
for (int j = 0; j < 4; ++j)
|
|
{
|
|
GridInfo.Transform.M[i][j] = static_cast<float>(GridTransformVDB[j][i]);
|
|
}
|
|
}
|
|
|
|
// Figure out the type/format of the grid
|
|
if (Grid->isType<FOpenVDBFloat1Grid>())
|
|
{
|
|
GridInfo.NumComponents = 1;
|
|
GridInfo.Type = EOpenVDBGridType::Float;
|
|
}
|
|
else if (Grid->isType<FOpenVDBFloat2Grid>())
|
|
{
|
|
GridInfo.NumComponents = 2;
|
|
GridInfo.Type = EOpenVDBGridType::Float2;
|
|
}
|
|
else if (Grid->isType<FOpenVDBFloat3Grid>())
|
|
{
|
|
GridInfo.NumComponents = 3;
|
|
GridInfo.Type = EOpenVDBGridType::Float3;
|
|
}
|
|
else if (Grid->isType<FOpenVDBFloat4Grid>())
|
|
{
|
|
GridInfo.NumComponents = 4;
|
|
GridInfo.Type = EOpenVDBGridType::Float4;
|
|
}
|
|
else if (Grid->isType<FOpenVDBDouble1Grid>())
|
|
{
|
|
GridInfo.NumComponents = 1;
|
|
GridInfo.Type = EOpenVDBGridType::Double;
|
|
}
|
|
else if (Grid->isType<FOpenVDBDouble2Grid>())
|
|
{
|
|
GridInfo.NumComponents = 2;
|
|
GridInfo.Type = EOpenVDBGridType::Double2;
|
|
}
|
|
else if (Grid->isType<FOpenVDBDouble3Grid>())
|
|
{
|
|
GridInfo.NumComponents = 3;
|
|
GridInfo.Type = EOpenVDBGridType::Double3;
|
|
}
|
|
else if (Grid->isType<FOpenVDBDouble4Grid>())
|
|
{
|
|
GridInfo.NumComponents = 4;
|
|
GridInfo.Type = EOpenVDBGridType::Double4;
|
|
}
|
|
|
|
if (bCreateStrings)
|
|
{
|
|
GridInfo.Name = Grid->getName().c_str();
|
|
|
|
FStringFormatOrderedArguments FormatArgs;
|
|
FormatArgs.Add(GridInfo.Index);
|
|
FormatArgs.Add(OpenVDBGridTypeToString(GridInfo.Type));
|
|
FormatArgs.Add(GridInfo.Name);
|
|
|
|
GridInfo.DisplayString = FString::Format(TEXT("{0}. Type: {1}, Name: \"{2}\""), FormatArgs);
|
|
}
|
|
|
|
return GridInfo;
|
|
}
|
|
|
|
#endif
|
|
|
|
bool IsOpenVDBGridValid(const FOpenVDBGridInfo& GridInfo, const FString& Filename)
|
|
{
|
|
if (GridInfo.VolumeActiveDim.X * GridInfo.VolumeActiveDim.Y * GridInfo.VolumeActiveDim.Z == 0)
|
|
{
|
|
// SVT_TODO we should gently handle that case
|
|
UE_LOG(LogSparseVolumeTextureOpenVDBUtility, Warning, TEXT("OpenVDB grid is empty due to volume size being 0: %s"), *Filename);
|
|
return false;
|
|
}
|
|
|
|
if (!GridInfo.bHasUniformVoxels)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureOpenVDBUtility, Warning, TEXT("OpenVDB importer cannot handle non uniform voxels: %s"), *Filename);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GetOpenVDBGridInfo(TArray<uint8>& SourceFile, bool bCreateStrings, TArray<FOpenVDBGridInfo>* OutGridInfo)
|
|
{
|
|
#if OPENVDB_AVAILABLE
|
|
FArrayUint8StreamBuf StreamBuf(SourceFile);
|
|
std::istream IStream(&StreamBuf);
|
|
openvdb::io::Stream Stream(IStream, false /*delayLoad*/);
|
|
|
|
openvdb::GridPtrVecPtr Grids = Stream.getGrids();
|
|
|
|
OutGridInfo->Empty(Grids->size());
|
|
|
|
uint32 GridIndex = 0;
|
|
for (openvdb::GridBase::Ptr& Grid : *Grids)
|
|
{
|
|
OutGridInfo->Add(GetOpenVDBGridInfo(Grid, GridIndex, bCreateStrings));
|
|
++GridIndex;
|
|
}
|
|
|
|
return true;
|
|
|
|
#endif // OPENVDB_AVAILABLE
|
|
return false;
|
|
}
|
|
|
|
static EPixelFormat GetMultiComponentFormat(ESparseVolumeAttributesFormat Format, uint32 NumComponents)
|
|
{
|
|
switch (Format)
|
|
{
|
|
case ESparseVolumeAttributesFormat::Unorm8:
|
|
{
|
|
switch (NumComponents)
|
|
{
|
|
case 1: return PF_R8;
|
|
case 2: return PF_R8G8;
|
|
case 3:
|
|
case 4: return PF_R8G8B8A8;
|
|
}
|
|
break;
|
|
}
|
|
case ESparseVolumeAttributesFormat::Float16:
|
|
{
|
|
switch (NumComponents)
|
|
{
|
|
case 1: return PF_R16F;
|
|
case 2: return PF_G16R16F;
|
|
case 3:
|
|
case 4: return PF_FloatRGBA;
|
|
}
|
|
break;
|
|
}
|
|
case ESparseVolumeAttributesFormat::Float32:
|
|
{
|
|
switch (NumComponents)
|
|
{
|
|
case 1: return PF_R32_FLOAT;
|
|
case 2: return PF_G32R32F;
|
|
case 3:
|
|
case 4: return PF_A32B32G32R32F;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return PF_Unknown;
|
|
}
|
|
|
|
static uint32 PackPageTableEntry(const FIntVector3& Coord)
|
|
{
|
|
// A page encodes the physical tile coord as unsigned int of 11 11 10 bits
|
|
// This means a page coord cannot be larger than 2047 for x and y and 1023 for z
|
|
// which mean we cannot have more than 2048*2048*1024 = 4 Giga tiles of 16^3 tiles.
|
|
uint32 Result = (Coord.X & 0x7FF) | ((Coord.Y & 0x7FF) << 11) | ((Coord.Z & 0x3FF) << 22);
|
|
return Result;
|
|
}
|
|
|
|
static FIntVector3 UnpackPageTableEntry(uint32 Packed)
|
|
{
|
|
FIntVector3 Result;
|
|
Result.X = Packed & 0x7FF;
|
|
Result.Y = (Packed >> 11) & 0x7FF;
|
|
Result.Z = (Packed >> 22) & 0x3FF;
|
|
return Result;
|
|
}
|
|
|
|
bool ConvertOpenVDBToSparseVolumeTexture(
|
|
TArray<uint8>& SourceFile,
|
|
FOpenVDBImportOptions& ImportOptions,
|
|
FOpenVDBToSVTConversionResult* OutResult,
|
|
bool bOverrideActiveMinMax,
|
|
FVector ActiveMin,
|
|
FVector ActiveMax)
|
|
{
|
|
#if OPENVDB_AVAILABLE
|
|
|
|
constexpr uint32 NumAttributesDescs = 2; // AttributesA and AttributesB, representing the two textures with voxel data
|
|
TStaticArray<FOpenVDBSparseVolumeAttributesDesc, 2>& Attributes = ImportOptions.Attributes;
|
|
|
|
// Compute some basic info about the number of components and which format to use
|
|
uint32 NumActualComponents[NumAttributesDescs] = {};
|
|
EPixelFormat MultiCompFormat[NumAttributesDescs] = {};
|
|
uint32 FormatSize[NumAttributesDescs] = {};
|
|
uint32 SingleComponentFormatSize[NumAttributesDescs] = {};
|
|
bool bNormalizedFormat[NumAttributesDescs] = {};
|
|
bool bHasValidSourceGrids[NumAttributesDescs] = {};
|
|
bool bAnySourceGridIndicesValid = false;
|
|
|
|
for (uint32 AttributesIdx = 0; AttributesIdx < NumAttributesDescs; ++AttributesIdx)
|
|
{
|
|
uint32 NumRequiredComponents = 0;
|
|
for (uint32 ComponentIdx = 0; ComponentIdx < 4; ++ComponentIdx)
|
|
{
|
|
if (Attributes[AttributesIdx].Mappings[ComponentIdx].SourceGridIndex != INDEX_NONE)
|
|
{
|
|
check(Attributes[AttributesIdx].Mappings[ComponentIdx].SourceComponentIndex != INDEX_NONE);
|
|
NumRequiredComponents = FMath::Max(ComponentIdx + 1, NumRequiredComponents);
|
|
bHasValidSourceGrids[AttributesIdx] = true;
|
|
bAnySourceGridIndicesValid = true;
|
|
}
|
|
}
|
|
|
|
if (bHasValidSourceGrids[AttributesIdx])
|
|
{
|
|
NumActualComponents[AttributesIdx] = NumRequiredComponents == 3 ? 4 : NumRequiredComponents; // We don't support formats with only 3 components
|
|
bNormalizedFormat[AttributesIdx] = Attributes[AttributesIdx].Format == ESparseVolumeAttributesFormat::Unorm8;
|
|
MultiCompFormat[AttributesIdx] = GetMultiComponentFormat(Attributes[AttributesIdx].Format, NumActualComponents[AttributesIdx]);
|
|
|
|
if (MultiCompFormat[AttributesIdx] == PF_Unknown)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureOpenVDBUtility, Warning, TEXT("SparseVolumeTexture is set to use an unsupported format: %i"), (int32)Attributes[AttributesIdx].Format);
|
|
return false;
|
|
}
|
|
|
|
FormatSize[AttributesIdx] = (uint32)GPixelFormats[(SIZE_T)MultiCompFormat[AttributesIdx]].BlockBytes;
|
|
SingleComponentFormatSize[AttributesIdx] = FormatSize[AttributesIdx] / NumActualComponents[AttributesIdx];
|
|
}
|
|
}
|
|
|
|
// All source grid indices are INDEX_NONE, so nothing was selected for import
|
|
if (!bAnySourceGridIndicesValid)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureOpenVDBUtility, Warning, TEXT("SparseVolumeTexture has all components set to <None>, so there is nothing to import."));
|
|
return false;
|
|
}
|
|
|
|
// Load file
|
|
FArrayUint8StreamBuf StreamBuf(SourceFile);
|
|
std::istream IStream(&StreamBuf);
|
|
openvdb::io::Stream Stream(IStream, false /*delayLoad*/);
|
|
|
|
// Check that the source grid indices are valid
|
|
openvdb::GridPtrVecPtr Grids = Stream.getGrids();
|
|
const size_t NumSourceGrids = Grids ? Grids->size() : 0;
|
|
for (const FOpenVDBSparseVolumeAttributesDesc& AttributesDesc : ImportOptions.Attributes)
|
|
{
|
|
for (const FOpenVDBSparseVolumeComponentMapping& Mapping : AttributesDesc.Mappings)
|
|
{
|
|
const int32 SourceGridIndex = Mapping.SourceGridIndex;
|
|
if (SourceGridIndex != INDEX_NONE && SourceGridIndex >= (int32)NumSourceGrids)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureOpenVDBUtility, Warning, TEXT("SparseVolumeTexture has invalid index into the array of grids in the source file: %i"), SourceGridIndex);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
FSparseVolumeAssetHeader& Header = *OutResult->Header;
|
|
Header.AttributesAFormat = MultiCompFormat[0];
|
|
Header.AttributesBFormat = MultiCompFormat[1];
|
|
Header.SourceVolumeResolution = FIntVector::ZeroValue;
|
|
|
|
FIntVector SmallestAABBMin = FIntVector(INT32_MAX);
|
|
|
|
// Compute per source grid data of up to 4 different grids (one per component)
|
|
TArray<TSharedPtr<IOpenVDBGridAdapterBase>> UniqueGridAdapters;
|
|
UniqueGridAdapters.SetNum((int32)NumSourceGrids);
|
|
TSharedPtr<IOpenVDBGridAdapterBase> GridAdapters[NumAttributesDescs][4]{};
|
|
float GridBackgroundValues[NumAttributesDescs][4]{};
|
|
float NormalizeScale[NumAttributesDescs][4]{};
|
|
float NormalizeBias[NumAttributesDescs][4]{};
|
|
for (uint32 AttributesIdx = 0; AttributesIdx < NumAttributesDescs; ++AttributesIdx)
|
|
{
|
|
for (uint32 CompIdx = 0; CompIdx < 4; ++CompIdx)
|
|
{
|
|
NormalizeScale[AttributesIdx][CompIdx] = 1.0f;
|
|
const uint32 SourceGridIndex = Attributes[AttributesIdx].Mappings[CompIdx].SourceGridIndex;
|
|
const uint32 SourceComponentIndex = Attributes[AttributesIdx].Mappings[CompIdx].SourceComponentIndex;
|
|
if (SourceGridIndex == INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
openvdb::GridBase::Ptr GridBase = (*Grids)[SourceGridIndex];
|
|
|
|
// Try to reuse adapters. Internally they use caching to accelerate read accesses,
|
|
// so using three different adapters to access the three components of a single grid would be wasteful.
|
|
if (UniqueGridAdapters[SourceGridIndex] == nullptr)
|
|
{
|
|
UniqueGridAdapters[SourceGridIndex] = CreateOpenVDBGridAdapter(GridBase);
|
|
if (!UniqueGridAdapters[SourceGridIndex])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
GridAdapters[AttributesIdx][CompIdx] = UniqueGridAdapters[SourceGridIndex];
|
|
|
|
FOpenVDBGridInfo GridInfo = GetOpenVDBGridInfo(GridBase, 0, false);
|
|
if (!IsOpenVDBGridValid(GridInfo, TEXT("")))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (bOverrideActiveMinMax)
|
|
{
|
|
GridInfo.VolumeActiveAABBMin = ActiveMin;
|
|
GridInfo.VolumeActiveAABBMax = ActiveMax;
|
|
GridInfo.VolumeActiveDim = ActiveMax + 1 - ActiveMin;
|
|
}
|
|
|
|
Header.SourceVolumeResolution.X = FMath::Max(Header.SourceVolumeResolution.X, GridInfo.VolumeActiveDim.X);
|
|
Header.SourceVolumeResolution.Y = FMath::Max(Header.SourceVolumeResolution.Y, GridInfo.VolumeActiveDim.Y);
|
|
Header.SourceVolumeResolution.Z = FMath::Max(Header.SourceVolumeResolution.Z, GridInfo.VolumeActiveDim.Z);
|
|
SmallestAABBMin.X = FMath::Min(SmallestAABBMin.X, GridInfo.VolumeActiveAABBMin.X);
|
|
SmallestAABBMin.Y = FMath::Min(SmallestAABBMin.Y, GridInfo.VolumeActiveAABBMin.Y);
|
|
SmallestAABBMin.Z = FMath::Min(SmallestAABBMin.Z, GridInfo.VolumeActiveAABBMin.Z);
|
|
|
|
GridBackgroundValues[AttributesIdx][CompIdx] = GridAdapters[AttributesIdx][CompIdx]->GetBackgroundValue(SourceComponentIndex);
|
|
if (bNormalizedFormat[AttributesIdx] && Attributes[AttributesIdx].bRemapInputForUnorm)
|
|
{
|
|
float MinVal = 0.0f;
|
|
float MaxVal = 0.0f;
|
|
GridAdapters[AttributesIdx][CompIdx]->GetMinMaxValue(SourceComponentIndex, &MinVal, &MaxVal);
|
|
const float Diff = MaxVal - MinVal;
|
|
NormalizeScale[AttributesIdx][CompIdx] = MaxVal > SMALL_NUMBER ? (1.0f / Diff) : 1.0f;
|
|
NormalizeBias[AttributesIdx][CompIdx] = -MinVal * NormalizeScale[AttributesIdx][CompIdx];
|
|
}
|
|
}
|
|
}
|
|
OutResult->Header->SourceVolumeAABBMin = SmallestAABBMin;
|
|
|
|
FIntVector3 PageTableVolumeResolution = FIntVector3(
|
|
FMath::DivideAndRoundUp(Header.SourceVolumeResolution.X, SPARSE_VOLUME_TILE_RES),
|
|
FMath::DivideAndRoundUp(Header.SourceVolumeResolution.Y, SPARSE_VOLUME_TILE_RES),
|
|
FMath::DivideAndRoundUp(Header.SourceVolumeResolution.Z, SPARSE_VOLUME_TILE_RES));
|
|
if (PageTableVolumeResolution.X > SVTMaxVolumeTextureDim
|
|
|| PageTableVolumeResolution.Y > SVTMaxVolumeTextureDim
|
|
|| PageTableVolumeResolution.Z > SVTMaxVolumeTextureDim)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureOpenVDBUtility, Warning, TEXT("SparseVolumeTexture page table texture dimensions exceed limit (%ix%ix%i): %ix%ix%i"), SVTMaxVolumeTextureDim, SVTMaxVolumeTextureDim, SVTMaxVolumeTextureDim, PageTableVolumeResolution.X, PageTableVolumeResolution.Y, PageTableVolumeResolution.Z);
|
|
return false;
|
|
}
|
|
|
|
Header.PageTableVolumeResolution = PageTableVolumeResolution;
|
|
Header.TileDataVolumeResolution = FIntVector::ZeroValue; // unknown for now
|
|
|
|
// Tag all pages with valid data
|
|
TBitArray PagesWithData(false, Header.PageTableVolumeResolution.Z * Header.PageTableVolumeResolution.Y * Header.PageTableVolumeResolution.X);
|
|
for (uint32 GridIdx = 0; GridIdx < NumSourceGrids; ++GridIdx)
|
|
{
|
|
if (!UniqueGridAdapters[GridIdx])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UniqueGridAdapters[GridIdx]->IteratePhysical(
|
|
[&](const FIntVector3& Coord, uint32 NumComponents, float* VoxelValues)
|
|
{
|
|
// Check if the source grid component is used at all
|
|
bool bValueIsUsed = false;
|
|
for (uint32 AttributesIdx = 0; AttributesIdx < NumAttributesDescs && !bValueIsUsed; ++AttributesIdx)
|
|
{
|
|
for (uint32 DstCompIdx = 0; DstCompIdx < NumActualComponents[AttributesIdx] && !bValueIsUsed; ++DstCompIdx)
|
|
{
|
|
for (uint32 SrcCompIdx = 0; SrcCompIdx < NumComponents && !bValueIsUsed; ++SrcCompIdx)
|
|
{
|
|
const bool bIsBackgroundValue = (VoxelValues[SrcCompIdx] == GridBackgroundValues[AttributesIdx][DstCompIdx]);
|
|
bValueIsUsed |= !bIsBackgroundValue && (Attributes[AttributesIdx].Mappings[DstCompIdx].SourceGridIndex == GridIdx) && (Attributes[AttributesIdx].Mappings[DstCompIdx].SourceComponentIndex == SrcCompIdx);
|
|
}
|
|
}
|
|
}
|
|
if (!bValueIsUsed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FIntVector3 GridCoord = Coord - SmallestAABBMin;
|
|
check(GridCoord.X >= 0 && GridCoord.Y >= 0 && GridCoord.Z >= 0);
|
|
check(GridCoord.X < Header.SourceVolumeResolution.X && GridCoord.Y < Header.SourceVolumeResolution.Y && GridCoord.Z < Header.SourceVolumeResolution.Z);
|
|
const FIntVector3 PageCoord = GridCoord / SPARSE_VOLUME_TILE_RES;
|
|
check(PageCoord.X >= 0 && PageCoord.Y >= 0 && PageCoord.Z >= 0);
|
|
check(PageCoord.X < Header.PageTableVolumeResolution.X && PageCoord.Y < Header.PageTableVolumeResolution.Y && PageCoord.Z < Header.PageTableVolumeResolution.Z);
|
|
|
|
const int32 PageIndex = PageCoord.Z * (Header.PageTableVolumeResolution.Y * Header.PageTableVolumeResolution.X) + PageCoord.Y * Header.PageTableVolumeResolution.X + PageCoord.X;
|
|
PagesWithData[PageIndex] = true;
|
|
});
|
|
}
|
|
|
|
// Allocate some memory for temp data (worst case)
|
|
TArray<FIntVector3> LinearAllocatedPages;
|
|
LinearAllocatedPages.SetNum(Header.PageTableVolumeResolution.X * Header.PageTableVolumeResolution.Y * Header.PageTableVolumeResolution.Z);
|
|
|
|
// Go over each potential page from the source data and push allocate it if it has any data.
|
|
// Otherwise point to the default empty page.
|
|
bool bAnyEmptyPageExists = false;
|
|
uint32 NumAllocatedPages = 0;
|
|
for (int32_t PageZ = 0; PageZ < Header.PageTableVolumeResolution.Z; ++PageZ)
|
|
{
|
|
for (int32_t PageY = 0; PageY < Header.PageTableVolumeResolution.Y; ++PageY)
|
|
{
|
|
for (int32_t PageX = 0; PageX < Header.PageTableVolumeResolution.X; ++PageX)
|
|
{
|
|
const int32 PageIndex = PageZ * (Header.PageTableVolumeResolution.Y * Header.PageTableVolumeResolution.X) + PageY * Header.PageTableVolumeResolution.X + PageX;
|
|
bool bHasAnyData = PagesWithData[PageIndex];
|
|
if (bHasAnyData)
|
|
{
|
|
LinearAllocatedPages[NumAllocatedPages] = FIntVector3(PageX, PageY, PageZ);
|
|
NumAllocatedPages++;
|
|
}
|
|
bAnyEmptyPageExists |= !bHasAnyData;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute Page and Tile VolumeResolution from allocated pages
|
|
const uint32 EffectivelyAllocatedPageEntries = NumAllocatedPages + (bAnyEmptyPageExists ? 1 : 0);
|
|
uint32 TileVolumeResolutionCube = 1;
|
|
while (TileVolumeResolutionCube * TileVolumeResolutionCube * TileVolumeResolutionCube < EffectivelyAllocatedPageEntries)
|
|
{
|
|
TileVolumeResolutionCube++; // We use a simple loop to compute the minimum resolution of a cube to store all the tile data
|
|
}
|
|
Header.TileDataVolumeResolution = FIntVector3(TileVolumeResolutionCube, TileVolumeResolutionCube, TileVolumeResolutionCube);
|
|
while (Header.TileDataVolumeResolution.X * Header.TileDataVolumeResolution.Y * (Header.TileDataVolumeResolution.Z - 1) > int32(EffectivelyAllocatedPageEntries))
|
|
{
|
|
Header.TileDataVolumeResolution.Z--; // We then trim an edge to get back space.
|
|
}
|
|
const FIntVector3 TileCoordResolution = Header.TileDataVolumeResolution;
|
|
Header.TileDataVolumeResolution = Header.TileDataVolumeResolution * SPARSE_VOLUME_TILE_RES;
|
|
if (Header.TileDataVolumeResolution.X > SVTMaxVolumeTextureDim
|
|
|| Header.TileDataVolumeResolution.Y > SVTMaxVolumeTextureDim
|
|
|| Header.TileDataVolumeResolution.Z > SVTMaxVolumeTextureDim)
|
|
{
|
|
UE_LOG(LogSparseVolumeTextureOpenVDBUtility, Warning, TEXT("SparseVolumeTexture tile data texture dimensions exceed limit (%ix%ix%i): %ix%ix%i"), SVTMaxVolumeTextureDim, SVTMaxVolumeTextureDim, SVTMaxVolumeTextureDim, Header.TileDataVolumeResolution.X, Header.TileDataVolumeResolution.Y, Header.TileDataVolumeResolution.Z);
|
|
return false;
|
|
}
|
|
|
|
// Initialise the SparseVolumeTexture page and tile.
|
|
OutResult->PageTable->SetNumZeroed(Header.PageTableVolumeResolution.X * Header.PageTableVolumeResolution.Y * Header.PageTableVolumeResolution.Z);
|
|
OutResult->PhysicalTileDataA->SetNumZeroed(Header.TileDataVolumeResolution.X * Header.TileDataVolumeResolution.Y * Header.TileDataVolumeResolution.Z * FormatSize[0] * (bHasValidSourceGrids[0] ? 1 : 0));
|
|
OutResult->PhysicalTileDataB->SetNumZeroed(Header.TileDataVolumeResolution.X * Header.TileDataVolumeResolution.Y * Header.TileDataVolumeResolution.Z * FormatSize[1] * (bHasValidSourceGrids[1] ? 1 : 0));
|
|
uint32* PageTablePtr = OutResult->PageTable->GetData();
|
|
uint8* PhysicalTileDataPtrs[] = { OutResult->PhysicalTileDataA->GetData(), OutResult->PhysicalTileDataB->GetData() };
|
|
|
|
// Generate page table and tile volume data by splatting the data
|
|
{
|
|
FIntVector3 DstTileCoord = FIntVector3::ZeroValue;
|
|
auto GoToNextTileCoord = [&]()
|
|
{
|
|
DstTileCoord.X++;
|
|
if (DstTileCoord.X >= TileCoordResolution.X)
|
|
{
|
|
DstTileCoord.X = 0;
|
|
DstTileCoord.Y++;
|
|
}
|
|
if (DstTileCoord.Y >= TileCoordResolution.Y)
|
|
{
|
|
DstTileCoord.Y = 0;
|
|
DstTileCoord.Z++;
|
|
}
|
|
};
|
|
|
|
// Add an empty tile is needed, reserve slot at coord 0
|
|
if (bAnyEmptyPageExists)
|
|
{
|
|
// PageTable is all cleared to zero, simply skip a tile
|
|
GoToNextTileCoord();
|
|
}
|
|
|
|
for (uint32 i = 0; i < NumAllocatedPages; ++i)
|
|
{
|
|
FIntVector3 PageCoordToSplat = LinearAllocatedPages[i];
|
|
uint32 DestinationTileCoord32bit = PackPageTableEntry(DstTileCoord);
|
|
|
|
// Setup the page table entry
|
|
PageTablePtr
|
|
[
|
|
PageCoordToSplat.Z * Header.PageTableVolumeResolution.X * Header.PageTableVolumeResolution.Y +
|
|
PageCoordToSplat.Y * Header.PageTableVolumeResolution.X +
|
|
PageCoordToSplat.X
|
|
] = DestinationTileCoord32bit;
|
|
|
|
// Set the next tile to be written to
|
|
GoToNextTileCoord();
|
|
}
|
|
}
|
|
|
|
for (uint32 GridIdx = 0; GridIdx < NumSourceGrids; ++GridIdx)
|
|
{
|
|
if (!UniqueGridAdapters[GridIdx])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UniqueGridAdapters[GridIdx]->IteratePhysical(
|
|
[&](const FIntVector3& Coord, uint32 NumComponents, float* VoxelValues)
|
|
{
|
|
const FIntVector3 GridCoord = Coord - SmallestAABBMin;
|
|
check(GridCoord.X >= 0 && GridCoord.Y >= 0 && GridCoord.Z >= 0);
|
|
check(GridCoord.X < Header.SourceVolumeResolution.X&& GridCoord.Y < Header.SourceVolumeResolution.Y&& GridCoord.Z < Header.SourceVolumeResolution.Z);
|
|
const FIntVector3 PageCoord = GridCoord / SPARSE_VOLUME_TILE_RES;
|
|
check(PageCoord.X >= 0 && PageCoord.Y >= 0 && PageCoord.Z >= 0);
|
|
check(PageCoord.X < Header.PageTableVolumeResolution.X&& PageCoord.Y < Header.PageTableVolumeResolution.Y&& PageCoord.Z < Header.PageTableVolumeResolution.Z);
|
|
|
|
const int32 PageIndex = PageCoord.Z * (Header.PageTableVolumeResolution.Y * Header.PageTableVolumeResolution.X) + PageCoord.Y * Header.PageTableVolumeResolution.X + PageCoord.X;
|
|
|
|
if (!PagesWithData[PageIndex])
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FIntVector3 DstTileCoord = UnpackPageTableEntry(PageTablePtr[PageIndex]);
|
|
const FIntVector3 TileLocalCoord = GridCoord % SPARSE_VOLUME_TILE_RES;
|
|
|
|
// Check all output components and splat VoxelValue if they map to this source grid/component
|
|
for (uint32 AttributesIdx = 0; AttributesIdx < NumAttributesDescs; ++AttributesIdx)
|
|
{
|
|
for (uint32 DstCompIdx = 0; DstCompIdx < NumActualComponents[AttributesIdx]; ++DstCompIdx)
|
|
{
|
|
for (uint32 SrcCompIdx = 0; SrcCompIdx < NumComponents; ++SrcCompIdx)
|
|
{
|
|
if ((Attributes[AttributesIdx].Mappings[DstCompIdx].SourceGridIndex != GridIdx) || (Attributes[AttributesIdx].Mappings[DstCompIdx].SourceComponentIndex != SrcCompIdx))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const float VoxelValueNormalized = FMath::Clamp(VoxelValues[SrcCompIdx] * NormalizeScale[AttributesIdx][DstCompIdx] + NormalizeBias[AttributesIdx][DstCompIdx], 0.0f, 1.0f);
|
|
|
|
const size_t DstVoxelIndex =
|
|
(DstTileCoord.Z * SPARSE_VOLUME_TILE_RES + TileLocalCoord.Z) * Header.TileDataVolumeResolution.X * Header.TileDataVolumeResolution.Y +
|
|
(DstTileCoord.Y * SPARSE_VOLUME_TILE_RES + TileLocalCoord.Y) * Header.TileDataVolumeResolution.X +
|
|
(DstTileCoord.X * SPARSE_VOLUME_TILE_RES + TileLocalCoord.X);
|
|
const size_t DstCoord = DstVoxelIndex * FormatSize[AttributesIdx] + DstCompIdx * SingleComponentFormatSize[AttributesIdx];
|
|
|
|
switch (Attributes[AttributesIdx].Format)
|
|
{
|
|
case ESparseVolumeAttributesFormat::Unorm8:
|
|
{
|
|
PhysicalTileDataPtrs[AttributesIdx][DstCoord] = uint8(VoxelValueNormalized * 255.0f);
|
|
break;
|
|
}
|
|
case ESparseVolumeAttributesFormat::Float16:
|
|
{
|
|
const uint16 VoxelValue16FEncoded = FFloat16(VoxelValues[SrcCompIdx]).Encoded;
|
|
*((uint16*)(&PhysicalTileDataPtrs[AttributesIdx][DstCoord])) = VoxelValue16FEncoded;
|
|
break;
|
|
}
|
|
case ESparseVolumeAttributesFormat::Float32:
|
|
{
|
|
*((float*)(&PhysicalTileDataPtrs[AttributesIdx][DstCoord])) = VoxelValues[SrcCompIdx];
|
|
break;
|
|
}
|
|
default:
|
|
checkNoEntry();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif // OPENVDB_AVAILABLE
|
|
}
|
|
|
|
const TCHAR* OpenVDBGridTypeToString(EOpenVDBGridType Type)
|
|
{
|
|
switch (Type)
|
|
{
|
|
case EOpenVDBGridType::Float:
|
|
return TEXT("Float");
|
|
case EOpenVDBGridType::Float2:
|
|
return TEXT("Float2");
|
|
case EOpenVDBGridType::Float3:
|
|
return TEXT("Float3");
|
|
case EOpenVDBGridType::Float4:
|
|
return TEXT("Float4");
|
|
case EOpenVDBGridType::Double:
|
|
return TEXT("Double");
|
|
case EOpenVDBGridType::Double2:
|
|
return TEXT("Double2");
|
|
case EOpenVDBGridType::Double3:
|
|
return TEXT("Double3");
|
|
case EOpenVDBGridType::Double4:
|
|
return TEXT("Double4");
|
|
default:
|
|
return TEXT("Unknown");
|
|
}
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|